Index: head/graphics/Makefile =================================================================== --- head/graphics/Makefile (revision 468803) +++ head/graphics/Makefile (revision 468804) @@ -1,1155 +1,1156 @@ # $FreeBSD$ # COMMENT = Graphics tools and libraries SUBDIR += 4va SUBDIR += Coin SUBDIR += EZWGL SUBDIR += GraphicsMagick SUBDIR += Hermes SUBDIR += IPA SUBDIR += ImageMagick SUBDIR += ImageMagick-nox11 SUBDIR += ImageMagick7 SUBDIR += ImageMagick7-nox11 SUBDIR += O2-tools SUBDIR += OpenEXR SUBDIR += R-cran-DiagrammeR SUBDIR += R-cran-GDD SUBDIR += R-cran-RColorBrewer SUBDIR += R-cran-colorspace SUBDIR += R-cran-diagram SUBDIR += R-cran-dichromat SUBDIR += R-cran-dygraphs SUBDIR += R-cran-ggplot2 SUBDIR += R-cran-gridBase SUBDIR += R-cran-gridExtra SUBDIR += R-cran-munsell SUBDIR += R-cran-pixmap SUBDIR += R-cran-png SUBDIR += R-cran-qcc SUBDIR += R-cran-rgdal SUBDIR += R-cran-rtiff SUBDIR += R-cran-scales SUBDIR += R-cran-shape SUBDIR += R-cran-viridis SUBDIR += R-cran-viridisLite SUBDIR += R-cran-visNetwork SUBDIR += SciPlot SUBDIR += a2png SUBDIR += aalib SUBDIR += aaphoto SUBDIR += acidwarp SUBDIR += aeskulap SUBDIR += agave SUBDIR += agg SUBDIR += alembic SUBDIR += alpng SUBDIR += amide SUBDIR += ampasACES-container SUBDIR += ampasCTL SUBDIR += animorph SUBDIR += ansilove SUBDIR += aoi SUBDIR += apngasm SUBDIR += apngdis SUBDIR += appleseed SUBDIR += apvlv SUBDIR += aqsis SUBDIR += argyllcms SUBDIR += asciio SUBDIR += aseprite SUBDIR += atril SUBDIR += atril-lite SUBDIR += autopano-sift-c SUBDIR += autoq3d SUBDIR += autotrace SUBDIR += aview SUBDIR += ayam SUBDIR += azpainter SUBDIR += backfract SUBDIR += barbecue SUBDIR += barcode SUBDIR += batik SUBDIR += blender SUBDIR += blender-doc SUBDIR += bmeps SUBDIR += bmp2html SUBDIR += box SUBDIR += boxer SUBDIR += bugle SUBDIR += burplex SUBDIR += c-a-i-r SUBDIR += cadubi SUBDIR += caffe SUBDIR += cairo SUBDIR += cairo-reference SUBDIR += cairomm SUBDIR += cal3d SUBDIR += camera SUBDIR += camerakit SUBDIR += cbrpager SUBDIR += cbview SUBDIR += cbviewer SUBDIR += cegui SUBDIR += cenon SUBDIR += cfdg SUBDIR += chbg SUBDIR += cimg SUBDIR += cinepaint SUBDIR += cloudcompare SUBDIR += clutter SUBDIR += clutter-gtk SUBDIR += clutter-gtk3 SUBDIR += cluttermm SUBDIR += cogl SUBDIR += colmap SUBDIR += colord SUBDIR += colord-gtk SUBDIR += comical SUBDIR += commons-utilities SUBDIR += compupic SUBDIR += converseen SUBDIR += copperspice SUBDIR += cosmoplayer SUBDIR += cptutils SUBDIR += crw SUBDIR += cthumb SUBDIR += cuneiform SUBDIR += curator SUBDIR += cuttlefish SUBDIR += danpei SUBDIR += darknock SUBDIR += darktable SUBDIR += dataplot SUBDIR += dc20pack SUBDIR += dcp2icc SUBDIR += dcraw SUBDIR += dcraw-m SUBDIR += deegree-csw SUBDIR += deegree-igeoportal SUBDIR += deegree-wcs SUBDIR += deegree-wfs SUBDIR += deegree-wms SUBDIR += deegree-wps SUBDIR += deegree-wpvs SUBDIR += delaboratory SUBDIR += derelict-gl3 SUBDIR += devil SUBDIR += dia SUBDIR += diffpdf SUBDIR += digikam SUBDIR += digikam-kde4 SUBDIR += digikam-kde4-doc SUBDIR += digikam-kde4-l10n SUBDIR += dilay SUBDIR += ditaa SUBDIR += djview4 SUBDIR += djview4-qt4 SUBDIR += djvulibre SUBDIR += dmtx-utils SUBDIR += drawpile SUBDIR += driconf SUBDIR += drm-next-kmod SUBDIR += drm-stable-kmod SUBDIR += dspdfviewer SUBDIR += duhdraw SUBDIR += dynamechs SUBDIR += easypaint SUBDIR += edje_viewer SUBDIR += electrix SUBDIR += embree SUBDIR += enblend SUBDIR += entangle SUBDIR += eog SUBDIR += eog-plugins SUBDIR += eom SUBDIR += eos-movrec SUBDIR += epdfview SUBDIR += ephoto SUBDIR += epix SUBDIR += eps2png SUBDIR += epstool SUBDIR += eterm-bg SUBDIR += evince SUBDIR += evince-lite SUBDIR += evolvotron SUBDIR += exact-image SUBDIR += exif SUBDIR += exifprobe SUBDIR += exiftags SUBDIR += exiftran SUBDIR += exiv2 SUBDIR += exrtools SUBDIR += facedetect SUBDIR += farbfeld SUBDIR += feh SUBDIR += fig2sxd SUBDIR += figurine SUBDIR += flam3 SUBDIR += flasm SUBDIR += flphoto SUBDIR += fly SUBDIR += fortytwo SUBDIR += fotofix SUBDIR += fotoxx SUBDIR += founts SUBDIR += fpc-cairo SUBDIR += fpc-graph SUBDIR += fpc-hermes SUBDIR += fpc-imagemagick SUBDIR += fpc-libgd SUBDIR += fpc-libpng SUBDIR += fpc-ncurses SUBDIR += fpc-opengl SUBDIR += fpc-pasjpeg SUBDIR += fpc-proj4 SUBDIR += fpc-rsvg SUBDIR += fpc-svgalib SUBDIR += fracplanet SUBDIR += fraqtive SUBDIR += freeglut SUBDIR += freeimage SUBDIR += frei0r SUBDIR += frei0r-plugins SUBDIR += frei0r-plugins-gavl SUBDIR += frei0r-plugins-opencv SUBDIR += frogr SUBDIR += ftgl SUBDIR += fujiplay SUBDIR += fusefs-gphotofs SUBDIR += fv SUBDIR += fyre SUBDIR += g2 SUBDIR += gauche-gl SUBDIR += gcolor SUBDIR += gcolor2 SUBDIR += gcolor3 SUBDIR += gd SUBDIR += gdal SUBDIR += gdchart SUBDIR += gdk-pixbuf SUBDIR += gdk-pixbuf2 SUBDIR += gdtclft SUBDIR += geeqie SUBDIR += gegl SUBDIR += gegl3 SUBDIR += generic_image_decoder SUBDIR += geoapi SUBDIR += geomorph SUBDIR += geomview SUBDIR += geos SUBDIR += geoserver SUBDIR += gexiv2 SUBDIR += giblib SUBDIR += gif2png SUBDIR += giflib SUBDIR += gifmerge SUBDIR += gifsicle SUBDIR += giftool SUBDIR += gimageview SUBDIR += gimmage SUBDIR += gimp SUBDIR += gimp-app SUBDIR += gimp-beautify-plugin SUBDIR += gimp-data-extras SUBDIR += gimp-ez-perspective-plugin SUBDIR += gimp-focusblur-plugin SUBDIR += gimp-gmic-plugin SUBDIR += gimp-help SUBDIR += gimp-jagged-border-script SUBDIR += gimp-lensfun-plugin SUBDIR += gimp-lqr-plugin SUBDIR += gimp-manual-html SUBDIR += gimp-refocus-plugin SUBDIR += gimp-resynthesizer SUBDIR += gimp-save-for-web SUBDIR += gimp-wavelet-decompose-plugin SUBDIR += gimp-wavelet-denoise-plugin SUBDIR += gimp-wavelet-sharpen-plugin SUBDIR += gimpfx-foundry SUBDIR += giram SUBDIR += gkrellkam2 SUBDIR += gle SUBDIR += gle-graphics SUBDIR += glew SUBDIR += glexcess SUBDIR += glfw SUBDIR += glfw2 SUBDIR += glitz SUBDIR += gliv SUBDIR += glosm SUBDIR += glpng SUBDIR += gltt SUBDIR += gmic-qt SUBDIR += gmt SUBDIR += gnash SUBDIR += gnofract4d SUBDIR += gnome-color-manager SUBDIR += gnome-video-effects SUBDIR += gnustep-slideshow SUBDIR += gnustep-slideshowkit SUBDIR += gocr SUBDIR += goocanvas SUBDIR += goocanvas2 SUBDIR += goocanvasmm2 SUBDIR += goom SUBDIR += gource SUBDIR += goxel SUBDIR += gpaint SUBDIR += gphoto2 SUBDIR += gpicview SUBDIR += gpsmanshp SUBDIR += gpu-firmware-kmod SUBDIR += gracula SUBDIR += grads SUBDIR += grafx2 SUBDIR += graphene SUBDIR += graphite2 SUBDIR += graphopt SUBDIR += graphos SUBDIR += graphviz SUBDIR += grx SUBDIR += gscan2pdf SUBDIR += gsculpt SUBDIR += gstreamer-plugins-aalib SUBDIR += gstreamer-plugins-cairo SUBDIR += gstreamer-plugins-gdkpixbuf SUBDIR += gstreamer-plugins-gl SUBDIR += gstreamer-plugins-jpeg SUBDIR += gstreamer-plugins-libcaca SUBDIR += gstreamer-plugins-libpng SUBDIR += gstreamer-plugins-libvisual SUBDIR += gstreamer-plugins-opencv SUBDIR += gstreamer1-plugins-aalib SUBDIR += gstreamer1-plugins-cairo SUBDIR += gstreamer1-plugins-gdkpixbuf SUBDIR += gstreamer1-plugins-gl SUBDIR += gstreamer1-plugins-jpeg SUBDIR += gstreamer1-plugins-kms SUBDIR += gstreamer1-plugins-libcaca SUBDIR += gstreamer1-plugins-libvisual SUBDIR += gstreamer1-plugins-opencv SUBDIR += gstreamer1-plugins-openexr SUBDIR += gstreamer1-plugins-openjpeg SUBDIR += gstreamer1-plugins-png SUBDIR += gstreamer1-plugins-rsvg SUBDIR += gstreamer1-plugins-webp SUBDIR += gstreamer1-plugins-zbar SUBDIR += gthumb SUBDIR += gtimelapse SUBDIR += gtk-update-icon-cache SUBDIR += gtkam SUBDIR += gts SUBDIR += guetzli SUBDIR += guilib SUBDIR += gwenview SUBDIR += gwenview-kde4 SUBDIR += gx SUBDIR += hdr_tools SUBDIR += hiptext SUBDIR += hobbes-icons-xpm SUBDIR += hppsmtools SUBDIR += hs-HGL SUBDIR += hs-JuicyPixels SUBDIR += hs-cairo SUBDIR += hs-dia-base SUBDIR += hs-dia-functions SUBDIR += hs-graphviz SUBDIR += hs-svgcairo SUBDIR += hugin SUBDIR += icat SUBDIR += icc-profiles-adobe-cs4 SUBDIR += icc-profiles-basiccolor SUBDIR += icc-profiles-openicc SUBDIR += iccexamin SUBDIR += iccxml SUBDIR += icon-slicer SUBDIR += icontact SUBDIR += icoutils SUBDIR += ida SUBDIR += iec16022 SUBDIR += iiview SUBDIR += ilmbase SUBDIR += imageindex SUBDIR += imageviewer SUBDIR += imageworsener SUBDIR += imc SUBDIR += imgtops SUBDIR += imgv SUBDIR += imlib2 SUBDIR += imlib2_loaders SUBDIR += impressive SUBDIR += imv SUBDIR += inkscape SUBDIR += instant-meshes SUBDIR += intel-backlight SUBDIR += intergif SUBDIR += inventor SUBDIR += ipe SUBDIR += jalbum SUBDIR += jasper SUBDIR += jave6 SUBDIR += jbig2dec SUBDIR += jbigkit SUBDIR += jdraw SUBDIR += jgraph SUBDIR += jhead SUBDIR += jogamp-jogl SUBDIR += jogl SUBDIR += jp2a SUBDIR += jpatch SUBDIR += jpeg SUBDIR += jpeg-turbo SUBDIR += jpeginfo SUBDIR += jpegoptim SUBDIR += jpg2pdf SUBDIR += jpgraph2 SUBDIR += jpgtn SUBDIR += jslice SUBDIR += kamera SUBDIR += kamera-kde4 SUBDIR += kcolorchooser SUBDIR += kcolorchooser-kde4 SUBDIR += kcoloredit SUBDIR += kdegraphics SUBDIR += kdegraphics-kde4 SUBDIR += kdegraphics-mobipocket SUBDIR += kdegraphics-mobipocket-kde4 SUBDIR += kdegraphics-strigi-analyzer-kde4 SUBDIR += kdegraphics-svgpart SUBDIR += kdegraphics-svgpart-kde4 SUBDIR += kdegraphics-thumbnailers SUBDIR += kdegraphics-thumbnailers-kde4 SUBDIR += kdiagram SUBDIR += kf5-kimageformats SUBDIR += kf5-kplotting SUBDIR += kf5-prison SUBDIR += kgraphviewer SUBDIR += kiconedit SUBDIR += kimagemapeditor SUBDIR += kipi-plugin-acquireimages SUBDIR += kipi-plugin-advancedslideshow SUBDIR += kipi-plugin-batchprocess SUBDIR += kipi-plugin-calendar SUBDIR += kipi-plugin-debianscreenshots SUBDIR += kipi-plugin-dngconverter SUBDIR += kipi-plugin-dropbox SUBDIR += kipi-plugin-expoblending SUBDIR += kipi-plugin-facebook SUBDIR += kipi-plugin-flashexport SUBDIR += kipi-plugin-flickrexport SUBDIR += kipi-plugin-galleryexport SUBDIR += kipi-plugin-googleservices SUBDIR += kipi-plugin-gpssync SUBDIR += kipi-plugin-htmlexport SUBDIR += kipi-plugin-imageshackexport SUBDIR += kipi-plugin-imageviewer SUBDIR += kipi-plugin-imgurexport SUBDIR += kipi-plugin-ipodexport SUBDIR += kipi-plugin-jalbumexport SUBDIR += kipi-plugin-jpeglossless SUBDIR += kipi-plugin-kioexport SUBDIR += kipi-plugin-kmlexport SUBDIR += kipi-plugin-kopete SUBDIR += kipi-plugin-mediawiki SUBDIR += kipi-plugin-metadataedit SUBDIR += kipi-plugin-panorama SUBDIR += kipi-plugin-photolayoutseditor SUBDIR += kipi-plugin-piwigoexport SUBDIR += kipi-plugin-printimages SUBDIR += kipi-plugin-rajceexport SUBDIR += kipi-plugin-rawconverter SUBDIR += kipi-plugin-removeredeyes SUBDIR += kipi-plugin-sendimages SUBDIR += kipi-plugin-shwup SUBDIR += kipi-plugin-smug SUBDIR += kipi-plugin-timeadjust SUBDIR += kipi-plugin-videoslideshow SUBDIR += kipi-plugin-vkontakte SUBDIR += kipi-plugin-yandexfotki SUBDIR += kipi-plugins-kde4 SUBDIR += kipiplugins SUBDIR += kix-kmod SUBDIR += klatexformula SUBDIR += kludge3d SUBDIR += kolourpaint SUBDIR += kolourpaint-kde4 SUBDIR += kphotoalbum SUBDIR += kphotoalbum-kde4 SUBDIR += kpovmodeler SUBDIR += kqtquickcharts-kde4 SUBDIR += krita SUBDIR += ksaneplugin-kde4 SUBDIR += ksnapshot-kde4 SUBDIR += kudu SUBDIR += l2p SUBDIR += largetifftools SUBDIR += laternamagica SUBDIR += lcdtest SUBDIR += lcms SUBDIR += lcms-python SUBDIR += lcms2 SUBDIR += leafpak SUBDIR += lensfun SUBDIR += lepton SUBDIR += leptonica SUBDIR += lfview SUBDIR += lib3ds SUBDIR += libGLU SUBDIR += libGLw SUBDIR += libQGLViewer SUBDIR += libafterimage SUBDIR += libart_lgpl SUBDIR += libboard SUBDIR += libbpg SUBDIR += libcaca SUBDIR += libcdr01 SUBDIR += libchamplain SUBDIR += libdmtx SUBDIR += libdrm SUBDIR += libecwj2 SUBDIR += libemf SUBDIR += libepoxy SUBDIR += libetonyek01 SUBDIR += libexif SUBDIR += libexif-gtk SUBDIR += libfpx SUBDIR += libfreehand SUBDIR += libgaiagraphics SUBDIR += libgeotiff SUBDIR += libgfx SUBDIR += libgltext SUBDIR += libgltf SUBDIR += libgnomecanvas SUBDIR += libgnomecanvas-reference SUBDIR += libgnomecanvasmm26 SUBDIR += libgphoto2 SUBDIR += libgxps SUBDIR += libimg SUBDIR += libiptcdata SUBDIR += libjpeg-turbo SUBDIR += libjxr SUBDIR += libkdcraw SUBDIR += libkdcraw-kde4 SUBDIR += libkexiv2 SUBDIR += libkexiv2-kde4 SUBDIR += libkface SUBDIR += libkipi SUBDIR += libkipi-kde4 SUBDIR += libkipiplugins SUBDIR += libksane SUBDIR += libksane-kde4 SUBDIR += libkscreen SUBDIR += liblqr-1 SUBDIR += liblug SUBDIR += libmng SUBDIR += libmorph SUBDIR += libnsbmp SUBDIR += libnsgif SUBDIR += libopenraw SUBDIR += libosmesa SUBDIR += libpano13 SUBDIR += libpcd SUBDIR += libpgf SUBDIR += libpotrace SUBDIR += libprojectm SUBDIR += libpuzzle SUBDIR += libqrencode SUBDIR += librasterlite SUBDIR += librasterlite2 SUBDIR += libraw SUBDIR += libreatlas SUBDIR += librsvg2 SUBDIR += libsixel SUBDIR += libspiro SUBDIR += libsvg SUBDIR += libsvg-cairo SUBDIR += libvisual SUBDIR += libvisual04 SUBDIR += libvisual04-plugins SUBDIR += libwmf SUBDIR += libwmf-nox11 SUBDIR += libwpg03 SUBDIR += libyuv SUBDIR += libzmf SUBDIR += lightzone SUBDIR += linplasma SUBDIR += linux-c6-cairo SUBDIR += linux-c6-dri SUBDIR += linux-c6-gdk-pixbuf2 SUBDIR += linux-c6-glx-utils SUBDIR += linux-c6-jasper SUBDIR += linux-c6-jpeg SUBDIR += linux-c6-png SUBDIR += linux-c6-sdl_image SUBDIR += linux-c6-sdl_ttf SUBDIR += linux-c6-tiff SUBDIR += linux-c7-cairo SUBDIR += linux-c7-dri SUBDIR += linux-c7-gdk-pixbuf2 SUBDIR += linux-c7-glx-utils SUBDIR += linux-c7-graphite2 SUBDIR += linux-c7-jasper SUBDIR += linux-c7-jbigkit SUBDIR += linux-c7-jpeg SUBDIR += linux-c7-png SUBDIR += linux-c7-sdl_image SUBDIR += linux-c7-sdl_ttf SUBDIR += linux-c7-tiff SUBDIR += llpp SUBDIR += lprof-devel SUBDIR += lua-gd SUBDIR += luminance SUBDIR += luminance-qt5 SUBDIR += luxrender SUBDIR += luxrender14 SUBDIR += lximage-qt SUBDIR += lximageqt-l10n SUBDIR += mahotas SUBDIR += maim SUBDIR += makehuman SUBDIR += mandelbulber SUBDIR += mapcache SUBDIR += mapnik SUBDIR += mapserver SUBDIR += mapyrus SUBDIR += megapov SUBDIR += mesa-demos SUBDIR += mesa-dri SUBDIR += mesa-libs SUBDIR += meshviewer SUBDIR += metacam SUBDIR += metapixel SUBDIR += mhgui SUBDIR += ming SUBDIR += mirage SUBDIR += mitsuba SUBDIR += mmrecover SUBDIR += movit SUBDIR += mozjpeg SUBDIR += mscgen SUBDIR += mtpaint SUBDIR += multican SUBDIR += mupdf SUBDIR += mxp SUBDIR += mypaint SUBDIR += nathive SUBDIR += netpbm SUBDIR += nip2 SUBDIR += nomacs SUBDIR += nplot SUBDIR += npretty SUBDIR += nurbs++ SUBDIR += nvidia-texture-tools SUBDIR += ocaml-images SUBDIR += ocaml-lablgl SUBDIR += ocrad SUBDIR += ocre SUBDIR += ogre3d SUBDIR += okular SUBDIR += okular-kde4 SUBDIR += opencollada SUBDIR += opencolorio SUBDIR += opencolorio-tools SUBDIR += opencsg SUBDIR += opencv SUBDIR += opencv-core SUBDIR += opencv-java SUBDIR += opendx SUBDIR += openfx-io + SUBDIR += openfx-misc SUBDIR += opengl-man SUBDIR += openicc-config SUBDIR += openimageio SUBDIR += openjpeg SUBDIR += openjpeg15 SUBDIR += openjump SUBDIR += opennurbs SUBDIR += openorienteering-mapper SUBDIR += openrm SUBDIR += openshadinglanguage SUBDIR += opensubdiv SUBDIR += optar SUBDIR += optipng SUBDIR += osg SUBDIR += osg-devel SUBDIR += osgearth SUBDIR += oyranos SUBDIR += p5-Acme-Steganography-Image-Png SUBDIR += p5-Alien-Gimp SUBDIR += p5-Barcode-ZBar SUBDIR += p5-CAD-Drawing SUBDIR += p5-CAD-Drawing-Template SUBDIR += p5-Cairo SUBDIR += p5-Captcha-reCAPTCHA SUBDIR += p5-Captcha-reCAPTCHA-Mailhide SUBDIR += p5-Chart SUBDIR += p5-Chart-Clicker SUBDIR += p5-Chart-Gnuplot SUBDIR += p5-Chart-Graph SUBDIR += p5-Chart-PNGgraph SUBDIR += p5-Color-Calc SUBDIR += p5-Color-Library SUBDIR += p5-Color-Palette SUBDIR += p5-Color-Rgb SUBDIR += p5-Color-Scheme SUBDIR += p5-Convert-Color SUBDIR += p5-Convert-Color-XTerm SUBDIR += p5-Data-Google-Visualization-DataSource SUBDIR += p5-Data-Google-Visualization-DataTable SUBDIR += p5-GD SUBDIR += p5-GD-Arrow SUBDIR += p5-GD-Barcode SUBDIR += p5-GD-Graph SUBDIR += p5-GD-Graph-histogram SUBDIR += p5-GD-Graph-ohlc SUBDIR += p5-GD-Graph3d SUBDIR += p5-GD-SVG SUBDIR += p5-GD-TextUtil SUBDIR += p5-GD-Thumbnail SUBDIR += p5-Geo-EOP SUBDIR += p5-Geo-GDAL SUBDIR += p5-Geo-GML SUBDIR += p5-Geo-Gpx SUBDIR += p5-Geo-Point SUBDIR += p5-Geo-Proj4 SUBDIR += p5-Geometry-Primitive SUBDIR += p5-Gimp SUBDIR += p5-Google-Chart SUBDIR += p5-Graph-Easy SUBDIR += p5-Graph-ReadWrite SUBDIR += p5-Graph-SocialMap SUBDIR += p5-Graph-Writer-GraphViz SUBDIR += p5-GraphViz SUBDIR += p5-GraphViz-Data-Structure SUBDIR += p5-GraphViz-Traverse SUBDIR += p5-GraphViz2 SUBDIR += p5-Graphics-Color SUBDIR += p5-Graphics-ColorNames SUBDIR += p5-Graphics-ColorNames-WWW SUBDIR += p5-Graphics-GnuplotIF SUBDIR += p5-Graphics-Primitive SUBDIR += p5-Graphics-Primitive-Driver-Cairo SUBDIR += p5-Image-Base SUBDIR += p5-Image-Base-SVG SUBDIR += p5-Image-Caa SUBDIR += p5-Image-Compare SUBDIR += p5-Image-ExifTool SUBDIR += p5-Image-ExifTool-devel SUBDIR += p5-Image-Grab SUBDIR += p5-Image-Heatmap SUBDIR += p5-Image-IPTCInfo SUBDIR += p5-Image-Imgur SUBDIR += p5-Image-Imlib2 SUBDIR += p5-Image-Info SUBDIR += p5-Image-LibExif SUBDIR += p5-Image-Magick-Iterator SUBDIR += p5-Image-Math-Constrain SUBDIR += p5-Image-MetaData-GQview SUBDIR += p5-Image-MetaData-JPEG SUBDIR += p5-Image-ObjectDetect SUBDIR += p5-Image-PBMlib SUBDIR += p5-Image-Pngslimmer SUBDIR += p5-Image-Sane SUBDIR += p5-Image-Scale SUBDIR += p5-Image-Size SUBDIR += p5-Image-Size-FillFullSelect SUBDIR += p5-Imager SUBDIR += p5-Imager-AverageGray SUBDIR += p5-Imager-Graph SUBDIR += p5-Imager-Plot SUBDIR += p5-Imager-QRCode SUBDIR += p5-Imlib2 SUBDIR += p5-Layout-Manager SUBDIR += p5-OpenGL SUBDIR += p5-PGPLOT SUBDIR += p5-SVG-DOM2 SUBDIR += p5-SVG-Graph SUBDIR += p5-SVG-Metadata SUBDIR += p5-SWF-Builder SUBDIR += p5-SWF-File SUBDIR += p5-Sane SUBDIR += p5-SpringGraph SUBDIR += p5-Tk-JPEG-Lite SUBDIR += p5-URI-GoogleChart SUBDIR += p5-VCG SUBDIR += p5-Visio SUBDIR += p5-feedgnuplot SUBDIR += p5-ming SUBDIR += panoglview SUBDIR += panomatic SUBDIR += partio SUBDIR += pcl-pointclouds SUBDIR += pdf2svg SUBDIR += pdfpc SUBDIR += pear-Horde_Image SUBDIR += pear-Image_3D SUBDIR += pear-Image_Barcode SUBDIR += pear-Image_Barcode2 SUBDIR += pear-Image_Canvas SUBDIR += pear-Image_Color SUBDIR += pear-Image_Graph SUBDIR += pear-Image_GraphViz SUBDIR += pear-Image_Transform SUBDIR += pecl-gmagick SUBDIR += pecl-imagick SUBDIR += pecl-imlib2 SUBDIR += pecl-qrencode SUBDIR += pecomato SUBDIR += pencil SUBDIR += peps SUBDIR += perceptualdiff SUBDIR += pfstools SUBDIR += pgplot SUBDIR += pho SUBDIR += photivo SUBDIR += photopc SUBDIR += phototonic SUBDIR += php-facedetect SUBDIR += php-gdal SUBDIR += php-geos SUBDIR += php-libpuzzle SUBDIR += php-magickwand SUBDIR += php5-ffmpeg SUBDIR += php56-exif SUBDIR += php56-gd SUBDIR += php70-exif SUBDIR += php70-gd SUBDIR += php71-exif SUBDIR += php71-gd SUBDIR += php72-exif SUBDIR += php72-gd SUBDIR += phplot SUBDIR += picpuz SUBDIR += piddle SUBDIR += piglit SUBDIR += pikopixel SUBDIR += pinpoint SUBDIR += pixd SUBDIR += pixelize SUBDIR += pixen SUBDIR += pixie SUBDIR += pixmap SUBDIR += plasma-kmod SUBDIR += plotutils SUBDIR += png SUBDIR += png2html SUBDIR += png2ico SUBDIR += pngcheck SUBDIR += pngcrush SUBDIR += pngnq SUBDIR += pngquant SUBDIR += pngrewrite SUBDIR += pngwriter SUBDIR += podofo SUBDIR += polyclipping SUBDIR += poppler SUBDIR += poppler-data SUBDIR += poppler-glib SUBDIR += poppler-qt4 SUBDIR += poppler-qt5 SUBDIR += poppler-utils SUBDIR += potrace SUBDIR += povray-meta SUBDIR += povray36 SUBDIR += povray37 SUBDIR += ppmcaption SUBDIR += ppminfo SUBDIR += ppsei SUBDIR += pqiv SUBDIR += preview SUBDIR += price SUBDIR += prison SUBDIR += processing SUBDIR += proj SUBDIR += projectm-libvisual SUBDIR += pstoedit SUBDIR += pstoepsi SUBDIR += ptex SUBDIR += py-PyOpenGL SUBDIR += py-PyOpenGL-accelerate SUBDIR += py-PyX SUBDIR += py-PyX12 SUBDIR += py-actdiag SUBDIR += py-blockdiag SUBDIR += py-blockdiagcontrib-cisco SUBDIR += py-cairo SUBDIR += py-cairocffi SUBDIR += py-chart SUBDIR += py-django-easy-thumbnails SUBDIR += py-djvulibre SUBDIR += py-editobj SUBDIR += py-exif SUBDIR += py-exifread SUBDIR += py-exiv2 SUBDIR += py-freeimagepy SUBDIR += py-gd SUBDIR += py-gdal SUBDIR += py-gimp SUBDIR += py-gizeh SUBDIR += py-glewpy SUBDIR += py-goocanvas SUBDIR += py-gphoto2 SUBDIR += py-graph-core SUBDIR += py-graphviz SUBDIR += py-graphy SUBDIR += py-gvgen SUBDIR += py-imageio SUBDIR += py-imagesize SUBDIR += py-imgurpython SUBDIR += py-leather SUBDIR += py-mayavi SUBDIR += py-mcomix SUBDIR += py-ming SUBDIR += py-nwdiag SUBDIR += py-opencolorio SUBDIR += py-opencv SUBDIR += py-openexr SUBDIR += py-openimageio SUBDIR += py-paint SUBDIR += py-photocollage SUBDIR += py-pillow SUBDIR += py-pivy SUBDIR += py-plotly SUBDIR += py-png SUBDIR += py-poppler SUBDIR += py-poppler-qt4 SUBDIR += py-pycha SUBDIR += py-pycollada SUBDIR += py-pydot SUBDIR += py-pyembree SUBDIR += py-pygal SUBDIR += py-pyganim SUBDIR += py-pyglet SUBDIR += py-pygooglechart SUBDIR += py-pygraphviz SUBDIR += py-pyproj SUBDIR += py-qt4-svg SUBDIR += py-qt5-svg SUBDIR += py-sane SUBDIR += py-scikit-image SUBDIR += py-seqdiag SUBDIR += py-sk1 SUBDIR += py-sorl-thumbnail SUBDIR += py-soya3d SUBDIR += py-stltools SUBDIR += py-toyplot SUBDIR += py-traitsui SUBDIR += py-wand SUBDIR += py-webcolors SUBDIR += py-yaswfp SUBDIR += pygts SUBDIR += pymorph SUBDIR += pysvg SUBDIR += qcomicbook SUBDIR += qgis SUBDIR += qiv SUBDIR += qslim SUBDIR += qt4-iconengines SUBDIR += qt4-imageformats SUBDIR += qt4-opengl SUBDIR += qt4-pixeltool SUBDIR += qt4-svg SUBDIR += qt5-3d SUBDIR += qt5-graphicaleffects SUBDIR += qt5-imageformats SUBDIR += qt5-opengl SUBDIR += qt5-pixeltool SUBDIR += qt5-svg SUBDIR += qt5-wayland SUBDIR += qtawesome SUBDIR += quat SUBDIR += quat-gui SUBDIR += quesa SUBDIR += quesoglc SUBDIR += qxv SUBDIR += radius-engine SUBDIR += raster3d SUBDIR += rawstudio SUBDIR += rawtherapee SUBDIR += rayshade SUBDIR += reallyslick SUBDIR += recoverjpeg SUBDIR += renrot SUBDIR += repng2jpeg SUBDIR += rgbpaint SUBDIR += rigsofrods-caelum SUBDIR += rigsofrods-pagedgeometry SUBDIR += ristretto SUBDIR += ruby-gd SUBDIR += rubygem-cairo SUBDIR += rubygem-captcha SUBDIR += rubygem-chunky_png SUBDIR += rubygem-clutter SUBDIR += rubygem-clutter-gdk SUBDIR += rubygem-clutter-gtk SUBDIR += rubygem-dragonfly SUBDIR += rubygem-emoji SUBDIR += rubygem-exifr SUBDIR += rubygem-ezprint SUBDIR += rubygem-fastimage SUBDIR += rubygem-flamegraph SUBDIR += rubygem-gd2 SUBDIR += rubygem-gdk_pixbuf2 SUBDIR += rubygem-gemojione SUBDIR += rubygem-geokit SUBDIR += rubygem-gitlab_emoji SUBDIR += rubygem-goocanvas SUBDIR += rubygem-gruff SUBDIR += rubygem-image_science SUBDIR += rubygem-imagesize SUBDIR += rubygem-mini_magick SUBDIR += rubygem-objectdetect SUBDIR += rubygem-opengl SUBDIR += rubygem-pdfkit SUBDIR += rubygem-png SUBDIR += rubygem-railroad SUBDIR += rubygem-rmagick SUBDIR += rubygem-rsvg2 SUBDIR += rubygem-ruby-graphviz SUBDIR += rubygem-scruffy SUBDIR += s10sh SUBDIR += s2tc SUBDIR += sage SUBDIR += sam2p SUBDIR += sampleicc SUBDIR += sane-backends SUBDIR += sane-epkowa SUBDIR += sane-frontends SUBDIR += scale2x SUBDIR += scantailor SUBDIR += scr2png SUBDIR += scrot SUBDIR += scwm-icons SUBDIR += sdl2_gfx SUBDIR += sdl2_image SUBDIR += sdl2_ttf SUBDIR += sdl_gfx SUBDIR += sdl_image SUBDIR += sdl_ttf SUBDIR += sdump SUBDIR += seejpeg SUBDIR += seexpr SUBDIR += sekrit-twc-zimg SUBDIR += seom SUBDIR += separate SUBDIR += seq2gif SUBDIR += shared-color-profiles SUBDIR += sharpconstruct SUBDIR += shotwell SUBDIR += showimage SUBDIR += silgraphite SUBDIR += simage SUBDIR += simpleviewer SUBDIR += sk1libs SUBDIR += skanlite-kde4 SUBDIR += smillaenlarger SUBDIR += spectacle SUBDIR += springgraph SUBDIR += squish SUBDIR += ssocr SUBDIR += sswf SUBDIR += stamp SUBDIR += structuresynth SUBDIR += svg2pdf SUBDIR += svg2png SUBDIR += svgalib SUBDIR += swfdec SUBDIR += swfmill SUBDIR += swftools SUBDIR += sxiv SUBDIR += synaesthesia SUBDIR += synfigstudio SUBDIR += telak SUBDIR += tesseract SUBDIR += tesseract-data SUBDIR += tgif SUBDIR += tif22pnm SUBDIR += tiff SUBDIR += tiffgt SUBDIR += tifmerge SUBDIR += tilecache SUBDIR += tiled SUBDIR += timeless SUBDIR += timg SUBDIR += tintfu SUBDIR += tinyows SUBDIR += tkpng SUBDIR += togl SUBDIR += truevision SUBDIR += ttygif SUBDIR += tulip SUBDIR += tumble SUBDIR += ufraw SUBDIR += uniconvertor SUBDIR += uniconvw SUBDIR += unpaper SUBDIR += urt SUBDIR += vapoursynth-fmtconv SUBDIR += vapoursynth-waifu2x-w2xc SUBDIR += variety SUBDIR += vcg SUBDIR += viewnior SUBDIR += vigra SUBDIR += vips SUBDIR += visprint SUBDIR += volpack SUBDIR += vp SUBDIR += waffle SUBDIR += waifu2x-converter-cpp SUBDIR += wayland SUBDIR += waylandpp SUBDIR += wayland-protocols SUBDIR += webp SUBDIR += wings SUBDIR += wmicons SUBDIR += wxsvg SUBDIR += xaos SUBDIR += xbmbrowser SUBDIR += xcftools SUBDIR += xd3d SUBDIR += xdgagrab SUBDIR += xface.el SUBDIR += xfig SUBDIR += xfpovray SUBDIR += xfractint SUBDIR += xglurbules SUBDIR += xgrasp SUBDIR += xli SUBDIR += xmandel SUBDIR += xmedcon SUBDIR += xmlgraphics-commons SUBDIR += xmountains SUBDIR += xnview SUBDIR += xoris SUBDIR += xournal SUBDIR += xpaint SUBDIR += xpdf SUBDIR += xpdf3 SUBDIR += xpdf4 SUBDIR += xpeps SUBDIR += xpx SUBDIR += xsane SUBDIR += xsvg SUBDIR += xtexcad SUBDIR += xv SUBDIR += xv-m17n SUBDIR += xwpick SUBDIR += yafray SUBDIR += yagf SUBDIR += yed SUBDIR += yukon SUBDIR += zathura SUBDIR += zathura-cb SUBDIR += zathura-djvu SUBDIR += zathura-pdf-mupdf SUBDIR += zathura-pdf-poppler SUBDIR += zathura-ps SUBDIR += zbar SUBDIR += zgv SUBDIR += zimg SUBDIR += zint SUBDIR += zphoto .include Index: head/graphics/openfx-misc/Makefile =================================================================== --- head/graphics/openfx-misc/Makefile (nonexistent) +++ head/graphics/openfx-misc/Makefile (revision 468804) @@ -0,0 +1,42 @@ +# $FreeBSD$ + +PORTNAME= openfx-misc +PORTVERSION= 2.3.10 +CATEGORIES= graphics + +MAINTAINER= olivier@FreeBSD.org +COMMENT= Miscellaneous OpenFX Effects plugins + +LICENSE= GPLv2 + +ONLY_FOR_ARCHS= amd64 + +USES= gmake +USE_GITHUB= yes +GH_ACCOUNT= devernay +GH_PROJECT= openfx-misc +GH_TAGNAME= 3ab0531 +GH_TUPLE= devernay:openfx-supportext:2c43fc8:openfx_supportext/SupportExt \ + devernay:openfx:e98216f:openfx/openfx + +MAKE_ENV+= CONFIG=release +USE_GL= gl + +pre-patch: + @${RM} ${WRKSRC}/CImg/CImg.h.orig + +post-patch: + @${REINPLACE_CMD} -e 's|/usr/OFX/Plugins|${PREFIX}/OFX/Plugins|g' \ + ${WRKSRC}/openfx/Examples/Makefile.master \ + ${WRKSRC}/openfx/Support/Plugins/Makefile.master \ + ${WRKSRC}/DebugProxy/DebugProxy.cpp \ + ${WRKSRC}/openfx/HostSupport/src/ofxhPluginCache.cpp + @${REINPLACE_CMD} -e 's|/Contents/$$(ARCH)|/Contents/FreeBSD-x86-64/|g' \ + ${WRKSRC}/openfx/Support/Plugins/Makefile.master + +post-install: + ${STRIP_CMD} ${STAGEDIR}${LOCALBASE}/OFX/Plugins/CImg.ofx.bundle/Contents/FreeBSD-x86-64/CImg.ofx + ${STRIP_CMD} ${STAGEDIR}${LOCALBASE}/OFX/Plugins/Misc.ofx.bundle/Contents/FreeBSD-x86-64/Misc.ofx + ${STRIP_CMD} ${STAGEDIR}${LOCALBASE}/OFX/Plugins/Shadertoy.ofx.bundle/Contents/FreeBSD-x86-64/Shadertoy.ofx + +.include Property changes on: head/graphics/openfx-misc/Makefile ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/graphics/openfx-misc/distinfo =================================================================== --- head/graphics/openfx-misc/distinfo (nonexistent) +++ head/graphics/openfx-misc/distinfo (revision 468804) @@ -0,0 +1,7 @@ +TIMESTAMP = 1525127464 +SHA256 (devernay-openfx-misc-2.3.10-3ab0531_GH0.tar.gz) = c2c4c5b056a107709d380a4d4dda803d3a7b619ba2c8f3227f89b5ab6a0464c5 +SIZE (devernay-openfx-misc-2.3.10-3ab0531_GH0.tar.gz) = 2305004 +SHA256 (devernay-openfx-supportext-2c43fc8_GH0.tar.gz) = 9b6fd4817a2cfb72772f81136f36fff2d10ecfb86602d0856585abaae019b946 +SIZE (devernay-openfx-supportext-2c43fc8_GH0.tar.gz) = 287047 +SHA256 (devernay-openfx-e98216f_GH0.tar.gz) = f8495104b7c077b659d57c9a5c107dcc313c943183cd7d47acffab6504c3bc51 +SIZE (devernay-openfx-e98216f_GH0.tar.gz) = 10651653 Property changes on: head/graphics/openfx-misc/distinfo ___________________________________________________________________ Added: fbsd:nokeywords ## -0,0 +1 ## +yes \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/graphics/openfx-misc/files/patch-CImg_CImg.h =================================================================== --- head/graphics/openfx-misc/files/patch-CImg_CImg.h (nonexistent) +++ head/graphics/openfx-misc/files/patch-CImg_CImg.h (revision 468804) @@ -0,0 +1,60855 @@ +--- CImg/CImg.h.orig 2018-04-30 23:16:26 UTC ++++ CImg/CImg.h +@@ -0,0 +1,60852 @@ ++/* ++ # ++ # File : CImg.h ++ # ( C++ header file ) ++ # ++ # Description : The C++ Template Image Processing Toolkit. ++ # This file is the main component of the CImg Library project. ++ # ( http://cimg.eu ) ++ # ++ # Project manager : David Tschumperle. ++ # ( http://tschumperle.users.greyc.fr/ ) ++ # ++ # A complete list of contributors is available in file 'README.txt' ++ # distributed within the CImg package. ++ # ++ # Licenses : This file is 'dual-licensed', you have to choose one ++ # of the two licenses below to apply. ++ # ++ # 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.1 ++ # The CeCILL license is compatible with the GNU GPL. ++ # ( http://www.cecill.info/licences/Licence_CeCILL_V2.1-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. ++ # ++*/ ++ ++// Set version number of the library. ++#ifndef cimg_version ++#define cimg_version 221 ++ ++/*----------------------------------------------------------- ++ # ++ # Test and possibly auto-set CImg configuration variables ++ # and include required headers. ++ # ++ # If you find that the default configuration variables are ++ # not adapted to your system, you can override their values ++ # before including the header file "CImg.h" ++ # (use the #define directive). ++ # ++ ------------------------------------------------------------*/ ++ ++// Include standard C++ headers. ++// This is the minimal set of required headers to make CImg-based codes compile. ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++// Detect/configure OS variables. ++// ++// Define 'cimg_OS' to: '0' for an unknown OS (will try to minize library dependencies). ++// '1' for a Unix-like OS (Linux, Solaris, BSD, MacOSX, Irix, ...). ++// '2' for Microsoft Windows. ++// (auto-detection is performed if 'cimg_OS' is not set by the user). ++#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: Invalid configuration variable 'cimg_OS'. ++#error (correct values are '0 = unknown OS', '1 = Unix-like OS', '2 = Microsoft Windows'). ++#endif ++#ifndef cimg_date ++#define cimg_date __DATE__ ++#endif ++#ifndef cimg_time ++#define cimg_time __TIME__ ++#endif ++ ++// Disable silly warnings on some Microsoft VC++ compilers. ++#ifdef _MSC_VER ++#pragma warning(push) ++#pragma warning(disable:4127) ++#pragma warning(disable:4244) ++#pragma warning(disable:4311) ++#pragma warning(disable:4312) ++#pragma warning(disable:4319) ++#pragma warning(disable:4512) ++#pragma warning(disable:4571) ++#pragma warning(disable:4640) ++#pragma warning(disable:4706) ++#pragma warning(disable:4710) ++#pragma warning(disable:4800) ++#pragma warning(disable:4804) ++#pragma warning(disable:4820) ++#pragma warning(disable:4996) ++ ++#ifndef _CRT_SECURE_NO_DEPRECATE ++#define _CRT_SECURE_NO_DEPRECATE 1 ++#endif ++#ifndef _CRT_SECURE_NO_WARNINGS ++#define _CRT_SECURE_NO_WARNINGS 1 ++#endif ++#ifndef _CRT_NONSTDC_NO_DEPRECATE ++#define _CRT_NONSTDC_NO_DEPRECATE 1 ++#endif ++#endif ++ ++// Define correct string functions for each compiler and OS. ++#if cimg_OS==2 && defined(_MSC_VER) ++#define cimg_sscanf std::sscanf ++#define cimg_sprintf std::sprintf ++#define cimg_snprintf cimg::_snprintf ++#define cimg_vsnprintf cimg::_vsnprintf ++#else ++#include ++#if defined(__MACOSX__) || defined(__APPLE__) ++#define cimg_sscanf cimg::_sscanf ++#define cimg_sprintf cimg::_sprintf ++#define cimg_snprintf cimg::_snprintf ++#define cimg_vsnprintf cimg::_vsnprintf ++#else ++#define cimg_sscanf std::sscanf ++#define cimg_sprintf std::sprintf ++#define cimg_snprintf snprintf ++#define cimg_vsnprintf vsnprintf ++#endif ++#endif ++ ++// Include OS-specific headers. ++#if cimg_OS==1 ++#include ++#include ++#include ++#include ++#include ++#include ++#elif cimg_OS==2 ++#ifndef std_fopen ++#define std_fopen cimg::win_fopen ++#endif ++#ifndef NOMINMAX ++#define NOMINMAX ++#endif ++#ifndef WIN32_LEAN_AND_MEAN ++#define WIN32_LEAN_AND_MEAN ++#endif ++#include ++#ifndef _WIN32_IE ++#define _WIN32_IE 0x0400 ++#endif ++#include ++#include ++#include ++#endif ++ ++// Look for C++11 features. ++#ifndef cimg_use_cpp11 ++#if __cplusplus>201100 ++#define cimg_use_cpp11 1 ++#else ++#define cimg_use_cpp11 0 ++#endif ++#endif ++#if cimg_use_cpp11==1 ++#include ++#include ++#endif ++ ++// Convenient macro to define pragma ++#ifdef _MSC_VER ++#define cimg_pragma(x) __pragma(x) ++#else ++#define cimg_pragma(x) _Pragma(#x) ++#endif ++ ++// Define own types 'cimg_long/ulong' and 'cimg_int64/uint64' to ensure portability. ++// ( constrained to 'sizeof(cimg_ulong/cimg_long) = sizeof(void*)' and 'sizeof(cimg_int64/cimg_uint64)=8' ). ++#if cimg_OS==2 ++ ++#define cimg_uint64 unsigned __int64 ++#define cimg_int64 __int64 ++#define cimg_ulong UINT_PTR ++#define cimg_long INT_PTR ++#ifdef _MSC_VER ++#define cimg_fuint64 "%I64u" ++#define cimg_fint64 "%I64d" ++#else ++#define cimg_fuint64 "%llu" ++#define cimg_fint64 "%lld" ++#endif ++ ++#else ++ ++#if UINTPTR_MAX==0xffffffff || defined(__arm__) || defined(_M_ARM) || ((ULONG_MAX)==(UINT_MAX)) ++#define cimg_uint64 unsigned long long ++#define cimg_int64 long long ++#define cimg_fuint64 "%llu" ++#define cimg_fint64 "%lld" ++#else ++#define cimg_uint64 unsigned long ++#define cimg_int64 long ++#define cimg_fuint64 "%lu" ++#define cimg_fint64 "%ld" ++#endif ++ ++#if defined(__arm__) || defined(_M_ARM) ++#define cimg_ulong unsigned long long ++#define cimg_long long long ++#else ++#define cimg_ulong unsigned long ++#define cimg_long long ++#endif ++ ++#endif ++ ++// Configure filename separator. ++// ++// Filename separator is set by default to '/', except for Windows where it is '\'. ++#ifndef cimg_file_separator ++#if cimg_OS==2 ++#define cimg_file_separator '\\' ++#else ++#define cimg_file_separator '/' ++#endif ++#endif ++ ++// Configure verbosity of output messages. ++// ++// Define 'cimg_verbosity' to: '0' to hide library messages (quiet mode). ++// '1' to output library messages on the console. ++// '2' to output library messages on a basic dialog window (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 on VT100-compatible terminals. ++#ifndef cimg_verbosity ++#if cimg_OS==2 ++#define cimg_verbosity 2 ++#else ++#define cimg_verbosity 1 ++#endif ++#elif !(cimg_verbosity==0 || cimg_verbosity==1 || cimg_verbosity==2 || cimg_verbosity==3 || cimg_verbosity==4) ++#error CImg Library: Configuration variable 'cimg_verbosity' is badly defined. ++#error (should be { 0=quiet | 1=console | 2=dialog | 3=console+warnings | 4=dialog+warnings }). ++#endif ++ ++// Configure display framework. ++// ++// Define 'cimg_display' to: '0' to disable display capabilities. ++// '1' to use the X-Window framework (X11). ++// '2' to use the Microsoft GDI32 framework. ++#ifndef cimg_display ++#if cimg_OS==0 ++#define cimg_display 0 ++#elif cimg_OS==1 ++#define cimg_display 1 ++#elif cimg_OS==2 ++#define cimg_display 2 ++#endif ++#elif !(cimg_display==0 || cimg_display==1 || cimg_display==2) ++#error CImg Library: Configuration variable 'cimg_display' is badly defined. ++#error (should be { 0=none | 1=X-Window (X11) | 2=Microsoft GDI32 }). ++#endif ++ ++// Configure the 'abort' signal handler (does nothing by default). ++// A typical signal handler can be defined in your own source like this: ++// #define cimg_abort_test if (is_abort) throw CImgAbortException("") ++// ++// where 'is_abort' is a boolean variable defined somewhere in your code and reachable in the method. ++// 'cimg_abort_test2' does the same but is called more often (in inner loops). ++#if defined(cimg_abort_test) && defined(cimg_use_openmp) ++ ++// Define abort macros to be used with OpenMP. ++#ifndef _cimg_abort_init_omp ++#define _cimg_abort_init_omp bool _cimg_abort_go_omp = true; cimg::unused(_cimg_abort_go_omp) ++#endif ++#ifndef _cimg_abort_try_omp ++#define _cimg_abort_try_omp if (_cimg_abort_go_omp) try ++#endif ++#ifndef _cimg_abort_catch_omp ++#define _cimg_abort_catch_omp catch (CImgAbortException&) { cimg_pragma(omp atomic) _cimg_abort_go_omp&=false; } ++#endif ++#ifdef cimg_abort_test2 ++#ifndef _cimg_abort_try_omp2 ++#define _cimg_abort_try_omp2 _cimg_abort_try_omp ++#endif ++#ifndef _cimg_abort_catch_omp2 ++#define _cimg_abort_catch_omp2 _cimg_abort_catch_omp ++#endif ++#ifndef _cimg_abort_catch_fill_omp ++#define _cimg_abort_catch_fill_omp \ ++ catch (CImgException& e) { cimg_pragma(omp critical(abort)) CImg::string(e._message).move_to(is_error); \ ++ cimg_pragma(omp atomic) _cimg_abort_go_omp&=false; } ++#endif ++#endif ++#endif ++ ++#ifndef _cimg_abort_init_omp ++#define _cimg_abort_init_omp ++#endif ++#ifndef _cimg_abort_try_omp ++#define _cimg_abort_try_omp ++#endif ++#ifndef _cimg_abort_catch_omp ++#define _cimg_abort_catch_omp ++#endif ++#ifndef _cimg_abort_try_omp2 ++#define _cimg_abort_try_omp2 ++#endif ++#ifndef _cimg_abort_catch_omp2 ++#define _cimg_abort_catch_omp2 ++#endif ++#ifndef _cimg_abort_catch_fill_omp ++#define _cimg_abort_catch_fill_omp ++#endif ++#ifndef cimg_abort_init ++#define cimg_abort_init ++#endif ++#ifndef cimg_abort_test ++#define cimg_abort_test ++#endif ++#ifndef cimg_abort_test2 ++#define cimg_abort_test2 ++#endif ++#ifndef std_fopen ++#define std_fopen std::fopen ++#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 ++#endif ++#ifndef cimg_appname ++#define cimg_appname "CImg" ++#endif ++ ++// Configure OpenMP support. ++// (http://www.openmp.org) ++// ++// Define 'cimg_use_openmp' to enable OpenMP support. ++// ++// OpenMP directives may be used in a (very) few CImg functions to get ++// advantages of multi-core CPUs. ++#ifdef cimg_use_openmp ++#include ++#define cimg_pragma_openmp(p) cimg_pragma(omp p) ++#else ++#define cimg_pragma_openmp(p) ++#endif ++ ++// Configure OpenCV support. ++// (http://opencv.willowgarage.com/wiki/) ++// ++// Define 'cimg_use_opencv' to enable OpenCV support. ++// ++// OpenCV library may be used to access images from cameras ++// (see method 'CImg::load_camera()'). ++#ifdef cimg_use_opencv ++#ifdef True ++#undef True ++#define _cimg_redefine_True ++#endif ++#ifdef False ++#undef False ++#define _cimg_redefine_False ++#endif ++#include ++#include "cv.h" ++#include "highgui.h" ++#endif ++ ++// Configure LibPNG support. ++// (http://www.libpng.org) ++// ++// Define 'cimg_use_png' to enable LibPNG support. ++// ++// PNG library may be used to get a native support of '.png' files. ++// (see methods 'CImg::{load,save}_png()'. ++#ifdef cimg_use_png ++extern "C" { ++#include "png.h" ++} ++#endif ++ ++// Configure LibJPEG support. ++// (http://en.wikipedia.org/wiki/Libjpeg) ++// ++// Define 'cimg_use_jpeg' to enable LibJPEG support. ++// ++// JPEG library may be used to get a native support of '.jpg' files. ++// (see methods 'CImg::{load,save}_jpeg()'). ++#ifdef cimg_use_jpeg ++extern "C" { ++#include "jpeglib.h" ++#include "setjmp.h" ++} ++#endif ++ ++// Configure LibTIFF support. ++// (http://www.libtiff.org) ++// ++// Define 'cimg_use_tiff' to enable LibTIFF support. ++// ++// TIFF library may be used to get a native support of '.tif' files. ++// (see methods 'CImg[List]::{load,save}_tiff()'). ++#ifdef cimg_use_tiff ++extern "C" { ++#define uint64 uint64_hack_ ++#define int64 int64_hack_ ++#include "tiffio.h" ++#undef uint64 ++#undef int64 ++} ++#endif ++ ++// Configure LibMINC2 support. ++// (http://en.wikibooks.org/wiki/MINC/Reference/MINC2.0_File_Format_Reference) ++// ++// Define 'cimg_use_minc2' to enable LibMINC2 support. ++// ++// MINC2 library may be used to get a native support of '.mnc' files. ++// (see methods 'CImg::{load,save}_minc2()'). ++#ifdef cimg_use_minc2 ++#include "minc_io_simple_volume.h" ++#include "minc_1_simple.h" ++#include "minc_1_simple_rw.h" ++#endif ++ ++// Configure Zlib support. ++// (http://www.zlib.net) ++// ++// Define 'cimg_use_zlib' to enable Zlib support. ++// ++// Zlib library may be used to allow compressed data in '.cimgz' files ++// (see methods 'CImg[List]::{load,save}_cimg()'). ++#ifdef cimg_use_zlib ++extern "C" { ++#include "zlib.h" ++} ++#endif ++ ++// Configure libcurl support. ++// (http://curl.haxx.se/libcurl/) ++// ++// Define 'cimg_use_curl' to enable libcurl support. ++// ++// Libcurl may be used to get a native support of file downloading from the network. ++// (see method 'cimg::load_network()'.) ++#ifdef cimg_use_curl ++#include "curl/curl.h" ++#endif ++ ++// Configure Magick++ support. ++// (http://www.imagemagick.org/Magick++) ++// ++// Define 'cimg_use_magick' to enable Magick++ support. ++// ++// Magick++ library may be used to get a native support of various image file formats. ++// (see methods 'CImg::{load,save}()'). ++#ifdef cimg_use_magick ++#include "Magick++.h" ++#endif ++ ++// Configure FFTW3 support. ++// (http://www.fftw.org) ++// ++// Define 'cimg_use_fftw3' to enable libFFTW3 support. ++// ++// FFTW3 library may be used to efficiently compute the Fast Fourier Transform ++// of image data, without restriction on the image size. ++// (see method 'CImg[List]::FFT()'). ++#ifdef cimg_use_fftw3 ++extern "C" { ++#include "fftw3.h" ++} ++#endif ++ ++// Configure LibBoard support. ++// (http://libboard.sourceforge.net/) ++// ++// Define 'cimg_use_board' to enable Board support. ++// ++// Board library may be used to draw 3d objects in vector-graphics canvas ++// that can be saved as '.ps' or '.svg' files afterwards. ++// (see method 'CImg::draw_object3d()'). ++#ifdef cimg_use_board ++#include "Board.h" ++#endif ++ ++// Configure OpenEXR support. ++// (http://www.openexr.com/) ++// ++// Define 'cimg_use_openexr' to enable OpenEXR support. ++// ++// OpenEXR library may be used to get a native support of '.exr' files. ++// (see methods 'CImg::{load,save}_exr()'). ++#ifdef cimg_use_openexr ++#include "ImfRgbaFile.h" ++#include "ImfInputFile.h" ++#include "ImfChannelList.h" ++#include "ImfMatrixAttribute.h" ++#include "ImfArray.h" ++#endif ++ ++// Configure TinyEXR support. ++// (https://github.com/syoyo/tinyexr) ++// ++// Define 'cimg_use_tinyexr' to enable TinyEXR support. ++// ++// TinyEXR is a small, single header-only library to load and save OpenEXR(.exr) images. ++#ifdef cimg_use_tinyexr ++#ifndef TINYEXR_IMPLEMENTATION ++#define TINYEXR_IMPLEMENTATION ++#endif ++#include "tinyexr.h" ++#endif ++ ++// Lapack configuration. ++// (http://www.netlib.org/lapack) ++// ++// Define 'cimg_use_lapack' to enable LAPACK support. ++// ++// Lapack library may be used in several CImg methods to speed up ++// matrix computations (eigenvalues, inverse, ...). ++#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*); ++ extern void dgels_(char*, int*,int*,int*,double*,int*,double*,int*,double*,int*,int*); ++ extern void sgels_(char*, int*,int*,int*,float*,int*,float*,int*,float*,int*,int*); ++} ++#endif ++ ++// Check if min/max/PI macros are defined. ++// ++// CImg does not compile if macros 'min', 'max' or 'PI' are defined, ++// because it redefines functions min(), max() and const variable PI in the cimg:: namespace. ++// so it '#undef' these macros if necessary, and restore them to reasonable ++// values at the end of this file. ++#ifdef min ++#undef min ++#define _cimg_redefine_min ++#endif ++#ifdef max ++#undef max ++#define _cimg_redefine_max ++#endif ++#ifdef PI ++#undef PI ++#define _cimg_redefine_PI ++#endif ++ ++// Define 'cimg_library' namespace suffix. ++// ++// You may want to add a suffix to the 'cimg_library' namespace, for instance if you need to work ++// with several versions of the library at the same time. ++#ifdef cimg_namespace_suffix ++#define __cimg_library_suffixed(s) cimg_library_##s ++#define _cimg_library_suffixed(s) __cimg_library_suffixed(s) ++#define cimg_library_suffixed _cimg_library_suffixed(cimg_namespace_suffix) ++#else ++#define cimg_library_suffixed cimg_library ++#endif ++ ++/*------------------------------------------------------------------------------ ++ # ++ # Define user-friendly macros. ++ # ++ # These CImg macros are prefixed by 'cimg_' and can be used safely in your own ++ # code. They are useful to parse command line options, or to write image loops. ++ # ++ ------------------------------------------------------------------------------*/ ++ ++// Macros to define program usage, and retrieve command line arguments. ++#define cimg_usage(usage) cimg_library_suffixed::cimg::option((char*)0,argc,argv,(char*)0,usage,false) ++#define cimg_help(str) cimg_library_suffixed::cimg::option((char*)0,argc,argv,str,(char*)0) ++#define cimg_option(name,defaut,usage) cimg_library_suffixed::cimg::option(name,argc,argv,defaut,usage) ++ ++// Macros to 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,c,I,T) \ ++ I[0] = (T)(img)(x,y,z,c), I[1] = (T)(img)(_n1##x,y,z,c), I[2] = (T)(img)(x,_n1##y,z,c), \ ++ I[3] = (T)(img)(_n1##x,_n1##y,z,c) ++ ++#define cimg_get3x3(img,x,y,z,c,I,T) \ ++ I[0] = (T)(img)(_p1##x,_p1##y,z,c), I[1] = (T)(img)(x,_p1##y,z,c), I[2] = (T)(img)(_n1##x,_p1##y,z,c), \ ++ I[3] = (T)(img)(_p1##x,y,z,c), I[4] = (T)(img)(x,y,z,c), I[5] = (T)(img)(_n1##x,y,z,c), \ ++ I[6] = (T)(img)(_p1##x,_n1##y,z,c), I[7] = (T)(img)(x,_n1##y,z,c), I[8] = (T)(img)(_n1##x,_n1##y,z,c) ++ ++#define cimg_get4x4(img,x,y,z,c,I,T) \ ++ I[0] = (T)(img)(_p1##x,_p1##y,z,c), I[1] = (T)(img)(x,_p1##y,z,c), I[2] = (T)(img)(_n1##x,_p1##y,z,c), \ ++ I[3] = (T)(img)(_n2##x,_p1##y,z,c), I[4] = (T)(img)(_p1##x,y,z,c), I[5] = (T)(img)(x,y,z,c), \ ++ I[6] = (T)(img)(_n1##x,y,z,c), I[7] = (T)(img)(_n2##x,y,z,c), I[8] = (T)(img)(_p1##x,_n1##y,z,c), \ ++ I[9] = (T)(img)(x,_n1##y,z,c), I[10] = (T)(img)(_n1##x,_n1##y,z,c), I[11] = (T)(img)(_n2##x,_n1##y,z,c), \ ++ I[12] = (T)(img)(_p1##x,_n2##y,z,c), I[13] = (T)(img)(x,_n2##y,z,c), I[14] = (T)(img)(_n1##x,_n2##y,z,c), \ ++ I[15] = (T)(img)(_n2##x,_n2##y,z,c) ++ ++#define cimg_get5x5(img,x,y,z,c,I,T) \ ++ I[0] = (T)(img)(_p2##x,_p2##y,z,c), I[1] = (T)(img)(_p1##x,_p2##y,z,c), I[2] = (T)(img)(x,_p2##y,z,c), \ ++ I[3] = (T)(img)(_n1##x,_p2##y,z,c), I[4] = (T)(img)(_n2##x,_p2##y,z,c), I[5] = (T)(img)(_p2##x,_p1##y,z,c), \ ++ I[6] = (T)(img)(_p1##x,_p1##y,z,c), I[7] = (T)(img)(x,_p1##y,z,c), I[8] = (T)(img)(_n1##x,_p1##y,z,c), \ ++ I[9] = (T)(img)(_n2##x,_p1##y,z,c), I[10] = (T)(img)(_p2##x,y,z,c), I[11] = (T)(img)(_p1##x,y,z,c), \ ++ I[12] = (T)(img)(x,y,z,c), I[13] = (T)(img)(_n1##x,y,z,c), I[14] = (T)(img)(_n2##x,y,z,c), \ ++ I[15] = (T)(img)(_p2##x,_n1##y,z,c), I[16] = (T)(img)(_p1##x,_n1##y,z,c), I[17] = (T)(img)(x,_n1##y,z,c), \ ++ I[18] = (T)(img)(_n1##x,_n1##y,z,c), I[19] = (T)(img)(_n2##x,_n1##y,z,c), I[20] = (T)(img)(_p2##x,_n2##y,z,c), \ ++ I[21] = (T)(img)(_p1##x,_n2##y,z,c), I[22] = (T)(img)(x,_n2##y,z,c), I[23] = (T)(img)(_n1##x,_n2##y,z,c), \ ++ I[24] = (T)(img)(_n2##x,_n2##y,z,c) ++ ++#define cimg_get6x6(img,x,y,z,c,I,T) \ ++ I[0] = (T)(img)(_p2##x,_p2##y,z,c), I[1] = (T)(img)(_p1##x,_p2##y,z,c), I[2] = (T)(img)(x,_p2##y,z,c), \ ++ I[3] = (T)(img)(_n1##x,_p2##y,z,c), I[4] = (T)(img)(_n2##x,_p2##y,z,c), I[5] = (T)(img)(_n3##x,_p2##y,z,c), \ ++ I[6] = (T)(img)(_p2##x,_p1##y,z,c), I[7] = (T)(img)(_p1##x,_p1##y,z,c), I[8] = (T)(img)(x,_p1##y,z,c), \ ++ I[9] = (T)(img)(_n1##x,_p1##y,z,c), I[10] = (T)(img)(_n2##x,_p1##y,z,c), I[11] = (T)(img)(_n3##x,_p1##y,z,c), \ ++ I[12] = (T)(img)(_p2##x,y,z,c), I[13] = (T)(img)(_p1##x,y,z,c), I[14] = (T)(img)(x,y,z,c), \ ++ I[15] = (T)(img)(_n1##x,y,z,c), I[16] = (T)(img)(_n2##x,y,z,c), I[17] = (T)(img)(_n3##x,y,z,c), \ ++ I[18] = (T)(img)(_p2##x,_n1##y,z,c), I[19] = (T)(img)(_p1##x,_n1##y,z,c), I[20] = (T)(img)(x,_n1##y,z,c), \ ++ I[21] = (T)(img)(_n1##x,_n1##y,z,c), I[22] = (T)(img)(_n2##x,_n1##y,z,c), I[23] = (T)(img)(_n3##x,_n1##y,z,c), \ ++ I[24] = (T)(img)(_p2##x,_n2##y,z,c), I[25] = (T)(img)(_p1##x,_n2##y,z,c), I[26] = (T)(img)(x,_n2##y,z,c), \ ++ I[27] = (T)(img)(_n1##x,_n2##y,z,c), I[28] = (T)(img)(_n2##x,_n2##y,z,c), I[29] = (T)(img)(_n3##x,_n2##y,z,c), \ ++ I[30] = (T)(img)(_p2##x,_n3##y,z,c), I[31] = (T)(img)(_p1##x,_n3##y,z,c), I[32] = (T)(img)(x,_n3##y,z,c), \ ++ I[33] = (T)(img)(_n1##x,_n3##y,z,c), I[34] = (T)(img)(_n2##x,_n3##y,z,c), I[35] = (T)(img)(_n3##x,_n3##y,z,c) ++ ++#define cimg_get7x7(img,x,y,z,c,I,T) \ ++ I[0] = (T)(img)(_p3##x,_p3##y,z,c), I[1] = (T)(img)(_p2##x,_p3##y,z,c), I[2] = (T)(img)(_p1##x,_p3##y,z,c), \ ++ I[3] = (T)(img)(x,_p3##y,z,c), I[4] = (T)(img)(_n1##x,_p3##y,z,c), I[5] = (T)(img)(_n2##x,_p3##y,z,c), \ ++ I[6] = (T)(img)(_n3##x,_p3##y,z,c), I[7] = (T)(img)(_p3##x,_p2##y,z,c), I[8] = (T)(img)(_p2##x,_p2##y,z,c), \ ++ I[9] = (T)(img)(_p1##x,_p2##y,z,c), I[10] = (T)(img)(x,_p2##y,z,c), I[11] = (T)(img)(_n1##x,_p2##y,z,c), \ ++ I[12] = (T)(img)(_n2##x,_p2##y,z,c), I[13] = (T)(img)(_n3##x,_p2##y,z,c), I[14] = (T)(img)(_p3##x,_p1##y,z,c), \ ++ I[15] = (T)(img)(_p2##x,_p1##y,z,c), I[16] = (T)(img)(_p1##x,_p1##y,z,c), I[17] = (T)(img)(x,_p1##y,z,c), \ ++ I[18] = (T)(img)(_n1##x,_p1##y,z,c), I[19] = (T)(img)(_n2##x,_p1##y,z,c), I[20] = (T)(img)(_n3##x,_p1##y,z,c), \ ++ I[21] = (T)(img)(_p3##x,y,z,c), I[22] = (T)(img)(_p2##x,y,z,c), I[23] = (T)(img)(_p1##x,y,z,c), \ ++ I[24] = (T)(img)(x,y,z,c), I[25] = (T)(img)(_n1##x,y,z,c), I[26] = (T)(img)(_n2##x,y,z,c), \ ++ I[27] = (T)(img)(_n3##x,y,z,c), I[28] = (T)(img)(_p3##x,_n1##y,z,c), I[29] = (T)(img)(_p2##x,_n1##y,z,c), \ ++ I[30] = (T)(img)(_p1##x,_n1##y,z,c), I[31] = (T)(img)(x,_n1##y,z,c), I[32] = (T)(img)(_n1##x,_n1##y,z,c), \ ++ I[33] = (T)(img)(_n2##x,_n1##y,z,c), I[34] = (T)(img)(_n3##x,_n1##y,z,c), I[35] = (T)(img)(_p3##x,_n2##y,z,c), \ ++ I[36] = (T)(img)(_p2##x,_n2##y,z,c), I[37] = (T)(img)(_p1##x,_n2##y,z,c), I[38] = (T)(img)(x,_n2##y,z,c), \ ++ I[39] = (T)(img)(_n1##x,_n2##y,z,c), I[40] = (T)(img)(_n2##x,_n2##y,z,c), I[41] = (T)(img)(_n3##x,_n2##y,z,c), \ ++ I[42] = (T)(img)(_p3##x,_n3##y,z,c), I[43] = (T)(img)(_p2##x,_n3##y,z,c), I[44] = (T)(img)(_p1##x,_n3##y,z,c), \ ++ I[45] = (T)(img)(x,_n3##y,z,c), I[46] = (T)(img)(_n1##x,_n3##y,z,c), I[47] = (T)(img)(_n2##x,_n3##y,z,c), \ ++ I[48] = (T)(img)(_n3##x,_n3##y,z,c) ++ ++#define cimg_get8x8(img,x,y,z,c,I,T) \ ++ I[0] = (T)(img)(_p3##x,_p3##y,z,c), I[1] = (T)(img)(_p2##x,_p3##y,z,c), I[2] = (T)(img)(_p1##x,_p3##y,z,c), \ ++ I[3] = (T)(img)(x,_p3##y,z,c), I[4] = (T)(img)(_n1##x,_p3##y,z,c), I[5] = (T)(img)(_n2##x,_p3##y,z,c), \ ++ I[6] = (T)(img)(_n3##x,_p3##y,z,c), I[7] = (T)(img)(_n4##x,_p3##y,z,c), I[8] = (T)(img)(_p3##x,_p2##y,z,c), \ ++ I[9] = (T)(img)(_p2##x,_p2##y,z,c), I[10] = (T)(img)(_p1##x,_p2##y,z,c), I[11] = (T)(img)(x,_p2##y,z,c), \ ++ I[12] = (T)(img)(_n1##x,_p2##y,z,c), I[13] = (T)(img)(_n2##x,_p2##y,z,c), I[14] = (T)(img)(_n3##x,_p2##y,z,c), \ ++ I[15] = (T)(img)(_n4##x,_p2##y,z,c), I[16] = (T)(img)(_p3##x,_p1##y,z,c), I[17] = (T)(img)(_p2##x,_p1##y,z,c), \ ++ I[18] = (T)(img)(_p1##x,_p1##y,z,c), I[19] = (T)(img)(x,_p1##y,z,c), I[20] = (T)(img)(_n1##x,_p1##y,z,c), \ ++ I[21] = (T)(img)(_n2##x,_p1##y,z,c), I[22] = (T)(img)(_n3##x,_p1##y,z,c), I[23] = (T)(img)(_n4##x,_p1##y,z,c), \ ++ I[24] = (T)(img)(_p3##x,y,z,c), I[25] = (T)(img)(_p2##x,y,z,c), I[26] = (T)(img)(_p1##x,y,z,c), \ ++ I[27] = (T)(img)(x,y,z,c), I[28] = (T)(img)(_n1##x,y,z,c), I[29] = (T)(img)(_n2##x,y,z,c), \ ++ I[30] = (T)(img)(_n3##x,y,z,c), I[31] = (T)(img)(_n4##x,y,z,c), I[32] = (T)(img)(_p3##x,_n1##y,z,c), \ ++ I[33] = (T)(img)(_p2##x,_n1##y,z,c), I[34] = (T)(img)(_p1##x,_n1##y,z,c), I[35] = (T)(img)(x,_n1##y,z,c), \ ++ I[36] = (T)(img)(_n1##x,_n1##y,z,c), I[37] = (T)(img)(_n2##x,_n1##y,z,c), I[38] = (T)(img)(_n3##x,_n1##y,z,c), \ ++ I[39] = (T)(img)(_n4##x,_n1##y,z,c), I[40] = (T)(img)(_p3##x,_n2##y,z,c), I[41] = (T)(img)(_p2##x,_n2##y,z,c), \ ++ I[42] = (T)(img)(_p1##x,_n2##y,z,c), I[43] = (T)(img)(x,_n2##y,z,c), I[44] = (T)(img)(_n1##x,_n2##y,z,c), \ ++ I[45] = (T)(img)(_n2##x,_n2##y,z,c), I[46] = (T)(img)(_n3##x,_n2##y,z,c), I[47] = (T)(img)(_n4##x,_n2##y,z,c), \ ++ I[48] = (T)(img)(_p3##x,_n3##y,z,c), I[49] = (T)(img)(_p2##x,_n3##y,z,c), I[50] = (T)(img)(_p1##x,_n3##y,z,c), \ ++ I[51] = (T)(img)(x,_n3##y,z,c), I[52] = (T)(img)(_n1##x,_n3##y,z,c), I[53] = (T)(img)(_n2##x,_n3##y,z,c), \ ++ I[54] = (T)(img)(_n3##x,_n3##y,z,c), I[55] = (T)(img)(_n4##x,_n3##y,z,c), I[56] = (T)(img)(_p3##x,_n4##y,z,c), \ ++ I[57] = (T)(img)(_p2##x,_n4##y,z,c), I[58] = (T)(img)(_p1##x,_n4##y,z,c), I[59] = (T)(img)(x,_n4##y,z,c), \ ++ I[60] = (T)(img)(_n1##x,_n4##y,z,c), I[61] = (T)(img)(_n2##x,_n4##y,z,c), I[62] = (T)(img)(_n3##x,_n4##y,z,c), \ ++ I[63] = (T)(img)(_n4##x,_n4##y,z,c); ++ ++#define cimg_get9x9(img,x,y,z,c,I,T) \ ++ I[0] = (T)(img)(_p4##x,_p4##y,z,c), I[1] = (T)(img)(_p3##x,_p4##y,z,c), I[2] = (T)(img)(_p2##x,_p4##y,z,c), \ ++ I[3] = (T)(img)(_p1##x,_p4##y,z,c), I[4] = (T)(img)(x,_p4##y,z,c), I[5] = (T)(img)(_n1##x,_p4##y,z,c), \ ++ I[6] = (T)(img)(_n2##x,_p4##y,z,c), I[7] = (T)(img)(_n3##x,_p4##y,z,c), I[8] = (T)(img)(_n4##x,_p4##y,z,c), \ ++ I[9] = (T)(img)(_p4##x,_p3##y,z,c), I[10] = (T)(img)(_p3##x,_p3##y,z,c), I[11] = (T)(img)(_p2##x,_p3##y,z,c), \ ++ I[12] = (T)(img)(_p1##x,_p3##y,z,c), I[13] = (T)(img)(x,_p3##y,z,c), I[14] = (T)(img)(_n1##x,_p3##y,z,c), \ ++ I[15] = (T)(img)(_n2##x,_p3##y,z,c), I[16] = (T)(img)(_n3##x,_p3##y,z,c), I[17] = (T)(img)(_n4##x,_p3##y,z,c), \ ++ I[18] = (T)(img)(_p4##x,_p2##y,z,c), I[19] = (T)(img)(_p3##x,_p2##y,z,c), I[20] = (T)(img)(_p2##x,_p2##y,z,c), \ ++ I[21] = (T)(img)(_p1##x,_p2##y,z,c), I[22] = (T)(img)(x,_p2##y,z,c), I[23] = (T)(img)(_n1##x,_p2##y,z,c), \ ++ I[24] = (T)(img)(_n2##x,_p2##y,z,c), I[25] = (T)(img)(_n3##x,_p2##y,z,c), I[26] = (T)(img)(_n4##x,_p2##y,z,c), \ ++ I[27] = (T)(img)(_p4##x,_p1##y,z,c), I[28] = (T)(img)(_p3##x,_p1##y,z,c), I[29] = (T)(img)(_p2##x,_p1##y,z,c), \ ++ I[30] = (T)(img)(_p1##x,_p1##y,z,c), I[31] = (T)(img)(x,_p1##y,z,c), I[32] = (T)(img)(_n1##x,_p1##y,z,c), \ ++ I[33] = (T)(img)(_n2##x,_p1##y,z,c), I[34] = (T)(img)(_n3##x,_p1##y,z,c), I[35] = (T)(img)(_n4##x,_p1##y,z,c), \ ++ I[36] = (T)(img)(_p4##x,y,z,c), I[37] = (T)(img)(_p3##x,y,z,c), I[38] = (T)(img)(_p2##x,y,z,c), \ ++ I[39] = (T)(img)(_p1##x,y,z,c), I[40] = (T)(img)(x,y,z,c), I[41] = (T)(img)(_n1##x,y,z,c), \ ++ I[42] = (T)(img)(_n2##x,y,z,c), I[43] = (T)(img)(_n3##x,y,z,c), I[44] = (T)(img)(_n4##x,y,z,c), \ ++ I[45] = (T)(img)(_p4##x,_n1##y,z,c), I[46] = (T)(img)(_p3##x,_n1##y,z,c), I[47] = (T)(img)(_p2##x,_n1##y,z,c), \ ++ I[48] = (T)(img)(_p1##x,_n1##y,z,c), I[49] = (T)(img)(x,_n1##y,z,c), I[50] = (T)(img)(_n1##x,_n1##y,z,c), \ ++ I[51] = (T)(img)(_n2##x,_n1##y,z,c), I[52] = (T)(img)(_n3##x,_n1##y,z,c), I[53] = (T)(img)(_n4##x,_n1##y,z,c), \ ++ I[54] = (T)(img)(_p4##x,_n2##y,z,c), I[55] = (T)(img)(_p3##x,_n2##y,z,c), I[56] = (T)(img)(_p2##x,_n2##y,z,c), \ ++ I[57] = (T)(img)(_p1##x,_n2##y,z,c), I[58] = (T)(img)(x,_n2##y,z,c), I[59] = (T)(img)(_n1##x,_n2##y,z,c), \ ++ I[60] = (T)(img)(_n2##x,_n2##y,z,c), I[61] = (T)(img)(_n3##x,_n2##y,z,c), I[62] = (T)(img)(_n4##x,_n2##y,z,c), \ ++ I[63] = (T)(img)(_p4##x,_n3##y,z,c), I[64] = (T)(img)(_p3##x,_n3##y,z,c), I[65] = (T)(img)(_p2##x,_n3##y,z,c), \ ++ I[66] = (T)(img)(_p1##x,_n3##y,z,c), I[67] = (T)(img)(x,_n3##y,z,c), I[68] = (T)(img)(_n1##x,_n3##y,z,c), \ ++ I[69] = (T)(img)(_n2##x,_n3##y,z,c), I[70] = (T)(img)(_n3##x,_n3##y,z,c), I[71] = (T)(img)(_n4##x,_n3##y,z,c), \ ++ I[72] = (T)(img)(_p4##x,_n4##y,z,c), I[73] = (T)(img)(_p3##x,_n4##y,z,c), I[74] = (T)(img)(_p2##x,_n4##y,z,c), \ ++ I[75] = (T)(img)(_p1##x,_n4##y,z,c), I[76] = (T)(img)(x,_n4##y,z,c), I[77] = (T)(img)(_n1##x,_n4##y,z,c), \ ++ I[78] = (T)(img)(_n2##x,_n4##y,z,c), I[79] = (T)(img)(_n3##x,_n4##y,z,c), I[80] = (T)(img)(_n4##x,_n4##y,z,c) ++ ++#define cimg_get2x2x2(img,x,y,z,c,I,T) \ ++ I[0] = (T)(img)(x,y,z,c), I[1] = (T)(img)(_n1##x,y,z,c), I[2] = (T)(img)(x,_n1##y,z,c), \ ++ I[3] = (T)(img)(_n1##x,_n1##y,z,c), I[4] = (T)(img)(x,y,_n1##z,c), I[5] = (T)(img)(_n1##x,y,_n1##z,c), \ ++ I[6] = (T)(img)(x,_n1##y,_n1##z,c), I[7] = (T)(img)(_n1##x,_n1##y,_n1##z,c) ++ ++#define cimg_get3x3x3(img,x,y,z,c,I,T) \ ++ I[0] = (T)(img)(_p1##x,_p1##y,_p1##z,c), I[1] = (T)(img)(x,_p1##y,_p1##z,c), \ ++ I[2] = (T)(img)(_n1##x,_p1##y,_p1##z,c), I[3] = (T)(img)(_p1##x,y,_p1##z,c), I[4] = (T)(img)(x,y,_p1##z,c), \ ++ I[5] = (T)(img)(_n1##x,y,_p1##z,c), I[6] = (T)(img)(_p1##x,_n1##y,_p1##z,c), I[7] = (T)(img)(x,_n1##y,_p1##z,c), \ ++ I[8] = (T)(img)(_n1##x,_n1##y,_p1##z,c), I[9] = (T)(img)(_p1##x,_p1##y,z,c), I[10] = (T)(img)(x,_p1##y,z,c), \ ++ I[11] = (T)(img)(_n1##x,_p1##y,z,c), I[12] = (T)(img)(_p1##x,y,z,c), I[13] = (T)(img)(x,y,z,c), \ ++ I[14] = (T)(img)(_n1##x,y,z,c), I[15] = (T)(img)(_p1##x,_n1##y,z,c), I[16] = (T)(img)(x,_n1##y,z,c), \ ++ I[17] = (T)(img)(_n1##x,_n1##y,z,c), I[18] = (T)(img)(_p1##x,_p1##y,_n1##z,c), I[19] = (T)(img)(x,_p1##y,_n1##z,c), \ ++ I[20] = (T)(img)(_n1##x,_p1##y,_n1##z,c), I[21] = (T)(img)(_p1##x,y,_n1##z,c), I[22] = (T)(img)(x,y,_n1##z,c), \ ++ I[23] = (T)(img)(_n1##x,y,_n1##z,c), I[24] = (T)(img)(_p1##x,_n1##y,_n1##z,c), I[25] = (T)(img)(x,_n1##y,_n1##z,c), \ ++ I[26] = (T)(img)(_n1##x,_n1##y,_n1##z,c) ++ ++// Macros to perform various image loops. ++// ++// These macros are simpler to use than loops with C++ iterators. ++#define cimg_for(img,ptrs,T_ptrs) \ ++ for (T_ptrs *ptrs = (img)._data, *_max##ptrs = (img)._data + (img).size(); ptrs<_max##ptrs; ++ptrs) ++#define cimg_rof(img,ptrs,T_ptrs) for (T_ptrs *ptrs = (img)._data + (img).size() - 1; ptrs>=(img)._data; --ptrs) ++#define cimg_foroff(img,off) for (cimg_ulong off = 0, _max##off = (img).size(); off<_max##off; ++off) ++ ++#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_forC(img,c) cimg_for1((img)._spectrum,c) ++#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_forXC(img,x,c) cimg_forC(img,c) cimg_forX(img,x) ++#define cimg_forYC(img,y,c) cimg_forC(img,c) cimg_forY(img,y) ++#define cimg_forZC(img,z,c) cimg_forC(img,c) cimg_forZ(img,z) ++#define cimg_forXYZ(img,x,y,z) cimg_forZ(img,z) cimg_forXY(img,x,y) ++#define cimg_forXYC(img,x,y,c) cimg_forC(img,c) cimg_forXY(img,x,y) ++#define cimg_forXZC(img,x,z,c) cimg_forC(img,c) cimg_forXZ(img,x,z) ++#define cimg_forYZC(img,y,z,c) cimg_forC(img,c) cimg_forYZ(img,y,z) ++#define cimg_forXYZC(img,x,y,z,c) cimg_forC(img,c) cimg_forXYZ(img,x,y,z) ++ ++#define cimg_rof1(bound,i) for (int i = (int)(bound) - 1; i>=0; --i) ++#define cimg_rofX(img,x) cimg_rof1((img)._width,x) ++#define cimg_rofY(img,y) cimg_rof1((img)._height,y) ++#define cimg_rofZ(img,z) cimg_rof1((img)._depth,z) ++#define cimg_rofC(img,c) cimg_rof1((img)._spectrum,c) ++#define cimg_rofXY(img,x,y) cimg_rofY(img,y) cimg_rofX(img,x) ++#define cimg_rofXZ(img,x,z) cimg_rofZ(img,z) cimg_rofX(img,x) ++#define cimg_rofYZ(img,y,z) cimg_rofZ(img,z) cimg_rofY(img,y) ++#define cimg_rofXC(img,x,c) cimg_rofC(img,c) cimg_rofX(img,x) ++#define cimg_rofYC(img,y,c) cimg_rofC(img,c) cimg_rofY(img,y) ++#define cimg_rofZC(img,z,c) cimg_rofC(img,c) cimg_rofZ(img,z) ++#define cimg_rofXYZ(img,x,y,z) cimg_rofZ(img,z) cimg_rofXY(img,x,y) ++#define cimg_rofXYC(img,x,y,c) cimg_rofC(img,c) cimg_rofXY(img,x,y) ++#define cimg_rofXZC(img,x,z,c) cimg_rofC(img,c) cimg_rofXZ(img,x,z) ++#define cimg_rofYZC(img,y,z,c) cimg_rofC(img,c) cimg_rofYZ(img,y,z) ++#define cimg_rofXYZC(img,x,y,z,c) cimg_rofC(img,c) cimg_rofXYZ(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_inC(img,c0,c1,c) cimg_for_in1((img)._spectrum,c0,c1,c) ++#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_inXC(img,x0,c0,x1,c1,x,c) cimg_for_inC(img,c0,c1,c) 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_inYC(img,y0,c0,y1,c1,y,c) cimg_for_inC(img,c0,c1,c) cimg_for_inY(img,y0,y1,y) ++#define cimg_for_inZC(img,z0,c0,z1,c1,z,c) cimg_for_inC(img,c0,c1,c) 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_inXYC(img,x0,y0,c0,x1,y1,c1,x,y,c) cimg_for_inC(img,c0,c1,c) cimg_for_inXY(img,x0,y0,x1,y1,x,y) ++#define cimg_for_inXZC(img,x0,z0,c0,x1,z1,c1,x,z,c) cimg_for_inC(img,c0,c1,c) cimg_for_inXZ(img,x0,z0,x1,z1,x,z) ++#define cimg_for_inYZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_inC(img,c0,c1,c) cimg_for_inYZ(img,y0,z0,y1,z1,y,z) ++#define cimg_for_inXYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ ++ cimg_for_inC(img,c0,c1,c) 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_insideC(img,c,n) cimg_for_inC(img,n,(img)._spectrum - 1 - (n),c) ++#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_insideXYZC(img,x,y,z,c,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_outC(img,c0,c1,c) cimg_for_out1((img)._spectrum,c0,c1,c) ++#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_outXC(img,x0,c0,x1,c1,x,c) cimg_for_out2((img)._width,(img)._spectrum,x0,c0,x1,c1,x,c) ++#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_outYC(img,y0,c0,y1,c1,y,c) cimg_for_out2((img)._height,(img)._spectrum,y0,c0,y1,c1,y,c) ++#define cimg_for_outZC(img,z0,c0,z1,c1,z,c) cimg_for_out2((img)._depth,(img)._spectrum,z0,c0,z1,c1,z,c) ++#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_outXYC(img,x0,y0,c0,x1,y1,c1,x,y,c) \ ++ cimg_for_out3((img)._width,(img)._height,(img)._spectrum,x0,y0,c0,x1,y1,c1,x,y,c) ++#define cimg_for_outXZC(img,x0,z0,c0,x1,z1,c1,x,z,c) \ ++ cimg_for_out3((img)._width,(img)._depth,(img)._spectrum,x0,z0,c0,x1,z1,c1,x,z,c) ++#define cimg_for_outYZC(img,y0,z0,c0,y1,z1,c1,y,z,c) \ ++ cimg_for_out3((img)._height,(img)._depth,(img)._spectrum,y0,z0,c0,y1,z1,c1,y,z,c) ++#define cimg_for_outXYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ ++ cimg_for_out4((img)._width,(img)._height,(img)._depth,(img)._spectrum,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) ++#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_borderC(img,c,n) cimg_for_outC(img,n,(img)._spectrum - 1 - (n),c) ++#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_borderXYZC(img,x,y,z,c,n) \ ++ cimg_for_outXYZC(img,n,n,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n), \ ++ (img)._depth - 1 - (n),(img)._spectrum - 1 - (n),x,y,z,c) ++ ++#define cimg_for_spiralXY(img,x,y) \ ++ for (int x = 0, y = 0, _n1##x = 1, _n1##y = (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_for2C(img,c) cimg_for2((img)._spectrum,c) ++#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_for2XC(img,x,c) cimg_for2C(img,c) cimg_for2X(img,x) ++#define cimg_for2YZ(img,y,z) cimg_for2Z(img,z) cimg_for2Y(img,y) ++#define cimg_for2YC(img,y,c) cimg_for2C(img,c) cimg_for2Y(img,y) ++#define cimg_for2ZC(img,z,c) cimg_for2C(img,c) cimg_for2Z(img,z) ++#define cimg_for2XYZ(img,x,y,z) cimg_for2Z(img,z) cimg_for2XY(img,x,y) ++#define cimg_for2XZC(img,x,z,c) cimg_for2C(img,c) cimg_for2XZ(img,x,z) ++#define cimg_for2YZC(img,y,z,c) cimg_for2C(img,c) cimg_for2YZ(img,y,z) ++#define cimg_for2XYZC(img,x,y,z,c) cimg_for2C(img,c) 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_in2C(img,c0,c1,c) cimg_for_in2((img)._spectrum,c0,c1,c) ++#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_in2XC(img,x0,c0,x1,c1,x,c) cimg_for_in2C(img,c0,c1,c) 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_in2YC(img,y0,c0,y1,c1,y,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2Y(img,y0,y1,y) ++#define cimg_for_in2ZC(img,z0,c0,z1,c1,z,c) cimg_for_in2C(img,c0,c1,c) 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_in2XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2XZ(img,x0,y0,x1,y1,x,z) ++#define cimg_for_in2YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2YZ(img,y0,z0,y1,z1,y,z) ++#define cimg_for_in2XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ ++ cimg_for_in2C(img,c0,c1,c) 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_for3C(img,c) cimg_for3((img)._spectrum,c) ++#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_for3XC(img,x,c) cimg_for3C(img,c) cimg_for3X(img,x) ++#define cimg_for3YZ(img,y,z) cimg_for3Z(img,z) cimg_for3Y(img,y) ++#define cimg_for3YC(img,y,c) cimg_for3C(img,c) cimg_for3Y(img,y) ++#define cimg_for3ZC(img,z,c) cimg_for3C(img,c) cimg_for3Z(img,z) ++#define cimg_for3XYZ(img,x,y,z) cimg_for3Z(img,z) cimg_for3XY(img,x,y) ++#define cimg_for3XZC(img,x,z,c) cimg_for3C(img,c) cimg_for3XZ(img,x,z) ++#define cimg_for3YZC(img,y,z,c) cimg_for3C(img,c) cimg_for3YZ(img,y,z) ++#define cimg_for3XYZC(img,x,y,z,c) cimg_for3C(img,c) 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_in3C(img,c0,c1,c) cimg_for_in3((img)._spectrum,c0,c1,c) ++#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_in3XC(img,x0,c0,x1,c1,x,c) cimg_for_in3C(img,c0,c1,c) 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_in3YC(img,y0,c0,y1,c1,y,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3Y(img,y0,y1,y) ++#define cimg_for_in3ZC(img,z0,c0,z1,c1,z,c) cimg_for_in3C(img,c0,c1,c) 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_in3XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3XZ(img,x0,y0,x1,y1,x,z) ++#define cimg_for_in3YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3YZ(img,y0,z0,y1,z1,y,z) ++#define cimg_for_in3XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ ++ cimg_for_in3C(img,c0,c1,c) 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_for4C(img,c) cimg_for4((img)._spectrum,c) ++#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_for4XC(img,x,c) cimg_for4C(img,c) cimg_for4X(img,x) ++#define cimg_for4YZ(img,y,z) cimg_for4Z(img,z) cimg_for4Y(img,y) ++#define cimg_for4YC(img,y,c) cimg_for4C(img,c) cimg_for4Y(img,y) ++#define cimg_for4ZC(img,z,c) cimg_for4C(img,c) cimg_for4Z(img,z) ++#define cimg_for4XYZ(img,x,y,z) cimg_for4Z(img,z) cimg_for4XY(img,x,y) ++#define cimg_for4XZC(img,x,z,c) cimg_for4C(img,c) cimg_for4XZ(img,x,z) ++#define cimg_for4YZC(img,y,z,c) cimg_for4C(img,c) cimg_for4YZ(img,y,z) ++#define cimg_for4XYZC(img,x,y,z,c) cimg_for4C(img,c) 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_in4C(img,c0,c1,c) cimg_for_in4((img)._spectrum,c0,c1,c) ++#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_in4XC(img,x0,c0,x1,c1,x,c) cimg_for_in4C(img,c0,c1,c) 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_in4YC(img,y0,c0,y1,c1,y,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4Y(img,y0,y1,y) ++#define cimg_for_in4ZC(img,z0,c0,z1,c1,z,c) cimg_for_in4C(img,c0,c1,c) 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_in4XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4XZ(img,x0,y0,x1,y1,x,z) ++#define cimg_for_in4YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4YZ(img,y0,z0,y1,z1,y,z) ++#define cimg_for_in4XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ ++ cimg_for_in4C(img,c0,c1,c) 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_for5C(img,c) cimg_for5((img)._spectrum,c) ++#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_for5XC(img,x,c) cimg_for5C(img,c) cimg_for5X(img,x) ++#define cimg_for5YZ(img,y,z) cimg_for5Z(img,z) cimg_for5Y(img,y) ++#define cimg_for5YC(img,y,c) cimg_for5C(img,c) cimg_for5Y(img,y) ++#define cimg_for5ZC(img,z,c) cimg_for5C(img,c) cimg_for5Z(img,z) ++#define cimg_for5XYZ(img,x,y,z) cimg_for5Z(img,z) cimg_for5XY(img,x,y) ++#define cimg_for5XZC(img,x,z,c) cimg_for5C(img,c) cimg_for5XZ(img,x,z) ++#define cimg_for5YZC(img,y,z,c) cimg_for5C(img,c) cimg_for5YZ(img,y,z) ++#define cimg_for5XYZC(img,x,y,z,c) cimg_for5C(img,c) 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_in5C(img,c0,c1,c) cimg_for_in5((img)._spectrum,c0,c1,c) ++#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_in5XC(img,x0,c0,x1,c1,x,c) cimg_for_in5C(img,c0,c1,c) 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_in5YC(img,y0,c0,y1,c1,y,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5Y(img,y0,y1,y) ++#define cimg_for_in5ZC(img,z0,c0,z1,c1,z,c) cimg_for_in5C(img,c0,c1,c) 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_in5XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5XZ(img,x0,y0,x1,y1,x,z) ++#define cimg_for_in5YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5YZ(img,y0,z0,y1,z1,y,z) ++#define cimg_for_in5XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ ++ cimg_for_in5C(img,c0,c1,c) 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_for6C(img,c) cimg_for6((img)._spectrum,c) ++#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_for6XC(img,x,c) cimg_for6C(img,c) cimg_for6X(img,x) ++#define cimg_for6YZ(img,y,z) cimg_for6Z(img,z) cimg_for6Y(img,y) ++#define cimg_for6YC(img,y,c) cimg_for6C(img,c) cimg_for6Y(img,y) ++#define cimg_for6ZC(img,z,c) cimg_for6C(img,c) cimg_for6Z(img,z) ++#define cimg_for6XYZ(img,x,y,z) cimg_for6Z(img,z) cimg_for6XY(img,x,y) ++#define cimg_for6XZC(img,x,z,c) cimg_for6C(img,c) cimg_for6XZ(img,x,z) ++#define cimg_for6YZC(img,y,z,c) cimg_for6C(img,c) cimg_for6YZ(img,y,z) ++#define cimg_for6XYZC(img,x,y,z,c) cimg_for6C(img,c) 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_in6C(img,c0,c1,c) cimg_for_in6((img)._spectrum,c0,c1,c) ++#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_in6XC(img,x0,c0,x1,c1,x,c) cimg_for_in6C(img,c0,c1,c) 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_in6YC(img,y0,c0,y1,c1,y,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6Y(img,y0,y1,y) ++#define cimg_for_in6ZC(img,z0,c0,z1,c1,z,c) cimg_for_in6C(img,c0,c1,c) 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_in6XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6XZ(img,x0,y0,x1,y1,x,z) ++#define cimg_for_in6YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6YZ(img,y0,z0,y1,z1,y,z) ++#define cimg_for_in6XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ ++ cimg_for_in6C(img,c0,c1,c) 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_for7C(img,c) cimg_for7((img)._spectrum,c) ++#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_for7XC(img,x,c) cimg_for7C(img,c) cimg_for7X(img,x) ++#define cimg_for7YZ(img,y,z) cimg_for7Z(img,z) cimg_for7Y(img,y) ++#define cimg_for7YC(img,y,c) cimg_for7C(img,c) cimg_for7Y(img,y) ++#define cimg_for7ZC(img,z,c) cimg_for7C(img,c) cimg_for7Z(img,z) ++#define cimg_for7XYZ(img,x,y,z) cimg_for7Z(img,z) cimg_for7XY(img,x,y) ++#define cimg_for7XZC(img,x,z,c) cimg_for7C(img,c) cimg_for7XZ(img,x,z) ++#define cimg_for7YZC(img,y,z,c) cimg_for7C(img,c) cimg_for7YZ(img,y,z) ++#define cimg_for7XYZC(img,x,y,z,c) cimg_for7C(img,c) 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_in7C(img,c0,c1,c) cimg_for_in7((img)._spectrum,c0,c1,c) ++#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_in7XC(img,x0,c0,x1,c1,x,c) cimg_for_in7C(img,c0,c1,c) 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_in7YC(img,y0,c0,y1,c1,y,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7Y(img,y0,y1,y) ++#define cimg_for_in7ZC(img,z0,c0,z1,c1,z,c) cimg_for_in7C(img,c0,c1,c) 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_in7XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7XZ(img,x0,y0,x1,y1,x,z) ++#define cimg_for_in7YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7YZ(img,y0,z0,y1,z1,y,z) ++#define cimg_for_in7XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ ++ cimg_for_in7C(img,c0,c1,c) 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_for8C(img,c) cimg_for8((img)._spectrum,c) ++#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_for8XC(img,x,c) cimg_for8C(img,c) cimg_for8X(img,x) ++#define cimg_for8YZ(img,y,z) cimg_for8Z(img,z) cimg_for8Y(img,y) ++#define cimg_for8YC(img,y,c) cimg_for8C(img,c) cimg_for8Y(img,y) ++#define cimg_for8ZC(img,z,c) cimg_for8C(img,c) cimg_for8Z(img,z) ++#define cimg_for8XYZ(img,x,y,z) cimg_for8Z(img,z) cimg_for8XY(img,x,y) ++#define cimg_for8XZC(img,x,z,c) cimg_for8C(img,c) cimg_for8XZ(img,x,z) ++#define cimg_for8YZC(img,y,z,c) cimg_for8C(img,c) cimg_for8YZ(img,y,z) ++#define cimg_for8XYZC(img,x,y,z,c) cimg_for8C(img,c) 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_in8C(img,c0,c1,c) cimg_for_in8((img)._spectrum,c0,c1,c) ++#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_in8XC(img,x0,c0,x1,c1,x,c) cimg_for_in8C(img,c0,c1,c) 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_in8YC(img,y0,c0,y1,c1,y,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8Y(img,y0,y1,y) ++#define cimg_for_in8ZC(img,z0,c0,z1,c1,z,c) cimg_for_in8C(img,c0,c1,c) 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_in8XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8XZ(img,x0,y0,x1,y1,x,z) ++#define cimg_for_in8YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8YZ(img,y0,z0,y1,z1,y,z) ++#define cimg_for_in8XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ ++ cimg_for_in8C(img,c0,c1,c) 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_for9C(img,c) cimg_for9((img)._spectrum,c) ++#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_for9XC(img,x,c) cimg_for9C(img,c) cimg_for9X(img,x) ++#define cimg_for9YZ(img,y,z) cimg_for9Z(img,z) cimg_for9Y(img,y) ++#define cimg_for9YC(img,y,c) cimg_for9C(img,c) cimg_for9Y(img,y) ++#define cimg_for9ZC(img,z,c) cimg_for9C(img,c) cimg_for9Z(img,z) ++#define cimg_for9XYZ(img,x,y,z) cimg_for9Z(img,z) cimg_for9XY(img,x,y) ++#define cimg_for9XZC(img,x,z,c) cimg_for9C(img,c) cimg_for9XZ(img,x,z) ++#define cimg_for9YZC(img,y,z,c) cimg_for9C(img,c) cimg_for9YZ(img,y,z) ++#define cimg_for9XYZC(img,x,y,z,c) cimg_for9C(img,c) 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_in9C(img,c0,c1,c) cimg_for_in9((img)._spectrum,c0,c1,c) ++#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_in9XC(img,x0,c0,x1,c1,x,c) cimg_for_in9C(img,c0,c1,c) 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_in9YC(img,y0,c0,y1,c1,y,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9Y(img,y0,y1,y) ++#define cimg_for_in9ZC(img,z0,c0,z1,c1,z,c) cimg_for_in9C(img,c0,c1,c) 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_in9XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9XZ(img,x0,y0,x1,y1,x,z) ++#define cimg_for_in9YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9YZ(img,y0,z0,y1,z1,y,z) ++#define cimg_for_in9XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \ ++ cimg_for_in9C(img,c0,c1,c) cimg_for_in9XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) ++ ++#define cimg_for2x2(img,x,y,z,c,I,T) \ ++ cimg_for2((img)._height,y) for (int x = 0, \ ++ _n1##x = (int)( \ ++ (I[0] = (T)(img)(0,y,z,c)), \ ++ (I[2] = (T)(img)(0,_n1##y,z,c)), \ ++ 1>=(img)._width?(img).width() - 1:1); \ ++ (_n1##x<(img).width() && ( \ ++ (I[1] = (T)(img)(_n1##x,y,z,c)), \ ++ (I[3] = (T)(img)(_n1##x,_n1##y,z,c)),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,c,I,T) \ ++ cimg_for_in2((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \ ++ _n1##x = (int)( \ ++ (I[0] = (T)(img)(x,y,z,c)), \ ++ (I[2] = (T)(img)(x,_n1##y,z,c)), \ ++ x + 1>=(int)(img)._width?(img).width() - 1:x + 1); \ ++ x<=(int)(x1) && ((_n1##x<(img).width() && ( \ ++ (I[1] = (T)(img)(_n1##x,y,z,c)), \ ++ (I[3] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \ ++ x==--_n1##x); \ ++ I[0] = I[1], \ ++ I[2] = I[3], \ ++ ++x, ++_n1##x) ++ ++#define cimg_for3x3(img,x,y,z,c,I,T) \ ++ cimg_for3((img)._height,y) for (int x = 0, \ ++ _p1##x = 0, \ ++ _n1##x = (int)( \ ++ (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \ ++ (I[3] = I[4] = (T)(img)(0,y,z,c)), \ ++ (I[6] = I[7] = (T)(img)(0,_n1##y,z,c)), \ ++ 1>=(img)._width?(img).width() - 1:1); \ ++ (_n1##x<(img).width() && ( \ ++ (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ ++ (I[5] = (T)(img)(_n1##x,y,z,c)), \ ++ (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),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,c,I,T) \ ++ 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] = (T)(img)(_p1##x,_p1##y,z,c)), \ ++ (I[3] = (T)(img)(_p1##x,y,z,c)), \ ++ (I[6] = (T)(img)(_p1##x,_n1##y,z,c)), \ ++ (I[1] = (T)(img)(x,_p1##y,z,c)), \ ++ (I[4] = (T)(img)(x,y,z,c)), \ ++ (I[7] = (T)(img)(x,_n1##y,z,c)), \ ++ x + 1>=(int)(img)._width?(img).width() - 1:x + 1); \ ++ x<=(int)(x1) && ((_n1##x<(img).width() && ( \ ++ (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ ++ (I[5] = (T)(img)(_n1##x,y,z,c)), \ ++ (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),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,c,I,T) \ ++ cimg_for4((img)._height,y) for (int x = 0, \ ++ _p1##x = 0, \ ++ _n1##x = 1>=(img)._width?(img).width() - 1:1, \ ++ _n2##x = (int)( \ ++ (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \ ++ (I[4] = I[5] = (T)(img)(0,y,z,c)), \ ++ (I[8] = I[9] = (T)(img)(0,_n1##y,z,c)), \ ++ (I[12] = I[13] = (T)(img)(0,_n2##y,z,c)), \ ++ (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ ++ (I[6] = (T)(img)(_n1##x,y,z,c)), \ ++ (I[10] = (T)(img)(_n1##x,_n1##y,z,c)), \ ++ (I[14] = (T)(img)(_n1##x,_n2##y,z,c)), \ ++ 2>=(img)._width?(img).width() - 1:2); \ ++ (_n2##x<(img).width() && ( \ ++ (I[3] = (T)(img)(_n2##x,_p1##y,z,c)), \ ++ (I[7] = (T)(img)(_n2##x,y,z,c)), \ ++ (I[11] = (T)(img)(_n2##x,_n1##y,z,c)), \ ++ (I[15] = (T)(img)(_n2##x,_n2##y,z,c)),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,c,I,T) \ ++ 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?(img).width() - 1:x + 1, \ ++ _n2##x = (int)( \ ++ (I[0] = (T)(img)(_p1##x,_p1##y,z,c)), \ ++ (I[4] = (T)(img)(_p1##x,y,z,c)), \ ++ (I[8] = (T)(img)(_p1##x,_n1##y,z,c)), \ ++ (I[12] = (T)(img)(_p1##x,_n2##y,z,c)), \ ++ (I[1] = (T)(img)(x,_p1##y,z,c)), \ ++ (I[5] = (T)(img)(x,y,z,c)), \ ++ (I[9] = (T)(img)(x,_n1##y,z,c)), \ ++ (I[13] = (T)(img)(x,_n2##y,z,c)), \ ++ (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ ++ (I[6] = (T)(img)(_n1##x,y,z,c)), \ ++ (I[10] = (T)(img)(_n1##x,_n1##y,z,c)), \ ++ (I[14] = (T)(img)(_n1##x,_n2##y,z,c)), \ ++ x + 2>=(int)(img)._width?(img).width() - 1:x + 2); \ ++ x<=(int)(x1) && ((_n2##x<(img).width() && ( \ ++ (I[3] = (T)(img)(_n2##x,_p1##y,z,c)), \ ++ (I[7] = (T)(img)(_n2##x,y,z,c)), \ ++ (I[11] = (T)(img)(_n2##x,_n1##y,z,c)), \ ++ (I[15] = (T)(img)(_n2##x,_n2##y,z,c)),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,c,I,T) \ ++ cimg_for5((img)._height,y) for (int x = 0, \ ++ _p2##x = 0, _p1##x = 0, \ ++ _n1##x = 1>=(img)._width?(img).width() - 1:1, \ ++ _n2##x = (int)( \ ++ (I[0] = I[1] = I[2] = (T)(img)(_p2##x,_p2##y,z,c)), \ ++ (I[5] = I[6] = I[7] = (T)(img)(0,_p1##y,z,c)), \ ++ (I[10] = I[11] = I[12] = (T)(img)(0,y,z,c)), \ ++ (I[15] = I[16] = I[17] = (T)(img)(0,_n1##y,z,c)), \ ++ (I[20] = I[21] = I[22] = (T)(img)(0,_n2##y,z,c)), \ ++ (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \ ++ (I[8] = (T)(img)(_n1##x,_p1##y,z,c)), \ ++ (I[13] = (T)(img)(_n1##x,y,z,c)), \ ++ (I[18] = (T)(img)(_n1##x,_n1##y,z,c)), \ ++ (I[23] = (T)(img)(_n1##x,_n2##y,z,c)), \ ++ 2>=(img)._width?(img).width() - 1:2); \ ++ (_n2##x<(img).width() && ( \ ++ (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \ ++ (I[9] = (T)(img)(_n2##x,_p1##y,z,c)), \ ++ (I[14] = (T)(img)(_n2##x,y,z,c)), \ ++ (I[19] = (T)(img)(_n2##x,_n1##y,z,c)), \ ++ (I[24] = (T)(img)(_n2##x,_n2##y,z,c)),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,c,I,T) \ ++ 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?(img).width() - 1:x + 1, \ ++ _n2##x = (int)( \ ++ (I[0] = (T)(img)(_p2##x,_p2##y,z,c)), \ ++ (I[5] = (T)(img)(_p2##x,_p1##y,z,c)), \ ++ (I[10] = (T)(img)(_p2##x,y,z,c)), \ ++ (I[15] = (T)(img)(_p2##x,_n1##y,z,c)), \ ++ (I[20] = (T)(img)(_p2##x,_n2##y,z,c)), \ ++ (I[1] = (T)(img)(_p1##x,_p2##y,z,c)), \ ++ (I[6] = (T)(img)(_p1##x,_p1##y,z,c)), \ ++ (I[11] = (T)(img)(_p1##x,y,z,c)), \ ++ (I[16] = (T)(img)(_p1##x,_n1##y,z,c)), \ ++ (I[21] = (T)(img)(_p1##x,_n2##y,z,c)), \ ++ (I[2] = (T)(img)(x,_p2##y,z,c)), \ ++ (I[7] = (T)(img)(x,_p1##y,z,c)), \ ++ (I[12] = (T)(img)(x,y,z,c)), \ ++ (I[17] = (T)(img)(x,_n1##y,z,c)), \ ++ (I[22] = (T)(img)(x,_n2##y,z,c)), \ ++ (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \ ++ (I[8] = (T)(img)(_n1##x,_p1##y,z,c)), \ ++ (I[13] = (T)(img)(_n1##x,y,z,c)), \ ++ (I[18] = (T)(img)(_n1##x,_n1##y,z,c)), \ ++ (I[23] = (T)(img)(_n1##x,_n2##y,z,c)), \ ++ x + 2>=(int)(img)._width?(img).width() - 1:x + 2); \ ++ x<=(int)(x1) && ((_n2##x<(img).width() && ( \ ++ (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \ ++ (I[9] = (T)(img)(_n2##x,_p1##y,z,c)), \ ++ (I[14] = (T)(img)(_n2##x,y,z,c)), \ ++ (I[19] = (T)(img)(_n2##x,_n1##y,z,c)), \ ++ (I[24] = (T)(img)(_n2##x,_n2##y,z,c)),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,c,I,T) \ ++ cimg_for6((img)._height,y) for (int x = 0, \ ++ _p2##x = 0, _p1##x = 0, \ ++ _n1##x = 1>=(img)._width?(img).width() - 1:1, \ ++ _n2##x = 2>=(img)._width?(img).width() - 1:2, \ ++ _n3##x = (int)( \ ++ (I[0] = I[1] = I[2] = (T)(img)(_p2##x,_p2##y,z,c)), \ ++ (I[6] = I[7] = I[8] = (T)(img)(0,_p1##y,z,c)), \ ++ (I[12] = I[13] = I[14] = (T)(img)(0,y,z,c)), \ ++ (I[18] = I[19] = I[20] = (T)(img)(0,_n1##y,z,c)), \ ++ (I[24] = I[25] = I[26] = (T)(img)(0,_n2##y,z,c)), \ ++ (I[30] = I[31] = I[32] = (T)(img)(0,_n3##y,z,c)), \ ++ (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \ ++ (I[9] = (T)(img)(_n1##x,_p1##y,z,c)), \ ++ (I[15] = (T)(img)(_n1##x,y,z,c)), \ ++ (I[21] = (T)(img)(_n1##x,_n1##y,z,c)), \ ++ (I[27] = (T)(img)(_n1##x,_n2##y,z,c)), \ ++ (I[33] = (T)(img)(_n1##x,_n3##y,z,c)), \ ++ (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \ ++ (I[10] = (T)(img)(_n2##x,_p1##y,z,c)), \ ++ (I[16] = (T)(img)(_n2##x,y,z,c)), \ ++ (I[22] = (T)(img)(_n2##x,_n1##y,z,c)), \ ++ (I[28] = (T)(img)(_n2##x,_n2##y,z,c)), \ ++ (I[34] = (T)(img)(_n2##x,_n3##y,z,c)), \ ++ 3>=(img)._width?(img).width() - 1:3); \ ++ (_n3##x<(img).width() && ( \ ++ (I[5] = (T)(img)(_n3##x,_p2##y,z,c)), \ ++ (I[11] = (T)(img)(_n3##x,_p1##y,z,c)), \ ++ (I[17] = (T)(img)(_n3##x,y,z,c)), \ ++ (I[23] = (T)(img)(_n3##x,_n1##y,z,c)), \ ++ (I[29] = (T)(img)(_n3##x,_n2##y,z,c)), \ ++ (I[35] = (T)(img)(_n3##x,_n3##y,z,c)),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,c,I,T) \ ++ 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?(img).width() - 1:x + 1, \ ++ _n2##x = x + 2>=(int)(img)._width?(img).width() - 1:x + 2, \ ++ _n3##x = (int)( \ ++ (I[0] = (T)(img)(_p2##x,_p2##y,z,c)), \ ++ (I[6] = (T)(img)(_p2##x,_p1##y,z,c)), \ ++ (I[12] = (T)(img)(_p2##x,y,z,c)), \ ++ (I[18] = (T)(img)(_p2##x,_n1##y,z,c)), \ ++ (I[24] = (T)(img)(_p2##x,_n2##y,z,c)), \ ++ (I[30] = (T)(img)(_p2##x,_n3##y,z,c)), \ ++ (I[1] = (T)(img)(_p1##x,_p2##y,z,c)), \ ++ (I[7] = (T)(img)(_p1##x,_p1##y,z,c)), \ ++ (I[13] = (T)(img)(_p1##x,y,z,c)), \ ++ (I[19] = (T)(img)(_p1##x,_n1##y,z,c)), \ ++ (I[25] = (T)(img)(_p1##x,_n2##y,z,c)), \ ++ (I[31] = (T)(img)(_p1##x,_n3##y,z,c)), \ ++ (I[2] = (T)(img)(x,_p2##y,z,c)), \ ++ (I[8] = (T)(img)(x,_p1##y,z,c)), \ ++ (I[14] = (T)(img)(x,y,z,c)), \ ++ (I[20] = (T)(img)(x,_n1##y,z,c)), \ ++ (I[26] = (T)(img)(x,_n2##y,z,c)), \ ++ (I[32] = (T)(img)(x,_n3##y,z,c)), \ ++ (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \ ++ (I[9] = (T)(img)(_n1##x,_p1##y,z,c)), \ ++ (I[15] = (T)(img)(_n1##x,y,z,c)), \ ++ (I[21] = (T)(img)(_n1##x,_n1##y,z,c)), \ ++ (I[27] = (T)(img)(_n1##x,_n2##y,z,c)), \ ++ (I[33] = (T)(img)(_n1##x,_n3##y,z,c)), \ ++ (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \ ++ (I[10] = (T)(img)(_n2##x,_p1##y,z,c)), \ ++ (I[16] = (T)(img)(_n2##x,y,z,c)), \ ++ (I[22] = (T)(img)(_n2##x,_n1##y,z,c)), \ ++ (I[28] = (T)(img)(_n2##x,_n2##y,z,c)), \ ++ (I[34] = (T)(img)(_n2##x,_n3##y,z,c)), \ ++ x + 3>=(int)(img)._width?(img).width() - 1:x + 3); \ ++ x<=(int)(x1) && ((_n3##x<(img).width() && ( \ ++ (I[5] = (T)(img)(_n3##x,_p2##y,z,c)), \ ++ (I[11] = (T)(img)(_n3##x,_p1##y,z,c)), \ ++ (I[17] = (T)(img)(_n3##x,y,z,c)), \ ++ (I[23] = (T)(img)(_n3##x,_n1##y,z,c)), \ ++ (I[29] = (T)(img)(_n3##x,_n2##y,z,c)), \ ++ (I[35] = (T)(img)(_n3##x,_n3##y,z,c)),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,c,I,T) \ ++ cimg_for7((img)._height,y) for (int x = 0, \ ++ _p3##x = 0, _p2##x = 0, _p1##x = 0, \ ++ _n1##x = 1>=(img)._width?(img).width() - 1:1, \ ++ _n2##x = 2>=(img)._width?(img).width() - 1:2, \ ++ _n3##x = (int)( \ ++ (I[0] = I[1] = I[2] = I[3] = (T)(img)(_p3##x,_p3##y,z,c)), \ ++ (I[7] = I[8] = I[9] = I[10] = (T)(img)(0,_p2##y,z,c)), \ ++ (I[14] = I[15] = I[16] = I[17] = (T)(img)(0,_p1##y,z,c)), \ ++ (I[21] = I[22] = I[23] = I[24] = (T)(img)(0,y,z,c)), \ ++ (I[28] = I[29] = I[30] = I[31] = (T)(img)(0,_n1##y,z,c)), \ ++ (I[35] = I[36] = I[37] = I[38] = (T)(img)(0,_n2##y,z,c)), \ ++ (I[42] = I[43] = I[44] = I[45] = (T)(img)(0,_n3##y,z,c)), \ ++ (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \ ++ (I[11] = (T)(img)(_n1##x,_p2##y,z,c)), \ ++ (I[18] = (T)(img)(_n1##x,_p1##y,z,c)), \ ++ (I[25] = (T)(img)(_n1##x,y,z,c)), \ ++ (I[32] = (T)(img)(_n1##x,_n1##y,z,c)), \ ++ (I[39] = (T)(img)(_n1##x,_n2##y,z,c)), \ ++ (I[46] = (T)(img)(_n1##x,_n3##y,z,c)), \ ++ (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \ ++ (I[12] = (T)(img)(_n2##x,_p2##y,z,c)), \ ++ (I[19] = (T)(img)(_n2##x,_p1##y,z,c)), \ ++ (I[26] = (T)(img)(_n2##x,y,z,c)), \ ++ (I[33] = (T)(img)(_n2##x,_n1##y,z,c)), \ ++ (I[40] = (T)(img)(_n2##x,_n2##y,z,c)), \ ++ (I[47] = (T)(img)(_n2##x,_n3##y,z,c)), \ ++ 3>=(img)._width?(img).width() - 1:3); \ ++ (_n3##x<(img).width() && ( \ ++ (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \ ++ (I[13] = (T)(img)(_n3##x,_p2##y,z,c)), \ ++ (I[20] = (T)(img)(_n3##x,_p1##y,z,c)), \ ++ (I[27] = (T)(img)(_n3##x,y,z,c)), \ ++ (I[34] = (T)(img)(_n3##x,_n1##y,z,c)), \ ++ (I[41] = (T)(img)(_n3##x,_n2##y,z,c)), \ ++ (I[48] = (T)(img)(_n3##x,_n3##y,z,c)),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,c,I,T) \ ++ 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?(img).width() - 1:x + 1, \ ++ _n2##x = x + 2>=(int)(img)._width?(img).width() - 1:x + 2, \ ++ _n3##x = (int)( \ ++ (I[0] = (T)(img)(_p3##x,_p3##y,z,c)), \ ++ (I[7] = (T)(img)(_p3##x,_p2##y,z,c)), \ ++ (I[14] = (T)(img)(_p3##x,_p1##y,z,c)), \ ++ (I[21] = (T)(img)(_p3##x,y,z,c)), \ ++ (I[28] = (T)(img)(_p3##x,_n1##y,z,c)), \ ++ (I[35] = (T)(img)(_p3##x,_n2##y,z,c)), \ ++ (I[42] = (T)(img)(_p3##x,_n3##y,z,c)), \ ++ (I[1] = (T)(img)(_p2##x,_p3##y,z,c)), \ ++ (I[8] = (T)(img)(_p2##x,_p2##y,z,c)), \ ++ (I[15] = (T)(img)(_p2##x,_p1##y,z,c)), \ ++ (I[22] = (T)(img)(_p2##x,y,z,c)), \ ++ (I[29] = (T)(img)(_p2##x,_n1##y,z,c)), \ ++ (I[36] = (T)(img)(_p2##x,_n2##y,z,c)), \ ++ (I[43] = (T)(img)(_p2##x,_n3##y,z,c)), \ ++ (I[2] = (T)(img)(_p1##x,_p3##y,z,c)), \ ++ (I[9] = (T)(img)(_p1##x,_p2##y,z,c)), \ ++ (I[16] = (T)(img)(_p1##x,_p1##y,z,c)), \ ++ (I[23] = (T)(img)(_p1##x,y,z,c)), \ ++ (I[30] = (T)(img)(_p1##x,_n1##y,z,c)), \ ++ (I[37] = (T)(img)(_p1##x,_n2##y,z,c)), \ ++ (I[44] = (T)(img)(_p1##x,_n3##y,z,c)), \ ++ (I[3] = (T)(img)(x,_p3##y,z,c)), \ ++ (I[10] = (T)(img)(x,_p2##y,z,c)), \ ++ (I[17] = (T)(img)(x,_p1##y,z,c)), \ ++ (I[24] = (T)(img)(x,y,z,c)), \ ++ (I[31] = (T)(img)(x,_n1##y,z,c)), \ ++ (I[38] = (T)(img)(x,_n2##y,z,c)), \ ++ (I[45] = (T)(img)(x,_n3##y,z,c)), \ ++ (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \ ++ (I[11] = (T)(img)(_n1##x,_p2##y,z,c)), \ ++ (I[18] = (T)(img)(_n1##x,_p1##y,z,c)), \ ++ (I[25] = (T)(img)(_n1##x,y,z,c)), \ ++ (I[32] = (T)(img)(_n1##x,_n1##y,z,c)), \ ++ (I[39] = (T)(img)(_n1##x,_n2##y,z,c)), \ ++ (I[46] = (T)(img)(_n1##x,_n3##y,z,c)), \ ++ (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \ ++ (I[12] = (T)(img)(_n2##x,_p2##y,z,c)), \ ++ (I[19] = (T)(img)(_n2##x,_p1##y,z,c)), \ ++ (I[26] = (T)(img)(_n2##x,y,z,c)), \ ++ (I[33] = (T)(img)(_n2##x,_n1##y,z,c)), \ ++ (I[40] = (T)(img)(_n2##x,_n2##y,z,c)), \ ++ (I[47] = (T)(img)(_n2##x,_n3##y,z,c)), \ ++ x + 3>=(int)(img)._width?(img).width() - 1:x + 3); \ ++ x<=(int)(x1) && ((_n3##x<(img).width() && ( \ ++ (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \ ++ (I[13] = (T)(img)(_n3##x,_p2##y,z,c)), \ ++ (I[20] = (T)(img)(_n3##x,_p1##y,z,c)), \ ++ (I[27] = (T)(img)(_n3##x,y,z,c)), \ ++ (I[34] = (T)(img)(_n3##x,_n1##y,z,c)), \ ++ (I[41] = (T)(img)(_n3##x,_n2##y,z,c)), \ ++ (I[48] = (T)(img)(_n3##x,_n3##y,z,c)),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,c,I,T) \ ++ cimg_for8((img)._height,y) for (int x = 0, \ ++ _p3##x = 0, _p2##x = 0, _p1##x = 0, \ ++ _n1##x = 1>=((img)._width)?(img).width() - 1:1, \ ++ _n2##x = 2>=((img)._width)?(img).width() - 1:2, \ ++ _n3##x = 3>=((img)._width)?(img).width() - 1:3, \ ++ _n4##x = (int)( \ ++ (I[0] = I[1] = I[2] = I[3] = (T)(img)(_p3##x,_p3##y,z,c)), \ ++ (I[8] = I[9] = I[10] = I[11] = (T)(img)(0,_p2##y,z,c)), \ ++ (I[16] = I[17] = I[18] = I[19] = (T)(img)(0,_p1##y,z,c)), \ ++ (I[24] = I[25] = I[26] = I[27] = (T)(img)(0,y,z,c)), \ ++ (I[32] = I[33] = I[34] = I[35] = (T)(img)(0,_n1##y,z,c)), \ ++ (I[40] = I[41] = I[42] = I[43] = (T)(img)(0,_n2##y,z,c)), \ ++ (I[48] = I[49] = I[50] = I[51] = (T)(img)(0,_n3##y,z,c)), \ ++ (I[56] = I[57] = I[58] = I[59] = (T)(img)(0,_n4##y,z,c)), \ ++ (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \ ++ (I[12] = (T)(img)(_n1##x,_p2##y,z,c)), \ ++ (I[20] = (T)(img)(_n1##x,_p1##y,z,c)), \ ++ (I[28] = (T)(img)(_n1##x,y,z,c)), \ ++ (I[36] = (T)(img)(_n1##x,_n1##y,z,c)), \ ++ (I[44] = (T)(img)(_n1##x,_n2##y,z,c)), \ ++ (I[52] = (T)(img)(_n1##x,_n3##y,z,c)), \ ++ (I[60] = (T)(img)(_n1##x,_n4##y,z,c)), \ ++ (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \ ++ (I[13] = (T)(img)(_n2##x,_p2##y,z,c)), \ ++ (I[21] = (T)(img)(_n2##x,_p1##y,z,c)), \ ++ (I[29] = (T)(img)(_n2##x,y,z,c)), \ ++ (I[37] = (T)(img)(_n2##x,_n1##y,z,c)), \ ++ (I[45] = (T)(img)(_n2##x,_n2##y,z,c)), \ ++ (I[53] = (T)(img)(_n2##x,_n3##y,z,c)), \ ++ (I[61] = (T)(img)(_n2##x,_n4##y,z,c)), \ ++ (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \ ++ (I[14] = (T)(img)(_n3##x,_p2##y,z,c)), \ ++ (I[22] = (T)(img)(_n3##x,_p1##y,z,c)), \ ++ (I[30] = (T)(img)(_n3##x,y,z,c)), \ ++ (I[38] = (T)(img)(_n3##x,_n1##y,z,c)), \ ++ (I[46] = (T)(img)(_n3##x,_n2##y,z,c)), \ ++ (I[54] = (T)(img)(_n3##x,_n3##y,z,c)), \ ++ (I[62] = (T)(img)(_n3##x,_n4##y,z,c)), \ ++ 4>=((img)._width)?(img).width() - 1:4); \ ++ (_n4##x<(img).width() && ( \ ++ (I[7] = (T)(img)(_n4##x,_p3##y,z,c)), \ ++ (I[15] = (T)(img)(_n4##x,_p2##y,z,c)), \ ++ (I[23] = (T)(img)(_n4##x,_p1##y,z,c)), \ ++ (I[31] = (T)(img)(_n4##x,y,z,c)), \ ++ (I[39] = (T)(img)(_n4##x,_n1##y,z,c)), \ ++ (I[47] = (T)(img)(_n4##x,_n2##y,z,c)), \ ++ (I[55] = (T)(img)(_n4##x,_n3##y,z,c)), \ ++ (I[63] = (T)(img)(_n4##x,_n4##y,z,c)),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,c,I,T) \ ++ 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>=(img).width()?(img).width() - 1:x + 1, \ ++ _n2##x = x + 2>=(img).width()?(img).width() - 1:x + 2, \ ++ _n3##x = x + 3>=(img).width()?(img).width() - 1:x + 3, \ ++ _n4##x = (int)( \ ++ (I[0] = (T)(img)(_p3##x,_p3##y,z,c)), \ ++ (I[8] = (T)(img)(_p3##x,_p2##y,z,c)), \ ++ (I[16] = (T)(img)(_p3##x,_p1##y,z,c)), \ ++ (I[24] = (T)(img)(_p3##x,y,z,c)), \ ++ (I[32] = (T)(img)(_p3##x,_n1##y,z,c)), \ ++ (I[40] = (T)(img)(_p3##x,_n2##y,z,c)), \ ++ (I[48] = (T)(img)(_p3##x,_n3##y,z,c)), \ ++ (I[56] = (T)(img)(_p3##x,_n4##y,z,c)), \ ++ (I[1] = (T)(img)(_p2##x,_p3##y,z,c)), \ ++ (I[9] = (T)(img)(_p2##x,_p2##y,z,c)), \ ++ (I[17] = (T)(img)(_p2##x,_p1##y,z,c)), \ ++ (I[25] = (T)(img)(_p2##x,y,z,c)), \ ++ (I[33] = (T)(img)(_p2##x,_n1##y,z,c)), \ ++ (I[41] = (T)(img)(_p2##x,_n2##y,z,c)), \ ++ (I[49] = (T)(img)(_p2##x,_n3##y,z,c)), \ ++ (I[57] = (T)(img)(_p2##x,_n4##y,z,c)), \ ++ (I[2] = (T)(img)(_p1##x,_p3##y,z,c)), \ ++ (I[10] = (T)(img)(_p1##x,_p2##y,z,c)), \ ++ (I[18] = (T)(img)(_p1##x,_p1##y,z,c)), \ ++ (I[26] = (T)(img)(_p1##x,y,z,c)), \ ++ (I[34] = (T)(img)(_p1##x,_n1##y,z,c)), \ ++ (I[42] = (T)(img)(_p1##x,_n2##y,z,c)), \ ++ (I[50] = (T)(img)(_p1##x,_n3##y,z,c)), \ ++ (I[58] = (T)(img)(_p1##x,_n4##y,z,c)), \ ++ (I[3] = (T)(img)(x,_p3##y,z,c)), \ ++ (I[11] = (T)(img)(x,_p2##y,z,c)), \ ++ (I[19] = (T)(img)(x,_p1##y,z,c)), \ ++ (I[27] = (T)(img)(x,y,z,c)), \ ++ (I[35] = (T)(img)(x,_n1##y,z,c)), \ ++ (I[43] = (T)(img)(x,_n2##y,z,c)), \ ++ (I[51] = (T)(img)(x,_n3##y,z,c)), \ ++ (I[59] = (T)(img)(x,_n4##y,z,c)), \ ++ (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \ ++ (I[12] = (T)(img)(_n1##x,_p2##y,z,c)), \ ++ (I[20] = (T)(img)(_n1##x,_p1##y,z,c)), \ ++ (I[28] = (T)(img)(_n1##x,y,z,c)), \ ++ (I[36] = (T)(img)(_n1##x,_n1##y,z,c)), \ ++ (I[44] = (T)(img)(_n1##x,_n2##y,z,c)), \ ++ (I[52] = (T)(img)(_n1##x,_n3##y,z,c)), \ ++ (I[60] = (T)(img)(_n1##x,_n4##y,z,c)), \ ++ (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \ ++ (I[13] = (T)(img)(_n2##x,_p2##y,z,c)), \ ++ (I[21] = (T)(img)(_n2##x,_p1##y,z,c)), \ ++ (I[29] = (T)(img)(_n2##x,y,z,c)), \ ++ (I[37] = (T)(img)(_n2##x,_n1##y,z,c)), \ ++ (I[45] = (T)(img)(_n2##x,_n2##y,z,c)), \ ++ (I[53] = (T)(img)(_n2##x,_n3##y,z,c)), \ ++ (I[61] = (T)(img)(_n2##x,_n4##y,z,c)), \ ++ (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \ ++ (I[14] = (T)(img)(_n3##x,_p2##y,z,c)), \ ++ (I[22] = (T)(img)(_n3##x,_p1##y,z,c)), \ ++ (I[30] = (T)(img)(_n3##x,y,z,c)), \ ++ (I[38] = (T)(img)(_n3##x,_n1##y,z,c)), \ ++ (I[46] = (T)(img)(_n3##x,_n2##y,z,c)), \ ++ (I[54] = (T)(img)(_n3##x,_n3##y,z,c)), \ ++ (I[62] = (T)(img)(_n3##x,_n4##y,z,c)), \ ++ x + 4>=(img).width()?(img).width() - 1:x + 4); \ ++ x<=(int)(x1) && ((_n4##x<(img).width() && ( \ ++ (I[7] = (T)(img)(_n4##x,_p3##y,z,c)), \ ++ (I[15] = (T)(img)(_n4##x,_p2##y,z,c)), \ ++ (I[23] = (T)(img)(_n4##x,_p1##y,z,c)), \ ++ (I[31] = (T)(img)(_n4##x,y,z,c)), \ ++ (I[39] = (T)(img)(_n4##x,_n1##y,z,c)), \ ++ (I[47] = (T)(img)(_n4##x,_n2##y,z,c)), \ ++ (I[55] = (T)(img)(_n4##x,_n3##y,z,c)), \ ++ (I[63] = (T)(img)(_n4##x,_n4##y,z,c)),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,c,I,T) \ ++ 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)?(img).width() - 1:1, \ ++ _n2##x = 2>=((img)._width)?(img).width() - 1:2, \ ++ _n3##x = 3>=((img)._width)?(img).width() - 1:3, \ ++ _n4##x = (int)( \ ++ (I[0] = I[1] = I[2] = I[3] = I[4] = (T)(img)(_p4##x,_p4##y,z,c)), \ ++ (I[9] = I[10] = I[11] = I[12] = I[13] = (T)(img)(0,_p3##y,z,c)), \ ++ (I[18] = I[19] = I[20] = I[21] = I[22] = (T)(img)(0,_p2##y,z,c)), \ ++ (I[27] = I[28] = I[29] = I[30] = I[31] = (T)(img)(0,_p1##y,z,c)), \ ++ (I[36] = I[37] = I[38] = I[39] = I[40] = (T)(img)(0,y,z,c)), \ ++ (I[45] = I[46] = I[47] = I[48] = I[49] = (T)(img)(0,_n1##y,z,c)), \ ++ (I[54] = I[55] = I[56] = I[57] = I[58] = (T)(img)(0,_n2##y,z,c)), \ ++ (I[63] = I[64] = I[65] = I[66] = I[67] = (T)(img)(0,_n3##y,z,c)), \ ++ (I[72] = I[73] = I[74] = I[75] = I[76] = (T)(img)(0,_n4##y,z,c)), \ ++ (I[5] = (T)(img)(_n1##x,_p4##y,z,c)), \ ++ (I[14] = (T)(img)(_n1##x,_p3##y,z,c)), \ ++ (I[23] = (T)(img)(_n1##x,_p2##y,z,c)), \ ++ (I[32] = (T)(img)(_n1##x,_p1##y,z,c)), \ ++ (I[41] = (T)(img)(_n1##x,y,z,c)), \ ++ (I[50] = (T)(img)(_n1##x,_n1##y,z,c)), \ ++ (I[59] = (T)(img)(_n1##x,_n2##y,z,c)), \ ++ (I[68] = (T)(img)(_n1##x,_n3##y,z,c)), \ ++ (I[77] = (T)(img)(_n1##x,_n4##y,z,c)), \ ++ (I[6] = (T)(img)(_n2##x,_p4##y,z,c)), \ ++ (I[15] = (T)(img)(_n2##x,_p3##y,z,c)), \ ++ (I[24] = (T)(img)(_n2##x,_p2##y,z,c)), \ ++ (I[33] = (T)(img)(_n2##x,_p1##y,z,c)), \ ++ (I[42] = (T)(img)(_n2##x,y,z,c)), \ ++ (I[51] = (T)(img)(_n2##x,_n1##y,z,c)), \ ++ (I[60] = (T)(img)(_n2##x,_n2##y,z,c)), \ ++ (I[69] = (T)(img)(_n2##x,_n3##y,z,c)), \ ++ (I[78] = (T)(img)(_n2##x,_n4##y,z,c)), \ ++ (I[7] = (T)(img)(_n3##x,_p4##y,z,c)), \ ++ (I[16] = (T)(img)(_n3##x,_p3##y,z,c)), \ ++ (I[25] = (T)(img)(_n3##x,_p2##y,z,c)), \ ++ (I[34] = (T)(img)(_n3##x,_p1##y,z,c)), \ ++ (I[43] = (T)(img)(_n3##x,y,z,c)), \ ++ (I[52] = (T)(img)(_n3##x,_n1##y,z,c)), \ ++ (I[61] = (T)(img)(_n3##x,_n2##y,z,c)), \ ++ (I[70] = (T)(img)(_n3##x,_n3##y,z,c)), \ ++ (I[79] = (T)(img)(_n3##x,_n4##y,z,c)), \ ++ 4>=((img)._width)?(img).width() - 1:4); \ ++ (_n4##x<(img).width() && ( \ ++ (I[8] = (T)(img)(_n4##x,_p4##y,z,c)), \ ++ (I[17] = (T)(img)(_n4##x,_p3##y,z,c)), \ ++ (I[26] = (T)(img)(_n4##x,_p2##y,z,c)), \ ++ (I[35] = (T)(img)(_n4##x,_p1##y,z,c)), \ ++ (I[44] = (T)(img)(_n4##x,y,z,c)), \ ++ (I[53] = (T)(img)(_n4##x,_n1##y,z,c)), \ ++ (I[62] = (T)(img)(_n4##x,_n2##y,z,c)), \ ++ (I[71] = (T)(img)(_n4##x,_n3##y,z,c)), \ ++ (I[80] = (T)(img)(_n4##x,_n4##y,z,c)),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,c,I,T) \ ++ 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>=(img).width()?(img).width() - 1:x + 1, \ ++ _n2##x = x + 2>=(img).width()?(img).width() - 1:x + 2, \ ++ _n3##x = x + 3>=(img).width()?(img).width() - 1:x + 3, \ ++ _n4##x = (int)( \ ++ (I[0] = (T)(img)(_p4##x,_p4##y,z,c)), \ ++ (I[9] = (T)(img)(_p4##x,_p3##y,z,c)), \ ++ (I[18] = (T)(img)(_p4##x,_p2##y,z,c)), \ ++ (I[27] = (T)(img)(_p4##x,_p1##y,z,c)), \ ++ (I[36] = (T)(img)(_p4##x,y,z,c)), \ ++ (I[45] = (T)(img)(_p4##x,_n1##y,z,c)), \ ++ (I[54] = (T)(img)(_p4##x,_n2##y,z,c)), \ ++ (I[63] = (T)(img)(_p4##x,_n3##y,z,c)), \ ++ (I[72] = (T)(img)(_p4##x,_n4##y,z,c)), \ ++ (I[1] = (T)(img)(_p3##x,_p4##y,z,c)), \ ++ (I[10] = (T)(img)(_p3##x,_p3##y,z,c)), \ ++ (I[19] = (T)(img)(_p3##x,_p2##y,z,c)), \ ++ (I[28] = (T)(img)(_p3##x,_p1##y,z,c)), \ ++ (I[37] = (T)(img)(_p3##x,y,z,c)), \ ++ (I[46] = (T)(img)(_p3##x,_n1##y,z,c)), \ ++ (I[55] = (T)(img)(_p3##x,_n2##y,z,c)), \ ++ (I[64] = (T)(img)(_p3##x,_n3##y,z,c)), \ ++ (I[73] = (T)(img)(_p3##x,_n4##y,z,c)), \ ++ (I[2] = (T)(img)(_p2##x,_p4##y,z,c)), \ ++ (I[11] = (T)(img)(_p2##x,_p3##y,z,c)), \ ++ (I[20] = (T)(img)(_p2##x,_p2##y,z,c)), \ ++ (I[29] = (T)(img)(_p2##x,_p1##y,z,c)), \ ++ (I[38] = (T)(img)(_p2##x,y,z,c)), \ ++ (I[47] = (T)(img)(_p2##x,_n1##y,z,c)), \ ++ (I[56] = (T)(img)(_p2##x,_n2##y,z,c)), \ ++ (I[65] = (T)(img)(_p2##x,_n3##y,z,c)), \ ++ (I[74] = (T)(img)(_p2##x,_n4##y,z,c)), \ ++ (I[3] = (T)(img)(_p1##x,_p4##y,z,c)), \ ++ (I[12] = (T)(img)(_p1##x,_p3##y,z,c)), \ ++ (I[21] = (T)(img)(_p1##x,_p2##y,z,c)), \ ++ (I[30] = (T)(img)(_p1##x,_p1##y,z,c)), \ ++ (I[39] = (T)(img)(_p1##x,y,z,c)), \ ++ (I[48] = (T)(img)(_p1##x,_n1##y,z,c)), \ ++ (I[57] = (T)(img)(_p1##x,_n2##y,z,c)), \ ++ (I[66] = (T)(img)(_p1##x,_n3##y,z,c)), \ ++ (I[75] = (T)(img)(_p1##x,_n4##y,z,c)), \ ++ (I[4] = (T)(img)(x,_p4##y,z,c)), \ ++ (I[13] = (T)(img)(x,_p3##y,z,c)), \ ++ (I[22] = (T)(img)(x,_p2##y,z,c)), \ ++ (I[31] = (T)(img)(x,_p1##y,z,c)), \ ++ (I[40] = (T)(img)(x,y,z,c)), \ ++ (I[49] = (T)(img)(x,_n1##y,z,c)), \ ++ (I[58] = (T)(img)(x,_n2##y,z,c)), \ ++ (I[67] = (T)(img)(x,_n3##y,z,c)), \ ++ (I[76] = (T)(img)(x,_n4##y,z,c)), \ ++ (I[5] = (T)(img)(_n1##x,_p4##y,z,c)), \ ++ (I[14] = (T)(img)(_n1##x,_p3##y,z,c)), \ ++ (I[23] = (T)(img)(_n1##x,_p2##y,z,c)), \ ++ (I[32] = (T)(img)(_n1##x,_p1##y,z,c)), \ ++ (I[41] = (T)(img)(_n1##x,y,z,c)), \ ++ (I[50] = (T)(img)(_n1##x,_n1##y,z,c)), \ ++ (I[59] = (T)(img)(_n1##x,_n2##y,z,c)), \ ++ (I[68] = (T)(img)(_n1##x,_n3##y,z,c)), \ ++ (I[77] = (T)(img)(_n1##x,_n4##y,z,c)), \ ++ (I[6] = (T)(img)(_n2##x,_p4##y,z,c)), \ ++ (I[15] = (T)(img)(_n2##x,_p3##y,z,c)), \ ++ (I[24] = (T)(img)(_n2##x,_p2##y,z,c)), \ ++ (I[33] = (T)(img)(_n2##x,_p1##y,z,c)), \ ++ (I[42] = (T)(img)(_n2##x,y,z,c)), \ ++ (I[51] = (T)(img)(_n2##x,_n1##y,z,c)), \ ++ (I[60] = (T)(img)(_n2##x,_n2##y,z,c)), \ ++ (I[69] = (T)(img)(_n2##x,_n3##y,z,c)), \ ++ (I[78] = (T)(img)(_n2##x,_n4##y,z,c)), \ ++ (I[7] = (T)(img)(_n3##x,_p4##y,z,c)), \ ++ (I[16] = (T)(img)(_n3##x,_p3##y,z,c)), \ ++ (I[25] = (T)(img)(_n3##x,_p2##y,z,c)), \ ++ (I[34] = (T)(img)(_n3##x,_p1##y,z,c)), \ ++ (I[43] = (T)(img)(_n3##x,y,z,c)), \ ++ (I[52] = (T)(img)(_n3##x,_n1##y,z,c)), \ ++ (I[61] = (T)(img)(_n3##x,_n2##y,z,c)), \ ++ (I[70] = (T)(img)(_n3##x,_n3##y,z,c)), \ ++ (I[79] = (T)(img)(_n3##x,_n4##y,z,c)), \ ++ x + 4>=(img).width()?(img).width() - 1:x + 4); \ ++ x<=(int)(x1) && ((_n4##x<(img).width() && ( \ ++ (I[8] = (T)(img)(_n4##x,_p4##y,z,c)), \ ++ (I[17] = (T)(img)(_n4##x,_p3##y,z,c)), \ ++ (I[26] = (T)(img)(_n4##x,_p2##y,z,c)), \ ++ (I[35] = (T)(img)(_n4##x,_p1##y,z,c)), \ ++ (I[44] = (T)(img)(_n4##x,y,z,c)), \ ++ (I[53] = (T)(img)(_n4##x,_n1##y,z,c)), \ ++ (I[62] = (T)(img)(_n4##x,_n2##y,z,c)), \ ++ (I[71] = (T)(img)(_n4##x,_n3##y,z,c)), \ ++ (I[80] = (T)(img)(_n4##x,_n4##y,z,c)),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,c,I,T) \ ++ cimg_for2((img)._depth,z) cimg_for2((img)._height,y) for (int x = 0, \ ++ _n1##x = (int)( \ ++ (I[0] = (T)(img)(0,y,z,c)), \ ++ (I[2] = (T)(img)(0,_n1##y,z,c)), \ ++ (I[4] = (T)(img)(0,y,_n1##z,c)), \ ++ (I[6] = (T)(img)(0,_n1##y,_n1##z,c)), \ ++ 1>=(img)._width?(img).width() - 1:1); \ ++ (_n1##x<(img).width() && ( \ ++ (I[1] = (T)(img)(_n1##x,y,z,c)), \ ++ (I[3] = (T)(img)(_n1##x,_n1##y,z,c)), \ ++ (I[5] = (T)(img)(_n1##x,y,_n1##z,c)), \ ++ (I[7] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),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,c,I,T) \ ++ 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] = (T)(img)(x,y,z,c)), \ ++ (I[2] = (T)(img)(x,_n1##y,z,c)), \ ++ (I[4] = (T)(img)(x,y,_n1##z,c)), \ ++ (I[6] = (T)(img)(x,_n1##y,_n1##z,c)), \ ++ x + 1>=(int)(img)._width?(img).width() - 1:x + 1); \ ++ x<=(int)(x1) && ((_n1##x<(img).width() && ( \ ++ (I[1] = (T)(img)(_n1##x,y,z,c)), \ ++ (I[3] = (T)(img)(_n1##x,_n1##y,z,c)), \ ++ (I[5] = (T)(img)(_n1##x,y,_n1##z,c)), \ ++ (I[7] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),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,c,I,T) \ ++ cimg_for3((img)._depth,z) cimg_for3((img)._height,y) for (int x = 0, \ ++ _p1##x = 0, \ ++ _n1##x = (int)( \ ++ (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,_p1##z,c)), \ ++ (I[3] = I[4] = (T)(img)(0,y,_p1##z,c)), \ ++ (I[6] = I[7] = (T)(img)(0,_n1##y,_p1##z,c)), \ ++ (I[9] = I[10] = (T)(img)(0,_p1##y,z,c)), \ ++ (I[12] = I[13] = (T)(img)(0,y,z,c)), \ ++ (I[15] = I[16] = (T)(img)(0,_n1##y,z,c)), \ ++ (I[18] = I[19] = (T)(img)(0,_p1##y,_n1##z,c)), \ ++ (I[21] = I[22] = (T)(img)(0,y,_n1##z,c)), \ ++ (I[24] = I[25] = (T)(img)(0,_n1##y,_n1##z,c)), \ ++ 1>=(img)._width?(img).width() - 1:1); \ ++ (_n1##x<(img).width() && ( \ ++ (I[2] = (T)(img)(_n1##x,_p1##y,_p1##z,c)), \ ++ (I[5] = (T)(img)(_n1##x,y,_p1##z,c)), \ ++ (I[8] = (T)(img)(_n1##x,_n1##y,_p1##z,c)), \ ++ (I[11] = (T)(img)(_n1##x,_p1##y,z,c)), \ ++ (I[14] = (T)(img)(_n1##x,y,z,c)), \ ++ (I[17] = (T)(img)(_n1##x,_n1##y,z,c)), \ ++ (I[20] = (T)(img)(_n1##x,_p1##y,_n1##z,c)), \ ++ (I[23] = (T)(img)(_n1##x,y,_n1##z,c)), \ ++ (I[26] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),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,c,I,T) \ ++ 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] = (T)(img)(_p1##x,_p1##y,_p1##z,c)), \ ++ (I[3] = (T)(img)(_p1##x,y,_p1##z,c)), \ ++ (I[6] = (T)(img)(_p1##x,_n1##y,_p1##z,c)), \ ++ (I[9] = (T)(img)(_p1##x,_p1##y,z,c)), \ ++ (I[12] = (T)(img)(_p1##x,y,z,c)), \ ++ (I[15] = (T)(img)(_p1##x,_n1##y,z,c)), \ ++ (I[18] = (T)(img)(_p1##x,_p1##y,_n1##z,c)), \ ++ (I[21] = (T)(img)(_p1##x,y,_n1##z,c)), \ ++ (I[24] = (T)(img)(_p1##x,_n1##y,_n1##z,c)), \ ++ (I[1] = (T)(img)(x,_p1##y,_p1##z,c)), \ ++ (I[4] = (T)(img)(x,y,_p1##z,c)), \ ++ (I[7] = (T)(img)(x,_n1##y,_p1##z,c)), \ ++ (I[10] = (T)(img)(x,_p1##y,z,c)), \ ++ (I[13] = (T)(img)(x,y,z,c)), \ ++ (I[16] = (T)(img)(x,_n1##y,z,c)), \ ++ (I[19] = (T)(img)(x,_p1##y,_n1##z,c)), \ ++ (I[22] = (T)(img)(x,y,_n1##z,c)), \ ++ (I[25] = (T)(img)(x,_n1##y,_n1##z,c)), \ ++ x + 1>=(int)(img)._width?(img).width() - 1:x + 1); \ ++ x<=(int)(x1) && ((_n1##x<(img).width() && ( \ ++ (I[2] = (T)(img)(_n1##x,_p1##y,_p1##z,c)), \ ++ (I[5] = (T)(img)(_n1##x,y,_p1##z,c)), \ ++ (I[8] = (T)(img)(_n1##x,_n1##y,_p1##z,c)), \ ++ (I[11] = (T)(img)(_n1##x,_p1##y,z,c)), \ ++ (I[14] = (T)(img)(_n1##x,y,z,c)), \ ++ (I[17] = (T)(img)(_n1##x,_n1##y,z,c)), \ ++ (I[20] = (T)(img)(_n1##x,_p1##y,_n1##z,c)), \ ++ (I[23] = (T)(img)(_n1##x,y,_n1##z,c)), \ ++ (I[26] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),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 cimglist_for(list,l) for (int l = 0; l<(int)(list)._width; ++l) ++#define cimglist_for_in(list,l0,l1,l) \ ++ for (int l = (int)(l0)<0?0:(int)(l0), _max##l = (unsigned int)l1<(list)._width?(int)(l1):(int)(list)._width - 1; \ ++ l<=_max##l; ++l) ++ ++#define cimglist_apply(list,fn) cimglist_for(list,__##fn) (list)[__##fn].fn ++ ++// Macros used to display error messages when exceptions are thrown. ++// You should not use these macros is your own code. ++#define _cimgdisplay_instance "[instance(%u,%u,%u,%c%s%c)] CImgDisplay::" ++#define cimgdisplay_instance _width,_height,_normalization,_title?'\"':'[',_title?_title:"untitled",_title?'\"':']' ++#define _cimg_instance "[instance(%u,%u,%u,%u,%p,%sshared)] CImg<%s>::" ++#define cimg_instance _width,_height,_depth,_spectrum,_data,_is_shared?"":"non-",pixel_type() ++#define _cimglist_instance "[instance(%u,%u,%p)] CImgList<%s>::" ++#define cimglist_instance _width,_allocated_width,_data,pixel_type() ++ ++/*------------------------------------------------ ++ # ++ # ++ # Define cimg_library:: namespace ++ # ++ # ++ -------------------------------------------------*/ ++//! Contains all classes and functions of the \CImg library. ++/** ++ This namespace is defined to avoid functions and class names collisions ++ that could happen with the inclusion 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 objects afterwards. ++**/ ++namespace cimg_library_suffixed { ++ ++ // Declare the four classes of the CImg Library. ++ template struct CImg; ++ template struct CImgList; ++ struct CImgDisplay; ++ struct CImgException; ++ ++ // Declare cimg:: namespace. ++ // This is an uncomplete namespace definition here. It only contains some ++ // necessary stuff to ensure a correct declaration order of the classes and functions ++ // defined afterwards. ++ namespace cimg { ++ ++ // Define ascii sequences for colored terminal output. ++#ifdef cimg_use_vt100 ++ static const char t_normal[] = { 0x1b, '[', '0', ';', '0', ';', '0', 'm', 0 }; ++ static const char t_black[] = { 0x1b, '[', '0', ';', '3', '0', ';', '5', '9', 'm', 0 }; ++ static const char t_red[] = { 0x1b, '[', '0', ';', '3', '1', ';', '5', '9', 'm', 0 }; ++ static const char t_green[] = { 0x1b, '[', '0', ';', '3', '2', ';', '5', '9', 'm', 0 }; ++ static const char t_yellow[] = { 0x1b, '[', '0', ';', '3', '3', ';', '5', '9', 'm', 0 }; ++ static const char t_blue[] = { 0x1b, '[', '0', ';', '3', '4', ';', '5', '9', 'm', 0 }; ++ static const char t_magenta[] = { 0x1b, '[', '0', ';', '3', '5', ';', '5', '9', 'm', 0 }; ++ static const char t_cyan[] = { 0x1b, '[', '0', ';', '3', '6', ';', '5', '9', 'm', 0 }; ++ static const char t_white[] = { 0x1b, '[', '0', ';', '3', '7', ';', '5', '9', 'm', 0 }; ++ static const char t_bold[] = { 0x1b, '[', '1', 'm', 0 }; ++ static const char t_underscore[] = { 0x1b, '[', '4', 'm', 0 }; ++#else ++ static const char t_normal[] = { 0 }; ++ static const char *const t_black = cimg::t_normal, ++ *const t_red = cimg::t_normal, ++ *const t_green = cimg::t_normal, ++ *const t_yellow = cimg::t_normal, ++ *const t_blue = cimg::t_normal, ++ *const t_magenta = cimg::t_normal, ++ *const t_cyan = cimg::t_normal, ++ *const t_white = cimg::t_normal, ++ *const t_bold = cimg::t_normal, ++ *const t_underscore = cimg::t_normal; ++#endif ++ ++ inline std::FILE* output(std::FILE *file=0); ++ inline void info(); ++ ++ //! Avoid warning messages due to unused parameters. Do nothing actually. ++ template ++ inline void unused(const T&, ...) {} ++ ++ // [internal] Lock/unlock a mutex for managing concurrent threads. ++ // 'lock_mode' can be { 0=unlock | 1=lock | 2=trylock }. ++ // 'n' can be in [0,31] but mutex range [0,15] is reserved by CImg. ++ inline int mutex(const unsigned int n, const int lock_mode=1); ++ ++ inline unsigned int& _exception_mode(const unsigned int value, const bool is_set) { ++ static unsigned int mode = cimg_verbosity; ++ if (is_set) { cimg::mutex(0); mode = value<4?value:4; cimg::mutex(0,0); } ++ return mode; ++ } ++ ++ // Functions to return standard streams 'stdin', 'stdout' and 'stderr'. ++ inline FILE* _stdin(const bool throw_exception=true); ++ inline FILE* _stdout(const bool throw_exception=true); ++ inline FILE* _stderr(const bool throw_exception=true); ++ ++ // Mandatory because Microsoft's _snprintf() and _vsnprintf() do not add the '\0' character ++ // at the end of the string. ++#if cimg_OS==2 && defined(_MSC_VER) ++ inline int _snprintf(char *const s, const size_t size, const char *const format, ...) { ++ va_list ap; ++ va_start(ap,format); ++ const int result = _vsnprintf(s,size,format,ap); ++ va_end(ap); ++ return result; ++ } ++ ++ inline int _vsnprintf(char *const s, const size_t size, const char *const format, va_list ap) { ++ int result = -1; ++ cimg::mutex(6); ++ if (size) result = _vsnprintf_s(s,size,_TRUNCATE,format,ap); ++ if (result==-1) result = _vscprintf(format,ap); ++ cimg::mutex(6,0); ++ return result; ++ } ++ ++ // Mutex-protected version of sscanf, sprintf and snprintf. ++ // Used only MacOSX, as it seems those functions are not re-entrant on MacOSX. ++#elif defined(__MACOSX__) || defined(__APPLE__) ++ inline int _sscanf(const char *const s, const char *const format, ...) { ++ cimg::mutex(6); ++ va_list args; ++ va_start(args,format); ++ const int result = std::vsscanf(s,format,args); ++ va_end(args); ++ cimg::mutex(6,0); ++ return result; ++ } ++ ++ inline int _sprintf(char *const s, const char *const format, ...) { ++ cimg::mutex(6); ++ va_list args; ++ va_start(args,format); ++ const int result = std::vsprintf(s,format,args); ++ va_end(args); ++ cimg::mutex(6,0); ++ return result; ++ } ++ ++ inline int _snprintf(char *const s, const size_t n, const char *const format, ...) { ++ cimg::mutex(6); ++ va_list args; ++ va_start(args,format); ++ const int result = std::vsnprintf(s,n,format,args); ++ va_end(args); ++ cimg::mutex(6,0); ++ return result; ++ } ++ ++ inline int _vsnprintf(char *const s, const size_t size, const char* format, va_list ap) { ++ cimg::mutex(6); ++ const int result = std::vsnprintf(s,size,format,ap); ++ cimg::mutex(6,0); ++ return result; ++ } ++#endif ++ ++ //! Set current \CImg exception mode. ++ /** ++ The way error messages are handled by \CImg can be changed dynamically, using this function. ++ \param mode Desired exception mode. Possible values are: ++ - \c 0: Hide library messages (quiet mode). ++ - \c 1: Print library messages on the console. ++ - \c 2: Display library messages on a dialog window. ++ - \c 3: Do as \c 1 + add extra debug warnings (slow down the code!). ++ - \c 4: Do as \c 2 + add extra debug warnings (slow down the code!). ++ **/ ++ inline unsigned int& exception_mode(const unsigned int mode) { ++ return _exception_mode(mode,true); ++ } ++ ++ //! Return current \CImg exception mode. ++ /** ++ \note By default, return the value of configuration macro \c cimg_verbosity ++ **/ ++ inline unsigned int& exception_mode() { ++ return _exception_mode(0,false); ++ } ++ ++ //! Set current \CImg openmp mode. ++ /** ++ The way openmp-based methods are handled by \CImg can be changed dynamically, using this function. ++ \param mode Desired openmp mode. Possible values are: ++ - \c 0: Never parallelize. ++ - \c 1: Always parallelize. ++ - \c 2: Adaptive parallelization mode (default behavior). ++ **/ ++ inline unsigned int& _openmp_mode(const unsigned int value, const bool is_set) { ++ static unsigned int mode = 2; ++ if (is_set) { cimg::mutex(0); mode = value<2?value:2; cimg::mutex(0,0); } ++ return mode; ++ } ++ ++ inline unsigned int& openmp_mode(const unsigned int mode) { ++ return _openmp_mode(mode,true); ++ } ++ ++ //! Return current \CImg openmp mode. ++ inline unsigned int& openmp_mode() { ++ return _openmp_mode(0,false); ++ } ++ ++#define cimg_openmp_if(cond) if (cimg::openmp_mode()==1 || (cimg::openmp_mode()>1 && (cond))) ++ ++ // Display a simple dialog box, and wait for the user's response. ++ inline int dialog(const char *const title, const char *const msg, const char *const button1_label="OK", ++ const char *const button2_label=0, const char *const button3_label=0, ++ const char *const button4_label=0, const char *const button5_label=0, ++ const char *const button6_label=0, const bool centering=false); ++ ++ // Evaluate math expression. ++ inline double eval(const char *const expression, ++ const double x=0, const double y=0, const double z=0, const double c=0); ++ ++ } ++ ++ /*--------------------------------------- ++ # ++ # Define the CImgException structures ++ # ++ --------------------------------------*/ ++ //! Instances of \c CImgException are thrown when errors are encountered in a \CImg function call. ++ /** ++ \par Overview ++ ++ CImgException is the base class of all exceptions thrown by \CImg (except \b CImgAbortException). ++ CImgException is never thrown itself. Derived classes that specify the type of errord are thrown instead. ++ These classes can be: ++ ++ - \b CImgAbortException: Thrown when a computationally-intensive function is aborted by an external signal. ++ This is the only \c non-derived exception class. ++ ++ - \b CImgArgumentException: Thrown when one argument of a called \CImg function is invalid. ++ This is probably one of the most thrown exception by \CImg. ++ For instance, the following example throws a \c CImgArgumentException: ++ \code ++ CImg img(100,100,1,3); // Define a 100x100 color image with float-valued pixels. ++ img.mirror('e'); // Try to mirror image along the (non-existing) 'e'-axis. ++ \endcode ++ ++ - \b CImgDisplayException: Thrown when something went wrong during the display of images in CImgDisplay instances. ++ ++ - \b CImgInstanceException: Thrown when an instance associated to a called \CImg method does not fit ++ the function requirements. For instance, the following example throws a \c CImgInstanceException: ++ \code ++ const CImg img; // Define an empty image. ++ const float value = img.at(0); // Try to read first pixel value (does not exist). ++ \endcode ++ ++ - \b CImgIOException: Thrown when an error occured when trying to load or save image files. ++ This happens when trying to read files that do not exist or with invalid formats. ++ For instance, the following example throws a \c CImgIOException: ++ \code ++ const CImg img("missing_file.jpg"); // Try to load a file that does not exist. ++ \endcode ++ ++ - \b CImgWarningException: Thrown only if configuration macro \c cimg_strict_warnings is set, and ++ when a \CImg function has to display a warning message (see cimg::warn()). ++ ++ It is not recommended to throw CImgException instances by yourself, ++ since they are expected to be thrown only by \CImg. ++ When an error occurs in a library function call, \CImg may display error messages on the screen or on the ++ standard output, depending on the current \CImg exception mode. ++ The \CImg exception mode can be get and set by functions cimg::exception_mode() and ++ cimg::exception_mode(unsigned int). ++ ++ \par Exceptions handling ++ ++ In all cases, when an error occurs in \CImg, an instance of the corresponding exception class is thrown. ++ This may lead the program to break (this is the default behavior), but you can bypass this behavior by ++ handling the exceptions by yourself, ++ using a usual try { ... } catch () { ... } bloc, as in the following example: ++ \code ++ #define "CImg.h" ++ using namespace cimg_library; ++ int main() { ++ cimg::exception_mode(0); // Enable quiet exception mode. ++ try { ++ ... // Here, do what you want to stress CImg. ++ } catch (CImgException& e) { // You succeeded: something went wrong! ++ std::fprintf(stderr,"CImg Library Error: %s",e.what()); // Display your custom error message. ++ ... // Do what you want now to save the ship! ++ } ++ } ++ \endcode ++ **/ ++ struct CImgException : public std::exception { ++#define _cimg_exception_err(etype,disp_flag) \ ++ std::va_list ap, ap2; \ ++ va_start(ap,format); va_start(ap2,format); \ ++ int size = cimg_vsnprintf(0,0,format,ap2); \ ++ if (size++>=0) { \ ++ delete[] _message; \ ++ _message = new char[size]; \ ++ cimg_vsnprintf(_message,size,format,ap); \ ++ if (cimg::exception_mode()) { \ ++ std::fprintf(cimg::output(),"\n%s[CImg] *** %s ***%s %s\n",cimg::t_red,etype,cimg::t_normal,_message); \ ++ if (cimg_display && disp_flag && !(cimg::exception_mode()%2)) try { cimg::dialog(etype,_message,"Abort"); } \ ++ catch (CImgException&) {} \ ++ if (cimg::exception_mode()>=3) cimg_library_suffixed::cimg::info(); \ ++ } \ ++ } \ ++ va_end(ap); va_end(ap2); \ ++ ++ char *_message; ++ CImgException() { _message = new char[1]; *_message = 0; } ++ CImgException(const char *const format, ...):_message(0) { _cimg_exception_err("CImgException",true); } ++ CImgException(const CImgException& e):std::exception(e) { ++ const size_t size = std::strlen(e._message); ++ _message = new char[size + 1]; ++ std::strncpy(_message,e._message,size); ++ _message[size] = 0; ++ } ++ ~CImgException() throw() { delete[] _message; } ++ CImgException& operator=(const CImgException& e) { ++ const size_t size = std::strlen(e._message); ++ _message = new char[size + 1]; ++ std::strncpy(_message,e._message,size); ++ _message[size] = 0; ++ return *this; ++ } ++ //! Return a C-string containing the error message associated to the thrown exception. ++ const char *what() const throw() { return _message; } ++ }; ++ ++ // The CImgAbortException class is used to throw an exception when ++ // a computationally-intensive function has been aborted by an external signal. ++ struct CImgAbortException : public std::exception { ++ char *_message; ++ CImgAbortException() { _message = new char[1]; *_message = 0; } ++ CImgAbortException(const char *const format, ...):_message(0) { _cimg_exception_err("CImgAbortException",true); } ++ CImgAbortException(const CImgAbortException& e):std::exception(e) { ++ const size_t size = std::strlen(e._message); ++ _message = new char[size + 1]; ++ std::strncpy(_message,e._message,size); ++ _message[size] = 0; ++ } ++ ~CImgAbortException() throw() { delete[] _message; } ++ CImgAbortException& operator=(const CImgAbortException& e) { ++ const size_t size = std::strlen(e._message); ++ _message = new char[size + 1]; ++ std::strncpy(_message,e._message,size); ++ _message[size] = 0; ++ return *this; ++ } ++ //! Return a C-string containing the error message associated to the thrown exception. ++ const char *what() const throw() { return _message; } ++ }; ++ ++ // The 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 *const format, ...) { _cimg_exception_err("CImgArgumentException",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 *const format, ...) { _cimg_exception_err("CImgDisplayException",false); } ++ }; ++ ++ // The CImgInstanceException class is used to throw an exception related ++ // to an invalid instance encountered in a library function call. ++ struct CImgInstanceException : public CImgException { ++ CImgInstanceException(const char *const format, ...) { _cimg_exception_err("CImgInstanceException",true); } ++ }; ++ ++ // The 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 *const format, ...) { _cimg_exception_err("CImgIOException",true); } ++ }; ++ ++ // The CImgWarningException class is used to throw an exception for warnings ++ // encountered in a library function call. ++ struct CImgWarningException : public CImgException { ++ CImgWarningException(const char *const format, ...) { _cimg_exception_err("CImgWarningException",false); } ++ }; ++ ++ /*------------------------------------- ++ # ++ # Define cimg:: namespace ++ # ++ -----------------------------------*/ ++ //! Contains \a low-level functions and variables of the \CImg Library. ++ /** ++ Most of the functions and variables within this namespace are used by the \CImg library for low-level operations. ++ You may use them to access specific const values or environment variables internally used by \CImg. ++ \warning Never write using namespace cimg_library::cimg; in your source code. Lot of functions in the ++ cimg:: namespace have the same names as standard C functions that may be defined in the global ++ namespace ::. ++ **/ ++ namespace cimg { ++ ++ // Define traits that will be used to determine the best data type to work in CImg functions. ++ // ++ 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 bool is_inf(const T) { return false; } ++ static bool is_nan(const T) { return false; } ++ static T min() { return ~max(); } ++ static T max() { return (T)1<<(8*sizeof(T) - 1); } ++ static T inf() { return max(); } ++ static T cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(T)val; } ++ static const char* format() { return "%s"; } ++ static const char* format_s() { return "%s"; } ++ static const char* format(const T& val) { static const char *const s = "unknown"; cimg::unused(val); 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 is_inf(const bool) { return false; } ++ static bool is_nan(const bool) { return false; } ++ static bool min() { return false; } ++ static bool max() { return true; } ++ static bool inf() { return max(); } ++ static bool is_inf() { return false; } ++ static bool cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(bool)val; } ++ static const char* format() { return "%s"; } ++ static const char* format_s() { 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 bool is_inf(const unsigned char) { return false; } ++ static bool is_nan(const unsigned char) { return false; } ++ static unsigned char min() { return 0; } ++ static unsigned char max() { return (unsigned char)-1; } ++ static unsigned char inf() { return max(); } ++ static unsigned char cut(const double val) { ++ return val<(double)min()?min():val>(double)max()?max():(unsigned char)val; } ++ static const char* format() { return "%u"; } ++ static const char* format_s() { return "%u"; } ++ static unsigned int format(const unsigned char val) { return (unsigned int)val; } ++ }; ++ ++#if defined(CHAR_MAX) && CHAR_MAX==255 ++ template<> struct type { ++ static const char* string() { static const char *const s = "char"; return s; } ++ static bool is_float() { return false; } ++ static bool is_inf(const char) { return false; } ++ static bool is_nan(const char) { return false; } ++ static char min() { return 0; } ++ static char max() { return (char)-1; } ++ static char inf() { return max(); } ++ static char cut(const double val) { ++ return val<(double)min()?min():val>(double)max()?max():(unsigned char)val; } ++ static const char* format() { return "%u"; } ++ static const char* format_s() { return "%u"; } ++ static unsigned int format(const char val) { return (unsigned int)val; } ++ }; ++#else ++ template<> struct type { ++ static const char* string() { static const char *const s = "char"; return s; } ++ static bool is_float() { return false; } ++ static bool is_inf(const char) { return false; } ++ static bool is_nan(const char) { return false; } ++ static char min() { return ~max(); } ++ static char max() { return (char)((unsigned char)-1>>1); } ++ static char inf() { return max(); } ++ static char cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(char)val; } ++ static const char* format() { return "%d"; } ++ static const char* format_s() { return "%d"; } ++ static int format(const char val) { return (int)val; } ++ }; ++#endif ++ ++ template<> struct type { ++ static const char* string() { static const char *const s = "signed char"; return s; } ++ static bool is_float() { return false; } ++ static bool is_inf(const signed char) { return false; } ++ static bool is_nan(const signed char) { return false; } ++ static signed char min() { return ~max(); } ++ static signed char max() { return (signed char)((unsigned char)-1>>1); } ++ static signed char inf() { return max(); } ++ static signed char cut(const double val) { ++ return val<(double)min()?min():val>(double)max()?max():(signed char)val; } ++ static const char* format() { return "%d"; } ++ static const char* format_s() { return "%d"; } ++ static 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 bool is_inf(const unsigned short) { return false; } ++ static bool is_nan(const unsigned short) { return false; } ++ static unsigned short min() { return 0; } ++ static unsigned short max() { return (unsigned short)-1; } ++ static unsigned short inf() { return max(); } ++ static unsigned short cut(const double val) { ++ return val<(double)min()?min():val>(double)max()?max():(unsigned short)val; } ++ static const char* format() { return "%u"; } ++ static const char* format_s() { 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 bool is_inf(const short) { return false; } ++ static bool is_nan(const short) { return false; } ++ static short min() { return ~max(); } ++ static short max() { return (short)((unsigned short)-1>>1); } ++ static short inf() { return max(); } ++ static short cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(short)val; } ++ static const char* format() { return "%d"; } ++ static const char* format_s() { 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 bool is_inf(const unsigned int) { return false; } ++ static bool is_nan(const unsigned int) { return false; } ++ static unsigned int min() { return 0; } ++ static unsigned int max() { return (unsigned int)-1; } ++ static unsigned int inf() { return max(); } ++ static unsigned int cut(const double val) { ++ return val<(double)min()?min():val>(double)max()?max():(unsigned int)val; } ++ static const char* format() { return "%u"; } ++ static const char* format_s() { 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 bool is_inf(const int) { return false; } ++ static bool is_nan(const int) { return false; } ++ static int min() { return ~max(); } ++ static int max() { return (int)((unsigned int)-1>>1); } ++ static int inf() { return max(); } ++ static int cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(int)val; } ++ static const char* format() { return "%d"; } ++ static const char* format_s() { return "%d"; } ++ static int format(const int val) { return val; } ++ }; ++ ++ template<> struct type { ++ static const char* string() { static const char *const s = "unsigned int64"; return s; } ++ static bool is_float() { return false; } ++ static bool is_inf(const cimg_uint64) { return false; } ++ static bool is_nan(const cimg_uint64) { return false; } ++ static cimg_uint64 min() { return 0; } ++ static cimg_uint64 max() { return (cimg_uint64)-1; } ++ static cimg_uint64 inf() { return max(); } ++ static cimg_uint64 cut(const double val) { ++ return val<(double)min()?min():val>(double)max()?max():(cimg_uint64)val; } ++ static const char* format() { return cimg_fuint64; } ++ static const char* format_s() { return cimg_fuint64; } ++ static unsigned long format(const cimg_uint64 val) { return (unsigned long)val; } ++ }; ++ ++ template<> struct type { ++ static const char* string() { static const char *const s = "int64"; return s; } ++ static bool is_float() { return false; } ++ static bool is_inf(const cimg_int64) { return false; } ++ static bool is_nan(const cimg_int64) { return false; } ++ static cimg_int64 min() { return ~max(); } ++ static cimg_int64 max() { return (cimg_int64)((cimg_uint64)-1>>1); } ++ static cimg_int64 inf() { return max(); } ++ static cimg_int64 cut(const double val) { ++ return val<(double)min()?min():val>(double)max()?max():(cimg_int64)val; ++ } ++ static const char* format() { return cimg_fint64; } ++ static const char* format_s() { return cimg_fint64; } ++ static long format(const long val) { return (long)val; } ++ }; ++ ++ template<> struct type { ++ static const char* string() { static const char *const s = "double"; return s; } ++ static bool is_float() { return true; } ++ static bool is_inf(const double val) { ++#ifdef isinf ++ return (bool)isinf(val); ++#else ++ return !is_nan(val) && (val::min() || val>cimg::type::max()); ++#endif ++ } ++ static bool is_nan(const double val) { ++#ifdef isnan ++ return (bool)isnan(val); ++#else ++ return !(val==val); ++#endif ++ } ++ static double min() { return -DBL_MAX; } ++ static double max() { return DBL_MAX; } ++ static double inf() { ++#ifdef INFINITY ++ return (double)INFINITY; ++#else ++ return max()*max(); ++#endif ++ } ++ static double nan() { ++#ifdef NAN ++ return (double)NAN; ++#else ++ const double val_nan = -std::sqrt(-1.0); return val_nan; ++#endif ++ } ++ static double cut(const double val) { return val; } ++ static const char* format() { return "%.17g"; } ++ static const char* format_s() { return "%g"; } ++ static double format(const double 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 bool is_inf(const float val) { ++#ifdef isinf ++ return (bool)isinf(val); ++#else ++ return !is_nan(val) && (val::min() || val>cimg::type::max()); ++#endif ++ } ++ static bool is_nan(const float val) { ++#ifdef isnan ++ return (bool)isnan(val); ++#else ++ return !(val==val); ++#endif ++ } ++ static float min() { return -FLT_MAX; } ++ static float max() { return FLT_MAX; } ++ static float inf() { return (float)cimg::type::inf(); } ++ static float nan() { return (float)cimg::type::nan(); } ++ static float cut(const double val) { return (float)val; } ++ static float cut(const float val) { return (float)val; } ++ static const char* format() { return "%.9g"; } ++ static const char* format_s() { return "%g"; } ++ static double format(const float val) { return (double)val; } ++ }; ++ ++ template<> struct type { ++ static const char* string() { static const char *const s = "long double"; return s; } ++ static bool is_float() { return true; } ++ static bool is_inf(const long double val) { ++#ifdef isinf ++ return (bool)isinf(val); ++#else ++ return !is_nan(val) && (val::min() || val>cimg::type::max()); ++#endif ++ } ++ static bool is_nan(const long double val) { ++#ifdef isnan ++ return (bool)isnan(val); ++#else ++ return !(val==val); ++#endif ++ } ++ static long double min() { return -LDBL_MAX; } ++ static long double max() { return LDBL_MAX; } ++ static long double inf() { return max()*max(); } ++ static long double nan() { const long double val_nan = -std::sqrt(-1.0L); return val_nan; } ++ static long double cut(const long double val) { return val; } ++ static const char* format() { return "%.17g"; } ++ static const char* format_s() { return "%g"; } ++ static double format(const long double val) { return (double)val; } ++ }; ++ ++#ifdef cimg_use_half ++ template<> struct type { ++ static const char* string() { static const char *const s = "half"; return s; } ++ static bool is_float() { return true; } ++ static bool is_inf(const long double val) { ++#ifdef isinf ++ return (bool)isinf(val); ++#else ++ return !is_nan(val) && (val::min() || val>cimg::type::max()); ++#endif ++ } ++ static bool is_nan(const long double val) { ++#ifdef isnan ++ return (bool)isnan(val); ++#else ++ return !(val==val); ++#endif ++ } ++ static half min() { return (half)-65504; } ++ static half max() { return (half)65504; } ++ static half inf() { return max()*max(); } ++ static half nan() { const half val_nan = (half)-std::sqrt(-1.0); return val_nan; } ++ static half cut(const double val) { return (half)val; } ++ static const char* format() { return "%.9g"; } ++ static const char* format_s() { return "%g"; } ++ static double format(const half val) { return (double)val; } ++ }; ++#endif ++ ++ 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 cimg_uint64 type; }; ++ template<> struct superset { typedef cimg_int64 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 cimg_uint64 type; }; ++ template<> struct superset { typedef cimg_int64 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 cimg_int64 type; }; ++ template<> struct superset { typedef int type; }; ++ template<> struct superset { typedef cimg_int64 type; }; ++ template<> struct superset { typedef cimg_int64 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 cimg_int64 type; }; ++ template<> struct superset { typedef int type; }; ++ template<> struct superset { typedef cimg_int64 type; }; ++ template<> struct superset { typedef cimg_int64 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 cimg_uint64 type; }; ++ template<> struct superset { typedef cimg_int64 type; }; ++ template<> struct superset { typedef float type; }; ++ template<> struct superset { typedef double type; }; ++ template<> struct superset { typedef int type; }; ++ template<> struct superset { typedef cimg_int64 type; }; ++ template<> struct superset { typedef int type; }; ++ template<> struct superset { typedef cimg_int64 type; }; ++ template<> struct superset { typedef cimg_int64 type; }; ++ template<> struct superset { typedef float type; }; ++ template<> struct superset { typedef double type; }; ++ template<> struct superset { typedef cimg_int64 type; }; ++ template<> struct superset { typedef cimg_int64 type; }; ++ template<> struct superset { typedef cimg_int64 type; }; ++ template<> struct superset { typedef cimg_int64 type; }; ++ template<> struct superset { typedef cimg_uint64 type; }; ++ template<> struct superset { typedef cimg_int64 type; }; ++ template<> struct superset { typedef float type; }; ++ template<> struct superset { typedef double type; }; ++ template<> struct superset { typedef cimg_int64 type; }; ++ template<> struct superset { typedef cimg_int64 type; }; ++ template<> struct superset { typedef cimg_int64 type; }; ++ template<> struct superset { typedef float type; }; ++ template<> struct superset { typedef double type; }; ++ template<> struct superset { typedef cimg_int64 type; }; ++ template<> struct superset { typedef cimg_int64 type; }; ++ template<> struct superset { typedef cimg_int64 type; }; ++ template<> struct superset { typedef cimg_int64 type; }; ++ template<> struct superset { typedef cimg_int64 type; }; ++ template<> struct superset { typedef double type; }; ++ template<> struct superset { typedef double type; }; ++ template<> struct superset { typedef double type; }; ++ template<> struct superset { typedef double type; }; ++ template<> struct superset { typedef double type; }; ++#ifdef cimg_use_half ++ template<> struct superset { typedef float type; }; ++ template<> struct superset { typedef float type; }; ++ template<> struct superset { typedef float type; }; ++ template<> struct superset { typedef float type; }; ++ template<> struct superset { typedef float type; }; ++ template<> struct superset { typedef float type; }; ++ template<> struct superset { typedef float type; }; ++ template<> struct superset { typedef double type; }; ++#endif ++ ++ 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_Tt typename cimg::superset::type ++#define _cimg_Tfloat typename cimg::superset::type ++#define _cimg_Ttfloat typename cimg::superset2::type ++#define _cimg_Ttdouble typename cimg::superset2::type ++ ++ // Define variables used internally by CImg. ++#if cimg_display==1 ++ struct X11_info { ++ unsigned int nb_wins; ++ pthread_t *events_thread; ++ pthread_cond_t wait_event; ++ pthread_mutex_t wait_event_mutex; ++ CImgDisplay **wins; ++ Display *display; ++ unsigned int nb_bits; ++ bool is_blue_first; ++ bool is_shm_enabled; ++ bool byte_order; ++#ifdef cimg_use_xrandr ++ XRRScreenSize *resolutions; ++ Rotation curr_rotation; ++ unsigned int curr_resolution; ++ unsigned int nb_resolutions; ++#endif ++ X11_info():nb_wins(0),events_thread(0),display(0), ++ nb_bits(0),is_blue_first(false),is_shm_enabled(false),byte_order(false) { ++#ifdef __FreeBSD__ ++ XInitThreads(); ++#endif ++ wins = new CImgDisplay*[1024]; ++ pthread_mutex_init(&wait_event_mutex,0); ++ pthread_cond_init(&wait_event,0); ++#ifdef cimg_use_xrandr ++ resolutions = 0; ++ curr_rotation = 0; ++ curr_resolution = nb_resolutions = 0; ++#endif ++ } ++ ++ ~X11_info() { ++ delete[] wins; ++ /* ++ if (events_thread) { ++ pthread_cancel(*events_thread); ++ delete events_thread; ++ } ++ if (display) { } // XCloseDisplay(display); } ++ pthread_cond_destroy(&wait_event); ++ pthread_mutex_unlock(&wait_event_mutex); ++ pthread_mutex_destroy(&wait_event_mutex); ++ */ ++ } ++ }; ++#if defined(cimg_module) ++ X11_info& X11_attr(); ++#elif defined(cimg_main) ++ X11_info& X11_attr() { static X11_info val; return val; } ++#else ++ inline X11_info& X11_attr() { static X11_info val; return val; } ++#endif ++#define cimg_lock_display() cimg::mutex(15) ++#define cimg_unlock_display() cimg::mutex(15,0) ++ ++#elif cimg_display==2 ++ struct Win32_info { ++ HANDLE wait_event; ++ Win32_info() { wait_event = CreateEvent(0,FALSE,FALSE,0); } ++ }; ++#if defined(cimg_module) ++ Win32_info& Win32_attr(); ++#elif defined(cimg_main) ++ Win32_info& Win32_attr() { static Win32_info val; return val; } ++#else ++ inline Win32_info& Win32_attr() { static Win32_info val; return val; } ++#endif ++#endif ++ ++ struct Mutex_info { ++#if cimg_OS==2 ++ HANDLE mutex[32]; ++ Mutex_info() { for (unsigned int i = 0; i<32; ++i) mutex[i] = CreateMutex(0,FALSE,0); } ++ void lock(const unsigned int n) { WaitForSingleObject(mutex[n],INFINITE); } ++ void unlock(const unsigned int n) { ReleaseMutex(mutex[n]); } ++ int trylock(const unsigned int) { return 0; } ++#elif defined(_PTHREAD_H) ++ pthread_mutex_t mutex[32]; ++ Mutex_info() { for (unsigned int i = 0; i<32; ++i) pthread_mutex_init(&mutex[i],0); } ++ void lock(const unsigned int n) { pthread_mutex_lock(&mutex[n]); } ++ void unlock(const unsigned int n) { pthread_mutex_unlock(&mutex[n]); } ++ int trylock(const unsigned int n) { return pthread_mutex_trylock(&mutex[n]); } ++#else ++ Mutex_info() {} ++ void lock(const unsigned int) {} ++ void unlock(const unsigned int) {} ++ int trylock(const unsigned int) { return 0; } ++#endif ++ }; ++#if defined(cimg_module) ++ Mutex_info& Mutex_attr(); ++#elif defined(cimg_main) ++ Mutex_info& Mutex_attr() { static Mutex_info val; return val; } ++#else ++ inline Mutex_info& Mutex_attr() { static Mutex_info val; return val; } ++#endif ++ ++#if defined(cimg_use_magick) ++ static struct Magick_info { ++ Magick_info() { ++ Magick::InitializeMagick(""); ++ } ++ } _Magick_info; ++#endif ++ ++#if cimg_display==1 ++ // Define 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 ++ // Define 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; ++ ++#else ++ // Define random keycodes when no display is available. ++ // (should rarely be used then!). ++ const unsigned int keyESC = 1U; //!< Keycode for the \c ESC key (architecture-dependent). ++ const unsigned int keyF1 = 2U; //!< Keycode for the \c F1 key (architecture-dependent). ++ const unsigned int keyF2 = 3U; //!< Keycode for the \c F2 key (architecture-dependent). ++ const unsigned int keyF3 = 4U; //!< Keycode for the \c F3 key (architecture-dependent). ++ const unsigned int keyF4 = 5U; //!< Keycode for the \c F4 key (architecture-dependent). ++ const unsigned int keyF5 = 6U; //!< Keycode for the \c F5 key (architecture-dependent). ++ const unsigned int keyF6 = 7U; //!< Keycode for the \c F6 key (architecture-dependent). ++ const unsigned int keyF7 = 8U; //!< Keycode for the \c F7 key (architecture-dependent). ++ const unsigned int keyF8 = 9U; //!< Keycode for the \c F8 key (architecture-dependent). ++ const unsigned int keyF9 = 10U; //!< Keycode for the \c F9 key (architecture-dependent). ++ const unsigned int keyF10 = 11U; //!< Keycode for the \c F10 key (architecture-dependent). ++ const unsigned int keyF11 = 12U; //!< Keycode for the \c F11 key (architecture-dependent). ++ const unsigned int keyF12 = 13U; //!< Keycode for the \c F12 key (architecture-dependent). ++ const unsigned int keyPAUSE = 14U; //!< Keycode for the \c PAUSE key (architecture-dependent). ++ const unsigned int key1 = 15U; //!< Keycode for the \c 1 key (architecture-dependent). ++ const unsigned int key2 = 16U; //!< Keycode for the \c 2 key (architecture-dependent). ++ const unsigned int key3 = 17U; //!< Keycode for the \c 3 key (architecture-dependent). ++ const unsigned int key4 = 18U; //!< Keycode for the \c 4 key (architecture-dependent). ++ const unsigned int key5 = 19U; //!< Keycode for the \c 5 key (architecture-dependent). ++ const unsigned int key6 = 20U; //!< Keycode for the \c 6 key (architecture-dependent). ++ const unsigned int key7 = 21U; //!< Keycode for the \c 7 key (architecture-dependent). ++ const unsigned int key8 = 22U; //!< Keycode for the \c 8 key (architecture-dependent). ++ const unsigned int key9 = 23U; //!< Keycode for the \c 9 key (architecture-dependent). ++ const unsigned int key0 = 24U; //!< Keycode for the \c 0 key (architecture-dependent). ++ const unsigned int keyBACKSPACE = 25U; //!< Keycode for the \c BACKSPACE key (architecture-dependent). ++ const unsigned int keyINSERT = 26U; //!< Keycode for the \c INSERT key (architecture-dependent). ++ const unsigned int keyHOME = 27U; //!< Keycode for the \c HOME key (architecture-dependent). ++ const unsigned int keyPAGEUP = 28U; //!< Keycode for the \c PAGEUP key (architecture-dependent). ++ const unsigned int keyTAB = 29U; //!< Keycode for the \c TAB key (architecture-dependent). ++ const unsigned int keyQ = 30U; //!< Keycode for the \c Q key (architecture-dependent). ++ const unsigned int keyW = 31U; //!< Keycode for the \c W key (architecture-dependent). ++ const unsigned int keyE = 32U; //!< Keycode for the \c E key (architecture-dependent). ++ const unsigned int keyR = 33U; //!< Keycode for the \c R key (architecture-dependent). ++ const unsigned int keyT = 34U; //!< Keycode for the \c T key (architecture-dependent). ++ const unsigned int keyY = 35U; //!< Keycode for the \c Y key (architecture-dependent). ++ const unsigned int keyU = 36U; //!< Keycode for the \c U key (architecture-dependent). ++ const unsigned int keyI = 37U; //!< Keycode for the \c I key (architecture-dependent). ++ const unsigned int keyO = 38U; //!< Keycode for the \c O key (architecture-dependent). ++ const unsigned int keyP = 39U; //!< Keycode for the \c P key (architecture-dependent). ++ const unsigned int keyDELETE = 40U; //!< Keycode for the \c DELETE key (architecture-dependent). ++ const unsigned int keyEND = 41U; //!< Keycode for the \c END key (architecture-dependent). ++ const unsigned int keyPAGEDOWN = 42U; //!< Keycode for the \c PAGEDOWN key (architecture-dependent). ++ const unsigned int keyCAPSLOCK = 43U; //!< Keycode for the \c CAPSLOCK key (architecture-dependent). ++ const unsigned int keyA = 44U; //!< Keycode for the \c A key (architecture-dependent). ++ const unsigned int keyS = 45U; //!< Keycode for the \c S key (architecture-dependent). ++ const unsigned int keyD = 46U; //!< Keycode for the \c D key (architecture-dependent). ++ const unsigned int keyF = 47U; //!< Keycode for the \c F key (architecture-dependent). ++ const unsigned int keyG = 48U; //!< Keycode for the \c G key (architecture-dependent). ++ const unsigned int keyH = 49U; //!< Keycode for the \c H key (architecture-dependent). ++ const unsigned int keyJ = 50U; //!< Keycode for the \c J key (architecture-dependent). ++ const unsigned int keyK = 51U; //!< Keycode for the \c K key (architecture-dependent). ++ const unsigned int keyL = 52U; //!< Keycode for the \c L key (architecture-dependent). ++ const unsigned int keyENTER = 53U; //!< Keycode for the \c ENTER key (architecture-dependent). ++ const unsigned int keySHIFTLEFT = 54U; //!< Keycode for the \c SHIFTLEFT key (architecture-dependent). ++ const unsigned int keyZ = 55U; //!< Keycode for the \c Z key (architecture-dependent). ++ const unsigned int keyX = 56U; //!< Keycode for the \c X key (architecture-dependent). ++ const unsigned int keyC = 57U; //!< Keycode for the \c C key (architecture-dependent). ++ const unsigned int keyV = 58U; //!< Keycode for the \c V key (architecture-dependent). ++ const unsigned int keyB = 59U; //!< Keycode for the \c B key (architecture-dependent). ++ const unsigned int keyN = 60U; //!< Keycode for the \c N key (architecture-dependent). ++ const unsigned int keyM = 61U; //!< Keycode for the \c M key (architecture-dependent). ++ const unsigned int keySHIFTRIGHT = 62U; //!< Keycode for the \c SHIFTRIGHT key (architecture-dependent). ++ const unsigned int keyARROWUP = 63U; //!< Keycode for the \c ARROWUP key (architecture-dependent). ++ const unsigned int keyCTRLLEFT = 64U; //!< Keycode for the \c CTRLLEFT key (architecture-dependent). ++ const unsigned int keyAPPLEFT = 65U; //!< Keycode for the \c APPLEFT key (architecture-dependent). ++ const unsigned int keyALT = 66U; //!< Keycode for the \c ALT key (architecture-dependent). ++ const unsigned int keySPACE = 67U; //!< Keycode for the \c SPACE key (architecture-dependent). ++ const unsigned int keyALTGR = 68U; //!< Keycode for the \c ALTGR key (architecture-dependent). ++ const unsigned int keyAPPRIGHT = 69U; //!< Keycode for the \c APPRIGHT key (architecture-dependent). ++ const unsigned int keyMENU = 70U; //!< Keycode for the \c MENU key (architecture-dependent). ++ const unsigned int keyCTRLRIGHT = 71U; //!< Keycode for the \c CTRLRIGHT key (architecture-dependent). ++ const unsigned int keyARROWLEFT = 72U; //!< Keycode for the \c ARROWLEFT key (architecture-dependent). ++ const unsigned int keyARROWDOWN = 73U; //!< Keycode for the \c ARROWDOWN key (architecture-dependent). ++ const unsigned int keyARROWRIGHT = 74U; //!< Keycode for the \c ARROWRIGHT key (architecture-dependent). ++ const unsigned int keyPAD0 = 75U; //!< Keycode for the \c PAD0 key (architecture-dependent). ++ const unsigned int keyPAD1 = 76U; //!< Keycode for the \c PAD1 key (architecture-dependent). ++ const unsigned int keyPAD2 = 77U; //!< Keycode for the \c PAD2 key (architecture-dependent). ++ const unsigned int keyPAD3 = 78U; //!< Keycode for the \c PAD3 key (architecture-dependent). ++ const unsigned int keyPAD4 = 79U; //!< Keycode for the \c PAD4 key (architecture-dependent). ++ const unsigned int keyPAD5 = 80U; //!< Keycode for the \c PAD5 key (architecture-dependent). ++ const unsigned int keyPAD6 = 81U; //!< Keycode for the \c PAD6 key (architecture-dependent). ++ const unsigned int keyPAD7 = 82U; //!< Keycode for the \c PAD7 key (architecture-dependent). ++ const unsigned int keyPAD8 = 83U; //!< Keycode for the \c PAD8 key (architecture-dependent). ++ const unsigned int keyPAD9 = 84U; //!< Keycode for the \c PAD9 key (architecture-dependent). ++ const unsigned int keyPADADD = 85U; //!< Keycode for the \c PADADD key (architecture-dependent). ++ const unsigned int keyPADSUB = 86U; //!< Keycode for the \c PADSUB key (architecture-dependent). ++ const unsigned int keyPADMUL = 87U; //!< Keycode for the \c PADMUL key (architecture-dependent). ++ const unsigned int keyPADDIV = 88U; //!< Keycode for the \c PADDDIV key (architecture-dependent). ++#endif ++ ++ const double PI = 3.14159265358979323846; //!< Value of the mathematical constant PI ++ ++ // Define a 12x13 font (small size). ++ static const char *const data_font12x13 = ++ " .wjwlwmyuw>wjwkwbwjwkwRxuwmwjwkwmyuwJwjwlx`w Fw " ++ " mwlwlwuwnwuynwuwmyTwlwkwuwmwuwnwlwkwuwmwuw_wuxlwlwkwuwnwuynwuwTwlwlwtwnwtwnw my Qw +wlw b" ++ "{ \\w Wx`xTw_w[wbxawSwkw nynwkyw bwswcw" ++ "kwuwjwuwozpwtwuwnwtwowkwjwmwuwuwkwIxmxuxowuwmwswowswmxnwjwhwowswowsw0wmwowswuwnwrwowswpwswowkwjwrwqw" ++ "rwpwkwkwtwnwkxsxqxswowswpwswnwswpwswowrwnwmwrwqwqwqwswswrwswowswjwpwlxjwkxuxLw[wcw_wSwkw mw\"wlwiw=wt" ++ "wmxlwFw cwswnwuwnwkwjwswo{pwrwpwtwtwpwswby`w`yUwlwtwpwqwpwswowlw\\wrwrxuwHwrwfwuwjwlwlwTyuwVwlwtwawsw" ++ "owswowswcwuwmwuwmwuwmwuwmwuwlwkwuwnwswpwkwkwkwkwkwkwkwkwswoxswowswowswowswowswowswowrwpwswpwrwpwrwpw" ++ "rwpwrwpwswoznwtw Ww (wGwtwtwqwqwqwuwuwuwqwswuwqwqw=wqxtw`{nzp~q{ozowrwnxmwtwow bzawkwuwl}rwuwnwtwuw" ++ "nwtwowkwjwlyjwIwlwswmwiwkwnwuwnwkwhwnwswowswowkwewewixnwsytwswuwnwrwpwkwrwpwkwkwkwrwpwkwkwuwmwkxsxqw" ++ "uwtwpwqwqwswowqwqwswowiwmwrwpwswowtwtwpwuwmwuwjwowkwjwlxsxXynzmymznyozlzoznwkwkwtwnwkzuyrzmynzmzowux" ++ "myozmwswpwrwowtwtwrwrwpwrwp{mwlwiwHyuwpwtwkwmxlynzoxswmwmwswnwswowtxq|owtwtwpym{p{owswnwuwmwlwkwqwqx" ++ "uwuxqwrwpwtwtwqwqwowlwuwuwkwmwlwtwowuwuwdwjznwl{nwuwnwkx_wtxtwswtwlwtwWwuytwgyjwmwjwawswoyuwVwlwtwnw" ++ "twmwtwnwtwmwuwmwlwuwmwuwmwuwmwuwmwuwmwuwmxuwowkwkwkwkwkwkwkwkwkwrwpwuwtwpwqwqwqwqwqwqwqwqwqwowtwpwsw" ++ "uwqwrwpwrwpwrwpwrwowuwnwswowuwlymymymymymymyuyqymymymymynwkwkwkwjynzmymymymymykwmzowswowswowswowswpw" ++ "rwozowrwW}q}qwtwtwqwtwtwqwtwtwA}rwuw_{p~r~r}pwtwowrwnxmwtwow aw_w]wtwpwuwmxuwmybwjwlyjwIwlwswmwiwnyn" ++ "wtwnznzkwmynwswTyp}pylwmwtwtwtwswuwn{owkwrwp{o{owk|pwkwkxlwkwuwuwuwqwuwtwpwqwqwswowqwqwswoykwmwrwpws" ++ "wowuwuwuwowkwjwnwkwjwDwowswowkwswowswowkwswowswowkwkwuwmwkwswswswswowswowswowswoxlwswowkwswpwrwowtwt" ++ "wqwtwowrwlwoxkwhxVxuxpwtypwuwjwnwtwnwkwswowtxnxmwswowqwqwtwuxqwtwnwtwtwqwswowswmwm{nwuwlxnwkwqwqwtwt" ++ "wqwrwpwtwtwqwuyuwpwiwhwnwmwrwnwbwkwuwlwlwswoxuxowlwtw`wuwrwszmwtwo}dwuwtwuw[}qymx`wswoyuwow_ylxlwtwo" ++ "yuwoyuwoyuwmwlwuwmwuwmwuwmwuwmwuwmwuwmwt{swk{o{o{o{owkwkwkwlztwpwuwtwpwqwqwqwqwqwqwqwqwqwnxowtwtwqwr" ++ "wpwrwpwrwpwrwnwmwswowuwiwkwkwkwkwkwkwswswkwswowswowswowswowkwkwkwkwswowswowswowswowswowswowswcwtxows" ++ "wowswowswowswpwrwowswpwrwWwtwtwqwqwqwuwuwuwqwuwswqwqw>wowuw`}q~q|q}qwrwpwrwowtwnwtwo~ izaw]wtwoykwux" ++ "qwtwswfwjwmwuwuwn}eyaxlwswmwjwjwpwswjwowswmwmwswnzWy]ypwlwtwtwuwswswowrwpwkwrwpwkwkwsyqwrwpwkwkwuwmw" ++ "kwuwuwuwqwtwuwpwqwqznwqwqzkynwmwrwowuwnwuwuwuwowkwjwnwkxkwGzowswowkwswo{owkwswowswowkwkxlwkwswswswsw" ++ "owswowswowswowjxmwkwswowtwnwuwuwuwpxmwtwlwlwlwiwlytwewtwtwqwswowtxoznwswnxmwswnwuwmwuwnwswowtwtwqwtw" ++ "twqwtwnwtwtwqwswowswmwmwswowswmwmwkwqwqwtwtwqwrwowuwuwpwuyuwq~own~own~owbwkwuwmznwswmwbwswawuwrwgwtw" ++ "hwdwuytwXwJwswnxuw=wtwmwswowtxowswqxmwswowswowswowswowswowswnwtwowkwkwkwkwkwkwkwkwkwrwpwtwuwpwqwqwqw" ++ "qwqwqwqwqwqwnxowtwtwqwrwpwrwpwrwpwrwnwmwswowtwmznznznznznzn~swk{o{o{o{owkwkwkwkwswowswowswowswowswow" ++ "swowswo}qwuwuwowswowswowswowswowtwnwswowtwUwuwuwowswowswowswowsw@}qx`}q~pzo{pwrwpwrwowtwnwtwow aw_w_" ++ "}owuwmwuwtwrwswuwewjwkwiwJwkwswmwkwiwp|kwowswmwmwswkwWym}mypwlwszr{owrwpwkwrwpwkwkwqwqwrwpwkwkwtwnwk" ++ "wtwtwqwtwuwpwqwqwkwqwqwtwiwnwmwrwowuwnwuwuwuwpwuwlwkwmwjwkwHwswowswowkwswowkwkwswowswowkwkwuwmwkwsws" ++ "wswswowswowswowswowhwnwkwswowtwnwuwuwuwpxmwtwmwkwlwiwmwtydwtwtwqwswowswowtwnwswowkwswnwuwnwtwnwswowt" ++ "wtwqwtwtwqwtwnwtwtwqwswowswmwmwswowswnwlwkwqwqxuwuxqwrwnyowqwpwiwhwpwuwuwowrwpwuwuwdwkwuwlwlwswo{owk" ++ "xuwawtxtwszmwtwiwdwuwtwuwXwJwswmwuwKzmwtwlwtxowrwpwtxrxl{o{o{o{o{o{o{owkwkwkwkwkwkwkwkwkwrwpwtwuwpwq" ++ "wqwqwqwqwqwqwqwqwowtwpwuwswqwrwpwrwpwrwpwrwnwmznwswowswowswowswowswowswowswowswowkwkwkwkwkwkwkwkwkws" ++ "wowswowswowswowswowswowswcwuwuwowswowswowswowswowtwnwswowtwTymymymymy=wmw^wuwuwmxlxmyowrwowtwnwtwmxm" ++ "w bwswIwuwmwuwmwuwtwrxswdwjw]wJwkxuxmwlwlwswlwjwowswmwmwswlwSycyawlwswowrwowswpwswowkwjwrwqwrwpwkwkw" ++ "swowkwqwqwsxowswpwjwswpwswowrwnwmxtxnwlwswpwswmwlwlwjwkwHwswowswowkwswowswowkwswowswowkwkwtwnwkwswsw" ++ "swswowswowswowswowkwswowkwswnxlwswpwtwmxmwjwlwiwTxuxpwtxowswowtwnwswowkwswnynwtwnwswowtwtwqxuwuxqwtw" ++ "nwtwtwqwswowswmwlwuwnwswowkwjwswo{pwrwmwmwswnwjwiwnymwtwnycwkwuwlwl{mwmwiw_wrwdwtwVwrw*wswmwuw?wtwlw" ++ "tzqwrwpwtzswkwswowswowswowswowswowswowswnwswpwkwkwkwkwkwkwkwkwswowsxowswowswowswowswowswowrwpwswpxtx" ++ "pxtxpxtxpxtxnwmwkwswowswowswowswowswowswowswowtxowkwswowswowswowswowkwkwkwkwswowswowswowswowswowswow" ++ "swlwnxtwowswowswowswowswnxmwswnx >wlw\\wkx`wnwrwoznwtwmxl| gybw^wtwozmwsxpzuxfxlx]wnw_wlxjyn{o{nykwnz" ++ "mymwkynymwkwewewjwjwrwswqwp{myozn{owizpwrwpwkwkwrwp{owqwqwsxnyowiyowrwozmwlzmwlwswqxsxnwm}qwjxlwGzoz" ++ "mymznynwjzowswowkwkwswowkwswswswswnynzmzowjymxlznxlwswqwrwnwm{mwlwiwHxuxpzmxlymynwswmwnwrwozmxuxo{pw" ++ "txn{pzmykwmyo}p{owkyuynwnwrwmwly`w_w_wbwjzo{pwqwnwmwhw_z>zY}M|nwuw2wqwqwryrwqwqyowqwqwqwqwqwqwqwqwqw" ++ "qwqwqwr{qyo{o{o{o{owkwkwkwkznwsxnymymymymycwuynznznznzmwmwkwuynznznznznznznyuzrymymymymynwkwkwkwjynw" ++ "swnymymymymybzmznznznznwlzmw hwHwlwSwTw {+qnrmqapmp Kpepgpiuhpephscqfqhqfqhqfqhqfqhqfqhqfqhqixgudxdxdxdxdq]q]q]q]wcqjr" ++ "bt`t`t`t`taphpgplt`s_s_s_s_q`q]qmsctnqctnqctnqctnqctnqctnqbsktgs_uauauaucq]q]q]q[saqjqbs_s_s_s_sNpms" ++ "_snqbsnqbsnqbsnqaq`qns_q !p Zp jp#q\\q6q7q lq [sjq Qq -q OqZq]q Cq;q HqWq $rIq`qZq _q iqbqK" ++ "qFqIq`q hp$q]u JqYpmpLp .p jp ]p Xr`q[r !p Tp\"p\\p6q6q mq Yx Qr -r Ps\\q_s" ++ " Ipkq:q HqWq $qHq`qZq _q iqbqKqFqIq`q hp$q]t IqYpmpLq /q kq Fq_q[q #s Tp\"q" ++ "^q6p 1p Vu Rs YsJsMy &v])]2_4^U^ 6^T\\5])]1_2]T\\8^U^ K])]2`4^V^3] " ++ " " ++ " U]*\\2a4`V\\8^U^5a F]*\\1\\X\\4^U^=]*\\" ++ "2a5^U^ 7aV\\4]*\\1a4`V\\8^U^ J]*\\1\\X\\4^V^3\\ " ++ " " ++ " S],\\1\\W\\5g8^U^6c F],\\1\\V\\5^U^<],\\2]W]6^U^" ++ " 8h3],\\0\\W\\5g8^U^ I],\\1\\V\\5^V^4\\ ;] " ++ " " ++ " :\\-]2\\U\\6\\V`7^U^7]U] F\\-]2\\T\\6^U^;\\-]3]U]7^U^ 8\\" ++ "Va1\\-]1\\U\\6\\V`7^U^ H\\-]2\\T\\6^V^5] =a J] " ++ " " ++ " N\\/]2\\S\\7\\T]6^U^7\\S\\ E\\/]2\\R\\7^U^:\\/]3]S]8^U^" ++ " 8\\T^/\\/]1\\S\\7\\T]6^U^ G\\/]2\\R\\7^V^6] =c L^ " ++ " *^ U` " ++ " O^ )\\S\\ !^$^3\\ E]" ++ "U\\ K^$^4^ G^$^4] J^$^3\\ #^$^3\\ 4^ B[ " ++ " &^ Xe " ++ " S^ (\\S\\ )Z Q^&^3^2]S\\ A\\S\\ K^&^3^ F^&^4_ >]S" ++ "\\9^&^3^2]S\\ W^&^3^ 6^ Q] M[ ?` ![1^H]?` =]4](\\ %` >b4c Bb " ++ "?`2a .a Ib Pb Aa `0`*^ $^.` <^F]F^F]G`G] " ++ " F\\S\\ ;b %a2a2a2a2a a:].a !^T_ Bg ` Dd2_8n?" ++ "m7g3]:rD]P]P]@g <] 8] 8] B] 3e J^K^ If7^U^+b@d Fb@f5a Ad4e-] :f Ra0d AaF\\HaF\\HeJ\\?]._0_" ++ "0_0_0_2\\U\\0tHh@n?n?n?n?].].].]-h:_J]w " ++ "P[ 9[/a:aQa7[ Wl \"h E]1]T]+\\R\\;[4dL]Ag=])]2])\\ U^1f8c8k;j1`;k7h?n;h9g 5i*b:_" ++ "8k6kBl=n?l7mD]H]C].].]L_A].`I`H`K]>kAj6kAj9kBuB]H]F]E]E^L_L^R^L^D^I^BrBb7^+b(a D] ;] '] Gd" ++ " A].].].].] ;] (b:].b #^Q] Dj !a Ff3_8n?m8i4]:rD]P]P]Bk ?_ 9] 9_ C]&[0f " ++ "I]K]=]0g7^U^-fC\\S] IfBf6c B[S]5[S].] `K]>k]*]3]W]6^U^._V_;]Wa5]*]2\\V\\6]Wa7^V^ I]*]2\\V\\5^V^2]7]+^V^ @]W\\=v P[ 9\\1c_8m:`R`Cn?n?l9`QaE]H]C].].]M_@].aKaH`K]?`S`Bk8`S`Bk;_R_BuB]H]F]E]D]MaM]P]L]B^K^ArB]1]&])" ++ "c D] <] '] G] :].].].].] ;] (^6]*^ #]P^ E^P\\ V^ H^T^4_8n?m:`S`6]:rD]P]P" ++ "]C`S` Aa :] :a D]&[1^S\\ I^M^=]0^R[7^U^/^R^EZO\\ L^R^ N]U] :],\\0] \\H]B\\H]=\\M]>" ++ "]._0_0_0_0_0_/uK`R`Cn?n?n?n?].].].]-n@`K]?`S`>`S`>`S`>`S`>`S` H`ScE]H]C]H]C]H]C]H]E^K^@],^" ++ "T^5],]1\\V\\6\\U`7^V^6]U\\ F],]2\\T\\6^U^=],]2\\U\\6^U^-e9\\U`4],]1\\V\\6\\U`7^V^ H],]1\\V\\5^V^3]6]+^" ++ "V^ B`1`1`1`1`6]W]>u P[ 9]2e>eUf;^ %q $^O\\ F]1]T],]S];[5]T]N\\@]P[=]*]0]2ZR\\RZ $" ++ "]2]P]<_W]8]N]\\H\\A\\H\\<\\M\\=]/a2a2a2a2a1_/]V];_M]C].].].].].].].]" ++ "-]ObBaL]@^M^@^M^@^M^@^M^@^M^ J^N`D]H]C]H]C]H]C]H]E^K^@]-^Q]5].]1\\T\\7\\S]6^V^5c E].]2]S\\7^U" ++ "^<].]2\\S\\7^U^,a6\\S]2].]1\\T\\7\\S]6^V^ G].]1\\T\\6^V^4]5]+^V^ De6e6e6e6e9\\U\\>u P[ :_3f@gVf<" ++ "_ &r $]M[ F]1]T],\\R]>d<^T^P]A^OZ=]+].]4]T\\T] &^3^P^=[S]8[K].]4\\X];],]!]<]N]>^O^ " ++ " 8ZM^3`P`Ba9]M^=^J\\C]K_B].],^H\\E]H]C].].]O_>].aKaHaL]A^K^D]N^<^K^D]N^>]JZ6]6]H]E]G]C]MaM]" ++ "O^P^@^M^-^A]1]&]+_W_ D] >] '] H] 9] B].] ;] )]4](] %]N]:c6] G] J^P^7a8" ++ "_1],^K^;c=]H]D]P]P]E^K^ Ee <] " ++ "\\I]A\\I]<\\N]=]/a2a2a2a2a2a1]U]<^J\\C].].].].].].].]-]K_CaL]A^K^B^K^B^K^B^K^B^K^ K]K^D]H]C]H]" ++ "C]H]C]H]D^M^?]-]P]4]0]1\\R\\ Ha C]0]2]R] E]0]2\\Q\\ 9c 9]0]1\\R\\ !]0]1\\R\\ ?]4] Di:i:i:i:i" ++ ";\\6]G] P\\ :`5g@gWh>a (_ J]KZ F]1]T],\\R\\?h>]R]P\\@]1]+].]3^V\\V^.] T]2]N]5]8ZJ]-]" ++ "6]X];]-]!^=]L]?]M] *]5_J_Ec:]L^>]H[C]I^C].],]F[E]H]C].].]P_=].]X]M]X]HbM]A]I]D]M]<]I]D]" ++ "M]?]%]6]H]E]G]C^NaN^N]Q^>^O^-^@]0]'],_U_ &] '] H] 9] B].] ;] )]4](] %]N]:d7] " ++ " F] K]N]8c8^1],]I]>i@]H]D]P]P]E]I] Fg =] =g G]&[2] <]O];]1] 1\\F\\=\\ Q\\F\\ S\\Q\\+]3\\." ++ "] IeU\\ M\\3\\N\\ ?\\I\\@\\I\\=]M\\<]0c4c4c4c4c3a1]U]<]H[C].].].].].].].]-]J_DbM]A]I]B]I]B]I]B]I]" ++ "B]I] L]J_E]H]C]H]C]H]C]H]C^O^>].]N] .] '`X_ I] FbWa=bWa=bWa=bWa=bWa<" ++ "\\6^I^ ?Z2[ :a5gAiXh?c *^ H] 7]1]T]-]S]Aj>]R]Q]@]1],],\\1^X\\X^,] T]3]L]6]'].]7]W]" ++ ";]-]!]<]L]?]M^ +]6^F^F]W]:]K]?]FZC]H^D].]-]DZE]H]C].].]Q_<].]X]M]X]H]X]M]B]G]E]M^>]G]E]" ++ "M^@]%]6]H]E^I^B]O^X]O]M^R^=]O^-^@]0]']-_S_ '] '] H] 9] B].] ;] )]4](] %]N]:e8" ++ "_ H] L]M]8]W]7^2]-]G]AmB]H]D]P]P]F]G] Hi >] >i J[3] ;^Q^;]1] 2\\RbT\\Ge R\\VdR\\ T\\" ++ "Q\\+]4\\2a IfU\\ M\\3\\N\\ ?\\J\\?\\J\\AaM\\ G]W]4]W]4]W]4]W]4]W]4c3^U]=]FZC].].].].].].].]-]H]D]X]" ++ "M]B]G]D]G]D]G]D]G]D]G]A[H[B]J`E]H]C]H]C]H]C]H]B]O^>g8]N] 1]T_ 3[ 9] " ++ "G_O^?_O^?_O^?_O^?_O^=\\5]I^ @\\3[ ;c6gAy?d7`8]L]7^7]L]>^ H] 6]1]T]-]S]B_W[U]>]R]R]?]1" ++ "],],]0d*] T]3]L]6]'].]7\\V];].] ]<]L]@]K] 7Z PZ X]7^D^G]W]:]K]?]/]G]D].]-]/]H]C].].]R_;]" ++ ".]X^O^X]H]X^N]B]G]E]L]>]G]E]L]@]%]6]H]D]I]A]O]W]O]L^T^<^Q^-^?]0]'].^O^ Sb7]U`2b4`U]8a8])`" ++ "7]T_ M].]%_O_@_2`0`3`/_3c9] )]4](] N_6]N]3^7a/c0_ <^ D[U^ Ga N]L]9]W]6^3]-]G]B`W" ++ "]W`C]H]D]P]P]F]G] I_X]X_ ?] ?_X]X_ Nb7]2ZFZ=]Q]:]0] 3[SfU[Ig R[UfS[ T\\Q\\+]5]2a IfU\\ M" ++ "\\3\\N\\ ?\\K]?\\K]AaN] G]W]4]W]4]W]4]W]4]W]4]W]3]T]=]/].].].].].].].]-]G]E]X^N]B]G]D]G]D]G]D]G" ++ "]D]G]B]J]C]KbF]H]C]H]C]H]C]H]B^Q^=j;]P_9b3b3b3b3b3b3bN`Bb3a2a2a2a V_2_2`1`1`1`1` ;aU] " ++ " :]U` S^T]U^A^L^A^L^A^L^A^L^?]5]I] @^5\\ ]R]R\\>]1],],].`(] U^3]L]6]'].]8]V];].]!^<]L]@]K] :] P]#^8^A]I^W^;]K]@].]G^E].].].]H]" ++ "C].].]S_:].]W]O]W]H]W]N]C]E]F]L]?]E]F]L]@]%]6]H]D]J^A]O]W]O]L^U^:^S^-^>]0^(]/^M^ Wh:]Wd6f" ++ "8dW]:e>h2dW]?]Vd<].].]O_>].]WdScK]Vd8f;]Wd7dW]?]Wa6h>h6]L]B]I]A]P`P]K^L^B^K^@l4]4](] PdU" ++ "]A]N]2^8e5g;]Vd?^J^8]6]L] E]V`>pA]S]S]:e6kDo>]L]:^W^6^4].]E]D_U]U_D]H]D]P]P]G]E] K_W]W_ @]" ++ " @_W]W_ Qf9]3\\H\\>^S^:]0_ 6[ThT[K]Q\\ S[T\\R]S[ U]S]+]6],] ?]L]@fU\\ M\\3\\N\\ ?\\K\\>\\K\\;]O\\ G" ++ "^W^6^W^6^W^6^W^6^W^5]W]4^T]>].].].].].].].].]-]G^F]W]N]C]E]F]E]F]E]F]E]F]E]D_L_E]K]W]F]H]C" ++ "]H]C]H]C]H]A^S^^K^ O]S]S]B]I]B]I]B]I]B]I]@]5^K^ @]4[ ;f8gAyAg] F] 6]1]T]" ++ "-\\R\\B]T[6]R]S]>^2]-]*\\.`(] U]2]L]6]'].]9]U];].]!];]L]@]K] =` P`'^7]?\\I]U];]K]@].]F]E].]" ++ ".].]H]C].].]T_9].]W]O]W]H]W^O]C]E]F]L]?]E]F]L]@]%]6]H]C]K]@^P]W]P^K^V^9]S]-^=]/](]0^K^ Xi" ++ ";]Xf9h9fX]h6]L]A]K]@^Q`Q^J^N^@]K]?l4]4](]" ++ " QfW^A]O^1]6f9h;]Xg@_K]7]6]L]=]G]C^Wc@pA]S]S]]L]:]U]5^5].]E]E^S]S^E]H]D]P]P]G]E]@" ++ "Z+]V]V^-Z4]5ZKZ:]V]V^ Sh9]4^J^>]S]9]._ 8[U_Q[T[L]P\\ S[T\\Q]T[ T]U]*]7]*] @]L]@fU\\ M\\3\\N" ++ "\\ ?\\L]>\\L]:]Q]:]1]U]6]U]6]U]6]U]6]U]6^W^5]S]>].].].].].].].].]-]F]F]W^O]C]E]F]E]F]E]F]E]F]" ++ "E]C_N_D]L^W]F]H]C]H]C]H]C]H]@]S];]P_=]S^8i:i:i:i:i:i:iVgIh9h9h9h9h<].].].]'d<]Xg:h9h9h9h9h" ++ "0^8k?]L]?]L]?]L]?]L]A]K]>]Xf>]K] O]R]R]D]G]D]VZOZV]D]KZV]D]G]A]4]K] @]3[ j=]L]8" ++ "`7]N]?] F^ 6]1]T]5uI]T[6]R]S\\<^3]-]*]1d*] U]3]J]7]'].]9\\T];].\\Ua-^;]L]@]K^?].] Uc " ++ "Pc+_8]>]J]U];]K]@].]F]E].].].]H]C].].]U_8].]W^Q^W]H]V]O]C]E]F]L]?]E]F]L]@^&]6]H]C]K]?]Q^V]" ++ "Q]I^X^8^U^.^<]/](]1^I^ ]R_h6]L]A]K]?]Q`Q]H^P^?]K]?l4]4](] R^U^W]@]O]0^7g;_S];bT^@`L]8_7]L]>]E]E^W]V]@pA]S]S]" ++ "=_T_].].].].].].].].]-]F]F]V]O]C]E]F]E]F]E]F]E]F]E]B_P_C]L]V^G]H]C]H]C]H]C]H]@^U^;]N^>" ++ "]T]6]R_;]R_;]R_;]R_;]R_;]R_;]R_X_T^K_R\\:_S^;_S^;_S^;_S^=].].].]*h=bT^;_T_;_T_;_T_;_T_;_T_1" ++ "^9_T`>]L]?]L]?]L]?]L]A]K]>aT_?]K] P]Q]R]E]F]E]V\\Q\\W]E]K\\W]E]F]A]4^L] A^@ZN\\ =i8e@yCk?^R^" ++ "=]L]9b8]O^?] Im B]1]T]5uI]T[6]S^T]<^3]-]*]3^X\\X^,] V^3]J]7](^/]9]T];e7]We/]9]N]?]K" ++ "^?].] Wd Nd._8]O`U\\T\\K]S]<]L^A]-]F^F].]/]-]H]C].].]V_7].]V]Q]V]H]V^P]D]C]G]L]@]C]G]L]?^']6" ++ "]H]C^M^?]Q]U]Q]Ic6^W^._<]/^)]2^G^ !ZM^=`Q^=^NZ;^Q`>^P^=].^Q`?`Q^>].].]R_;].`R^X\\R^M`Q^=^P^" ++ ">`Q^=^Q`?`1]MZ;].]L]A^M^?]Q`Q]G^R^>^M^1^4]4](] D]P^A]R^X]@]P^/]9^Vb=^NZ;`Q^AaN^8_7]L]>]E]" ++ "F^V]U]>]P]>]S]S]>^P^>`T`7]6]J]<]S]5^6]/]C]G]Q]Q]F]H]D]P]P]H]C]C^&]TZ,^7]7^N^6]TZ H]/^U[TZ9" ++ "]2n;]U]8]0d <[U]F[M\\P]2[R[ M[S\\P\\S[ Tb(]9]'\\ @]L]@fU\\ M\\3]P]9[R[1\\M\\<\\M\\7\\R\\8]2]S]8]S]8]" ++ "S]8]S]8]S]7]U]6]R]?]-].].].].].].].]-]F]F]V^P]D]C]H]C]H]C]H]C]H]C]B_R_C]L]T]G]H]C]H]C]H]C]" ++ "H]?^W^:]M]>]U^6ZM^].].].]+i=`Q^=^P^=^P^=^P^" ++ "=^P^=^P^2^:^P^>]L]?]L]?]L]?]L]A^M^>`Q^@^M^ P]Q]Q]F]E]F]W^S^W]F]L^W]F]E]B]3]M^ B^B^O[ =k8" ++ "d?xClA^P^>]L]9]X]8^P]>\\ Hl A] 9uI]T[5]T]T]:^ =]*]5^V\\V^.] V]2]J]7](]/^:]S];h:]Xg0]" ++ "9^P^?]K^?].]!e Je2_7\\PdW\\S\\L]S]<]M^@]-]E]F].]/]-]H]C].].]X_5].]V]Q]V]H]U^Q]D]C]G]L]@]C]G]M" ++ "^?`)]6]H]B]M]>]Q]U]Q]Hb5c-^;].])] B]=_O]=].]O_>]N^>].]O_?_O]>].].]S_:]._P`P]M_O]=]N]>_O]" ++ "=]O_?_1]-].]L]@]M]>]RbR]G^R^=]M]1^3]4](] FaSaD^Qa?]R_.]9]R`>]._O]>^N]8`7]L]>]E]G^U]U^?]P]" ++ ">]S]S]>]N]>^P^7]6]J]<]S]4^7]/]C]G]Q]Q]F]H]D]P]P]H]C]D_&]&_8]8_N_7] B]/]T[3]1l:^W^8]1]W` >\\" ++ "U\\E\\N\\P]3\\S\\ N\\S\\P\\S\\ S_']:]&\\ @]L]@fU\\ M\\2\\P\\8\\S\\2\\N]<\\N]7\\S]8]2]S]8]S]8]S]8]S]8]S]8]S]" ++ "7]R]?]-].].].].].].].]-]E]G]U^Q]D]C]H]C]H]C]H]C]H]C]A_T_B]M]S]G]H]C]H]C]H]C]H]>c9]M^?]U]']" ++ ".].].].].].`O^N].]N^>]N^>]N^>]N^?].].].],_R^>_O]=]N]=]N]=]N]=]N]=]N]2^:]O_?]L]?]L]?]L]?]L]" ++ "@]M]=_O]?]M] O\\P]Q]F\\D]F\\U^U^V]F\\L^V]F\\D]B]3]M] RuJ`O[ >m9c>wCmA]N]>]L]9]X]7]P]?] " ++ "Im A] 2\\R\\A]T[5^V^T\\:` ?](\\6]T\\T]/] V]2]J]7])^1_9]S];i;bS^2^8^S_>]K^?].]$e@u@e6_7]QfX\\S\\" ++ "M^S^=]N^?]-]E]F].]/]-]H]C].].c4].]U]S]U]H]T]Q]D]C]G]M^@]C]G]M]=c-]6]H]B]M]>^R]U]R^G`4c.^:]" ++ ".])] B]=^M]?^/]M^?]L]>]/]M^?^N^?].].]T_9].^O_O^N^N^?]M^?^M]?]M^?^0]-].]L]@]M]>^S]X]S^F^T" ++ "^<^O^2_3]4](] GcUcE]Pa?]Vb-]:]O_?].^N^>]O^8a8]L]?]C]H]T]T]?]P]>]S]S]?]L]@^N^8]6]J]=^S^4^8" ++ "]/]C]H^Q]Q^G]H]D]P]P]H]C]E_%]%_9]9_L_8] B]0^T[3]0_T_>cWc=]1]U_ ?[U\\C[N]R^4]T] N[R\\Q]R[ 'u" ++ "G]&] @]L]?eU\\ M\\2]R]8]T]3\\N\\;\\N\\7]S\\7]3^S^:^S^:^S^:^S^:^S^9]S]8^R]?]-].].].].].].].]-]E]G" ++ "]T]Q]D]C]H]C]H]C]H]C]H]C]@_V_A]N]R]G]H]C]H]C]H]C]H]>c9]L]?]U]'].].].].].]._M]O^/]L]?]L]?]L" ++ "]?]L]?].].].]-^O]>^N^?]M^?]M^?]M^?]M^?]M^ I]O`?]L]?]L]?]L]?]L]@^O^=^M]@^O^ P]P]P\\G]C\\G]T^" ++ "W^T\\G]M^T\\G]C\\B]3^O^ RuJ[X]P[ >o=\\XaX]BwDoC]L\\>]L]:^X^8]P]?] E] 5] 3]S]A^U[4dT];b @" ++ "](]6ZR\\RZ.] V]2]J]7]*^7d8]R];]R_]-]E]Fm>k=]-rC].]" ++ ".b3].]U]S]U]H]T^R]D]C]G]M]?]C]G]N^^M]?].]M^?]L]>]/]M" ++ "^?^M]?].].]U_8].^N^N]N^M]?]L]?^M]?]M^?^0]-].]L]@^O^=]S]X]S]D^V^:]O]2_2]4](] H\\U^W]U\\E]Pa?" ++ "]Vb-];]M^?].^M]>^P]7a8]L]?]C]H]T]T]?]P]>]S]S]?]L]@]L]8]6p=]Q]3^9]/]C]H]P]P]G]H]C]Q]Q]G]ViV" ++ "]F_$]$_:]:_J_9] B]0]S[3]0]P]>o=]2]S_ @[U\\C[M]T_5^U^;u O[R\\R]Q[ 'uH]/ZQ] ?]L]?eU\\ M\\1]T]7^" ++ "U^4\\O]O]I\\O]T`MZQ]S]O]E]3]Q]:]Q]:]Q]:]Q]:]Q]:^S^9]QmO]-m>m>m>m>].].].]1hL]G]T^R]D]C]H]C]H]" ++ "C]H]C]H]C]?_X_@]O]Q]G]H]C]H]C]H]C]H]=a8]L]?]U]&].].].].].].^M]O].]L]?]L]?]L]?]L]?].].].].^" ++ "M]?^M]?]L]?]L]?]L]?]L]?]L] I]Pa?]L]?]L]?]L]?]L]?]O]<^M]?]O] O]P]P\\G]C\\G]ScS\\G]N^S\\G]P]P\\B" ++ "]2]O] QuF]Q[ >oAqDuDqD]L]?]L]:^X^8^R^?\\ D] 5] 3]S]@`X[3bS\\R^G]W^N] P](].\\&] W]1]J" ++ "]7]*^7c8]Q];ZM^=`O^4]4d:]M_?].])d:u:d=_5\\R]O^R\\N]Q]=j<]-]E]Fm>k=]-rC].].a2].]U^U^U]H]S]R]D" ++ "]C]G]N^?]C]G]P_:g3]6]H]A]O]<]S]S]S]E^1_.^8]-]*] A]>^M]?]/^M^?]K]?]0^M^?]L]?].].]V_7].]M]" ++ "M]N]L]@^L]?^M]@^M^?]/]-].]L]?]O]<]S]X]S]C^X^9]O]2^1]4](]0_IZ O[R\\X]S\\G^O_>]Vd9_U];]L]?].]L" ++ "]=]P]8]X^9]L]?]C]I^T]S]@]P]>]S]S]?]L]@]L^9]6p=]Q]3^9]/]C]H]P]P]G]H]C]Q]Q]G]ViV]G_#]#_;];_H" ++ "_:] B]0]S[3]0\\N\\>o=]2]Q^ A[U\\C[LcX\\6]T]9u O[RfP[ 'uIf7e >]L]>dU\\<] :f5d4]T]:fT\\O^NfT\\UdOeR" ++ "\\O^F^3]Q]:]Q]:]Q]:]Q]:]Q]:]Q]:^QmO]-m>m>m>m>].].].]1hL]G]S]R]D]C]H]C]H]C]H]C]H]C]>d?]P^Q]G" ++ "]H]C]H]C]H]C]H]<_7]L]?]U^'].].].].].].^L]P].]K]@]K]@]K]@]K]@].].].].]L]?]L]@^L]@^L]@^L]@^L" ++ "]@^L] I]Q]X^@]L]?]L]?]L]?]L]?]O]<^M]?]O] O\\WmX]H\\WmX]H\\QaR]H\\N^R]H\\O]P]C]2]O] QuF]R\\ ?qC" ++ "sDtDrE]L]?]L]:]V]7]R]>x '] 5] 3\\R\\?e3^R\\SbJ^V^O] P](].\\&] W]1]J]7]+^6e:]Q]-^>_M]5^6" ++ "h<^O` Qe8u8e@^5]R\\M]R\\O^Q^>m?]-]E]Fm>k=]KdFrC].].b3].]T]U]T]H]S^S]D]C]G]P_>]C]Gk6f5]6]H]A" ++ "^Q^<]S]S]S]F_1_/_8]-]*] A]>]K]A].]K]@]J]?]0]K]?]L]?].].]W_6].]M]M]N]L]@]J]@]K]A]K]?]/^.]" ++ ".]L]?]O]<]T^W]T]C^X^9^Q^3^1]3]']3dN\\ P\\R`Q[G]N_>]Q`;bW];\\K^?]/]L]=]Q^8]W]9]L]?]C]I]S]S]@]P" ++ "]>]S]S]@]J]B^L^9]6p>^Q^4^9]/]C]H]P]P]G]H]C]Q]Q]G]ViV]H_\"]\"_<]<_F_;] B]1]R[3]1]N]8a6]2]P^ B" ++ "[U\\C[K`V\\7]T]8u O[RdN[ 'uIf5a <]L]=cU\\<] :f3`1]T];fU\\N^NfU\\T[S]NaQ\\N^G^3^Q^<^Q^<^Q^<^Q^<^Q" ++ "^;]Q]:]PmO]-m>m>m>m>].].].]1hL]G]S^S]D]C]H]C]H]C]H]C]H]C]=b>]P]P]G]H]C]H]C]H]C]H]<_7]L]?]U" ++ "_(].].].].].].]K]Q].]J]A]J]A]J]A]J]@].].].].]L]?]L]@]J]A]J]A]J]A]J]A]J] K]P\\V]@]L]?]L]?]L]" ++ "?]L]?^Q^<]K]@^Q^ O\\WmX]H\\WmX]H\\P_Q]H\\O^Q]H\\O]P]C]2^Q^ D^<]R[ >qDuEsCqD]L]?]L]:]V]7]R]>x " ++ " '] 5] 3\\R\\=f+]TdL^T^P] P](].\\2u *]1]J]7],^-_=]P],]>_M]5]7_R^<^Qa Sd .dC^4\\R]M]R\\O]O" ++ "]>]N_@]-]E]F].]/]KdF]H]C].].]X^4].]T]U]T]H]R]S]D]C]Gk=]C]Gj1c6]6]H]@]Q];^T]S]T^Ga1].^7]-]*" ++ "] Lh>]K]A].]K]@]J]?]0]K]?]L]?].].]X_5].]M]M]N]L]@]J]@]K]A]K]?]._0].]L]>]Q];^U]V]U^Bb7]Q]" ++ "3^1^3]'^6iS^ P[P^P[G]N_>]N^=dX]<]J]>^1]L]=^R]8^W]9]L]@]A]J]S]S]@]P]>]S]S]@]J]B]J]9]6]J]>]O" ++ "]5^8]/]C]H]P]P]G]H]B]R]R]F]C]Iz<]]K]@]O[X\\I`3]O]<]O]<]O]<]O]<]O]<]O];]P]?]-].].].].].].].]-]E]G]R]" ++ "S]D]C]H]C]H]C]H]C]H]C]<`=]Q]O]G]H]C]H]C]H]C]H];]6]L]?]T_4h9h9h9h9h9h9hK]Q].]J]A]J]A]J]A]J]" ++ "@].].].]/]J]@]L]@]J]A]J]A]J]A]J]A]J]?tG]Q\\U]@]L]?]L]?]L]?]L]>]Q];]K]?]Q] N\\WmX]H\\WmX]H\\P_" ++ "Q]H\\P^P]H\\O]P]C]1]Q] C]:]S[ ?sEvEqAoC]L]?]L];^V^8^T^>x '] 5] 4]S]]K]A].]K]@p?]0]K]?]L]?]" ++ ".].b3].]M]M]N]L]@]J]@]K]A]K]?].c4].]L]>]Q]:]U]V]U]@`6^S^4^5b2]&b^Ua<]J]=" ++ "c7]L]<]S^8]V^:]L]@]A]J]S]S]@]P]>]S]S]@]J]B]J]9]6]J]?^O^7^7]/]C]H]P]P]G]H]B]R]R]F]C]Iz<]\\I\\@\\O\\X\\J`" ++ "3^O^>^O^>^O^>^O^>^O^=]O]<^P]?]-].].].].].].].]-]E]G]R^T]D]C]H]C]H]C]H]C]H]C];^<]R]N]G]H]C]" ++ "H]C]H]C]H];]6]L]?]S`8j;j;j;j;j;j;|Q].pApApAp@].].].]/]J]@]L]@]J]A]J]A]J]A]J]A]J]?tG]R]U]@]" ++ "L]?]L]?]L]?]L]>^S^;]K]?^S^ N\\WmX]H\\WmX]H\\QaR]H\\Q^O]H\\O]P]C]1^S^ D]9]T\\ ?sFwDo?nC]L]?]L];" ++ "]T]7]T]=] Hj ?] 4]S]8d/]T]T]N^R_R\\ O](] =u Se =]0]J]7].^(]?]O]+]?^K]7]7]L]]K]A].]K]@p?]0]K]?]L]?].].a2].]M]M]N]L]@]J]@]K]A]K]?]-f8].]L]>^S^" ++ ":]U]V]U]?^4]S]4^4`0]$`<^Si O[O\\O\\H]N^=]M^@^S`<]J]=c7]L]<]S]8^U]:]L]@]O]O]J]S]S]@]P]>]S]S]@" ++ "]J]B]J]9]6]J]?]M]7]6]/^E^H]P]P]G]H]A]S]S]E]C]Iz<]]M]>]M]>]M]>]M]>^O^=]O]?]-].].]" ++ ".].].].].]-]E]G]Q]T]D]C]H]C]H]C]H]C]H]C]<`=]S]M]G]H]C]H]C]H]C]H];]6]M^?]R`;l=l=l=l=l=l=~Q]" ++ ".pApApAp@].].].]/]J]@]L]@]J]A]J]A]J]A]J]A]J]?tG]S]T]@]L]?]L]?]L]?]L]=]S]:]K]>]S] M]P]P\\G]" ++ "C\\G]ScS\\G]S^N\\G]P]P\\B]0]S] D]7\\T[ >sFwCn?mB]L]?]L];]T]7]T]=] Hi >] 4]S]7[Xa1]T^T^O]" ++ "P_T] O](] =u Se =]0]J]7]/^'^A]N]+]?^K]7]8^L^]K]A].]K]@p" ++ "?]0]K]?]L]?].].b3].]M]M]N]L]@]J]@]K]A]K]?]+e9].]L]=]S]9]V]T]V]@_4]S]5_4b2]&b<\\Nd M[O]P\\H]N" ++ "^=]L]@]Q_<]J]?e7]L];]T]8]T]:]L]@]O]O]J]S]S]@]P]>]S]S]@]J]B]J]9]6]J]?]M]8^6].]E]G]P]Q^G]H]A" ++ "^T]T^E]C]Iz<]]M]>]M]>]M]>]M]>]M]>^O]?]-].].].].].].].]-]E]G]Q^U]D]C]H]C]H]C]H]C]" ++ "H]C]=b>]T]L]G]H]C]H]C]H]C]H];]6]M]>]Qa>`P]>`P]>`P]>`P]>`P]>`P]>`PoQ].pApApAp@].].].]/]J]@]" ++ "L]@]J]A]J]A]J]A]J]A]J]?tG]T]S]@]L]?]L]?]L]?]L]=]S]:]K]>]S] L\\P]P\\F\\C\\F\\T^W^T\\F\\T^M\\F\\C\\B]" ++ "0]S] E^7]U[ >sFwBl=kA]L]?]L]<^T^8^V^=] Ij >] ]K]A].]K]@],]0]K]?]L]?].].c4].]M]M]N]" ++ "L]@]J]@]K]A]K]?](d;].]L]=]S]9^W]T]W^@`5^U^5^/_3]'_8ZJ` K[O]P\\H]N^=]L]@]P];]J]@_0]L];]U^9^T" ++ "^;]L]@]O]O]J]S]S]@]P]>]S]S]@]J]B]J]9]6]J]@^M^:^5].]E]F]Q]Q]F]H]@^U]U^C]E]G_\"]\"_BZT]TZB_F_;" ++ "] B]1]R[3]1\\L\\?o I_S] A[U]F[ V]T] W] N[S\\R]R[ S] ]L]6\\U\\ ']T]/\\O\\V\\@\\H\\A\\O\\V\\M_0o@o@o" ++ "@o@o?m>l>].].].].].].].].]-]F^G]P]U]C]E]F]E]F]E]F]E]F]E]=d?^V]L]F]H]C]H]C]H]C]H];]6]N^>]O`" ++ "?]M]>]M]>]M]>]M]>]M]>]M]>]M]?].].].].]-].].].]/]J]@]L]@]J]A]J]A]J]A]J]A]J] K]U]R]@]L]?]L]?" ++ "]L]?]L]=^U^:]K]>^U^ L\\P]Q]F\\D]F\\U^U^V]F\\U^M]F\\D]B\\/^U^ OuD]V[ =sFwBk;i@]L]?]L]<]R]7]V];]" ++ " F^ Nu=[T^3]S]R]O]N_V\\ N](] 1] ].]L]6]1_%]Aq0]>]K]8]7]J]/] Md:u:d>]3\\R\\K\\S\\Po@]" ++ "J]A].]F]E].].]E]F]H]C].].]S^9].]RaR]H]P^V]C]E]F].]E]F]M],]8]6]H]>]U^8]W^Q^W]H^U^4]2^3]+],]" ++ " R^M]>]K]A].]K]@],]0]K]?]L]?].].]X_5].]M]M]N]L]@]J]@]K]A]K]?]$`;].]L]=^U^8]W]T]W]@b5]U]5" ++ "^,]3]'] J\\Q_Q[G]N^=]L]A]O];]J]@].]L];]U]8]R];]L]@]O]O]J]S]S]@]P]>]S]S]@]J]B]J]9]5]L]?]K];" ++ "^4].^G^F]Q]Q]F]H]?_W]W_B]E]F_#]#_B\\U]U\\B_H_A\\U]U[ H]1]R[3]1]N]?o H`V] @[T]G[ U]T] X] N[S\\Q" ++ "]S[ S] ]L]6\\U\\ (]T]/]P\\U\\A]I]B]P\\U\\M^/o@o@o@o@o@o@m>].].].].].].].].]-]F]F]P^V]C]E]F]" ++ "E]F]E]F]E]F]E]>_X_?]W^L]F]H]C]H]C]H]C]H];]6]P_=]M^@^M]?^M]?^M]?^M]?^M]?^M]?^M]?].].].].]-]" ++ ".].].]/]J]@]L]@]J]A]J]A]J]A]J]A]J] K]U\\Q]@]L]?]L]?]L]?]L]<]U]9]K]=]U] K]Q]Q]F]E]F]W^S^W]F" ++ "]W^L]F]E]B\\.]U] NuC\\V[ =eXZXdFgXhAi9h@]L]?]L]<]R]7]V];] E] Nu=[S]3\\R]R]O]M_X\\ M](" ++ "] 1] ].]L]6]2_$]Aq0]>]K]8]7]J]/] Ke=u=e<]3\\R\\K\\S\\Po@]J]A].]F]E].].]E]F]H]C].].]R^:].]Ra" ++ "R]H]O^W]C]E]F].]E]F]M^-]8]6]H]>]U]7]W]O]W]I^S^5]3^2]+],] R]L]>]K]A].]K]@],]0]K]?]L]?].]." ++ "]W_6].]M]M]N]L]@]J]@]K]A]K]?]\"_<].]L]<]U]7]W]T]W]Ac5^W^6^+^4](] H[R\\X]S\\G]N^=]L]A]O];]J]A" ++ "^.]L]:]W^9^R];]L]@]O]O]J]S]S]@]P]>]S]S]@]J]B]J]9]5]L]?]K];^4]-]G]D]R]R]E]H]>kA]E]E_$]$_B^V" ++ "]V^B_J_A^V]V] I]1]R[3]0\\N\\>o G`X] ?\\U_Q[T\\ T]T] ] N\\T\\Q]T\\ S] ]L]6\\U\\ )]T].\\P\\T\\A\\I]A" ++ "\\P\\T\\N^.o@o@o@o@o@o@m>].].].].].].].].]-]F]F]O^W]C]E]F]E]F]E]F]E]F]E]?_V_@]W]K]F]H]C]H]C]H" ++ "]C]H];]6k<]L^A]L]?]L]?]L]?]L]?]L]?]L]?]L]?].].].].]-].].].]/]J]@]L]@]J]A]J]A]J]A]J]A]J] K]" ++ "V\\P]@]L]?]L]?]L]?]L]<^W^9]K]=^W^ J]R]R]D]G]D]W\\Q\\W]D]W\\L]D]G]A\\.^V] NuC]W[ ]K]9]6]J]/] H" ++ "e@u@e H\\R]M]T]Q^J]A]J]@]/]G^E].]-]F]F]H]C].].]Q^;].]Q_Q]H]N]W]B]G]E]-]G^F]L]-]8]6]I^>^W^7]" ++ "W]O]W]I^R^6]4^1]+],] R]M^>^M^@]/^M^?]-]0^M^?]L]?].].]V_7].]M]M]N]L]@^L]?^M^A^M^?] ]<].]L" ++ "]<]U]7]X]R]X]B^W^5]W]6^)]4](] H\\T]W]U\\F]O_=]L]A]P^;^L^A]-]L]:]W]8]P]<]L]@]O]O]J^T]T]?]P]>" ++ "]S]S]@^L]A^L]8]5]L]@^J]=^3]-^I^D^S]S^E]H]]G]C_%]%_A_W]W_A_L_@_W]W_ J]0]S[3]0]P]5]4],b =" ++ "[ThT[ R]T]!] M[T\\P]U[ R] ]L]6\\U\\ *]T].]P[S\\B]J]A]P[S\\N].^J]B^J]B^J]B^J]B^J]B^K^A]M]=]" ++ "/].].].].].].].]-]G^F]N]W]B]G]D]G]D]G]D]G]D]G]?_T_AbK]E]I^C]I^C]I^C]I^;]6j;]K]A]M^?]M^?]M^" ++ "?]M^?]M^?]M^?]M_?].].].].].].].].]/]J]@]L]@^L]@^L]@^L]@^L]@^L] J^X]Q]?]L]?]L]?]L]?]L];]W]8" ++ "^M^<]W] I]R]S]C]H]C]VZOZW]C]VZL]C]H]@\\-]W] MuC]X[ ;cWZWbDeWZXe>e6e>]L]?]L]=]P]8^X^:] " ++ " F^ H\\R\\5[S]5]Q]R]O^L` K]*] 0] !^.]L]6]4_\"]2],^>^M]8]6]J]0] DeCuCe E]R\\M]T\\P]I]A]J]@" ++ "]/]G]D].]-]F]F]H]C].].]P^<].]Q_Q]H]N^X]B]G]E]-]G]E]L^.]8]5]J]<]W]6^X]O]X^J^Q^6]5^0]+^-] " ++ "R]M^>^M]?].]M^?]-]/]M^?]L]?].].]U_8].]M]M]N]L]?]L]?^M]?]M^?] ]<].]M^<^W^6aRbB^V^6]W]7^(]4]" ++ "(] GcUcE]P_=]L]A]P]9]L]@]-]L]:^X]9^P]<]M^@]P^O]I]T]T]?]P]>]S]S]@^L]@]L]8]5]M]?]I]>^2],]I]" ++ "B_U]U_D]H]:c<]G]B_&]&_?_X]X_?_N_>_X]X_ I]0]S[3]0_T_5]4]+` ;[SfU[ P^U^#] L[U\\P]V[ Q] ]M^" ++ "6\\U\\ ,^U^-\\P\\S\\B\\J]@\\P\\S\\N].]I]B]I]B]I]B]I]B]I]B]I]B^M]=]/].].].].].].].]-]G]E]N^X]B]G]D" ++ "]G]D]G]D]G]D]G]@_R_A`J]D]J]A]J]A]J]A]J]:]6g8]K]A]M^?]M^?]M^?]M^?]M^?]M^?]M_?].].].].].].]." ++ "].].]L]?]L]?]L]?]L]?]L]?]L]?]L]3^;aP]?]M^?]M^?]M^?]M^;]W]8^M];]W] H]S]T^B]J^B]J^B]J^B]J^@" ++ "\\-]W] G^1_ :aW[V`BcW[Wc]N]<]P]7]X]8] F]KZ X]S]5[S]5\\P]R]N]K_ K]*] 0] !]" ++ ",]N]5]5_\"]1],]<]M]9^6^L^0] Ad Nd A\\R]O^U\\P^I^B]K^?]H[C]H^D].],]G]F]H]C].].]O^=].]P^Q]H]M]" ++ "X]A]I]D],]I^E]K]AZH^8]5]J]<]W]5bObJ^O^7]6_0]*]-] R]M^>^M]?^/]M^?^.]/]M^?]L]?].].]T_9].]M" ++ "]M]N]L]?]L]?^M]?]M^?] ]<].]M^;]W]5aRaB^U^6c8_(]4](] FaSaD]P_=]M]@]P]9]L]@]-]L]9b9]O^=^N^?" ++ "\\P_Q]H]T]T]?]P]=]T]T]?^L]@]L]8]4]N]@^I^?]1],^K^A`W]W`C]H]7]8]I]@^&]&^=i=^N^^P^=^P]7]X]8_ H^M[ F] 6" ++ "]S]>ZQ[T^6]P]S^N^K^ K]*] 0]:] 8]0],]O^5]6_2ZI]1]-^<^O^9]4]L]0]<].] Uc Pc1]2\\Q^S`W^P]G]B]K]" ++ ">^J\\C]I^C].],^H]F]H]C].].]N^>].]C]H]MbA^K^D],^K^D]K^B[I]7]5^L^" ++ "_O]=].]O_>].].]O_?]L]?].].]S_:].]M]M]N]L]>]N]>_O]=]O_?] ]<]-]O_;]X^5aRaC^S^6a8_']4](] D]P" ++ "^B^Ra>^N]@]Q]7]N]?^.]L]9a8]N]=^N^?]Q_Q]G]U]U]>]P]=]T]T]?_N]>]N]7]4^P^@]G]@^1]+^M^?mB]H]7]8" ++ "^K^?\\%]%\\;g;\\L\\:g G]/]T[3]2n7]4]'^ <\\F\\ M\\S\\ J\\F\\ L^N^6\\U\\ ,\\S\\-]OhG]K]@]OhQ]LZ=]G]" ++ "D]G]D]G]D]G]D]G]D]G]D^L]<^J\\C].].].].].].].]-]J_D]MbA^K^B^K^B^K^B^K^B^K^A_N_B^K]B^L^A^L^A^" ++ "L^A^L^:]6].]K]A^O_?^O_?^O_?^O_?^O_?^O_?^Oa?].].].].]/].].].]-]N]>]L]>]N]=]N]=]N]=]N]=]N]2^" ++ ";_O]=]O_>]O_>]O_>]O_:a7_O]9a E^P_>^P_>^P_>^P_>^P_>\\,a H^.] /[5]T[S\\8a1`<]L]=^R^<]O^8b7_ " ++ " H^O\\ F] 6\\R\\=[R[U^5\\N]T]L^M` L]*] 0]:] 8]1^+]P]4]7_1[L_1]ZM];].] R` P" ++ "`.]2]QfXaN]G]B]L^=^L]C]K_B].]+_J]F]H]C].].]M^?].]C]H]La@^M^C]+^M^C]J]B]L^7]4^N^:a4aMaK^M^8" ++ "]7^.]*^.] Q]P`>`Q^=^NZ;^Q`>_LZ>].^Q`?]L]?].].]Q^;].]M]M]N]L]>^P^>`Q^=^Q`?]/ZL];]-^Q`:a4`" ++ "P`D^Q^7a8^&]4](] S]Sb>_P^@]R^7^P^>^MZ<]L]9a9]M]=_P`XZB]Q_Q]G^V]V^>]P]=^U]U^?`P^>^P^6]4]Q" ++ "^?]G]A^0]*^O^]P`>]P`>]P`>]P`>]P`>]P]X^LZN^NZ;_LZ>_LZ>_" ++ "LZ>_LZ?].].].]-^P^>]L]>^P^=^P^=^P^=^P^=^P^2^:^P^=^Q`>^Q`>^Q`>^Q`:a7`Q^9a Dk],a " ++ "H]-] /[,[._0_;]L]=j<]N]7`5a J_S^ F] 6\\R\\=^U[W_5]N^V^K_Rd L],] /]:] 8]1])^T^3]8_0^Q`0" ++ "]<]Q_8^S^8^3_R_=]R^:].] O] P]+]1\\PdW`N^G^C]N_;`R`C]NaA].]*`O`F]H]C].].]L^@].]C]H]La?`S`B]*" ++ "`S`B]J]B`Q_6]3_R_9a4aMaL^K^9]8^-])].] Q_Tb>aS^;_R\\:^Sa=`Q]>]-^Sa?]L]?].].]P^<].]M]M]N]L]" ++ "=_T_=aS^;^Sa?]/^R_:]-^Sa:a3_P_C^P^7_8^%]4](] S_V^X^?aS^>]T^5_T_=`R]<]L]8_8]M^>`SdA]SaS]E" ++ "^W]W^=]P^=_W]W_>]X]T_<_T_5^4^T^?^G^C^/])^Q^8c=]H]7]6`S` ?] ;c >c E]._W[V\\9]4^J^9]4]%] ;]L]" ++ " IZQZ H]L] !u ,`Sd9\\U\\ ,ZQZ,]E\\E]L]?]E\\M_S^>^G^F^G^F^G^F^G^F^G^F^G^F^K]:`R`C].].].].]." ++ "].].]-]ObB]La?`S`>`S`>`S`>`S`>`S`?]J]CcS`?_R_=_R_=_R_=_R_8]6].]V[R^?_Tb>_Tb>_Tb>_Tb>_Tb>_T" ++ "b>_T^V_Q]M_R\\:`Q]=`Q]=`Q]=`Q]?].].].],_T_=]L]=_T_;_T_;_T_;_T_;_T_1^:`T_;^Sa=^Sa=^Sa=^Sa9_6" ++ "aS^7_ Bi:i:i:i:i=]+` I],] /[,[-].]:]L]]C]H]K`>kA])kA]J^Cm5" ++ "]2j7_2`M`K^J]9]8tC])].] PgX]>]Xf9h9fX]],fX]?]L]?].].]O^=].]M]M]N]L]qA^U]W]U^Di<]O`?k=]Xg:h3a7f>uCn?]/eSe;]:" ++ "]H]7]5k >] :a n?\\H\\8]4]%] 9^R^ *^R^ Xu ,q9\\U\\ /]D\\F]LfH]D\\Li>]E]F]E]F]E]F]E" ++ "]F]E]F]E]F]JnIkBn?n?n?n?].].].]-n@]K`>ki-]]C]H]K`]Wd" ++ "6f8dW]:i>]+dW]?]L]?].].]N^>].]M]M]N]L];f;]Wd7dW]?]/i7c3dV]9_2_P_E^M^8_8m4]4](] QdV`B]Xe;" ++ "d1f8h<]L]8_9]K]>]XdW_@eWeBg;]O`=g;]Vd8f1`6d=uCn?]/eSe;]:]H]7]3g <] 9_ :_ C]+f>n>ZFZ7]4]%] " ++ "7f &f Vu ,]XdW_9\\U\\ /\\C\\F\\KfH\\C\\Kg=]E]F]E]F]E]F]E]F]E]F]E]F]JnHh@n?n?n?n?].].].]-l>" ++ "]K`]C]H]J_9a<]$d?]I^?c0].b3_2_K_M^G^;]8tC](]/] M`T]>]U`2b4`U]7c;])`U]?]L]?].].]M^?].]M]" ++ "M]N]L]8`8]U`3`U]?],c2a0_T]9_2^N^F^K^8]7m4]4](] O`R^B]Va8b-`3d:]L]7]9^J]?]V`T]>cUc?c9]N_:" ++ "a8]T`3`-_4`X IX *W FW " ++ " " ++ " " ++ " " ++ " " ++ " HX W 4Z 3VCT X W 4Z " ++ " HX W 4Z 'VCT ;X W 3Y 2UCT KX W 3Y 0W " ++ " " ++ " " ++ " " ++ " " ++ " @W !W 4\\ 5YET ?XHX 8] >W !W 4\\ 7XGX KW !W 4\\ 7XHX " ++ " +YET :W !W 3[ 5ZFT ?XGX EW !W 3[ 7XGX 5W " ++ " " ++ " " ++ " " ++ " " ++ " >W \"V 3\\ 7]HU ?XHX 9` ?W \"V 3\\ 7XGX JW \"V 3\\ 7XHX -]HU" ++ " 9W \"V 3] 7]HT ?XGX DW \"V 3] 8XGX 5V " ++ " " ++ " " ++ " " ++ " " ++ " W $V 3VNV 8XGX IW $V 3VNV 8XHX -_KV" ++ " 8W $V 2] 7_KU ?XGX CW $V 2] 8XGX 6V " ++ " " ++ " " ++ " " ++ " " ++ " :W &W 4VLV :j >XHX :VJV >W &W 4VLV 9XGX HW &W 4VLV 9XHX .j 6" ++ "W &W 3VMV 9i >XGX BW &W 3VMV 9XGX 7W MW " ++ " " ++ " " ++ " " ++ " " ++ " CV 'W 4VJV ;j >XHX ;UGV >V 'W 4VJV :XGX GV 'W 4VJV :XHX .j" ++ " 5V 'W 3VKV :i >XGX AV 'W 3VKV :XGX 8W N[ " ++ " " ++ " " ++ " " ++ " " ++ " DV )W 4VHU TEY ;XHX V ,V 2UEU TCU :XGX =U -V 2UCU =XGX ;V NV" ++ "IV \"W " ++ " " ++ " " ++ " " ++ " JU /V 3VBV ETBT :U /" ++ "V 3VBV FU /V 3VBV (U /V 2UAU DU /V 2UAU @V NVGV " ++ " $X " ++ " " ++ " *X " ++ " " ++ " JX GTBT " ++ " MX GX 7V :UEU DX GX 7V JX GX 7W 4X GX 6V " ++ " GX GX 5V (X &X " ++ " " ++ " )X 8V " ++ " " ++ " ;X FTBT " ++ " LX IX 7X W E\\ AW ,W ,W ,W ,W HY GV +Y " ++ " 4Z NX @X " ++ " %W DUDU " ++ " =Y 7W KW 6Z 4XDT BTAT BW KW 6Z IW KW 6[ ,Y )XDT AW KW 5Z 4XDT " ++ " KW KW 4Z ,W BW 8V (S " ++ " W H_ AW ,W ,W ,W ,W " ++ " L] GV +] ;a #[ F^ " ++ " 8XGX +W BTEU " ++ " *R 9a :W MW 6\\ 6ZET ?XHX W Ja AW ,W ,W ,W ,W N_ GV +_ " ++ "?e 8] J] Jb 8[ <[ $Y FY 7XGX " ++ "=Z Di 5W 8Z .Y !W FW *Y 4W)V*W)V-Y(V W $a MY EW 5W >W Kb AW ,W ,W" ++ " ,W ,W !a GV +a Ch =f ^ Mf 2Z @" ++ "x Mx a" ++ " 5a &W 0g #\\ -_ <\\*V.\\*V0a-V\"X )Z /Z /Z /Z /Z 4WJV 1~U+d Kx Mx Mx Mx MX -X -X -X ,j" ++ " @[3X Dc 8c 8c 8c 8c W \"W 4VNV 8]HU ?XHX BW \"W 3VNV 8XHX 2W ?W &XHX " ++ " ^ K~\\ >S 3Q +[ @[;[ ;Q ;e HX 2VFV #VBV FS 6`" ++ "1V#g GV !V 3V !T 7W 0d :` ;j ?k -[ Dq :g Ky Df ;d $f 1Z @o 5j Np Ex Mt " ++ ":m\"X/X'X -X -X3Z%X -]0]0\\4X Gi Lm 4i Ln ;m#~W$X/X-X(X-X4Y4XCY1Y-Y.Y&~S%a >W $a N[ EV " ++ "5W >W Lc AW ,W ,W ,W ,W \"b GV +a Dk Aj \"_" ++ " h 3Z @x Mx ?i 6X C~Q)X?X?X Ni 6V /V /V DX &f #W0W e >XGX %c#" ++ "e +b\"i 9_ Be 9d 'V 3k %^ /c @^*V0^*V2d.V\"X )Z /Z /Z /Z /Z 3b 1~U.j Nx Mx Mx" ++ " Mx MX -X -X -X ,p F\\4X Gi >i >i >i >i BiEV.X/X'X/X'X/X'X/X.Y.Y#X 'j ;V \"V 5VLV :_IT >XH" ++ "X V \"V 5VLV 9XGX IV \"V 4VMV 9XGX ,ZHY A_IT XHX AV \"V 3VLV 9" ++ "XHX 2V >W &XHX !_ K~[ >T 4R -_ D_?_ >S =t Fh " ++ " IX 2VFV #VBV FS 7c4V#i HV \"W 3V !T 7V 0f @e >o Co 0\\ Dq W Md AW ,W ,W ,W ,W HW 1b GV +b " ++ " Fm Dm #` \"j 4Z @x Mx Am 8X C~Q)X?X?X!m 9X 0V 0X EX 'h" ++ " $W0W \"h ?XGX 'g%g 0h%i :a Cf :f *V 4m %^ 0e A^+V/^+V1f1V!X )Z /Z /Z /Z /" ++ "Z 2` 1~V0o\"x Mx Mx Mx MX -X -X -X ,t J\\4X Im Bm Bm Bm Bm FmHV-X/X'X/X'X/X'X/X-X.X\"X (l ;" ++ "V $V 4UJU :ULXLU >XHX XHX @V $V 2UJU 9XHX 3V =W &XHX !` K~Z >T 4S /a FaAa @T " ++ " @w Hl KX 2VFV $WCV ES 8e5V$j HV \"V 1V \"T 7V 2j Eh ?q Dp 1\\ Dq >" ++ "l Ly Hn Bj +l %e E\\ At >s$v Kx Mt >u&X/X'X -X -X5Z#X -^2^0]5X Jo q ;o r Br%~W$X/X" ++ "-X(X,X6[6XAY3Y+Y0Y%~S%W 3V IW !_ FW 7W >W Md AW ,W ,W ,W ,W HW " ++ " 2[ ?V #[ Hn En #` #l 6\\ Ax Mx Cp 9X C~Q)X?X?X\"o " ++ " ;Z 1V 1Z FX KS 0i #W2W LV ,i ?XGX *l'h 3l'i ;c Dg ;g ,W 6o %^ 1g B" ++ "^,V.^,V0g3V X *\\ 1\\ 1\\ 1\\ 1\\ 2^ 0~V2s$x Mx Mx Mx MX -X -X -X ,v L]5X Jo Do Do Do Do HpKW" ++ "-X/X'X/X'X/X'X/X-Y0Y\"X )n XHX ;UEU XHX @W &W 3VJV :XHX 4W =W &XHX 1\\ 1\\ 1\\ 1\\ 1\\ =XMV K~Y " ++ " =S 4U 1c IdCc AU Dz In LX 2VFV $VBV ES 9g7V$k HV #W 1W #T " ++ " 8W 3l Fh ?r Eq 3] Dq ?m Ly Ip Em -n )k H\\ Au Av%x Mx Mt ?x(X/X'X -X -X6Z\"X -" ++ "^2^0]5X Ls\"s ?s\"s Et%~W$X/X,X*X+X6[6X@Y5Y)Y2Y$~S%W 3W JW \"a FW 8W >W NZ 6W ,W " ++ ",W ,W ,W HW 2X \\ 2V 2\\ GX KS 1j #W2W LV -j ?XGX +ZEZ)VGY " ++ "5ZDZ)i T 5V 2e KfEe CW G| " ++ " Jp MX 2VFV $VBV ES 9XIX8V$l HV #V /V #T 8V 3n Gh ?s Fr 5^ Dq @n Lx Ir" ++ " Go .o -q L^ Bv Cx&z x Mt A{)X/X'X -X -X7Z!X -^2^0^6X Mu#t Au#t Gu%~W$X/X,X*X+X6[" ++ "6X?X5X'X2X#~S%W 2V JW #c FW 9W >W NX 4W ,W ,W ,W ,W HW " ++ " 2W ;V NW IZCY Hp JY &ZDZ 9^ Bx Mx Eu W *W 2UFU ;XHX 6W ;W &XHX 7h =h" ++ " =h =h =h DWJV K~X >T 5W 4g MgFg EY J~ K]FZ MX 2VFV $VBV " ++ "ES :XGX9V%\\GX HV $W /W 3PATAP GV 3[H[ Gh ?]F] GZE^ 6^ Dq A]FX Lx I\\F\\ G\\G[ " ++ " /[H] 0u N^ Bw E_D^&{!x Mt B`C_)X/X'X -X -X8Z X -_4_0_7X N^E^$u C^E^$u H^E\\%~W$X/X,Y,Y*W7" ++ "]8X>Y7Y'Y4Y#~S%W 2V JW $e FV 9W >W NW 3W ,W ,W ,W ,W HW " ++ " 2W ;V NW IY@X >X 4[AV IX &X@X 9^ Bx Mx F^E^ =X C~Q)X?X?X" ++ "&^E^ B` 4V 4` IX KS 3\\GW \"W4W KV .YBT ?XGX .V7V,P=W :W8W /VEV 3V +V /V " ++ " 7eGU KU 3WCW ;U-V$U-V LV5V NX +^ 3^ 3^ 3^ 3^ 3^ 1~W6_D^&x Mx Mx Mx MX -X -X -X ,{\"" ++ "_7X N^E^ L^E^ L^E^ L^E^ L^E^ !^Ed*X/X'X/X'X/X'X/X+Y4Y X +Y?X ;V *V 4UDU >TEZ TEZ T 5Y 5g MhHi G[ M~Q L\\AW M" ++ "X 2VFV $VCV DS :WEW:V%ZAU HV $V -V 3RCTCR HW 4ZDZ H\\LX ?Y?[ HV>\\ 8_ DX )[?T -Y J[B" ++ "[ I[CZ 0WAZ 2x ^ BX>^ G]=Z&X=b#X -X '];[)X/X'X -X -X:[ NX -_4_0_7X \\?\\%X@^ E\\?\\%X" ++ "?] J[=X =X W X 3W 4W ,W " ++ " HW 3X ;V NX KY?X Ca 9Y:R HX (X>X :VNV " ++ "BZ /X '\\?\\ A^ FX0X)X?X?X'\\?\\ Db 5V 5b JX KS 3ZBT !W6W JV .X?R 4V4U HV ;V" ++ "4V 1VCV 4V *U 0V 7fGU KU 4WAW TDX ;a 6V ,V 4UBU GV ,V 3UCU 0` 6TDX 4V ,V 2UDU >TDX >V ,V 1UDU " ++ ":V 9W (o Do Do Do Do GWIU J~V >T 6Z 6i jIj I\\ N~R M[=" ++ "U MX 2VFV %VBV H] AWCW;V%Y=R HV %W -V 4UETEU IV 4ZBZ IWGX ?V;[ IS9Z 9VNX DX *Z;R" ++ " -X JZ>Y JZ?Y 1U>Z 5`C_#` CX;[ H[7W&X9_$X -X (\\6X)X/X'X -X -X;[ MX -_4_0`8X![;[&X" ++ "=[ F[;[&X<[ LZ8U =X W W 2W 4" ++ "W ,W HW 3W :V MW KX=W Cc ;X7P HX (" ++ "WR !X8X JV /X

W W " ++ " 2W 4W ,W HW 3W :V MW KWU.U 4VAV &V 5U *U 2V 6gGU KU 5W?W =U/V\"U/V IU7V LX ,WNW 5WNW 5WNW 5" ++ "WNW 5WNW 5WNW 4XHX H[4U&X -X -X -X -X -X -X -X ,X6]&`8X\"Z7Z#Z7Z#Z7Z#Z7Z#Z7Z 'Z8['X/X'X/X'" ++ "X/X'X/X)Y8Y MX ,W:W 9V 0V 3U@U ?[ 1V 0V 3U@V GV 0V 3U?U 8h 1V 0V 2U@U " ++ " CV 0V 1U@U >V 7W *`L` I`L` I`L` I`L` I`L` JV =X,X >T 6] 9k\"lKl K_ " ++ " #\\ 'Y8S MX 2VFV %VBV Nk IVAV=V$X 1V %V +V 6YHTHY -V EW 5Y>Y :X ?R5" ++ "Z .Y ;VMX DX +Y DX IYW W 2W 4W ,W HW 3W :V MW KW;W De =W " ++ " -X *W:W V$X 1V &W +W 5XITIX +V EV 4X[ JX -XNW8WNX0a9X#Y" ++ "3Y(X9Y JY3Y(X9Y NX LX W W 2W " ++ " 4W ,W HW 3W :V MW LX;W Df >W ,W " ++ " +W8W >WLW @Y 2X +Z3Z!t\"X0X)X?X?X*Y3Y Kj 9V 9j AS 5X 8W:W HV /W #T)T KV " ++ " @T(T 6U?U &V 5T +V AhGU KU 5V=V =U0V!U0V JV7V WLW 7WLW 7WLW 7WLW 7WLW 7XNX " ++ "6XGX IY.R&X -X -X -X -X -X -X -X ,X2Z'a9X#Y3Y%Y3Y%Y3Y%Y3Y%Y3Y )Y3Z)X/X'X/X'X/X'X/X'X:X Ki" ++ " >W8V *XHZ FW ,ZW W 2W 4W ,W HW" ++ " 3W :V MW LW:W Dg ?W ,X ,W8W >WLW ?Y 3X +Y1Y\"v#X" ++ "0X)X?X?X+Y1Y MYNVNY :V :YNVNY BS 5X 8XU1V U1V KW7V NWLW 7WLW 7WLW 7WLW 7WLW 7WLW 6XGX JY,Q&X -X " ++ "-X -X -X -X -X -X ,X1Z(XNX:X$Y1Y'Y1Y'Y1Y'Y1Y'Y1Y P)P$Y3[)X/X'X/X'X/X'X/X'YVKX DX -X BX IX8X NX7W KP 1P =X Y *Z W 0W MW +ZAZ 0W >W W 2W 4W ,W HW " ++ " 3W :V MW LW:W DSF[ @X -X -X8W ?WJW ?Y 4X ,Y/Y%z%X0X)" ++ "X?X?X,Y/Y YMVMY ;V ;YMVMY CS 5X 5P*Q JWU2V NU2V$_7V NXLX 9XLX 9XLX 9XLX 9XLX 8WLW 6XGX KY*P&X -X" ++ " -X -X -X -X -X -X ,X0Z)XNX:X%Y/Y)Y/Y)Y/Y)Y/Y)Y/Y\"R+R&Y3]*X/X'X/X'X/X'X/X&Y>Y Jp EW:Y " ++ " +R@Y 7Q 2W .XEVFY\"X5Y\"X5Y\"X5Y\"X5Y N" ++ "V ;X/X 0V 5T 8c ^ AW4W ?Z >W6W KY \"Y 0X 2VFV &VCW#[LSKZ K" ++ "V?V@V\"W 0V 'W )W 1XNTNX &V FW 6Y:Y X *Z NW 0W MW ,Z?Z 1W >W W 2W 4W ,W H" ++ "W 3W :V MW LW:W DPAY ?Y .W -W6W @WJW >Y 5X ,X-X&" ++ "_MXM_&X0X)X?X?X,Y/Y !YLVLY W FV /X 'TCfFT2i CUGfB" ++ "T 9U?U &V 7U 5] >iGU KU 6V;V >U2V NU2V$]5V NWJW 9WJW 9WJW 9WJW 9WJW 9WJW 8XFX" ++ " KY /X -X -X -X -X -X -X -X ,X/Y)XMX;X%Y/Y)Y/Y)Y/Y)Y/Y)Y/Y#T-T'Y3]*X/X'X/X'X/X'X/X%X>X Ir " ++ "GW=\\ GY 9S 3W /XDVDX$X2X$X2X$X" ++ "2X$X2X V ;X0X 0X 7T 8d X$X-WJW EX6X Y .X.Y)X -X -Y .X/X'X -X -XBZ EX -XLV:VLX0XMX;X&Y-Y+X7X NY-Y+X7X!X KX Z W FV " ++ ".X (TDgFT3j CTFhDT 9U?U &V 8U 4\\ =iGU KU 6V;V >U3V MU3V#\\5V MWJW 9WJW" ++ " 9WJW 9WJW 9WJW 9WJW 8XFX LY .X -X -X -X -X -X -X -X ,X.Y*XMX;X&Y-Y+Y-Y+Y-Y+Y-Y+Y-Y%V/V)Y3" ++ "_+X/X'X/X'X/X'X/X%Y@Y Is HW?^ ?Z /Z /Z /Z /Z /Z /Z6Y NZ 0Z /Z /Z /Z 8Y 1Y 3Z /Z /Z" ++ " /Z /Z 3ZCV 5WDX DXCVCW%X0W%X0W%X0W%X0W V :X1X 0X 7T 9f =k#~`\"h Cf " ++ "EW4W @\\ ?X8X LX !Y /X 2VFV 'VBV#XHSET KV?VAV!W 0V (W 'W .` \"V GW 5X" ++ "8X W\"W.XJX FX6X X -X.Y)X -X -X -X/X'X -X" ++ " -XCZ DX -XLV:VLX0XLX^4WG_ 9` @WG^ 9^GW MWG\\ ;f Gm ^BV\"W:W 3X ?^ 0e AWG_ KV.X ?X Z 7X -X+X)\\HXH\\(X0X)X?X?X-X+X $YJVJY >V >YJVJY Ma =X 7V0V JW@W E" ++ "V .Y *TEiET5k DTEiDT :VAV &V 9U 3_ ;W6W NiGU KU 6V;V >U3V MU3V#_8V NXJX" ++ " ;XJX ;XJX ;XJX ;XJX ;XJX :XEX LX -X -X -X -X -X -X -X -X ,X.Y*XLXa'b 7` 5` 5` 5` AW ,W ,W ,W DY EW" ++ "G_ 9` 5` 5` 5` 5` (Z <`GV W6W MW6W MW6W MW6W#W1X NWG^ HW1X NWBVBW&W.W&WJP:PJW&W4PJW&W." ++ "W!V :X2X 0X 6S 8g >k#~`#j Fj GW4W @\\ >W8W LX X .X 2VFV 'VBV$XGSCR " ++ "KV?VBV X 1V (W 'W ,\\ V GW 5X8X f CWIb =bIW MWI^ =j Im U4V LU4V\"`:V GX /WHW ;WHW ;WHW ;WHW ;WHW ;WHW :XEX MY -X -X -X -X -X " ++ "-X -X -X ,X-Y+XKWf ;f ;f ;f ;f +Z >eJU NW6W MW6W MW6W MW6W\"W" ++ "2W MWIb IW2W NWAVAW(W,W(WJRU5V KU5V GXTKW)W4TKW" ++ ")W+W\"V 9X3X 2X 5T :k ?i\"~`$m Jn IW4W A^ ?X:X MW NY .X 2VFV 7~X2XFS" ++ " VIV>X2YIY DYFY +Z JW .V NW 1Y3Y 1n DWLh Bm ChLW Gk Ll 6hLW MWKg HW ,W ,W;Y JW " ++ ",WKfGg8WKg Cl FWLh ChLW MWK` @m Im Y =W6W JW-W&YJb }!WCWCW Hk Dx&{ W4W CWFW P JSCVAVDS :WEV " ++ "$V W6W NiGU KU 6V;V BP>P /U5V KU5V EW=V FX 0XHX =XHX =XHX =XHX =XHX =XHX W:X MW NX -X 2VFV 7~X2WES WKX0XJX>X(Y)X,X7X!Y)X,X7X!Y LX VIV>X1YKY BXFX +Z IW .W " ++ " W 2Y1Y 2o EWMj Dn DjMW Hn Nl 7jMW MWLi IW ,W ,WW6W NiGU KU 6V;V BQ" ++ "?Q 0U6V JU6V BU>V EX 0WFW =WFW =WFW =WFW =WFW =WFW X(Y)X.Y)X.Y)X.Y)X.Y)X%Z9Z*Y6WJX,X/X'X/X'X/X'X/X!XFX EX;Z LWDX ?o Do Do Do Do Do DoKn4n C" ++ "n Cn Cn Cn HW ,W ,W ,W %l HWLi En Cn Cn Cn Cn /Z Cs LW6W MW6W MW6W MW6W!W4W LWMj LW4W " ++ "W?V?V+W(V+WKXBXKV+W5XKV+W(V$W 8W4X 2X 5T ;n ?g!~_%p LZDZ JW4W A^ >W:W MW " ++ " MX -X 2VFV 7~X2WES VJX0XIW>X(X" ++ "'X-X7X!X'X-X7X!Y LX VIV>X1YKY AXHX +Z HW -V W 3Y/Y 3p FWMk Fo EkMW Io Nl 8" ++ "kMW MWMk JW ,W ,W=Y HW ,WMjJj:WMk Gp HWMk GkMW MWMb Bo Im \\>W0X=X LW5X u 6W :V " ++ " MW EkJV Wj Fn CWMk\"\\6X =Z >W6W KW+W)[Ke\"}!WCWCW Jo Hz&{ W4W DWDW ;Y ;X /X'X." ++ "YBXBY+X0X)X?X?X/X'X#T HV IT :V ;T3T :V CV +o BX 6ZM`MZ GXFX CV *\\ 3SFW,S:V>V 0R@R " ++ " KSBV@VDS 9e #V ?W \"V ?W6W NiGU KU 6V;V BR@R 1U6V JU6V BV?V EX 1XFX ?XFX ?XFX ?XFX" ++ " ?XFX ?XFW =XCX NX +X -X -X -X -X -X -X -X ,X+X,XIW>X(X'X/X'X/X'X/X'X/X'X%Z;Z)X5VHX-X/X'X/" ++ "X'X/X'X/X XHX DX:Y LWEX >p Ep Ep Ep Ep Ep EpMp6o Do Do Do Do HW ,W ,W ,W 'o IWMk Gp Ep Ep " ++ "Ep Ep 0Z Ds KW6W MW6W MW6W MW6W!W5X LWMk MW5X V>V?W,V'W,VKZDYKW,V5YKW,V'W%W 8X5W 2" ++ "X 4T ;o @g ~^%q NY@Y KW4W B` ?XX -XJW@WJX0XIX?X(X'X-X7X!X'X-X8Y Y MX " ++ "W/YMY @YJY +Y GW -V W 4X+X 4YE\\ FWNXG\\ H]EX F\\GXNW J\\F[ GW ,\\GXNW MWNXG[ JW ,W ,W?Z GW" ++ " ,WNXH[KXH[:WNXG[ H]H] IWNXG\\ I\\GXNW MWNXFQ C\\CW CW ,W6W!X6X NW?\\?W.X?X JW6W 1X 6W :V MW " ++ " 9X=X\"[IZKW W=Y /W @m H]DV CWNXG[\"\\6W =[ >W6W LW)W*ZJWKY\"}!WCWCW K\\H] J{&{ V3W DWDW :Y " ++ "XCX NX +X -X -X -X -X -X -X -X ,X+X,XIX?X(X'X/X'X/X'X/X'X/X'X$Z=Z(X6WH" ++ "X-X/X'X/X'X/X'X/X YJY DX9Y MWEW =YE\\ EYE\\ EYE\\ EYE\\ EYE\\ EYE\\ EYE]N\\G[7]EX E\\F[ F\\F[ F\\F[ " ++ "F\\F[ IW ,W ,W ,W (p IWNXG[ H]H] G]H] G]H] G]H] G]H] 1Z E]H^ JW6W MW6W MW6W MW6W W6W KWNXG\\" ++ " MW6W NV>V>V,V&V,VJZFYIV,V6YIV,V&V%W 7W6X 3X LR:T ;q @e N~^&s!Y>Y LW4W B` >WXJX +Z GW -W !W 5X)X 5U>" ++ "Z G_CZ I[>T FZC_ KZAZ HW -ZB_ M^BZ KW ,W ,W@Z FW ,^CZMVCZ;^BZ IZBZ I_CZ IZC_ M^ 5YY .W AXJa IZW2W EWDW 9Y =X /X'X/YAXAY,X0X)X?X?X/X'X%X JV KX Z FU>Z " ++ "FU>Z FU>Z FU>Z FU>Z FU>eBZ9[>T FZAZ HZAZ HZAZ HZAZ JW ,W ,W ,W )r J^BZ IZBZ GZBZ GZBZ GZBZ" ++ " GZBZ 1Z EZB[ JW6W MW6W MW6W MW6W W6W K_CZ MW6W V=V>V-V%V-VHZHYHV-V6YHV-V%V%W 7X7X " ++ " 4X NU:T WX !Y 0Y BVDX Dk CXJc -X BX>X LX5Y MX -X Ee Le 3Z ?U=bKUC" ++ "U6XDX IX9Y X +X+X+X -X /X +X/X'X -X -XL[ Y J]?Y KY?] M] 4X8P CW ,W6W X8X MW?\\?W-XAX IW7X 3Y 5W :V MW =_C_(YBXLV NW" ++ "?Z -W CXC\\ KY ,]@Y LW8X >] ?W6W LW)W,YHWHY MW=W JWCWCW MY>Y L[B[ ;W >W2W FWBW 9Y >X 0X%X0X" ++ "@X@X,X0X)X?X?X/X'X&Y JV KY =V >Y7Y =V CV .[HSFR BX 3t BWHW AV .WN\\ 9SFV)S;V?W 3UCU " ++ " LSAV@VCS 7_ V BV LU ?W6W MhGU KU 5W?W AUCU 4U8V HU8V ?UAV CX 2XDX AXDX AXDX AX" ++ "DX AXDX AXDX @XBX NX +X -X -X -X -X -X -X -X ,X+X,XHX@X(X'X/X'X/X'X/X'X/X'X\"ZAZ&X8WFX-X/X'" ++ "X/X'X/X'X/X MXLX BX8X MWFW Y;Z:R GY=Y JY=Y JY=Y JY=Y KW" ++ " ,W ,W ,W *]E[ J]@Y JY>Y IY>Y IY>Y IY>Y IY>Y 2Z FY>Y JW6W MW6W MW6W MW6W W7X K]?Y NW7X " ++ " V=V=U-V$U-VGZJYFU-V7YFU-V$U%W 7X8X &~X/X:T =t @c L~\\'v\"W:W LW4W CXNX ?X>X MV " ++ " $x EX 2~X2WES :VDWEV FZ :W #W 7XKTKX )V IV 4X4X >X !X 0Y BWDX Dm FXKf " ++ "/Y AYBY KX5Y MX -X Gd ~X d 5Y ?V>dLUCU6WBW IX;Z Y +X+Y,X -X 0Y +X/X'X -X -XM[ ;X -XIWBWIX" ++ "0XGW@X)Y'Y.X8X!Y'Y.X9Y M] #X aEa)X@XNW NWA[ ,W DW?[ LX +[=X KW:X =] ?W6W MW'W-XGWGX MW=W JWCWC" ++ "W MXZ W2W FWBW 9Z ?X 0X%X0X@X@X,X0X(X@X@X/Y'Y(Y IV JY >V ?Y5Y >V CV .YFSDP B" ++ "X 2q @XJX AV /WK[ :SFV)S;V@X 4VDV LSAV@VCS 6\\ MV CV KU ?W6W MhGU KU 4V?V @V" ++ "DV 5U9V GU9V >UBV BX 2WBW AWBW AWBW AWBW AWBW AXDX @XBX Y +X -X -X -X -X -X -X -X ,X+Y-XGW" ++ "@X)Y'Y1Y'Y1Y'Y1Y'Y1Y'Y\"ZCZ&Y9WEY.X/X'X/X'X/X'X/X MYNY BX8Y NWFW X NW $w DX $VBV#XFS :WFXEV H] ;W #W 9XITIX" ++ " +V JW 4X4X >X \"Y 3[ BWCX Dn GXLi 1X ?ZFZ JY7Z MX -X Je M~X Me 9Y >U?gMUCV7WBW IX>\\" ++ " NX *X*X,X -X 0X *X/X'X -X -XNZ 9X -XHVBVHX0XGXAX)X%X.X9Y!X%X.X:Y La 'X _ @W6W MW'W.YGWFX NW=W JWCWCW NX:X NYW2W FWBW 8Z @X 0X%X0X@X@X,X0X(X@X@X" ++ "/X%X)Y HV IY ?V @Y3Y ?V CV /YES 6X 1\\H[ JcJc LV 0WI\\ =TFV)S;WAX 5WEW MTAVAWCS 3" ++ "W 4~W.W KV ?W6W LgGU KU 4WAW @WEW 6U9V GU9V ?VBV BX 2WBW AWBW AWBW AWBW AWBW AWBW A" ++ "XAX X *X -X -X -X -X -X -X -X ,X*X-XGXAX)X%X1X%X1X%X1X%X1X%X!ZEZ%X9WCX.X/X'X/X'X/X'X/X LXN" ++ "X AX7X NWFW !W ,W ,W ,W ,W ,W ,]:X=Y .X9X LX9X LX9X LX9X LW ,W ,W ,W +Z=X K[x A` J~\\(y%W8W MW4W CXMW >W>W MV $x DX $VCV\"XFS 9X" ++ "IXEV H_ X #Y ?g AVBX Do HXMk 3Y >l HX7Z MX -X Me J~X Je " ++ "=Y >V?hNUBU8XBX Ju MX *X*X,w Lq IX *~R'X -X -c 8X -XHVBVHX0XFWAX)X%X.X9Y!X%X.X;Z Ke ,X WNV MW" ++ "Ib +W EW;Y MW *Z;X KV:W =_ @W6W NW%W/XFWFX NW=W JWCWCW NW8X!Y:Y =W >| GW@W 8Y @X 0X%X1Y@X@" ++ "Y-X0X(X@X@X/XImIX*Y GV HY @V AY1Y @V CV /XDS 6X 0YDY JdLd LV 1WF[ >SFV'SW6W LgGU KU 3WCW ?XFX 7U:V FU:V >UBV AX 3XBX CXBX CXBX CXBX" ++ " CXBX CXBX BXAw?X *w Lw Lw Lw LX -X -X -X ,X*X-XFWAX)X%X1X%X1X%X1X%X1X%X ZGZ$X:WBX.X/X'X/X" ++ "'X/X'X/X K` @X7X NWFW W ,W ,W ,W ,W ,W ,[8W=X -W7W LW7W LW7W LW7W LW ,W ,W ,W ,Y:X LZ;X M" ++ "Y:Y MY:Y MY:Y MY:Y MY:Y \"Y=\\ LW6W MW6W MW6W MW6W MW:W IZ9X NW:W NVV&V 4W:X %~X2TNVW \"W ;WFTFW -V JV 3X4X >X #Y ?f AWBX Dp IXNm 4X ` @W6W NW%W/WEWEW NW=W JWCWCW X8X!X8X =W >| GW@W 7Y AX " ++ "0X%X1X?X?X-X0X(X@X@X/XImIX+Y FV GY AV BY/Y AV DX 1XCS 6X 0W@X KdLd LV 1VCZ ?SFV'S;WE" ++ "[ 7XFX G~X .S@VBWAS @~W0W .P>W >W6W KfGU KU 3XEX >XFX 8U;V:W3U;VCZ9P>WCV:W/Y 3W@" ++ "W CW@W CW@W CW@W CW@W CXBX CX@w?X *w Lw Lw Lw LX -X -X -X 5p9X-XFXBX)X%X1X%X1X%X1X%X1X%X N" ++ "ZIZ#X:VAX.X/X'X/X'X/X'X/X K` @X7X NWFW W ,W ,W ,W ,W ,W ,[8X?X -X7X NX7X NX7X NX7X MW ,W " ++ ",W ,W ,X9X LY9W MX8X MX8X MX8X MX8X MX8X \"X=] LW6W MW6W MW6W MW6W MW:W IZ9X NW:W NVLu" ++ "KU/VLuKU/VBaAU/V:YAU/V=X=U&V 4X;X %~X2RLW>T >{!z'~Z)}(W6W NW4W DXLX ?X@X MV " ++ " KX ,X %VBV!YHS 8eEV Ic ?W !W ;UETEU ,V KW 3X4X >X $Y >c ?WAX DWD^ JbG] " ++ "5X 9d DY9[ MX -X #d D~X Dd DY a AW6W NW%W0XEWEX W=W JWCWCW W6W!X8X " ++ "=W >| HX@X 7Y BX 0X%X1X?X?X-X0X(X@X@X/XImIX,Y EV FY BV CY-Y BV DX 1XCS 6X 1W>W KeNe LV" ++ " 1VB[ ASFV'S;YI] 9YGY F~X .S@VDX@S @~W1V ,TEZ >W6W JeGU IX +U 2YIY T ?|\"}(~X)~(W6W NW4W DXKW >" ++ "W@X MV KX ,X %VBV!ZIS 7cEV IYNZ8W 0W !W :RCTCR +V KW 3X4X >X %Y" ++ " =b >V@X DS=\\ K`C[ 6Y 8b BX9[ Nd A~X Ad HY W ,X8X8W=X8X X6X MY7X\"X7Y MX 0W )W ,W6W MXXMW AW6W NW%W0XEWDW W" ++ "=W JWCWCW!X6X#X6X >W >| HW>W 6Y CX 0X%X1X?X?X-X0X'XAXAX.XImIX-Y DV EY CV DY+Y CV DX 2X" ++ "BS 6X 1Vh =W6W JeGU IX 4g :g :" ++ "YFX DgEV:XhCV:X/X 3X?W EX?W EX?W EX?W EX?W EX@X EX?w?X *w Lw Lw Lw LX -X -X -X 5p9X" ++ "-XEXCX)X%X1X%X1X%X1X%X1X%X LZMZ!XX7X NWFY !V +V +V +V +V +V +Y6W@" ++ "X ,W5W NW5W NW5W NW5W MW ,W ,W ,W -X7X MX8X X6X X6X X6X X6X X6X $X=_ MW6W MW6W MW6W MW6W " ++ "LWS >}%~R)~V(~P)W6W NW4W" ++ " DWJX ?XAW L~^ $X ,X %VCV N\\LS 6aDVAW0XLZ9W 0W !W :PATAP +V KV 2X" ++ "4X >X &Z =e BW@X DP8[ L^?Z 7X :h EY;\\ \"d >~X ?e LY ;U@W>YAU:W>W Ks KX *X*X,w Lq IX6f+~R" ++ "'X -X -b 7X -XGWFWGX0XDWCX)X%X.X@^ NX%X.s Bl 8X X IXDVCVDX)[ 4\\ -Z @W *V #W $" ++ "W JX5W\"X -W5X W4W KW 0W5X MX7W MW ,W ,WIZ =W ,X8X8W=X7W W4W MX5W\"W5X MX 0X *W ,W6W LWX >XMX BW6W W#W1WD" ++ "WDW W=W JWCWCW!W4W#X6X >W >| HW>W 7Y BX 0X%X1X?X?X-X0X'XAXAX.XImIX.Y CV DY DV EY)Y DV " ++ "DX 2XBS 6X 2WY BSFV'S9bMV ;XFY D~X .S@h>S @~W2i >g W EW>W EW>W EW>W EW>W EW>W EX?w?X *w Lw Lw Lw LX -X -X -X 5" ++ "p9X-XDWCX)X%X1X%X1X%X1X%X1X%X Ke X=W?X.X/X'X/X'X/X'X/X I\\ >X7X NWEY \"W ,W ,W ,W ,W ,W ,X5W" ++ "@X -W4W W4W W4W W4W MW ,W ,W ,W -W6X MX7W W4W W4W W4W W4W W4W $W=VMW MW6W MW6W MW6W MW6W " ++ "LW=X HX5W NW=X MVLuKU/VLuKU/V?[>U/V=Y>U/V=X=U&V 3X=W 7X FW@T ?~&~T*~V)~R*W5V NW4" ++ "W EXJX ?XBX L~^ $X ,X &VBV Mb 4]CVC]4XJZ:W 0W !W +T KV KV 2X4X >" ++ "X 'Z X Lu MX *X*X,w Lq IX6f+~R'X -X -c " ++ "8X -XFVFVFX0XDXDX)X%X.u MX%X.r ?l :X X IXDVCVDX)\\ 4Z ,Y ?W *V #W $W JX5W\"W ,W" ++ "5X W3W LW 0W5X MX7W MW ,W ,WJY ;W ,X8X8W=X7W W4W MX5W\"W5X MX 0X *W ,W6W LWW 6Y 0X 9V LX 5`3R 0T?[?T/W:[ KWId DbKW HW5X NW +X7W JV>W =WLX BW6W W#W1WDWDW W=W JWC" ++ "WCW!W4W#W4W >W >| IX>X 9Y AX 0X%X1X?X?X-X0X'XAXAX.XImIX/Y BV CY EV FY'Y EV DX 2WAS ?r " ++ "CV:V =^ =V 2V=Y CSFV'S8`LV e :W6W GbGU IX 4g 8c 5XFX FgFV" ++ ":YX GX>X GX>X GX>X GX>X GX>X FX?w?X *w Lw Lw Lw LX -X -X -X 5p9X-XDXDX)X" ++ "%X1X%X1X%X1X%X1X%X Jc NX>W>X.X/X'X/X'X/X'X/X HZ =X7X NWEZ #W ,W ,W ,W ,W ,W ,X4WAW ,W3W!W3" ++ "W!W3W!W3W NW ,W ,W ,W .X5W MX7W W4W W4W W4W W4W W4W $W>VLW MW6W MW6W MW6W MW6W KW>W GX5W " ++ "MW>W LVLuKU/VLuKU/V>Z>U/V>Y=U/V=X=U&V 2W>X 8Y FW@T ?~P(~V*~T(~Q)V4V NW4W EXJX >W" ++ "BX L~^ $X ,X &VBV Ld 4WAVD`6XHZ;W 0W !W +T KV LW 2X4X >X 'Y ;i G" ++ "V>X *Z M\\;Y 9X =p HZ?^ 'd Id$Y 9UAWX GWEVJVEW#a >W>W 7Y 1Y " ++ "8V KY 9e8T 0T?Z>T0X:[ KWIf GdLW HW4W MW ,W6W JV?X >XKW BW6W W#W2XDWDX!W=W JWCWCW!W4W#W4W" ++ " >W >| IWX GX>w?X *w Lw Lw Lw LX -X -X -X 5p9X-XCWDX)X%X1X%X1X%", ++ ++ // Second string: ++ "X1X%X1X%X Ia MX?W=X.X/X'X/X'X/X'X/X GX W GX5W MW>W " ++ "LVLuKU/VLuKU/V?\\?U/V?YX 8X DWBT ?~Q)~W)~R&~(V4V NW4W EWHW >WBW K~^ " ++ " $X ,X &VBV Kg \"VEc8WFZ=W /W !W +T 4~W 5V 1X4X >X (Y -] IW>X )Y M[9X 9" ++ "X >\\F\\ H[C` 'a Ca$Y 9UAV:WAU;WW )V $W 6i JX5X$X -X5X V2W LW 1W3W " ++ "MW6W MW ,W ,WLY 9W ,W7W7W=W6W!X4X NX5X$X5X MW .[ .W ,W6W KW>W FWEVJVEW#a >W?X 8Z 4\\ 8V K[" ++ " =iW" ++ "2W IWX X *X -X -X -X -X -X -X -X ,X*X-XCXEX)X%X1X%X1X%X1X" ++ "%X1X%X H_ LX@Wi >i >i >i >i >i3WBX ,V2W!V2W!V2W!V2W NW" ++ " ,W ,W ,W .W4W MW6W!X4X\"X4X\"X4X\"X4X\"X4X M~Y2X@VIW NW6W MW6W MW6W MW6W KW?X GX5X NW?X L" ++ "VLuKU/VLuKU/V@^@U/V@Y;U/V=X=U&V 2X?W 8X CWBT ?~R*~X)~Q%}(V4W W4W FXHX ?XDX K~^ " ++ " $X ,X 'WCV Ii &VEe:XEZ>W /W !W +T 4~W 5V 1X4X >X )Y )[ KW=X (Y N[9Y ;Y " ++ "?Z@Z I]Gb '^ =^$X 9U@V:WAUXIW CW6W!W!W3WCWCW!W=W JWCWCW\"W2W%W3X ?W >W" ++ "2W JW;X ~R+~Z*~P#{'V4W W4W FXHX ?XDX K~^ $X " ++ " ,X 'VBV Gi (VFg;WCZ?W /W !W +T 4~W 6W 1X4X >X *Y &Z LW=X (Y NZ7X ;X ?Z>Z ImNX " ++ "'[ 8\\%Y 9UAW:WAUX XIW CW6W!W!W3WCWCW!W=W JWCWCW\"W2W%W2W ?W >W2W JW:W =Y >X 0Y'" ++ "X0X?X?X-X0X%XCXCX,X%X2~a GV H~a HV I~b HV DX 3W@S ?r DV8V V&V 1XAW 9" ++ "X @WDT ?~S+~Z)}!y'W4W W4W FWFW >WDW J~^ *r ?V &VBV Eh *VEXIXX +Y $Z NWXHX DW6W!WW2W KX:X ?Y =X /X'X0Y@X@Y-X0X%YDXDY,X%X2~a " ++ "GV H~a HV I~b HV DX 3W@S ?r DV8V ;X DW;V DSFV'S >XFX ;V .S@VFW=S (V \"W6W " ++ ":UGU IX 0XFX -V;TLU MV0U!V;TLU6Y 0X:X KX:X KX:X KX:X KX:X KX:X JWV&V 1XBX :X ?WDT ?~S,~[({ x&W4W W" ++ "4W FWFX ?XFX JV \"q >V &VBV Af -VEXGX=W@ZBW .W !W +T 4~W 5f 8V 0X4X " ++ ">X ,Y \"Y W;X 'X NZ7X X -XDVJVDX0XAXGX)X%X.i AX%X.X>Z ,\\ ?X XGW DW6W!WW2W KW9X ?Y =X /X'X/X@X@X,X0X$YEXEY+X%X2~a GV H~a HV I~b HV DX " ++ "3W@S 6X 3V8V ;X DXWEW :V .TAVEW?T (V \"W6W :UGU IX /WEW .V;" ++ "TKU NV/U\"V;TKU7Y /X:X KX:X KX:X KX:X KX:X KX:X KXWDS >~T-~\\(y Mw&W4W W4W GXFX ?XFX JV " ++ " #r >V 'WCV X -Y Y!W;X 'Y Y5X =X" ++ " @Y8Y HgKX 'a Ca%X 8UAV8VAU=W8W NX4X%X *X+Y,X -X 0X(X+X/X'X -X -XI[ ?X -XDWLWDX0X@WG" ++ "X)X&Y.X 0X&Y.X=Y *[ @X XFX EW6W!WW2W KW8W @Y ] Jt It It It It It I~iBW ,|\"|\"|\"| NW ,W ,W ,W /W2W NW6W!W" ++ "2W\"W2W\"W2W\"W2W\"W2W M~Y2WCVEW NW6W MW6W MW6W MW6W IWCX EW3W LWCX IV=V=V.V$V.VFYKZFV.VFY" ++ "7V.V$V&V 0XCW ;Y =WFT >~T-~\\'w Ku%W4W W4W GXEW >WFW IV #q =V 6~X " ++ "JSN^ /VEWCW?W=ZDW .W !W :~W 5f 9V /X4X >X .Y MX\"W:X &X Y5X >Y @X6X FcJX &d Id" ++ "%X 8UAV8VAU>X8X X4X$X +X+X+X -X /X)X+X/X'X -X -XH[ @X -XCVLVCX0X@XHX(X'X-X /X'X-XXFX EW6W!WV-V%V-VGYIZHV-VGY7V-V%V%V " ++ "/WDX ;X ~T-~\\'v Is$W4W W4W GWDX ?XGW HV %r =V 6~X JSJ[ 0VEV" ++ "AV?WX ?X6X D`IX $d Ne#X 8UAV8" ++ "VBU=x X4X$X +X+X+X -X /X)X+X/X'X -X -XG[ AX -XCVLVCX0X?WHX(X'X-X /X'X-X;Y *Y @X WDW EW6W!WV>V,V&V,VIYGZIV,VIY6V,V&V&W /XEW N~X'VGT =~T-~\\" ++ "&u Ir#W4W NV4W HXDX ?XHX HV KX ,V 6~X JSHZ 2VDVAV?W;ZGW -W !W \"V " ++ "Lf :W .X6X =X 0Z LY#~ /X NX5X >X @X5Y AYFX !d >~X >d X 8UAV8VBU>z!X3X%X +X+X+X -X /X" ++ ")X+X/X'X -X -XF[ BX -XCWNWCX0X?XIX(X'X-X /X'X-X:X )Y AX XDX FW" ++ "6W!WV?W,V'W,VJYEZKW,VJY6W,V'W&W /XFX N~X'WHT =~T-~\\%s Gp\"W4W NV4V GXCW >WH" ++ "X HW LX ,V 6~X JSGY 3VDWAW@W:ZIW ,W !W \"V Lf :W .X6X =X 1Z JX#" ++ "~ /X NX5X ?Y @X4X .X Md A~X Ad LX 8UAV8VBU>z!X3X%X +X+X+X -X /X)X+X/X'X -X -XE[ CX -XB" ++ "VNVBX0X>WIX(X'X-X /X'X-X9X *Y AX Q.X $T>Z?T0W8W HW5W\"WWCX FW6W!WXFX >V ,SBVBWCS &V \"W6W :UGU *m 8XFX .VWIX(X'X/X'X/X'X/X'X/X'X KZMZ XHW6X-X" ++ "/X'X/X'X/X'X/X GX XIW GW LX " ++ " ;~X JSFX 3VDV?V@W9ZJW +V \"W !V V -X6X =X 2Z IX#~ /X NX5X ?X ?X4X .X Jd D~" ++ "X Dd IX 8UAV8VCV>z!X3X%Y ,X,Y+X -X /Y*X+X/X'X -X -XD[ DX -XBVNVBX0X>XJX(Y)X,X /Y)X,X9Y *X " ++ "AX XBW FW6W!WXJX(Y)X.Y)X.Y)X.Y)X.Y)X KZKZ!YJW6X,X/X'X/X'X/X'X/X GX |\"X3X$X ," ++ "X,X*X -X .X*X+X/X'X -X -XC[ EX -XA\\AX0X=WJX'X)X,X .X)X,X8X *X AX XBX GW6W!WW 9X =\\KW >SEWWJX FW LX <~X JSEX 6WCV?V@W7ZMW *W #W !V !W -X6" ++ "X =X 4Z GX#~ /X NX5X @X >X4X /X De J~X Je DX 8U@V:WDV>|\"X3X$X ,X-Y*X -X .X*X+X/X'X -X" ++ " -XB[ FX -XA\\AX0X=XKX'X*Y,X .X*Y,X8Y +X AX W WJW DW" ++ " MX .VCV :SDW 6VBV?V@W6b )W #W !V !V +X8X X4X /X Ad L~X Ld AX 8VAV:WDU=|\"X3X$Y -X-Y*X -X .Y+X+X/X'X -X -XA[ GX -XA\\AX0XWKVDVKW\"XLX 9WJW =Z #X" ++ " :V MX AUEVKVDU/X:Y IW5W#WX@W GW6W!W=Y=W2WDWDW W=W JWCWCW\"X4W#W4" ++ "W >W X4X 0X =d ~X" ++ " d LUAWX2X#X3X#X -X.Y)X -X -X+X+X/X'X -X -X@[ HX -X@Z@X0XW " ++ ",W7W7W=W6W W4W MX5W\"W5X MW BX FW ,W7X FWHW >WLVBVLW#YKX :WJW =Y !W :V MW @VHXJWHV-W:" ++ "Y IW5W#WY>W1WDWDW W=W JWCWCW\"X4W#W4W >W W MW7X MW7X MW7X MW7X EWJW AX5W GWJW AXCVCW%X0W%X0W%X0W%X0W\"V +WJX " ++ " ?X 2WLT 9bKQKb)gLQMh Mi =g MW4W MV6W IX@X ?XLX CW MX 0VBV :SDW " ++ "7VAV?V@X5_ (W #W !V \"W +X8X XL" ++ "V;VLX1Y?Y >X 9Z 2W %W )W EW7X JX5W\"X -W5X X )W 0X7Y MW6W MW ,W ,WFY ?W ,W7W7W=W6W W4" ++ "W MX5W\"W5X MW AW FW ,W7X FXJX =WMVBVMW#YJY ;WKX >Y W :V MW ?dId,W;Z IW5W#W=W DW4W!W" ++ " )W6W DVKW >X>W HW6W W>Y>W1WDWDW W=W JWCWDX\"X4W#W4W >W ;V7W LX2X LY 4X *X1X%]JXJ]'X0X Hj L" ++ "Y-Y%Y IV JY LYKVKY MY5Y MYJVJY $X 2XBS 6X 2q 9X :V #\\ 7TDgFT /XFX EV )TFV>VJT #" ++ "V \"W6W :UGU +XFX *V=TCU%V1V!V=TCU=X ,X1W$X1W$X1W$X1W$X1W$X2X%X7X LY .X -X -X -" ++ "X -X -X -X -X ,X.Y*X;XMX&Y-Y+Y-Y+Y-Y+Y-Y+Y-Y ZAZ$_3Y*X1X%X1X%X1X%X1X FX W3W$W7X MW7X M" ++ "W7X MW7X MW7X MW7X MW7Z NX -X -X -X -X +W ,W ,W ,W .W4W MW6W W4W W4W W4W W4W W4W 5Z IWMV=W" ++ " MW7X MW7X MW7X MW7X EWKX AX5W GWKX @XDVDX$X2X$X2X$X2X$X2X\"V +XKW ?X 1WMT 7`JQKa" ++ "'fLQLf Kg W >WLW BX NY 1VBV :SDW 8V@V?V?W4] &V $W " ++ " V \"V *Y:Y YGW>X0X$X4Y\"Y /X/Y(X -X" ++ " ,Y-X+X/X'X -X -X>[ JX -X@Z@X0X;XMX%Y/Y*X ,Y/Y*X6Y -X AX ;Y3Y IXLX =WLV;VLW0X=Y ?X :Z 1W $" ++ "V )W EW8Y JY7X\"X -X7Y X )W 0X7Y MW6W MW ,W ,WEY @W ,W7W7W=W6W X6X MY7X\"X7Y MW AW FW" ++ " ,X8X EWJW Y NW :V MW >bGc,W;[ JW6X#W=W DX6X!W )W6W DVLX >W=X IW7" ++ "X W>Y>W1XEWEX W=W IWDWDW!Y6X#X6X >W ;W8W MX0X MY 4X *Y3Y$^LXL^&X0X Ff IY/Y#Y JV KY JYLVL" ++ "Y KY7Y KYKVKY #X 2XBS 6X 3t ;X :V ![ 8TCfFT .XFX FV )UGV>WKT MW7X :UGU " ++ " ,XFX *V=TBU&V2W!V=TBU=X -X0X&X0X&X0X&X0X&X0X&X0W%X7X KY /X -X -X -X -X -X -X -X ,X/Y)" ++ "X;XMX%Y/Y)Y/Y)Y/Y)Y/Y)Y/Y Z?Z$^4Y)Y3Y%Y3Y%Y3Y%Y3Y FX XEVFY\"X5Y\"X5Y\"X5Y\"X5Y!V *WLX @X /WNT 7`JQJ_&eKQKe Je :d KW4W MW8" ++ "W HW>X ?XNX AX Y 1VCV 9SDW 9V?V?V?X4\\ &W %W V \"V )X:X ;X 9Z" ++ " CX 4X (Y KW7X AX W BW6W W )W6W DWMX ?X=X IX8X W?[?W0WEWEW NW=W " ++ "IWDWDW!Y6W!W6W =W ;W8W MX0X NY 3X )Y5Y\"z%X0X C` FY/Y\"X JV KX HYMVMY IX7X IYLVLY \"X 1XCS" ++ " 6X 4v X ?XNX AY " ++ " Y4P VBV 9SDW 9V?V?V?Y4Z %W %W V #W )X:X ;X :Z CY 4X (Y KX9Y AX ;X6X 1" ++ "Y 1e /e @U@XB[JXW BX8X W )W6W CVNX >W;W IX8X X@[@X0XFWEW NW=W IWDWEX!Z8X!X8X =W :W:W LX" ++ "0X Y 2X (Y7Y Nv#X0X ?X AY1Y V IV JV FYNVNY GV5V GYMVMY !X 1XCS 6X 5x =X :V MZ 8T?ZBT" ++ " *VDV FV 'T&T KX8X :UGU ,VDV )VWNX @Y !Z6Q VBV K" ++ "P>SEW 9V>WAW>X3Z &W %W V #V 'XU?" ++ "ZH^MZ\\ JX8X\"W?W AX" ++ "9Y X *W6W CVNX ?X;X JX9Y NW@[@W/XFWFX NW=W IXEWEX!Z8X!X8W ;W ;W;X MX.X\"Y 1X 'Y9Y Lt\"X0X ?X" ++ " @Y3Y MT HV IT Dj ET3T EYNVNY X 0XDS 6X 6ZM`LY >X :V LY 7T)T (UCU ET(T " ++ " JX9Y :UGU ,UCU )V;m.V3V NV;mCY7P HX.X(X.X(X.X(X.X(X.X(X.X(X6X IY.R&X -X -X -X -" ++ "X -X -X -X ,X2Z'X9a$Z3Y&Z3Y&Z3Y&Z3Y&Z3Y!Z9Z&Z3Y&Y5Y#Y5Y#Y5Y#Y5Y EX ` >Y !Y8S MX +VBV KQ?SFX 9V=VAV=Y6] &V &W" ++ " NV BX 1X 1V 'Y>Y :X X:W JY;Z NXB]BX.XGWGX MW=W HXFWFX [:X NX:X ;W :WX HXX 9X =Z 1P2Z 3X GQ5Z GX=Y @X 9Y:Y KP8Z GX -X 4^ 1^ +X 5U?gM_9W,W%X7Z L[4U&X6]%X -X )" ++ "[2X+X/X'X -X -X9[ X -X&X0X8`\"Z7Z'X )Z7Z'X3X%T2Y ?X 9Z9Z E` :_9_3Y7Y BX >Z -W #W +W D" ++ "X=\\ J\\=Y LY7P HY=\\ LY5R JW -Y?] MW6W MW ,W ,W@Y EW ,W7W7W=W6W MYX LX.X#Y 0X %Y=Z Gl MX0X ?X ?Z7Z JP FV GP @f AP/P Ah MX " ++ "/YFSDP BX 8ZFVEY @X :V JX 7V.U %SAS CU.U HZ" ++ "\\=Y B^ 7r Gr Gr Gr Gr KV (_ BX )Y S 8RBSCR <] 2\\ GW4W KZBZ HX;W >_ <[ " ++ " $[=U MX ,VBV JUCSHY :V;WCW<[Z 0R5Z 2X GT9[ G" ++ "Y?Z AY 9[>[ KR;Z FX -X 1[ 1[ (X 5V>dL^9X,X&X9[ J[7W&X9_$X -X (\\6Z+X/X'X -X -X8[!X -X&X0X" ++ "8`![;[&X ([;[&X3Y&W7[ ?X 8Z;Z D` :^7^3X5Y CX ?Z ,W #W +W DY?] J]?Y KZ:R GY?] LZ8T JW" ++ " -ZA^ MW6W MW ,W ,W?Y FW ,W7W7W=W6W LY>Y J]?Y KY?] MW /T9X DX ,Y@] CWNW 9]>]'Y@Y =^ AY IW" ++ " :V MW HYCXNW L\\>Y VAX >Y>Y LY ,W6W B] >X9X K[>[ MXDVMVDX,YIWIY LW=W GYHWHY N]>Y LY" ++ ">Y :X :X@X LX,X%Y /X $ZAZ Ch KX0X ?X >[;[ ?V 6d >f LX /[HSFR BX 9Z3Y AX :V IX 7" ++ "V1V #R@R BU0U G[>[ :UGU ,R@R 'V(U)V6W LV(UU IX,X*X,X*X,X*X,X*X,X" ++ "*X,X*W4X G[7W&X -X -X -X -X -X -X -X ,X9_%X8`![;[![;[![;[![;[![;[\"Z3Z(];[\"Z;Z NZ;Z NZ;Z NZ" ++ ";Z CX Y JW6W LY>Y IY>Y IY>Y IY>Y IY>Y 2Z FY>Y HY@] KY@] KY@] KY@] B^ >]?Y A^ 6o Do Do Do " ++ "Do IV (_ CX (Y S (S ,[ 0[ GW4W J\\H\\ GW:W >^ :\\ %[@W MX ,VBV JXFS" ++ "IZ :V:WEW:\\@e (V 'V MV BX 1X 2V $ZDZ 8X ?Z /U;] 2X GV=\\ EZC[ @X 7[@[ JT?[ EX -X /Y " ++ " 1Y &X 5V=bK\\7X,X&X<^ I]=Z&X=b#X -X ']:\\+X/X'X -X -X7[\"X -X&X0X7_ \\?\\%X '\\?\\%X2X&Z<\\ >X 7[" ++ "?[ B^ 9^7^4Y5Y CX ?Y +W \"V +W DZB_ J_CZ I[>T G[C_ K[=W JW ,\\GXNW MW6W MW ,W ,W>Y GW " ++ ",W7W7W=W6W KZBZ I_CZ J[C_ MW /W>Z DZ .ZB^ C` 8\\>\\&X>Y =\\ AY HW :V MW GZFYNY N]AZ N" ++ "WCX _ FX0X ?X =\\?\\ >V 5b W;[>T F[=W J[=W J[=W J[=W LW ,W ,W ,W *ZBZ IW6W KZBZ GZBZ " ++ "GZBZ GZBZ GZBZ 1Z F[BZ GZB^ KZB^ KZB^ KZB^ A\\ =_CZ ?\\ 3l Al Al Al Al HV (^ BX (X " ++ " NS (S ,Z .Y FW4W In GX:X ?^ 9_ (]FZ MX ,VBV J[ISL\\ :V9XGX9^Fi )W )W " ++ " MV BX 1X 3W #[H[ Et Mx MZC_ 1X GZD^ C[G\\ @Y 7^F] IXF] DX -X ,V 1V #X 4V<^IY5X*X'y G" ++ "_D^&{!y NX &`B`+X/X'X -X -X6[#w LX&X0X7_ N^E^$X &^E^$X2Y'^C^ =X 7^E^ B^ 8]7]4Y3Y DX @~U&W " ++ "\"W ,W C\\HYNW JWNXG\\ H]EX F\\GXNW J]D[ JW +kMW MW6W MW ,W ,W=Y HW ,W7W7W=W6W K]H] IWNX" ++ "G\\ I\\GXNW MW /[E\\ Be 9[GXNW B^ 7\\>\\'XP @W8W 3~W :_GaKP" ++ " @UGU ,P>P 'V&U+V6V KV&U;]GZ JX*X,X*X,X*X,X*X,X*X,Y,Y,X4y7_D^&y Ny Ny Ny NX -X -X -" ++ "X ,{\"X7_ N^E^ L^E^ L^E^ L^E^ L^E^ MV/V(dE^ N^E^ L^E^ L^E^ L^E^ BX \\ Av 6W :V MW FkL]$u LXGX 9p Hp EW6W A[ ?X6X LpN\\#hKh)s JW<] Lu LWNm Hp 6` Bl K~" ++ "W'x MX 1iEi HX CX0X ?X ;u X :V HW 3X=X )X\\ " ++ " /c 8c 8c 8c 8c CV '\\ ?T %W U *T *W ,V DW4W Gj EW8W >\\ 5~P In LX " ++ " -VBV Is 9V7g6qJZ *V )V LV BX 1X 3V !l Dt Mx Mt /X Gr ?m ?X 4r Hm BX -X &P 1P LX" ++ " 3V 3X*X'w Cv%x My NX #x(X/X'X -X -X4[%w LX&X0X5] Ls\"X $s\"X1Y(w ;X 5s ?\\ 7\\5\\5Y1Y EX @~U&W" ++ " !V ,W BjLW JWMj Dn DjMW Hr JW )hLW MW6W MW ,W ,W;Y JW ,W7W7W=W6W In GWMj EjMW MW /p" ++ " ?d 8iLW B^ 6Z<[)Y:Y >Z @v 6W :V MW EiK]$t JYLZ 7n Fo EW6W A[ ?X5W LWNfM\\\"gKg'q IW<" ++ "] Ks KWMk Fn 5` Aj J~W'x MX 1iEi HX CX0X ?X :s ;V 2\\ 6^ HX +n Lz MR,R =X :V HW " ++ "1ZEZ %ZDZ 0~W :WNfM\\ @UGU !V%U,V6i/V%U9n JX*X,X*X,X*X,X*X,X*X,X*X-" ++ "X3y5v%y Ny Ny Ny NX -X -X -X ,x NX5] Ls Hs Hs Hs Hs IR+R(WMs Js Hs Hs Hs @X R $V NU *U *U *U DW4W Fh DW8X ?\\ " ++ "4~ Hl KX -VBV Hp 8V5e4nGZ +W +W LV BX 1X 3V j Ct Mx Mr -X Gq =j " ++ ">Y 3p Gl AX -X 2X 3W 5X(X(u ?s$v Ky NX \"v'X/X'X -X -X3[&w LX&X0X5] Kq!X #p X0X(v :X " ++ "4p =\\ 7\\5\\6Y/Y FX @~U&W !V ,W AhKW JWLh Bm ChLW Gq JW (eJW MW6W MW ,W ,W:Y KW ,W7W7W" ++ "=W6W Hl FWLh ChLW MW /o >d 7gKW A\\ 5ZZ @v 6W :V MW DgI\\$s He 5l Dn EW6W @Y " ++ ">W4X MWMeM\\!eIe%o HW<] Jq JWLi Dk 2_ @h J~Y(x MX 1iEi HX CX0X ?X 9q :V 1Z 4\\ GX *m" ++ " Lz LP*P X X ?v 6W :V MW CeG[$r Fc " ++ "2h Am EW6W @Y ?X3W MWMdL\\ cGc#m GW;\\ Hm HWKg Ah /] ?f I~Y(x MX 1iEi HX CX0X ?X 7m 8V 0" ++ "X 2Z FX (j Kz AX :V HW -g Lh ,~W :WMdL\\ @UGU \"V$U-V5i0V$" ++ "U7i HX(X.X(X.X(X.X(X.X(X.X(X/X2y1o\"y Ny Ny Ny NX -X -X -X ,t JX4\\ Im Bm Bm Bm Bm %VHm Dm " ++ "Bm Bm Bm =X eJW GeJW GeJW GeJW ?X ;WJe 9X MW &Z =U W ,W *" ++ "R &Q BW4W B` AW6W >[ /y Dd GX -VCV Af 5V2a.gBZ ,W -W KV CX 0X 4V " ++ " Kd @t Mx Km *X Ek 6d ;X .h Bh >X .X 1X 1W 7X(X(q 7j Np Ey NX Mm\"X/X'X -X -X1[(w LX" ++ "&X0X4\\ Gi LX Ni LX/X$n 7X 0i 9Z 5[5[6Y-Y GX @~U&W V -W >cIW JWIb k EW6W @Y ?W2W MWK`I[ NaEa i EW;\\ Fi FWIc >e ,\\ =b G~Y(x MX 1iEi HX CX0" ++ "X ?X 5i 6V /V 0X EX &f Iz AX :V /P;W *c Gb )~W :WK`I[ @UGU " ++ " #V#U.V4i1V#U6f FX(X.X(X.X(X.X(X.X(X.X(X/X2y/j Ny Ny Ny Ny NX -X -X -X ,p FX4\\ Gi >i " ++ ">i >i >i $VEi @i >i >i >i ;X i0g ;i >i >i >i HW " ++ ",W ,W ,W #d BW6W Ef ;f ;f ;f ;f JUJe ;cIW FcIW FcIW FcIW ?X ;WIb 7X MW %Y " ++ " =T X -X )P %P AW4W ?Z >W6X ?Z ,w B` EX .VBV <] 1V0]*b?[ -W -W" ++ " KV CW /X 4V I` >t Mx Hg 'X Bf 2` :X +d =b ;X .W 0X 1X 9X&X)m 0d Kj ?y NX Jg " ++ "NX/X'X -X -X0[)w LX&X0X3[ Dc IX Kf LX/Y!g 4X .e 7Z 5Z3Z7Y+Y HX @~U&W V -W =`GW JWG" ++ "^ 7b 9^GW Ad CW \"YDW MW6W MW ,W ,W7Y NW ,W7W7W=W6W B` @WG^ 9^GW MW (c 2] 3_GW @Z 3X:X*Y4Y " ++ "@X ?v 6W :V MW ?_AW$WKb @^ +` 9g CW6W ?W ?X2X NWJ^GY K]B^ Ke CW:[ Dd CWG_ 9` 'Y ;^ " ++ "F~[)x MX 1iEi HX CX0X ?X 2c 3V .T .V DX $b Gz AX :V /R>X &[ ?Z %~W " ++ " :WJ^GY ?UGU #V +V +V 1b EX&X0X&X0X&X0X&X0X&X0Y'X1X1y,d Ky Ny Ny Ny NX -X -X " ++ "-X ,j @X3[ Dc 8c 8c 8c 8c !VBc ;e :e :e :e 9X Y BS .V,W#Z ;V -V " ++ " 7W ;W EX ;\\ 6] +Z 5\\ 5Z WGXBU FX=X E` \"W >] @WDY 3Z " ++ "2X C[ >T :[ KV /TAY EWGXBU =UGU" ++ " BT 6V +V +V ,Y ?\\ +[ 0[ 0[ 0[ 0[ KT=[ 2[ 0[ 0[" ++ " 0[ 7Z ;Y .Y .Y .Y .Y .Y -Y2\\\"Z /\\ 1\\ 1\\ 1\\ CZ 3Z /Z /Z /Z /Z FVCZ 1Y .Y ." ++ "Y .Y ,W :WDX 2W LW 7R #S" ++ " >W /W 8W :V \"W 5X )X " ++ " &Z CW NV .W :W %W @W :W " ++ " -X -W :V MW LW FW ?W >W NW 0W =W " ++ " 3S GV /XGZ DW HUGU AT %" ++ "T 'R JT " ++ " #T (X :W NX LW " ++ " 7S =V /V 7W :V \"W 4X'Q " ++ "&Y %Z DW NV .W :W %W @W :W " ++ " -W ,W :V MW LW FW ?W >W NW 0W =W " ++ " 3S GV /j CW HUGU @T " ++ " %T 'P HT " ++ " \"Q 'W 9W NW KW " ++ " 7S =W 1W 7V :W \"V 2X)R " ++ " &X #Z EW NW /W :W %W " ++ " @W :W -W ,X ;V NX LW FW ?W >W NW 0W =W " ++ " 3S GV /j CW HUGU @U " ++ " &U U " ++ " \"P 'W 9W NW KV " ++ " 6S W NW 0W =W " ++ " 3S GV /h AW HUGU ?T " ++ " %T NT " ++ " )X 9W X KV " ++ " 6S W NW 0W =W" ++ " 3S GV .f @W HUGU ?" ++ "U &U " ++ " U *W 8W W JV " ++ " 6S ;V 3V 6V :W \"V " ++ " .[5[ *Y Z Ha (W :a W NW 0W" ++ " =W 3S GV +a >W HUGU " ++ " >T %T " ++ " NT +X 8W !X (VIV " ++ " 6S :V 5V 5U 9W \"" ++ "U +\\;] )X MZ Ia (W :a " ++ " =Y %W ?W :W /W )[ ?V #[ KW FW ?W >W N" ++ "W 0W =W 3S GV 'Z ;W " ++ " HUGU >U &U " ++ " U ,W 7W !W 'VIV " ++ " 6S :V 6W 6V " ++ " 4V *_C` )Y LZ Ja :a " ++ " (P7Y $W ?W :W 0X (b GV +b JW FW ?W >W " ++ " NW 0W =W 3S GV " ++ "7W HUGU >U &U " ++ " U -X 7W \"X 'VJW " ++ " 6S 9V 7V 5U " ++ " 3U 'x (Z KZ Ka :a " ++ " (R:Z $W ?W :W 0X (b GV +b JW FW ?W >W" ++ " NW 0W =W 3S GV " ++ " 7W #U &U " ++ " U -X 7W \"X &UJW " ++ " 6S 9W 9W " ++ " Bu ([ IZ La :a " ++ " (T>[ $X ?W :W 1X &a GV +a IW FW ?W >W N" ++ "W 0W =W 3S GV 7W " ++ " $V 'V " ++ " !V .X 6W #X %VLW " ++ " 5S " ++ " 2p -a 8XE] %Y" ++ " >W :W 3Z $_ GV +_ GW FW ?W >W NW 0W =W " ++ " 3S GV 7W /QGW " ++ " 2QGW ,QG" ++ "W 0Z 6W %Z %a " ++ " 5S 0l " ++ " +a 8p +_ " ++ " >W :W ;a !] GV +] EW FW ?W >W NW 0W =W " ++ " 3S GV 7W /` " ++ " 1` +` " ++ " 7a 5W -a #` " ++ " >e '`" ++ " 7o *^ =W :W " ++ " ;` KY GV +Y AW FW ?W >W NW 0W =W " ++ " 3S GV 7W /` 1` " ++ " +` " ++ " 7` 4W -` \"_ " ++ " 8\\ #_ " ++ " \"} 3n )^ =W :W ;` 9V " ++ " BW FW ?W >W NW 0W =W 'V " ++ " 7W /_ 0_ " ++ " *_ 6` 4W -` " ++ " !] " ++ " -] " ++ " } 3l '] W NW 0W =W 'V " ++ " 7W /^ /^ " ++ " )^ 5_ 3W -_ N[ " ++ " " ++ " ,[ M} 2j " ++ " &\\ ;W :W ;^ 7V BW FW ?W >W NW 0W =W" ++ " 7W -Y " ++ " *Y $Y " ++ " 2^ 2W -^ LX " ++ " " ++ " *X J} /d #Z 9W :" ++ "W ;\\ 5V BW FW ?W >W NW 0W =W " ++ " 7W " ++ " " ++ " /\\ 0W HT " ++ " " ++ " I} *[ NW 6W :W ;Z 3V " ++ " BW FW ?W >W NW 0W =W " ++ " 7W " ++ " /Z .W " ++ " " ++ " =} " ++ " " ++ " " ++ " " ++ " D" }; ++ ++ // Define a 40x38 'danger' color logo (used by cimg::dialog()). ++ static 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 }; ++ ++ //! Get/set default output stream for the \CImg library messages. ++ /** ++ \param file Desired output stream. Set to \c 0 to get the currently used output stream only. ++ \return Currently used output stream. ++ **/ ++ inline std::FILE* output(std::FILE *file) { ++ cimg::mutex(1); ++ static std::FILE *res = cimg::_stderr(); ++ if (file) res = file; ++ cimg::mutex(1,0); ++ return res; ++ } ++ ++ // Return number of available CPU cores. ++ inline unsigned int nb_cpus() { ++ unsigned int res = 1; ++#if cimg_OS==2 ++ SYSTEM_INFO sysinfo; ++ GetSystemInfo(&sysinfo); ++ res = (unsigned int)sysinfo.dwNumberOfProcessors; ++#elif cimg_OS == 1 ++ res = (unsigned int)sysconf(_SC_NPROCESSORS_ONLN); ++#endif ++ return res?res:1U; ++ } ++ ++ // Lock/unlock mutex for CImg multi-thread programming. ++ inline int mutex(const unsigned int n, const int lock_mode) { ++ switch (lock_mode) { ++ case 0 : cimg::Mutex_attr().unlock(n); return 0; ++ case 1 : cimg::Mutex_attr().lock(n); return 0; ++ default : return cimg::Mutex_attr().trylock(n); ++ } ++ } ++ ++ //! Display a warning message on the default output stream. ++ /** ++ \param format C-string containing the format of the message, as with std::printf(). ++ \note If configuration macro \c cimg_strict_warnings is set, this function throws a ++ \c CImgWarningException instead. ++ \warning As the first argument is a format string, it is highly recommended to write ++ \code ++ cimg::warn("%s",warning_message); ++ \endcode ++ instead of ++ \code ++ cimg::warn(warning_message); ++ \endcode ++ if \c warning_message can be arbitrary, to prevent nasty memory access. ++ **/ ++ inline void warn(const char *const format, ...) { ++ if (cimg::exception_mode()>=1) { ++ char *const message = new char[16384]; ++ std::va_list ap; ++ va_start(ap,format); ++ cimg_vsnprintf(message,16384,format,ap); ++ va_end(ap); ++#ifdef cimg_strict_warnings ++ throw CImgWarningException(message); ++#else ++ std::fprintf(cimg::output(),"\n%s[CImg] *** Warning ***%s%s\n",cimg::t_red,cimg::t_normal,message); ++#endif ++ delete[] message; ++ } ++ } ++ ++ // Execute an external system command. ++ /** ++ \param command C-string containing the command line to execute. ++ \param module_name Module name. ++ \return Status value of the executed command, whose meaning is OS-dependent. ++ \note This function is similar to std::system() ++ but it does not open an extra console windows ++ on Windows-based systems. ++ **/ ++ inline int system(const char *const command, const char *const module_name=0) { ++ cimg::unused(module_name); ++#ifdef cimg_no_system_calls ++ return -1; ++#else ++#if cimg_OS==1 ++ const unsigned int l = (unsigned int)std::strlen(command); ++ if (l) { ++ char *const ncommand = new char[l + 24]; ++ std::strncpy(ncommand,command,l); ++ std::strcpy(ncommand + l," >/dev/null 2>&1"); // Make command silent. ++ const int out_val = std::system(ncommand); ++ delete[] ncommand; ++ return out_val; ++ } else return -1; ++#elif cimg_OS==2 ++ PROCESS_INFORMATION pi; ++ STARTUPINFO si; ++ std::memset(&pi,0,sizeof(PROCESS_INFORMATION)); ++ std::memset(&si,0,sizeof(STARTUPINFO)); ++ GetStartupInfo(&si); ++ si.cb = sizeof(si); ++ si.wShowWindow = SW_HIDE; ++ si.dwFlags |= SW_HIDE | STARTF_USESHOWWINDOW; ++ 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 return std::system(command); ++#else ++ return std::system(command); ++#endif ++#endif ++ } ++ ++ //! 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 \c a and \c b. ++ template ++ inline void swap(T& a, T& b) { T t = a; a = b; b = t; } ++ ++ //! Exchange values of variables (\c a1,\c a2) and (\c b1,\c 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 (\c a1,\c a2,\c a3) and (\c b1,\c b2,\c 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 (\c a1,\c a2,...,\c a4) and (\c b1,\c b2,...,\c 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 (\c a1,\c a2,...,\c a5) and (\c b1,\c b2,...,\c 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 (\c a1,\c a2,...,\c a6) and (\c b1,\c b2,...,\c 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 (\c a1,\c a2,...,\c a7) and (\c b1,\c b2,...,\c 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 (\c a1,\c a2,...,\c a8) and (\c b1,\c b2,...,\c 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 endianness of the current architecture. ++ /** ++ \return \c false for Little Endian or \c true for Big Endian. ++ **/ ++ inline bool endianness() { ++ const int x = 1; ++ return ((unsigned char*)&x)[0]?false:true; ++ } ++ ++ //! Reverse endianness of all elements in a memory buffer. ++ /** ++ \param[in,out] buffer Memory buffer whose endianness must be reversed. ++ \param size Number of buffer elements to reverse. ++ **/ ++ template ++ inline void invert_endianness(T* const buffer, const cimg_ulong 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; ++ case 8 : { ++ const cimg_uint64 ++ m0 = (cimg_uint64)0xff, m1 = m0<<8, m2 = m0<<16, m3 = m0<<24, ++ m4 = m0<<32, m5 = m0<<40, m6 = m0<<48, m7 = m0<<56; ++ for (cimg_uint64 *ptr = (cimg_uint64*)buffer + size; ptr>(cimg_uint64*)buffer; ) { ++ const cimg_uint64 val = *(--ptr); ++ *ptr = (((val&m7)>>56) | ((val&m6)>>40) | ((val&m5)>>24) | ((val&m4)>>8) | ++ ((val&m3)<<8) |((val&m2)<<24) | ((val&m1)<<40) | ((val&m0)<<56)); ++ } ++ } 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)); ++ } ++ } ++ } ++ } ++ ++ //! Reverse endianness of a single variable. ++ /** ++ \param[in,out] a Variable to reverse. ++ \return Reference to reversed variable. ++ **/ ++ template ++ inline T& invert_endianness(T& a) { ++ invert_endianness(&a,1); ++ return a; ++ } ++ ++ // Conversion functions to get more precision when trying to store unsigned ints values as floats. ++ inline unsigned int float2uint(const float f) { ++ int tmp = 0; ++ std::memcpy(&tmp,&f,sizeof(float)); ++ if (tmp>=0) return (unsigned int)f; ++ unsigned int u; ++ // use memcpy instead of assignment to avoid undesired optimizations by C++-compiler. ++ std::memcpy(&u,&f,sizeof(float)); ++ return ((u)<<1)>>1; // set sign bit to 0. ++ } ++ ++ inline float uint2float(const unsigned int u) { ++ if (u<(1U<<19)) return (float)u; // Consider safe storage of unsigned int as floats until 19bits (i.e 524287). ++ float f; ++ const unsigned int v = u|(1U<<(8*sizeof(unsigned int)-1)); // set sign bit to 1. ++ // use memcpy instead of simple assignment to avoid undesired optimizations by C++-compiler. ++ std::memcpy(&f,&v,sizeof(float)); ++ return f; ++ } ++ ++ //! Return the value of a system timer, with a millisecond precision. ++ /** ++ \note The timer does not necessarily starts from \c 0. ++ **/ ++ inline cimg_ulong time() { ++#if cimg_OS==1 ++ struct timeval st_time; ++ gettimeofday(&st_time,0); ++ return (cimg_ulong)(st_time.tv_usec/1000 + st_time.tv_sec*1000); ++#elif cimg_OS==2 ++ SYSTEMTIME st_time; ++ GetLocalTime(&st_time); ++ return (cimg_ulong)(st_time.wMilliseconds + 1000*(st_time.wSecond + 60*(st_time.wMinute + 60*st_time.wHour))); ++#else ++ return 0; ++#endif ++ } ++ ++ // Implement a tic/toc mechanism to display elapsed time of algorithms. ++ inline cimg_ulong tictoc(const bool is_tic); ++ ++ //! Start tic/toc timer for time measurement between code instructions. ++ /** ++ \return Current value of the timer (same value as time()). ++ **/ ++ inline cimg_ulong tic() { ++ return cimg::tictoc(true); ++ } ++ ++ //! End tic/toc timer and displays elapsed time from last call to tic(). ++ /** ++ \return Time elapsed (in ms) since last call to tic(). ++ **/ ++ inline cimg_ulong toc() { ++ return cimg::tictoc(false); ++ } ++ ++ //! Sleep for a given numbers of milliseconds. ++ /** ++ \param milliseconds Number of milliseconds to wait for. ++ \note This function frees the CPU ressources during the sleeping time. ++ It can 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); ++#else ++ cimg::unused(milliseconds); ++#endif ++ } ++ ++ inline unsigned int _wait(const unsigned int milliseconds, cimg_ulong& timer) { ++ if (!timer) timer = cimg::time(); ++ const cimg_ulong current_time = cimg::time(); ++ if (current_time>=timer + milliseconds) { timer = current_time; return 0; } ++ const unsigned int time_diff = (unsigned int)(timer + milliseconds - current_time); ++ timer = current_time + time_diff; ++ cimg::sleep(time_diff); ++ return time_diff; ++ } ++ ++ //! Wait for a given number of milliseconds since the last call to wait(). ++ /** ++ \param milliseconds Number of milliseconds to wait for. ++ \return Number of milliseconds elapsed since the last call to wait(). ++ \note Same as sleep() with a waiting time computed with regard to the last call ++ of wait(). It may be used to temporize your program properly, without wasting CPU time. ++ **/ ++ inline cimg_long wait(const unsigned int milliseconds) { ++ cimg::mutex(3); ++ static cimg_ulong timer = 0; ++ if (!timer) timer = cimg::time(); ++ cimg::mutex(3,0); ++ return _wait(milliseconds,timer); ++ } ++ ++ // Random number generators. ++ // CImg may use its own Random Number Generator (RNG) if configuration macro 'cimg_use_rng' is set. ++ // Use it for instance when you have to deal with concurrent threads trying to call std::srand() ++ // at the same time! ++#ifdef cimg_use_rng ++ ++#include ++ ++ // Use a custom RNG. ++ inline unsigned int _rand(const unsigned int seed=0, const bool set_seed=false) { ++ static cimg_ulong next = 0xB16B00B5; ++ cimg::mutex(4); ++ if (set_seed) next = (cimg_ulong)seed; ++ else next = next*1103515245 + 12345U; ++ cimg::mutex(4,0); ++ return (unsigned int)(next&0xFFFFFFU); ++ } ++ ++ inline unsigned int srand() { ++ unsigned int t = (unsigned int)cimg::time(); ++#if cimg_OS==1 ++ t+=(unsigned int)getpid(); ++#elif cimg_OS==2 ++ t+=(unsigned int)_getpid(); ++#endif ++ return cimg::_rand(t,true); ++ } ++ ++ inline unsigned int srand(const unsigned int seed) { ++ return _rand(seed,true); ++ } ++ ++ inline double rand(const double val_min, const double val_max) { ++ const double val = cimg::_rand()/16777215.; ++ return val_min + (val_max - val_min)*val; ++ } ++ ++#else ++ ++ // Use the system RNG. ++ inline unsigned int srand() { ++ const unsigned int t = (unsigned int)cimg::time(); ++#if cimg_OS==1 || defined(__BORLANDC__) ++ std::srand(t + (unsigned int)getpid()); ++#elif cimg_OS==2 ++ std::srand(t + (unsigned int)_getpid()); ++#else ++ std::srand(t); ++#endif ++ return t; ++ } ++ ++ inline unsigned int srand(const unsigned int seed) { ++ std::srand(seed); ++ return seed; ++ } ++ ++ //! Return a random variable uniformely distributed between [val_min,val_max]. ++ /** ++ **/ ++ inline double rand(const double val_min, const double val_max) { ++ const double val = (double)std::rand()/RAND_MAX; ++ return val_min + (val_max - val_min)*val; ++ } ++#endif ++ ++ //! Return a random variable uniformely distributed between [0,val_max]. ++ /** ++ **/ ++ inline double rand(const double val_max=1) { ++ return cimg::rand(0,val_max); ++ } ++ ++ //! Return a random variable following a gaussian distribution and a standard deviation of 1. ++ /** ++ **/ ++ inline double grand() { ++ double x1, w; ++ do { ++ const double x2 = cimg::rand(-1,1); ++ x1 = cimg::rand(-1,1); ++ w = x1*x1 + x2*x2; ++ } while (w<=0 || w>=1.0); ++ return x1*std::sqrt((-2*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) 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; ++ } ++ ++ //! Cut (i.e. clamp) value in specified interval. ++ template ++ inline T cut(const T& val, const t& val_min, const t& val_max) { ++ return valval_max?(T)val_max:val; ++ } ++ ++ //! Bitwise-rotate value on the left. ++ template ++ inline T rol(const T& a, const unsigned int n=1) { ++ return n?(T)((a<>((sizeof(T)<<3) - n))):a; ++ } ++ ++ inline float rol(const float a, const unsigned int n=1) { ++ return (float)rol((int)a,n); ++ } ++ ++ inline double rol(const double a, const unsigned int n=1) { ++ return (double)rol((cimg_long)a,n); ++ } ++ ++ inline double rol(const long double a, const unsigned int n=1) { ++ return (double)rol((cimg_long)a,n); ++ } ++ ++#ifdef cimg_use_half ++ inline half rol(const half a, const unsigned int n=1) { ++ return (half)rol((int)a,n); ++ } ++#endif ++ ++ //! Bitwise-rotate value on the right. ++ template ++ inline T ror(const T& a, const unsigned int n=1) { ++ return n?(T)((a>>n)|(a<<((sizeof(T)<<3) - n))):a; ++ } ++ ++ inline float ror(const float a, const unsigned int n=1) { ++ return (float)ror((int)a,n); ++ } ++ ++ inline double ror(const double a, const unsigned int n=1) { ++ return (double)ror((cimg_long)a,n); ++ } ++ ++ inline double ror(const long double a, const unsigned int n=1) { ++ return (double)ror((cimg_long)a,n); ++ } ++ ++#ifdef cimg_use_half ++ inline half ror(const half a, const unsigned int n=1) { ++ return (half)ror((int)a,n); ++ } ++#endif ++ ++ //! Return absolute value of a value. ++ template ++ inline T abs(const T& a) { ++ return a>=0?a:-a; ++ } ++ inline bool abs(const bool a) { ++ return a; ++ } ++ inline int abs(const unsigned char a) { ++ return (int)a; ++ } ++ inline int abs(const unsigned short a) { ++ return (int)a; ++ } ++ inline int abs(const unsigned int a) { ++ return (int)a; ++ } ++ inline int abs(const int a) { ++ return std::abs(a); ++ } ++ inline cimg_int64 abs(const cimg_uint64 a) { ++ return (cimg_int64)a; ++ } ++ inline double abs(const double a) { ++ return std::fabs(a); ++ } ++ inline float abs(const float a) { ++ return (float)std::fabs((double)a); ++ } ++ ++ //! Return square of a value. ++ template ++ inline T sqr(const T& val) { ++ return val*val; ++ } ++ ++ //! Return 1 + log_10(x) of a value \c x. ++ inline int xln(const int x) { ++ return x>0?(int)(1 + std::log10((double)x)):1; ++ } ++ ++ //! Return the minimum between three values. ++ template ++ inline t min(const t& a, const t& b, const t& c) { ++ return std::min(std::min(a,b),c); ++ } ++ ++ //! Return the minimum between four values. ++ template ++ inline t min(const t& a, const t& b, const t& c, const t& d) { ++ return std::min(std::min(a,b),std::min(c,d)); ++ } ++ ++ //! Return the maximum between three values. ++ template ++ inline t max(const t& a, const t& b, const t& c) { ++ return std::max(std::max(a,b),c); ++ } ++ ++ //! Return the maximum between four values. ++ template ++ inline t max(const t& a, const t& b, const t& c, const t& d) { ++ return std::max(std::max(a,b),std::max(c,d)); ++ } ++ ++ //! Return the sign of a value. ++ template ++ inline T sign(const T& x) { ++ return (T)(x<0?-1:x>0); ++ } ++ ++ //! Return the nearest power of 2 higher than given value. ++ template ++ inline cimg_ulong nearest_pow2(const T& x) { ++ cimg_ulong i = 1; ++ while (x>i) i<<=1; ++ return i; ++ } ++ ++ //! Return the sinc of a given value. ++ inline double sinc(const double x) { ++ return x?std::sin(x)/x:1; ++ } ++ ++ //! Return the modulo of a value. ++ /** ++ \param x Input value. ++ \param m Modulo value. ++ \note This modulo function accepts negative and floating-points modulo numbers, as well as variables of any type. ++ **/ ++ template ++ inline T mod(const T& x, const T& m) { ++ const double dx = (double)x, dm = (double)m; ++ return (T)(dx - dm * std::floor(dx / dm)); ++ } ++ inline int mod(const bool x, const bool m) { ++ return m?(x?1:0):0; ++ } ++ inline int mod(const unsigned char x, const unsigned char m) { ++ return x%m; ++ } ++ inline int mod(const char x, const char m) { ++#if defined(CHAR_MAX) && CHAR_MAX==255 ++ return x%m; ++#else ++ return x>=0?x%m:(x%m?m + x%m:0); ++#endif ++ } ++ inline int mod(const unsigned short x, const unsigned short m) { ++ return x%m; ++ } ++ inline int mod(const short x, const short m) { ++ return x>=0?x%m:(x%m?m + x%m:0); ++ } ++ inline int mod(const unsigned int x, const unsigned int m) { ++ return (int)(x%m); ++ } ++ inline int mod(const int x, const int m) { ++ return x>=0?x%m:(x%m?m + x%m:0); ++ } ++ inline cimg_int64 mod(const cimg_uint64 x, const cimg_uint64 m) { ++ return x%m; ++ } ++ inline cimg_int64 mod(const cimg_int64 x, const cimg_int64 m) { ++ return x>=0?x%m:(x%m?m + x%m:0); ++ } ++ ++ //! Return the min-mod of two values. ++ /** ++ \note 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 ++ inline T round(const T& x) { ++ return (T)std::floor((_cimg_Tfloat)x + 0.5f); ++ } ++ ++ //! Return rounded value. ++ /** ++ \param x Value to be rounded. ++ \param y Rounding precision. ++ \param rounding_type Type of rounding operation (\c 0 = nearest, \c -1 = backward, \c 1 = forward). ++ \return Rounded value, having the same type as input value \c x. ++ **/ ++ template ++ inline T round(const T& x, const double y, const int rounding_type=0) { ++ if (y<=0) return x; ++ if (y==1) switch (rounding_type) { ++ case 0 : return round(x); ++ case 1 : return (T)std::ceil((_cimg_Tfloat)x); ++ default : return (T)std::floor((_cimg_Tfloat)x); ++ } ++ const double sx = (double)x/y, floor = std::floor(sx), delta = sx - floor; ++ return (T)(y*(rounding_type<0?floor:rounding_type>0?std::ceil(sx):delta<0.5?floor:std::ceil(sx))); ++ } ++ ++ //! Return x^(1/3). ++ template ++ inline double cbrt(const T& x) { ++#if cimg_use_cpp11==1 ++ return std::cbrt(x); ++#else ++ return x>=0?std::pow((double)x,1.0/3):-std::pow(-(double)x,1.0/3); ++#endif ++ } ++ ++ // Code to compute fast median from 2,3,5,7,9,13,25 and 49 values. ++ // (contribution by RawTherapee: http://rawtherapee.com/). ++ template ++ inline T median(T val0, T val1) { ++ return (val0 + val1)/2; ++ } ++ ++ template ++ inline T median(T val0, T val1, T val2) { ++ return std::max(std::min(val0,val1),std::min(val2,std::max(val0,val1))); ++ } ++ ++ template ++ inline T median(T val0, T val1, T val2, T val3, T val4) { ++ T tmp = std::min(val0,val1); ++ val1 = std::max(val0,val1); val0 = tmp; tmp = std::min(val3,val4); val4 = std::max(val3,val4); ++ val3 = std::max(val0,tmp); val1 = std::min(val1,val4); tmp = std::min(val1,val2); val2 = std::max(val1,val2); ++ val1 = tmp; tmp = std::min(val2,val3); ++ return std::max(val1,tmp); ++ } ++ ++ template ++ inline T median(T val0, T val1, T val2, T val3, T val4, T val5, T val6) { ++ T tmp = std::min(val0,val5); ++ val5 = std::max(val0,val5); val0 = tmp; tmp = std::min(val0,val3); val3 = std::max(val0,val3); val0 = tmp; ++ tmp = std::min(val1,val6); val6 = std::max(val1,val6); val1 = tmp; tmp = std::min(val2,val4); ++ val4 = std::max(val2,val4); val2 = tmp; val1 = std::max(val0,val1); tmp = std::min(val3,val5); ++ val5 = std::max(val3,val5); val3 = tmp; tmp = std::min(val2,val6); val6 = std::max(val2,val6); ++ val3 = std::max(tmp,val3); val3 = std::min(val3,val6); tmp = std::min(val4,val5); val4 = std::max(val1,tmp); ++ tmp = std::min(val1,tmp); val3 = std::max(tmp,val3); ++ return std::min(val3,val4); ++ } ++ ++ template ++ inline T median(T val0, T val1, T val2, T val3, T val4, T val5, T val6, T val7, T val8) { ++ T tmp = std::min(val1,val2); ++ val2 = std::max(val1,val2); val1 = tmp; tmp = std::min(val4,val5); ++ val5 = std::max(val4,val5); val4 = tmp; tmp = std::min(val7,val8); ++ val8 = std::max(val7,val8); val7 = tmp; tmp = std::min(val0,val1); ++ val1 = std::max(val0,val1); val0 = tmp; tmp = std::min(val3,val4); ++ val4 = std::max(val3,val4); val3 = tmp; tmp = std::min(val6,val7); ++ val7 = std::max(val6,val7); val6 = tmp; tmp = std::min(val1,val2); ++ val2 = std::max(val1,val2); val1 = tmp; tmp = std::min(val4,val5); ++ val5 = std::max(val4,val5); val4 = tmp; tmp = std::min(val7,val8); ++ val8 = std::max(val7,val8); val3 = std::max(val0,val3); val5 = std::min(val5,val8); ++ val7 = std::max(val4,tmp); tmp = std::min(val4,tmp); val6 = std::max(val3,val6); ++ val4 = std::max(val1,tmp); val2 = std::min(val2,val5); val4 = std::min(val4,val7); ++ tmp = std::min(val4,val2); val2 = std::max(val4,val2); val4 = std::max(val6,tmp); ++ return std::min(val4,val2); ++ } ++ ++ template ++ inline T median(T val0, T val1, T val2, T val3, T val4, T val5, T val6, T val7, T val8, T val9, T val10, T val11, ++ T val12) { ++ T tmp = std::min(val1,val7); ++ val7 = std::max(val1,val7); val1 = tmp; tmp = std::min(val9,val11); val11 = std::max(val9,val11); val9 = tmp; ++ tmp = std::min(val3,val4); val4 = std::max(val3,val4); val3 = tmp; tmp = std::min(val5,val8); ++ val8 = std::max(val5,val8); val5 = tmp; tmp = std::min(val0,val12); val12 = std::max(val0,val12); ++ val0 = tmp; tmp = std::min(val2,val6); val6 = std::max(val2,val6); val2 = tmp; tmp = std::min(val0,val1); ++ val1 = std::max(val0,val1); val0 = tmp; tmp = std::min(val2,val3); val3 = std::max(val2,val3); val2 = tmp; ++ tmp = std::min(val4,val6); val6 = std::max(val4,val6); val4 = tmp; tmp = std::min(val8,val11); ++ val11 = std::max(val8,val11); val8 = tmp; tmp = std::min(val7,val12); val12 = std::max(val7,val12); val7 = tmp; ++ tmp = std::min(val5,val9); val9 = std::max(val5,val9); val5 = tmp; tmp = std::min(val0,val2); ++ val2 = std::max(val0,val2); val0 = tmp; tmp = std::min(val3,val7); val7 = std::max(val3,val7); val3 = tmp; ++ tmp = std::min(val10,val11); val11 = std::max(val10,val11); val10 = tmp; tmp = std::min(val1,val4); ++ val4 = std::max(val1,val4); val1 = tmp; tmp = std::min(val6,val12); val12 = std::max(val6,val12); val6 = tmp; ++ tmp = std::min(val7,val8); val8 = std::max(val7,val8); val7 = tmp; val11 = std::min(val11,val12); ++ tmp = std::min(val4,val9); val9 = std::max(val4,val9); val4 = tmp; tmp = std::min(val6,val10); ++ val10 = std::max(val6,val10); val6 = tmp; tmp = std::min(val3,val4); val4 = std::max(val3,val4); val3 = tmp; ++ tmp = std::min(val5,val6); val6 = std::max(val5,val6); val5 = tmp; val8 = std::min(val8,val9); ++ val10 = std::min(val10,val11); tmp = std::min(val1,val7); val7 = std::max(val1,val7); val1 = tmp; ++ tmp = std::min(val2,val6); val6 = std::max(val2,val6); val2 = tmp; val3 = std::max(val1,val3); ++ tmp = std::min(val4,val7); val7 = std::max(val4,val7); val4 = tmp; val8 = std::min(val8,val10); ++ val5 = std::max(val0,val5); val5 = std::max(val2,val5); tmp = std::min(val6,val8); val8 = std::max(val6,val8); ++ val5 = std::max(val3,val5); val7 = std::min(val7,val8); val6 = std::max(val4,tmp); tmp = std::min(val4,tmp); ++ val5 = std::max(tmp,val5); val6 = std::min(val6,val7); ++ return std::max(val5,val6); ++ } ++ ++ template ++ inline T median(T val0, T val1, T val2, T val3, T val4, ++ T val5, T val6, T val7, T val8, T val9, ++ T val10, T val11, T val12, T val13, T val14, ++ T val15, T val16, T val17, T val18, T val19, ++ T val20, T val21, T val22, T val23, T val24) { ++ T tmp = std::min(val0,val1); ++ val1 = std::max(val0,val1); val0 = tmp; tmp = std::min(val3,val4); val4 = std::max(val3,val4); ++ val3 = tmp; tmp = std::min(val2,val4); val4 = std::max(val2,val4); val2 = std::min(tmp,val3); ++ val3 = std::max(tmp,val3); tmp = std::min(val6,val7); val7 = std::max(val6,val7); val6 = tmp; ++ tmp = std::min(val5,val7); val7 = std::max(val5,val7); val5 = std::min(tmp,val6); val6 = std::max(tmp,val6); ++ tmp = std::min(val9,val10); val10 = std::max(val9,val10); val9 = tmp; tmp = std::min(val8,val10); ++ val10 = std::max(val8,val10); val8 = std::min(tmp,val9); val9 = std::max(tmp,val9); ++ tmp = std::min(val12,val13); val13 = std::max(val12,val13); val12 = tmp; tmp = std::min(val11,val13); ++ val13 = std::max(val11,val13); val11 = std::min(tmp,val12); val12 = std::max(tmp,val12); ++ tmp = std::min(val15,val16); val16 = std::max(val15,val16); val15 = tmp; tmp = std::min(val14,val16); ++ val16 = std::max(val14,val16); val14 = std::min(tmp,val15); val15 = std::max(tmp,val15); ++ tmp = std::min(val18,val19); val19 = std::max(val18,val19); val18 = tmp; tmp = std::min(val17,val19); ++ val19 = std::max(val17,val19); val17 = std::min(tmp,val18); val18 = std::max(tmp,val18); ++ tmp = std::min(val21,val22); val22 = std::max(val21,val22); val21 = tmp; tmp = std::min(val20,val22); ++ val22 = std::max(val20,val22); val20 = std::min(tmp,val21); val21 = std::max(tmp,val21); ++ tmp = std::min(val23,val24); val24 = std::max(val23,val24); val23 = tmp; tmp = std::min(val2,val5); ++ val5 = std::max(val2,val5); val2 = tmp; tmp = std::min(val3,val6); val6 = std::max(val3,val6); val3 = tmp; ++ tmp = std::min(val0,val6); val6 = std::max(val0,val6); val0 = std::min(tmp,val3); val3 = std::max(tmp,val3); ++ tmp = std::min(val4,val7); val7 = std::max(val4,val7); val4 = tmp; tmp = std::min(val1,val7); ++ val7 = std::max(val1,val7); val1 = std::min(tmp,val4); val4 = std::max(tmp,val4); tmp = std::min(val11,val14); ++ val14 = std::max(val11,val14); val11 = tmp; tmp = std::min(val8,val14); val14 = std::max(val8,val14); ++ val8 = std::min(tmp,val11); val11 = std::max(tmp,val11); tmp = std::min(val12,val15); ++ val15 = std::max(val12,val15); val12 = tmp; tmp = std::min(val9,val15); val15 = std::max(val9,val15); ++ val9 = std::min(tmp,val12); val12 = std::max(tmp,val12); tmp = std::min(val13,val16); ++ val16 = std::max(val13,val16); val13 = tmp; tmp = std::min(val10,val16); val16 = std::max(val10,val16); ++ val10 = std::min(tmp,val13); val13 = std::max(tmp,val13); tmp = std::min(val20,val23); ++ val23 = std::max(val20,val23); val20 = tmp; tmp = std::min(val17,val23); val23 = std::max(val17,val23); ++ val17 = std::min(tmp,val20); val20 = std::max(tmp,val20); tmp = std::min(val21,val24); ++ val24 = std::max(val21,val24); val21 = tmp; tmp = std::min(val18,val24); val24 = std::max(val18,val24); ++ val18 = std::min(tmp,val21); val21 = std::max(tmp,val21); tmp = std::min(val19,val22); ++ val22 = std::max(val19,val22); val19 = tmp; val17 = std::max(val8,val17); tmp = std::min(val9,val18); ++ val18 = std::max(val9,val18); val9 = tmp; tmp = std::min(val0,val18); val18 = std::max(val0,val18); ++ val9 = std::max(tmp,val9); tmp = std::min(val10,val19); val19 = std::max(val10,val19); val10 = tmp; ++ tmp = std::min(val1,val19); val19 = std::max(val1,val19); val1 = std::min(tmp,val10); ++ val10 = std::max(tmp,val10); tmp = std::min(val11,val20); val20 = std::max(val11,val20); val11 = tmp; ++ tmp = std::min(val2,val20); val20 = std::max(val2,val20); val11 = std::max(tmp,val11); ++ tmp = std::min(val12,val21); val21 = std::max(val12,val21); val12 = tmp; tmp = std::min(val3,val21); ++ val21 = std::max(val3,val21); val3 = std::min(tmp,val12); val12 = std::max(tmp,val12); ++ tmp = std::min(val13,val22); val22 = std::max(val13,val22); val4 = std::min(val4,val22); ++ val13 = std::max(val4,tmp); tmp = std::min(val4,tmp); val4 = tmp; tmp = std::min(val14,val23); ++ val23 = std::max(val14,val23); val14 = tmp; tmp = std::min(val5,val23); val23 = std::max(val5,val23); ++ val5 = std::min(tmp,val14); val14 = std::max(tmp,val14); tmp = std::min(val15,val24); ++ val24 = std::max(val15,val24); val15 = tmp; val6 = std::min(val6,val24); tmp = std::min(val6,val15); ++ val15 = std::max(val6,val15); val6 = tmp; tmp = std::min(val7,val16); val7 = std::min(tmp,val19); ++ tmp = std::min(val13,val21); val15 = std::min(val15,val23); tmp = std::min(val7,tmp); ++ val7 = std::min(tmp,val15); val9 = std::max(val1,val9); val11 = std::max(val3,val11); ++ val17 = std::max(val5,val17); val17 = std::max(val11,val17); val17 = std::max(val9,val17); ++ tmp = std::min(val4,val10); val10 = std::max(val4,val10); val4 = tmp; tmp = std::min(val6,val12); ++ val12 = std::max(val6,val12); val6 = tmp; tmp = std::min(val7,val14); val14 = std::max(val7,val14); ++ val7 = tmp; tmp = std::min(val4,val6); val6 = std::max(val4,val6); val7 = std::max(tmp,val7); ++ tmp = std::min(val12,val14); val14 = std::max(val12,val14); val12 = tmp; val10 = std::min(val10,val14); ++ tmp = std::min(val6,val7); val7 = std::max(val6,val7); val6 = tmp; tmp = std::min(val10,val12); ++ val12 = std::max(val10,val12); val10 = std::max(val6,tmp); tmp = std::min(val6,tmp); ++ val17 = std::max(tmp,val17); tmp = std::min(val12,val17); val17 = std::max(val12,val17); val12 = tmp; ++ val7 = std::min(val7,val17); tmp = std::min(val7,val10); val10 = std::max(val7,val10); val7 = tmp; ++ tmp = std::min(val12,val18); val18 = std::max(val12,val18); val12 = std::max(val7,tmp); ++ val10 = std::min(val10,val18); tmp = std::min(val12,val20); val20 = std::max(val12,val20); val12 = tmp; ++ tmp = std::min(val10,val20); ++ return std::max(tmp,val12); ++ } ++ ++ template ++ inline T median(T val0, T val1, T val2, T val3, T val4, T val5, T val6, ++ T val7, T val8, T val9, T val10, T val11, T val12, T val13, ++ T val14, T val15, T val16, T val17, T val18, T val19, T val20, ++ T val21, T val22, T val23, T val24, T val25, T val26, T val27, ++ T val28, T val29, T val30, T val31, T val32, T val33, T val34, ++ T val35, T val36, T val37, T val38, T val39, T val40, T val41, ++ T val42, T val43, T val44, T val45, T val46, T val47, T val48) { ++ T tmp = std::min(val0,val32); ++ val32 = std::max(val0,val32); val0 = tmp; tmp = std::min(val1,val33); val33 = std::max(val1,val33); val1 = tmp; ++ tmp = std::min(val2,val34); val34 = std::max(val2,val34); val2 = tmp; tmp = std::min(val3,val35); ++ val35 = std::max(val3,val35); val3 = tmp; tmp = std::min(val4,val36); val36 = std::max(val4,val36); val4 = tmp; ++ tmp = std::min(val5,val37); val37 = std::max(val5,val37); val5 = tmp; tmp = std::min(val6,val38); ++ val38 = std::max(val6,val38); val6 = tmp; tmp = std::min(val7,val39); val39 = std::max(val7,val39); val7 = tmp; ++ tmp = std::min(val8,val40); val40 = std::max(val8,val40); val8 = tmp; tmp = std::min(val9,val41); ++ val41 = std::max(val9,val41); val9 = tmp; tmp = std::min(val10,val42); val42 = std::max(val10,val42); ++ val10 = tmp; tmp = std::min(val11,val43); val43 = std::max(val11,val43); val11 = tmp; ++ tmp = std::min(val12,val44); val44 = std::max(val12,val44); val12 = tmp; tmp = std::min(val13,val45); ++ val45 = std::max(val13,val45); val13 = tmp; tmp = std::min(val14,val46); val46 = std::max(val14,val46); ++ val14 = tmp; tmp = std::min(val15,val47); val47 = std::max(val15,val47); val15 = tmp; ++ tmp = std::min(val16,val48); val48 = std::max(val16,val48); val16 = tmp; tmp = std::min(val0,val16); ++ val16 = std::max(val0,val16); val0 = tmp; tmp = std::min(val1,val17); val17 = std::max(val1,val17); ++ val1 = tmp; tmp = std::min(val2,val18); val18 = std::max(val2,val18); val2 = tmp; tmp = std::min(val3,val19); ++ val19 = std::max(val3,val19); val3 = tmp; tmp = std::min(val4,val20); val20 = std::max(val4,val20); val4 = tmp; ++ tmp = std::min(val5,val21); val21 = std::max(val5,val21); val5 = tmp; tmp = std::min(val6,val22); ++ val22 = std::max(val6,val22); val6 = tmp; tmp = std::min(val7,val23); val23 = std::max(val7,val23); val7 = tmp; ++ tmp = std::min(val8,val24); val24 = std::max(val8,val24); val8 = tmp; tmp = std::min(val9,val25); ++ val25 = std::max(val9,val25); val9 = tmp; tmp = std::min(val10,val26); val26 = std::max(val10,val26); ++ val10 = tmp; tmp = std::min(val11,val27); val27 = std::max(val11,val27); val11 = tmp; ++ tmp = std::min(val12,val28); val28 = std::max(val12,val28); val12 = tmp; tmp = std::min(val13,val29); ++ val29 = std::max(val13,val29); val13 = tmp; tmp = std::min(val14,val30); val30 = std::max(val14,val30); ++ val14 = tmp; tmp = std::min(val15,val31); val31 = std::max(val15,val31); val15 = tmp; ++ tmp = std::min(val32,val48); val48 = std::max(val32,val48); val32 = tmp; tmp = std::min(val16,val32); ++ val32 = std::max(val16,val32); val16 = tmp; tmp = std::min(val17,val33); val33 = std::max(val17,val33); ++ val17 = tmp; tmp = std::min(val18,val34); val34 = std::max(val18,val34); val18 = tmp; ++ tmp = std::min(val19,val35); val35 = std::max(val19,val35); val19 = tmp; tmp = std::min(val20,val36); ++ val36 = std::max(val20,val36); val20 = tmp; tmp = std::min(val21,val37); val37 = std::max(val21,val37); ++ val21 = tmp; tmp = std::min(val22,val38); val38 = std::max(val22,val38); val22 = tmp; ++ tmp = std::min(val23,val39); val39 = std::max(val23,val39); val23 = tmp; tmp = std::min(val24,val40); ++ val40 = std::max(val24,val40); val24 = tmp; tmp = std::min(val25,val41); val41 = std::max(val25,val41); ++ val25 = tmp; tmp = std::min(val26,val42); val42 = std::max(val26,val42); val26 = tmp; ++ tmp = std::min(val27,val43); val43 = std::max(val27,val43); val27 = tmp; tmp = std::min(val28,val44); ++ val44 = std::max(val28,val44); val28 = tmp; tmp = std::min(val29,val45); val45 = std::max(val29,val45); ++ val29 = tmp; tmp = std::min(val30,val46); val46 = std::max(val30,val46); val30 = tmp; ++ tmp = std::min(val31,val47); val47 = std::max(val31,val47); val31 = tmp; tmp = std::min(val0,val8); ++ val8 = std::max(val0,val8); val0 = tmp; tmp = std::min(val1,val9); val9 = std::max(val1,val9); val1 = tmp; ++ tmp = std::min(val2,val10); val10 = std::max(val2,val10); val2 = tmp; tmp = std::min(val3,val11); ++ val11 = std::max(val3,val11); val3 = tmp; tmp = std::min(val4,val12); val12 = std::max(val4,val12); val4 = tmp; ++ tmp = std::min(val5,val13); val13 = std::max(val5,val13); val5 = tmp; tmp = std::min(val6,val14); ++ val14 = std::max(val6,val14); val6 = tmp; tmp = std::min(val7,val15); val15 = std::max(val7,val15); val7 = tmp; ++ tmp = std::min(val16,val24); val24 = std::max(val16,val24); val16 = tmp; tmp = std::min(val17,val25); ++ val25 = std::max(val17,val25); val17 = tmp; tmp = std::min(val18,val26); val26 = std::max(val18,val26); ++ val18 = tmp; tmp = std::min(val19,val27); val27 = std::max(val19,val27); val19 = tmp; ++ tmp = std::min(val20,val28); val28 = std::max(val20,val28); val20 = tmp; tmp = std::min(val21,val29); ++ val29 = std::max(val21,val29); val21 = tmp; tmp = std::min(val22,val30); val30 = std::max(val22,val30); ++ val22 = tmp; tmp = std::min(val23,val31); val31 = std::max(val23,val31); val23 = tmp; ++ tmp = std::min(val32,val40); val40 = std::max(val32,val40); val32 = tmp; tmp = std::min(val33,val41); ++ val41 = std::max(val33,val41); val33 = tmp; tmp = std::min(val34,val42); val42 = std::max(val34,val42); ++ val34 = tmp; tmp = std::min(val35,val43); val43 = std::max(val35,val43); val35 = tmp; ++ tmp = std::min(val36,val44); val44 = std::max(val36,val44); val36 = tmp; tmp = std::min(val37,val45); ++ val45 = std::max(val37,val45); val37 = tmp; tmp = std::min(val38,val46); val46 = std::max(val38,val46); ++ val38 = tmp; tmp = std::min(val39,val47); val47 = std::max(val39,val47); val39 = tmp; ++ tmp = std::min(val8,val32); val32 = std::max(val8,val32); val8 = tmp; tmp = std::min(val9,val33); ++ val33 = std::max(val9,val33); val9 = tmp; tmp = std::min(val10,val34); val34 = std::max(val10,val34); ++ val10 = tmp; tmp = std::min(val11,val35); val35 = std::max(val11,val35); val11 = tmp; ++ tmp = std::min(val12,val36); val36 = std::max(val12,val36); val12 = tmp; tmp = std::min(val13,val37); ++ val37 = std::max(val13,val37); val13 = tmp; tmp = std::min(val14,val38); val38 = std::max(val14,val38); ++ val14 = tmp; tmp = std::min(val15,val39); val39 = std::max(val15,val39); val15 = tmp; ++ tmp = std::min(val24,val48); val48 = std::max(val24,val48); val24 = tmp; tmp = std::min(val8,val16); ++ val16 = std::max(val8,val16); val8 = tmp; tmp = std::min(val9,val17); val17 = std::max(val9,val17); ++ val9 = tmp; tmp = std::min(val10,val18); val18 = std::max(val10,val18); val10 = tmp; ++ tmp = std::min(val11,val19); val19 = std::max(val11,val19); val11 = tmp; tmp = std::min(val12,val20); ++ val20 = std::max(val12,val20); val12 = tmp; tmp = std::min(val13,val21); val21 = std::max(val13,val21); ++ val13 = tmp; tmp = std::min(val14,val22); val22 = std::max(val14,val22); val14 = tmp; ++ tmp = std::min(val15,val23); val23 = std::max(val15,val23); val15 = tmp; tmp = std::min(val24,val32); ++ val32 = std::max(val24,val32); val24 = tmp; tmp = std::min(val25,val33); val33 = std::max(val25,val33); ++ val25 = tmp; tmp = std::min(val26,val34); val34 = std::max(val26,val34); val26 = tmp; ++ tmp = std::min(val27,val35); val35 = std::max(val27,val35); val27 = tmp; tmp = std::min(val28,val36); ++ val36 = std::max(val28,val36); val28 = tmp; tmp = std::min(val29,val37); val37 = std::max(val29,val37); ++ val29 = tmp; tmp = std::min(val30,val38); val38 = std::max(val30,val38); val30 = tmp; ++ tmp = std::min(val31,val39); val39 = std::max(val31,val39); val31 = tmp; tmp = std::min(val40,val48); ++ val48 = std::max(val40,val48); val40 = tmp; tmp = std::min(val0,val4); val4 = std::max(val0,val4); ++ val0 = tmp; tmp = std::min(val1,val5); val5 = std::max(val1,val5); val1 = tmp; tmp = std::min(val2,val6); ++ val6 = std::max(val2,val6); val2 = tmp; tmp = std::min(val3,val7); val7 = std::max(val3,val7); val3 = tmp; ++ tmp = std::min(val8,val12); val12 = std::max(val8,val12); val8 = tmp; tmp = std::min(val9,val13); ++ val13 = std::max(val9,val13); val9 = tmp; tmp = std::min(val10,val14); val14 = std::max(val10,val14); ++ val10 = tmp; tmp = std::min(val11,val15); val15 = std::max(val11,val15); val11 = tmp; ++ tmp = std::min(val16,val20); val20 = std::max(val16,val20); val16 = tmp; tmp = std::min(val17,val21); ++ val21 = std::max(val17,val21); val17 = tmp; tmp = std::min(val18,val22); val22 = std::max(val18,val22); ++ val18 = tmp; tmp = std::min(val19,val23); val23 = std::max(val19,val23); val19 = tmp; ++ tmp = std::min(val24,val28); val28 = std::max(val24,val28); val24 = tmp; tmp = std::min(val25,val29); ++ val29 = std::max(val25,val29); val25 = tmp; tmp = std::min(val26,val30); val30 = std::max(val26,val30); ++ val26 = tmp; tmp = std::min(val27,val31); val31 = std::max(val27,val31); val27 = tmp; ++ tmp = std::min(val32,val36); val36 = std::max(val32,val36); val32 = tmp; tmp = std::min(val33,val37); ++ val37 = std::max(val33,val37); val33 = tmp; tmp = std::min(val34,val38); val38 = std::max(val34,val38); ++ val34 = tmp; tmp = std::min(val35,val39); val39 = std::max(val35,val39); val35 = tmp; ++ tmp = std::min(val40,val44); val44 = std::max(val40,val44); val40 = tmp; tmp = std::min(val41,val45); ++ val45 = std::max(val41,val45); val41 = tmp; tmp = std::min(val42,val46); val46 = std::max(val42,val46); ++ val42 = tmp; tmp = std::min(val43,val47); val47 = std::max(val43,val47); val43 = tmp; ++ tmp = std::min(val4,val32); val32 = std::max(val4,val32); val4 = tmp; tmp = std::min(val5,val33); ++ val33 = std::max(val5,val33); val5 = tmp; tmp = std::min(val6,val34); val34 = std::max(val6,val34); ++ val6 = tmp; tmp = std::min(val7,val35); val35 = std::max(val7,val35); val7 = tmp; ++ tmp = std::min(val12,val40); val40 = std::max(val12,val40); val12 = tmp; tmp = std::min(val13,val41); ++ val41 = std::max(val13,val41); val13 = tmp; tmp = std::min(val14,val42); val42 = std::max(val14,val42); ++ val14 = tmp; tmp = std::min(val15,val43); val43 = std::max(val15,val43); val15 = tmp; ++ tmp = std::min(val20,val48); val48 = std::max(val20,val48); val20 = tmp; tmp = std::min(val4,val16); ++ val16 = std::max(val4,val16); val4 = tmp; tmp = std::min(val5,val17); val17 = std::max(val5,val17); ++ val5 = tmp; tmp = std::min(val6,val18); val18 = std::max(val6,val18); val6 = tmp; ++ tmp = std::min(val7,val19); val19 = std::max(val7,val19); val7 = tmp; tmp = std::min(val12,val24); ++ val24 = std::max(val12,val24); val12 = tmp; tmp = std::min(val13,val25); val25 = std::max(val13,val25); ++ val13 = tmp; tmp = std::min(val14,val26); val26 = std::max(val14,val26); val14 = tmp; ++ tmp = std::min(val15,val27); val27 = std::max(val15,val27); val15 = tmp; tmp = std::min(val20,val32); ++ val32 = std::max(val20,val32); val20 = tmp; tmp = std::min(val21,val33); val33 = std::max(val21,val33); ++ val21 = tmp; tmp = std::min(val22,val34); val34 = std::max(val22,val34); val22 = tmp; ++ tmp = std::min(val23,val35); val35 = std::max(val23,val35); val23 = tmp; tmp = std::min(val28,val40); ++ val40 = std::max(val28,val40); val28 = tmp; tmp = std::min(val29,val41); val41 = std::max(val29,val41); ++ val29 = tmp; tmp = std::min(val30,val42); val42 = std::max(val30,val42); val30 = tmp; ++ tmp = std::min(val31,val43); val43 = std::max(val31,val43); val31 = tmp; tmp = std::min(val36,val48); ++ val48 = std::max(val36,val48); val36 = tmp; tmp = std::min(val4,val8); val8 = std::max(val4,val8); ++ val4 = tmp; tmp = std::min(val5,val9); val9 = std::max(val5,val9); val5 = tmp; tmp = std::min(val6,val10); ++ val10 = std::max(val6,val10); val6 = tmp; tmp = std::min(val7,val11); val11 = std::max(val7,val11); val7 = tmp; ++ tmp = std::min(val12,val16); val16 = std::max(val12,val16); val12 = tmp; tmp = std::min(val13,val17); ++ val17 = std::max(val13,val17); val13 = tmp; tmp = std::min(val14,val18); val18 = std::max(val14,val18); ++ val14 = tmp; tmp = std::min(val15,val19); val19 = std::max(val15,val19); val15 = tmp; ++ tmp = std::min(val20,val24); val24 = std::max(val20,val24); val20 = tmp; tmp = std::min(val21,val25); ++ val25 = std::max(val21,val25); val21 = tmp; tmp = std::min(val22,val26); val26 = std::max(val22,val26); ++ val22 = tmp; tmp = std::min(val23,val27); val27 = std::max(val23,val27); val23 = tmp; ++ tmp = std::min(val28,val32); val32 = std::max(val28,val32); val28 = tmp; tmp = std::min(val29,val33); ++ val33 = std::max(val29,val33); val29 = tmp; tmp = std::min(val30,val34); val34 = std::max(val30,val34); ++ val30 = tmp; tmp = std::min(val31,val35); val35 = std::max(val31,val35); val31 = tmp; ++ tmp = std::min(val36,val40); val40 = std::max(val36,val40); val36 = tmp; tmp = std::min(val37,val41); ++ val41 = std::max(val37,val41); val37 = tmp; tmp = std::min(val38,val42); val42 = std::max(val38,val42); ++ val38 = tmp; tmp = std::min(val39,val43); val43 = std::max(val39,val43); val39 = tmp; ++ tmp = std::min(val44,val48); val48 = std::max(val44,val48); val44 = tmp; tmp = std::min(val0,val2); ++ val2 = std::max(val0,val2); val0 = tmp; tmp = std::min(val1,val3); val3 = std::max(val1,val3); val1 = tmp; ++ tmp = std::min(val4,val6); val6 = std::max(val4,val6); val4 = tmp; tmp = std::min(val5,val7); ++ val7 = std::max(val5,val7); val5 = tmp; tmp = std::min(val8,val10); val10 = std::max(val8,val10); val8 = tmp; ++ tmp = std::min(val9,val11); val11 = std::max(val9,val11); val9 = tmp; tmp = std::min(val12,val14); ++ val14 = std::max(val12,val14); val12 = tmp; tmp = std::min(val13,val15); val15 = std::max(val13,val15); ++ val13 = tmp; tmp = std::min(val16,val18); val18 = std::max(val16,val18); val16 = tmp; ++ tmp = std::min(val17,val19); val19 = std::max(val17,val19); val17 = tmp; tmp = std::min(val20,val22); ++ val22 = std::max(val20,val22); val20 = tmp; tmp = std::min(val21,val23); val23 = std::max(val21,val23); ++ val21 = tmp; tmp = std::min(val24,val26); val26 = std::max(val24,val26); val24 = tmp; ++ tmp = std::min(val25,val27); val27 = std::max(val25,val27); val25 = tmp; tmp = std::min(val28,val30); ++ val30 = std::max(val28,val30); val28 = tmp; tmp = std::min(val29,val31); val31 = std::max(val29,val31); ++ val29 = tmp; tmp = std::min(val32,val34); val34 = std::max(val32,val34); val32 = tmp; ++ tmp = std::min(val33,val35); val35 = std::max(val33,val35); val33 = tmp; tmp = std::min(val36,val38); ++ val38 = std::max(val36,val38); val36 = tmp; tmp = std::min(val37,val39); val39 = std::max(val37,val39); ++ val37 = tmp; tmp = std::min(val40,val42); val42 = std::max(val40,val42); val40 = tmp; ++ tmp = std::min(val41,val43); val43 = std::max(val41,val43); val41 = tmp; tmp = std::min(val44,val46); ++ val46 = std::max(val44,val46); val44 = tmp; tmp = std::min(val45,val47); val47 = std::max(val45,val47); ++ val45 = tmp; tmp = std::min(val2,val32); val32 = std::max(val2,val32); val2 = tmp; tmp = std::min(val3,val33); ++ val33 = std::max(val3,val33); val3 = tmp; tmp = std::min(val6,val36); val36 = std::max(val6,val36); val6 = tmp; ++ tmp = std::min(val7,val37); val37 = std::max(val7,val37); val7 = tmp; tmp = std::min(val10,val40); ++ val40 = std::max(val10,val40); val10 = tmp; tmp = std::min(val11,val41); val41 = std::max(val11,val41); ++ val11 = tmp; tmp = std::min(val14,val44); val44 = std::max(val14,val44); val14 = tmp; ++ tmp = std::min(val15,val45); val45 = std::max(val15,val45); val15 = tmp; tmp = std::min(val18,val48); ++ val48 = std::max(val18,val48); val18 = tmp; tmp = std::min(val2,val16); val16 = std::max(val2,val16); ++ val2 = tmp; tmp = std::min(val3,val17); val17 = std::max(val3,val17); val3 = tmp; ++ tmp = std::min(val6,val20); val20 = std::max(val6,val20); val6 = tmp; tmp = std::min(val7,val21); ++ val21 = std::max(val7,val21); val7 = tmp; tmp = std::min(val10,val24); val24 = std::max(val10,val24); ++ val10 = tmp; tmp = std::min(val11,val25); val25 = std::max(val11,val25); val11 = tmp; ++ tmp = std::min(val14,val28); val28 = std::max(val14,val28); val14 = tmp; tmp = std::min(val15,val29); ++ val29 = std::max(val15,val29); val15 = tmp; tmp = std::min(val18,val32); val32 = std::max(val18,val32); ++ val18 = tmp; tmp = std::min(val19,val33); val33 = std::max(val19,val33); val19 = tmp; ++ tmp = std::min(val22,val36); val36 = std::max(val22,val36); val22 = tmp; tmp = std::min(val23,val37); ++ val37 = std::max(val23,val37); val23 = tmp; tmp = std::min(val26,val40); val40 = std::max(val26,val40); ++ val26 = tmp; tmp = std::min(val27,val41); val41 = std::max(val27,val41); val27 = tmp; ++ tmp = std::min(val30,val44); val44 = std::max(val30,val44); val30 = tmp; tmp = std::min(val31,val45); ++ val45 = std::max(val31,val45); val31 = tmp; tmp = std::min(val34,val48); val48 = std::max(val34,val48); ++ val34 = tmp; tmp = std::min(val2,val8); val8 = std::max(val2,val8); val2 = tmp; tmp = std::min(val3,val9); ++ val9 = std::max(val3,val9); val3 = tmp; tmp = std::min(val6,val12); val12 = std::max(val6,val12); val6 = tmp; ++ tmp = std::min(val7,val13); val13 = std::max(val7,val13); val7 = tmp; tmp = std::min(val10,val16); ++ val16 = std::max(val10,val16); val10 = tmp; tmp = std::min(val11,val17); val17 = std::max(val11,val17); ++ val11 = tmp; tmp = std::min(val14,val20); val20 = std::max(val14,val20); val14 = tmp; ++ tmp = std::min(val15,val21); val21 = std::max(val15,val21); val15 = tmp; tmp = std::min(val18,val24); ++ val24 = std::max(val18,val24); val18 = tmp; tmp = std::min(val19,val25); val25 = std::max(val19,val25); ++ val19 = tmp; tmp = std::min(val22,val28); val28 = std::max(val22,val28); val22 = tmp; ++ tmp = std::min(val23,val29); val29 = std::max(val23,val29); val23 = tmp; tmp = std::min(val26,val32); ++ val32 = std::max(val26,val32); val26 = tmp; tmp = std::min(val27,val33); val33 = std::max(val27,val33); ++ val27 = tmp; tmp = std::min(val30,val36); val36 = std::max(val30,val36); val30 = tmp; ++ tmp = std::min(val31,val37); val37 = std::max(val31,val37); val31 = tmp; tmp = std::min(val34,val40); ++ val40 = std::max(val34,val40); val34 = tmp; tmp = std::min(val35,val41); val41 = std::max(val35,val41); ++ val35 = tmp; tmp = std::min(val38,val44); val44 = std::max(val38,val44); val38 = tmp; ++ tmp = std::min(val39,val45); val45 = std::max(val39,val45); val39 = tmp; tmp = std::min(val42,val48); ++ val48 = std::max(val42,val48); val42 = tmp; tmp = std::min(val2,val4); val4 = std::max(val2,val4); ++ val2 = tmp; tmp = std::min(val3,val5); val5 = std::max(val3,val5); val3 = tmp; tmp = std::min(val6,val8); ++ val8 = std::max(val6,val8); val6 = tmp; tmp = std::min(val7,val9); val9 = std::max(val7,val9); val7 = tmp; ++ tmp = std::min(val10,val12); val12 = std::max(val10,val12); val10 = tmp; tmp = std::min(val11,val13); ++ val13 = std::max(val11,val13); val11 = tmp; tmp = std::min(val14,val16); val16 = std::max(val14,val16); ++ val14 = tmp; tmp = std::min(val15,val17); val17 = std::max(val15,val17); val15 = tmp; ++ tmp = std::min(val18,val20); val20 = std::max(val18,val20); val18 = tmp; tmp = std::min(val19,val21); ++ val21 = std::max(val19,val21); val19 = tmp; tmp = std::min(val22,val24); val24 = std::max(val22,val24); ++ val22 = tmp; tmp = std::min(val23,val25); val25 = std::max(val23,val25); val23 = tmp; ++ tmp = std::min(val26,val28); val28 = std::max(val26,val28); val26 = tmp; tmp = std::min(val27,val29); ++ val29 = std::max(val27,val29); val27 = tmp; tmp = std::min(val30,val32); val32 = std::max(val30,val32); ++ val30 = tmp; tmp = std::min(val31,val33); val33 = std::max(val31,val33); val31 = tmp; ++ tmp = std::min(val34,val36); val36 = std::max(val34,val36); val34 = tmp; tmp = std::min(val35,val37); ++ val37 = std::max(val35,val37); val35 = tmp; tmp = std::min(val38,val40); val40 = std::max(val38,val40); ++ val38 = tmp; tmp = std::min(val39,val41); val41 = std::max(val39,val41); val39 = tmp; ++ tmp = std::min(val42,val44); val44 = std::max(val42,val44); val42 = tmp; tmp = std::min(val43,val45); ++ val45 = std::max(val43,val45); val43 = tmp; tmp = std::min(val46,val48); val48 = std::max(val46,val48); ++ val46 = tmp; val1 = std::max(val0,val1); val3 = std::max(val2,val3); val5 = std::max(val4,val5); ++ val7 = std::max(val6,val7); val9 = std::max(val8,val9); val11 = std::max(val10,val11); ++ val13 = std::max(val12,val13); val15 = std::max(val14,val15); val17 = std::max(val16,val17); ++ val19 = std::max(val18,val19); val21 = std::max(val20,val21); val23 = std::max(val22,val23); ++ val24 = std::min(val24,val25); val26 = std::min(val26,val27); val28 = std::min(val28,val29); ++ val30 = std::min(val30,val31); val32 = std::min(val32,val33); val34 = std::min(val34,val35); ++ val36 = std::min(val36,val37); val38 = std::min(val38,val39); val40 = std::min(val40,val41); ++ val42 = std::min(val42,val43); val44 = std::min(val44,val45); val46 = std::min(val46,val47); ++ val32 = std::max(val1,val32); val34 = std::max(val3,val34); val36 = std::max(val5,val36); ++ val38 = std::max(val7,val38); val9 = std::min(val9,val40); val11 = std::min(val11,val42); ++ val13 = std::min(val13,val44); val15 = std::min(val15,val46); val17 = std::min(val17,val48); ++ val24 = std::max(val9,val24); val26 = std::max(val11,val26); val28 = std::max(val13,val28); ++ val30 = std::max(val15,val30); val17 = std::min(val17,val32); val19 = std::min(val19,val34); ++ val21 = std::min(val21,val36); val23 = std::min(val23,val38); val24 = std::max(val17,val24); ++ val26 = std::max(val19,val26); val21 = std::min(val21,val28); val23 = std::min(val23,val30); ++ val24 = std::max(val21,val24); val23 = std::min(val23,val26); ++ return std::max(val23,val24); ++ } ++ ++ //! Return sqrt(x^2 + y^2). ++ template ++ inline T hypot(const T x, const T y) { ++ return std::sqrt(x*x + y*y); ++ } ++ ++ template ++ inline T hypot(const T x, const T y, const T z) { ++ return std::sqrt(x*x + y*y + z*z); ++ } ++ ++ template ++ inline T _hypot(const T x, const T y) { // Slower but more precise version ++ T nx = cimg::abs(x), ny = cimg::abs(y), t; ++ if (nx0) { t/=nx; return nx*std::sqrt(1 + t*t); } ++ return 0; ++ } ++ ++ //! Return the factorial of n ++ inline double factorial(const int n) { ++ if (n<0) return cimg::type::nan(); ++ if (n<2) return 1; ++ double res = 2; ++ for (int i = 3; i<=n; ++i) res*=i; ++ return res; ++ } ++ ++ //! Return the number of permutations of k objects in a set of n objects. ++ inline double permutations(const int k, const int n, const bool with_order) { ++ if (n<0 || k<0) return cimg::type::nan(); ++ if (k>n) return 0; ++ double res = 1; ++ for (int i = n; i>=n - k + 1; --i) res*=i; ++ return with_order?res:res/cimg::factorial(k); ++ } ++ ++ inline double _fibonacci(int exp) { ++ double ++ base = (1 + std::sqrt(5.0))/2, ++ result = 1/std::sqrt(5.0); ++ while (exp) { ++ if (exp&1) result*=base; ++ exp>>=1; ++ base*=base; ++ } ++ return result; ++ } ++ ++ //! Calculate fibonacci number. ++ // (Precise up to n = 78, less precise for n>78). ++ inline double fibonacci(const int n) { ++ if (n<0) return cimg::type::nan(); ++ if (n<3) return 1; ++ if (n<11) { ++ cimg_uint64 fn1 = 1, fn2 = 1, fn = 0; ++ for (int i = 3; i<=n; ++i) { fn = fn1 + fn2; fn2 = fn1; fn1 = fn; } ++ return (double)fn; ++ } ++ if (n<75) // precise up to n = 74, faster than the integer calculation above for n>10 ++ return (double)((cimg_uint64)(_fibonacci(n) + 0.5)); ++ ++ if (n<94) { // precise up to n = 78, less precise for n>78 up to n = 93, overflows for n>93 ++ cimg_uint64 ++ fn1 = (cimg_uint64)1304969544928657ULL, ++ fn2 = (cimg_uint64)806515533049393ULL, ++ fn = 0; ++ for (int i = 75; i<=n; ++i) { fn = fn1 + fn2; fn2 = fn1; fn1 = fn; } ++ return (double)fn; ++ } ++ return _fibonacci(n); // Not precise, but better than the wrong overflowing calculation ++ } ++ ++ //! Calculate greatest common divisor. ++ inline long gcd(long a, long b) { ++ while (a) { const long c = a; a = b%a; b = c; } ++ return b; ++ } ++ ++ //! Convert ascii character to lower case. ++ inline char lowercase(const char x) { ++ return (char)((x<'A'||x>'Z')?x:x - 'A' + 'a'); ++ } ++ inline double lowercase(const double x) { ++ return (double)((x<'A'||x>'Z')?x:x - 'A' + 'a'); ++ } ++ ++ //! Convert C-string to lower case. ++ inline void lowercase(char *const str) { ++ if (str) for (char *ptr = str; *ptr; ++ptr) *ptr = lowercase(*ptr); ++ } ++ ++ //! Convert ascii character to upper case. ++ inline char uppercase(const char x) { ++ return (char)((x<'a'||x>'z')?x:x - 'a' + 'A'); ++ } ++ ++ inline double uppercase(const double x) { ++ return (double)((x<'a'||x>'z')?x:x - 'a' + 'A'); ++ } ++ ++ //! Convert C-string to upper case. ++ inline void uppercase(char *const str) { ++ if (str) for (char *ptr = str; *ptr; ++ptr) *ptr = uppercase(*ptr); ++ } ++ ++ //! Read value in a C-string. ++ /** ++ \param str C-string containing the float value to read. ++ \return Read value. ++ \note Same as std::atof() extended to manage the retrieval of fractions from C-strings, ++ as in "1/2". ++ **/ ++ inline double atof(const char *const str) { ++ double x = 0, y = 1; ++ return str && cimg_sscanf(str,"%lf/%lf",&x,&y)>0?x/y:0; ++ } ++ ++ //! Compare the first \p l characters of two C-strings, ignoring the case. ++ /** ++ \param str1 C-string. ++ \param str2 C-string. ++ \param l Number of characters to compare. ++ \return \c 0 if the two strings are equal, something else otherwise. ++ \note This function has to be defined since it is not provided by all C++-compilers (not ANSI). ++ **/ ++ inline int strncasecmp(const char *const str1, const char *const str2, const int l) { ++ if (!l) return 0; ++ if (!str1) return str2?-1:0; ++ const char *nstr1 = str1, *nstr2 = str2; ++ int k, diff = 0; for (k = 0; kp && str[q]==delimiter; ) { --q; if (!is_iterative) break; } ++ } ++ const int n = q - p + 1; ++ if (n!=l) { std::memmove(str,str + p,(unsigned int)n); str[n] = 0; return true; } ++ return false; ++ } ++ ++ //! Remove white spaces on the start and/or end of a C-string. ++ inline bool strpare(char *const str, const bool is_symmetric, const bool is_iterative) { ++ if (!str) return false; ++ const int l = (int)std::strlen(str); ++ int p, q; ++ if (is_symmetric) for (p = 0, q = l - 1; pp && (signed char)str[q]<=' '; ) { --q; if (!is_iterative) break; } ++ } ++ const int n = q - p + 1; ++ if (n!=l) { std::memmove(str,str + p,(unsigned int)n); str[n] = 0; return true; } ++ return false; ++ } ++ ++ //! Replace reserved characters (for Windows filename) by another character. ++ /** ++ \param[in,out] str C-string to work with (modified at output). ++ \param[in] c Replacement character. ++ **/ ++ inline void strwindows_reserved(char *const str, const char c='_') { ++ for (char *s = str; *s; ++s) { ++ const char i = *s; ++ if (i=='<' || i=='>' || i==':' || i=='\"' || i=='/' || i=='\\' || i=='|' || i=='?' || i=='*') *s = c; ++ } ++ } ++ ++ //! Replace escape sequences in C-strings by their binary ascii values. ++ /** ++ \param[in,out] str C-string to work with (modified at output). ++ **/ ++ inline void strunescape(char *const str) { ++#define cimg_strunescape(ci,co) case ci : *nd = co; ++ns; break; ++ unsigned int val = 0; ++ for (char *ns = str, *nd = str; *ns || (bool)(*nd=0); ++nd) if (*ns=='\\') switch (*(++ns)) { ++ cimg_strunescape('a','\a'); ++ cimg_strunescape('b','\b'); ++ cimg_strunescape('e',0x1B); ++ cimg_strunescape('f','\f'); ++ cimg_strunescape('n','\n'); ++ cimg_strunescape('r','\r'); ++ cimg_strunescape('t','\t'); ++ cimg_strunescape('v','\v'); ++ cimg_strunescape('\\','\\'); ++ cimg_strunescape('\'','\''); ++ cimg_strunescape('\"','\"'); ++ cimg_strunescape('\?','\?'); ++ case 0 : *nd = 0; break; ++ case '0' : case '1' : case '2' : case '3' : case '4' : case '5' : case '6' : case '7' : ++ cimg_sscanf(ns,"%o",&val); while (*ns>='0' && *ns<='7') ++ns; ++ *nd = (char)val; break; ++ case 'x' : ++ cimg_sscanf(++ns,"%x",&val); ++ while ((*ns>='0' && *ns<='9') || (*ns>='a' && *ns<='f') || (*ns>='A' && *ns<='F')) ++ns; ++ *nd = (char)val; break; ++ default : *nd = *(ns++); ++ } else *nd = *(ns++); ++ } ++ ++ // Return a temporary string describing the size of a memory buffer. ++ inline const char *strbuffersize(const cimg_ulong size); ++ ++ // Return string that identifies the running OS. ++ inline const char *stros() { ++#if defined(linux) || defined(__linux) || defined(__linux__) ++ static const char *const str = "Linux"; ++#elif defined(sun) || defined(__sun) ++ static const char *const str = "Sun OS"; ++#elif defined(BSD) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) || defined (__DragonFly__) ++ static const char *const str = "BSD"; ++#elif defined(sgi) || defined(__sgi) ++ static const char *const str = "Irix"; ++#elif defined(__MACOSX__) || defined(__APPLE__) ++ static const char *const str = "Mac OS"; ++#elif defined(unix) || defined(__unix) || defined(__unix__) ++ static const char *const str = "Generic Unix"; ++#elif defined(_MSC_VER) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || \ ++ defined(WIN64) || defined(_WIN64) || defined(__WIN64__) ++ static const char *const str = "Windows"; ++#else ++ const char ++ *const _str1 = std::getenv("OSTYPE"), ++ *const _str2 = _str1?_str1:std::getenv("OS"), ++ *const str = _str2?_str2:"Unknown OS"; ++#endif ++ return str; ++ } ++ ++ //! Return the basename of a filename. ++ inline const char* basename(const char *const s, const char separator=cimg_file_separator) { ++ const char *p = 0, *np = s; ++ while (np>=s && (p=np)) np = std::strchr(np,separator) + 1; ++ return p; ++ } ++ ++ // Return a random filename. ++ inline const char* filenamerand() { ++ cimg::mutex(6); ++ static char randomid[9]; ++ cimg::srand(); ++ for (unsigned int k = 0; k<8; ++k) { ++ const int v = (int)cimg::rand(65535)%3; ++ randomid[k] = (char)(v==0?('0' + ((int)cimg::rand(65535)%10)): ++ (v==1?('a' + ((int)cimg::rand(65535)%26)): ++ ('A' + ((int)cimg::rand(65535)%26)))); ++ } ++ cimg::mutex(6,0); ++ return randomid; ++ } ++ ++ // Convert filename as a Windows-style filename (short path name). ++ inline void winformat_string(char *const str) { ++ if (str && *str) { ++#if cimg_OS==2 ++ char *const nstr = new char[MAX_PATH]; ++ if (GetShortPathNameA(str,nstr,MAX_PATH)) std::strcpy(str,nstr); ++ delete[] nstr; ++#endif ++ } ++ } ++ ++ // Open a file (with wide character support on Windows). ++ inline std::FILE *win_fopen(const char *const path, const char *const mode); ++ ++ //! Open a file. ++ /** ++ \param path Path of the filename to open. ++ \param mode C-string describing the opening mode. ++ \return Opened file. ++ \note Same as std::fopen() but throw a \c CImgIOException when ++ the specified file cannot be opened, instead of returning \c 0. ++ **/ ++ inline std::FILE *fopen(const char *const path, const char *const mode) { ++ if (!path) ++ throw CImgArgumentException("cimg::fopen(): Specified file path is (null)."); ++ if (!mode) ++ throw CImgArgumentException("cimg::fopen(): File '%s', specified mode is (null).", ++ path); ++ std::FILE *res = 0; ++ if (*path=='-' && (!path[1] || path[1]=='.')) { ++ res = (*mode=='r')?cimg::_stdin():cimg::_stdout(); ++#if cimg_OS==2 ++ if (*mode && mode[1]=='b') { // Force stdin/stdout to be in binary mode. ++#ifdef __BORLANDC__ ++ if (setmode(_fileno(res),0x8000)==-1) res = 0; ++#else ++ if (_setmode(_fileno(res),0x8000)==-1) res = 0; ++#endif ++ } ++#endif ++ } else res = std_fopen(path,mode); ++ if (!res) throw CImgIOException("cimg::fopen(): Failed to open file '%s' with mode '%s'.", ++ path,mode); ++ return res; ++ } ++ ++ //! Close a file. ++ /** ++ \param file File to close. ++ \return \c 0 if file has been closed properly, something else otherwise. ++ \note Same as std::fclose() but display a warning message if ++ the file has not been closed properly. ++ **/ ++ inline int fclose(std::FILE *file) { ++ if (!file) { warn("cimg::fclose(): Specified file is (null)."); return 0; } ++ if (file==cimg::_stdin(false) || file==cimg::_stdout(false)) return 0; ++ const int errn = std::fclose(file); ++ if (errn!=0) warn("cimg::fclose(): Error code %d returned during file closing.", ++ errn); ++ return errn; ++ } ++ ++ //! Version of 'fseek()' that supports >=64bits offsets everywhere (for Windows). ++ inline int fseek(FILE *stream, cimg_long offset, int origin) { ++#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) ++ return _fseeki64(stream,(__int64)offset,origin); ++#else ++ return std::fseek(stream,offset,origin); ++#endif ++ } ++ ++ //! Version of 'ftell()' that supports >=64bits offsets everywhere (for Windows). ++ inline cimg_long ftell(FILE *stream) { ++#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) ++ return (cimg_long)_ftelli64(stream); ++#else ++ return (cimg_long)std::ftell(stream); ++#endif ++ } ++ ++ //! Check if a path is a directory. ++ /** ++ \param path Specified path to test. ++ **/ ++ inline bool is_directory(const char *const path) { ++ if (!path || !*path) return false; ++#if cimg_OS==1 ++ struct stat st_buf; ++ return (!stat(path,&st_buf) && S_ISDIR(st_buf.st_mode)); ++#elif cimg_OS==2 ++ const unsigned int res = (unsigned int)GetFileAttributesA(path); ++ return res==INVALID_FILE_ATTRIBUTES?false:(res&16); ++#else ++ return false; ++#endif ++ } ++ ++ //! Check if a path is a file. ++ /** ++ \param path Specified path to test. ++ **/ ++ inline bool is_file(const char *const path) { ++ if (!path || !*path) return false; ++ std::FILE *const file = std_fopen(path,"rb"); ++ if (!file) return false; ++ std::fclose(file); ++ return !is_directory(path); ++ } ++ ++ //! Get file size. ++ /** ++ \param filename Specified filename to get size from. ++ \return File size or '-1' if file does not exist. ++ **/ ++ inline cimg_int64 fsize(const char *const filename) { ++ std::FILE *const file = std::fopen(filename,"rb"); ++ if (!file) return (cimg_int64)-1; ++ std::fseek(file,0,SEEK_END); ++ const cimg_int64 siz = (cimg_int64)std::ftell(file); ++ std::fclose(file); ++ return siz; ++ } ++ ++ //! Get last write time of a given file or directory (multiple-attributes version). ++ /** ++ \param path Specified path to get attributes from. ++ \param[in,out] attr Type of requested time attributes. ++ Can be { 0=year | 1=month | 2=day | 3=day of week | 4=hour | 5=minute | 6=second } ++ Replaced by read attributes after return (or -1 if an error occured). ++ \param nb_attr Number of attributes to read/write. ++ \return Latest read attribute. ++ **/ ++ template ++ inline int fdate(const char *const path, T *attr, const unsigned int nb_attr) { ++#define _cimg_fdate_err() for (unsigned int i = 0; i ++ inline int date(T *attr, const unsigned int nb_attr) { ++ int res = -1; ++ cimg::mutex(6); ++#if cimg_OS==2 ++ SYSTEMTIME st; ++ GetLocalTime(&st); ++ for (unsigned int i = 0; itm_year + 1900:attr[i]==1?st->tm_mon + 1:attr[i]==2?st->tm_mday: ++ attr[i]==3?st->tm_wday:attr[i]==4?st->tm_hour:attr[i]==5?st->tm_min: ++ attr[i]==6?st->tm_sec:-1); ++ attr[i] = (T)res; ++ } ++#endif ++ cimg::mutex(6,0); ++ return res; ++ } ++ ++ //! Get current local time (single-attribute version). ++ /** ++ \param attr Type of requested time attribute. ++ Can be { 0=year | 1=month | 2=day | 3=day of week | 4=hour | 5=minute | 6=second } ++ \return Specified attribute or -1 if an error occured. ++ **/ ++ inline int date(unsigned int attr) { ++ int out = (int)attr; ++ return date(&out,1); ++ } ++ ++ // Get/set path to store temporary files. ++ inline const char* temporary_path(const char *const user_path=0, const bool reinit_path=false); ++ ++ // Get/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); ++#endif ++ ++ // Get/set path to the ImageMagick's \c convert binary. ++ inline const char* imagemagick_path(const char *const user_path=0, const bool reinit_path=false); ++ ++ // Get/set path to the GraphicsMagick's \c gm binary. ++ inline const char* graphicsmagick_path(const char *const user_path=0, const bool reinit_path=false); ++ ++ // Get/set path to the XMedcon's \c medcon binary. ++ inline const char* medcon_path(const char *const user_path=0, const bool reinit_path=false); ++ ++ // Get/set path to the FFMPEG's \c ffmpeg binary. ++ inline const char *ffmpeg_path(const char *const user_path=0, const bool reinit_path=false); ++ ++ // Get/set path to the \c gzip binary. ++ inline const char *gzip_path(const char *const user_path=0, const bool reinit_path=false); ++ ++ // Get/set path to the \c gunzip binary. ++ inline const char *gunzip_path(const char *const user_path=0, const bool reinit_path=false); ++ ++ // Get/set path to the \c dcraw binary. ++ inline const char *dcraw_path(const char *const user_path=0, const bool reinit_path=false); ++ ++ // Get/set path to the \c wget binary. ++ inline const char *wget_path(const char *const user_path=0, const bool reinit_path=false); ++ ++ // Get/set path to the \c curl binary. ++ inline const char *curl_path(const char *const user_path=0, const bool reinit_path=false); ++ ++ //! Split filename into two C-strings \c body and \c extension. ++ /** ++ filename and body must not overlap! ++ **/ ++ inline const char *split_filename(const char *const filename, char *const body=0) { ++ if (!filename) { if (body) *body = 0; return 0; } ++ const char *p = 0; for (const char *np = filename; np>=filename && (p=np); np = std::strchr(np,'.') + 1) {} ++ if (p==filename) { ++ if (body) std::strcpy(body,filename); ++ return filename + std::strlen(filename); ++ } ++ const unsigned int l = (unsigned int)(p - filename - 1); ++ if (body) { if (l) std::memcpy(body,filename,l); body[l] = 0; } ++ return p; ++ } ++ ++ //! Generate a numbered version of a filename. ++ inline char* number_filename(const char *const filename, const int number, ++ const unsigned int digits, char *const str) { ++ if (!filename) { if (str) *str = 0; return 0; } ++ char *const format = new char[1024], *const body = new char[1024]; ++ const char *const ext = cimg::split_filename(filename,body); ++ if (*ext) cimg_snprintf(format,1024,"%%s_%%.%ud.%%s",digits); ++ else cimg_snprintf(format,1024,"%%s_%%.%ud",digits); ++ cimg_sprintf(str,format,body,number,ext); ++ delete[] format; delete[] body; ++ return str; ++ } ++ ++ //! Read data from file. ++ /** ++ \param[out] ptr Pointer to memory buffer that will contain the binary data read from file. ++ \param nmemb Number of elements to read. ++ \param stream File to read data from. ++ \return Number of read elements. ++ \note Same as std::fread() but may display warning message if all elements could not be read. ++ **/ ++ template ++ inline size_t fread(T *const ptr, const size_t nmemb, std::FILE *stream) { ++ if (!ptr || !stream) ++ throw CImgArgumentException("cimg::fread(): Invalid reading request of %u %s%s from file %p to buffer %p.", ++ nmemb,cimg::type::string(),nmemb>1?"s":"",stream,ptr); ++ if (!nmemb) return 0; ++ const size_t wlimitT = 63*1024*1024, wlimit = wlimitT/sizeof(T); ++ size_t to_read = nmemb, al_read = 0, l_to_read = 0, l_al_read = 0; ++ do { ++ l_to_read = (to_read*sizeof(T))0); ++ if (to_read>0) ++ warn("cimg::fread(): Only %lu/%lu elements could be read from file.", ++ (unsigned long)al_read,(unsigned long)nmemb); ++ return al_read; ++ } ++ ++ //! Write data to file. ++ /** ++ \param ptr Pointer to memory buffer containing the binary data to write on file. ++ \param nmemb Number of elements to write. ++ \param[out] stream File to write data on. ++ \return Number of written elements. ++ \note Similar to std::fwrite but may display warning messages if all elements could not be written. ++ **/ ++ template ++ inline size_t fwrite(const T *ptr, const size_t nmemb, std::FILE *stream) { ++ if (!ptr || !stream) ++ throw CImgArgumentException("cimg::fwrite(): Invalid writing request of %u %s%s from buffer %p to file %p.", ++ nmemb,cimg::type::string(),nmemb>1?"s":"",ptr,stream); ++ if (!nmemb) return 0; ++ const size_t wlimitT = 63*1024*1024, wlimit = wlimitT/sizeof(T); ++ size_t to_write = nmemb, al_write = 0, l_to_write = 0, l_al_write = 0; ++ do { ++ l_to_write = (to_write*sizeof(T))0); ++ if (to_write>0) ++ warn("cimg::fwrite(): Only %lu/%lu elements could be written in file.", ++ (unsigned long)al_write,(unsigned long)nmemb); ++ return al_write; ++ } ++ ++ //! Create an empty file. ++ /** ++ \param file Input file (can be \c 0 if \c filename is set). ++ \param filename Filename, as a C-string (can be \c 0 if \c file is set). ++ **/ ++ inline void fempty(std::FILE *const file, const char *const filename) { ++ if (!file && !filename) ++ throw CImgArgumentException("cimg::fempty(): Specified filename is (null)."); ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); ++ if (!file) cimg::fclose(nfile); ++ } ++ ++ // Try to guess format from an image file. ++ inline const char *ftype(std::FILE *const file, const char *const filename); ++ ++ // Load file from network as a local temporary file. ++ inline char *load_network(const char *const url, char *const filename_local, ++ const unsigned int timeout=0, const bool try_fallback=false, ++ const char *const referer=0); ++ ++ //! Return options specified on the command line. ++ inline const char* option(const char *const name, const int argc, const char *const *const argv, ++ const char *const defaut, const char *const usage, const bool reset_static) { ++ static bool first = true, visu = false; ++ if (reset_static) { first = true; return 0; } ++ const char *res = 0; ++ if (first) { ++ first = false; ++ visu = cimg::option("-h",argc,argv,(char*)0,(char*)0,false)!=0; ++ visu |= cimg::option("-help",argc,argv,(char*)0,(char*)0,false)!=0; ++ visu |= cimg::option("--help",argc,argv,(char*)0,(char*)0,false)!=0; ++ } ++ if (!name && visu) { ++ if (usage) { ++ std::fprintf(cimg::output(),"\n %s%s%s",cimg::t_red,cimg::basename(argv[0]),cimg::t_normal); ++ std::fprintf(cimg::output(),": %s",usage); ++ std::fprintf(cimg::output()," (%s, %s)\n\n",cimg_date,cimg_time); ++ } ++ if (defaut) std::fprintf(cimg::output(),"%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); ++ ++ std::fprintf(cimg::output()," > CPU endianness: %s%s Endian%s\n", ++ cimg::t_bold, ++ cimg::endianness()?"Big":"Little", ++ cimg::t_normal); ++ ++ std::fprintf(cimg::output()," > Verbosity mode: %s%-13s%s %s('cimg_verbosity'=%d)%s\n", ++ cimg::t_bold, ++ cimg_verbosity==0?"Quiet": ++ cimg_verbosity==1?"Console": ++ cimg_verbosity==2?"Dialog": ++ cimg_verbosity==3?"Console+Warnings":"Dialog+Warnings", ++ cimg::t_normal,cimg::t_green, ++ cimg_verbosity, ++ cimg::t_normal); ++ ++ std::fprintf(cimg::output()," > 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); ++ ++ std::fprintf(cimg::output()," > Support for C++11: %s%-13s%s %s('cimg_use_cpp11'=%d)%s\n", ++ cimg::t_bold, ++ cimg_use_cpp11?"Yes":"No", ++ cimg::t_normal,cimg::t_green, ++ (int)cimg_use_cpp11, ++ cimg::t_normal); ++ ++ std::fprintf(cimg::output()," > 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); ++ ++ std::fprintf(cimg::output()," > 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":"Unknown", ++ cimg::t_normal,cimg::t_green, ++ (int)cimg_display, ++ cimg::t_normal); ++ ++#if cimg_display==1 ++ std::fprintf(cimg::output()," > 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); ++ ++ std::fprintf(cimg::output()," > 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 ++ std::fprintf(cimg::output()," > 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); ++ std::fprintf(cimg::output()," > 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); ++ std::fprintf(cimg::output()," > 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); ++ ++ std::fprintf(cimg::output()," > 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); ++ ++ std::fprintf(cimg::output()," > 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); ++ ++ std::fprintf(cimg::output()," > 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); ++ ++ std::fprintf(cimg::output()," > 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); ++ ++ char *const tmp = new char[1024]; ++ cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::imagemagick_path()); ++ std::fprintf(cimg::output()," > Path of ImageMagick: %s%-13s%s\n", ++ cimg::t_bold, ++ tmp, ++ cimg::t_normal); ++ ++ cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::graphicsmagick_path()); ++ std::fprintf(cimg::output()," > Path of GraphicsMagick: %s%-13s%s\n", ++ cimg::t_bold, ++ tmp, ++ cimg::t_normal); ++ ++ cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::medcon_path()); ++ std::fprintf(cimg::output()," > Path of 'medcon': %s%-13s%s\n", ++ cimg::t_bold, ++ tmp, ++ cimg::t_normal); ++ ++ cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::temporary_path()); ++ std::fprintf(cimg::output()," > Temporary path: %s%-13s%s\n", ++ cimg::t_bold, ++ tmp, ++ cimg::t_normal); ++ ++ std::fprintf(cimg::output(),"\n"); ++ delete[] tmp; ++ } ++ ++ // Declare LAPACK function signatures if LAPACK support is enabled. ++#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); ++ } ++ ++ template ++ inline void sgels(char & TRANS, int &M, int &N, int &NRHS, T* lapA, int &LDA, ++ T* lapB, int &LDB, T* WORK, int &LWORK, int &INFO){ ++ dgels_(&TRANS, &M, &N, &NRHS, lapA, &LDA, lapB, &LDB, WORK, &LWORK, &INFO); ++ } ++ ++ inline void sgels(char & TRANS, int &M, int &N, int &NRHS, float* lapA, int &LDA, ++ float* lapB, int &LDB, float* WORK, int &LWORK, int &INFO){ ++ sgels_(&TRANS, &M, &N, &NRHS, lapA, &LDA, lapB, &LDB, WORK, &LWORK, &INFO); ++ } ++ ++#endif ++ ++ // End of the 'cimg' namespace ++ } ++ ++ /*------------------------------------------------ ++ # ++ # ++ # Definition of mathematical operators and ++ # external functions. ++ # ++ # ++ -------------------------------------------------*/ ++ ++#define _cimg_create_ext_operators(typ) \ ++ template \ ++ inline CImg::type> operator+(const typ val, const CImg& img) { \ ++ return img + val; \ ++ } \ ++ template \ ++ inline CImg::type> operator-(const typ val, const CImg& img) { \ ++ typedef typename cimg::superset::type Tt; \ ++ return CImg(img._width,img._height,img._depth,img._spectrum,val)-=img; \ ++ } \ ++ template \ ++ inline CImg::type> operator*(const typ val, const CImg& img) { \ ++ return img*val; \ ++ } \ ++ template \ ++ inline CImg::type> operator/(const typ val, const CImg& img) { \ ++ return val*img.get_invert(); \ ++ } \ ++ template \ ++ inline CImg::type> operator&(const typ val, const CImg& img) { \ ++ return img & val; \ ++ } \ ++ template \ ++ inline CImg::type> operator|(const typ val, const CImg& img) { \ ++ return img | val; \ ++ } \ ++ template \ ++ inline CImg::type> operator^(const typ val, const CImg& img) { \ ++ return img ^ val; \ ++ } \ ++ template \ ++ inline bool operator==(const typ val, const CImg& img) { \ ++ return img == val; \ ++ } \ ++ template \ ++ inline bool operator!=(const typ val, const CImg& img) { \ ++ return img != val; \ ++ } ++ ++ _cimg_create_ext_operators(bool) ++ _cimg_create_ext_operators(unsigned char) ++ _cimg_create_ext_operators(char) ++ _cimg_create_ext_operators(signed char) ++ _cimg_create_ext_operators(unsigned short) ++ _cimg_create_ext_operators(short) ++ _cimg_create_ext_operators(unsigned int) ++ _cimg_create_ext_operators(int) ++ _cimg_create_ext_operators(cimg_uint64) ++ _cimg_create_ext_operators(cimg_int64) ++ _cimg_create_ext_operators(float) ++ _cimg_create_ext_operators(double) ++ _cimg_create_ext_operators(long double) ++ ++ template ++ inline CImg<_cimg_Tfloat> operator+(const char *const expression, const CImg& img) { ++ return img + expression; ++ } ++ ++ template ++ inline CImg<_cimg_Tfloat> operator-(const char *const expression, const CImg& img) { ++ return CImg<_cimg_Tfloat>(img,false).fill(expression,true)-=img; ++ } ++ ++ template ++ inline CImg<_cimg_Tfloat> operator*(const char *const expression, const CImg& img) { ++ return img*expression; ++ } ++ ++ template ++ inline CImg<_cimg_Tfloat> operator/(const char *const expression, const CImg& img) { ++ return expression*img.get_invert(); ++ } ++ ++ template ++ inline CImg operator&(const char *const expression, const CImg& img) { ++ return img & expression; ++ } ++ ++ template ++ inline CImg operator|(const char *const expression, const CImg& img) { ++ return img | expression; ++ } ++ ++ template ++ inline CImg operator^(const char *const expression, const CImg& img) { ++ return img ^ expression; ++ } ++ ++ template ++ inline bool operator==(const char *const expression, const CImg& img) { ++ return img==expression; ++ } ++ ++ template ++ inline bool operator!=(const char *const expression, const CImg& img) { ++ return img!=expression; ++ } ++ ++ 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> log2(const CImg& instance) { ++ return instance.get_log2(); ++ } ++ ++ 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> sign(const CImg& instance) { ++ return instance.get_sign(); ++ } ++ ++ 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> sinc(const CImg& instance) { ++ return instance.get_sinc(); ++ } ++ ++ 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<_cimg_Tfloat> cosh(const CImg& instance) { ++ return instance.get_cosh(); ++ } ++ ++ template ++ inline CImg<_cimg_Tfloat> sinh(const CImg& instance) { ++ return instance.get_sinh(); ++ } ++ ++ template ++ inline CImg<_cimg_Tfloat> tanh(const CImg& instance) { ++ return instance.get_tanh(); ++ } ++ ++ 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(); ++ } ++ ++ /*----------------------------------- ++ # ++ # Define the CImgDisplay structure ++ # ++ ----------------------------------*/ ++ //! Allow the creation of windows, display images on them and manage user events (keyboard, mouse and windows events). ++ /** ++ CImgDisplay methods rely on a low-level graphic library to perform: it can be either \b X-Window ++ (X11, for Unix-based systems) or \b GDI32 (for Windows-based systems). ++ If both libraries are missing, CImgDisplay will not be able to display images on screen, and will enter ++ a minimal mode where warning messages will be outputed each time the program is trying to call one of the ++ CImgDisplay method. ++ ++ The configuration variable \c cimg_display tells about the graphic library used. ++ It is set automatically by \CImg when one of these graphic libraries has been detected. ++ But, you can override its value if necessary. Valid choices are: ++ - 0: Disable display capabilities. ++ - 1: Use \b X-Window (X11) library. ++ - 2: Use \b GDI32 library. ++ ++ Remember to link your program against \b X11 or \b GDI32 libraries if you use CImgDisplay. ++ **/ ++ struct CImgDisplay { ++ cimg_ulong _timer, _fps_frames, _fps_timer; ++ unsigned int _width, _height, _normalization; ++ float _fps_fps, _min, _max; ++ bool _is_fullscreen; ++ char *_title; ++ unsigned int _window_width, _window_height, _button, *_keys, *_released_keys; ++ int _window_x, _window_y, _mouse_x, _mouse_y, _wheel; ++ bool _is_closed, _is_resized, _is_moved, _is_event, ++ _is_keyESC, _is_keyF1, _is_keyF2, _is_keyF3, _is_keyF4, _is_keyF5, _is_keyF6, _is_keyF7, ++ _is_keyF8, _is_keyF9, _is_keyF10, _is_keyF11, _is_keyF12, _is_keyPAUSE, _is_key1, _is_key2, ++ _is_key3, _is_key4, _is_key5, _is_key6, _is_key7, _is_key8, _is_key9, _is_key0, ++ _is_keyBACKSPACE, _is_keyINSERT, _is_keyHOME, _is_keyPAGEUP, _is_keyTAB, _is_keyQ, _is_keyW, _is_keyE, ++ _is_keyR, _is_keyT, _is_keyY, _is_keyU, _is_keyI, _is_keyO, _is_keyP, _is_keyDELETE, ++ _is_keyEND, _is_keyPAGEDOWN, _is_keyCAPSLOCK, _is_keyA, _is_keyS, _is_keyD, _is_keyF, _is_keyG, ++ _is_keyH, _is_keyJ, _is_keyK, _is_keyL, _is_keyENTER, _is_keySHIFTLEFT, _is_keyZ, _is_keyX, ++ _is_keyC, _is_keyV, _is_keyB, _is_keyN, _is_keyM, _is_keySHIFTRIGHT, _is_keyARROWUP, _is_keyCTRLLEFT, ++ _is_keyAPPLEFT, _is_keyALT, _is_keySPACE, _is_keyALTGR, _is_keyAPPRIGHT, _is_keyMENU, _is_keyCTRLRIGHT, ++ _is_keyARROWLEFT, _is_keyARROWDOWN, _is_keyARROWRIGHT, _is_keyPAD0, _is_keyPAD1, _is_keyPAD2, _is_keyPAD3, ++ _is_keyPAD4, _is_keyPAD5, _is_keyPAD6, _is_keyPAD7, _is_keyPAD8, _is_keyPAD9, _is_keyPADADD, _is_keyPADSUB, ++ _is_keyPADMUL, _is_keyPADDIV; ++ ++ //@} ++ //--------------------------- ++ // ++ //! \name Plugins ++ //@{ ++ //--------------------------- ++ ++#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 ++ ++ //@} ++ //-------------------------------------------------------- ++ // ++ //! \name Constructors / Destructor / Instance Management ++ //@{ ++ //-------------------------------------------------------- ++ ++ //! Destructor. ++ /** ++ \note If the associated window is visible on the screen, it is closed by the call to the destructor. ++ **/ ++ ~CImgDisplay() { ++ assign(); ++ delete[] _keys; ++ delete[] _released_keys; ++ } ++ ++ //! Construct an empty display. ++ /** ++ \note Constructing an empty CImgDisplay instance does not make a window appearing on the screen, until ++ display of valid data is performed. ++ \par Example ++ \code ++ CImgDisplay disp; // Does actually nothing. ++ ... ++ disp.display(img); // Construct new window and display image in it. ++ \endcode ++ **/ ++ CImgDisplay(): ++ _width(0),_height(0),_normalization(0), ++ _min(0),_max(0), ++ _is_fullscreen(false), ++ _title(0), ++ _window_width(0),_window_height(0),_button(0), ++ _keys(new unsigned int[128]),_released_keys(new unsigned int[128]), ++ _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0), ++ _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { ++ assign(); ++ } ++ ++ //! Construct a display with specified dimensions. ++ /** \param width Window width. ++ \param height Window height. ++ \param title Window title. ++ \param normalization Normalization type ++ (0=none, 1=always, 2=once, 3=pixel type-dependent, see normalization()). ++ \param is_fullscreen Tells if fullscreen mode is enabled. ++ \param is_closed Tells if associated window is initially visible or not. ++ \note A black background is initially displayed on the associated window. ++ **/ ++ CImgDisplay(const unsigned int width, const unsigned int height, ++ const char *const title=0, const unsigned int normalization=3, ++ const bool is_fullscreen=false, const bool is_closed=false): ++ _width(0),_height(0),_normalization(0), ++ _min(0),_max(0), ++ _is_fullscreen(false), ++ _title(0), ++ _window_width(0),_window_height(0),_button(0), ++ _keys(new unsigned int[128]),_released_keys(new unsigned int[128]), ++ _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0), ++ _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { ++ assign(width,height,title,normalization,is_fullscreen,is_closed); ++ } ++ ++ //! Construct a display from an image. ++ /** \param img Image used as a model to create the window. ++ \param title Window title. ++ \param normalization Normalization type ++ (0=none, 1=always, 2=once, 3=pixel type-dependent, see normalization()). ++ \param is_fullscreen Tells if fullscreen mode is enabled. ++ \param is_closed Tells if associated window is initially visible or not. ++ \note The pixels of the input image are initially displayed on the associated window. ++ **/ ++ template ++ explicit CImgDisplay(const CImg& img, ++ const char *const title=0, const unsigned int normalization=3, ++ const bool is_fullscreen=false, const bool is_closed=false): ++ _width(0),_height(0),_normalization(0), ++ _min(0),_max(0), ++ _is_fullscreen(false), ++ _title(0), ++ _window_width(0),_window_height(0),_button(0), ++ _keys(new unsigned int[128]),_released_keys(new unsigned int[128]), ++ _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0), ++ _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { ++ assign(img,title,normalization,is_fullscreen,is_closed); ++ } ++ ++ //! Construct a display from an image list. ++ /** \param list The images list to display. ++ \param title Window title. ++ \param normalization Normalization type ++ (0=none, 1=always, 2=once, 3=pixel type-dependent, see normalization()). ++ \param is_fullscreen Tells if fullscreen mode is enabled. ++ \param is_closed Tells if associated window is initially visible or not. ++ \note All images of the list, appended along the X-axis, are initially displayed on the associated window. ++ **/ ++ template ++ explicit CImgDisplay(const CImgList& list, ++ const char *const title=0, const unsigned int normalization=3, ++ const bool is_fullscreen=false, const bool is_closed=false): ++ _width(0),_height(0),_normalization(0), ++ _min(0),_max(0), ++ _is_fullscreen(false), ++ _title(0), ++ _window_width(0),_window_height(0),_button(0), ++ _keys(new unsigned int[128]),_released_keys(new unsigned int[128]), ++ _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0), ++ _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { ++ assign(list,title,normalization,is_fullscreen,is_closed); ++ } ++ ++ //! Construct a display as a copy of an existing one. ++ /** ++ \param disp Display instance to copy. ++ \note The pixel buffer of the input window is initially displayed on the associated window. ++ **/ ++ CImgDisplay(const CImgDisplay& disp): ++ _width(0),_height(0),_normalization(0), ++ _min(0),_max(0), ++ _is_fullscreen(false), ++ _title(0), ++ _window_width(0),_window_height(0),_button(0), ++ _keys(new unsigned int[128]),_released_keys(new unsigned int[128]), ++ _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0), ++ _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { ++ assign(disp); ++ } ++ ++ //! Take a screenshot. ++ /** ++ \param[out] img Output screenshot. Can be empty on input ++ **/ ++ template ++ static void screenshot(CImg& img) { ++ return screenshot(0,0,cimg::type::max(),cimg::type::max(),img); ++ } ++ ++#if cimg_display==0 ++ ++ static void _no_display_exception() { ++ throw CImgDisplayException("CImgDisplay(): No display available."); ++ } ++ ++ //! Destructor - Empty constructor \inplace. ++ /** ++ \note Replace the current instance by an empty display. ++ **/ ++ CImgDisplay& assign() { ++ return flush(); ++ } ++ ++ //! Construct a display with specified dimensions \inplace. ++ /** ++ **/ ++ CImgDisplay& assign(const unsigned int width, const unsigned int height, ++ const char *const title=0, const unsigned int normalization=3, ++ const bool is_fullscreen=false, const bool is_closed=false) { ++ cimg::unused(width,height,title,normalization,is_fullscreen,is_closed); ++ _no_display_exception(); ++ return assign(); ++ } ++ ++ //! Construct a display from an image \inplace. ++ /** ++ **/ ++ template ++ CImgDisplay& assign(const CImg& img, ++ const char *const title=0, const unsigned int normalization=3, ++ const bool is_fullscreen=false, const bool is_closed=false) { ++ _no_display_exception(); ++ return assign(img._width,img._height,title,normalization,is_fullscreen,is_closed); ++ } ++ ++ //! Construct a display from an image list \inplace. ++ /** ++ **/ ++ template ++ CImgDisplay& assign(const CImgList& list, ++ const char *const title=0, const unsigned int normalization=3, ++ const bool is_fullscreen=false, const bool is_closed=false) { ++ _no_display_exception(); ++ return assign(list._width,list._width,title,normalization,is_fullscreen,is_closed); ++ } ++ ++ //! Construct a display as a copy of another one \inplace. ++ /** ++ **/ ++ CImgDisplay& assign(const CImgDisplay &disp) { ++ _no_display_exception(); ++ return assign(disp._width,disp._height); ++ } ++ ++#endif ++ ++ //! Return a reference to an empty display. ++ /** ++ \note Can be useful for writing function prototypes where one of the argument (of type CImgDisplay&) ++ must have a default value. ++ \par Example ++ \code ++ void foo(CImgDisplay& disp=CImgDisplay::empty()); ++ \endcode ++ **/ ++ static CImgDisplay& empty() { ++ static CImgDisplay _empty; ++ return _empty.assign(); ++ } ++ ++ //! Return a reference to an empty display \const. ++ static const CImgDisplay& const_empty() { ++ static const CImgDisplay _empty; ++ return _empty; ++ } ++ ++#define cimg_fitscreen(dx,dy,dz) CImgDisplay::_fitscreen(dx,dy,dz,128,-85,false), \ ++ CImgDisplay::_fitscreen(dx,dy,dz,128,-85,true) ++ static unsigned int _fitscreen(const unsigned int dx, const unsigned int dy, const unsigned int dz, ++ const int dmin, const int dmax,const bool return_y) { ++ const unsigned int _nw = dx + (dz>1?dz:0), _nh = dy + (dz>1?dz:0); ++ unsigned int nw = _nw?_nw:1, nh = _nh?_nh:1; ++ const unsigned int ++ sw = (unsigned int)CImgDisplay::screen_width(), ++ sh = (unsigned int)CImgDisplay::screen_height(), ++ 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 (nwdisp = img is equivalent to disp.display(img). ++ **/ ++ template ++ CImgDisplay& operator=(const CImg& img) { ++ return display(img); ++ } ++ ++ //! Display list of images on associated window. ++ /** ++ \note disp = list is equivalent to disp.display(list). ++ **/ ++ template ++ CImgDisplay& operator=(const CImgList& list) { ++ return display(list); ++ } ++ ++ //! Construct a display as a copy of another one \inplace. ++ /** ++ \note Equivalent to assign(const CImgDisplay&). ++ **/ ++ CImgDisplay& operator=(const CImgDisplay& disp) { ++ return assign(disp); ++ } ++ ++ //! Return \c false if display is empty, \c true otherwise. ++ /** ++ \note if (disp) { ... } is equivalent to if (!disp.is_empty()) { ... }. ++ **/ ++ operator bool() const { ++ return !is_empty(); ++ } ++ ++ //@} ++ //------------------------------------------ ++ // ++ //! \name Instance Checking ++ //@{ ++ //------------------------------------------ ++ ++ //! Return \c true if display is empty, \c false otherwise. ++ /** ++ **/ ++ bool is_empty() const { ++ return !(_width && _height); ++ } ++ ++ //! Return \c true if display is closed (i.e. not visible on the screen), \c false otherwise. ++ /** ++ \note ++ - When a user physically closes the associated window, the display is set to closed. ++ - A closed display is not destroyed. Its associated window can be show again on the screen using show(). ++ **/ ++ bool is_closed() const { ++ return _is_closed; ++ } ++ ++ //! Return \c true if associated window has been resized on the screen, \c false otherwise. ++ /** ++ **/ ++ bool is_resized() const { ++ return _is_resized; ++ } ++ ++ //! Return \c true if associated window has been moved on the screen, \c false otherwise. ++ /** ++ **/ ++ bool is_moved() const { ++ return _is_moved; ++ } ++ ++ //! Return \c true if any event has occured on the associated window, \c false otherwise. ++ /** ++ **/ ++ bool is_event() const { ++ return _is_event; ++ } ++ ++ //! Return \c true if current display is in fullscreen mode, \c false otherwise. ++ /** ++ **/ ++ bool is_fullscreen() const { ++ return _is_fullscreen; ++ } ++ ++ //! Return \c true if any key is being pressed on the associated window, \c false otherwise. ++ /** ++ \note The methods below do the same only for specific keys. ++ **/ ++ bool is_key() const { ++ return _is_keyESC || _is_keyF1 || _is_keyF2 || _is_keyF3 || ++ _is_keyF4 || _is_keyF5 || _is_keyF6 || _is_keyF7 || ++ _is_keyF8 || _is_keyF9 || _is_keyF10 || _is_keyF11 || ++ _is_keyF12 || _is_keyPAUSE || _is_key1 || _is_key2 || ++ _is_key3 || _is_key4 || _is_key5 || _is_key6 || ++ _is_key7 || _is_key8 || _is_key9 || _is_key0 || ++ _is_keyBACKSPACE || _is_keyINSERT || _is_keyHOME || ++ _is_keyPAGEUP || _is_keyTAB || _is_keyQ || _is_keyW || ++ _is_keyE || _is_keyR || _is_keyT || _is_keyY || ++ _is_keyU || _is_keyI || _is_keyO || _is_keyP || ++ _is_keyDELETE || _is_keyEND || _is_keyPAGEDOWN || ++ _is_keyCAPSLOCK || _is_keyA || _is_keyS || _is_keyD || ++ _is_keyF || _is_keyG || _is_keyH || _is_keyJ || ++ _is_keyK || _is_keyL || _is_keyENTER || ++ _is_keySHIFTLEFT || _is_keyZ || _is_keyX || _is_keyC || ++ _is_keyV || _is_keyB || _is_keyN || _is_keyM || ++ _is_keySHIFTRIGHT || _is_keyARROWUP || _is_keyCTRLLEFT || ++ _is_keyAPPLEFT || _is_keyALT || _is_keySPACE || _is_keyALTGR || ++ _is_keyAPPRIGHT || _is_keyMENU || _is_keyCTRLRIGHT || ++ _is_keyARROWLEFT || _is_keyARROWDOWN || _is_keyARROWRIGHT || ++ _is_keyPAD0 || _is_keyPAD1 || _is_keyPAD2 || ++ _is_keyPAD3 || _is_keyPAD4 || _is_keyPAD5 || ++ _is_keyPAD6 || _is_keyPAD7 || _is_keyPAD8 || ++ _is_keyPAD9 || _is_keyPADADD || _is_keyPADSUB || ++ _is_keyPADMUL || _is_keyPADDIV; ++ } ++ ++ //! Return \c true if key specified by given keycode is being pressed on the associated window, \c false otherwise. ++ /** ++ \param keycode Keycode to test. ++ \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure ++ your code stay portable (see cimg::keyESC). ++ \par Example ++ \code ++ CImgDisplay disp(400,400); ++ while (!disp.is_closed()) { ++ if (disp.key(cimg::keyTAB)) { ... } // Equivalent to 'if (disp.is_keyTAB())'. ++ disp.wait(); ++ } ++ \endcode ++ **/ ++ bool is_key(const unsigned int keycode) const { ++#define _cimg_iskey_test(k) if (keycode==cimg::key##k) return _is_key##k; ++ _cimg_iskey_test(ESC); _cimg_iskey_test(F1); _cimg_iskey_test(F2); _cimg_iskey_test(F3); ++ _cimg_iskey_test(F4); _cimg_iskey_test(F5); _cimg_iskey_test(F6); _cimg_iskey_test(F7); ++ _cimg_iskey_test(F8); _cimg_iskey_test(F9); _cimg_iskey_test(F10); _cimg_iskey_test(F11); ++ _cimg_iskey_test(F12); _cimg_iskey_test(PAUSE); _cimg_iskey_test(1); _cimg_iskey_test(2); ++ _cimg_iskey_test(3); _cimg_iskey_test(4); _cimg_iskey_test(5); _cimg_iskey_test(6); ++ _cimg_iskey_test(7); _cimg_iskey_test(8); _cimg_iskey_test(9); _cimg_iskey_test(0); ++ _cimg_iskey_test(BACKSPACE); _cimg_iskey_test(INSERT); _cimg_iskey_test(HOME); ++ _cimg_iskey_test(PAGEUP); _cimg_iskey_test(TAB); _cimg_iskey_test(Q); _cimg_iskey_test(W); ++ _cimg_iskey_test(E); _cimg_iskey_test(R); _cimg_iskey_test(T); _cimg_iskey_test(Y); ++ _cimg_iskey_test(U); _cimg_iskey_test(I); _cimg_iskey_test(O); _cimg_iskey_test(P); ++ _cimg_iskey_test(DELETE); _cimg_iskey_test(END); _cimg_iskey_test(PAGEDOWN); ++ _cimg_iskey_test(CAPSLOCK); _cimg_iskey_test(A); _cimg_iskey_test(S); _cimg_iskey_test(D); ++ _cimg_iskey_test(F); _cimg_iskey_test(G); _cimg_iskey_test(H); _cimg_iskey_test(J); ++ _cimg_iskey_test(K); _cimg_iskey_test(L); _cimg_iskey_test(ENTER); ++ _cimg_iskey_test(SHIFTLEFT); _cimg_iskey_test(Z); _cimg_iskey_test(X); _cimg_iskey_test(C); ++ _cimg_iskey_test(V); _cimg_iskey_test(B); _cimg_iskey_test(N); _cimg_iskey_test(M); ++ _cimg_iskey_test(SHIFTRIGHT); _cimg_iskey_test(ARROWUP); _cimg_iskey_test(CTRLLEFT); ++ _cimg_iskey_test(APPLEFT); _cimg_iskey_test(ALT); _cimg_iskey_test(SPACE); _cimg_iskey_test(ALTGR); ++ _cimg_iskey_test(APPRIGHT); _cimg_iskey_test(MENU); _cimg_iskey_test(CTRLRIGHT); ++ _cimg_iskey_test(ARROWLEFT); _cimg_iskey_test(ARROWDOWN); _cimg_iskey_test(ARROWRIGHT); ++ _cimg_iskey_test(PAD0); _cimg_iskey_test(PAD1); _cimg_iskey_test(PAD2); ++ _cimg_iskey_test(PAD3); _cimg_iskey_test(PAD4); _cimg_iskey_test(PAD5); ++ _cimg_iskey_test(PAD6); _cimg_iskey_test(PAD7); _cimg_iskey_test(PAD8); ++ _cimg_iskey_test(PAD9); _cimg_iskey_test(PADADD); _cimg_iskey_test(PADSUB); ++ _cimg_iskey_test(PADMUL); _cimg_iskey_test(PADDIV); ++ return false; ++ } ++ ++ //! Return \c true if key specified by given keycode is being pressed on the associated window, \c false otherwise. ++ /** ++ \param keycode C-string containing the keycode label of the key to test. ++ \note Use it when the key you want to test can be dynamically set by the user. ++ \par Example ++ \code ++ CImgDisplay disp(400,400); ++ const char *const keycode = "TAB"; ++ while (!disp.is_closed()) { ++ if (disp.is_key(keycode)) { ... } // Equivalent to 'if (disp.is_keyTAB())'. ++ disp.wait(); ++ } ++ \endcode ++ **/ ++ bool& is_key(const char *const keycode) { ++ static bool f = false; ++ f = false; ++#define _cimg_iskey_test2(k) if (!cimg::strcasecmp(keycode,#k)) return _is_key##k; ++ _cimg_iskey_test2(ESC); _cimg_iskey_test2(F1); _cimg_iskey_test2(F2); _cimg_iskey_test2(F3); ++ _cimg_iskey_test2(F4); _cimg_iskey_test2(F5); _cimg_iskey_test2(F6); _cimg_iskey_test2(F7); ++ _cimg_iskey_test2(F8); _cimg_iskey_test2(F9); _cimg_iskey_test2(F10); _cimg_iskey_test2(F11); ++ _cimg_iskey_test2(F12); _cimg_iskey_test2(PAUSE); _cimg_iskey_test2(1); _cimg_iskey_test2(2); ++ _cimg_iskey_test2(3); _cimg_iskey_test2(4); _cimg_iskey_test2(5); _cimg_iskey_test2(6); ++ _cimg_iskey_test2(7); _cimg_iskey_test2(8); _cimg_iskey_test2(9); _cimg_iskey_test2(0); ++ _cimg_iskey_test2(BACKSPACE); _cimg_iskey_test2(INSERT); _cimg_iskey_test2(HOME); ++ _cimg_iskey_test2(PAGEUP); _cimg_iskey_test2(TAB); _cimg_iskey_test2(Q); _cimg_iskey_test2(W); ++ _cimg_iskey_test2(E); _cimg_iskey_test2(R); _cimg_iskey_test2(T); _cimg_iskey_test2(Y); ++ _cimg_iskey_test2(U); _cimg_iskey_test2(I); _cimg_iskey_test2(O); _cimg_iskey_test2(P); ++ _cimg_iskey_test2(DELETE); _cimg_iskey_test2(END); _cimg_iskey_test2(PAGEDOWN); ++ _cimg_iskey_test2(CAPSLOCK); _cimg_iskey_test2(A); _cimg_iskey_test2(S); _cimg_iskey_test2(D); ++ _cimg_iskey_test2(F); _cimg_iskey_test2(G); _cimg_iskey_test2(H); _cimg_iskey_test2(J); ++ _cimg_iskey_test2(K); _cimg_iskey_test2(L); _cimg_iskey_test2(ENTER); ++ _cimg_iskey_test2(SHIFTLEFT); _cimg_iskey_test2(Z); _cimg_iskey_test2(X); _cimg_iskey_test2(C); ++ _cimg_iskey_test2(V); _cimg_iskey_test2(B); _cimg_iskey_test2(N); _cimg_iskey_test2(M); ++ _cimg_iskey_test2(SHIFTRIGHT); _cimg_iskey_test2(ARROWUP); _cimg_iskey_test2(CTRLLEFT); ++ _cimg_iskey_test2(APPLEFT); _cimg_iskey_test2(ALT); _cimg_iskey_test2(SPACE); _cimg_iskey_test2(ALTGR); ++ _cimg_iskey_test2(APPRIGHT); _cimg_iskey_test2(MENU); _cimg_iskey_test2(CTRLRIGHT); ++ _cimg_iskey_test2(ARROWLEFT); _cimg_iskey_test2(ARROWDOWN); _cimg_iskey_test2(ARROWRIGHT); ++ _cimg_iskey_test2(PAD0); _cimg_iskey_test2(PAD1); _cimg_iskey_test2(PAD2); ++ _cimg_iskey_test2(PAD3); _cimg_iskey_test2(PAD4); _cimg_iskey_test2(PAD5); ++ _cimg_iskey_test2(PAD6); _cimg_iskey_test2(PAD7); _cimg_iskey_test2(PAD8); ++ _cimg_iskey_test2(PAD9); _cimg_iskey_test2(PADADD); _cimg_iskey_test2(PADSUB); ++ _cimg_iskey_test2(PADMUL); _cimg_iskey_test2(PADDIV); ++ return f; ++ } ++ ++ //! Return \c true if specified key sequence has been typed on the associated window, \c false otherwise. ++ /** ++ \param keycodes_sequence Buffer of keycodes to test. ++ \param length Number of keys in the \c keycodes_sequence buffer. ++ \param remove_sequence Tells if the key sequence must be removed from the key history, if found. ++ \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure ++ your code stay portable (see cimg::keyESC). ++ \par Example ++ \code ++ CImgDisplay disp(400,400); ++ const unsigned int key_seq[] = { cimg::keyCTRLLEFT, cimg::keyD }; ++ while (!disp.is_closed()) { ++ if (disp.is_key_sequence(key_seq,2)) { ... } // Test for the 'CTRL+D' keyboard event. ++ disp.wait(); ++ } ++ \endcode ++ **/ ++ bool is_key_sequence(const unsigned int *const keycodes_sequence, const unsigned int length, ++ const bool remove_sequence=false) { ++ if (keycodes_sequence && length) { ++ const unsigned int ++ *const ps_end = keycodes_sequence + length - 1, ++ *const pk_end = (unsigned int*)_keys + 1 + 128 - length, ++ k = *ps_end; ++ for (unsigned int *pk = (unsigned int*)_keys; pk[0,255]. ++ If the range of values of the data to display is different, a normalization may be required for displaying ++ the data in a correct way. The normalization type can be one of: ++ - \c 0: Value normalization is disabled. It is then assumed that all input data to be displayed by the ++ CImgDisplay instance have values in range [0,255]. ++ - \c 1: Value normalization is always performed (this is the default behavior). ++ Before displaying an input image, its values will be (virtually) stretched ++ in range [0,255], so that the contrast of the displayed pixels will be maximum. ++ Use this mode for images whose minimum and maximum values are not prescribed to known values ++ (e.g. float-valued images). ++ Note that when normalized versions of images are computed for display purposes, the actual values of these ++ images are not modified. ++ - \c 2: Value normalization is performed once (on the first image display), then the same normalization ++ coefficients are kept for next displayed frames. ++ - \c 3: Value normalization depends on the pixel type of the data to display. For integer pixel types, ++ the normalization is done regarding the minimum/maximum values of the type (no normalization occurs then ++ for unsigned char). ++ For float-valued pixel types, the normalization is done regarding the minimum/maximum value of the image ++ data instead. ++ **/ ++ unsigned int normalization() const { ++ return _normalization; ++ } ++ ++ //! Return title of the associated window as a C-string. ++ /** ++ \note Window title may be not visible, depending on the used window manager or if the current display is ++ in fullscreen mode. ++ **/ ++ const char *title() const { ++ return _title?_title:""; ++ } ++ ++ //! Return width of the associated window. ++ /** ++ \note The width of the display (i.e. the width of the pixel data buffer associated to the CImgDisplay instance) ++ may be different from the actual width of the associated window. ++ **/ ++ int window_width() const { ++ return (int)_window_width; ++ } ++ ++ //! Return height of the associated window. ++ /** ++ \note The height of the display (i.e. the height of the pixel data buffer associated to the CImgDisplay instance) ++ may be different from the actual height of the associated window. ++ **/ ++ int window_height() const { ++ return (int)_window_height; ++ } ++ ++ //! Return X-coordinate of the associated window. ++ /** ++ \note The returned coordinate corresponds to the location of the upper-left corner of the associated window. ++ **/ ++ int window_x() const { ++ return _window_x; ++ } ++ ++ //! Return Y-coordinate of the associated window. ++ /** ++ \note The returned coordinate corresponds to the location of the upper-left corner of the associated window. ++ **/ ++ int window_y() const { ++ return _window_y; ++ } ++ ++ //! Return X-coordinate of the mouse pointer. ++ /** ++ \note ++ - If the mouse pointer is outside window area, \c -1 is returned. ++ - Otherwise, the returned value is in the range [0,width()-1]. ++ **/ ++ int mouse_x() const { ++ return _mouse_x; ++ } ++ ++ //! Return Y-coordinate of the mouse pointer. ++ /** ++ \note ++ - If the mouse pointer is outside window area, \c -1 is returned. ++ - Otherwise, the returned value is in the range [0,height()-1]. ++ **/ ++ int mouse_y() const { ++ return _mouse_y; ++ } ++ ++ //! Return current state of the mouse buttons. ++ /** ++ \note Three mouse buttons can be managed. If one button is pressed, its corresponding bit in the returned ++ value is set: ++ - bit \c 0 (value \c 0x1): State of the left mouse button. ++ - bit \c 1 (value \c 0x2): State of the right mouse button. ++ - bit \c 2 (value \c 0x4): State of the middle mouse button. ++ ++ Several bits can be activated if more than one button are pressed at the same time. ++ \par Example ++ \code ++ CImgDisplay disp(400,400); ++ while (!disp.is_closed()) { ++ if (disp.button()&1) { // Left button clicked. ++ ... ++ } ++ if (disp.button()&2) { // Right button clicked. ++ ... ++ } ++ if (disp.button()&4) { // Middle button clicked. ++ ... ++ } ++ disp.wait(); ++ } ++ \endcode ++ **/ ++ unsigned int button() const { ++ return _button; ++ } ++ ++ //! Return current state of the mouse wheel. ++ /** ++ \note ++ - The returned value can be positive or negative depending on whether the mouse wheel has been scrolled ++ forward or backward. ++ - Scrolling the wheel forward add \c 1 to the wheel value. ++ - Scrolling the wheel backward substract \c 1 to the wheel value. ++ - The returned value cumulates the number of forward of backward scrolls since the creation of the display, ++ or since the last reset of the wheel value (using set_wheel()). It is strongly recommended to quickly reset ++ the wheel counter when an action has been performed regarding the current wheel value. ++ Otherwise, the returned wheel value may be for instance \c 0 despite the fact that many scrolls have been done ++ (as many in forward as in backward directions). ++ \par Example ++ \code ++ CImgDisplay disp(400,400); ++ while (!disp.is_closed()) { ++ if (disp.wheel()) { ++ int counter = disp.wheel(); // Read the state of the mouse wheel. ++ ... // Do what you want with 'counter'. ++ disp.set_wheel(); // Reset the wheel value to 0. ++ } ++ disp.wait(); ++ } ++ \endcode ++ **/ ++ int wheel() const { ++ return _wheel; ++ } ++ ++ //! Return one entry from the pressed keys history. ++ /** ++ \param pos Indice to read from the pressed keys history (indice \c 0 corresponds to latest entry). ++ \return Keycode of a pressed key or \c 0 for a released key. ++ \note ++ - Each CImgDisplay stores a history of the pressed keys in a buffer of size \c 128. When a new key is pressed, ++ its keycode is stored in the pressed keys history. When a key is released, \c 0 is put instead. ++ This means that up to the 64 last pressed keys may be read from the pressed keys history. ++ When a new value is stored, the pressed keys history is shifted so that the latest entry is always ++ stored at position \c 0. ++ - Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure ++ your code stay portable (see cimg::keyESC). ++ **/ ++ unsigned int key(const unsigned int pos=0) const { ++ return pos<128?_keys[pos]:0; ++ } ++ ++ //! Return one entry from the released keys history. ++ /** ++ \param pos Indice to read from the released keys history (indice \c 0 corresponds to latest entry). ++ \return Keycode of a released key or \c 0 for a pressed key. ++ \note ++ - Each CImgDisplay stores a history of the released keys in a buffer of size \c 128. When a new key is released, ++ its keycode is stored in the pressed keys history. When a key is pressed, \c 0 is put instead. ++ This means that up to the 64 last released keys may be read from the released keys history. ++ When a new value is stored, the released keys history is shifted so that the latest entry is always ++ stored at position \c 0. ++ - Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure ++ your code stay portable (see cimg::keyESC). ++ **/ ++ unsigned int released_key(const unsigned int pos=0) const { ++ return pos<128?_released_keys[pos]:0; ++ } ++ ++ //! Return keycode corresponding to the specified string. ++ /** ++ \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure ++ your code stay portable (see cimg::keyESC). ++ \par Example ++ \code ++ const unsigned int keyTAB = CImgDisplay::keycode("TAB"); // Return cimg::keyTAB. ++ \endcode ++ **/ ++ static unsigned int keycode(const char *const keycode) { ++#define _cimg_keycode(k) if (!cimg::strcasecmp(keycode,#k)) return cimg::key##k; ++ _cimg_keycode(ESC); _cimg_keycode(F1); _cimg_keycode(F2); _cimg_keycode(F3); ++ _cimg_keycode(F4); _cimg_keycode(F5); _cimg_keycode(F6); _cimg_keycode(F7); ++ _cimg_keycode(F8); _cimg_keycode(F9); _cimg_keycode(F10); _cimg_keycode(F11); ++ _cimg_keycode(F12); _cimg_keycode(PAUSE); _cimg_keycode(1); _cimg_keycode(2); ++ _cimg_keycode(3); _cimg_keycode(4); _cimg_keycode(5); _cimg_keycode(6); ++ _cimg_keycode(7); _cimg_keycode(8); _cimg_keycode(9); _cimg_keycode(0); ++ _cimg_keycode(BACKSPACE); _cimg_keycode(INSERT); _cimg_keycode(HOME); ++ _cimg_keycode(PAGEUP); _cimg_keycode(TAB); _cimg_keycode(Q); _cimg_keycode(W); ++ _cimg_keycode(E); _cimg_keycode(R); _cimg_keycode(T); _cimg_keycode(Y); ++ _cimg_keycode(U); _cimg_keycode(I); _cimg_keycode(O); _cimg_keycode(P); ++ _cimg_keycode(DELETE); _cimg_keycode(END); _cimg_keycode(PAGEDOWN); ++ _cimg_keycode(CAPSLOCK); _cimg_keycode(A); _cimg_keycode(S); _cimg_keycode(D); ++ _cimg_keycode(F); _cimg_keycode(G); _cimg_keycode(H); _cimg_keycode(J); ++ _cimg_keycode(K); _cimg_keycode(L); _cimg_keycode(ENTER); ++ _cimg_keycode(SHIFTLEFT); _cimg_keycode(Z); _cimg_keycode(X); _cimg_keycode(C); ++ _cimg_keycode(V); _cimg_keycode(B); _cimg_keycode(N); _cimg_keycode(M); ++ _cimg_keycode(SHIFTRIGHT); _cimg_keycode(ARROWUP); _cimg_keycode(CTRLLEFT); ++ _cimg_keycode(APPLEFT); _cimg_keycode(ALT); _cimg_keycode(SPACE); _cimg_keycode(ALTGR); ++ _cimg_keycode(APPRIGHT); _cimg_keycode(MENU); _cimg_keycode(CTRLRIGHT); ++ _cimg_keycode(ARROWLEFT); _cimg_keycode(ARROWDOWN); _cimg_keycode(ARROWRIGHT); ++ _cimg_keycode(PAD0); _cimg_keycode(PAD1); _cimg_keycode(PAD2); ++ _cimg_keycode(PAD3); _cimg_keycode(PAD4); _cimg_keycode(PAD5); ++ _cimg_keycode(PAD6); _cimg_keycode(PAD7); _cimg_keycode(PAD8); ++ _cimg_keycode(PAD9); _cimg_keycode(PADADD); _cimg_keycode(PADSUB); ++ _cimg_keycode(PADMUL); _cimg_keycode(PADDIV); ++ return 0; ++ } ++ ++ //! Return the current refresh rate, in frames per second. ++ /** ++ \note Returns a significant value when the current instance is used to display successive frames. ++ It measures the delay between successive calls to frames_per_second(). ++ **/ ++ 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; ++ } ++ ++ //@} ++ //--------------------------------------- ++ // ++ //! \name Window Manipulation ++ //@{ ++ //--------------------------------------- ++ ++#if cimg_display==0 ++ ++ //! Display image on associated window. ++ /** ++ \param img Input image to display. ++ \note This method returns immediately. ++ **/ ++ template ++ CImgDisplay& display(const CImg& img) { ++ return assign(img); ++ } ++ ++#endif ++ ++ //! Display list of images on associated window. ++ /** ++ \param list List of images to display. ++ \param axis Axis used to append the images along, for the visualization (can be \c x, \c y, \c z or \c c). ++ \param align Relative position of aligned images when displaying lists with images of different sizes ++ (\c 0 for upper-left, \c 0.5 for centering and \c 1 for lower-right). ++ \note This method returns immediately. ++ **/ ++ template ++ CImgDisplay& display(const CImgList& list, const char axis='x', const float align=0) { ++ if (list._width==1) { ++ const CImg& img = list[0]; ++ if (img._depth==1 && (img._spectrum==1 || img._spectrum>=3) && _normalization!=1) return display(img); ++ } ++ CImgList::ucharT> visu(list._width); ++ unsigned int dims = 0; ++ cimglist_for(list,l) { ++ const CImg& img = list._data[l]; ++ img.__get_select(*this,_normalization,(img._width - 1)/2,(img._height - 1)/2, ++ (img._depth - 1)/2).move_to(visu[l]); ++ dims = std::max(dims,visu[l]._spectrum); ++ } ++ cimglist_for(list,l) if (visu[l]._spectrumimg.width() become equal, as well as height() and ++ img.height(). ++ - The associated window is also resized to specified dimensions. ++ **/ ++ template ++ CImgDisplay& resize(const CImg& img, const bool force_redraw=true) { ++ return resize(img._width,img._height,force_redraw); ++ } ++ ++ //! Resize display to the size of another CImgDisplay instance. ++ /** ++ \param disp Input display to take size from. ++ \param force_redraw Tells if the previous window content must be resized and updated as well. ++ \note ++ - Calling this method ensures that width() and disp.width() become equal, as well as height() and ++ disp.height(). ++ - The associated window is also resized to specified dimensions. ++ **/ ++ CImgDisplay& resize(const CImgDisplay& disp, const bool force_redraw=true) { ++ return resize(disp.width(),disp.height(),force_redraw); ++ } ++ ++ // [internal] Render pixel buffer with size (wd,hd) from source buffer of size (ws,hs). ++ 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; xstd::printf(). ++ \warning As the first argument is a format string, it is highly recommended to write ++ \code ++ disp.set_title("%s",window_title); ++ \endcode ++ instead of ++ \code ++ disp.set_title(window_title); ++ \endcode ++ if \c window_title can be arbitrary, to prevent nasty memory access. ++ **/ ++ CImgDisplay& set_title(const char *const format, ...) { ++ return assign(0,0,format); ++ } ++ ++#endif ++ ++ //! Enable or disable fullscreen mode. ++ /** ++ \param is_fullscreen Tells is the fullscreen mode must be activated or not. ++ \param force_redraw Tells if the previous window content must be displayed as well. ++ \note ++ - When the fullscreen mode is enabled, the associated window fills the entire screen but the size of the ++ current display is not modified. ++ - The screen resolution may be switched to fit the associated window size and ensure it appears the largest ++ as possible. ++ For X-Window (X11) users, the configuration flag \c cimg_use_xrandr has to be set to allow the screen ++ resolution change (requires the X11 extensions to be enabled). ++ **/ ++ CImgDisplay& set_fullscreen(const bool is_fullscreen, const bool force_redraw=true) { ++ if (is_empty() || _is_fullscreen==is_fullscreen) return *this; ++ return toggle_fullscreen(force_redraw); ++ } ++ ++#if cimg_display==0 ++ ++ //! Toggle fullscreen mode. ++ /** ++ \param force_redraw Tells if the previous window content must be displayed as well. ++ \note Enable fullscreen mode if it was not enabled, and disable it otherwise. ++ **/ ++ CImgDisplay& toggle_fullscreen(const bool force_redraw=true) { ++ return assign(_width,_height,0,3,force_redraw); ++ } ++ ++ //! Show mouse pointer. ++ /** ++ \note Depending on the window manager behavior, this method may not succeed ++ (no exceptions are thrown nevertheless). ++ **/ ++ CImgDisplay& show_mouse() { ++ return assign(); ++ } ++ ++ //! Hide mouse pointer. ++ /** ++ \note Depending on the window manager behavior, this method may not succeed ++ (no exceptions are thrown nevertheless). ++ **/ ++ CImgDisplay& hide_mouse() { ++ return assign(); ++ } ++ ++ //! Move mouse pointer to a specified location. ++ /** ++ \note Depending on the window manager behavior, this method may not succeed ++ (no exceptions are thrown nevertheless). ++ **/ ++ CImgDisplay& set_mouse(const int pos_x, const int pos_y) { ++ return assign(pos_x,pos_y); ++ } ++ ++#endif ++ ++ //! Simulate a mouse button release event. ++ /** ++ \note All mouse buttons are considered released at the same time. ++ **/ ++ CImgDisplay& set_button() { ++ _button = 0; ++ _is_event = true; ++#if cimg_display==1 ++ pthread_cond_broadcast(&cimg::X11_attr().wait_event); ++#elif cimg_display==2 ++ SetEvent(cimg::Win32_attr().wait_event); ++#endif ++ return *this; ++ } ++ ++ //! Simulate a mouse button press or release event. ++ /** ++ \param button Buttons event code, where each button is associated to a single bit. ++ \param is_pressed Tells if the mouse button is considered as pressed or released. ++ **/ ++ CImgDisplay& set_button(const unsigned int button, const bool is_pressed=true) { ++ const unsigned int buttoncode = button==1U?1U:button==2U?2U:button==3U?4U:0U; ++ if (is_pressed) _button |= buttoncode; else _button &= ~buttoncode; ++ _is_event = buttoncode?true:false; ++ if (buttoncode) { ++#if cimg_display==1 ++ pthread_cond_broadcast(&cimg::X11_attr().wait_event); ++#elif cimg_display==2 ++ SetEvent(cimg::Win32_attr().wait_event); ++#endif ++ } ++ return *this; ++ } ++ ++ //! Flush all mouse wheel events. ++ /** ++ \note Make wheel() to return \c 0, if called afterwards. ++ **/ ++ CImgDisplay& set_wheel() { ++ _wheel = 0; ++ _is_event = true; ++#if cimg_display==1 ++ pthread_cond_broadcast(&cimg::X11_attr().wait_event); ++#elif cimg_display==2 ++ SetEvent(cimg::Win32_attr().wait_event); ++#endif ++ return *this; ++ } ++ ++ //! Simulate a wheel event. ++ /** ++ \param amplitude Amplitude of the wheel scrolling to simulate. ++ \note Make wheel() to return \c amplitude, if called afterwards. ++ **/ ++ CImgDisplay& set_wheel(const int amplitude) { ++ _wheel+=amplitude; ++ _is_event = amplitude?true:false; ++ if (amplitude) { ++#if cimg_display==1 ++ pthread_cond_broadcast(&cimg::X11_attr().wait_event); ++#elif cimg_display==2 ++ SetEvent(cimg::Win32_attr().wait_event); ++#endif ++ } ++ return *this; ++ } ++ ++ //! Flush all key events. ++ /** ++ \note Make key() to return \c 0, if called afterwards. ++ **/ ++ CImgDisplay& set_key() { ++ std::memset((void*)_keys,0,128*sizeof(unsigned int)); ++ std::memset((void*)_released_keys,0,128*sizeof(unsigned int)); ++ _is_keyESC = _is_keyF1 = _is_keyF2 = _is_keyF3 = _is_keyF4 = _is_keyF5 = _is_keyF6 = _is_keyF7 = _is_keyF8 = ++ _is_keyF9 = _is_keyF10 = _is_keyF11 = _is_keyF12 = _is_keyPAUSE = _is_key1 = _is_key2 = _is_key3 = _is_key4 = ++ _is_key5 = _is_key6 = _is_key7 = _is_key8 = _is_key9 = _is_key0 = _is_keyBACKSPACE = _is_keyINSERT = ++ _is_keyHOME = _is_keyPAGEUP = _is_keyTAB = _is_keyQ = _is_keyW = _is_keyE = _is_keyR = _is_keyT = _is_keyY = ++ _is_keyU = _is_keyI = _is_keyO = _is_keyP = _is_keyDELETE = _is_keyEND = _is_keyPAGEDOWN = _is_keyCAPSLOCK = ++ _is_keyA = _is_keyS = _is_keyD = _is_keyF = _is_keyG = _is_keyH = _is_keyJ = _is_keyK = _is_keyL = ++ _is_keyENTER = _is_keySHIFTLEFT = _is_keyZ = _is_keyX = _is_keyC = _is_keyV = _is_keyB = _is_keyN = ++ _is_keyM = _is_keySHIFTRIGHT = _is_keyARROWUP = _is_keyCTRLLEFT = _is_keyAPPLEFT = _is_keyALT = _is_keySPACE = ++ _is_keyALTGR = _is_keyAPPRIGHT = _is_keyMENU = _is_keyCTRLRIGHT = _is_keyARROWLEFT = _is_keyARROWDOWN = ++ _is_keyARROWRIGHT = _is_keyPAD0 = _is_keyPAD1 = _is_keyPAD2 = _is_keyPAD3 = _is_keyPAD4 = _is_keyPAD5 = ++ _is_keyPAD6 = _is_keyPAD7 = _is_keyPAD8 = _is_keyPAD9 = _is_keyPADADD = _is_keyPADSUB = _is_keyPADMUL = ++ _is_keyPADDIV = false; ++ _is_event = true; ++#if cimg_display==1 ++ pthread_cond_broadcast(&cimg::X11_attr().wait_event); ++#elif cimg_display==2 ++ SetEvent(cimg::Win32_attr().wait_event); ++#endif ++ return *this; ++ } ++ ++ //! Simulate a keyboard press/release event. ++ /** ++ \param keycode Keycode of the associated key. ++ \param is_pressed Tells if the key is considered as pressed or released. ++ \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure ++ your code stay portable (see cimg::keyESC). ++ **/ ++ CImgDisplay& set_key(const unsigned int keycode, const bool is_pressed=true) { ++#define _cimg_set_key(k) if (keycode==cimg::key##k) _is_key##k = is_pressed; ++ _cimg_set_key(ESC); _cimg_set_key(F1); _cimg_set_key(F2); _cimg_set_key(F3); ++ _cimg_set_key(F4); _cimg_set_key(F5); _cimg_set_key(F6); _cimg_set_key(F7); ++ _cimg_set_key(F8); _cimg_set_key(F9); _cimg_set_key(F10); _cimg_set_key(F11); ++ _cimg_set_key(F12); _cimg_set_key(PAUSE); _cimg_set_key(1); _cimg_set_key(2); ++ _cimg_set_key(3); _cimg_set_key(4); _cimg_set_key(5); _cimg_set_key(6); ++ _cimg_set_key(7); _cimg_set_key(8); _cimg_set_key(9); _cimg_set_key(0); ++ _cimg_set_key(BACKSPACE); _cimg_set_key(INSERT); _cimg_set_key(HOME); ++ _cimg_set_key(PAGEUP); _cimg_set_key(TAB); _cimg_set_key(Q); _cimg_set_key(W); ++ _cimg_set_key(E); _cimg_set_key(R); _cimg_set_key(T); _cimg_set_key(Y); ++ _cimg_set_key(U); _cimg_set_key(I); _cimg_set_key(O); _cimg_set_key(P); ++ _cimg_set_key(DELETE); _cimg_set_key(END); _cimg_set_key(PAGEDOWN); ++ _cimg_set_key(CAPSLOCK); _cimg_set_key(A); _cimg_set_key(S); _cimg_set_key(D); ++ _cimg_set_key(F); _cimg_set_key(G); _cimg_set_key(H); _cimg_set_key(J); ++ _cimg_set_key(K); _cimg_set_key(L); _cimg_set_key(ENTER); ++ _cimg_set_key(SHIFTLEFT); _cimg_set_key(Z); _cimg_set_key(X); _cimg_set_key(C); ++ _cimg_set_key(V); _cimg_set_key(B); _cimg_set_key(N); _cimg_set_key(M); ++ _cimg_set_key(SHIFTRIGHT); _cimg_set_key(ARROWUP); _cimg_set_key(CTRLLEFT); ++ _cimg_set_key(APPLEFT); _cimg_set_key(ALT); _cimg_set_key(SPACE); _cimg_set_key(ALTGR); ++ _cimg_set_key(APPRIGHT); _cimg_set_key(MENU); _cimg_set_key(CTRLRIGHT); ++ _cimg_set_key(ARROWLEFT); _cimg_set_key(ARROWDOWN); _cimg_set_key(ARROWRIGHT); ++ _cimg_set_key(PAD0); _cimg_set_key(PAD1); _cimg_set_key(PAD2); ++ _cimg_set_key(PAD3); _cimg_set_key(PAD4); _cimg_set_key(PAD5); ++ _cimg_set_key(PAD6); _cimg_set_key(PAD7); _cimg_set_key(PAD8); ++ _cimg_set_key(PAD9); _cimg_set_key(PADADD); _cimg_set_key(PADSUB); ++ _cimg_set_key(PADMUL); _cimg_set_key(PADDIV); ++ if (is_pressed) { ++ if (*_keys) ++ std::memmove((void*)(_keys + 1),(void*)_keys,127*sizeof(unsigned int)); ++ *_keys = keycode; ++ if (*_released_keys) { ++ std::memmove((void*)(_released_keys + 1),(void*)_released_keys,127*sizeof(unsigned int)); ++ *_released_keys = 0; ++ } ++ } else { ++ if (*_keys) { ++ std::memmove((void*)(_keys + 1),(void*)_keys,127*sizeof(unsigned int)); ++ *_keys = 0; ++ } ++ if (*_released_keys) ++ std::memmove((void*)(_released_keys + 1),(void*)_released_keys,127*sizeof(unsigned int)); ++ *_released_keys = keycode; ++ } ++ _is_event = keycode?true:false; ++ if (keycode) { ++#if cimg_display==1 ++ pthread_cond_broadcast(&cimg::X11_attr().wait_event); ++#elif cimg_display==2 ++ SetEvent(cimg::Win32_attr().wait_event); ++#endif ++ } ++ return *this; ++ } ++ ++ //! Flush all display events. ++ /** ++ \note Remove all passed events from the current display. ++ **/ ++ CImgDisplay& flush() { ++ set_key().set_button().set_wheel(); ++ _is_resized = _is_moved = _is_event = false; ++ _fps_timer = _fps_frames = _timer = 0; ++ _fps_fps = 0; ++ return *this; ++ } ++ ++ //! Wait for any user event occuring on the current display. ++ CImgDisplay& wait() { ++ wait(*this); ++ return *this; ++ } ++ ++ //! Wait for a given number of milliseconds since the last call to wait(). ++ /** ++ \param milliseconds Number of milliseconds to wait for. ++ \note Similar to cimg::wait(). ++ **/ ++ CImgDisplay& wait(const unsigned int milliseconds) { ++ cimg::_wait(milliseconds,_timer); ++ return *this; ++ } ++ ++ //! Wait for any event occuring on the display \c disp1. ++ static void wait(CImgDisplay& disp1) { ++ disp1._is_event = false; ++ while (!disp1._is_closed && !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 = false; ++ while ((!disp1._is_closed || !disp2._is_closed) && ++ !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 = false; ++ while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed) && ++ !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 = false; ++ while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed) && ++ !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event) wait_all(); ++ } ++ ++ //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4 or \c disp5. ++ static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, ++ CImgDisplay& disp5) { ++ disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = false; ++ while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed) && ++ !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event) ++ wait_all(); ++ } ++ ++ //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp6. ++ static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5, ++ CImgDisplay& disp6) { ++ disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = ++ disp6._is_event = false; ++ while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed || ++ !disp6._is_closed) && ++ !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event && ++ !disp6._is_event) wait_all(); ++ } ++ ++ //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp7. ++ static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5, ++ CImgDisplay& disp6, CImgDisplay& disp7) { ++ disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = ++ disp6._is_event = disp7._is_event = false; ++ while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed || ++ !disp6._is_closed || !disp7._is_closed) && ++ !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event && ++ !disp6._is_event && !disp7._is_event) wait_all(); ++ } ++ ++ //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp8. ++ static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5, ++ CImgDisplay& disp6, CImgDisplay& disp7, CImgDisplay& disp8) { ++ disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = ++ disp6._is_event = disp7._is_event = disp8._is_event = false; ++ while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed || ++ !disp6._is_closed || !disp7._is_closed || !disp8._is_closed) && ++ !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event && ++ !disp6._is_event && !disp7._is_event && !disp8._is_event) wait_all(); ++ } ++ ++ //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp9. ++ static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5, ++ CImgDisplay& disp6, CImgDisplay& disp7, CImgDisplay& disp8, CImgDisplay& disp9) { ++ disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = ++ disp6._is_event = disp7._is_event = disp8._is_event = disp9._is_event = false; ++ while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed || ++ !disp6._is_closed || !disp7._is_closed || !disp8._is_closed || !disp9._is_closed) && ++ !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event && ++ !disp6._is_event && !disp7._is_event && !disp8._is_event && !disp9._is_event) wait_all(); ++ } ++ ++ //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp10. ++ static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5, ++ CImgDisplay& disp6, CImgDisplay& disp7, CImgDisplay& disp8, CImgDisplay& disp9, ++ CImgDisplay& disp10) { ++ disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = ++ disp6._is_event = disp7._is_event = disp8._is_event = disp9._is_event = disp10._is_event = false; ++ while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed || ++ !disp6._is_closed || !disp7._is_closed || !disp8._is_closed || !disp9._is_closed || !disp10._is_closed) && ++ !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event && ++ !disp6._is_event && !disp7._is_event && !disp8._is_event && !disp9._is_event && !disp10._is_event) ++ wait_all(); ++ } ++ ++#if cimg_display==0 ++ ++ //! Wait for any window event occuring in any opened CImgDisplay. ++ static void wait_all() { ++ return _no_display_exception(); ++ } ++ ++ //! Render image into internal display buffer. ++ /** ++ \param img Input image data to render. ++ \note ++ - Convert image data representation into the internal display buffer (architecture-dependent structure). ++ - The content of the associated window is not modified, until paint() is called. ++ - Should not be used for common CImgDisplay uses, since display() is more useful. ++ **/ ++ template ++ CImgDisplay& render(const CImg& img) { ++ return assign(img); ++ } ++ ++ //! Paint internal display buffer on associated window. ++ /** ++ \note ++ - Update the content of the associated window with the internal display buffer, e.g. after a render() call. ++ - Should not be used for common CImgDisplay uses, since display() is more useful. ++ **/ ++ CImgDisplay& paint() { ++ return assign(); ++ } ++ ++ ++ //! Take a snapshot of the current screen content. ++ /** ++ \param x0 X-coordinate of the upper left corner. ++ \param y0 Y-coordinate of the upper left corner. ++ \param x1 X-coordinate of the lower right corner. ++ \param y1 Y-coordinate of the lower right corner. ++ \param[out] img Output screenshot. Can be empty on input ++ **/ ++ template ++ static void screenshot(const int x0, const int y0, const int x1, const int y1, CImg& img) { ++ cimg::unused(x0,y0,x1,y1,&img); ++ _no_display_exception(); ++ } ++ ++ //! Take a snapshot of the associated window content. ++ /** ++ \param[out] img Output snapshot. Can be empty on input. ++ **/ ++ template ++ const CImgDisplay& snapshot(CImg& img) const { ++ cimg::unused(img); ++ _no_display_exception(); ++ return *this; ++ } ++#endif ++ ++ // X11-based implementation ++ //-------------------------- ++#if cimg_display==1 ++ ++ Atom _wm_window_atom, _wm_protocol_atom; ++ Window _window, _background_window; ++ Colormap _colormap; ++ XImage *_image; ++ void *_data; ++#ifdef cimg_use_xshm ++ XShmSegmentInfo *_shminfo; ++#endif ++ ++ static int screen_width() { ++ Display *const dpy = cimg::X11_attr().display; ++ int res = 0; ++ if (!dpy) { ++ Display *const _dpy = XOpenDisplay(0); ++ if (!_dpy) ++ throw CImgDisplayException("CImgDisplay::screen_width(): Failed to open X11 display."); ++ res = DisplayWidth(_dpy,DefaultScreen(_dpy)); ++ XCloseDisplay(_dpy); ++ } else { ++#ifdef cimg_use_xrandr ++ if (cimg::X11_attr().resolutions && cimg::X11_attr().curr_resolution) ++ res = cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].width; ++ else res = DisplayWidth(dpy,DefaultScreen(dpy)); ++#else ++ res = DisplayWidth(dpy,DefaultScreen(dpy)); ++#endif ++ } ++ return res; ++ } ++ ++ static int screen_height() { ++ Display *const dpy = cimg::X11_attr().display; ++ int res = 0; ++ if (!dpy) { ++ Display *const _dpy = XOpenDisplay(0); ++ if (!_dpy) ++ throw CImgDisplayException("CImgDisplay::screen_height(): Failed to open X11 display."); ++ res = DisplayHeight(_dpy,DefaultScreen(_dpy)); ++ XCloseDisplay(_dpy); ++ } else { ++#ifdef cimg_use_xrandr ++ if (cimg::X11_attr().resolutions && cimg::X11_attr().curr_resolution) ++ res = cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].height; ++ else res = DisplayHeight(dpy,DefaultScreen(dpy)); ++#else ++ res = DisplayHeight(dpy,DefaultScreen(dpy)); ++#endif ++ } ++ return res; ++ } ++ ++ static void wait_all() { ++ if (!cimg::X11_attr().display) return; ++ pthread_mutex_lock(&cimg::X11_attr().wait_event_mutex); ++ pthread_cond_wait(&cimg::X11_attr().wait_event,&cimg::X11_attr().wait_event_mutex); ++ pthread_mutex_unlock(&cimg::X11_attr().wait_event_mutex); ++ } ++ ++ void _handle_events(const XEvent *const pevent) { ++ Display *const dpy = cimg::X11_attr().display; ++ XEvent event = *pevent; ++ switch (event.type) { ++ case ClientMessage : { ++ if ((int)event.xclient.message_type==(int)_wm_protocol_atom && ++ (int)event.xclient.data.l[0]==(int)_wm_window_atom) { ++ XUnmapWindow(cimg::X11_attr().display,_window); ++ _is_closed = _is_event = true; ++ pthread_cond_broadcast(&cimg::X11_attr().wait_event); ++ } ++ } break; ++ case ConfigureNotify : { ++ while (XCheckWindowEvent(dpy,_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(dpy,_window,_window_width,_window_height); ++ _is_resized = _is_event = true; ++ pthread_cond_broadcast(&cimg::X11_attr().wait_event); ++ } ++ if (nx!=_window_x || ny!=_window_y) { ++ _window_x = nx; _window_y = ny; _is_moved = _is_event = true; ++ pthread_cond_broadcast(&cimg::X11_attr().wait_event); ++ } ++ } break; ++ case Expose : { ++ while (XCheckWindowEvent(dpy,_window,ExposureMask,&event)) {} ++ _paint(false); ++ if (_is_fullscreen) { ++ XWindowAttributes attr; ++ XGetWindowAttributes(dpy,_window,&attr); ++ while (attr.map_state!=IsViewable) XSync(dpy,0); ++ XSetInputFocus(dpy,_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>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1; ++ switch (event.xbutton.button) { ++ case 1 : set_button(1); break; ++ case 3 : set_button(2); break; ++ case 2 : set_button(3); break; ++ } ++ } while (XCheckWindowEvent(dpy,_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>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1; ++ switch (event.xbutton.button) { ++ case 1 : set_button(1,false); break; ++ case 3 : set_button(2,false); break; ++ case 2 : set_button(3,false); break; ++ case 4 : set_wheel(1); break; ++ case 5 : set_wheel(-1); break; ++ } ++ } while (XCheckWindowEvent(dpy,_window,ButtonReleaseMask,&event)); ++ } break; ++ case KeyPress : { ++ char tmp = 0; KeySym ksym; ++ XLookupString(&event.xkey,&tmp,1,&ksym,0); ++ set_key((unsigned int)ksym,true); ++ } break; ++ case KeyRelease : { ++ char keys_return[32]; // Check that the key has been physically unpressed. ++ XQueryKeymap(dpy,keys_return); ++ const unsigned int kc = event.xkey.keycode, kc1 = kc/8, kc2 = kc%8; ++ const bool is_key_pressed = kc1>=32?false:(keys_return[kc1]>>kc2)&1; ++ if (!is_key_pressed) { ++ char tmp = 0; KeySym ksym; ++ XLookupString(&event.xkey,&tmp,1,&ksym,0); ++ set_key((unsigned int)ksym,false); ++ } ++ } break; ++ case EnterNotify: { ++ while (XCheckWindowEvent(dpy,_window,EnterWindowMask,&event)) {} ++ _mouse_x = event.xmotion.x; ++ _mouse_y = event.xmotion.y; ++ if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1; ++ } break; ++ case LeaveNotify : { ++ while (XCheckWindowEvent(dpy,_window,LeaveWindowMask,&event)) {} ++ _mouse_x = _mouse_y = -1; _is_event = true; ++ pthread_cond_broadcast(&cimg::X11_attr().wait_event); ++ } break; ++ case MotionNotify : { ++ while (XCheckWindowEvent(dpy,_window,PointerMotionMask,&event)) {} ++ _mouse_x = event.xmotion.x; ++ _mouse_y = event.xmotion.y; ++ if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1; ++ _is_event = true; ++ pthread_cond_broadcast(&cimg::X11_attr().wait_event); ++ } break; ++ } ++ } ++ ++ static void* _events_thread(void *arg) { // Thread to manage events for all opened display windows. ++ Display *const dpy = cimg::X11_attr().display; ++ XEvent event; ++ pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,0); ++ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,0); ++ if (!arg) for ( ; ; ) { ++ cimg_lock_display(); ++ bool event_flag = XCheckTypedEvent(dpy,ClientMessage,&event); ++ if (!event_flag) event_flag = XCheckMaskEvent(dpy, ++ ExposureMask | StructureNotifyMask | ButtonPressMask | ++ KeyPressMask | PointerMotionMask | EnterWindowMask | ++ LeaveWindowMask | ButtonReleaseMask | KeyReleaseMask,&event); ++ if (event_flag) ++ for (unsigned int i = 0; i_is_closed && event.xany.window==cimg::X11_attr().wins[i]->_window) ++ cimg::X11_attr().wins[i]->_handle_events(&event); ++ cimg_unlock_display(); ++ pthread_testcancel(); ++ cimg::sleep(8); ++ } ++ return 0; ++ } ++ ++ void _set_colormap(Colormap& _colormap, const unsigned int dim) { ++ XColor *const colormap = new XColor[256]; ++ switch (dim) { ++ case 1 : { // colormap for greyscale images ++ for (unsigned int index = 0; index<256; ++index) { ++ colormap[index].pixel = index; ++ colormap[index].red = colormap[index].green = colormap[index].blue = (unsigned short)(index<<8); ++ colormap[index].flags = DoRed | DoGreen | DoBlue; ++ } ++ } break; ++ case 2 : { // colormap for RG images ++ for (unsigned int index = 0, r = 8; r<256; r+=16) ++ for (unsigned int g = 8; g<256; g+=16) { ++ colormap[index].pixel = index; ++ colormap[index].red = colormap[index].blue = (unsigned short)(r<<8); ++ colormap[index].green = (unsigned short)(g<<8); ++ colormap[index++].flags = DoRed | DoGreen | DoBlue; ++ } ++ } break; ++ default : { // colormap 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) { ++ colormap[index].pixel = index; ++ colormap[index].red = (unsigned short)(r<<8); ++ colormap[index].green = (unsigned short)(g<<8); ++ colormap[index].blue = (unsigned short)(b<<8); ++ colormap[index++].flags = DoRed | DoGreen | DoBlue; ++ } ++ } ++ } ++ XStoreColors(cimg::X11_attr().display,_colormap,colormap,256); ++ delete[] colormap; ++ } ++ ++ void _map_window() { ++ Display *const dpy = cimg::X11_attr().display; ++ bool is_exposed = false, is_mapped = false; ++ XWindowAttributes attr; ++ XEvent event; ++ XMapRaised(dpy,_window); ++ do { // Wait for the window to be mapped. ++ XWindowEvent(dpy,_window,StructureNotifyMask | ExposureMask,&event); ++ switch (event.type) { ++ case MapNotify : is_mapped = true; break; ++ case Expose : is_exposed = true; break; ++ } ++ } while (!is_exposed || !is_mapped); ++ do { // Wait for the window to be visible. ++ XGetWindowAttributes(dpy,_window,&attr); ++ if (attr.map_state!=IsViewable) { XSync(dpy,0); 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 || !_image) return; ++ Display *const dpy = cimg::X11_attr().display; ++ if (wait_expose) { // Send an expose event sticked to display window to force repaint. ++ XEvent event; ++ event.xexpose.type = Expose; ++ event.xexpose.serial = 0; ++ event.xexpose.send_event = 1; ++ event.xexpose.display = dpy; ++ event.xexpose.window = _window; ++ event.xexpose.x = 0; ++ event.xexpose.y = 0; ++ event.xexpose.width = width(); ++ event.xexpose.height = height(); ++ event.xexpose.count = 0; ++ XSendEvent(dpy,_window,0,0,&event); ++ } else { // Repaint directly (may be called from the expose event). ++ GC gc = DefaultGC(dpy,DefaultScreen(dpy)); ++#ifdef cimg_use_xshm ++ if (_shminfo) XShmPutImage(dpy,_window,gc,_image,0,0,0,0,_width,_height,1); ++ else XPutImage(dpy,_window,gc,_image,0,0,0,0,_width,_height); ++#else ++ XPutImage(dpy,_window,gc,_image,0,0,0,0,_width,_height); ++#endif ++ } ++ } ++ ++ template ++ void _resize(T pixel_type, const unsigned int ndimx, const unsigned int ndimy, const bool force_redraw) { ++ Display *const dpy = cimg::X11_attr().display; ++ cimg::unused(pixel_type); ++ ++#ifdef cimg_use_xshm ++ if (_shminfo) { ++ XShmSegmentInfo *const nshminfo = new XShmSegmentInfo; ++ XImage *const nimage = XShmCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)), ++ cimg::X11_attr().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 = 0; ++ cimg::X11_attr().is_shm_enabled = true; ++ XErrorHandler oldXErrorHandler = XSetErrorHandler(_assign_xshm); ++ XShmAttach(dpy,nshminfo); ++ XFlush(dpy); ++ XSetErrorHandler(oldXErrorHandler); ++ if (!cimg::X11_attr().is_shm_enabled) { ++ shmdt(nshminfo->shmaddr); ++ shmctl(nshminfo->shmid,IPC_RMID,0); ++ XDestroyImage(nimage); ++ delete nshminfo; ++ return; ++ } else { ++ T *const ndata = (T*)nimage->data; ++ if (force_redraw) _render_resize((T*)_data,_width,_height,ndata,ndimx,ndimy); ++ else std::memset(ndata,0,sizeof(T)*ndimx*ndimy); ++ XShmDetach(dpy,_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*)std::malloc(ndimx*ndimy*sizeof(T)); ++ if (force_redraw) _render_resize((T*)_data,_width,_height,ndata,ndimx,ndimy); ++ else std::memset(ndata,0,sizeof(T)*ndimx*ndimy); ++ _data = (void*)ndata; ++ XDestroyImage(_image); ++ _image = XCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)), ++ cimg::X11_attr().nb_bits,ZPixmap,0,(char*)_data,ndimx,ndimy,8,0); ++ } ++ } ++ ++ void _init_fullscreen() { ++ if (!_is_fullscreen || _is_closed) return; ++ Display *const dpy = cimg::X11_attr().display; ++ _background_window = 0; ++ ++#ifdef cimg_use_xrandr ++ int foo; ++ if (XRRQueryExtension(dpy,&foo,&foo)) { ++ XRRRotations(dpy,DefaultScreen(dpy),&cimg::X11_attr().curr_rotation); ++ if (!cimg::X11_attr().resolutions) { ++ cimg::X11_attr().resolutions = XRRSizes(dpy,DefaultScreen(dpy),&foo); ++ cimg::X11_attr().nb_resolutions = (unsigned int)foo; ++ } ++ if (cimg::X11_attr().resolutions) { ++ cimg::X11_attr().curr_resolution = 0; ++ for (unsigned int i = 0; i=_width && nh>=_height && ++ nw<=(unsigned int)(cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].width) && ++ nh<=(unsigned int)(cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].height)) ++ cimg::X11_attr().curr_resolution = i; ++ } ++ if (cimg::X11_attr().curr_resolution>0) { ++ XRRScreenConfiguration *config = XRRGetScreenInfo(dpy,DefaultRootWindow(dpy)); ++ XRRSetScreenConfig(dpy,config,DefaultRootWindow(dpy), ++ cimg::X11_attr().curr_resolution,cimg::X11_attr().curr_rotation,CurrentTime); ++ XRRFreeScreenConfigInfo(config); ++ XSync(dpy,0); ++ } ++ } ++ } ++ if (!cimg::X11_attr().resolutions) ++ cimg::warn(_cimgdisplay_instance ++ "init_fullscreen(): Xrandr extension not supported by the X server.", ++ cimgdisplay_instance); ++#endif ++ ++ const unsigned int sx = screen_width(), sy = screen_height(); ++ if (sx==_width && sy==_height) return; ++ XSetWindowAttributes winattr; ++ winattr.override_redirect = 1; ++ _background_window = XCreateWindow(dpy,DefaultRootWindow(dpy),0,0,sx,sy,0,0, ++ InputOutput,CopyFromParent,CWOverrideRedirect,&winattr); ++ const cimg_ulong buf_size = (cimg_ulong)sx*sy*(cimg::X11_attr().nb_bits==8?1: ++ (cimg::X11_attr().nb_bits==16?2:4)); ++ void *background_data = std::malloc(buf_size); ++ std::memset(background_data,0,buf_size); ++ XImage *background_image = XCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)),cimg::X11_attr().nb_bits, ++ ZPixmap,0,(char*)background_data,sx,sy,8,0); ++ XEvent event; ++ XSelectInput(dpy,_background_window,StructureNotifyMask); ++ XMapRaised(dpy,_background_window); ++ do XWindowEvent(dpy,_background_window,StructureNotifyMask,&event); ++ while (event.type!=MapNotify); ++ GC gc = DefaultGC(dpy,DefaultScreen(dpy)); ++#ifdef cimg_use_xshm ++ if (_shminfo) XShmPutImage(dpy,_background_window,gc,background_image,0,0,0,0,sx,sy,0); ++ else XPutImage(dpy,_background_window,gc,background_image,0,0,0,0,sx,sy); ++#else ++ XPutImage(dpy,_background_window,gc,background_image,0,0,0,0,sx,sy); ++#endif ++ XWindowAttributes attr; ++ XGetWindowAttributes(dpy,_background_window,&attr); ++ while (attr.map_state!=IsViewable) XSync(dpy,0); ++ XDestroyImage(background_image); ++ } ++ ++ void _desinit_fullscreen() { ++ if (!_is_fullscreen) return; ++ Display *const dpy = cimg::X11_attr().display; ++ XUngrabKeyboard(dpy,CurrentTime); ++#ifdef cimg_use_xrandr ++ if (cimg::X11_attr().resolutions && cimg::X11_attr().curr_resolution) { ++ XRRScreenConfiguration *config = XRRGetScreenInfo(dpy,DefaultRootWindow(dpy)); ++ XRRSetScreenConfig(dpy,config,DefaultRootWindow(dpy),0,cimg::X11_attr().curr_rotation,CurrentTime); ++ XRRFreeScreenConfigInfo(config); ++ XSync(dpy,0); ++ cimg::X11_attr().curr_resolution = 0; ++ } ++#endif ++ if (_background_window) XDestroyWindow(dpy,_background_window); ++ _background_window = 0; ++ _is_fullscreen = false; ++ } ++ ++ static int _assign_xshm(Display *dpy, XErrorEvent *error) { ++ cimg::unused(dpy,error); ++ cimg::X11_attr().is_shm_enabled = false; ++ return 0; ++ } ++ ++ void _assign(const unsigned int dimw, const unsigned int dimh, const char *const ptitle=0, ++ const unsigned int normalization_type=3, ++ const bool fullscreen_flag=false, const bool closed_flag=false) { ++ cimg::mutex(14); ++ ++ // Allocate space for window title ++ const char *const nptitle = ptitle?ptitle:""; ++ const unsigned int s = (unsigned int)std::strlen(nptitle) + 1; ++ char *const tmp_title = s?new char[s]:0; ++ if (s) std::memcpy(tmp_title,nptitle,s*sizeof(char)); ++ ++ // Destroy previous display window if existing ++ if (!is_empty()) assign(); ++ ++ // Open X11 display and retrieve graphical properties. ++ Display* &dpy = cimg::X11_attr().display; ++ if (!dpy) { ++ dpy = XOpenDisplay(0); ++ if (!dpy) ++ throw CImgDisplayException(_cimgdisplay_instance ++ "assign(): Failed to open X11 display.", ++ cimgdisplay_instance); ++ ++ cimg::X11_attr().nb_bits = DefaultDepth(dpy,DefaultScreen(dpy)); ++ if (cimg::X11_attr().nb_bits!=8 && cimg::X11_attr().nb_bits!=16 && ++ cimg::X11_attr().nb_bits!=24 && cimg::X11_attr().nb_bits!=32) ++ throw CImgDisplayException(_cimgdisplay_instance ++ "assign(): Invalid %u bits screen mode detected " ++ "(only 8, 16, 24 and 32 bits modes are managed).", ++ cimgdisplay_instance, ++ cimg::X11_attr().nb_bits); ++ XVisualInfo vtemplate; ++ vtemplate.visualid = XVisualIDFromVisual(DefaultVisual(dpy,DefaultScreen(dpy))); ++ int nb_visuals; ++ XVisualInfo *vinfo = XGetVisualInfo(dpy,VisualIDMask,&vtemplate,&nb_visuals); ++ if (vinfo && vinfo->red_maskblue_mask) cimg::X11_attr().is_blue_first = true; ++ cimg::X11_attr().byte_order = ImageByteOrder(dpy); ++ XFree(vinfo); ++ ++ cimg_lock_display(); ++ cimg::X11_attr().events_thread = new pthread_t; ++ pthread_create(cimg::X11_attr().events_thread,0,_events_thread,0); ++ } else cimg_lock_display(); ++ ++ // Set display variables. ++ _width = std::min(dimw,(unsigned int)screen_width()); ++ _height = std::min(dimh,(unsigned int)screen_height()); ++ _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 LUT, if 8bits display) ++ if (_is_fullscreen) { ++ if (!_is_closed) _init_fullscreen(); ++ const unsigned int sx = screen_width(), sy = screen_height(); ++ XSetWindowAttributes winattr; ++ winattr.override_redirect = 1; ++ _window = XCreateWindow(dpy,DefaultRootWindow(dpy),(sx - _width)/2,(sy - _height)/2,_width,_height,0,0, ++ InputOutput,CopyFromParent,CWOverrideRedirect,&winattr); ++ } else ++ _window = XCreateSimpleWindow(dpy,DefaultRootWindow(dpy),0,0,_width,_height,0,0L,0L); ++ ++ XSelectInput(dpy,_window, ++ ExposureMask | StructureNotifyMask | ButtonPressMask | KeyPressMask | PointerMotionMask | ++ EnterWindowMask | LeaveWindowMask | ButtonReleaseMask | KeyReleaseMask); ++ ++ XStoreName(dpy,_window,_title?_title:" "); ++ if (cimg::X11_attr().nb_bits==8) { ++ _colormap = XCreateColormap(dpy,_window,DefaultVisual(dpy,DefaultScreen(dpy)),AllocAll); ++ _set_colormap(_colormap,3); ++ XSetWindowColormap(dpy,_window,_colormap); ++ } ++ ++ static const char *const _window_class = cimg_appname; ++ XClassHint *const window_class = XAllocClassHint(); ++ window_class->res_name = (char*)_window_class; ++ window_class->res_class = (char*)_window_class; ++ XSetClassHint(dpy,_window,window_class); ++ XFree(window_class); ++ ++ _window_width = _width; ++ _window_height = _height; ++ ++ // Create XImage ++#ifdef cimg_use_xshm ++ _shminfo = 0; ++ if (XShmQueryExtension(dpy)) { ++ _shminfo = new XShmSegmentInfo; ++ _image = XShmCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)),cimg::X11_attr().nb_bits, ++ ZPixmap,0,_shminfo,_width,_height); ++ if (!_image) { delete _shminfo; _shminfo = 0; } ++ else { ++ _shminfo->shmid = shmget(IPC_PRIVATE,_image->bytes_per_line*_image->height,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 = 0; ++ cimg::X11_attr().is_shm_enabled = true; ++ XErrorHandler oldXErrorHandler = XSetErrorHandler(_assign_xshm); ++ XShmAttach(dpy,_shminfo); ++ XSync(dpy,0); ++ XSetErrorHandler(oldXErrorHandler); ++ if (!cimg::X11_attr().is_shm_enabled) { ++ shmdt(_shminfo->shmaddr); shmctl(_shminfo->shmid,IPC_RMID,0); XDestroyImage(_image); ++ delete _shminfo; _shminfo = 0; ++ } ++ } ++ } ++ } ++ } ++ if (!_shminfo) ++#endif ++ { ++ const cimg_ulong buf_size = (cimg_ulong)_width*_height*(cimg::X11_attr().nb_bits==8?1: ++ (cimg::X11_attr().nb_bits==16?2:4)); ++ _data = std::malloc(buf_size); ++ _image = XCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)),cimg::X11_attr().nb_bits, ++ ZPixmap,0,(char*)_data,_width,_height,8,0); ++ } ++ ++ _wm_window_atom = XInternAtom(dpy,"WM_DELETE_WINDOW",0); ++ _wm_protocol_atom = XInternAtom(dpy,"WM_PROTOCOLS",0); ++ XSetWMProtocols(dpy,_window,&_wm_window_atom,1); ++ ++ if (_is_fullscreen) XGrabKeyboard(dpy,_window,1,GrabModeAsync,GrabModeAsync,CurrentTime); ++ cimg::X11_attr().wins[cimg::X11_attr().nb_wins++]=this; ++ if (!_is_closed) _map_window(); else { _window_x = _window_y = cimg::type::min(); } ++ cimg_unlock_display(); ++ cimg::mutex(14,0); ++ } ++ ++ CImgDisplay& assign() { ++ if (is_empty()) return flush(); ++ Display *const dpy = cimg::X11_attr().display; ++ cimg_lock_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::X11_attr().nb_bits==8) XFreeColormap(dpy,_colormap); ++ _colormap = 0; ++ XSync(dpy,0); ++ ++ // Reset display variables. ++ 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(); ++ ++ cimg_unlock_display(); ++ return *this; ++ } ++ ++ CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *const 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; ++ std::memset(_data,0,(cimg::X11_attr().nb_bits==8?sizeof(unsigned char): ++ (cimg::X11_attr().nb_bits==16?sizeof(unsigned short):sizeof(unsigned int)))* ++ (size_t)_width*_height); ++ return paint(); ++ } ++ ++ template ++ CImgDisplay& assign(const CImg& img, const char *const 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 - 1)/2, ++ (img._height - 1)/2, ++ (img._depth - 1)/2)); ++ _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag); ++ if (_normalization==2) _min = (float)nimg.min_max(_max); ++ return render(nimg).paint(); ++ } ++ ++ template ++ CImgDisplay& assign(const CImgList& list, const char *const 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>'x', &nimg = (img._depth==1)?img:(tmp=img.get_projections2d((img._width - 1)/2, ++ (img._height - 1)/2, ++ (img._depth - 1)/2)); ++ _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag); ++ if (_normalization==2) _min = (float)nimg.min_max(_max); ++ return render(nimg).paint(); ++ } ++ ++ CImgDisplay& assign(const CImgDisplay& disp) { ++ if (!disp) return assign(); ++ _assign(disp._width,disp._height,disp._title,disp._normalization,disp._is_fullscreen,disp._is_closed); ++ std::memcpy(_data,disp._data,(cimg::X11_attr().nb_bits==8?sizeof(unsigned char): ++ cimg::X11_attr().nb_bits==16?sizeof(unsigned short): ++ sizeof(unsigned int))*(size_t)_width*_height); ++ return paint(); ++ } ++ ++ CImgDisplay& resize(const int nwidth, const int nheight, const bool force_redraw=true) { ++ if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign(); ++ if (is_empty()) return assign(nwidth,nheight); ++ Display *const dpy = cimg::X11_attr().display; ++ 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 (_width!=dimx || _height!=dimy || _window_width!=dimx || _window_height!=dimy) { ++ show(); ++ cimg_lock_display(); ++ if (_window_width!=dimx || _window_height!=dimy) { ++ XWindowAttributes attr; ++ for (unsigned int i = 0; i<10; ++i) { ++ XResizeWindow(dpy,_window,dimx,dimy); ++ XGetWindowAttributes(dpy,_window,&attr); ++ if (attr.width==(int)dimx && attr.height==(int)dimy) break; ++ cimg::wait(5); ++ } ++ } ++ if (_width!=dimx || _height!=dimy) switch (cimg::X11_attr().nb_bits) { ++ case 8 : { unsigned char pixel_type = 0; _resize(pixel_type,dimx,dimy,force_redraw); } break; ++ case 16 : { unsigned short pixel_type = 0; _resize(pixel_type,dimx,dimy,force_redraw); } break; ++ default : { unsigned int pixel_type = 0; _resize(pixel_type,dimx,dimy,force_redraw); } ++ } ++ _window_width = _width = dimx; _window_height = _height = dimy; ++ cimg_unlock_display(); ++ } ++ _is_resized = false; ++ if (_is_fullscreen) move((screen_width() - _width)/2,(screen_height() - _height)/2); ++ if (force_redraw) return paint(); ++ return *this; ++ } ++ ++ CImgDisplay& toggle_fullscreen(const bool force_redraw=true) { ++ if (is_empty()) return *this; ++ if (force_redraw) { ++ const cimg_ulong buf_size = (cimg_ulong)_width*_height* ++ (cimg::X11_attr().nb_bits==8?1:(cimg::X11_attr().nb_bits==16?2:4)); ++ void *image_data = std::malloc(buf_size); ++ std::memcpy(image_data,_data,buf_size); ++ assign(_width,_height,_title,_normalization,!_is_fullscreen,false); ++ std::memcpy(_data,image_data,buf_size); ++ std::free(image_data); ++ return paint(); ++ } ++ return assign(_width,_height,_title,_normalization,!_is_fullscreen,false); ++ } ++ ++ CImgDisplay& show() { ++ if (is_empty() || !_is_closed) return *this; ++ cimg_lock_display(); ++ if (_is_fullscreen) _init_fullscreen(); ++ _map_window(); ++ _is_closed = false; ++ cimg_unlock_display(); ++ return paint(); ++ } ++ ++ CImgDisplay& close() { ++ if (is_empty() || _is_closed) return *this; ++ Display *const dpy = cimg::X11_attr().display; ++ cimg_lock_display(); ++ if (_is_fullscreen) _desinit_fullscreen(); ++ XUnmapWindow(dpy,_window); ++ _window_x = _window_y = -1; ++ _is_closed = true; ++ cimg_unlock_display(); ++ return *this; ++ } ++ ++ CImgDisplay& move(const int posx, const int posy) { ++ if (is_empty()) return *this; ++ if (_window_x!=posx || _window_y!=posy) { ++ show(); ++ Display *const dpy = cimg::X11_attr().display; ++ cimg_lock_display(); ++ XMoveWindow(dpy,_window,posx,posy); ++ _window_x = posx; _window_y = posy; ++ cimg_unlock_display(); ++ } ++ _is_moved = false; ++ return paint(); ++ } ++ ++ CImgDisplay& show_mouse() { ++ if (is_empty()) return *this; ++ Display *const dpy = cimg::X11_attr().display; ++ cimg_lock_display(); ++ XUndefineCursor(dpy,_window); ++ cimg_unlock_display(); ++ return *this; ++ } ++ ++ CImgDisplay& hide_mouse() { ++ if (is_empty()) return *this; ++ Display *const dpy = cimg::X11_attr().display; ++ cimg_lock_display(); ++ static const char pix_data[8] = { 0 }; ++ XColor col; ++ col.red = col.green = col.blue = 0; ++ Pixmap pix = XCreateBitmapFromData(dpy,_window,pix_data,8,8); ++ Cursor cur = XCreatePixmapCursor(dpy,pix,pix,&col,&col,0,0); ++ XFreePixmap(dpy,pix); ++ XDefineCursor(dpy,_window,cur); ++ cimg_unlock_display(); ++ return *this; ++ } ++ ++ CImgDisplay& set_mouse(const int posx, const int posy) { ++ if (is_empty() || _is_closed) return *this; ++ Display *const dpy = cimg::X11_attr().display; ++ cimg_lock_display(); ++ XWarpPointer(dpy,0L,_window,0,0,0,0,posx,posy); ++ _mouse_x = posx; _mouse_y = posy; ++ _is_moved = false; ++ XSync(dpy,0); ++ cimg_unlock_display(); ++ return *this; ++ } ++ ++ CImgDisplay& set_title(const char *const format, ...) { ++ if (is_empty()) return *this; ++ char *const tmp = new char[1024]; ++ va_list ap; ++ va_start(ap, format); ++ cimg_vsnprintf(tmp,1024,format,ap); ++ va_end(ap); ++ if (!std::strcmp(_title,tmp)) { delete[] tmp; return *this; } ++ delete[] _title; ++ const unsigned int s = (unsigned int)std::strlen(tmp) + 1; ++ _title = new char[s]; ++ std::memcpy(_title,tmp,s*sizeof(char)); ++ Display *const dpy = cimg::X11_attr().display; ++ cimg_lock_display(); ++ XStoreName(dpy,_window,tmp); ++ cimg_unlock_display(); ++ delete[] tmp; ++ return *this; ++ } ++ ++ template ++ CImgDisplay& display(const CImg& img) { ++ if (!img) ++ throw CImgArgumentException(_cimgdisplay_instance ++ "display(): Empty specified image.", ++ cimgdisplay_instance); ++ if (is_empty()) return assign(img); ++ return render(img).paint(false); ++ } ++ ++ CImgDisplay& paint(const bool wait_expose=true) { ++ if (is_empty()) return *this; ++ cimg_lock_display(); ++ _paint(wait_expose); ++ cimg_unlock_display(); ++ return *this; ++ } ++ ++ template ++ CImgDisplay& render(const CImg& img, const bool flag8=false) { ++ if (!img) ++ throw CImgArgumentException(_cimgdisplay_instance ++ "render(): Empty specified image.", ++ cimgdisplay_instance); ++ if (is_empty()) return *this; ++ if (img._depth!=1) return render(img.get_projections2d((img._width - 1)/2,(img._height - 1)/2, ++ (img._depth - 1)/2)); ++ if (cimg::X11_attr().nb_bits==8 && (img._width!=_width || img._height!=_height)) ++ return render(img.get_resize(_width,_height,1,-100,1)); ++ if (cimg::X11_attr().nb_bits==8 && !flag8 && img._spectrum==3) { ++ static const CImg::ucharT> default_colormap = CImg::ucharT>::default_LUT256(); ++ return render(img.get_index(default_colormap,1,false)); ++ } ++ ++ const T ++ *data1 = img._data, ++ *data2 = (img._spectrum>1)?img.data(0,0,0,1):data1, ++ *data3 = (img._spectrum>2)?img.data(0,0,0,2):data1; ++ ++ if (cimg::X11_attr().is_blue_first) cimg::swap(data1,data3); ++ cimg_lock_display(); ++ ++ if (!_normalization || (_normalization==3 && cimg::type::string()==cimg::type::string())) { ++ _min = _max = 0; ++ switch (cimg::X11_attr().nb_bits) { ++ case 8 : { // 256 colormap, no normalization ++ _set_colormap(_colormap,img._spectrum); ++ unsigned char ++ *const ndata = (img._width==_width && img._height==_height)?(unsigned char*)_data: ++ new unsigned char[(size_t)img._width*img._height], ++ *ptrd = (unsigned char*)ndata; ++ switch (img._spectrum) { ++ case 1 : ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) ++ (*ptrd++) = (unsigned char)*(data1++); ++ break; ++ case 2 : for (cimg_ulong xy = (cimg_ulong)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 (cimg_ulong xy = (cimg_ulong)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[(size_t)img._width*img._height]; ++ unsigned char *ptrd = (unsigned char*)ndata; ++ const unsigned int M = 248; ++ switch (img._spectrum) { ++ case 1 : ++ if (cimg::X11_attr().byte_order) ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char val = (unsigned char)*(data1++), G = val>>2; ++ ptrd[0] = (val&M) | (G>>3); ++ ptrd[1] = (G<<5) | (G>>1); ++ ptrd+=2; ++ } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char val = (unsigned char)*(data1++), G = val>>2; ++ ptrd[0] = (G<<5) | (G>>1); ++ ptrd[1] = (val&M) | (G>>3); ++ ptrd+=2; ++ } ++ break; ++ case 2 : ++ if (cimg::X11_attr().byte_order) ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char G = (unsigned char)*(data2++)>>2; ++ ptrd[0] = ((unsigned char)*(data1++)&M) | (G>>3); ++ ptrd[1] = (G<<5); ++ ptrd+=2; ++ } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char G = (unsigned char)*(data2++)>>2; ++ ptrd[0] = (G<<5); ++ ptrd[1] = ((unsigned char)*(data1++)&M) | (G>>3); ++ ptrd+=2; ++ } ++ break; ++ default : ++ if (cimg::X11_attr().byte_order) ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char G = (unsigned char)*(data2++)>>2; ++ ptrd[0] = ((unsigned char)*(data1++)&M) | (G>>3); ++ ptrd[1] = (G<<5) | ((unsigned char)*(data3++)>>3); ++ ptrd+=2; ++ } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char G = (unsigned char)*(data2++)>>2; ++ ptrd[0] = (G<<5) | ((unsigned char)*(data3++)>>3); ++ ptrd[1] = ((unsigned char)*(data1++)&M) | (G>>3); ++ ptrd+=2; ++ } ++ } ++ 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[(size_t)img._width*img._height]; ++ if (sizeof(int)==4) { // 32 bits int uses optimized version ++ unsigned int *ptrd = ndata; ++ switch (img._spectrum) { ++ case 1 : ++ if (cimg::X11_attr().byte_order==cimg::endianness()) ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char val = (unsigned char)*(data1++); ++ *(ptrd++) = (val<<16) | (val<<8) | val; ++ } ++ else ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char val = (unsigned char)*(data1++); ++ *(ptrd++) = (val<<16) | (val<<8) | val; ++ } ++ break; ++ case 2 : ++ if (cimg::X11_attr().byte_order==cimg::endianness()) ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) ++ *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8); ++ else ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) ++ *(ptrd++) = ((unsigned char)*(data2++)<<16) | ((unsigned char)*(data1++)<<8); ++ break; ++ default : ++ if (cimg::X11_attr().byte_order==cimg::endianness()) ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) ++ *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8) | ++ (unsigned char)*(data3++); ++ else ++ for (cimg_ulong xy = (cimg_ulong)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._spectrum) { ++ case 1 : ++ if (cimg::X11_attr().byte_order) ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ ptrd[0] = 0; ++ ptrd[1] = (unsigned char)*(data1++); ++ ptrd[2] = 0; ++ ptrd[3] = 0; ++ ptrd+=4; ++ } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ ptrd[0] = 0; ++ ptrd[1] = 0; ++ ptrd[2] = (unsigned char)*(data1++); ++ ptrd[3] = 0; ++ ptrd+=4; ++ } ++ break; ++ case 2 : ++ if (cimg::X11_attr().byte_order) cimg::swap(data1,data2); ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ ptrd[0] = 0; ++ ptrd[1] = (unsigned char)*(data2++); ++ ptrd[2] = (unsigned char)*(data1++); ++ ptrd[3] = 0; ++ ptrd+=4; ++ } ++ break; ++ default : ++ if (cimg::X11_attr().byte_order) ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ ptrd[0] = 0; ++ ptrd[1] = (unsigned char)*(data1++); ++ ptrd[2] = (unsigned char)*(data2++); ++ ptrd[3] = (unsigned char)*(data3++); ++ ptrd+=4; ++ } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ ptrd[0] = (unsigned char)*(data3++); ++ ptrd[1] = (unsigned char)*(data2++); ++ ptrd[2] = (unsigned char)*(data1++); ++ ptrd[3] = 0; ++ ptrd+=4; ++ } ++ } ++ } ++ 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.min_max(_max); ++ else { _min = (float)cimg::type::min(); _max = (float)cimg::type::max(); } ++ } else if ((_min>_max) || _normalization==1) _min = (float)img.min_max(_max); ++ const float delta = _max - _min, mm = 255/(delta?delta:1.0f); ++ switch (cimg::X11_attr().nb_bits) { ++ case 8 : { // 256 colormap, with normalization ++ _set_colormap(_colormap,img._spectrum); ++ unsigned char *const ndata = (img._width==_width && img._height==_height)?(unsigned char*)_data: ++ new unsigned char[(size_t)img._width*img._height]; ++ unsigned char *ptrd = (unsigned char*)ndata; ++ switch (img._spectrum) { ++ case 1 : for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char R = (unsigned char)((*(data1++) - _min)*mm); ++ *(ptrd++) = R; ++ } break; ++ case 2 : for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char ++ R = (unsigned char)((*(data1++) - _min)*mm), ++ G = (unsigned char)((*(data2++) - _min)*mm); ++ (*ptrd++) = (R&0xf0) | (G>>4); ++ } break; ++ default : ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char ++ R = (unsigned char)((*(data1++) - _min)*mm), ++ G = (unsigned char)((*(data2++) - _min)*mm), ++ B = (unsigned char)((*(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[(size_t)img._width*img._height]; ++ unsigned char *ptrd = (unsigned char*)ndata; ++ const unsigned int M = 248; ++ switch (img._spectrum) { ++ case 1 : ++ if (cimg::X11_attr().byte_order) ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char val = (unsigned char)((*(data1++) - _min)*mm), G = val>>2; ++ ptrd[0] = (val&M) | (G>>3); ++ ptrd[1] = (G<<5) | (val>>3); ++ ptrd+=2; ++ } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char val = (unsigned char)((*(data1++) - _min)*mm), G = val>>2; ++ ptrd[0] = (G<<5) | (val>>3); ++ ptrd[1] = (val&M) | (G>>3); ++ ptrd+=2; ++ } ++ break; ++ case 2 : ++ if (cimg::X11_attr().byte_order) ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char G = (unsigned char)((*(data2++) - _min)*mm)>>2; ++ ptrd[0] = ((unsigned char)((*(data1++) - _min)*mm)&M) | (G>>3); ++ ptrd[1] = (G<<5); ++ ptrd+=2; ++ } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char G = (unsigned char)((*(data2++) - _min)*mm)>>2; ++ ptrd[0] = (G<<5); ++ ptrd[1] = ((unsigned char)((*(data1++) - _min)*mm)&M) | (G>>3); ++ ptrd+=2; ++ } ++ break; ++ default : ++ if (cimg::X11_attr().byte_order) ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char G = (unsigned char)((*(data2++) - _min)*mm)>>2; ++ ptrd[0] = ((unsigned char)((*(data1++) - _min)*mm)&M) | (G>>3); ++ ptrd[1] = (G<<5) | ((unsigned char)((*(data3++) - _min)*mm)>>3); ++ ptrd+=2; ++ } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char G = (unsigned char)((*(data2++) - _min)*mm)>>2; ++ ptrd[0] = (G<<5) | ((unsigned char)((*(data3++) - _min)*mm)>>3); ++ ptrd[1] = ((unsigned char)((*(data1++) - _min)*mm)&M) | (G>>3); ++ ptrd+=2; ++ } ++ } ++ 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[(size_t)img._width*img._height]; ++ if (sizeof(int)==4) { // 32 bits int uses optimized version ++ unsigned int *ptrd = ndata; ++ switch (img._spectrum) { ++ case 1 : ++ if (cimg::X11_attr().byte_order==cimg::endianness()) ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char val = (unsigned char)((*(data1++) - _min)*mm); ++ *(ptrd++) = (val<<16) | (val<<8) | val; ++ } ++ else ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char val = (unsigned char)((*(data1++) - _min)*mm); ++ *(ptrd++) = (val<<24) | (val<<16) | (val<<8); ++ } ++ break; ++ case 2 : ++ if (cimg::X11_attr().byte_order==cimg::endianness()) ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) ++ *(ptrd++) = ++ ((unsigned char)((*(data1++) - _min)*mm)<<16) | ++ ((unsigned char)((*(data2++) - _min)*mm)<<8); ++ else ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) ++ *(ptrd++) = ++ ((unsigned char)((*(data2++) - _min)*mm)<<16) | ++ ((unsigned char)((*(data1++) - _min)*mm)<<8); ++ break; ++ default : ++ if (cimg::X11_attr().byte_order==cimg::endianness()) ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) ++ *(ptrd++) = ++ ((unsigned char)((*(data1++) - _min)*mm)<<16) | ++ ((unsigned char)((*(data2++) - _min)*mm)<<8) | ++ (unsigned char)((*(data3++) - _min)*mm); ++ else ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) ++ *(ptrd++) = ++ ((unsigned char)((*(data3++) - _min)*mm)<<24) | ++ ((unsigned char)((*(data2++) - _min)*mm)<<16) | ++ ((unsigned char)((*(data1++) - _min)*mm)<<8); ++ } ++ } else { ++ unsigned char *ptrd = (unsigned char*)ndata; ++ switch (img._spectrum) { ++ case 1 : ++ if (cimg::X11_attr().byte_order) ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char val = (unsigned char)((*(data1++) - _min)*mm); ++ ptrd[0] = 0; ++ ptrd[1] = val; ++ ptrd[2] = val; ++ ptrd[3] = val; ++ ptrd+=4; ++ } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char val = (unsigned char)((*(data1++) - _min)*mm); ++ ptrd[0] = val; ++ ptrd[1] = val; ++ ptrd[2] = val; ++ ptrd[3] = 0; ++ ptrd+=4; ++ } ++ break; ++ case 2 : ++ if (cimg::X11_attr().byte_order) cimg::swap(data1,data2); ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ ptrd[0] = 0; ++ ptrd[1] = (unsigned char)((*(data2++) - _min)*mm); ++ ptrd[2] = (unsigned char)((*(data1++) - _min)*mm); ++ ptrd[3] = 0; ++ ptrd+=4; ++ } ++ break; ++ default : ++ if (cimg::X11_attr().byte_order) ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ ptrd[0] = 0; ++ ptrd[1] = (unsigned char)((*(data1++) - _min)*mm); ++ ptrd[2] = (unsigned char)((*(data2++) - _min)*mm); ++ ptrd[3] = (unsigned char)((*(data3++) - _min)*mm); ++ ptrd+=4; ++ } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ ptrd[0] = (unsigned char)((*(data3++) - _min)*mm); ++ ptrd[1] = (unsigned char)((*(data2++) - _min)*mm); ++ ptrd[2] = (unsigned char)((*(data1++) - _min)*mm); ++ ptrd[3] = 0; ++ ptrd+=4; ++ } ++ } ++ } ++ if (ndata!=_data) { ++ _render_resize(ndata,img._width,img._height,(unsigned int*)_data,_width,_height); ++ delete[] ndata; ++ } ++ } ++ } ++ } ++ cimg_unlock_display(); ++ return *this; ++ } ++ ++ template ++ static void screenshot(const int x0, const int y0, const int x1, const int y1, CImg& img) { ++ img.assign(); ++ Display *dpy = cimg::X11_attr().display; ++ cimg_lock_display(); ++ if (!dpy) { ++ dpy = XOpenDisplay(0); ++ if (!dpy) ++ throw CImgDisplayException("CImgDisplay::screenshot(): Failed to open X11 display."); ++ } ++ Window root = DefaultRootWindow(dpy); ++ XWindowAttributes gwa; ++ XGetWindowAttributes(dpy,root,&gwa); ++ const int width = gwa.width, height = gwa.height; ++ int _x0 = x0, _y0 = y0, _x1 = x1, _y1 = y1; ++ if (_x0>_x1) cimg::swap(_x0,_x1); ++ if (_y0>_y1) cimg::swap(_y0,_y1); ++ ++ XImage *image = 0; ++ if (_x1>=0 && _x0=0 && _y0red_mask, ++ green_mask = image->green_mask, ++ blue_mask = image->blue_mask; ++ img.assign(image->width,image->height,1,3); ++ T *pR = img.data(0,0,0,0), *pG = img.data(0,0,0,1), *pB = img.data(0,0,0,2); ++ cimg_forXY(img,x,y) { ++ const unsigned long pixel = XGetPixel(image,x,y); ++ *(pR++) = (T)((pixel & red_mask)>>16); ++ *(pG++) = (T)((pixel & green_mask)>>8); ++ *(pB++) = (T)(pixel & blue_mask); ++ } ++ XDestroyImage(image); ++ } ++ } ++ if (!cimg::X11_attr().display) XCloseDisplay(dpy); ++ cimg_unlock_display(); ++ if (img.is_empty()) ++ throw CImgDisplayException("CImgDisplay::screenshot(): Failed to take screenshot " ++ "with coordinates (%d,%d)-(%d,%d).", ++ x0,y0,x1,y1); ++ } ++ ++ template ++ const CImgDisplay& snapshot(CImg& img) const { ++ if (is_empty()) { img.assign(); return *this; } ++ const unsigned char *ptrs = (unsigned char*)_data; ++ img.assign(_width,_height,1,3); ++ T ++ *data1 = img.data(0,0,0,0), ++ *data2 = img.data(0,0,0,1), ++ *data3 = img.data(0,0,0,2); ++ if (cimg::X11_attr().is_blue_first) cimg::swap(data1,data3); ++ switch (cimg::X11_attr().nb_bits) { ++ case 8 : { ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char val = *(ptrs++); ++ *(data1++) = (T)(val&0xe0); ++ *(data2++) = (T)((val&0x1c)<<3); ++ *(data3++) = (T)(val<<6); ++ } ++ } break; ++ case 16 : { ++ if (cimg::X11_attr().byte_order) for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char ++ val0 = ptrs[0], ++ val1 = ptrs[1]; ++ ptrs+=2; ++ *(data1++) = (T)(val0&0xf8); ++ *(data2++) = (T)((val0<<5) | ((val1&0xe0)>>5)); ++ *(data3++) = (T)(val1<<3); ++ } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned short ++ val0 = ptrs[0], ++ val1 = ptrs[1]; ++ ptrs+=2; ++ *(data1++) = (T)(val1&0xf8); ++ *(data2++) = (T)((val1<<5) | ((val0&0xe0)>>5)); ++ *(data3++) = (T)(val0<<3); ++ } ++ } break; ++ default : { ++ if (cimg::X11_attr().byte_order) for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ ++ptrs; ++ *(data1++) = (T)ptrs[0]; ++ *(data2++) = (T)ptrs[1]; ++ *(data3++) = (T)ptrs[2]; ++ ptrs+=3; ++ } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ *(data3++) = (T)ptrs[0]; ++ *(data2++) = (T)ptrs[1]; ++ *(data1++) = (T)ptrs[2]; ++ ptrs+=3; ++ ++ptrs; ++ } ++ } ++ } ++ return *this; ++ } ++ ++ // Windows-based implementation. ++ //------------------------------- ++#elif cimg_display==2 ++ ++ bool _is_mouse_tracked, _is_cursor_visible; ++ HANDLE _thread, _is_created, _mutex; ++ HWND _window, _background_window; ++ CLIENTCREATESTRUCT _ccs; ++ unsigned int *_data; ++ DEVMODE _curr_mode; ++ BITMAPINFO _bmi; ++ HDC _hdc; ++ ++ static int screen_width() { ++ DEVMODE mode; ++ mode.dmSize = sizeof(DEVMODE); ++ mode.dmDriverExtra = 0; ++ EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&mode); ++ return (int)mode.dmPelsWidth; ++ } ++ ++ static int screen_height() { ++ DEVMODE mode; ++ mode.dmSize = sizeof(DEVMODE); ++ mode.dmDriverExtra = 0; ++ EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&mode); ++ return (int)mode.dmPelsHeight; ++ } ++ ++ static void wait_all() { ++ WaitForSingleObject(cimg::Win32_attr().wait_event,INFINITE); ++ } ++ ++ static LRESULT APIENTRY _handle_events(HWND window, UINT msg, WPARAM wParam, LPARAM lParam) { ++#ifdef _WIN64 ++ CImgDisplay *const disp = (CImgDisplay*)GetWindowLongPtr(window,GWLP_USERDATA); ++#else ++ CImgDisplay *const 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; ++ disp->set_button().set_key(0).set_key(0,false)._is_closed = true; ++ ReleaseMutex(disp->_mutex); ++ ShowWindow(disp->_window,SW_HIDE); ++ disp->_is_event = true; ++ SetEvent(cimg::Win32_attr().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::Win32_attr().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::Win32_attr().wait_event); ++ } ++ ReleaseMutex(disp->_mutex); ++ } break; ++ case WM_PAINT : ++ disp->paint(); ++ cimg::mutex(15); ++ if (disp->_is_cursor_visible) while (ShowCursor(TRUE)<0); else while (ShowCursor(FALSE)>=0); ++ cimg::mutex(15,0); ++ break; ++ case WM_ERASEBKGND : ++ // return 0; ++ break; ++ case WM_KEYDOWN : ++ disp->set_key((unsigned int)wParam); ++ SetEvent(cimg::Win32_attr().wait_event); ++ break; ++ case WM_KEYUP : ++ disp->set_key((unsigned int)wParam,false); ++ SetEvent(cimg::Win32_attr().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->_is_mouse_tracked) { ++ TRACKMOUSEEVENT tme; ++ tme.cbSize = sizeof(TRACKMOUSEEVENT); ++ tme.dwFlags = TME_LEAVE; ++ tme.hwndTrack = disp->_window; ++ if (TrackMouseEvent(&tme)) disp->_is_mouse_tracked = true; ++ } ++#endif ++ if (disp->_mouse_x<0 || disp->_mouse_y<0 || disp->_mouse_x>=disp->width() || disp->_mouse_y>=disp->height()) ++ disp->_mouse_x = disp->_mouse_y = -1; ++ disp->_is_event = true; ++ SetEvent(cimg::Win32_attr().wait_event); ++ cimg::mutex(15); ++ if (disp->_is_cursor_visible) while (ShowCursor(TRUE)<0); else while (ShowCursor(FALSE)>=0); ++ cimg::mutex(15,0); ++ } break; ++ case WM_MOUSELEAVE : { ++ disp->_mouse_x = disp->_mouse_y = -1; ++ disp->_is_mouse_tracked = false; ++ cimg::mutex(15); ++ while (ShowCursor(TRUE)<0) {} ++ cimg::mutex(15,0); ++ } break; ++ case WM_LBUTTONDOWN : ++ disp->set_button(1); ++ SetEvent(cimg::Win32_attr().wait_event); ++ break; ++ case WM_RBUTTONDOWN : ++ disp->set_button(2); ++ SetEvent(cimg::Win32_attr().wait_event); ++ break; ++ case WM_MBUTTONDOWN : ++ disp->set_button(3); ++ SetEvent(cimg::Win32_attr().wait_event); ++ break; ++ case WM_LBUTTONUP : ++ disp->set_button(1,false); ++ SetEvent(cimg::Win32_attr().wait_event); ++ break; ++ case WM_RBUTTONUP : ++ disp->set_button(2,false); ++ SetEvent(cimg::Win32_attr().wait_event); ++ break; ++ case WM_MBUTTONUP : ++ disp->set_button(3,false); ++ SetEvent(cimg::Win32_attr().wait_event); ++ break; ++ case 0x020A : // WM_MOUSEWHEEL: ++ disp->set_wheel((int)((short)HIWORD(wParam))/120); ++ SetEvent(cimg::Win32_attr().wait_event); ++ } ++ return DefWindowProc(window,msg,wParam,lParam); ++ } ++ ++ static DWORD WINAPI _events_thread(void* arg) { ++ CImgDisplay *const disp = (CImgDisplay*)(((void**)arg)[0]); ++ const char *const 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 = -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[(size_t)disp->_width*disp->_height]; ++ if (!disp->_is_fullscreen) { // Normal window ++ RECT rect; ++ rect.left = rect.top = 0; rect.right = (LONG)disp->_width - 1; rect.bottom = (LONG)disp->_height - 1; ++ AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false); ++ const int ++ border1 = (int)((rect.right - rect.left + 1 - disp->_width)/2), ++ border2 = (int)(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 = (unsigned int)screen_width(), ++ sy = (unsigned int)screen_height(); ++ 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->_is_created); ++ while (GetMessage(&msg,0,0,0)) DispatchMessage(&msg); ++ return 0; ++ } ++ ++ CImgDisplay& _update_window_pos() { ++ if (_is_closed) _window_x = _window_y = -1; ++ else { ++ RECT rect; ++ rect.left = rect.top = 0; rect.right = (LONG)_width - 1; rect.bottom = (LONG)_height - 1; ++ AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false); ++ const int ++ border1 = (int)((rect.right - rect.left + 1 - _width)/2), ++ border2 = (int)(rect.bottom - rect.top + 1 - _height - border1); ++ GetWindowRect(_window,&rect); ++ _window_x = rect.left + border1; ++ _window_y = rect.top + border2; ++ } ++ return *this; ++ } ++ ++ void _init_fullscreen() { ++ _background_window = 0; ++ if (!_is_fullscreen || _is_closed) _curr_mode.dmSize = 0; ++ else { ++ 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 = (unsigned int)screen_width(), ++ sy = (unsigned int)screen_height(); ++ 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); ++ } ++ } ++ } ++ ++ void _desinit_fullscreen() { ++ if (!_is_fullscreen) return; ++ 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 *const ptitle=0, ++ const unsigned int normalization_type=3, ++ const bool fullscreen_flag=false, const bool closed_flag=false) { ++ ++ // Allocate space for window title ++ const char *const nptitle = ptitle?ptitle:""; ++ const unsigned int s = (unsigned int)std::strlen(nptitle) + 1; ++ char *const tmp_title = s?new char[s]:0; ++ if (s) std::memcpy(tmp_title,nptitle,s*sizeof(char)); ++ ++ // Destroy previous window if existing ++ if (!is_empty()) assign(); ++ ++ // Set display variables ++ _width = std::min(dimw,(unsigned int)screen_width()); ++ _height = std::min(dimh,(unsigned int)screen_height()); ++ _normalization = normalization_type<4?normalization_type:3; ++ _is_fullscreen = fullscreen_flag; ++ _window_x = _window_y = 0; ++ _is_closed = closed_flag; ++ _is_cursor_visible = true; ++ _is_mouse_tracked = false; ++ _title = tmp_title; ++ flush(); ++ if (_is_fullscreen) _init_fullscreen(); ++ ++ // Create event thread ++ void *const arg = (void*)(new void*[2]); ++ ((void**)arg)[0] = (void*)this; ++ ((void**)arg)[1] = (void*)_title; ++ _mutex = CreateMutex(0,FALSE,0); ++ _is_created = CreateEvent(0,FALSE,FALSE,0); ++ _thread = CreateThread(0,0,_events_thread,arg,0,0); ++ WaitForSingleObject(_is_created,INFINITE); ++ return *this; ++ } ++ ++ CImgDisplay& assign() { ++ if (is_empty()) return flush(); ++ DestroyWindow(_window); ++ TerminateThread(_thread,0); ++ delete[] _data; ++ delete[] _title; ++ _data = 0; ++ _title = 0; ++ 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 *const 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; ++ std::memset(_data,0,sizeof(unsigned int)*_width*_height); ++ return paint(); ++ } ++ ++ template ++ CImgDisplay& assign(const CImg& img, const char *const 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 - 1)/2, ++ (img._height - 1)/2, ++ (img._depth - 1)/2)); ++ _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag); ++ if (_normalization==2) _min = (float)nimg.min_max(_max); ++ return display(nimg); ++ } ++ ++ template ++ CImgDisplay& assign(const CImgList& list, const char *const 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>'x', &nimg = (img._depth==1)?img:(tmp=img.get_projections2d((img._width - 1)/2, ++ (img._height - 1)/2, ++ (img._depth - 1)/2)); ++ _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag); ++ if (_normalization==2) _min = (float)nimg.min_max(_max); ++ return display(nimg); ++ } ++ ++ CImgDisplay& assign(const CImgDisplay& disp) { ++ if (!disp) return assign(); ++ _assign(disp._width,disp._height,disp._title,disp._normalization,disp._is_fullscreen,disp._is_closed); ++ std::memcpy(_data,disp._data,sizeof(unsigned int)*_width*_height); ++ return paint(); ++ } ++ ++ CImgDisplay& resize(const int nwidth, const int nheight, const bool force_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 (_width!=dimx || _height!=dimy || _window_width!=dimx || _window_height!=dimy) { ++ if (_window_width!=dimx || _window_height!=dimy) { ++ RECT rect; rect.left = rect.top = 0; rect.right = (LONG)dimx - 1; rect.bottom = (LONG)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 *const ndata = new unsigned int[dimx*dimy]; ++ if (force_redraw) _render_resize(_data,_width,_height,ndata,dimx,dimy); ++ else std::memset(ndata,0x80,sizeof(unsigned int)*dimx*dimy); ++ delete[] _data; ++ _data = ndata; ++ _bmi.bmiHeader.biWidth = (LONG)dimx; ++ _bmi.bmiHeader.biHeight = -(int)dimy; ++ _width = dimx; ++ _height = dimy; ++ } ++ _window_width = dimx; _window_height = dimy; ++ show(); ++ } ++ _is_resized = false; ++ if (_is_fullscreen) move((screen_width() - width())/2,(screen_height() - height())/2); ++ if (force_redraw) return paint(); ++ return *this; ++ } ++ ++ CImgDisplay& toggle_fullscreen(const bool force_redraw=true) { ++ if (is_empty()) return *this; ++ if (force_redraw) { ++ const cimg_ulong buf_size = (cimg_ulong)_width*_height*4; ++ void *odata = std::malloc(buf_size); ++ if (odata) { ++ std::memcpy(odata,_data,buf_size); ++ assign(_width,_height,_title,_normalization,!_is_fullscreen,false); ++ std::memcpy(_data,odata,buf_size); ++ std::free(odata); ++ } ++ return paint(); ++ } ++ return assign(_width,_height,_title,_normalization,!_is_fullscreen,false); ++ } ++ ++ CImgDisplay& show() { ++ if (is_empty() || !_is_closed) return *this; ++ _is_closed = false; ++ if (_is_fullscreen) _init_fullscreen(); ++ ShowWindow(_window,SW_SHOW); ++ _update_window_pos(); ++ return paint(); ++ } ++ ++ CImgDisplay& close() { ++ if (is_empty() || _is_closed) return *this; ++ _is_closed = true; ++ if (_is_fullscreen) _desinit_fullscreen(); ++ ShowWindow(_window,SW_HIDE); ++ _window_x = _window_y = 0; ++ return *this; ++ } ++ ++ CImgDisplay& move(const int posx, const int posy) { ++ if (is_empty()) return *this; ++ if (_window_x!=posx || _window_y!=posy) { ++ if (!_is_fullscreen) { ++ RECT rect; ++ rect.left = rect.top = 0; rect.right = (LONG)_window_width - 1; rect.bottom = (LONG)_window_height - 1; ++ AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false); ++ const int ++ border1 = (int)((rect.right - rect.left + 1 -_width)/2), ++ border2 = (int)(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; ++ show(); ++ } ++ _is_moved = false; ++ return *this; ++ } ++ ++ CImgDisplay& show_mouse() { ++ if (is_empty()) return *this; ++ _is_cursor_visible = true; ++ return *this; ++ } ++ ++ CImgDisplay& hide_mouse() { ++ if (is_empty()) return *this; ++ _is_cursor_visible = false; ++ return *this; ++ } ++ ++ CImgDisplay& set_mouse(const int posx, const int posy) { ++ if (is_empty() || _is_closed || posx<0 || posy<0) return *this; ++ _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 *const format, ...) { ++ if (is_empty()) return *this; ++ char *const tmp = new char[1024]; ++ va_list ap; ++ va_start(ap, format); ++ cimg_vsnprintf(tmp,1024,format,ap); ++ va_end(ap); ++ if (!std::strcmp(_title,tmp)) { delete[] tmp; return *this; } ++ delete[] _title; ++ const unsigned int s = (unsigned int)std::strlen(tmp) + 1; ++ _title = new char[s]; ++ std::memcpy(_title,tmp,s*sizeof(char)); ++ SetWindowTextA(_window, tmp); ++ delete[] tmp; ++ return *this; ++ } ++ ++ template ++ CImgDisplay& display(const CImg& img) { ++ if (!img) ++ throw CImgArgumentException(_cimgdisplay_instance ++ "display(): Empty specified image.", ++ cimgdisplay_instance); ++ if (is_empty()) return assign(img); ++ return render(img).paint(); ++ } ++ ++ CImgDisplay& paint() { ++ if (_is_closed) return *this; ++ 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 (!img) ++ throw CImgArgumentException(_cimgdisplay_instance ++ "render(): Empty specified image.", ++ cimgdisplay_instance); ++ ++ if (is_empty()) return *this; ++ if (img._depth!=1) return render(img.get_projections2d((img._width - 1)/2,(img._height - 1)/2, ++ (img._depth - 1)/2)); ++ ++ const T ++ *data1 = img._data, ++ *data2 = (img._spectrum>=2)?img.data(0,0,0,1):data1, ++ *data3 = (img._spectrum>=3)?img.data(0,0,0,2):data1; ++ ++ WaitForSingleObject(_mutex,INFINITE); ++ unsigned int ++ *const ndata = (img._width==_width && img._height==_height)?_data: ++ new unsigned int[(size_t)img._width*img._height], ++ *ptrd = ndata; ++ ++ if (!_normalization || (_normalization==3 && cimg::type::string()==cimg::type::string())) { ++ _min = _max = 0; ++ switch (img._spectrum) { ++ case 1 : { ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char val = (unsigned char)*(data1++); ++ *(ptrd++) = (unsigned int)((val<<16) | (val<<8) | val); ++ } ++ } break; ++ case 2 : { ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char ++ R = (unsigned char)*(data1++), ++ G = (unsigned char)*(data2++); ++ *(ptrd++) = (unsigned int)((R<<16) | (G<<8)); ++ } ++ } break; ++ default : { ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char ++ R = (unsigned char)*(data1++), ++ G = (unsigned char)*(data2++), ++ B = (unsigned char)*(data3++); ++ *(ptrd++) = (unsigned int)((R<<16) | (G<<8) | B); ++ } ++ } ++ } ++ } else { ++ if (_normalization==3) { ++ if (cimg::type::is_float()) _min = (float)img.min_max(_max); ++ else { _min = (float)cimg::type::min(); _max = (float)cimg::type::max(); } ++ } else if ((_min>_max) || _normalization==1) _min = (float)img.min_max(_max); ++ const float delta = _max - _min, mm = 255/(delta?delta:1.0f); ++ switch (img._spectrum) { ++ case 1 : { ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char val = (unsigned char)((*(data1++) - _min)*mm); ++ *(ptrd++) = (unsigned int)((val<<16) | (val<<8) | val); ++ } ++ } break; ++ case 2 : { ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char ++ R = (unsigned char)((*(data1++) - _min)*mm), ++ G = (unsigned char)((*(data2++) - _min)*mm); ++ *(ptrd++) = (unsigned int)((R<<16) | (G<<8)); ++ } ++ } break; ++ default : { ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned char ++ R = (unsigned char)((*(data1++) - _min)*mm), ++ G = (unsigned char)((*(data2++) - _min)*mm), ++ B = (unsigned char)((*(data3++) - _min)*mm); ++ *(ptrd++) = (unsigned int)((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 ++ static void screenshot(const int x0, const int y0, const int x1, const int y1, CImg& img) { ++ img.assign(); ++ HDC hScreen = GetDC(GetDesktopWindow()); ++ if (hScreen) { ++ const int ++ width = GetDeviceCaps(hScreen,HORZRES), ++ height = GetDeviceCaps(hScreen,VERTRES); ++ int _x0 = x0, _y0 = y0, _x1 = x1, _y1 = y1; ++ if (_x0>_x1) cimg::swap(_x0,_x1); ++ if (_y0>_y1) cimg::swap(_y0,_y1); ++ if (_x1>=0 && _x0=0 && _y0 ++ const CImgDisplay& snapshot(CImg& img) const { ++ if (is_empty()) { img.assign(); return *this; } ++ const unsigned int *ptrs = _data; ++ img.assign(_width,_height,1,3); ++ T ++ *data1 = img.data(0,0,0,0), ++ *data2 = img.data(0,0,0,1), ++ *data3 = img.data(0,0,0,2); ++ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { ++ const unsigned int val = *(ptrs++); ++ *(data1++) = (T)(unsigned char)(val>>16); ++ *(data2++) = (T)(unsigned char)((val>>8)&0xFF); ++ *(data3++) = (T)(unsigned char)(val&0xFF); ++ } ++ 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 \c CImg, 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 \c CImgList rather than simple images \c CImg. ++ ++ Thus, the \c CImg 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 \c CImg structure contains \e six fields: ++ - \c _width defines the number of \a columns of the image (size along the X-axis). ++ - \c _height defines the number of \a rows of the image (size along the Y-axis). ++ - \c _depth defines the number of \a slices of the image (size along the Z-axis). ++ - \c _spectrum defines the number of \a channels of the image (size along the C-axis). ++ - \c _data defines a \a pointer to the \a pixel \a data (of type \c T). ++ - \c _is_shared is a boolean that tells if the memory buffer \c data is shared with ++ another image. ++ ++ You can access these fields publicly although it is recommended to use the dedicated functions ++ width(), height(), depth(), spectrum() 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 \c 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); constructs a 256x256 color image ++ from a \c unsigned \c char* buffer \c data_buffer (where R,G,B channels follow each others). ++ ++ The complete list of constructors can be found here. ++ ++ \par Most useful functions ++ ++ The \c CImg class contains a lot of functions that operates on images. ++ Some of the most useful are: ++ ++ - operator()(): Read or write pixel values. ++ - display(): displays the image in a new window. ++ **/ ++ template ++ struct CImg { ++ ++ unsigned int _width, _height, _depth, _spectrum; ++ bool _is_shared; ++ T *_data; ++ ++ //! Simple iterator type, to loop through each pixel value of an image instance. ++ /** ++ \note ++ - The \c CImg::iterator type is defined to be a T*. ++ - You will seldom have to use iterators in %CImg, most classical operations ++ being achieved (often in a faster way) using methods of \c CImg. ++ \par Example ++ \code ++ CImg img("reference.jpg"); // Load image from file. ++ // Set all pixels to '0', with a CImg iterator. ++ for (CImg::iterator it = img.begin(), it::const_iterator type is defined to be a \c const \c T*. ++ - You will seldom have to use iterators in %CImg, most classical operations ++ being achieved (often in a faster way) using methods of \c CImg. ++ \par Example ++ \code ++ const CImg img("reference.jpg"); // Load image from file. ++ float sum = 0; ++ // Compute sum of all pixel values, with a CImg iterator. ++ for (CImg::iterator it = img.begin(), it::value_type type of a \c CImg is defined to be a \c T. ++ - \c CImg::value_type is actually not used in %CImg methods. It has been mainly defined for ++ compatibility with STL naming conventions. ++ **/ ++ typedef T value_type; ++ ++ // Define common types related to template type T. ++ 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 uint64T; ++ typedef typename cimg::last::type int64T; ++ 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 ++ ++ //@} ++ //--------------------------------------------------------- ++ // ++ //! \name Constructors / Destructor / Instance Management ++ //@{ ++ //--------------------------------------------------------- ++ ++ //! Destroy image. ++ /** ++ \note ++ - The pixel buffer data() is deallocated if necessary, e.g. for non-empty and non-shared image instances. ++ - Destroying an empty or shared image does nothing actually. ++ \warning ++ - When destroying a non-shared image, make sure that you will \e not operate on a remaining shared image ++ that shares its buffer with the destroyed instance, in order to avoid further invalid memory access ++ (to a deallocated buffer). ++ **/ ++ ~CImg() { ++ if (!_is_shared) delete[] _data; ++ } ++ ++ //! Construct empty image. ++ /** ++ \note ++ - An empty image has no pixel data and all of its dimensions width(), height(), depth(), spectrum() ++ are set to \c 0, as well as its pixel buffer pointer data(). ++ - An empty image may be re-assigned afterwards, e.g. with the family of ++ assign(unsigned int,unsigned int,unsigned int,unsigned int) methods, ++ or by operator=(const CImg&). In all cases, the type of pixels stays \c T. ++ - An empty image is never shared. ++ \par Example ++ \code ++ CImg img1, img2; // Construct two empty images. ++ img1.assign(256,256,1,3); // Re-assign 'img1' to be a 256x256x1x3 (color) image. ++ img2 = img1.get_rand(0,255); // Re-assign 'img2' to be a random-valued version of 'img1'. ++ img2.assign(); // Re-assign 'img2' to be an empty image again. ++ \endcode ++ **/ ++ CImg():_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {} ++ ++ //! Construct image with specified size. ++ /** ++ \param size_x Image width(). ++ \param size_y Image height(). ++ \param size_z Image depth(). ++ \param size_c Image spectrum() (number of channels). ++ \note ++ - It is able to create only \e non-shared images, and allocates thus a pixel buffer data() ++ for each constructed image instance. ++ - Setting one dimension \c size_x,\c size_y,\c size_z or \c size_c to \c 0 leads to the construction of ++ an \e empty image. ++ - A \c CImgInstanceException is thrown when the pixel buffer cannot be allocated ++ (e.g. when requested size is too big for available memory). ++ \warning ++ - The allocated pixel buffer is \e not filled with a default value, and is likely to contain garbage values. ++ In order to initialize pixel values during construction (e.g. with \c 0), use constructor ++ CImg(unsigned int,unsigned int,unsigned int,unsigned int,T) instead. ++ \par Example ++ \code ++ CImg img1(256,256,1,3); // Construct a 256x256x1x3 (color) image, filled with garbage values. ++ CImg img2(256,256,1,3,0); // Construct a 256x256x1x3 (color) image, filled with value '0'. ++ \endcode ++ **/ ++ explicit CImg(const unsigned int size_x, const unsigned int size_y=1, ++ const unsigned int size_z=1, const unsigned int size_c=1): ++ _is_shared(false) { ++ size_t siz = (size_t)size_x*size_y*size_z*size_c; ++ if (siz) { ++ _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; ++ try { _data = new T[siz]; } catch (...) { ++ _width = _height = _depth = _spectrum = 0; _data = 0; ++ throw CImgInstanceException(_cimg_instance ++ "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", ++ cimg_instance, ++ cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), ++ size_x,size_y,size_z,size_c); ++ } ++ } else { _width = _height = _depth = _spectrum = 0; _data = 0; } ++ } ++ ++ //! Construct image with specified size and initialize pixel values. ++ /** ++ \param size_x Image width(). ++ \param size_y Image height(). ++ \param size_z Image depth(). ++ \param size_c Image spectrum() (number of channels). ++ \param value Initialization value. ++ \note ++ - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), ++ but it also fills the pixel buffer with the specified \c value. ++ \warning ++ - It cannot be used to construct a vector-valued image and initialize it with \e vector-valued pixels ++ (e.g. RGB vector, for color images). ++ For this task, you may use fillC() after construction. ++ **/ ++ CImg(const unsigned int size_x, const unsigned int size_y, ++ const unsigned int size_z, const unsigned int size_c, const T& value): ++ _is_shared(false) { ++ const size_t siz = (size_t)size_x*size_y*size_z*size_c; ++ if (siz) { ++ _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; ++ try { _data = new T[siz]; } catch (...) { ++ _width = _height = _depth = _spectrum = 0; _data = 0; ++ throw CImgInstanceException(_cimg_instance ++ "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", ++ cimg_instance, ++ cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), ++ size_x,size_y,size_z,size_c); ++ } ++ fill(value); ++ } else { _width = _height = _depth = _spectrum = 0; _data = 0; } ++ } ++ ++ //! Construct image with specified size and initialize pixel values from a sequence of integers. ++ /** ++ Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, ++ with pixels of type \c T, and initialize pixel ++ values from the specified sequence of integers \c value0,\c value1,\c ... ++ \param size_x Image width(). ++ \param size_y Image height(). ++ \param size_z Image depth(). ++ \param size_c Image spectrum() (number of channels). ++ \param value0 First value of the initialization sequence (must be an \e integer). ++ \param value1 Second value of the initialization sequence (must be an \e integer). ++ \param ... ++ \note ++ - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it also fills ++ the pixel buffer with a sequence of specified integer values. ++ \warning ++ - You must specify \e exactly \c size_x*\c size_y*\c size_z*\c size_c integers in the initialization sequence. ++ Otherwise, the constructor may crash or fill your image pixels with garbage. ++ \par Example ++ \code ++ const CImg img(2,2,1,3, // Construct a 2x2 color (RGB) image. ++ 0,255,0,255, // Set the 4 values for the red component. ++ 0,0,255,255, // Set the 4 values for the green component. ++ 64,64,64,64); // Set the 4 values for the blue component. ++ img.resize(150,150).display(); ++ \endcode ++ \image html ref_constructor1.jpg ++ **/ ++ CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c, ++ const int value0, const int value1, ...): ++ _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { ++#define _CImg_stdarg(img,a0,a1,N,t) { \ ++ size_t _siz = (size_t)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(size_x,size_y,size_z,size_c); ++ _CImg_stdarg(*this,value0,value1,(size_t)size_x*size_y*size_z*size_c,int); ++ } ++ ++#if cimg_use_cpp11==1 ++ //! Construct image with specified size and initialize pixel values from an initializer list of integers. ++ /** ++ Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, ++ with pixels of type \c T, and initialize pixel ++ values from the specified initializer list of integers { \c value0,\c value1,\c ... } ++ \param size_x Image width(). ++ \param size_y Image height(). ++ \param size_z Image depth(). ++ \param size_c Image spectrum() (number of channels). ++ \param { value0, value1, ... } Initialization list ++ \param repeat_values Tells if the value filling process is repeated over the image. ++ ++ \note ++ - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it also fills ++ the pixel buffer with a sequence of specified integer values. ++ \par Example ++ \code ++ const CImg img(2,2,1,3, // Construct a 2x2 color (RGB) image. ++ { 0,255,0,255, // Set the 4 values for the red component. ++ 0,0,255,255, // Set the 4 values for the green component. ++ 64,64,64,64 }); // Set the 4 values for the blue component. ++ img.resize(150,150).display(); ++ \endcode ++ \image html ref_constructor1.jpg ++ **/ ++ template ++ CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c, ++ const std::initializer_list values, ++ const bool repeat_values=true): ++ _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { ++#define _cimg_constructor_cpp11(repeat_values) \ ++ auto it = values.begin(); \ ++ size_t siz = size(); \ ++ if (repeat_values) for (T *ptrd = _data; siz--; ) { \ ++ *(ptrd++) = (T)(*(it++)); if (it==values.end()) it = values.begin(); } \ ++ else { siz = std::min(siz,values.size()); for (T *ptrd = _data; siz--; ) *(ptrd++) = (T)(*(it++)); } ++ assign(size_x,size_y,size_z,size_c); ++ _cimg_constructor_cpp11(repeat_values); ++ } ++ ++ template ++ CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, ++ std::initializer_list values, ++ const bool repeat_values=true): ++ _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { ++ assign(size_x,size_y,size_z); ++ _cimg_constructor_cpp11(repeat_values); ++ } ++ ++ template ++ CImg(const unsigned int size_x, const unsigned int size_y, ++ std::initializer_list values, ++ const bool repeat_values=true): ++ _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { ++ assign(size_x,size_y); ++ _cimg_constructor_cpp11(repeat_values); ++ } ++ ++ template ++ CImg(const unsigned int size_x, ++ std::initializer_list values, ++ const bool repeat_values=true):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { ++ assign(size_x); ++ _cimg_constructor_cpp11(repeat_values); ++ } ++ ++ //! Construct single channel 1D image with pixel values and width obtained from an initializer list of integers. ++ /** ++ Construct a new image instance of size \c width x \c 1 x \c 1 x \c 1, ++ with pixels of type \c T, and initialize pixel ++ values from the specified initializer list of integers { \c value0,\c value1,\c ... }. Image width is ++ given by the size of the initializer list. ++ \param { value0, value1, ... } Initialization list ++ \note ++ - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int) with height=1, depth=1, and spectrum=1, ++ but it also fills the pixel buffer with a sequence of specified integer values. ++ \par Example ++ \code ++ const CImg img = {10,20,30,20,10 }; // Construct a 5x1 image with one channel, and set its pixel values. ++ img.resize(150,150).display(); ++ \endcode ++ \image html ref_constructor1.jpg ++ **/ ++ template ++ CImg(const std::initializer_list values): ++ _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { ++ assign(values.size(),1,1,1); ++ auto it = values.begin(); ++ unsigned int siz = _width; ++ for (T *ptrd = _data; siz--; ) *(ptrd++) = (T)(*(it++)); ++ } ++ ++ template ++ CImg & operator=(std::initializer_list values) { ++ _cimg_constructor_cpp11(siz>values.size()); ++ return *this; ++ } ++#endif ++ ++ //! Construct image with specified size and initialize pixel values from a sequence of doubles. ++ /** ++ Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, with pixels of type \c T, ++ and initialize pixel values from the specified sequence of doubles \c value0,\c value1,\c ... ++ \param size_x Image width(). ++ \param size_y Image height(). ++ \param size_z Image depth(). ++ \param size_c Image spectrum() (number of channels). ++ \param value0 First value of the initialization sequence (must be a \e double). ++ \param value1 Second value of the initialization sequence (must be a \e double). ++ \param ... ++ \note ++ - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int,int,int,...), but ++ takes a sequence of double values instead of integers. ++ \warning ++ - You must specify \e exactly \c dx*\c dy*\c dz*\c dc doubles in the initialization sequence. ++ Otherwise, the constructor may crash or fill your image with garbage. ++ For instance, the code below will probably crash on most platforms: ++ \code ++ const CImg img(2,2,1,1, 0.5,0.5,255,255); // FAIL: The two last arguments are 'int', not 'double'! ++ \endcode ++ **/ ++ CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c, ++ const double value0, const double value1, ...): ++ _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { ++ assign(size_x,size_y,size_z,size_c); ++ _CImg_stdarg(*this,value0,value1,(size_t)size_x*size_y*size_z*size_c,double); ++ } ++ ++ //! Construct image with specified size and initialize pixel values from a value string. ++ /** ++ Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, with pixels of type \c T, ++ and initializes pixel values from the specified string \c values. ++ \param size_x Image width(). ++ \param size_y Image height(). ++ \param size_z Image depth(). ++ \param size_c Image spectrum() (number of channels). ++ \param values Value string describing the way pixel values are set. ++ \param repeat_values Tells if the value filling process is repeated over the image. ++ \note ++ - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it also fills ++ the pixel buffer with values described in the value string \c values. ++ - Value string \c values may describe two different filling processes: ++ - Either \c values is a sequences of values assigned to the image pixels, as in "1,2,3,7,8,2". ++ In this case, set \c repeat_values to \c true to periodically fill the image with the value sequence. ++ - Either, \c values is a formula, as in "cos(x/10)*sin(y/20)". ++ In this case, parameter \c repeat_values is pointless. ++ - For both cases, specifying \c repeat_values is mandatory. ++ It disambiguates the possible overloading of constructor ++ CImg(unsigned int,unsigned int,unsigned int,unsigned int,T) with \c T being a const char*. ++ - A \c CImgArgumentException is thrown when an invalid value string \c values is specified. ++ \par Example ++ \code ++ const CImg img1(129,129,1,3,"0,64,128,192,255",true), // Construct image from a value sequence. ++ img2(129,129,1,3,"if(c==0,255*abs(cos(x/10)),1.8*y)",false); // Construct image from a formula. ++ (img1,img2).display(); ++ \endcode ++ \image html ref_constructor2.jpg ++ **/ ++ CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c, ++ const char *const values, const bool repeat_values):_is_shared(false) { ++ const size_t siz = (size_t)size_x*size_y*size_z*size_c; ++ if (siz) { ++ _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; ++ try { _data = new T[siz]; } catch (...) { ++ _width = _height = _depth = _spectrum = 0; _data = 0; ++ throw CImgInstanceException(_cimg_instance ++ "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", ++ cimg_instance, ++ cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), ++ size_x,size_y,size_z,size_c); ++ } ++ fill(values,repeat_values); ++ } else { _width = _height = _depth = _spectrum = 0; _data = 0; } ++ } ++ ++ //! Construct image with specified size and initialize pixel values from a memory buffer. ++ /** ++ Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, with pixels of type \c T, ++ and initializes pixel values from the specified \c t* memory buffer. ++ \param values Pointer to the input memory buffer. ++ \param size_x Image width(). ++ \param size_y Image height(). ++ \param size_z Image depth(). ++ \param size_c Image spectrum() (number of channels). ++ \param is_shared Tells if input memory buffer must be shared by the current instance. ++ \note ++ - If \c is_shared is \c false, the image instance allocates its own pixel buffer, ++ and values from the specified input buffer are copied to the instance buffer. ++ If buffer types \c T and \c t are different, a regular static cast is performed during buffer copy. ++ - Otherwise, the image instance does \e not allocate a new buffer, and uses the input memory buffer as its ++ own pixel buffer. This case requires that types \c T and \c t are the same. Later, destroying such a shared ++ image will not deallocate the pixel buffer, this task being obviously charged to the initial buffer allocator. ++ - A \c CImgInstanceException is thrown when the pixel buffer cannot be allocated ++ (e.g. when requested size is too big for available memory). ++ \warning ++ - You must take care when operating on a shared image, since it may have an invalid pixel buffer pointer data() ++ (e.g. already deallocated). ++ \par Example ++ \code ++ unsigned char tab[256*256] = { 0 }; ++ CImg img1(tab,256,256,1,1,false), // Construct new non-shared image from buffer 'tab'. ++ img2(tab,256,256,1,1,true); // Construct new shared-image from buffer 'tab'. ++ tab[1024] = 255; // Here, 'img2' is indirectly modified, but not 'img1'. ++ \endcode ++ **/ ++ template ++ CImg(const t *const values, const unsigned int size_x, const unsigned int size_y=1, ++ const unsigned int size_z=1, const unsigned int size_c=1, const bool is_shared=false):_is_shared(false) { ++ if (is_shared) { ++ _width = _height = _depth = _spectrum = 0; _data = 0; ++ throw CImgArgumentException(_cimg_instance ++ "CImg(): Invalid construction request of a (%u,%u,%u,%u) shared instance " ++ "from a (%s*) buffer (pixel types are different).", ++ cimg_instance, ++ size_x,size_y,size_z,size_c,CImg::pixel_type()); ++ } ++ const size_t siz = (size_t)size_x*size_y*size_z*size_c; ++ if (values && siz) { ++ _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; ++ try { _data = new T[siz]; } catch (...) { ++ _width = _height = _depth = _spectrum = 0; _data = 0; ++ throw CImgInstanceException(_cimg_instance ++ "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", ++ cimg_instance, ++ cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), ++ size_x,size_y,size_z,size_c); ++ ++ } ++ const t *ptrs = values; cimg_for(*this,ptrd,T) *ptrd = (T)*(ptrs++); ++ } else { _width = _height = _depth = _spectrum = 0; _data = 0; } ++ } ++ ++ //! Construct image with specified size and initialize pixel values from a memory buffer \specialization. ++ CImg(const T *const values, const unsigned int size_x, const unsigned int size_y=1, ++ const unsigned int size_z=1, const unsigned int size_c=1, const bool is_shared=false) { ++ const size_t siz = (size_t)size_x*size_y*size_z*size_c; ++ if (values && siz) { ++ _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; _is_shared = is_shared; ++ if (_is_shared) _data = const_cast(values); ++ else { ++ try { _data = new T[siz]; } catch (...) { ++ _width = _height = _depth = _spectrum = 0; _data = 0; ++ throw CImgInstanceException(_cimg_instance ++ "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", ++ cimg_instance, ++ cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), ++ size_x,size_y,size_z,size_c); ++ } ++ std::memcpy(_data,values,siz*sizeof(T)); ++ } ++ } else { _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; } ++ } ++ ++ //! Construct image from reading an image file. ++ /** ++ Construct a new image instance with pixels of type \c T, and initialize pixel values with the data read from ++ an image file. ++ \param filename Filename, as a C-string. ++ \note ++ - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it reads the image ++ dimensions and pixel values from the specified image file. ++ - The recognition of the image file format by %CImg higly depends on the tools installed on your system ++ and on the external libraries you used to link your code against. ++ - Considered pixel type \c T should better fit the file format specification, or data loss may occur during ++ file load (e.g. constructing a \c CImg from a float-valued image file). ++ - A \c CImgIOException is thrown when the specified \c filename cannot be read, or if the file format is not ++ recognized. ++ \par Example ++ \code ++ const CImg img("reference.jpg"); ++ img.display(); ++ \endcode ++ \image html ref_image.jpg ++ **/ ++ explicit CImg(const char *const filename):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { ++ assign(filename); ++ } ++ ++ //! Construct image copy. ++ /** ++ Construct a new image instance with pixels of type \c T, as a copy of an existing \c CImg instance. ++ \param img Input image to copy. ++ \note ++ - Constructed copy has the same size width() x height() x depth() x spectrum() and pixel values as the ++ input image \c img. ++ - If input image \c img is \e shared and if types \c T and \c t are the same, the constructed copy is also ++ \e shared, and shares its pixel buffer with \c img. ++ Modifying a pixel value in the constructed copy will thus also modifies it in the input image \c img. ++ This behavior is needful to allow functions to return shared images. ++ - Otherwise, the constructed copy allocates its own pixel buffer, and copies pixel values from the input ++ image \c img into its buffer. The copied pixel values may be eventually statically casted if types \c T and ++ \c t are different. ++ - Constructing a copy from an image \c img when types \c t and \c T are the same is significantly faster than ++ with different types. ++ - A \c CImgInstanceException is thrown when the pixel buffer cannot be allocated ++ (e.g. not enough available memory). ++ **/ ++ template ++ CImg(const CImg& img):_is_shared(false) { ++ const size_t siz = (size_t)img.size(); ++ if (img._data && siz) { ++ _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum; ++ try { _data = new T[siz]; } catch (...) { ++ _width = _height = _depth = _spectrum = 0; _data = 0; ++ throw CImgInstanceException(_cimg_instance ++ "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", ++ cimg_instance, ++ cimg::strbuffersize(sizeof(T)*img._width*img._height*img._depth*img._spectrum), ++ img._width,img._height,img._depth,img._spectrum); ++ } ++ const t *ptrs = img._data; cimg_for(*this,ptrd,T) *ptrd = (T)*(ptrs++); ++ } else { _width = _height = _depth = _spectrum = 0; _data = 0; } ++ } ++ ++ //! Construct image copy \specialization. ++ CImg(const CImg& img) { ++ const size_t siz = (size_t)img.size(); ++ if (img._data && siz) { ++ _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum; ++ _is_shared = img._is_shared; ++ if (_is_shared) _data = const_cast(img._data); ++ else { ++ try { _data = new T[siz]; } catch (...) { ++ _width = _height = _depth = _spectrum = 0; _data = 0; ++ throw CImgInstanceException(_cimg_instance ++ "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", ++ cimg_instance, ++ cimg::strbuffersize(sizeof(T)*img._width*img._height*img._depth*img._spectrum), ++ img._width,img._height,img._depth,img._spectrum); ++ ++ } ++ std::memcpy(_data,img._data,siz*sizeof(T)); ++ } ++ } else { _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; } ++ } ++ ++ //! Advanced copy constructor. ++ /** ++ Construct a new image instance with pixels of type \c T, as a copy of an existing \c CImg instance, ++ while forcing the shared state of the constructed copy. ++ \param img Input image to copy. ++ \param is_shared Tells about the shared state of the constructed copy. ++ \note ++ - Similar to CImg(const CImg&), except that it allows to decide the shared state of ++ the constructed image, which does not depend anymore on the shared state of the input image \c img: ++ - If \c is_shared is \c true, the constructed copy will share its pixel buffer with the input image \c img. ++ For that case, the pixel types \c T and \c t \e must be the same. ++ - If \c is_shared is \c false, the constructed copy will allocate its own pixel buffer, whether the input ++ image \c img is shared or not. ++ - A \c CImgArgumentException is thrown when a shared copy is requested with different pixel types \c T and \c t. ++ **/ ++ template ++ CImg(const CImg& img, const bool is_shared):_is_shared(false) { ++ if (is_shared) { ++ _width = _height = _depth = _spectrum = 0; _data = 0; ++ throw CImgArgumentException(_cimg_instance ++ "CImg(): Invalid construction request of a shared instance from a " ++ "CImg<%s> image (%u,%u,%u,%u,%p) (pixel types are different).", ++ cimg_instance, ++ CImg::pixel_type(),img._width,img._height,img._depth,img._spectrum,img._data); ++ } ++ const size_t siz = (size_t)img.size(); ++ if (img._data && siz) { ++ _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum; ++ try { _data = new T[siz]; } catch (...) { ++ _width = _height = _depth = _spectrum = 0; _data = 0; ++ throw CImgInstanceException(_cimg_instance ++ "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", ++ cimg_instance, ++ cimg::strbuffersize(sizeof(T)*img._width*img._height*img._depth*img._spectrum), ++ img._width,img._height,img._depth,img._spectrum); ++ } ++ const t *ptrs = img._data; cimg_for(*this,ptrd,T) *ptrd = (T)*(ptrs++); ++ } else { _width = _height = _depth = _spectrum = 0; _data = 0; } ++ } ++ ++ //! Advanced copy constructor \specialization. ++ CImg(const CImg& img, const bool is_shared) { ++ const size_t siz = (size_t)img.size(); ++ if (img._data && siz) { ++ _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum; ++ _is_shared = is_shared; ++ if (_is_shared) _data = const_cast(img._data); ++ else { ++ try { _data = new T[siz]; } catch (...) { ++ _width = _height = _depth = _spectrum = 0; _data = 0; ++ throw CImgInstanceException(_cimg_instance ++ "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", ++ cimg_instance, ++ cimg::strbuffersize(sizeof(T)*img._width*img._height*img._depth*img._spectrum), ++ img._width,img._height,img._depth,img._spectrum); ++ } ++ std::memcpy(_data,img._data,siz*sizeof(T)); ++ } ++ } else { _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; } ++ } ++ ++ //! Construct image with dimensions borrowed from another image. ++ /** ++ Construct a new image instance with pixels of type \c T, and size get from some dimensions of an existing ++ \c CImg instance. ++ \param img Input image from which dimensions are borrowed. ++ \param dimensions C-string describing the image size along the X,Y,Z and C-dimensions. ++ \note ++ - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it takes the image dimensions ++ (\e not its pixel values) from an existing \c CImg instance. ++ - The allocated pixel buffer is \e not filled with a default value, and is likely to contain garbage values. ++ In order to initialize pixel values (e.g. with \c 0), use constructor CImg(const CImg&,const char*,T) ++ instead. ++ \par Example ++ \code ++ const CImg img1(256,128,1,3), // 'img1' is a 256x128x1x3 image. ++ img2(img1,"xyzc"), // 'img2' is a 256x128x1x3 image. ++ img3(img1,"y,x,z,c"), // 'img3' is a 128x256x1x3 image. ++ img4(img1,"c,x,y,3",0), // 'img4' is a 3x128x256x3 image (with pixels initialized to '0'). ++ \endcode ++ **/ ++ template ++ CImg(const CImg& img, const char *const dimensions): ++ _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { ++ assign(img,dimensions); ++ } ++ ++ //! Construct image with dimensions borrowed from another image and initialize pixel values. ++ /** ++ Construct a new image instance with pixels of type \c T, and size get from the dimensions of an existing ++ \c CImg instance, and set all pixel values to specified \c value. ++ \param img Input image from which dimensions are borrowed. ++ \param dimensions String describing the image size along the X,Y,Z and V-dimensions. ++ \param value Value used for initialization. ++ \note ++ - Similar to CImg(const CImg&,const char*), but it also fills the pixel buffer with the specified \c value. ++ **/ ++ template ++ CImg(const CImg& img, const char *const dimensions, const T& value): ++ _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { ++ assign(img,dimensions).fill(value); ++ } ++ ++ //! Construct image from a display window. ++ /** ++ Construct a new image instance with pixels of type \c T, as a snapshot of an existing \c CImgDisplay instance. ++ \param disp Input display window. ++ \note ++ - The width() and height() of the constructed image instance are the same as the specified \c CImgDisplay. ++ - The depth() and spectrum() of the constructed image instance are respectively set to \c 1 and \c 3 ++ (i.e. a 2d color image). ++ - The image pixels are read as 8-bits RGB values. ++ **/ ++ explicit CImg(const CImgDisplay &disp):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { ++ disp.snapshot(*this); ++ } ++ ++ // Constructor and assignment operator for rvalue references (c++11). ++ // This avoids an additional image copy for methods returning new images. Can save RAM for big images ! ++#if cimg_use_cpp11==1 ++ CImg(CImg&& img):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { ++ swap(img); ++ } ++ CImg& operator=(CImg&& img) { ++ if (_is_shared) return assign(img); ++ return img.swap(*this); ++ } ++#endif ++ ++ //! Construct empty image \inplace. ++ /** ++ In-place version of the default constructor CImg(). It simply resets the instance to an empty image. ++ **/ ++ CImg& assign() { ++ if (!_is_shared) delete[] _data; ++ _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; ++ return *this; ++ } ++ ++ //! Construct image with specified size \inplace. ++ /** ++ In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int). ++ **/ ++ CImg& assign(const unsigned int size_x, const unsigned int size_y=1, ++ const unsigned int size_z=1, const unsigned int size_c=1) { ++ const size_t siz = (size_t)size_x*size_y*size_z*size_c; ++ if (!siz) return assign(); ++ const size_t curr_siz = (size_t)size(); ++ if (siz!=curr_siz) { ++ if (_is_shared) ++ throw CImgArgumentException(_cimg_instance ++ "assign(): Invalid assignement request of shared instance from specified " ++ "image (%u,%u,%u,%u).", ++ cimg_instance, ++ size_x,size_y,size_z,size_c); ++ else { ++ delete[] _data; ++ try { _data = new T[siz]; } catch (...) { ++ _width = _height = _depth = _spectrum = 0; _data = 0; ++ throw CImgInstanceException(_cimg_instance ++ "assign(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", ++ cimg_instance, ++ cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), ++ size_x,size_y,size_z,size_c); ++ } ++ } ++ } ++ _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; ++ return *this; ++ } ++ ++ //! Construct image with specified size and initialize pixel values \inplace. ++ /** ++ In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,T). ++ **/ ++ CImg& assign(const unsigned int size_x, const unsigned int size_y, ++ const unsigned int size_z, const unsigned int size_c, const T& value) { ++ return assign(size_x,size_y,size_z,size_c).fill(value); ++ } ++ ++ //! Construct image with specified size and initialize pixel values from a sequence of integers \inplace. ++ /** ++ In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,int,int,...). ++ **/ ++ CImg& assign(const unsigned int size_x, const unsigned int size_y, ++ const unsigned int size_z, const unsigned int size_c, ++ const int value0, const int value1, ...) { ++ assign(size_x,size_y,size_z,size_c); ++ _CImg_stdarg(*this,value0,value1,(size_t)size_x*size_y*size_z*size_c,int); ++ return *this; ++ } ++ ++ //! Construct image with specified size and initialize pixel values from a sequence of doubles \inplace. ++ /** ++ In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,double,double,...). ++ **/ ++ CImg& assign(const unsigned int size_x, const unsigned int size_y, ++ const unsigned int size_z, const unsigned int size_c, ++ const double value0, const double value1, ...) { ++ assign(size_x,size_y,size_z,size_c); ++ _CImg_stdarg(*this,value0,value1,(size_t)size_x*size_y*size_z*size_c,double); ++ return *this; ++ } ++ ++ //! Construct image with specified size and initialize pixel values from a value string \inplace. ++ /** ++ In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,const char*,bool). ++ **/ ++ CImg& assign(const unsigned int size_x, const unsigned int size_y, ++ const unsigned int size_z, const unsigned int size_c, ++ const char *const values, const bool repeat_values) { ++ return assign(size_x,size_y,size_z,size_c).fill(values,repeat_values); ++ } ++ ++ //! Construct image with specified size and initialize pixel values from a memory buffer \inplace. ++ /** ++ In-place version of the constructor CImg(const t*,unsigned int,unsigned int,unsigned int,unsigned int). ++ **/ ++ template ++ CImg& assign(const t *const values, const unsigned int size_x, const unsigned int size_y=1, ++ const unsigned int size_z=1, const unsigned int size_c=1) { ++ const size_t siz = (size_t)size_x*size_y*size_z*size_c; ++ if (!values || !siz) return assign(); ++ assign(size_x,size_y,size_z,size_c); ++ const t *ptrs = values; cimg_for(*this,ptrd,T) *ptrd = (T)*(ptrs++); ++ return *this; ++ } ++ ++ //! Construct image with specified size and initialize pixel values from a memory buffer \specialization. ++ CImg& assign(const T *const values, const unsigned int size_x, const unsigned int size_y=1, ++ const unsigned int size_z=1, const unsigned int size_c=1) { ++ const size_t siz = (size_t)size_x*size_y*size_z*size_c; ++ if (!values || !siz) return assign(); ++ const size_t curr_siz = (size_t)size(); ++ if (values==_data && siz==curr_siz) return assign(size_x,size_y,size_z,size_c); ++ if (_is_shared || values + siz<_data || values>=_data + size()) { ++ assign(size_x,size_y,size_z,size_c); ++ if (_is_shared) std::memmove(_data,values,siz*sizeof(T)); ++ else std::memcpy(_data,values,siz*sizeof(T)); ++ } else { ++ T *new_data = 0; ++ try { new_data = new T[siz]; } catch (...) { ++ _width = _height = _depth = _spectrum = 0; _data = 0; ++ throw CImgInstanceException(_cimg_instance ++ "assign(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).", ++ cimg_instance, ++ cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c), ++ size_x,size_y,size_z,size_c); ++ } ++ std::memcpy(new_data,values,siz*sizeof(T)); ++ delete[] _data; _data = new_data; _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; ++ } ++ return *this; ++ } ++ ++ //! Construct image with specified size and initialize pixel values from a memory buffer \overloading. ++ template ++ CImg& assign(const t *const values, const unsigned int size_x, const unsigned int size_y, ++ const unsigned int size_z, const unsigned int size_c, const bool is_shared) { ++ if (is_shared) ++ throw CImgArgumentException(_cimg_instance ++ "assign(): Invalid assignment request of shared instance from (%s*) buffer" ++ "(pixel types are different).", ++ cimg_instance, ++ CImg::pixel_type()); ++ return assign(values,size_x,size_y,size_z,size_c); ++ } ++ ++ //! Construct image with specified size and initialize pixel values from a memory buffer \overloading. ++ CImg& assign(const T *const values, const unsigned int size_x, const unsigned int size_y, ++ const unsigned int size_z, const unsigned int size_c, const bool is_shared) { ++ const size_t siz = (size_t)size_x*size_y*size_z*size_c; ++ if (!values || !siz) return assign(); ++ if (!is_shared) { if (_is_shared) assign(); assign(values,size_x,size_y,size_z,size_c); } ++ else { ++ if (!_is_shared) { ++ if (values + siz<_data || values>=_data + size()) assign(); ++ else cimg::warn(_cimg_instance ++ "assign(): Shared image instance has overlapping memory.", ++ cimg_instance); ++ } ++ _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; _is_shared = true; ++ _data = const_cast(values); ++ } ++ return *this; ++ } ++ ++ //! Construct image from reading an image file \inplace. ++ /** ++ In-place version of the constructor CImg(const char*). ++ **/ ++ CImg& assign(const char *const filename) { ++ return load(filename); ++ } ++ ++ //! Construct image copy \inplace. ++ /** ++ In-place version of the constructor CImg(const CImg&). ++ **/ ++ template ++ CImg& assign(const CImg& img) { ++ return assign(img._data,img._width,img._height,img._depth,img._spectrum); ++ } ++ ++ //! In-place version of the advanced copy constructor. ++ /** ++ In-place version of the constructor CImg(const CImg&,bool). ++ **/ ++ template ++ CImg& assign(const CImg& img, const bool is_shared) { ++ return assign(img._data,img._width,img._height,img._depth,img._spectrum,is_shared); ++ } ++ ++ //! Construct image with dimensions borrowed from another image \inplace. ++ /** ++ In-place version of the constructor CImg(const CImg&,const char*). ++ **/ ++ template ++ CImg& assign(const CImg& img, const char *const dimensions) { ++ if (!dimensions || !*dimensions) return assign(img._width,img._height,img._depth,img._spectrum); ++ unsigned int siz[4] = { 0,1,1,1 }, k = 0; ++ CImg item(256); ++ for (const char *s = dimensions; *s && k<4; ++k) { ++ if (cimg_sscanf(s,"%255[^0-9%xyzvwhdcXYZVWHDC]",item._data)>0) s+=std::strlen(item); ++ if (*s) { ++ unsigned int val = 0; char sep = 0; ++ if (cimg_sscanf(s,"%u%c",&val,&sep)>0) { ++ if (sep=='%') siz[k] = val*(k==0?_width:k==1?_height:k==2?_depth:_spectrum)/100; ++ else siz[k] = val; ++ while (*s>='0' && *s<='9') ++s; ++ if (sep=='%') ++s; ++ } else switch (cimg::lowercase(*s)) { ++ case 'x' : case 'w' : siz[k] = img._width; ++s; break; ++ case 'y' : case 'h' : siz[k] = img._height; ++s; break; ++ case 'z' : case 'd' : siz[k] = img._depth; ++s; break; ++ case 'c' : case 's' : siz[k] = img._spectrum; ++s; break; ++ default : ++ throw CImgArgumentException(_cimg_instance ++ "assign(): Invalid character '%c' detected in specified dimension string '%s'.", ++ cimg_instance, ++ *s,dimensions); ++ } ++ } ++ } ++ return assign(siz[0],siz[1],siz[2],siz[3]); ++ } ++ ++ //! Construct image with dimensions borrowed from another image and initialize pixel values \inplace. ++ /** ++ In-place version of the constructor CImg(const CImg&,const char*,T). ++ **/ ++ template ++ CImg& assign(const CImg& img, const char *const dimensions, const T& value) { ++ return assign(img,dimensions).fill(value); ++ } ++ ++ //! Construct image from a display window \inplace. ++ /** ++ In-place version of the constructor CImg(const CImgDisplay&). ++ **/ ++ CImg& assign(const CImgDisplay &disp) { ++ disp.snapshot(*this); ++ return *this; ++ } ++ ++ //! Construct empty image \inplace. ++ /** ++ Equivalent to assign(). ++ \note ++ - It has been defined for compatibility with STL naming conventions. ++ **/ ++ CImg& clear() { ++ return assign(); ++ } ++ ++ //! Transfer content of an image instance into another one. ++ /** ++ Transfer the dimensions and the pixel buffer content of an image instance into another one, ++ and replace instance by an empty image. It avoids the copy of the pixel buffer ++ when possible. ++ \param img Destination image. ++ \note ++ - Pixel types \c T and \c t of source and destination images can be different, though the process is ++ designed to be instantaneous when \c T and \c t are the same. ++ \par Example ++ \code ++ CImg src(256,256,1,3,0), // Construct a 256x256x1x3 (color) image filled with value '0'. ++ dest(16,16); // Construct a 16x16x1x1 (scalar) image. ++ src.move_to(dest); // Now, 'src' is empty and 'dest' is the 256x256x1x3 image. ++ \endcode ++ **/ ++ template ++ CImg& move_to(CImg& img) { ++ img.assign(*this); ++ assign(); ++ return img; ++ } ++ ++ //! Transfer content of an image instance into another one \specialization. ++ CImg& move_to(CImg& img) { ++ if (_is_shared || img._is_shared) img.assign(*this); ++ else swap(img); ++ assign(); ++ return img; ++ } ++ ++ //! Transfer content of an image instance into a new image in an image list. ++ /** ++ Transfer the dimensions and the pixel buffer content of an image instance ++ into a newly inserted image at position \c pos in specified \c CImgList instance. ++ \param list Destination list. ++ \param pos Position of the newly inserted image in the list. ++ \note ++ - When optional parameter \c pos is ommited, the image instance is transfered as a new ++ image at the end of the specified \c list. ++ - It is convenient to sequentially insert new images into image lists, with no ++ additional copies of memory buffer. ++ \par Example ++ \code ++ CImgList list; // Construct an empty image list. ++ CImg img("reference.jpg"); // Read image from filename. ++ img.move_to(list); // Transfer image content as a new item in the list (no buffer copy). ++ \endcode ++ **/ ++ template ++ CImgList& move_to(CImgList& list, const unsigned int pos=~0U) { ++ const unsigned int npos = pos>list._width?list._width:pos; ++ move_to(list.insert(1,npos)[npos]); ++ return list; ++ } ++ ++ //! Swap fields of two image instances. ++ /** ++ \param img Image to swap fields with. ++ \note ++ - It can be used to interchange the content of two images in a very fast way. Can be convenient when dealing ++ with algorithms requiring two swapping buffers. ++ \par Example ++ \code ++ CImg img1("lena.jpg"), ++ img2("milla.jpg"); ++ img1.swap(img2); // Now, 'img1' is 'milla' and 'img2' is 'lena'. ++ \endcode ++ **/ ++ CImg& swap(CImg& img) { ++ cimg::swap(_width,img._width,_height,img._height,_depth,img._depth,_spectrum,img._spectrum); ++ cimg::swap(_data,img._data); ++ cimg::swap(_is_shared,img._is_shared); ++ return img; ++ } ++ ++ //! Return a reference to an empty image. ++ /** ++ \note ++ This function is useful mainly to declare optional parameters having type \c CImg in functions prototypes, ++ e.g. ++ \code ++ void f(const int x=0, const int y=0, const CImg& img=CImg::empty()); ++ \endcode ++ **/ ++ static CImg& empty() { ++ static CImg _empty; ++ return _empty.assign(); ++ } ++ ++ //! Return a reference to an empty image \const. ++ static const CImg& const_empty() { ++ static const CImg _empty; ++ return _empty; ++ } ++ ++ //@} ++ //------------------------------------------ ++ // ++ //! \name Overloaded Operators ++ //@{ ++ //------------------------------------------ ++ ++ //! Access to a pixel value. ++ /** ++ Return a reference to a located pixel value of the image instance, ++ being possibly \e const, whether the image instance is \e const or not. ++ This is the standard method to get/set pixel values in \c CImg images. ++ \param x X-coordinate of the pixel value. ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ \param c C-coordinate of the pixel value. ++ \note ++ - Range of pixel coordinates start from (0,0,0,0) to ++ (width() - 1,height() - 1,depth() - 1,spectrum() - 1). ++ - Due to the particular arrangement of the pixel buffers defined in %CImg, you can omit one coordinate if the ++ corresponding dimension is equal to \c 1. ++ For instance, pixels of a 2d image (depth() equal to \c 1) can be accessed by img(x,y,c) instead of ++ img(x,y,0,c). ++ \warning ++ - There is \e no boundary checking done in this operator, to make it as fast as possible. ++ You \e must take care of out-of-bounds access by yourself, if necessary. ++ For debuging purposes, you may want to define macro \c 'cimg_verbosity'>=3 to enable additional boundary ++ checking operations in this operator. In that case, warning messages will be printed on the error output ++ when accessing out-of-bounds pixels. ++ \par Example ++ \code ++ CImg img(100,100,1,3,0); // Construct a 100x100x1x3 (color) image with pixels set to '0'. ++ const float ++ valR = img(10,10,0,0), // Read red value at coordinates (10,10). ++ valG = img(10,10,0,1), // Read green value at coordinates (10,10) ++ valB = img(10,10,2), // Read blue value at coordinates (10,10) (Z-coordinate can be omitted). ++ avg = (valR + valG + valB)/3; // Compute average pixel value. ++ img(10,10,0) = img(10,10,1) = img(10,10,2) = avg; // Replace the color pixel (10,10) by the average grey value. ++ \endcode ++ **/ ++#if cimg_verbosity>=3 ++ T& operator()(const unsigned int x, const unsigned int y=0, ++ const unsigned int z=0, const unsigned int c=0) { ++ const ulongT off = (ulongT)offset(x,y,z,c); ++ if (!_data || off>=size()) { ++ cimg::warn(_cimg_instance ++ "operator(): Invalid pixel request, at coordinates (%d,%d,%d,%d) [offset=%u].", ++ cimg_instance, ++ (int)x,(int)y,(int)z,(int)c,off); ++ return *_data; ++ } ++ else return _data[off]; ++ } ++ ++ //! Access to a pixel value \const. ++ const T& operator()(const unsigned int x, const unsigned int y=0, ++ const unsigned int z=0, const unsigned int c=0) const { ++ return const_cast*>(this)->operator()(x,y,z,c); ++ } ++ ++ //! Access to a pixel value. ++ /** ++ \param x X-coordinate of the pixel value. ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ \param c C-coordinate of the pixel value. ++ \param wh Precomputed offset, must be equal to width()*\ref height(). ++ \param whd Precomputed offset, must be equal to width()*\ref height()*\ref depth(). ++ \note ++ - Similar to (but faster than) operator()(). ++ It uses precomputed offsets to optimize memory access. You may use it to optimize ++ the reading/writing of several pixel values in the same image (e.g. in a loop). ++ **/ ++ T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c, ++ const ulongT wh, const ulongT whd=0) { ++ cimg::unused(wh,whd); ++ return (*this)(x,y,z,c); ++ } ++ ++ //! Access to a pixel value \const. ++ const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c, ++ const ulongT wh, const ulongT whd=0) const { ++ cimg::unused(wh,whd); ++ return (*this)(x,y,z,c); ++ } ++#else ++ T& operator()(const unsigned int x) { ++ return _data[x]; ++ } ++ ++ const T& operator()(const unsigned int x) const { ++ return _data[x]; ++ } ++ ++ T& operator()(const unsigned int x, const unsigned int y) { ++ return _data[x + y*_width]; ++ } ++ ++ const T& operator()(const unsigned int x, const unsigned int y) const { ++ return _data[x + y*_width]; ++ } ++ ++ T& operator()(const unsigned int x, const unsigned int y, const unsigned int z) { ++ return _data[x + y*(ulongT)_width + z*(ulongT)_width*_height]; ++ } ++ ++ const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z) const { ++ return _data[x + y*(ulongT)_width + z*(ulongT)_width*_height]; ++ } ++ ++ T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c) { ++ return _data[x + y*(ulongT)_width + z*(ulongT)_width*_height + c*(ulongT)_width*_height*_depth]; ++ } ++ ++ const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c) const { ++ return _data[x + y*(ulongT)_width + z*(ulongT)_width*_height + c*(ulongT)_width*_height*_depth]; ++ } ++ ++ T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int, ++ const ulongT wh) { ++ return _data[x + y*_width + z*wh]; ++ } ++ ++ const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int, ++ const ulongT wh) const { ++ return _data[x + y*_width + z*wh]; ++ } ++ ++ T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c, ++ const ulongT wh, const ulongT whd) { ++ return _data[x + y*_width + z*wh + c*whd]; ++ } ++ ++ const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c, ++ const ulongT wh, const ulongT whd) const { ++ return _data[x + y*_width + z*wh + c*whd]; ++ } ++#endif ++ ++ //! Implicitely cast an image into a \c T*. ++ /** ++ Implicitely cast a \c CImg instance into a \c T* or \c const \c T* pointer, whether the image instance ++ is \e const or not. The returned pointer points on the first value of the image pixel buffer. ++ \note ++ - It simply returns the pointer data() to the pixel buffer. ++ - This implicit conversion is convenient to test the empty state of images (data() being \c 0 in this case), e.g. ++ \code ++ CImg img1(100,100), img2; // 'img1' is a 100x100 image, 'img2' is an empty image. ++ if (img1) { // Test succeeds, 'img1' is not an empty image. ++ if (!img2) { // Test succeeds, 'img2' is an empty image. ++ std::printf("'img1' is not empty, 'img2' is empty."); ++ } ++ } ++ \endcode ++ - It also allows to use brackets to access pixel values, without need for a \c CImg::operator[](), e.g. ++ \code ++ CImg img(100,100); ++ const float value = img[99]; // Access to value of the last pixel on the first row. ++ img[510] = 255; // Set pixel value at (10,5). ++ \endcode ++ **/ ++ operator T*() { ++ return _data; ++ } ++ ++ //! Implicitely cast an image into a \c T* \const. ++ operator const T*() const { ++ return _data; ++ } ++ ++ //! Assign a value to all image pixels. ++ /** ++ Assign specified \c value to each pixel value of the image instance. ++ \param value Value that will be assigned to image pixels. ++ \note ++ - The image size is never modified. ++ - The \c value may be casted to pixel type \c T if necessary. ++ \par Example ++ \code ++ CImg img(100,100); // Declare image (with garbage values). ++ img = 0; // Set all pixel values to '0'. ++ img = 1.2; // Set all pixel values to '1' (cast of '1.2' as a 'char'). ++ \endcode ++ **/ ++ CImg& operator=(const T& value) { ++ return fill(value); ++ } ++ ++ //! Assign pixels values from a specified expression. ++ /** ++ Initialize all pixel values from the specified string \c expression. ++ \param expression Value string describing the way pixel values are set. ++ \note ++ - String parameter \c expression may describe different things: ++ - If \c expression is a list of values (as in \c "1,2,3,8,3,2"), or a formula (as in \c "(x*y)%255"), ++ the pixel values are set from specified \c expression and the image size is not modified. ++ - If \c expression is a filename (as in \c "reference.jpg"), the corresponding image file is loaded and ++ replace the image instance. The image size is modified if necessary. ++ \par Example ++ \code ++ CImg img1(100,100), img2(img1), img3(img1); // Declare 3 scalar images 100x100 with unitialized values. ++ img1 = "0,50,100,150,200,250,200,150,100,50"; // Set pixel values of 'img1' from a value sequence. ++ img2 = "10*((x*y)%25)"; // Set pixel values of 'img2' from a formula. ++ img3 = "reference.jpg"; // Set pixel values of 'img3' from a file (image size is modified). ++ (img1,img2,img3).display(); ++ \endcode ++ \image html ref_operator_eq.jpg ++ **/ ++ CImg& operator=(const char *const expression) { ++ const unsigned int omode = cimg::exception_mode(); ++ cimg::exception_mode(0); ++ try { ++ _fill(expression,true,true,0,0,"operator=",0); ++ } catch (CImgException&) { ++ cimg::exception_mode(omode); ++ load(expression); ++ } ++ cimg::exception_mode(omode); ++ return *this; ++ } ++ ++ //! Copy an image into the current image instance. ++ /** ++ Similar to the in-place copy constructor assign(const CImg&). ++ **/ ++ template ++ CImg& operator=(const CImg& img) { ++ return assign(img); ++ } ++ ++ //! Copy an image into the current image instance \specialization. ++ CImg& operator=(const CImg& img) { ++ return assign(img); ++ } ++ ++ //! Copy the content of a display window to the current image instance. ++ /** ++ Similar to assign(const CImgDisplay&). ++ **/ ++ CImg& operator=(const CImgDisplay& disp) { ++ disp.snapshot(*this); ++ return *this; ++ } ++ ++ //! In-place addition operator. ++ /** ++ Add specified \c value to all pixels of an image instance. ++ \param value Value to add. ++ \note ++ - Resulting pixel values are casted to fit the pixel type \c T. ++ For instance, adding \c 0.2 to a \c CImg is possible but does nothing indeed. ++ - Overflow values are treated as with standard C++ numeric types. For instance, ++ \code ++ CImg img(100,100,1,1,255); // Construct a 100x100 image with pixel values '255'. ++ img+=1; // Add '1' to each pixels -> Overflow. ++ // here all pixels of image 'img' are equal to '0'. ++ \endcode ++ - To prevent value overflow, you may want to consider pixel type \c T as \c float or \c double, ++ and use cut() after addition. ++ \par Example ++ \code ++ CImg img1("reference.jpg"); // Load a 8-bits RGB image (values in [0,255]). ++ CImg img2(img1); // Construct a float-valued copy of 'img1'. ++ img2+=100; // Add '100' to pixel values -> goes out of [0,255] but no problems with floats. ++ img2.cut(0,255); // Cut values in [0,255] to fit the 'unsigned char' constraint. ++ img1 = img2; // Rewrite safe result in 'unsigned char' version 'img1'. ++ const CImg img3 = (img1 + 100).cut(0,255); // Do the same in a more simple and elegant way. ++ (img1,img2,img3).display(); ++ \endcode ++ \image html ref_operator_plus.jpg ++ **/ ++ template ++ CImg& operator+=(const t value) { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=524288)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)(*ptrd + value); ++ return *this; ++ } ++ ++ //! In-place addition operator. ++ /** ++ Add values to image pixels, according to the specified string \c expression. ++ \param expression Value string describing the way pixel values are added. ++ \note ++ - Similar to operator=(const char*), except that it adds values to the pixels of the current image instance, ++ instead of assigning them. ++ **/ ++ CImg& operator+=(const char *const expression) { ++ return *this+=(+*this)._fill(expression,true,true,0,0,"operator+=",this); ++ } ++ ++ //! In-place addition operator. ++ /** ++ Add values to image pixels, according to the values of the input image \c img. ++ \param img Input image to add. ++ \note ++ - The size of the image instance is never modified. ++ - It is not mandatory that input image \c img has the same size as the image instance. ++ If less values are available in \c img, then the values are added periodically. For instance, adding one ++ WxH scalar image (spectrum() equal to \c 1) to one WxH color image (spectrum() equal to \c 3) ++ means each color channel will be incremented with the same values at the same locations. ++ \par Example ++ \code ++ CImg img1("reference.jpg"); // Load a RGB color image (img1.spectrum()==3) ++ // Construct a scalar shading (img2.spectrum()==1). ++ const CImg img2(img1.width(),img.height(),1,1,"255*(x/w)^2"); ++ img1+=img2; // Add shading to each channel of 'img1'. ++ img1.cut(0,255); // Prevent [0,255] overflow. ++ (img2,img1).display(); ++ \endcode ++ \image html ref_operator_plus1.jpg ++ **/ ++ template ++ CImg& operator+=(const CImg& img) { ++ const ulongT siz = size(), isiz = img.size(); ++ if (siz && isiz) { ++ if (is_overlapped(img)) return *this+=+img; ++ T *ptrd = _data, *const ptre = _data + siz; ++ if (siz>isiz) for (ulongT n = siz/isiz; n; --n) ++ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs& operator++() { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=524288)) ++ cimg_rof(*this,ptrd,T) ++*ptrd; ++ return *this; ++ } ++ ++ //! In-place increment operator (postfix). ++ /** ++ Add \c 1 to all image pixels, and return a new copy of the initial (pre-incremented) image instance. ++ \note ++ - Use the prefixed version operator++() if you don't need a copy of the initial ++ (pre-incremented) image instance, since a useless image copy may be expensive in terms of memory usage. ++ **/ ++ CImg operator++(int) { ++ const CImg copy(*this,false); ++ ++*this; ++ return copy; ++ } ++ ++ //! Return a non-shared copy of the image instance. ++ /** ++ \note ++ - Use this operator to ensure you get a non-shared copy of an image instance with same pixel type \c T. ++ Indeed, the usual copy constructor CImg(const CImg&) returns a shared copy of a shared input image, ++ and it may be not desirable to work on a regular copy (e.g. for a resize operation) if you have no ++ information about the shared state of the input image. ++ - Writing \c (+img) is equivalent to \c CImg(img,false). ++ **/ ++ CImg operator+() const { ++ return CImg(*this,false); ++ } ++ ++ //! Addition operator. ++ /** ++ Similar to operator+=(const t), except that it returns a new image instance instead of operating in-place. ++ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. ++ **/ ++ template ++ CImg<_cimg_Tt> operator+(const t value) const { ++ return CImg<_cimg_Tt>(*this,false)+=value; ++ } ++ ++ //! Addition operator. ++ /** ++ Similar to operator+=(const char*), except that it returns a new image instance instead of operating in-place. ++ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. ++ **/ ++ CImg operator+(const char *const expression) const { ++ return CImg(*this,false)+=expression; ++ } ++ ++ //! Addition operator. ++ /** ++ Similar to operator+=(const CImg&), except that it returns a new image instance instead of operating in-place. ++ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. ++ **/ ++ template ++ CImg<_cimg_Tt> operator+(const CImg& img) const { ++ return CImg<_cimg_Tt>(*this,false)+=img; ++ } ++ ++ //! In-place substraction operator. ++ /** ++ Similar to operator+=(const t), except that it performs a substraction instead of an addition. ++ **/ ++ template ++ CImg& operator-=(const t value) { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=524288)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)(*ptrd - value); ++ return *this; ++ } ++ ++ //! In-place substraction operator. ++ /** ++ Similar to operator+=(const char*), except that it performs a substraction instead of an addition. ++ **/ ++ CImg& operator-=(const char *const expression) { ++ return *this-=(+*this)._fill(expression,true,true,0,0,"operator-=",this); ++ } ++ ++ //! In-place substraction operator. ++ /** ++ Similar to operator+=(const CImg&), except that it performs a substraction instead of an addition. ++ **/ ++ template ++ CImg& operator-=(const CImg& img) { ++ const ulongT siz = size(), isiz = img.size(); ++ if (siz && isiz) { ++ if (is_overlapped(img)) return *this-=+img; ++ T *ptrd = _data, *const ptre = _data + siz; ++ if (siz>isiz) for (ulongT n = siz/isiz; n; --n) ++ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs& operator--() { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=524288)) ++ cimg_rof(*this,ptrd,T) *ptrd = *ptrd - (T)1; ++ return *this; ++ } ++ ++ //! In-place decrement operator (postfix). ++ /** ++ Similar to operator++(int), except that it performs a decrement instead of an increment. ++ **/ ++ CImg operator--(int) { ++ const CImg copy(*this,false); ++ --*this; ++ return copy; ++ } ++ ++ //! Replace each pixel by its opposite value. ++ /** ++ \note ++ - If the computed opposite values are out-of-range, they are treated as with standard C++ numeric types. ++ For instance, the \c unsigned \c char opposite of \c 1 is \c 255. ++ \par Example ++ \code ++ const CImg ++ img1("reference.jpg"), // Load a RGB color image. ++ img2 = -img1; // Compute its opposite (in 'unsigned char'). ++ (img1,img2).display(); ++ \endcode ++ \image html ref_operator_minus.jpg ++ **/ ++ CImg operator-() const { ++ return CImg(_width,_height,_depth,_spectrum,(T)0)-=*this; ++ } ++ ++ //! Substraction operator. ++ /** ++ Similar to operator-=(const t), except that it returns a new image instance instead of operating in-place. ++ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. ++ **/ ++ template ++ CImg<_cimg_Tt> operator-(const t value) const { ++ return CImg<_cimg_Tt>(*this,false)-=value; ++ } ++ ++ //! Substraction operator. ++ /** ++ Similar to operator-=(const char*), except that it returns a new image instance instead of operating in-place. ++ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. ++ **/ ++ CImg operator-(const char *const expression) const { ++ return CImg(*this,false)-=expression; ++ } ++ ++ //! Substraction operator. ++ /** ++ Similar to operator-=(const CImg&), except that it returns a new image instance instead of operating in-place. ++ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. ++ **/ ++ template ++ CImg<_cimg_Tt> operator-(const CImg& img) const { ++ return CImg<_cimg_Tt>(*this,false)-=img; ++ } ++ ++ //! In-place multiplication operator. ++ /** ++ Similar to operator+=(const t), except that it performs a multiplication instead of an addition. ++ **/ ++ template ++ CImg& operator*=(const t value) { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=262144)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)(*ptrd * value); ++ return *this; ++ } ++ ++ //! In-place multiplication operator. ++ /** ++ Similar to operator+=(const char*), except that it performs a multiplication instead of an addition. ++ **/ ++ CImg& operator*=(const char *const expression) { ++ return mul((+*this)._fill(expression,true,true,0,0,"operator*=",this)); ++ } ++ ++ //! In-place multiplication operator. ++ /** ++ Replace the image instance by the matrix multiplication between the image instance and the specified matrix ++ \c img. ++ \param img Second operand of the matrix multiplication. ++ \note ++ - It does \e not compute a pointwise multiplication between two images. For this purpose, use ++ mul(const CImg&) instead. ++ - The size of the image instance can be modified by this operator. ++ \par Example ++ \code ++ CImg A(2,2,1,1, 1,2,3,4); // Construct 2x2 matrix A = [1,2;3,4]. ++ const CImg X(1,2,1,1, 1,2); // Construct 1x2 vector X = [1;2]. ++ A*=X; // Assign matrix multiplication A*X to 'A'. ++ // 'A' is now a 1x2 vector whose values are [5;11]. ++ \endcode ++ **/ ++ template ++ CImg& operator*=(const CImg& img) { ++ return ((*this)*img).move_to(*this); ++ } ++ ++ //! Multiplication operator. ++ /** ++ Similar to operator*=(const t), except that it returns a new image instance instead of operating in-place. ++ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. ++ **/ ++ template ++ CImg<_cimg_Tt> operator*(const t value) const { ++ return CImg<_cimg_Tt>(*this,false)*=value; ++ } ++ ++ //! Multiplication operator. ++ /** ++ Similar to operator*=(const char*), except that it returns a new image instance instead of operating in-place. ++ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. ++ **/ ++ CImg operator*(const char *const expression) const { ++ return CImg(*this,false)*=expression; ++ } ++ ++ //! Multiplication operator. ++ /** ++ Similar to operator*=(const CImg&), except that it returns a new image instance instead of operating in-place. ++ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. ++ **/ ++ template ++ CImg<_cimg_Tt> operator*(const CImg& img) const { ++ if (_width!=img._height || _depth!=1 || _spectrum!=1) ++ throw CImgArgumentException(_cimg_instance ++ "operator*(): Invalid multiplication of instance by specified " ++ "matrix (%u,%u,%u,%u,%p)", ++ cimg_instance, ++ img._width,img._height,img._depth,img._spectrum,img._data); ++ CImg<_cimg_Tt> res(img._width,_height); ++#ifdef cimg_use_openmp ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(size()>1024 && img.size()>1024)) ++ cimg_forXY(res,i,j) { ++ _cimg_Ttdouble value = 0; cimg_forX(*this,k) value+=(*this)(k,j)*img(i,k); res(i,j) = (_cimg_Tt)value; ++ } ++#else ++ _cimg_Tt *ptrd = res._data; ++ cimg_forXY(res,i,j) { ++ _cimg_Ttdouble value = 0; cimg_forX(*this,k) value+=(*this)(k,j)*img(i,k); *(ptrd++) = (_cimg_Tt)value; ++ } ++#endif ++ return res; ++ } ++ ++ //! In-place division operator. ++ /** ++ Similar to operator+=(const t), except that it performs a division instead of an addition. ++ **/ ++ template ++ CImg& operator/=(const t value) { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)(*ptrd / value); ++ return *this; ++ } ++ ++ //! In-place division operator. ++ /** ++ Similar to operator+=(const char*), except that it performs a division instead of an addition. ++ **/ ++ CImg& operator/=(const char *const expression) { ++ return div((+*this)._fill(expression,true,true,0,0,"operator/=",this)); ++ } ++ ++ //! In-place division operator. ++ /** ++ Replace the image instance by the (right) matrix division between the image instance and the specified ++ matrix \c img. ++ \param img Second operand of the matrix division. ++ \note ++ - It does \e not compute a pointwise division between two images. For this purpose, use ++ div(const CImg&) instead. ++ - It returns the matrix operation \c A*inverse(img). ++ - The size of the image instance can be modified by this operator. ++ **/ ++ template ++ CImg& operator/=(const CImg& img) { ++ return (*this*img.get_invert()).move_to(*this); ++ } ++ ++ //! Division operator. ++ /** ++ Similar to operator/=(const t), except that it returns a new image instance instead of operating in-place. ++ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. ++ **/ ++ template ++ CImg<_cimg_Tt> operator/(const t value) const { ++ return CImg<_cimg_Tt>(*this,false)/=value; ++ } ++ ++ //! Division operator. ++ /** ++ Similar to operator/=(const char*), except that it returns a new image instance instead of operating in-place. ++ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. ++ **/ ++ CImg operator/(const char *const expression) const { ++ return CImg(*this,false)/=expression; ++ } ++ ++ //! Division operator. ++ /** ++ Similar to operator/=(const CImg&), except that it returns a new image instance instead of operating in-place. ++ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. ++ **/ ++ template ++ CImg<_cimg_Tt> operator/(const CImg& img) const { ++ return (*this)*img.get_invert(); ++ } ++ ++ //! In-place modulo operator. ++ /** ++ Similar to operator+=(const t), except that it performs a modulo operation instead of an addition. ++ **/ ++ template ++ CImg& operator%=(const t value) { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=16384)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)cimg::mod(*ptrd,(T)value); ++ return *this; ++ } ++ ++ //! In-place modulo operator. ++ /** ++ Similar to operator+=(const char*), except that it performs a modulo operation instead of an addition. ++ **/ ++ CImg& operator%=(const char *const expression) { ++ return *this%=(+*this)._fill(expression,true,true,0,0,"operator%=",this); ++ } ++ ++ //! In-place modulo operator. ++ /** ++ Similar to operator+=(const CImg&), except that it performs a modulo operation instead of an addition. ++ **/ ++ template ++ CImg& operator%=(const CImg& img) { ++ const ulongT siz = size(), isiz = img.size(); ++ if (siz && isiz) { ++ if (is_overlapped(img)) return *this%=+img; ++ T *ptrd = _data, *const ptre = _data + siz; ++ if (siz>isiz) for (ulongT n = siz/isiz; n; --n) ++ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs ++ CImg<_cimg_Tt> operator%(const t value) const { ++ return CImg<_cimg_Tt>(*this,false)%=value; ++ } ++ ++ //! Modulo operator. ++ /** ++ Similar to operator%=(const char*), except that it returns a new image instance instead of operating in-place. ++ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. ++ **/ ++ CImg operator%(const char *const expression) const { ++ return CImg(*this,false)%=expression; ++ } ++ ++ //! Modulo operator. ++ /** ++ Similar to operator%=(const CImg&), except that it returns a new image instance instead of operating in-place. ++ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary. ++ **/ ++ template ++ CImg<_cimg_Tt> operator%(const CImg& img) const { ++ return CImg<_cimg_Tt>(*this,false)%=img; ++ } ++ ++ //! In-place bitwise AND operator. ++ /** ++ Similar to operator+=(const t), except that it performs a bitwise AND operation instead of an addition. ++ **/ ++ template ++ CImg& operator&=(const t value) { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)((ulongT)*ptrd & (ulongT)value); ++ return *this; ++ } ++ ++ //! In-place bitwise AND operator. ++ /** ++ Similar to operator+=(const char*), except that it performs a bitwise AND operation instead of an addition. ++ **/ ++ CImg& operator&=(const char *const expression) { ++ return *this&=(+*this)._fill(expression,true,true,0,0,"operator&=",this); ++ } ++ ++ //! In-place bitwise AND operator. ++ /** ++ Similar to operator+=(const CImg&), except that it performs a bitwise AND operation instead of an addition. ++ **/ ++ template ++ CImg& operator&=(const CImg& img) { ++ const ulongT siz = size(), isiz = img.size(); ++ if (siz && isiz) { ++ if (is_overlapped(img)) return *this&=+img; ++ T *ptrd = _data, *const ptre = _data + siz; ++ if (siz>isiz) for (ulongT n = siz/isiz; n; --n) ++ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs ++ CImg operator&(const t value) const { ++ return (+*this)&=value; ++ } ++ ++ //! Bitwise AND operator. ++ /** ++ Similar to operator&=(const char*), except that it returns a new image instance instead of operating in-place. ++ The pixel type of the returned image is \c T. ++ **/ ++ CImg operator&(const char *const expression) const { ++ return (+*this)&=expression; ++ } ++ ++ //! Bitwise AND operator. ++ /** ++ Similar to operator&=(const CImg&), except that it returns a new image instance instead of operating in-place. ++ The pixel type of the returned image is \c T. ++ **/ ++ template ++ CImg operator&(const CImg& img) const { ++ return (+*this)&=img; ++ } ++ ++ //! In-place bitwise OR operator. ++ /** ++ Similar to operator+=(const t), except that it performs a bitwise OR operation instead of an addition. ++ **/ ++ template ++ CImg& operator|=(const t value) { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)((ulongT)*ptrd | (ulongT)value); ++ return *this; ++ } ++ ++ //! In-place bitwise OR operator. ++ /** ++ Similar to operator+=(const char*), except that it performs a bitwise OR operation instead of an addition. ++ **/ ++ CImg& operator|=(const char *const expression) { ++ return *this|=(+*this)._fill(expression,true,true,0,0,"operator|=",this); ++ } ++ ++ //! In-place bitwise OR operator. ++ /** ++ Similar to operator+=(const CImg&), except that it performs a bitwise OR operation instead of an addition. ++ **/ ++ template ++ CImg& operator|=(const CImg& img) { ++ const ulongT siz = size(), isiz = img.size(); ++ if (siz && isiz) { ++ if (is_overlapped(img)) return *this|=+img; ++ T *ptrd = _data, *const ptre = _data + siz; ++ if (siz>isiz) for (ulongT n = siz/isiz; n; --n) ++ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs ++ CImg operator|(const t value) const { ++ return (+*this)|=value; ++ } ++ ++ //! Bitwise OR operator. ++ /** ++ Similar to operator|=(const char*), except that it returns a new image instance instead of operating in-place. ++ The pixel type of the returned image is \c T. ++ **/ ++ CImg operator|(const char *const expression) const { ++ return (+*this)|=expression; ++ } ++ ++ //! Bitwise OR operator. ++ /** ++ Similar to operator|=(const CImg&), except that it returns a new image instance instead of operating in-place. ++ The pixel type of the returned image is \c T. ++ **/ ++ template ++ CImg operator|(const CImg& img) const { ++ return (+*this)|=img; ++ } ++ ++ //! In-place bitwise XOR operator. ++ /** ++ Similar to operator+=(const t), except that it performs a bitwise XOR operation instead of an addition. ++ \warning ++ - It does \e not compute the \e power of pixel values. For this purpose, use pow(const t) instead. ++ **/ ++ template ++ CImg& operator^=(const t value) { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)((ulongT)*ptrd ^ (ulongT)value); ++ return *this; ++ } ++ ++ //! In-place bitwise XOR operator. ++ /** ++ Similar to operator+=(const char*), except that it performs a bitwise XOR operation instead of an addition. ++ \warning ++ - It does \e not compute the \e power of pixel values. For this purpose, use pow(const char*) instead. ++ **/ ++ CImg& operator^=(const char *const expression) { ++ return *this^=(+*this)._fill(expression,true,true,0,0,"operator^=",this); ++ } ++ ++ //! In-place bitwise XOR operator. ++ /** ++ Similar to operator+=(const CImg&), except that it performs a bitwise XOR operation instead of an addition. ++ \warning ++ - It does \e not compute the \e power of pixel values. For this purpose, use pow(const CImg&) instead. ++ **/ ++ template ++ CImg& operator^=(const CImg& img) { ++ const ulongT siz = size(), isiz = img.size(); ++ if (siz && isiz) { ++ if (is_overlapped(img)) return *this^=+img; ++ T *ptrd = _data, *const ptre = _data + siz; ++ if (siz>isiz) for (ulongT n = siz/isiz; n; --n) ++ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs ++ CImg operator^(const t value) const { ++ return (+*this)^=value; ++ } ++ ++ //! Bitwise XOR operator. ++ /** ++ Similar to operator^=(const char*), except that it returns a new image instance instead of operating in-place. ++ The pixel type of the returned image is \c T. ++ **/ ++ CImg operator^(const char *const expression) const { ++ return (+*this)^=expression; ++ } ++ ++ //! Bitwise XOR operator. ++ /** ++ Similar to operator^=(const CImg&), except that it returns a new image instance instead of operating in-place. ++ The pixel type of the returned image is \c T. ++ **/ ++ template ++ CImg operator^(const CImg& img) const { ++ return (+*this)^=img; ++ } ++ ++ //! In-place bitwise left shift operator. ++ /** ++ Similar to operator+=(const t), except that it performs a bitwise left shift instead of an addition. ++ **/ ++ template ++ CImg& operator<<=(const t value) { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=65536)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)(((longT)*ptrd) << (int)value); ++ return *this; ++ } ++ ++ //! In-place bitwise left shift operator. ++ /** ++ Similar to operator+=(const char*), except that it performs a bitwise left shift instead of an addition. ++ **/ ++ CImg& operator<<=(const char *const expression) { ++ return *this<<=(+*this)._fill(expression,true,true,0,0,"operator<<=",this); ++ } ++ ++ //! In-place bitwise left shift operator. ++ /** ++ Similar to operator+=(const CImg&), except that it performs a bitwise left shift instead of an addition. ++ **/ ++ template ++ CImg& operator<<=(const CImg& img) { ++ const ulongT siz = size(), isiz = img.size(); ++ if (siz && isiz) { ++ if (is_overlapped(img)) return *this^=+img; ++ T *ptrd = _data, *const ptre = _data + siz; ++ if (siz>isiz) for (ulongT n = siz/isiz; n; --n) ++ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs ++ CImg operator<<(const t value) const { ++ return (+*this)<<=value; ++ } ++ ++ //! Bitwise left shift operator. ++ /** ++ Similar to operator<<=(const char*), except that it returns a new image instance instead of operating in-place. ++ The pixel type of the returned image is \c T. ++ **/ ++ CImg operator<<(const char *const expression) const { ++ return (+*this)<<=expression; ++ } ++ ++ //! Bitwise left shift operator. ++ /** ++ Similar to operator<<=(const CImg&), except that it returns a new image instance instead of ++ operating in-place. ++ The pixel type of the returned image is \c T. ++ **/ ++ template ++ CImg operator<<(const CImg& img) const { ++ return (+*this)<<=img; ++ } ++ ++ //! In-place bitwise right shift operator. ++ /** ++ Similar to operator+=(const t), except that it performs a bitwise right shift instead of an addition. ++ **/ ++ template ++ CImg& operator>>=(const t value) { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=65536)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)(((longT)*ptrd) >> (int)value); ++ return *this; ++ } ++ ++ //! In-place bitwise right shift operator. ++ /** ++ Similar to operator+=(const char*), except that it performs a bitwise right shift instead of an addition. ++ **/ ++ CImg& operator>>=(const char *const expression) { ++ return *this>>=(+*this)._fill(expression,true,true,0,0,"operator>>=",this); ++ } ++ ++ //! In-place bitwise right shift operator. ++ /** ++ Similar to operator+=(const CImg&), except that it performs a bitwise right shift instead of an addition. ++ **/ ++ template ++ CImg& operator>>=(const CImg& img) { ++ const ulongT siz = size(), isiz = img.size(); ++ if (siz && isiz) { ++ if (is_overlapped(img)) return *this^=+img; ++ T *ptrd = _data, *const ptre = _data + siz; ++ if (siz>isiz) for (ulongT n = siz/isiz; n; --n) ++ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs> (int)*(ptrs++)); ++ for (const t *ptrs = img._data; ptrd> (int)*(ptrs++)); ++ } ++ return *this; ++ } ++ ++ //! Bitwise right shift operator. ++ /** ++ Similar to operator>>=(const t), except that it returns a new image instance instead of operating in-place. ++ The pixel type of the returned image is \c T. ++ **/ ++ template ++ CImg operator>>(const t value) const { ++ return (+*this)>>=value; ++ } ++ ++ //! Bitwise right shift operator. ++ /** ++ Similar to operator>>=(const char*), except that it returns a new image instance instead of operating in-place. ++ The pixel type of the returned image is \c T. ++ **/ ++ CImg operator>>(const char *const expression) const { ++ return (+*this)>>=expression; ++ } ++ ++ //! Bitwise right shift operator. ++ /** ++ Similar to operator>>=(const CImg&), except that it returns a new image instance instead of ++ operating in-place. ++ The pixel type of the returned image is \c T. ++ **/ ++ template ++ CImg operator>>(const CImg& img) const { ++ return (+*this)>>=img; ++ } ++ ++ //! Bitwise inversion operator. ++ /** ++ Similar to operator-(), except that it compute the bitwise inverse instead of the opposite value. ++ **/ ++ CImg operator~() const { ++ CImg res(_width,_height,_depth,_spectrum); ++ const T *ptrs = _data; ++ cimg_for(res,ptrd,T) { const ulongT value = (ulongT)*(ptrs++); *ptrd = (T)~value; } ++ return res; ++ } ++ ++ //! Test if all pixels of an image have the same value. ++ /** ++ Return \c true is all pixels of the image instance are equal to the specified \c value. ++ \param value Reference value to compare with. ++ **/ ++ template ++ bool operator==(const t value) const { ++ if (is_empty()) return false; ++ typedef _cimg_Tt Tt; ++ bool is_equal = true; ++ for (T *ptrd = _data + size(); is_equal && ptrd>_data; is_equal = ((Tt)*(--ptrd)==(Tt)value)) {} ++ return is_equal; ++ } ++ ++ //! Test if all pixel values of an image follow a specified expression. ++ /** ++ Return \c true is all pixels of the image instance are equal to the specified \c expression. ++ \param expression Value string describing the way pixel values are compared. ++ **/ ++ bool operator==(const char *const expression) const { ++ return *this==(+*this)._fill(expression,true,true,0,0,"operator==",this); ++ } ++ ++ //! Test if two images have the same size and values. ++ /** ++ Return \c true if the image instance and the input image \c img have the same dimensions and pixel values, ++ and \c false otherwise. ++ \param img Input image to compare with. ++ \note ++ - The pixel buffer pointers data() of the two compared images do not have to be the same for operator==() ++ to return \c true. ++ Only the dimensions and the pixel values matter. Thus, the comparison can be \c true even for different ++ pixel types \c T and \c t. ++ \par Example ++ \code ++ const CImg img1(1,3,1,1, 0,1,2); // Construct a 1x3 vector [0;1;2] (with 'float' pixel values). ++ const CImg img2(1,3,1,1, 0,1,2); // Construct a 1x3 vector [0;1;2] (with 'char' pixel values). ++ if (img1==img2) { // Test succeeds, image dimensions and values are the same. ++ std::printf("'img1' and 'img2' have same dimensions and values."); ++ } ++ \endcode ++ **/ ++ template ++ bool operator==(const CImg& img) const { ++ typedef _cimg_Tt Tt; ++ const ulongT siz = size(); ++ bool is_equal = true; ++ if (siz!=img.size()) return false; ++ t *ptrs = img._data + siz; ++ for (T *ptrd = _data + siz; is_equal && ptrd>_data; is_equal = ((Tt)*(--ptrd)==(Tt)*(--ptrs))) {} ++ return is_equal; ++ } ++ ++ //! Test if pixels of an image are all different from a value. ++ /** ++ Return \c true is all pixels of the image instance are different than the specified \c value. ++ \param value Reference value to compare with. ++ **/ ++ template ++ bool operator!=(const t value) const { ++ return !((*this)==value); ++ } ++ ++ //! Test if all pixel values of an image are different from a specified expression. ++ /** ++ Return \c true is all pixels of the image instance are different to the specified \c expression. ++ \param expression Value string describing the way pixel values are compared. ++ **/ ++ bool operator!=(const char *const expression) const { ++ return !((*this)==expression); ++ } ++ ++ //! Test if two images have different sizes or values. ++ /** ++ Return \c true if the image instance and the input image \c img have different dimensions or pixel values, ++ and \c false otherwise. ++ \param img Input image to compare with. ++ \note ++ - Writing \c img1!=img2 is equivalent to \c !(img1==img2). ++ **/ ++ template ++ bool operator!=(const CImg& img) const { ++ return !((*this)==img); ++ } ++ ++ //! Construct an image list from two images. ++ /** ++ Return a new list of image (\c CImgList instance) containing exactly two elements: ++ - A copy of the image instance, at position [\c 0]. ++ - A copy of the specified image \c img, at position [\c 1]. ++ ++ \param img Input image that will be the second image of the resulting list. ++ \note ++ - The family of operator,() is convenient to easily create list of images, but it is also \e quite \e slow ++ in practice (see warning below). ++ - Constructed lists contain no shared images. If image instance or input image \c img are shared, they are ++ inserted as new non-shared copies in the resulting list. ++ - The pixel type of the returned list may be a superset of the initial pixel type \c T, if necessary. ++ \warning ++ - Pipelining operator,() \c N times will perform \c N copies of the entire content of a (growing) image list. ++ This may become very expensive in terms of speed and used memory. You should avoid using this technique to ++ build a new CImgList instance from several images, if you are seeking for performance. ++ Fast insertions of images in an image list are possible with ++ CImgList::insert(const CImg&,unsigned int,bool) or move_to(CImgList&,unsigned int). ++ \par Example ++ \code ++ const CImg ++ img1("reference.jpg"), ++ img2 = img1.get_mirror('x'), ++ img3 = img2.get_blur(5); ++ const CImgList list = (img1,img2); // Create list of two elements from 'img1' and 'img2'. ++ (list,img3).display(); // Display image list containing copies of 'img1','img2' and 'img3'. ++ \endcode ++ \image html ref_operator_comma.jpg ++ **/ ++ template ++ CImgList<_cimg_Tt> operator,(const CImg& img) const { ++ return CImgList<_cimg_Tt>(*this,img); ++ } ++ ++ //! Construct an image list from image instance and an input image list. ++ /** ++ Return a new list of images (\c CImgList instance) containing exactly \c list.size() \c + \c 1 elements: ++ - A copy of the image instance, at position [\c 0]. ++ - A copy of the specified image list \c list, from positions [\c 1] to [\c list.size()]. ++ ++ \param list Input image list that will be appended to the image instance. ++ \note ++ - Similar to operator,(const CImg&) const, except that it takes an image list as an argument. ++ **/ ++ template ++ CImgList<_cimg_Tt> operator,(const CImgList& list) const { ++ return CImgList<_cimg_Tt>(list,false).insert(*this,0); ++ } ++ ++ //! Split image along specified axis. ++ /** ++ Return a new list of images (\c CImgList instance) containing the splitted components ++ of the instance image along the specified axis. ++ \param axis Splitting axis (can be '\c x','\c y','\c z' or '\c c') ++ \note ++ - Similar to get_split(char,int) const, with default second argument. ++ \par Example ++ \code ++ const CImg img("reference.jpg"); // Load a RGB color image. ++ const CImgList list = (img<'c'); // Get a list of its three R,G,B channels. ++ (img,list).display(); ++ \endcode ++ \image html ref_operator_less.jpg ++ **/ ++ CImgList operator<(const char axis) const { ++ return get_split(axis); ++ } ++ ++ //@} ++ //------------------------------------- ++ // ++ //! \name Instance Characteristics ++ //@{ ++ //------------------------------------- ++ ++ //! Return the type of image pixel values as a C string. ++ /** ++ Return a \c char* string containing the usual type name of the image pixel values ++ (i.e. a stringified version of the template parameter \c T). ++ \note ++ - The returned string may contain spaces (as in \c "unsigned char"). ++ - If the pixel type \c T does not correspond to a registered type, the string "unknown" is returned. ++ **/ ++ static const char* pixel_type() { ++ return cimg::type::string(); ++ } ++ ++ //! Return the number of image columns. ++ /** ++ Return the image width, i.e. the image dimension along the X-axis. ++ \note ++ - The width() of an empty image is equal to \c 0. ++ - width() is typically equal to \c 1 when considering images as \e vectors for matrix calculations. ++ - width() returns an \c int, although the image width is internally stored as an \c unsigned \c int. ++ Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving ++ \c unsigned \c int variables. ++ Access to the initial \c unsigned \c int variable is possible (though not recommended) by ++ (*this)._width. ++ **/ ++ int width() const { ++ return (int)_width; ++ } ++ ++ //! Return the number of image rows. ++ /** ++ Return the image height, i.e. the image dimension along the Y-axis. ++ \note ++ - The height() of an empty image is equal to \c 0. ++ - height() returns an \c int, although the image height is internally stored as an \c unsigned \c int. ++ Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving ++ \c unsigned \c int variables. ++ Access to the initial \c unsigned \c int variable is possible (though not recommended) by ++ (*this)._height. ++ **/ ++ int height() const { ++ return (int)_height; ++ } ++ ++ //! Return the number of image slices. ++ /** ++ Return the image depth, i.e. the image dimension along the Z-axis. ++ \note ++ - The depth() of an empty image is equal to \c 0. ++ - depth() is typically equal to \c 1 when considering usual 2d images. When depth()\c > \c 1, the image ++ is said to be \e volumetric. ++ - depth() returns an \c int, although the image depth is internally stored as an \c unsigned \c int. ++ Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving ++ \c unsigned \c int variables. ++ Access to the initial \c unsigned \c int variable is possible (though not recommended) by ++ (*this)._depth. ++ **/ ++ int depth() const { ++ return (int)_depth; ++ } ++ ++ //! Return the number of image channels. ++ /** ++ Return the number of image channels, i.e. the image dimension along the C-axis. ++ \note ++ - The spectrum() of an empty image is equal to \c 0. ++ - spectrum() is typically equal to \c 1 when considering scalar-valued images, to \c 3 ++ for RGB-coded color images, and to \c 4 for RGBA-coded color images (with alpha-channel). ++ The number of channels of an image instance is not limited. The meaning of the pixel values is not linked ++ up to the number of channels (e.g. a 4-channel image may indifferently stands for a RGBA or CMYK color image). ++ - spectrum() returns an \c int, although the image spectrum is internally stored as an \c unsigned \c int. ++ Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving ++ \c unsigned \c int variables. ++ Access to the initial \c unsigned \c int variable is possible (though not recommended) by ++ (*this)._spectrum. ++ **/ ++ int spectrum() const { ++ return (int)_spectrum; ++ } ++ ++ //! Return the total number of pixel values. ++ /** ++ Return width()*\ref height()*\ref depth()*\ref spectrum(), ++ i.e. the total number of values of type \c T in the pixel buffer of the image instance. ++ \note ++ - The size() of an empty image is equal to \c 0. ++ - The allocated memory size for a pixel buffer of a non-shared \c CImg instance is equal to ++ size()*sizeof(T). ++ \par Example ++ \code ++ const CImg img(100,100,1,3); // Construct new 100x100 color image. ++ if (img.size()==30000) // Test succeeds. ++ std::printf("Pixel buffer uses %lu bytes", ++ img.size()*sizeof(float)); ++ \endcode ++ **/ ++ ulongT size() const { ++ return (ulongT)_width*_height*_depth*_spectrum; ++ } ++ ++ //! Return a pointer to the first pixel value. ++ /** ++ Return a \c T*, or a \c const \c T* pointer to the first value in the pixel buffer of the image instance, ++ whether the instance is \c const or not. ++ \note ++ - The data() of an empty image is equal to \c 0 (null pointer). ++ - The allocated pixel buffer for the image instance starts from \c data() ++ and goes to data()+\ref size() - 1 (included). ++ - To get the pointer to one particular location of the pixel buffer, use ++ data(unsigned int,unsigned int,unsigned int,unsigned int) instead. ++ **/ ++ T* data() { ++ return _data; ++ } ++ ++ //! Return a pointer to the first pixel value \const. ++ const T* data() const { ++ return _data; ++ } ++ ++ //! Return a pointer to a located pixel value. ++ /** ++ Return a \c T*, or a \c const \c T* pointer to the value located at (\c x,\c y,\c z,\c c) in the pixel buffer ++ of the image instance, ++ whether the instance is \c const or not. ++ \param x X-coordinate of the pixel value. ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ \param c C-coordinate of the pixel value. ++ \note ++ - Writing \c img.data(x,y,z,c) is equivalent to &(img(x,y,z,c)). Thus, this method has the same ++ properties as operator()(unsigned int,unsigned int,unsigned int,unsigned int). ++ **/ ++#if cimg_verbosity>=3 ++ T *data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) { ++ const ulongT off = (ulongT)offset(x,y,z,c); ++ if (off>=size()) ++ cimg::warn(_cimg_instance ++ "data(): Invalid pointer request, at coordinates (%u,%u,%u,%u) [offset=%u].", ++ cimg_instance, ++ x,y,z,c,off); ++ return _data + off; ++ } ++ ++ //! Return a pointer to a located pixel value \const. ++ const T* data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) const { ++ return const_cast*>(this)->data(x,y,z,c); ++ } ++#else ++ T* data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) { ++ return _data + x + (ulongT)y*_width + (ulongT)z*_width*_height + (ulongT)c*_width*_height*_depth; ++ } ++ ++ const T* data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) const { ++ return _data + x + (ulongT)y*_width + (ulongT)z*_width*_height + (ulongT)c*_width*_height*_depth; ++ } ++#endif ++ ++ //! Return the offset to a located pixel value, with respect to the beginning of the pixel buffer. ++ /** ++ \param x X-coordinate of the pixel value. ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ \param c C-coordinate of the pixel value. ++ \note ++ - Writing \c img.data(x,y,z,c) is equivalent to &(img(x,y,z,c)) - img.data(). ++ Thus, this method has the same properties as operator()(unsigned int,unsigned int,unsigned int,unsigned int). ++ \par Example ++ \code ++ const CImg img(100,100,1,3); // Define a 100x100 RGB-color image. ++ const long off = img.offset(10,10,0,2); // Get the offset of the blue value of the pixel located at (10,10). ++ const float val = img[off]; // Get the blue value of this pixel. ++ \endcode ++ **/ ++ longT offset(const int x, const int y=0, const int z=0, const int c=0) const { ++ return x + (longT)y*_width + (longT)z*_width*_height + (longT)c*_width*_height*_depth; ++ } ++ ++ //! Return a CImg::iterator pointing to the first pixel value. ++ /** ++ \note ++ - Equivalent to data(). ++ - It has been mainly defined for compatibility with STL naming conventions. ++ **/ ++ iterator begin() { ++ return _data; ++ } ++ ++ //! Return a CImg::iterator pointing to the first value of the pixel buffer \const. ++ const_iterator begin() const { ++ return _data; ++ } ++ ++ //! Return a CImg::iterator pointing next to the last pixel value. ++ /** ++ \note ++ - Writing \c img.end() is equivalent to img.data() + img.size(). ++ - It has been mainly defined for compatibility with STL naming conventions. ++ \warning ++ - The returned iterator actually points to a value located \e outside the acceptable bounds of the pixel buffer. ++ Trying to read or write the content of the returned iterator will probably result in a crash. ++ Use it mainly as a strict upper bound for a CImg::iterator. ++ \par Example ++ \code ++ CImg img(100,100,1,3); // Define a 100x100 RGB color image. ++ // 'img.end()' used below as an upper bound for the iterator. ++ for (CImg::iterator it = img.begin(); it::iterator pointing next to the last pixel value \const. ++ const_iterator end() const { ++ return _data + size(); ++ } ++ ++ //! Return a reference to the first pixel value. ++ /** ++ \note ++ - Writing \c img.front() is equivalent to img[0], or img(0,0,0,0). ++ - It has been mainly defined for compatibility with STL naming conventions. ++ **/ ++ T& front() { ++ return *_data; ++ } ++ ++ //! Return a reference to the first pixel value \const. ++ const T& front() const { ++ return *_data; ++ } ++ ++ //! Return a reference to the last pixel value. ++ /** ++ \note ++ - Writing \c img.back() is equivalent to img[img.size() - 1], or ++ img(img.width() - 1,img.height() - 1,img.depth() - 1,img.spectrum() - 1). ++ - It has been mainly defined for compatibility with STL naming conventions. ++ **/ ++ T& back() { ++ return *(_data + size() - 1); ++ } ++ ++ //! Return a reference to the last pixel value \const. ++ const T& back() const { ++ return *(_data + size() - 1); ++ } ++ ++ //! Access to a pixel value at a specified offset, using Dirichlet boundary conditions. ++ /** ++ Return a reference to the pixel value of the image instance located at a specified \c offset, ++ or to a specified default value in case of out-of-bounds access. ++ \param offset Offset to the desired pixel value. ++ \param out_value Default value returned if \c offset is outside image bounds. ++ \note ++ - Writing \c img.at(offset,out_value) is similar to img[offset], except that if \c offset ++ is outside bounds (e.g. \c offset<0 or \c offset>=img.size()), a reference to a value \c out_value ++ is safely returned instead. ++ - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when ++ you are \e not sure about the validity of the specified pixel offset. ++ **/ ++ T& at(const int offset, const T& out_value) { ++ return (offset<0 || offset>=(int)size())?(cimg::temporary(out_value)=out_value):(*this)[offset]; ++ } ++ ++ //! Access to a pixel value at a specified offset, using Dirichlet boundary conditions \const. ++ T at(const int offset, const T& out_value) const { ++ return (offset<0 || offset>=(int)size())?out_value:(*this)[offset]; ++ } ++ ++ //! Access to a pixel value at a specified offset, using Neumann boundary conditions. ++ /** ++ Return a reference to the pixel value of the image instance located at a specified \c offset, ++ or to the nearest pixel location in the image instance in case of out-of-bounds access. ++ \param offset Offset to the desired pixel value. ++ \note ++ - Similar to at(int,const T), except that an out-of-bounds access returns the value of the ++ nearest pixel in the image instance, regarding the specified offset, i.e. ++ - If \c offset<0, then \c img[0] is returned. ++ - If \c offset>=img.size(), then \c img[img.size() - 1] is returned. ++ - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when ++ you are \e not sure about the validity of the specified pixel offset. ++ - If you know your image instance is \e not empty, you may rather use the slightly faster method \c _at(int). ++ **/ ++ T& at(const int offset) { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "at(): Empty instance.", ++ cimg_instance); ++ return _at(offset); ++ } ++ ++ T& _at(const int offset) { ++ const unsigned int siz = (unsigned int)size(); ++ return (*this)[offset<0?0:(unsigned int)offset>=siz?siz - 1:offset]; ++ } ++ ++ //! Access to a pixel value at a specified offset, using Neumann boundary conditions \const. ++ const T& at(const int offset) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "at(): Empty instance.", ++ cimg_instance); ++ return _at(offset); ++ } ++ ++ const T& _at(const int offset) const { ++ const unsigned int siz = (unsigned int)size(); ++ return (*this)[offset<0?0:(unsigned int)offset>=siz?siz - 1:offset]; ++ } ++ ++ //! Access to a pixel value, using Dirichlet boundary conditions for the X-coordinate. ++ /** ++ Return a reference to the pixel value of the image instance located at (\c x,\c y,\c z,\c c), ++ or to a specified default value in case of out-of-bounds access along the X-axis. ++ \param x X-coordinate of the pixel value. ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ \param c C-coordinate of the pixel value. ++ \param out_value Default value returned if \c (\c x,\c y,\c z,\c c) is outside image bounds. ++ \note ++ - Similar to operator()(), except that an out-of-bounds access along the X-axis returns the specified value ++ \c out_value. ++ - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when ++ you are \e not sure about the validity of the specified pixel coordinates. ++ \warning ++ - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. ++ **/ ++ T& atX(const int x, const int y, const int z, const int c, const T& out_value) { ++ return (x<0 || x>=width())?(cimg::temporary(out_value)=out_value):(*this)(x,y,z,c); ++ } ++ ++ //! Access to a pixel value, using Dirichlet boundary conditions for the X-coordinate \const. ++ T atX(const int x, const int y, const int z, const int c, const T& out_value) const { ++ return (x<0 || x>=width())?out_value:(*this)(x,y,z,c); ++ } ++ ++ //! Access to a pixel value, using Neumann boundary conditions for the X-coordinate. ++ /** ++ Return a reference to the pixel value of the image instance located at (\c x,\c y,\c z,\c c), ++ or to the nearest pixel location in the image instance in case of out-of-bounds access along the X-axis. ++ \param x X-coordinate of the pixel value. ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ \param c C-coordinate of the pixel value. ++ \note ++ - Similar to at(int,int,int,int,const T), except that an out-of-bounds access returns the value of the ++ nearest pixel in the image instance, regarding the specified X-coordinate. ++ - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when ++ you are \e not sure about the validity of the specified pixel coordinates. ++ - If you know your image instance is \e not empty, you may rather use the slightly faster method ++ \c _at(int,int,int,int). ++ \warning ++ - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. ++ **/ ++ T& atX(const int x, const int y=0, const int z=0, const int c=0) { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "atX(): Empty instance.", ++ cimg_instance); ++ return _atX(x,y,z,c); ++ } ++ ++ T& _atX(const int x, const int y=0, const int z=0, const int c=0) { ++ return (*this)(x<0?0:(x>=width()?width() - 1:x),y,z,c); ++ } ++ ++ //! Access to a pixel value, using Neumann boundary conditions for the X-coordinate \const. ++ const T& atX(const int x, const int y=0, const int z=0, const int c=0) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "atX(): Empty instance.", ++ cimg_instance); ++ return _atX(x,y,z,c); ++ } ++ ++ const T& _atX(const int x, const int y=0, const int z=0, const int c=0) const { ++ return (*this)(x<0?0:(x>=width()?width() - 1:x),y,z,c); ++ } ++ ++ //! Access to a pixel value, using Dirichlet boundary conditions for the X and Y-coordinates. ++ /** ++ Similar to atX(int,int,int,int,const T), except that boundary checking is performed both on X and Y-coordinates. ++ **/ ++ T& atXY(const int x, const int y, const int z, const int c, const T& out_value) { ++ return (x<0 || y<0 || x>=width() || y>=height())?(cimg::temporary(out_value)=out_value):(*this)(x,y,z,c); ++ } ++ ++ //! Access to a pixel value, using Dirichlet boundary conditions for the X and Y coordinates \const. ++ T atXY(const int x, const int y, const int z, const int c, const T& out_value) const { ++ return (x<0 || y<0 || x>=width() || y>=height())?out_value:(*this)(x,y,z,c); ++ } ++ ++ //! Access to a pixel value, using Neumann boundary conditions for the X and Y-coordinates. ++ /** ++ Similar to atX(int,int,int,int), except that boundary checking is performed both on X and Y-coordinates. ++ \note ++ - If you know your image instance is \e not empty, you may rather use the slightly faster method ++ \c _atXY(int,int,int,int). ++ **/ ++ T& atXY(const int x, const int y, const int z=0, const int c=0) { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "atXY(): Empty instance.", ++ cimg_instance); ++ return _atXY(x,y,z,c); ++ } ++ ++ T& _atXY(const int x, const int y, const int z=0, const int c=0) { ++ return (*this)(cimg::cut(x,0,width() - 1), ++ cimg::cut(y,0,height() - 1),z,c); ++ } ++ ++ //! Access to a pixel value, using Neumann boundary conditions for the X and Y-coordinates \const. ++ const T& atXY(const int x, const int y, const int z=0, const int c=0) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "atXY(): Empty instance.", ++ cimg_instance); ++ return _atXY(x,y,z,c); ++ } ++ ++ const T& _atXY(const int x, const int y, const int z=0, const int c=0) const { ++ return (*this)(cimg::cut(x,0,width() - 1), ++ cimg::cut(y,0,height() - 1),z,c); ++ } ++ ++ //! Access to a pixel value, using Dirichlet boundary conditions for the X,Y and Z-coordinates. ++ /** ++ Similar to atX(int,int,int,int,const T), except that boundary checking is performed both on ++ X,Y and Z-coordinates. ++ **/ ++ T& atXYZ(const int x, const int y, const int z, const int c, const T& out_value) { ++ return (x<0 || y<0 || z<0 || x>=width() || y>=height() || z>=depth())? ++ (cimg::temporary(out_value)=out_value):(*this)(x,y,z,c); ++ } ++ ++ //! Access to a pixel value, using Dirichlet boundary conditions for the X,Y and Z-coordinates \const. ++ T atXYZ(const int x, const int y, const int z, const int c, const T& out_value) const { ++ return (x<0 || y<0 || z<0 || x>=width() || y>=height() || z>=depth())?out_value:(*this)(x,y,z,c); ++ } ++ ++ //! Access to a pixel value, using Neumann boundary conditions for the X,Y and Z-coordinates. ++ /** ++ Similar to atX(int,int,int,int), except that boundary checking is performed both on X,Y and Z-coordinates. ++ \note ++ - If you know your image instance is \e not empty, you may rather use the slightly faster method ++ \c _atXYZ(int,int,int,int). ++ **/ ++ T& atXYZ(const int x, const int y, const int z, const int c=0) { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "atXYZ(): Empty instance.", ++ cimg_instance); ++ return _atXYZ(x,y,z,c); ++ } ++ ++ T& _atXYZ(const int x, const int y, const int z, const int c=0) { ++ return (*this)(cimg::cut(x,0,width() - 1), ++ cimg::cut(y,0,height() - 1), ++ cimg::cut(z,0,depth() - 1),c); ++ } ++ ++ //! Access to a pixel value, using Neumann boundary conditions for the X,Y and Z-coordinates \const. ++ const T& atXYZ(const int x, const int y, const int z, const int c=0) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "atXYZ(): Empty instance.", ++ cimg_instance); ++ return _atXYZ(x,y,z,c); ++ } ++ ++ const T& _atXYZ(const int x, const int y, const int z, const int c=0) const { ++ return (*this)(cimg::cut(x,0,width() - 1), ++ cimg::cut(y,0,height() - 1), ++ cimg::cut(z,0,depth() - 1),c); ++ } ++ ++ //! Access to a pixel value, using Dirichlet boundary conditions. ++ /** ++ Similar to atX(int,int,int,int,const T), except that boundary checking is performed on all ++ X,Y,Z and C-coordinates. ++ **/ ++ T& atXYZC(const int x, const int y, const int z, const int c, const T& out_value) { ++ return (x<0 || y<0 || z<0 || c<0 || x>=width() || y>=height() || z>=depth() || c>=spectrum())? ++ (cimg::temporary(out_value)=out_value):(*this)(x,y,z,c); ++ } ++ ++ //! Access to a pixel value, using Dirichlet boundary conditions \const. ++ T atXYZC(const int x, const int y, const int z, const int c, const T& out_value) const { ++ return (x<0 || y<0 || z<0 || c<0 || x>=width() || y>=height() || z>=depth() || c>=spectrum())?out_value: ++ (*this)(x,y,z,c); ++ } ++ ++ //! Access to a pixel value, using Neumann boundary conditions. ++ /** ++ Similar to atX(int,int,int,int), except that boundary checking is performed on all X,Y,Z and C-coordinates. ++ \note ++ - If you know your image instance is \e not empty, you may rather use the slightly faster method ++ \c _atXYZC(int,int,int,int). ++ **/ ++ T& atXYZC(const int x, const int y, const int z, const int c) { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "atXYZC(): Empty instance.", ++ cimg_instance); ++ return _atXYZC(x,y,z,c); ++ } ++ ++ T& _atXYZC(const int x, const int y, const int z, const int c) { ++ return (*this)(cimg::cut(x,0,width() - 1), ++ cimg::cut(y,0,height() - 1), ++ cimg::cut(z,0,depth() - 1), ++ cimg::cut(c,0,spectrum() - 1)); ++ } ++ ++ //! Access to a pixel value, using Neumann boundary conditions \const. ++ const T& atXYZC(const int x, const int y, const int z, const int c) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "atXYZC(): Empty instance.", ++ cimg_instance); ++ return _atXYZC(x,y,z,c); ++ } ++ ++ const T& _atXYZC(const int x, const int y, const int z, const int c) const { ++ return (*this)(cimg::cut(x,0,width() - 1), ++ cimg::cut(y,0,height() - 1), ++ cimg::cut(z,0,depth() - 1), ++ cimg::cut(c,0,spectrum() - 1)); ++ } ++ ++ //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for the X-coordinate. ++ /** ++ Return a linearly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c), ++ or a specified default value in case of out-of-bounds access along the X-axis. ++ \param fx X-coordinate of the pixel value (float-valued). ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ \param c C-coordinate of the pixel value. ++ \param out_value Default value returned if \c (\c fx,\c y,\c z,\c c) is outside image bounds. ++ \note ++ - Similar to atX(int,int,int,int,const T), except that the returned pixel value is approximated by ++ a linear interpolation along the X-axis, if corresponding coordinates are not integers. ++ - The type of the returned pixel value is extended to \c float, if the pixel type \c T is not float-valued. ++ \warning ++ - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. ++ **/ ++ Tfloat linear_atX(const float fx, const int y, const int z, const int c, const T& out_value) 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,c,out_value), In = (Tfloat)atXY(nx,y,z,c,out_value); ++ return Ic + dx*(In - Ic); ++ } ++ ++ //! Return pixel value, using linear interpolation and Neumann boundary conditions for the X-coordinate. ++ /** ++ Return a linearly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c), ++ or the value of the nearest pixel location in the image instance in case of out-of-bounds access along ++ the X-axis. ++ \param fx X-coordinate of the pixel value (float-valued). ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ \param c C-coordinate of the pixel value. ++ \note ++ - Similar to linear_atX(float,int,int,int,const T) const, except that an out-of-bounds access returns ++ the value of the nearest pixel in the image instance, regarding the specified X-coordinate. ++ - If you know your image instance is \e not empty, you may rather use the slightly faster method ++ \c _linear_atX(float,int,int,int). ++ \warning ++ - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. ++ **/ ++ Tfloat linear_atX(const float fx, const int y=0, const int z=0, const int c=0) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "linear_atX(): Empty instance.", ++ cimg_instance); ++ ++ return _linear_atX(fx,y,z,c); ++ } ++ ++ Tfloat _linear_atX(const float fx, const int y=0, const int z=0, const int c=0) const { ++ const float ++ nfx = cimg::cut(fx,0,width() - 1); ++ 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,c), In = (Tfloat)(*this)(nx,y,z,c); ++ return Ic + dx*(In - Ic); ++ } ++ ++ //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for the X and Y-coordinates. ++ /** ++ Similar to linear_atX(float,int,int,int,const T) const, except that the linear interpolation and the ++ boundary checking are achieved both for X and Y-coordinates. ++ **/ ++ Tfloat linear_atXY(const float fx, const float fy, const int z, const int c, const T& out_value) 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,c,out_value), Inc = (Tfloat)atXY(nx,y,z,c,out_value), ++ Icn = (Tfloat)atXY(x,ny,z,c,out_value), Inn = (Tfloat)atXY(nx,ny,z,c,out_value); ++ return Icc + dx*(Inc - Icc + dy*(Icc + Inn - Icn - Inc)) + dy*(Icn - Icc); ++ } ++ ++ //! Return pixel value, using linear interpolation and Neumann boundary conditions for the X and Y-coordinates. ++ /** ++ Similar to linear_atX(float,int,int,int) const, except that the linear interpolation and the boundary checking ++ are achieved both for X and Y-coordinates. ++ \note ++ - If you know your image instance is \e not empty, you may rather use the slightly faster method ++ \c _linear_atXY(float,float,int,int). ++ **/ ++ Tfloat linear_atXY(const float fx, const float fy, const int z=0, const int c=0) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "linear_atXY(): Empty instance.", ++ cimg_instance); ++ ++ return _linear_atXY(fx,fy,z,c); ++ } ++ ++ Tfloat _linear_atXY(const float fx, const float fy, const int z=0, const int c=0) const { ++ const float ++ nfx = cimg::cut(fx,0,width() - 1), ++ nfy = cimg::cut(fy,0,height() - 1); ++ 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,c), Inc = (Tfloat)(*this)(nx,y,z,c), ++ Icn = (Tfloat)(*this)(x,ny,z,c), Inn = (Tfloat)(*this)(nx,ny,z,c); ++ return Icc + dx*(Inc - Icc + dy*(Icc + Inn - Icn - Inc)) + dy*(Icn - Icc); ++ } ++ ++ //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for the X,Y and Z-coordinates. ++ /** ++ Similar to linear_atX(float,int,int,int,const T) const, except that the linear interpolation and the ++ boundary checking are achieved both for X,Y and Z-coordinates. ++ **/ ++ Tfloat linear_atXYZ(const float fx, const float fy, const float fz, const int c, const T& out_value) 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,c,out_value), Incc = (Tfloat)atXYZ(nx,y,z,c,out_value), ++ Icnc = (Tfloat)atXYZ(x,ny,z,c,out_value), Innc = (Tfloat)atXYZ(nx,ny,z,c,out_value), ++ Iccn = (Tfloat)atXYZ(x,y,nz,c,out_value), Incn = (Tfloat)atXYZ(nx,y,nz,c,out_value), ++ Icnn = (Tfloat)atXYZ(x,ny,nz,c,out_value), Innn = (Tfloat)atXYZ(nx,ny,nz,c,out_value); ++ 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); ++ } ++ ++ //! Return pixel value, using linear interpolation and Neumann boundary conditions for the X,Y and Z-coordinates. ++ /** ++ Similar to linear_atX(float,int,int,int) const, except that the linear interpolation and the boundary checking ++ are achieved both for X,Y and Z-coordinates. ++ \note ++ - If you know your image instance is \e not empty, you may rather use the slightly faster method ++ \c _linear_atXYZ(float,float,float,int). ++ **/ ++ Tfloat linear_atXYZ(const float fx, const float fy=0, const float fz=0, const int c=0) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "linear_atXYZ(): Empty instance.", ++ cimg_instance); ++ ++ return _linear_atXYZ(fx,fy,fz,c); ++ } ++ ++ Tfloat _linear_atXYZ(const float fx, const float fy=0, const float fz=0, const int c=0) const { ++ const float ++ nfx = cimg::cut(fx,0,width() - 1), ++ nfy = cimg::cut(fy,0,height() - 1), ++ nfz = cimg::cut(fz,0,depth() - 1); ++ 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,c), Incc = (Tfloat)(*this)(nx,y,z,c), ++ Icnc = (Tfloat)(*this)(x,ny,z,c), Innc = (Tfloat)(*this)(nx,ny,z,c), ++ Iccn = (Tfloat)(*this)(x,y,nz,c), Incn = (Tfloat)(*this)(nx,y,nz,c), ++ Icnn = (Tfloat)(*this)(x,ny,nz,c), Innn = (Tfloat)(*this)(nx,ny,nz,c); ++ 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); ++ } ++ ++ //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for all X,Y,Z,C-coordinates. ++ /** ++ Similar to linear_atX(float,int,int,int,const T) const, except that the linear interpolation and the ++ boundary checking are achieved for all X,Y,Z and C-coordinates. ++ **/ ++ Tfloat linear_atXYZC(const float fx, const float fy, const float fz, const float fc, const T& out_value) 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, ++ c = (int)fc - (fc>=0?0:1), nc = c + 1; ++ const float ++ dx = fx - x, ++ dy = fy - y, ++ dz = fz - z, ++ dc = fc - c; ++ const Tfloat ++ Icccc = (Tfloat)atXYZC(x,y,z,c,out_value), Inccc = (Tfloat)atXYZC(nx,y,z,c,out_value), ++ Icncc = (Tfloat)atXYZC(x,ny,z,c,out_value), Inncc = (Tfloat)atXYZC(nx,ny,z,c,out_value), ++ Iccnc = (Tfloat)atXYZC(x,y,nz,c,out_value), Incnc = (Tfloat)atXYZC(nx,y,nz,c,out_value), ++ Icnnc = (Tfloat)atXYZC(x,ny,nz,c,out_value), Innnc = (Tfloat)atXYZC(nx,ny,nz,c,out_value), ++ Icccn = (Tfloat)atXYZC(x,y,z,nc,out_value), Inccn = (Tfloat)atXYZC(nx,y,z,nc,out_value), ++ Icncn = (Tfloat)atXYZC(x,ny,z,nc,out_value), Inncn = (Tfloat)atXYZC(nx,ny,z,nc,out_value), ++ Iccnn = (Tfloat)atXYZC(x,y,nz,nc,out_value), Incnn = (Tfloat)atXYZC(nx,y,nz,nc,out_value), ++ Icnnn = (Tfloat)atXYZC(x,ny,nz,nc,out_value), Innnn = (Tfloat)atXYZC(nx,ny,nz,nc,out_value); ++ return Icccc + ++ dx*(Inccc - Icccc + ++ dy*(Icccc + Inncc - Icncc - Inccc + ++ dz*(Iccnc + Innnc + Icncc + Inccc - Icnnc - Incnc - Icccc - Inncc + ++ dc*(Iccnn + Innnn + Icncn + Inccn + Icnnc + Incnc + Icccc + Inncc - ++ Icnnn - Incnn - Icccn - Inncn - Iccnc - Innnc - Icncc - Inccc)) + ++ dc*(Icccn + Inncn + Icncc + Inccc - Icncn - Inccn - Icccc - Inncc)) + ++ dz*(Icccc + Incnc - Iccnc - Inccc + ++ dc*(Icccn + Incnn + Iccnc + Inccc - Iccnn - Inccn - Icccc - Incnc)) + ++ dc*(Icccc + Inccn - Inccc - Icccn)) + ++ dy*(Icncc - Icccc + ++ dz*(Icccc + Icnnc - Iccnc - Icncc + ++ dc*(Icccn + Icnnn + Iccnc + Icncc - Iccnn - Icncn - Icccc - Icnnc)) + ++ dc*(Icccc + Icncn - Icncc - Icccn)) + ++ dz*(Iccnc - Icccc + ++ dc*(Icccc + Iccnn - Iccnc - Icccn)) + ++ dc*(Icccn -Icccc); ++ } ++ ++ //! Return pixel value, using linear interpolation and Neumann boundary conditions for all X,Y,Z and C-coordinates. ++ /** ++ Similar to linear_atX(float,int,int,int) const, except that the linear interpolation and the boundary checking ++ are achieved for all X,Y,Z and C-coordinates. ++ \note ++ - If you know your image instance is \e not empty, you may rather use the slightly faster method ++ \c _linear_atXYZC(float,float,float,float). ++ **/ ++ Tfloat linear_atXYZC(const float fx, const float fy=0, const float fz=0, const float fc=0) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "linear_atXYZC(): Empty instance.", ++ cimg_instance); ++ ++ return _linear_atXYZC(fx,fy,fz,fc); ++ } ++ ++ Tfloat _linear_atXYZC(const float fx, const float fy=0, const float fz=0, const float fc=0) const { ++ const float ++ nfx = cimg::cut(fx,0,width() - 1), ++ nfy = cimg::cut(fy,0,height() - 1), ++ nfz = cimg::cut(fz,0,depth() - 1), ++ nfc = cimg::cut(fc,0,spectrum() - 1); ++ const unsigned int ++ x = (unsigned int)nfx, ++ y = (unsigned int)nfy, ++ z = (unsigned int)nfz, ++ c = (unsigned int)nfc; ++ const float ++ dx = nfx - x, ++ dy = nfy - y, ++ dz = nfz - z, ++ dc = nfc - c; ++ const unsigned int ++ nx = dx>0?x + 1:x, ++ ny = dy>0?y + 1:y, ++ nz = dz>0?z + 1:z, ++ nc = dc>0?c + 1:c; ++ const Tfloat ++ Icccc = (Tfloat)(*this)(x,y,z,c), Inccc = (Tfloat)(*this)(nx,y,z,c), ++ Icncc = (Tfloat)(*this)(x,ny,z,c), Inncc = (Tfloat)(*this)(nx,ny,z,c), ++ Iccnc = (Tfloat)(*this)(x,y,nz,c), Incnc = (Tfloat)(*this)(nx,y,nz,c), ++ Icnnc = (Tfloat)(*this)(x,ny,nz,c), Innnc = (Tfloat)(*this)(nx,ny,nz,c), ++ Icccn = (Tfloat)(*this)(x,y,z,nc), Inccn = (Tfloat)(*this)(nx,y,z,nc), ++ Icncn = (Tfloat)(*this)(x,ny,z,nc), Inncn = (Tfloat)(*this)(nx,ny,z,nc), ++ Iccnn = (Tfloat)(*this)(x,y,nz,nc), Incnn = (Tfloat)(*this)(nx,y,nz,nc), ++ Icnnn = (Tfloat)(*this)(x,ny,nz,nc), Innnn = (Tfloat)(*this)(nx,ny,nz,nc); ++ return Icccc + ++ dx*(Inccc - Icccc + ++ dy*(Icccc + Inncc - Icncc - Inccc + ++ dz*(Iccnc + Innnc + Icncc + Inccc - Icnnc - Incnc - Icccc - Inncc + ++ dc*(Iccnn + Innnn + Icncn + Inccn + Icnnc + Incnc + Icccc + Inncc - ++ Icnnn - Incnn - Icccn - Inncn - Iccnc - Innnc - Icncc - Inccc)) + ++ dc*(Icccn + Inncn + Icncc + Inccc - Icncn - Inccn - Icccc - Inncc)) + ++ dz*(Icccc + Incnc - Iccnc - Inccc + ++ dc*(Icccn + Incnn + Iccnc + Inccc - Iccnn - Inccn - Icccc - Incnc)) + ++ dc*(Icccc + Inccn - Inccc - Icccn)) + ++ dy*(Icncc - Icccc + ++ dz*(Icccc + Icnnc - Iccnc - Icncc + ++ dc*(Icccn + Icnnn + Iccnc + Icncc - Iccnn - Icncn - Icccc - Icnnc)) + ++ dc*(Icccc + Icncn - Icncc - Icccn)) + ++ dz*(Iccnc - Icccc + ++ dc*(Icccc + Iccnn - Iccnc - Icccn)) + ++ dc*(Icccn - Icccc); ++ } ++ ++ //! Return pixel value, using cubic interpolation and Dirichlet boundary conditions for the X-coordinate. ++ /** ++ Return a cubicly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c), ++ or a specified default value in case of out-of-bounds access along the X-axis. ++ The cubic interpolation uses Hermite splines. ++ \param fx d X-coordinate of the pixel value (float-valued). ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ \param c C-coordinate of the pixel value. ++ \param out_value Default value returned if \c (\c fx,\c y,\c z,\c c) is outside image bounds. ++ \note ++ - Similar to linear_atX(float,int,int,int,const T) const, except that the returned pixel value is ++ approximated by a \e cubic interpolation along the X-axis. ++ - The type of the returned pixel value is extended to \c float, if the pixel type \c T is not float-valued. ++ \warning ++ - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. ++ **/ ++ Tfloat cubic_atX(const float fx, const int y, const int z, const int c, const T& out_value) 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,c,out_value), Ic = (Tfloat)atX(x,y,z,c,out_value), ++ In = (Tfloat)atX(nx,y,z,c,out_value), Ia = (Tfloat)atX(ax,y,z,c,out_value); ++ return Ic + 0.5f*(dx*(-Ip + In) + dx*dx*(2*Ip - 5*Ic + 4*In - Ia) + dx*dx*dx*(-Ip + 3*Ic - 3*In + Ia)); ++ } ++ ++ //! Return clamped pixel value, using cubic interpolation and Dirichlet boundary conditions for the X-coordinate. ++ /** ++ Similar to cubic_atX(float,int,int,int,const T) const, except that the return value is clamped to stay in the ++ min/max range of the datatype \c T. ++ **/ ++ T cubic_cut_atX(const float fx, const int y, const int z, const int c, const T& out_value) const { ++ return cimg::type::cut(cubic_atX(fx,y,z,c,out_value)); ++ } ++ ++ //! Return pixel value, using cubic interpolation and Neumann boundary conditions for the X-coordinate. ++ /** ++ Return a cubicly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c), ++ or the value of the nearest pixel location in the image instance in case of out-of-bounds access ++ along the X-axis. The cubic interpolation uses Hermite splines. ++ \param fx X-coordinate of the pixel value (float-valued). ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ \param c C-coordinate of the pixel value. ++ \note ++ - Similar to cubic_atX(float,int,int,int,const T) const, except that the returned pixel value is ++ approximated by a cubic interpolation along the X-axis. ++ - If you know your image instance is \e not empty, you may rather use the slightly faster method ++ \c _cubic_atX(float,int,int,int). ++ \warning ++ - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds. ++ **/ ++ Tfloat cubic_atX(const float fx, const int y=0, const int z=0, const int c=0) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "cubic_atX(): Empty instance.", ++ cimg_instance); ++ return _cubic_atX(fx,y,z,c); ++ } ++ ++ Tfloat _cubic_atX(const float fx, const int y=0, const int z=0, const int c=0) const { ++ const float ++ nfx = cimg::cut(fx,0,width() - 1); ++ 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>=width()?width() - 1:x + 2; ++ const Tfloat ++ Ip = (Tfloat)(*this)(px,y,z,c), Ic = (Tfloat)(*this)(x,y,z,c), ++ In = (Tfloat)(*this)(nx,y,z,c), Ia = (Tfloat)(*this)(ax,y,z,c); ++ return Ic + 0.5f*(dx*(-Ip + In) + dx*dx*(2*Ip - 5*Ic + 4*In - Ia) + dx*dx*dx*(-Ip + 3*Ic - 3*In + Ia)); ++ } ++ ++ //! Return clamped pixel value, using cubic interpolation and Neumann boundary conditions for the X-coordinate. ++ /** ++ Similar to cubic_atX(float,int,int,int) const, except that the return value is clamped to stay in the ++ min/max range of the datatype \c T. ++ **/ ++ T cubic_cut_atX(const float fx, const int y, const int z, const int c) const { ++ return cimg::type::cut(cubic_atX(fx,y,z,c)); ++ } ++ ++ T _cubic_cut_atX(const float fx, const int y, const int z, const int c) const { ++ return cimg::type::cut(_cubic_atX(fx,y,z,c)); ++ } ++ ++ //! Return pixel value, using cubic interpolation and Dirichlet boundary conditions for the X and Y-coordinates. ++ /** ++ Similar to cubic_atX(float,int,int,int,const T) const, except that the cubic interpolation and boundary checking ++ are achieved both for X and Y-coordinates. ++ **/ ++ Tfloat cubic_atXY(const float fx, const float fy, const int z, const int c, const T& out_value) 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, dy = fy - y; ++ const Tfloat ++ Ipp = (Tfloat)atXY(px,py,z,c,out_value), Icp = (Tfloat)atXY(x,py,z,c,out_value), ++ Inp = (Tfloat)atXY(nx,py,z,c,out_value), Iap = (Tfloat)atXY(ax,py,z,c,out_value), ++ Ip = Icp + 0.5f*(dx*(-Ipp + Inp) + dx*dx*(2*Ipp - 5*Icp + 4*Inp - Iap) + dx*dx*dx*(-Ipp + 3*Icp - 3*Inp + Iap)), ++ Ipc = (Tfloat)atXY(px,y,z,c,out_value), Icc = (Tfloat)atXY(x, y,z,c,out_value), ++ Inc = (Tfloat)atXY(nx,y,z,c,out_value), Iac = (Tfloat)atXY(ax,y,z,c,out_value), ++ Ic = Icc + 0.5f*(dx*(-Ipc + Inc) + dx*dx*(2*Ipc - 5*Icc + 4*Inc - Iac) + dx*dx*dx*(-Ipc + 3*Icc - 3*Inc + Iac)), ++ Ipn = (Tfloat)atXY(px,ny,z,c,out_value), Icn = (Tfloat)atXY(x,ny,z,c,out_value), ++ Inn = (Tfloat)atXY(nx,ny,z,c,out_value), Ian = (Tfloat)atXY(ax,ny,z,c,out_value), ++ In = Icn + 0.5f*(dx*(-Ipn + Inn) + dx*dx*(2*Ipn - 5*Icn + 4*Inn - Ian) + dx*dx*dx*(-Ipn + 3*Icn - 3*Inn + Ian)), ++ Ipa = (Tfloat)atXY(px,ay,z,c,out_value), Ica = (Tfloat)atXY(x,ay,z,c,out_value), ++ Ina = (Tfloat)atXY(nx,ay,z,c,out_value), Iaa = (Tfloat)atXY(ax,ay,z,c,out_value), ++ Ia = Ica + 0.5f*(dx*(-Ipa + Ina) + dx*dx*(2*Ipa - 5*Ica + 4*Ina - Iaa) + dx*dx*dx*(-Ipa + 3*Ica - 3*Ina + Iaa)); ++ return Ic + 0.5f*(dy*(-Ip + In) + dy*dy*(2*Ip - 5*Ic + 4*In - Ia) + dy*dy*dy*(-Ip + 3*Ic - 3*In + Ia)); ++ } ++ ++ //! Return clamped pixel value, using cubic interpolation and Dirichlet boundary conditions for the X,Y-coordinates. ++ /** ++ Similar to cubic_atXY(float,float,int,int,const T) const, except that the return value is clamped to stay in the ++ min/max range of the datatype \c T. ++ **/ ++ T cubic_cut_atXY(const float fx, const float fy, const int z, const int c, const T& out_value) const { ++ return cimg::type::cut(cubic_atXY(fx,fy,z,c,out_value)); ++ } ++ ++ //! Return pixel value, using cubic interpolation and Neumann boundary conditions for the X and Y-coordinates. ++ /** ++ Similar to cubic_atX(float,int,int,int) const, except that the cubic interpolation and boundary checking ++ are achieved for both X and Y-coordinates. ++ \note ++ - If you know your image instance is \e not empty, you may rather use the slightly faster method ++ \c _cubic_atXY(float,float,int,int). ++ **/ ++ Tfloat cubic_atXY(const float fx, const float fy, const int z=0, const int c=0) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "cubic_atXY(): Empty instance.", ++ cimg_instance); ++ return _cubic_atXY(fx,fy,z,c); ++ } ++ ++ Tfloat _cubic_atXY(const float fx, const float fy, const int z=0, const int c=0) const { ++ const float ++ nfx = cimg::cut(fx,0,width() - 1), ++ nfy = cimg::cut(fy,0,height() - 1); ++ const int x = (int)nfx, y = (int)nfy; ++ const float dx = nfx - x, dy = nfy - y; ++ const int ++ px = x - 1<0?0:x - 1, nx = dx>0?x + 1:x, ax = x + 2>=width()?width() - 1:x + 2, ++ py = y - 1<0?0:y - 1, ny = dy>0?y + 1:y, ay = y + 2>=height()?height() - 1:y + 2; ++ const Tfloat ++ Ipp = (Tfloat)(*this)(px,py,z,c), Icp = (Tfloat)(*this)(x,py,z,c), Inp = (Tfloat)(*this)(nx,py,z,c), ++ Iap = (Tfloat)(*this)(ax,py,z,c), ++ Ip = Icp + 0.5f*(dx*(-Ipp + Inp) + dx*dx*(2*Ipp - 5*Icp + 4*Inp - Iap) + dx*dx*dx*(-Ipp + 3*Icp - 3*Inp + Iap)), ++ Ipc = (Tfloat)(*this)(px,y,z,c), Icc = (Tfloat)(*this)(x, y,z,c), Inc = (Tfloat)(*this)(nx,y,z,c), ++ Iac = (Tfloat)(*this)(ax,y,z,c), ++ Ic = Icc + 0.5f*(dx*(-Ipc + Inc) + dx*dx*(2*Ipc - 5*Icc + 4*Inc - Iac) + dx*dx*dx*(-Ipc + 3*Icc - 3*Inc + Iac)), ++ Ipn = (Tfloat)(*this)(px,ny,z,c), Icn = (Tfloat)(*this)(x,ny,z,c), Inn = (Tfloat)(*this)(nx,ny,z,c), ++ Ian = (Tfloat)(*this)(ax,ny,z,c), ++ In = Icn + 0.5f*(dx*(-Ipn + Inn) + dx*dx*(2*Ipn - 5*Icn + 4*Inn - Ian) + dx*dx*dx*(-Ipn + 3*Icn - 3*Inn + Ian)), ++ Ipa = (Tfloat)(*this)(px,ay,z,c), Ica = (Tfloat)(*this)(x,ay,z,c), Ina = (Tfloat)(*this)(nx,ay,z,c), ++ Iaa = (Tfloat)(*this)(ax,ay,z,c), ++ Ia = Ica + 0.5f*(dx*(-Ipa + Ina) + dx*dx*(2*Ipa - 5*Ica + 4*Ina - Iaa) + dx*dx*dx*(-Ipa + 3*Ica - 3*Ina + Iaa)); ++ return Ic + 0.5f*(dy*(-Ip + In) + dy*dy*(2*Ip - 5*Ic + 4*In - Ia) + dy*dy*dy*(-Ip + 3*Ic - 3*In + Ia)); ++ } ++ ++ //! Return clamped pixel value, using cubic interpolation and Neumann boundary conditions for the X,Y-coordinates. ++ /** ++ Similar to cubic_atXY(float,float,int,int) const, except that the return value is clamped to stay in the ++ min/max range of the datatype \c T. ++ **/ ++ T cubic_cut_atXY(const float fx, const float fy, const int z, const int c) const { ++ return cimg::type::cut(cubic_atXY(fx,fy,z,c)); ++ } ++ ++ T _cubic_cut_atXY(const float fx, const float fy, const int z, const int c) const { ++ return cimg::type::cut(_cubic_atXY(fx,fy,z,c)); ++ } ++ ++ //! Return pixel value, using cubic interpolation and Dirichlet boundary conditions for the X,Y and Z-coordinates. ++ /** ++ Similar to cubic_atX(float,int,int,int,const T) const, except that the cubic interpolation and boundary checking ++ are achieved both for X,Y and Z-coordinates. ++ **/ ++ Tfloat cubic_atXYZ(const float fx, const float fy, const float fz, const int c, const T& out_value) 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, ++ z = (int)fz - (fz>=0?0:1), pz = z - 1, nz = z + 1, az = z + 2; ++ const float dx = fx - x, dy = fy - y, dz = fz - z; ++ const Tfloat ++ Ippp = (Tfloat)atXYZ(px,py,pz,c,out_value), Icpp = (Tfloat)atXYZ(x,py,pz,c,out_value), ++ Inpp = (Tfloat)atXYZ(nx,py,pz,c,out_value), Iapp = (Tfloat)atXYZ(ax,py,pz,c,out_value), ++ Ipp = Icpp + 0.5f*(dx*(-Ippp + Inpp) + dx*dx*(2*Ippp - 5*Icpp + 4*Inpp - Iapp) + ++ dx*dx*dx*(-Ippp + 3*Icpp - 3*Inpp + Iapp)), ++ Ipcp = (Tfloat)atXYZ(px,y,pz,c,out_value), Iccp = (Tfloat)atXYZ(x, y,pz,c,out_value), ++ Incp = (Tfloat)atXYZ(nx,y,pz,c,out_value), Iacp = (Tfloat)atXYZ(ax,y,pz,c,out_value), ++ Icp = Iccp + 0.5f*(dx*(-Ipcp + Incp) + dx*dx*(2*Ipcp - 5*Iccp + 4*Incp - Iacp) + ++ dx*dx*dx*(-Ipcp + 3*Iccp - 3*Incp + Iacp)), ++ Ipnp = (Tfloat)atXYZ(px,ny,pz,c,out_value), Icnp = (Tfloat)atXYZ(x,ny,pz,c,out_value), ++ Innp = (Tfloat)atXYZ(nx,ny,pz,c,out_value), Ianp = (Tfloat)atXYZ(ax,ny,pz,c,out_value), ++ Inp = Icnp + 0.5f*(dx*(-Ipnp + Innp) + dx*dx*(2*Ipnp - 5*Icnp + 4*Innp - Ianp) + ++ dx*dx*dx*(-Ipnp + 3*Icnp - 3*Innp + Ianp)), ++ Ipap = (Tfloat)atXYZ(px,ay,pz,c,out_value), Icap = (Tfloat)atXYZ(x,ay,pz,c,out_value), ++ Inap = (Tfloat)atXYZ(nx,ay,pz,c,out_value), Iaap = (Tfloat)atXYZ(ax,ay,pz,c,out_value), ++ Iap = Icap + 0.5f*(dx*(-Ipap + Inap) + dx*dx*(2*Ipap - 5*Icap + 4*Inap - Iaap) + ++ dx*dx*dx*(-Ipap + 3*Icap - 3*Inap + Iaap)), ++ Ip = Icp + 0.5f*(dy*(-Ipp + Inp) + dy*dy*(2*Ipp - 5*Icp + 4*Inp - Iap) + ++ dy*dy*dy*(-Ipp + 3*Icp - 3*Inp + Iap)), ++ Ippc = (Tfloat)atXYZ(px,py,z,c,out_value), Icpc = (Tfloat)atXYZ(x,py,z,c,out_value), ++ Inpc = (Tfloat)atXYZ(nx,py,z,c,out_value), Iapc = (Tfloat)atXYZ(ax,py,z,c,out_value), ++ Ipc = Icpc + 0.5f*(dx*(-Ippc + Inpc) + dx*dx*(2*Ippc - 5*Icpc + 4*Inpc - Iapc) + ++ dx*dx*dx*(-Ippc + 3*Icpc - 3*Inpc + Iapc)), ++ Ipcc = (Tfloat)atXYZ(px,y,z,c,out_value), Iccc = (Tfloat)atXYZ(x, y,z,c,out_value), ++ Incc = (Tfloat)atXYZ(nx,y,z,c,out_value), Iacc = (Tfloat)atXYZ(ax,y,z,c,out_value), ++ Icc = Iccc + 0.5f*(dx*(-Ipcc + Incc) + dx*dx*(2*Ipcc - 5*Iccc + 4*Incc - Iacc) + ++ dx*dx*dx*(-Ipcc + 3*Iccc - 3*Incc + Iacc)), ++ Ipnc = (Tfloat)atXYZ(px,ny,z,c,out_value), Icnc = (Tfloat)atXYZ(x,ny,z,c,out_value), ++ Innc = (Tfloat)atXYZ(nx,ny,z,c,out_value), Ianc = (Tfloat)atXYZ(ax,ny,z,c,out_value), ++ Inc = Icnc + 0.5f*(dx*(-Ipnc + Innc) + dx*dx*(2*Ipnc - 5*Icnc + 4*Innc - Ianc) + ++ dx*dx*dx*(-Ipnc + 3*Icnc - 3*Innc + Ianc)), ++ Ipac = (Tfloat)atXYZ(px,ay,z,c,out_value), Icac = (Tfloat)atXYZ(x,ay,z,c,out_value), ++ Inac = (Tfloat)atXYZ(nx,ay,z,c,out_value), Iaac = (Tfloat)atXYZ(ax,ay,z,c,out_value), ++ Iac = Icac + 0.5f*(dx*(-Ipac + Inac) + dx*dx*(2*Ipac - 5*Icac + 4*Inac - Iaac) + ++ dx*dx*dx*(-Ipac + 3*Icac - 3*Inac + Iaac)), ++ Ic = Icc + 0.5f*(dy*(-Ipc + Inc) + dy*dy*(2*Ipc - 5*Icc + 4*Inc - Iac) + ++ dy*dy*dy*(-Ipc + 3*Icc - 3*Inc + Iac)), ++ Ippn = (Tfloat)atXYZ(px,py,nz,c,out_value), Icpn = (Tfloat)atXYZ(x,py,nz,c,out_value), ++ Inpn = (Tfloat)atXYZ(nx,py,nz,c,out_value), Iapn = (Tfloat)atXYZ(ax,py,nz,c,out_value), ++ Ipn = Icpn + 0.5f*(dx*(-Ippn + Inpn) + dx*dx*(2*Ippn - 5*Icpn + 4*Inpn - Iapn) + ++ dx*dx*dx*(-Ippn + 3*Icpn - 3*Inpn + Iapn)), ++ Ipcn = (Tfloat)atXYZ(px,y,nz,c,out_value), Iccn = (Tfloat)atXYZ(x, y,nz,c,out_value), ++ Incn = (Tfloat)atXYZ(nx,y,nz,c,out_value), Iacn = (Tfloat)atXYZ(ax,y,nz,c,out_value), ++ Icn = Iccn + 0.5f*(dx*(-Ipcn + Incn) + dx*dx*(2*Ipcn - 5*Iccn + 4*Incn - Iacn) + ++ dx*dx*dx*(-Ipcn + 3*Iccn - 3*Incn + Iacn)), ++ Ipnn = (Tfloat)atXYZ(px,ny,nz,c,out_value), Icnn = (Tfloat)atXYZ(x,ny,nz,c,out_value), ++ Innn = (Tfloat)atXYZ(nx,ny,nz,c,out_value), Iann = (Tfloat)atXYZ(ax,ny,nz,c,out_value), ++ Inn = Icnn + 0.5f*(dx*(-Ipnn + Innn) + dx*dx*(2*Ipnn - 5*Icnn + 4*Innn - Iann) + ++ dx*dx*dx*(-Ipnn + 3*Icnn - 3*Innn + Iann)), ++ Ipan = (Tfloat)atXYZ(px,ay,nz,c,out_value), Ican = (Tfloat)atXYZ(x,ay,nz,c,out_value), ++ Inan = (Tfloat)atXYZ(nx,ay,nz,c,out_value), Iaan = (Tfloat)atXYZ(ax,ay,nz,c,out_value), ++ Ian = Ican + 0.5f*(dx*(-Ipan + Inan) + dx*dx*(2*Ipan - 5*Ican + 4*Inan - Iaan) + ++ dx*dx*dx*(-Ipan + 3*Ican - 3*Inan + Iaan)), ++ In = Icn + 0.5f*(dy*(-Ipn + Inn) + dy*dy*(2*Ipn - 5*Icn + 4*Inn - Ian) + ++ dy*dy*dy*(-Ipn + 3*Icn - 3*Inn + Ian)), ++ Ippa = (Tfloat)atXYZ(px,py,az,c,out_value), Icpa = (Tfloat)atXYZ(x,py,az,c,out_value), ++ Inpa = (Tfloat)atXYZ(nx,py,az,c,out_value), Iapa = (Tfloat)atXYZ(ax,py,az,c,out_value), ++ Ipa = Icpa + 0.5f*(dx*(-Ippa + Inpa) + dx*dx*(2*Ippa - 5*Icpa + 4*Inpa - Iapa) + ++ dx*dx*dx*(-Ippa + 3*Icpa - 3*Inpa + Iapa)), ++ Ipca = (Tfloat)atXYZ(px,y,az,c,out_value), Icca = (Tfloat)atXYZ(x, y,az,c,out_value), ++ Inca = (Tfloat)atXYZ(nx,y,az,c,out_value), Iaca = (Tfloat)atXYZ(ax,y,az,c,out_value), ++ Ica = Icca + 0.5f*(dx*(-Ipca + Inca) + dx*dx*(2*Ipca - 5*Icca + 4*Inca - Iaca) + ++ dx*dx*dx*(-Ipca + 3*Icca - 3*Inca + Iaca)), ++ Ipna = (Tfloat)atXYZ(px,ny,az,c,out_value), Icna = (Tfloat)atXYZ(x,ny,az,c,out_value), ++ Inna = (Tfloat)atXYZ(nx,ny,az,c,out_value), Iana = (Tfloat)atXYZ(ax,ny,az,c,out_value), ++ Ina = Icna + 0.5f*(dx*(-Ipna + Inna) + dx*dx*(2*Ipna - 5*Icna + 4*Inna - Iana) + ++ dx*dx*dx*(-Ipna + 3*Icna - 3*Inna + Iana)), ++ Ipaa = (Tfloat)atXYZ(px,ay,az,c,out_value), Icaa = (Tfloat)atXYZ(x,ay,az,c,out_value), ++ Inaa = (Tfloat)atXYZ(nx,ay,az,c,out_value), Iaaa = (Tfloat)atXYZ(ax,ay,az,c,out_value), ++ Iaa = Icaa + 0.5f*(dx*(-Ipaa + Inaa) + dx*dx*(2*Ipaa - 5*Icaa + 4*Inaa - Iaaa) + ++ dx*dx*dx*(-Ipaa + 3*Icaa - 3*Inaa + Iaaa)), ++ Ia = Ica + 0.5f*(dy*(-Ipa + Ina) + dy*dy*(2*Ipa - 5*Ica + 4*Ina - Iaa) + ++ dy*dy*dy*(-Ipa + 3*Ica - 3*Ina + Iaa)); ++ return Ic + 0.5f*(dz*(-Ip + In) + dz*dz*(2*Ip - 5*Ic + 4*In - Ia) + dz*dz*dz*(-Ip + 3*Ic - 3*In + Ia)); ++ } ++ ++ //! Return clamped pixel value, using cubic interpolation and Dirichlet boundary conditions for the XYZ-coordinates. ++ /** ++ Similar to cubic_atXYZ(float,float,float,int,const T) const, except that the return value is clamped to stay ++ in the min/max range of the datatype \c T. ++ **/ ++ T cubic_cut_atXYZ(const float fx, const float fy, const float fz, const int c, const T& out_value) const { ++ return cimg::type::cut(cubic_atXYZ(fx,fy,fz,c,out_value)); ++ } ++ ++ //! Return pixel value, using cubic interpolation and Neumann boundary conditions for the X,Y and Z-coordinates. ++ /** ++ Similar to cubic_atX(float,int,int,int) const, except that the cubic interpolation and boundary checking ++ are achieved both for X,Y and Z-coordinates. ++ \note ++ - If you know your image instance is \e not empty, you may rather use the slightly faster method ++ \c _cubic_atXYZ(float,float,float,int). ++ **/ ++ Tfloat cubic_atXYZ(const float fx, const float fy, const float fz, const int c=0) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "cubic_atXYZ(): Empty instance.", ++ cimg_instance); ++ return _cubic_atXYZ(fx,fy,fz,c); ++ } ++ ++ Tfloat _cubic_atXYZ(const float fx, const float fy, const float fz, const int c=0) const { ++ const float ++ nfx = cimg::cut(fx,0,width() - 1), ++ nfy = cimg::cut(fy,0,height() - 1), ++ nfz = cimg::cut(fz,0,depth() - 1); ++ const int x = (int)nfx, y = (int)nfy, z = (int)nfz; ++ const float dx = nfx - x, dy = nfy - y, dz = nfz - z; ++ const int ++ px = x - 1<0?0:x - 1, nx = dx>0?x + 1:x, ax = x + 2>=width()?width() - 1:x + 2, ++ py = y - 1<0?0:y - 1, ny = dy>0?y + 1:y, ay = y + 2>=height()?height() - 1:y + 2, ++ pz = z - 1<0?0:z - 1, nz = dz>0?z + 1:z, az = z + 2>=depth()?depth() - 1:z + 2; ++ const Tfloat ++ Ippp = (Tfloat)(*this)(px,py,pz,c), Icpp = (Tfloat)(*this)(x,py,pz,c), ++ Inpp = (Tfloat)(*this)(nx,py,pz,c), Iapp = (Tfloat)(*this)(ax,py,pz,c), ++ Ipp = Icpp + 0.5f*(dx*(-Ippp + Inpp) + dx*dx*(2*Ippp - 5*Icpp + 4*Inpp - Iapp) + ++ dx*dx*dx*(-Ippp + 3*Icpp - 3*Inpp + Iapp)), ++ Ipcp = (Tfloat)(*this)(px,y,pz,c), Iccp = (Tfloat)(*this)(x, y,pz,c), ++ Incp = (Tfloat)(*this)(nx,y,pz,c), Iacp = (Tfloat)(*this)(ax,y,pz,c), ++ Icp = Iccp + 0.5f*(dx*(-Ipcp + Incp) + dx*dx*(2*Ipcp - 5*Iccp + 4*Incp - Iacp) + ++ dx*dx*dx*(-Ipcp + 3*Iccp - 3*Incp + Iacp)), ++ Ipnp = (Tfloat)(*this)(px,ny,pz,c), Icnp = (Tfloat)(*this)(x,ny,pz,c), ++ Innp = (Tfloat)(*this)(nx,ny,pz,c), Ianp = (Tfloat)(*this)(ax,ny,pz,c), ++ Inp = Icnp + 0.5f*(dx*(-Ipnp + Innp) + dx*dx*(2*Ipnp - 5*Icnp + 4*Innp - Ianp) + ++ dx*dx*dx*(-Ipnp + 3*Icnp - 3*Innp + Ianp)), ++ Ipap = (Tfloat)(*this)(px,ay,pz,c), Icap = (Tfloat)(*this)(x,ay,pz,c), ++ Inap = (Tfloat)(*this)(nx,ay,pz,c), Iaap = (Tfloat)(*this)(ax,ay,pz,c), ++ Iap = Icap + 0.5f*(dx*(-Ipap + Inap) + dx*dx*(2*Ipap - 5*Icap + 4*Inap - Iaap) + ++ dx*dx*dx*(-Ipap + 3*Icap - 3*Inap + Iaap)), ++ Ip = Icp + 0.5f*(dy*(-Ipp + Inp) + dy*dy*(2*Ipp - 5*Icp + 4*Inp - Iap) + ++ dy*dy*dy*(-Ipp + 3*Icp - 3*Inp + Iap)), ++ Ippc = (Tfloat)(*this)(px,py,z,c), Icpc = (Tfloat)(*this)(x,py,z,c), ++ Inpc = (Tfloat)(*this)(nx,py,z,c), Iapc = (Tfloat)(*this)(ax,py,z,c), ++ Ipc = Icpc + 0.5f*(dx*(-Ippc + Inpc) + dx*dx*(2*Ippc - 5*Icpc + 4*Inpc - Iapc) + ++ dx*dx*dx*(-Ippc + 3*Icpc - 3*Inpc + Iapc)), ++ Ipcc = (Tfloat)(*this)(px,y,z,c), Iccc = (Tfloat)(*this)(x, y,z,c), ++ Incc = (Tfloat)(*this)(nx,y,z,c), Iacc = (Tfloat)(*this)(ax,y,z,c), ++ Icc = Iccc + 0.5f*(dx*(-Ipcc + Incc) + dx*dx*(2*Ipcc - 5*Iccc + 4*Incc - Iacc) + ++ dx*dx*dx*(-Ipcc + 3*Iccc - 3*Incc + Iacc)), ++ Ipnc = (Tfloat)(*this)(px,ny,z,c), Icnc = (Tfloat)(*this)(x,ny,z,c), ++ Innc = (Tfloat)(*this)(nx,ny,z,c), Ianc = (Tfloat)(*this)(ax,ny,z,c), ++ Inc = Icnc + 0.5f*(dx*(-Ipnc + Innc) + dx*dx*(2*Ipnc - 5*Icnc + 4*Innc - Ianc) + ++ dx*dx*dx*(-Ipnc + 3*Icnc - 3*Innc + Ianc)), ++ Ipac = (Tfloat)(*this)(px,ay,z,c), Icac = (Tfloat)(*this)(x,ay,z,c), ++ Inac = (Tfloat)(*this)(nx,ay,z,c), Iaac = (Tfloat)(*this)(ax,ay,z,c), ++ Iac = Icac + 0.5f*(dx*(-Ipac + Inac) + dx*dx*(2*Ipac - 5*Icac + 4*Inac - Iaac) + ++ dx*dx*dx*(-Ipac + 3*Icac - 3*Inac + Iaac)), ++ Ic = Icc + 0.5f*(dy*(-Ipc + Inc) + dy*dy*(2*Ipc - 5*Icc + 4*Inc - Iac) + ++ dy*dy*dy*(-Ipc + 3*Icc - 3*Inc + Iac)), ++ Ippn = (Tfloat)(*this)(px,py,nz,c), Icpn = (Tfloat)(*this)(x,py,nz,c), ++ Inpn = (Tfloat)(*this)(nx,py,nz,c), Iapn = (Tfloat)(*this)(ax,py,nz,c), ++ Ipn = Icpn + 0.5f*(dx*(-Ippn + Inpn) + dx*dx*(2*Ippn - 5*Icpn + 4*Inpn - Iapn) + ++ dx*dx*dx*(-Ippn + 3*Icpn - 3*Inpn + Iapn)), ++ Ipcn = (Tfloat)(*this)(px,y,nz,c), Iccn = (Tfloat)(*this)(x, y,nz,c), ++ Incn = (Tfloat)(*this)(nx,y,nz,c), Iacn = (Tfloat)(*this)(ax,y,nz,c), ++ Icn = Iccn + 0.5f*(dx*(-Ipcn + Incn) + dx*dx*(2*Ipcn - 5*Iccn + 4*Incn - Iacn) + ++ dx*dx*dx*(-Ipcn + 3*Iccn - 3*Incn + Iacn)), ++ Ipnn = (Tfloat)(*this)(px,ny,nz,c), Icnn = (Tfloat)(*this)(x,ny,nz,c), ++ Innn = (Tfloat)(*this)(nx,ny,nz,c), Iann = (Tfloat)(*this)(ax,ny,nz,c), ++ Inn = Icnn + 0.5f*(dx*(-Ipnn + Innn) + dx*dx*(2*Ipnn - 5*Icnn + 4*Innn - Iann) + ++ dx*dx*dx*(-Ipnn + 3*Icnn - 3*Innn + Iann)), ++ Ipan = (Tfloat)(*this)(px,ay,nz,c), Ican = (Tfloat)(*this)(x,ay,nz,c), ++ Inan = (Tfloat)(*this)(nx,ay,nz,c), Iaan = (Tfloat)(*this)(ax,ay,nz,c), ++ Ian = Ican + 0.5f*(dx*(-Ipan + Inan) + dx*dx*(2*Ipan - 5*Ican + 4*Inan - Iaan) + ++ dx*dx*dx*(-Ipan + 3*Ican - 3*Inan + Iaan)), ++ In = Icn + 0.5f*(dy*(-Ipn + Inn) + dy*dy*(2*Ipn - 5*Icn + 4*Inn - Ian) + ++ dy*dy*dy*(-Ipn + 3*Icn - 3*Inn + Ian)), ++ Ippa = (Tfloat)(*this)(px,py,az,c), Icpa = (Tfloat)(*this)(x,py,az,c), ++ Inpa = (Tfloat)(*this)(nx,py,az,c), Iapa = (Tfloat)(*this)(ax,py,az,c), ++ Ipa = Icpa + 0.5f*(dx*(-Ippa + Inpa) + dx*dx*(2*Ippa - 5*Icpa + 4*Inpa - Iapa) + ++ dx*dx*dx*(-Ippa + 3*Icpa - 3*Inpa + Iapa)), ++ Ipca = (Tfloat)(*this)(px,y,az,c), Icca = (Tfloat)(*this)(x, y,az,c), ++ Inca = (Tfloat)(*this)(nx,y,az,c), Iaca = (Tfloat)(*this)(ax,y,az,c), ++ Ica = Icca + 0.5f*(dx*(-Ipca + Inca) + dx*dx*(2*Ipca - 5*Icca + 4*Inca - Iaca) + ++ dx*dx*dx*(-Ipca + 3*Icca - 3*Inca + Iaca)), ++ Ipna = (Tfloat)(*this)(px,ny,az,c), Icna = (Tfloat)(*this)(x,ny,az,c), ++ Inna = (Tfloat)(*this)(nx,ny,az,c), Iana = (Tfloat)(*this)(ax,ny,az,c), ++ Ina = Icna + 0.5f*(dx*(-Ipna + Inna) + dx*dx*(2*Ipna - 5*Icna + 4*Inna - Iana) + ++ dx*dx*dx*(-Ipna + 3*Icna - 3*Inna + Iana)), ++ Ipaa = (Tfloat)(*this)(px,ay,az,c), Icaa = (Tfloat)(*this)(x,ay,az,c), ++ Inaa = (Tfloat)(*this)(nx,ay,az,c), Iaaa = (Tfloat)(*this)(ax,ay,az,c), ++ Iaa = Icaa + 0.5f*(dx*(-Ipaa + Inaa) + dx*dx*(2*Ipaa - 5*Icaa + 4*Inaa - Iaaa) + ++ dx*dx*dx*(-Ipaa + 3*Icaa - 3*Inaa + Iaaa)), ++ Ia = Ica + 0.5f*(dy*(-Ipa + Ina) + dy*dy*(2*Ipa - 5*Ica + 4*Ina - Iaa) + ++ dy*dy*dy*(-Ipa + 3*Ica - 3*Ina + Iaa)); ++ return Ic + 0.5f*(dz*(-Ip + In) + dz*dz*(2*Ip - 5*Ic + 4*In - Ia) + dz*dz*dz*(-Ip + 3*Ic - 3*In + Ia)); ++ } ++ ++ //! Return clamped pixel value, using cubic interpolation and Neumann boundary conditions for the XYZ-coordinates. ++ /** ++ Similar to cubic_atXYZ(float,float,float,int) const, except that the return value is clamped to stay in the ++ min/max range of the datatype \c T. ++ **/ ++ T cubic_cut_atXYZ(const float fx, const float fy, const float fz, const int c) const { ++ return cimg::type::cut(cubic_atXYZ(fx,fy,fz,c)); ++ } ++ ++ T _cubic_cut_atXYZ(const float fx, const float fy, const float fz, const int c) const { ++ return cimg::type::cut(_cubic_atXYZ(fx,fy,fz,c)); ++ } ++ ++ //! Set pixel value, using linear interpolation for the X-coordinates. ++ /** ++ Set pixel value at specified coordinates (\c fx,\c y,\c z,\c c) in the image instance, in a way that ++ the value is spread amongst several neighbors if the pixel coordinates are float-valued. ++ \param value Pixel value to set. ++ \param fx X-coordinate of the pixel value (float-valued). ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ \param c C-coordinate of the pixel value. ++ \param is_added Tells if the pixel value is added to (\c true), or simply replace (\c false) the current image ++ pixel(s). ++ \return A reference to the current image instance. ++ \note ++ - Calling this method with out-of-bounds coordinates does nothing. ++ **/ ++ CImg& set_linear_atX(const T& value, const float fx, const int y=0, const int z=0, const int c=0, ++ const bool is_added=false) { ++ const int ++ x = (int)fx - (fx>=0?0:1), nx = x + 1; ++ const float ++ dx = fx - x; ++ if (y>=0 && y=0 && z=0 && c=0 && x=0 && nx& set_linear_atXY(const T& value, const float fx, const float fy=0, const int z=0, const int c=0, ++ const bool is_added=false) { ++ 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; ++ if (z>=0 && z=0 && c=0 && y=0 && x=0 && nx=0 && ny=0 && x=0 && nx& set_linear_atXYZ(const T& value, const float fx, const float fy=0, const float fz=0, const int c=0, ++ const bool is_added=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 (c>=0 && c=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 image whose buffer data() is a \c char* string describing the list of all pixel values ++ of the image instance (written in base 10), separated by specified \c separator character. ++ \param separator A \c char character which specifies the separator between values in the returned C-string. ++ \param max_size Maximum size of the returned image (or \c 0 if no limits are set). ++ \param format For float/double-values, tell the printf format used to generate the ascii representation ++ of the numbers (or \c 0 for default representation). ++ \note ++ - The returned image is never empty. ++ - For an empty image instance, the returned string is "". ++ - If \c max_size is equal to \c 0, there are no limits on the size of the returned string. ++ - Otherwise, if the maximum number of string characters is exceeded, the value string is cut off ++ and terminated by character \c '\0'. In that case, the returned image size is max_size + 1. ++ **/ ++ CImg value_string(const char separator=',', const unsigned int max_size=0, ++ const char *const format=0) const { ++ if (is_empty() || max_size==1) return CImg(1,1,1,1,0); ++ CImgList items; ++ CImg s_item(256); *s_item = 0; ++ const T *ptrs = _data; ++ unsigned int string_size = 0; ++ const char *const _format = format?format:cimg::type::format(); ++ for (ulongT off = 0, siz = size(); off::format(*(ptrs++))); ++ CImg item(s_item._data,printed_size); ++ item[printed_size - 1] = separator; ++ item.move_to(items); ++ if (max_size) string_size+=printed_size; ++ } ++ CImg res; ++ (items>'x').move_to(res); ++ if (max_size && res._width>=max_size) res.crop(0,max_size - 1); ++ res.back() = 0; ++ return res; ++ } ++ ++ //@} ++ //------------------------------------- ++ // ++ //! \name Instance Checking ++ //@{ ++ //------------------------------------- ++ ++ //! Test shared state of the pixel buffer. ++ /** ++ Return \c true if image instance has a shared memory buffer, and \c false otherwise. ++ \note ++ - A shared image do not own his pixel buffer data() and will not deallocate it on destruction. ++ - Most of the time, a \c CImg image instance will \e not be shared. ++ - A shared image can only be obtained by a limited set of constructors and methods (see list below). ++ **/ ++ bool is_shared() const { ++ return _is_shared; ++ } ++ ++ //! Test if image instance is empty. ++ /** ++ Return \c true, if image instance is empty, i.e. does \e not contain any pixel values, has dimensions ++ \c 0 x \c 0 x \c 0 x \c 0 and a pixel buffer pointer set to \c 0 (null pointer), and \c false otherwise. ++ **/ ++ bool is_empty() const { ++ return !(_data && _width && _height && _depth && _spectrum); ++ } ++ ++ //! Test if image instance contains a 'inf' value. ++ /** ++ Return \c true, if image instance contains a 'inf' value, and \c false otherwise. ++ **/ ++ bool is_inf() const { ++ if (cimg::type::is_float()) cimg_for(*this,p,T) if (cimg::type::is_inf((float)*p)) return true; ++ return false; ++ } ++ ++ //! Test if image instance contains a NaN value. ++ /** ++ Return \c true, if image instance contains a NaN value, and \c false otherwise. ++ **/ ++ bool is_nan() const { ++ if (cimg::type::is_float()) cimg_for(*this,p,T) if (cimg::type::is_nan((float)*p)) return true; ++ return false; ++ } ++ ++ //! Test if image width is equal to specified value. ++ bool is_sameX(const unsigned int size_x) const { ++ return _width==size_x; ++ } ++ ++ //! Test if image width is equal to specified value. ++ template ++ bool is_sameX(const CImg& img) const { ++ return is_sameX(img._width); ++ } ++ ++ //! Test if image width is equal to specified value. ++ bool is_sameX(const CImgDisplay& disp) const { ++ return is_sameX(disp._width); ++ } ++ ++ //! Test if image height is equal to specified value. ++ bool is_sameY(const unsigned int size_y) const { ++ return _height==size_y; ++ } ++ ++ //! Test if image height is equal to specified value. ++ template ++ bool is_sameY(const CImg& img) const { ++ return is_sameY(img._height); ++ } ++ ++ //! Test if image height is equal to specified value. ++ bool is_sameY(const CImgDisplay& disp) const { ++ return is_sameY(disp._height); ++ } ++ ++ //! Test if image depth is equal to specified value. ++ bool is_sameZ(const unsigned int size_z) const { ++ return _depth==size_z; ++ } ++ ++ //! Test if image depth is equal to specified value. ++ template ++ bool is_sameZ(const CImg& img) const { ++ return is_sameZ(img._depth); ++ } ++ ++ //! Test if image spectrum is equal to specified value. ++ bool is_sameC(const unsigned int size_c) const { ++ return _spectrum==size_c; ++ } ++ ++ //! Test if image spectrum is equal to specified value. ++ template ++ bool is_sameC(const CImg& img) const { ++ return is_sameC(img._spectrum); ++ } ++ ++ //! Test if image width and height are equal to specified values. ++ /** ++ Test if is_sameX(unsigned int) const and is_sameY(unsigned int) const are both verified. ++ **/ ++ bool is_sameXY(const unsigned int size_x, const unsigned int size_y) const { ++ return _width==size_x && _height==size_y; ++ } ++ ++ //! Test if image width and height are the same as that of another image. ++ /** ++ Test if is_sameX(const CImg&) const and is_sameY(const CImg&) const are both verified. ++ **/ ++ template ++ bool is_sameXY(const CImg& img) const { ++ return is_sameXY(img._width,img._height); ++ } ++ ++ //! Test if image width and height are the same as that of an existing display window. ++ /** ++ Test if is_sameX(const CImgDisplay&) const and is_sameY(const CImgDisplay&) const are both verified. ++ **/ ++ bool is_sameXY(const CImgDisplay& disp) const { ++ return is_sameXY(disp._width,disp._height); ++ } ++ ++ //! Test if image width and depth are equal to specified values. ++ /** ++ Test if is_sameX(unsigned int) const and is_sameZ(unsigned int) const are both verified. ++ **/ ++ bool is_sameXZ(const unsigned int size_x, const unsigned int size_z) const { ++ return _width==size_x && _depth==size_z; ++ } ++ ++ //! Test if image width and depth are the same as that of another image. ++ /** ++ Test if is_sameX(const CImg&) const and is_sameZ(const CImg&) const are both verified. ++ **/ ++ template ++ bool is_sameXZ(const CImg& img) const { ++ return is_sameXZ(img._width,img._depth); ++ } ++ ++ //! Test if image width and spectrum are equal to specified values. ++ /** ++ Test if is_sameX(unsigned int) const and is_sameC(unsigned int) const are both verified. ++ **/ ++ bool is_sameXC(const unsigned int size_x, const unsigned int size_c) const { ++ return _width==size_x && _spectrum==size_c; ++ } ++ ++ //! Test if image width and spectrum are the same as that of another image. ++ /** ++ Test if is_sameX(const CImg&) const and is_sameC(const CImg&) const are both verified. ++ **/ ++ template ++ bool is_sameXC(const CImg& img) const { ++ return is_sameXC(img._width,img._spectrum); ++ } ++ ++ //! Test if image height and depth are equal to specified values. ++ /** ++ Test if is_sameY(unsigned int) const and is_sameZ(unsigned int) const are both verified. ++ **/ ++ bool is_sameYZ(const unsigned int size_y, const unsigned int size_z) const { ++ return _height==size_y && _depth==size_z; ++ } ++ ++ //! Test if image height and depth are the same as that of another image. ++ /** ++ Test if is_sameY(const CImg&) const and is_sameZ(const CImg&) const are both verified. ++ **/ ++ template ++ bool is_sameYZ(const CImg& img) const { ++ return is_sameYZ(img._height,img._depth); ++ } ++ ++ //! Test if image height and spectrum are equal to specified values. ++ /** ++ Test if is_sameY(unsigned int) const and is_sameC(unsigned int) const are both verified. ++ **/ ++ bool is_sameYC(const unsigned int size_y, const unsigned int size_c) const { ++ return _height==size_y && _spectrum==size_c; ++ } ++ ++ //! Test if image height and spectrum are the same as that of another image. ++ /** ++ Test if is_sameY(const CImg&) const and is_sameC(const CImg&) const are both verified. ++ **/ ++ template ++ bool is_sameYC(const CImg& img) const { ++ return is_sameYC(img._height,img._spectrum); ++ } ++ ++ //! Test if image depth and spectrum are equal to specified values. ++ /** ++ Test if is_sameZ(unsigned int) const and is_sameC(unsigned int) const are both verified. ++ **/ ++ bool is_sameZC(const unsigned int size_z, const unsigned int size_c) const { ++ return _depth==size_z && _spectrum==size_c; ++ } ++ ++ //! Test if image depth and spectrum are the same as that of another image. ++ /** ++ Test if is_sameZ(const CImg&) const and is_sameC(const CImg&) const are both verified. ++ **/ ++ template ++ bool is_sameZC(const CImg& img) const { ++ return is_sameZC(img._depth,img._spectrum); ++ } ++ ++ //! Test if image width, height and depth are equal to specified values. ++ /** ++ Test if is_sameXY(unsigned int,unsigned int) const and is_sameZ(unsigned int) const are both verified. ++ **/ ++ bool is_sameXYZ(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z) const { ++ return is_sameXY(size_x,size_y) && _depth==size_z; ++ } ++ ++ //! Test if image width, height and depth are the same as that of another image. ++ /** ++ Test if is_sameXY(const CImg&) const and is_sameZ(const CImg&) const are both verified. ++ **/ ++ template ++ bool is_sameXYZ(const CImg& img) const { ++ return is_sameXYZ(img._width,img._height,img._depth); ++ } ++ ++ //! Test if image width, height and spectrum are equal to specified values. ++ /** ++ Test if is_sameXY(unsigned int,unsigned int) const and is_sameC(unsigned int) const are both verified. ++ **/ ++ bool is_sameXYC(const unsigned int size_x, const unsigned int size_y, const unsigned int size_c) const { ++ return is_sameXY(size_x,size_y) && _spectrum==size_c; ++ } ++ ++ //! Test if image width, height and spectrum are the same as that of another image. ++ /** ++ Test if is_sameXY(const CImg&) const and is_sameC(const CImg&) const are both verified. ++ **/ ++ template ++ bool is_sameXYC(const CImg& img) const { ++ return is_sameXYC(img._width,img._height,img._spectrum); ++ } ++ ++ //! Test if image width, depth and spectrum are equal to specified values. ++ /** ++ Test if is_sameXZ(unsigned int,unsigned int) const and is_sameC(unsigned int) const are both verified. ++ **/ ++ bool is_sameXZC(const unsigned int size_x, const unsigned int size_z, const unsigned int size_c) const { ++ return is_sameXZ(size_x,size_z) && _spectrum==size_c; ++ } ++ ++ //! Test if image width, depth and spectrum are the same as that of another image. ++ /** ++ Test if is_sameXZ(const CImg&) const and is_sameC(const CImg&) const are both verified. ++ **/ ++ template ++ bool is_sameXZC(const CImg& img) const { ++ return is_sameXZC(img._width,img._depth,img._spectrum); ++ } ++ ++ //! Test if image height, depth and spectrum are equal to specified values. ++ /** ++ Test if is_sameYZ(unsigned int,unsigned int) const and is_sameC(unsigned int) const are both verified. ++ **/ ++ bool is_sameYZC(const unsigned int size_y, const unsigned int size_z, const unsigned int size_c) const { ++ return is_sameYZ(size_y,size_z) && _spectrum==size_c; ++ } ++ ++ //! Test if image height, depth and spectrum are the same as that of another image. ++ /** ++ Test if is_sameYZ(const CImg&) const and is_sameC(const CImg&) const are both verified. ++ **/ ++ template ++ bool is_sameYZC(const CImg& img) const { ++ return is_sameYZC(img._height,img._depth,img._spectrum); ++ } ++ ++ //! Test if image width, height, depth and spectrum are equal to specified values. ++ /** ++ Test if is_sameXYZ(unsigned int,unsigned int,unsigned int) const and is_sameC(unsigned int) const are both ++ verified. ++ **/ ++ bool is_sameXYZC(const unsigned int size_x, const unsigned int size_y, ++ const unsigned int size_z, const unsigned int size_c) const { ++ return is_sameXYZ(size_x,size_y,size_z) && _spectrum==size_c; ++ } ++ ++ //! Test if image width, height, depth and spectrum are the same as that of another image. ++ /** ++ Test if is_sameXYZ(const CImg&) const and is_sameC(const CImg&) const are both verified. ++ **/ ++ template ++ bool is_sameXYZC(const CImg& img) const { ++ return is_sameXYZC(img._width,img._height,img._depth,img._spectrum); ++ } ++ ++ //! Test if specified coordinates are inside image bounds. ++ /** ++ Return \c true if pixel located at (\c x,\c y,\c z,\c c) is inside bounds of the image instance, ++ and \c false otherwise. ++ \param x X-coordinate of the pixel value. ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ \param c C-coordinate of the pixel value. ++ \note ++ - Return \c true only if all these conditions are verified: ++ - The image instance is \e not empty. ++ - 0<=x<=\ref width() - 1. ++ - 0<=y<=\ref height() - 1. ++ - 0<=z<=\ref depth() - 1. ++ - 0<=c<=\ref spectrum() - 1. ++ **/ ++ bool containsXYZC(const int x, const int y=0, const int z=0, const int c=0) const { ++ return !is_empty() && x>=0 && x=0 && y=0 && z=0 && c img(100,100,1,3); // Construct a 100x100 RGB color image. ++ const unsigned long offset = 1249; // Offset to the pixel (49,12,0,0). ++ unsigned int x,y,z,c; ++ if (img.contains(img[offset],x,y,z,c)) { // Convert offset to (x,y,z,c) coordinates. ++ std::printf("Offset %u refers to pixel located at (%u,%u,%u,%u).\n", ++ offset,x,y,z,c); ++ } ++ \endcode ++ **/ ++ template ++ bool contains(const T& pixel, t& x, t& y, t& z, t& c) const { ++ const ulongT wh = (ulongT)_width*_height, whd = wh*_depth, siz = whd*_spectrum; ++ const T *const ppixel = &pixel; ++ if (is_empty() || ppixel<_data || ppixel>=_data + siz) return false; ++ ulongT off = (ulongT)(ppixel - _data); ++ const ulongT nc = off/whd; ++ off%=whd; ++ const ulongT nz = off/wh; ++ off%=wh; ++ const ulongT ny = off/_width, nx = off%_width; ++ x = (t)nx; y = (t)ny; z = (t)nz; c = (t)nc; ++ return true; ++ } ++ ++ //! Test if pixel value is inside image bounds and get its X,Y and Z-coordinates. ++ /** ++ Similar to contains(const T&,t&,t&,t&,t&) const, except that only the X,Y and Z-coordinates are set. ++ **/ ++ template ++ bool contains(const T& pixel, t& x, t& y, t& z) const { ++ const ulongT wh = (ulongT)_width*_height, whd = wh*_depth, siz = whd*_spectrum; ++ const T *const ppixel = &pixel; ++ if (is_empty() || ppixel<_data || ppixel>=_data + siz) return false; ++ ulongT off = ((ulongT)(ppixel - _data))%whd; ++ const ulongT nz = off/wh; ++ off%=wh; ++ const ulongT ny = off/_width, nx = off%_width; ++ x = (t)nx; y = (t)ny; z = (t)nz; ++ return true; ++ } ++ ++ //! Test if pixel value is inside image bounds and get its X and Y-coordinates. ++ /** ++ Similar to contains(const T&,t&,t&,t&,t&) const, except that only the X and Y-coordinates are set. ++ **/ ++ template ++ bool contains(const T& pixel, t& x, t& y) const { ++ const ulongT wh = (ulongT)_width*_height, siz = wh*_depth*_spectrum; ++ const T *const ppixel = &pixel; ++ if (is_empty() || ppixel<_data || ppixel>=_data + siz) return false; ++ ulongT off = ((unsigned int)(ppixel - _data))%wh; ++ const ulongT ny = off/_width, nx = off%_width; ++ x = (t)nx; y = (t)ny; ++ return true; ++ } ++ ++ //! Test if pixel value is inside image bounds and get its X-coordinate. ++ /** ++ Similar to contains(const T&,t&,t&,t&,t&) const, except that only the X-coordinate is set. ++ **/ ++ template ++ bool contains(const T& pixel, t& x) const { ++ const T *const ppixel = &pixel; ++ if (is_empty() || ppixel<_data || ppixel>=_data + size()) return false; ++ x = (t)(((ulongT)(ppixel - _data))%_width); ++ return true; ++ } ++ ++ //! Test if pixel value is inside image bounds. ++ /** ++ Similar to contains(const T&,t&,t&,t&,t&) const, except that no pixel coordinates are set. ++ **/ ++ bool contains(const T& pixel) const { ++ const T *const ppixel = &pixel; ++ return !is_empty() && ppixel>=_data && ppixel<_data + size(); ++ } ++ ++ //! Test if pixel buffers of instance and input images overlap. ++ /** ++ Return \c true, if pixel buffers attached to image instance and input image \c img overlap, ++ and \c false otherwise. ++ \param img Input image to compare with. ++ \note ++ - Buffer overlapping may happen when manipulating \e shared images. ++ - If two image buffers overlap, operating on one of the image will probably modify the other one. ++ - Most of the time, \c CImg instances are \e non-shared and do not overlap between each others. ++ \par Example ++ \code ++ const CImg ++ img1("reference.jpg"), // Load RGB-color image. ++ img2 = img1.get_shared_channel(1); // Get shared version of the green channel. ++ if (img1.is_overlapped(img2)) { // Test succeeds, 'img1' and 'img2' overlaps. ++ std::printf("Buffers overlap!\n"); ++ } ++ \endcode ++ **/ ++ template ++ bool is_overlapped(const CImg& img) const { ++ const ulongT csiz = size(), isiz = img.size(); ++ return !((void*)(_data + csiz)<=(void*)img._data || (void*)_data>=(void*)(img._data + isiz)); ++ } ++ ++ //! Test if the set {\c *this,\c primitives,\c colors,\c opacities} defines a valid 3d object. ++ /** ++ Return \c true is the 3d object represented by the set {\c *this,\c primitives,\c colors,\c opacities} defines a ++ valid 3d object, and \c false otherwise. The vertex coordinates are defined by the instance image. ++ \param primitives List of primitives of the 3d object. ++ \param colors List of colors of the 3d object. ++ \param opacities List (or image) of opacities of the 3d object. ++ \param full_check Tells if full checking of the 3d object must be performed. ++ \param[out] error_message C-string to contain the error message, if the test does not succeed. ++ \note ++ - Set \c full_checking to \c false to speed-up the 3d object checking. In this case, only the size of ++ each 3d object component is checked. ++ - Size of the string \c error_message should be at least 128-bytes long, to be able to contain the error message. ++ **/ ++ template ++ bool is_object3d(const CImgList& primitives, ++ const CImgList& colors, ++ const to& opacities, ++ const bool full_check=true, ++ char *const error_message=0) const { ++ if (error_message) *error_message = 0; ++ ++ // Check consistency for the particular case of an empty 3d object. ++ if (is_empty()) { ++ if (primitives || colors || opacities) { ++ if (error_message) cimg_sprintf(error_message, ++ "3d object (%u,%u) defines no vertices but %u primitives, " ++ "%u colors and %lu opacities", ++ _width,primitives._width,primitives._width, ++ colors._width,(unsigned long)opacities.size()); ++ return false; ++ } ++ return true; ++ } ++ ++ // Check consistency of vertices. ++ if (_height!=3 || _depth>1 || _spectrum>1) { // Check vertices dimensions. ++ if (error_message) cimg_sprintf(error_message, ++ "3d object (%u,%u) has invalid vertex dimensions (%u,%u,%u,%u)", ++ _width,primitives._width,_width,_height,_depth,_spectrum); ++ return false; ++ } ++ if (colors._width>primitives._width + 1) { ++ if (error_message) cimg_sprintf(error_message, ++ "3d object (%u,%u) defines %u colors", ++ _width,primitives._width,colors._width); ++ return false; ++ } ++ if (opacities.size()>primitives._width) { ++ if (error_message) cimg_sprintf(error_message, ++ "3d object (%u,%u) defines %lu opacities", ++ _width,primitives._width,(unsigned long)opacities.size()); ++ return false; ++ } ++ if (!full_check) return true; ++ ++ // Check consistency of primitives. ++ cimglist_for(primitives,l) { ++ const CImg& primitive = primitives[l]; ++ const unsigned int psiz = (unsigned int)primitive.size(); ++ switch (psiz) { ++ case 1 : { // Point. ++ const unsigned int i0 = (unsigned int)primitive(0); ++ if (i0>=_width) { ++ if (error_message) cimg_sprintf(error_message, ++ "3d object (%u,%u) refers to invalid vertex indice %u in " ++ "point primitive [%u]", ++ _width,primitives._width,i0,l); ++ return false; ++ } ++ } break; ++ case 5 : { // Sphere. ++ const unsigned int ++ i0 = (unsigned int)primitive(0), ++ i1 = (unsigned int)primitive(1); ++ if (i0>=_width || i1>=_width) { ++ if (error_message) cimg_sprintf(error_message, ++ "3d object (%u,%u) refers to invalid vertex indices (%u,%u) in " ++ "sphere primitive [%u]", ++ _width,primitives._width,i0,i1,l); ++ return false; ++ } ++ } break; ++ case 2 : // Segment. ++ case 6 : { ++ const unsigned int ++ i0 = (unsigned int)primitive(0), ++ i1 = (unsigned int)primitive(1); ++ if (i0>=_width || i1>=_width) { ++ if (error_message) cimg_sprintf(error_message, ++ "3d object (%u,%u) refers to invalid vertex indices (%u,%u) in " ++ "segment primitive [%u]", ++ _width,primitives._width,i0,i1,l); ++ return false; ++ } ++ } break; ++ case 3 : // Triangle. ++ case 9 : { ++ const unsigned int ++ i0 = (unsigned int)primitive(0), ++ i1 = (unsigned int)primitive(1), ++ i2 = (unsigned int)primitive(2); ++ if (i0>=_width || i1>=_width || i2>=_width) { ++ if (error_message) cimg_sprintf(error_message, ++ "3d object (%u,%u) refers to invalid vertex indices (%u,%u,%u) in " ++ "triangle primitive [%u]", ++ _width,primitives._width,i0,i1,i2,l); ++ return false; ++ } ++ } break; ++ case 4 : // Quadrangle. ++ case 12 : { ++ const unsigned int ++ i0 = (unsigned int)primitive(0), ++ i1 = (unsigned int)primitive(1), ++ i2 = (unsigned int)primitive(2), ++ i3 = (unsigned int)primitive(3); ++ if (i0>=_width || i1>=_width || i2>=_width || i3>=_width) { ++ if (error_message) cimg_sprintf(error_message, ++ "3d object (%u,%u) refers to invalid vertex indices (%u,%u,%u,%u) in " ++ "quadrangle primitive [%u]", ++ _width,primitives._width,i0,i1,i2,i3,l); ++ return false; ++ } ++ } break; ++ default : ++ if (error_message) cimg_sprintf(error_message, ++ "3d object (%u,%u) defines an invalid primitive [%u] of size %u", ++ _width,primitives._width,l,(unsigned int)psiz); ++ return false; ++ } ++ } ++ ++ // Check consistency of colors. ++ cimglist_for(colors,c) { ++ const CImg& color = colors[c]; ++ if (!color) { ++ if (error_message) cimg_sprintf(error_message, ++ "3d object (%u,%u) defines no color for primitive [%u]", ++ _width,primitives._width,c); ++ return false; ++ } ++ } ++ ++ // Check consistency of light texture. ++ if (colors._width>primitives._width) { ++ const CImg &light = colors.back(); ++ if (!light || light._depth>1) { ++ if (error_message) cimg_sprintf(error_message, ++ "3d object (%u,%u) defines an invalid light texture (%u,%u,%u,%u)", ++ _width,primitives._width,light._width, ++ light._height,light._depth,light._spectrum); ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ ++ //! Test if image instance represents a valid serialization of a 3d object. ++ /** ++ Return \c true if the image instance represents a valid serialization of a 3d object, and \c false otherwise. ++ \param full_check Tells if full checking of the instance must be performed. ++ \param[out] error_message C-string to contain the error message, if the test does not succeed. ++ \note ++ - Set \c full_check to \c false to speed-up the 3d object checking. In this case, only the size of ++ each 3d object component is checked. ++ - Size of the string \c error_message should be at least 128-bytes long, to be able to contain the error message. ++ **/ ++ bool is_CImg3d(const bool full_check=true, char *const error_message=0) const { ++ if (error_message) *error_message = 0; ++ ++ // Check instance dimension and header. ++ if (_width!=1 || _height<8 || _depth!=1 || _spectrum!=1) { ++ if (error_message) cimg_sprintf(error_message, ++ "CImg3d has invalid dimensions (%u,%u,%u,%u)", ++ _width,_height,_depth,_spectrum); ++ return false; ++ } ++ const T *ptrs = _data, *const ptre = end(); ++ if (!_is_CImg3d(*(ptrs++),'C') || !_is_CImg3d(*(ptrs++),'I') || !_is_CImg3d(*(ptrs++),'m') || ++ !_is_CImg3d(*(ptrs++),'g') || !_is_CImg3d(*(ptrs++),'3') || !_is_CImg3d(*(ptrs++),'d')) { ++ if (error_message) cimg_sprintf(error_message, ++ "CImg3d header not found"); ++ return false; ++ } ++ const unsigned int ++ nb_points = cimg::float2uint((float)*(ptrs++)), ++ nb_primitives = cimg::float2uint((float)*(ptrs++)); ++ ++ // Check consistency of number of vertices / primitives. ++ if (!full_check) { ++ const ulongT minimal_size = 8UL + 3*nb_points + 6*nb_primitives; ++ if (_data + minimal_size>ptre) { ++ if (error_message) cimg_sprintf(error_message, ++ "CImg3d (%u,%u) has only %lu values, while at least %lu values were expected", ++ nb_points,nb_primitives,(unsigned long)size(),(unsigned long)minimal_size); ++ return false; ++ } ++ } ++ ++ // Check consistency of vertex data. ++ if (!nb_points) { ++ if (nb_primitives) { ++ if (error_message) cimg_sprintf(error_message, ++ "CImg3d (%u,%u) defines no vertices but %u primitives", ++ nb_points,nb_primitives,nb_primitives); ++ return false; ++ } ++ if (ptrs!=ptre) { ++ if (error_message) cimg_sprintf(error_message, ++ "CImg3d (%u,%u) is an empty object but contains %u value%s " ++ "more than expected", ++ nb_points,nb_primitives,(unsigned int)(ptre - ptrs),(ptre - ptrs)>1?"s":""); ++ return false; ++ } ++ return true; ++ } ++ if (ptrs + 3*nb_points>ptre) { ++ if (error_message) cimg_sprintf(error_message, ++ "CImg3d (%u,%u) defines only %u vertices data", ++ nb_points,nb_primitives,(unsigned int)(ptre - ptrs)/3); ++ return false; ++ } ++ ptrs+=3*nb_points; ++ ++ // Check consistency of primitive data. ++ if (ptrs==ptre) { ++ if (error_message) cimg_sprintf(error_message, ++ "CImg3d (%u,%u) defines %u vertices but no primitive", ++ nb_points,nb_primitives,nb_points); ++ return false; ++ } ++ ++ if (!full_check) return true; ++ ++ for (unsigned int p = 0; p=nb_points) { ++ if (error_message) cimg_sprintf(error_message, ++ "CImg3d (%u,%u) refers to invalid vertex indice %u in point primitive [%u]", ++ nb_points,nb_primitives,i0,p); ++ return false; ++ } ++ } break; ++ case 5 : { // Sphere. ++ const unsigned int ++ i0 = cimg::float2uint((float)*(ptrs++)), ++ i1 = cimg::float2uint((float)*(ptrs++)); ++ ptrs+=3; ++ if (i0>=nb_points || i1>=nb_points) { ++ if (error_message) cimg_sprintf(error_message, ++ "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u) in " ++ "sphere primitive [%u]", ++ nb_points,nb_primitives,i0,i1,p); ++ return false; ++ } ++ } break; ++ case 2 : case 6 : { // Segment. ++ const unsigned int ++ i0 = cimg::float2uint((float)*(ptrs++)), ++ i1 = cimg::float2uint((float)*(ptrs++)); ++ if (nb_inds==6) ptrs+=4; ++ if (i0>=nb_points || i1>=nb_points) { ++ if (error_message) cimg_sprintf(error_message, ++ "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u) in " ++ "segment primitive [%u]", ++ nb_points,nb_primitives,i0,i1,p); ++ return false; ++ } ++ } break; ++ case 3 : case 9 : { // Triangle. ++ const unsigned int ++ i0 = cimg::float2uint((float)*(ptrs++)), ++ i1 = cimg::float2uint((float)*(ptrs++)), ++ i2 = cimg::float2uint((float)*(ptrs++)); ++ if (nb_inds==9) ptrs+=6; ++ if (i0>=nb_points || i1>=nb_points || i2>=nb_points) { ++ if (error_message) cimg_sprintf(error_message, ++ "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u,%u) in " ++ "triangle primitive [%u]", ++ nb_points,nb_primitives,i0,i1,i2,p); ++ return false; ++ } ++ } break; ++ case 4 : case 12 : { // Quadrangle. ++ const unsigned int ++ i0 = cimg::float2uint((float)*(ptrs++)), ++ i1 = cimg::float2uint((float)*(ptrs++)), ++ i2 = cimg::float2uint((float)*(ptrs++)), ++ i3 = cimg::float2uint((float)*(ptrs++)); ++ if (nb_inds==12) ptrs+=8; ++ if (i0>=nb_points || i1>=nb_points || i2>=nb_points || i3>=nb_points) { ++ if (error_message) cimg_sprintf(error_message, ++ "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u,%u,%u) in " ++ "quadrangle primitive [%u]", ++ nb_points,nb_primitives,i0,i1,i2,i3,p); ++ return false; ++ } ++ } break; ++ default : ++ if (error_message) cimg_sprintf(error_message, ++ "CImg3d (%u,%u) defines an invalid primitive [%u] of size %u", ++ nb_points,nb_primitives,p,nb_inds); ++ return false; ++ } ++ if (ptrs>ptre) { ++ if (error_message) cimg_sprintf(error_message, ++ "CImg3d (%u,%u) has incomplete primitive data for primitive [%u], " ++ "%u values missing", ++ nb_points,nb_primitives,p,(unsigned int)(ptrs - ptre)); ++ return false; ++ } ++ } ++ ++ // Check consistency of color data. ++ if (ptrs==ptre) { ++ if (error_message) cimg_sprintf(error_message, ++ "CImg3d (%u,%u) defines no color/texture data", ++ nb_points,nb_primitives); ++ return false; ++ } ++ for (unsigned int c = 0; c=c) { ++ if (error_message) cimg_sprintf(error_message, ++ "CImg3d (%u,%u) refers to invalid shared sprite/texture indice %u " ++ "for primitive [%u]", ++ nb_points,nb_primitives,w,c); ++ return false; ++ } ++ } else ptrs+=w*h*s; ++ } ++ if (ptrs>ptre) { ++ if (error_message) cimg_sprintf(error_message, ++ "CImg3d (%u,%u) has incomplete color/texture data for primitive [%u], " ++ "%u values missing", ++ nb_points,nb_primitives,c,(unsigned int)(ptrs - ptre)); ++ return false; ++ } ++ } ++ ++ // Check consistency of opacity data. ++ if (ptrs==ptre) { ++ if (error_message) cimg_sprintf(error_message, ++ "CImg3d (%u,%u) defines no opacity data", ++ nb_points,nb_primitives); ++ return false; ++ } ++ for (unsigned int o = 0; o=o) { ++ if (error_message) cimg_sprintf(error_message, ++ "CImg3d (%u,%u) refers to invalid shared opacity indice %u " ++ "for primitive [%u]", ++ nb_points,nb_primitives,w,o); ++ return false; ++ } ++ } else ptrs+=w*h*s; ++ } ++ if (ptrs>ptre) { ++ if (error_message) cimg_sprintf(error_message, ++ "CImg3d (%u,%u) has incomplete opacity data for primitive [%u]", ++ nb_points,nb_primitives,o); ++ return false; ++ } ++ } ++ ++ // Check end of data. ++ if (ptrs1?"s":""); ++ return false; ++ } ++ return true; ++ } ++ ++ static bool _is_CImg3d(const T val, const char c) { ++ return val>=(T)c && val<(T)(c + 1); ++ } ++ ++ //@} ++ //------------------------------------- ++ // ++ //! \name Mathematical Functions ++ //@{ ++ //------------------------------------- ++ ++ // Define the math formula parser/compiler and expression evaluator. ++ struct _cimg_math_parser { ++ CImg mem; ++ CImg memtype; ++ CImgList _code, &code, code_init, code_end; ++ CImg opcode; ++ const CImg *p_code_end, *p_code; ++ const CImg *const p_break; ++ ++ CImg expr, pexpr; ++ const CImg& imgin; ++ const CImgList& listin; ++ CImg &imgout; ++ CImgList& listout; ++ ++ CImg _img_stats, &img_stats, constcache_vals; ++ CImgList _list_stats, &list_stats, _list_median, &list_median; ++ CImg mem_img_stats, constcache_inds; ++ ++ CImg level, variable_pos, reserved_label; ++ CImgList variable_def, macro_def, macro_body; ++ CImgList macro_body_is_string; ++ char *user_macro; ++ ++ unsigned int mempos, mem_img_median, debug_indent, result_dim, break_type, constcache_size; ++ bool is_parallelizable, is_fill, need_input_copy; ++ double *result; ++ const char *const calling_function, *s_op, *ss_op; ++ typedef double (*mp_func)(_cimg_math_parser&); ++ ++#define _cimg_mp_is_constant(arg) (memtype[arg]==1) // Is constant value? ++#define _cimg_mp_is_scalar(arg) (memtype[arg]<2) // Is scalar value? ++#define _cimg_mp_is_comp(arg) (!memtype[arg]) // Is computation value? ++#define _cimg_mp_is_variable(arg) (memtype[arg]==-1) // Is scalar variable? ++#define _cimg_mp_is_vector(arg) (memtype[arg]>1) // Is vector? ++#define _cimg_mp_size(arg) (_cimg_mp_is_scalar(arg)?0U:(unsigned int)memtype[arg] - 1) // Size (0=scalar, N>0=vectorN) ++#define _cimg_mp_calling_function calling_function_s()._data ++#define _cimg_mp_op(s) s_op = s; ss_op = ss ++#define _cimg_mp_check_type(arg,n_arg,mode,N) check_type(arg,n_arg,mode,N,ss,se,saved_char) ++#define _cimg_mp_check_constant(arg,n_arg,mode) check_constant(arg,n_arg,mode,ss,se,saved_char) ++#define _cimg_mp_check_matrix_square(arg,n_arg) check_matrix_square(arg,n_arg,ss,se,saved_char) ++#define _cimg_mp_check_vector0(dim) check_vector0(dim,ss,se,saved_char) ++#define _cimg_mp_check_list(is_out) check_list(is_out,ss,se,saved_char) ++#define _cimg_mp_defunc(mp) (*(mp_func)(*(mp).opcode))(mp) ++#define _cimg_mp_return(x) { *se = saved_char; s_op = previous_s_op; ss_op = previous_ss_op; return x; } ++#define _cimg_mp_return_nan() _cimg_mp_return(_cimg_mp_slot_nan) ++#define _cimg_mp_constant(val) _cimg_mp_return(constant((double)(val))) ++#define _cimg_mp_scalar0(op) _cimg_mp_return(scalar0(op)) ++#define _cimg_mp_scalar1(op,i1) _cimg_mp_return(scalar1(op,i1)) ++#define _cimg_mp_scalar2(op,i1,i2) _cimg_mp_return(scalar2(op,i1,i2)) ++#define _cimg_mp_scalar3(op,i1,i2,i3) _cimg_mp_return(scalar3(op,i1,i2,i3)) ++#define _cimg_mp_scalar4(op,i1,i2,i3,i4) _cimg_mp_return(scalar4(op,i1,i2,i3,i4)) ++#define _cimg_mp_scalar5(op,i1,i2,i3,i4,i5) _cimg_mp_return(scalar5(op,i1,i2,i3,i4,i5)) ++#define _cimg_mp_scalar6(op,i1,i2,i3,i4,i5,i6) _cimg_mp_return(scalar6(op,i1,i2,i3,i4,i5,i6)) ++#define _cimg_mp_scalar7(op,i1,i2,i3,i4,i5,i6,i7) _cimg_mp_return(scalar7(op,i1,i2,i3,i4,i5,i6,i7)) ++#define _cimg_mp_vector1_v(op,i1) _cimg_mp_return(vector1_v(op,i1)) ++#define _cimg_mp_vector2_sv(op,i1,i2) _cimg_mp_return(vector2_sv(op,i1,i2)) ++#define _cimg_mp_vector2_vs(op,i1,i2) _cimg_mp_return(vector2_vs(op,i1,i2)) ++#define _cimg_mp_vector2_vv(op,i1,i2) _cimg_mp_return(vector2_vv(op,i1,i2)) ++#define _cimg_mp_vector3_vss(op,i1,i2,i3) _cimg_mp_return(vector3_vss(op,i1,i2,i3)) ++ ++ // Constructors. ++ _cimg_math_parser(const char *const expression, const char *const funcname=0, ++ const CImg& img_input=CImg::const_empty(), CImg *const img_output=0, ++ const CImgList *const list_inputs=0, CImgList *const list_outputs=0, ++ const bool _is_fill=false): ++ code(_code),p_break((CImg*)0 - 2), ++ imgin(img_input),listin(list_inputs?*list_inputs:CImgList::const_empty()), ++ imgout(img_output?*img_output:CImg::empty()),listout(list_outputs?*list_outputs:CImgList::empty()), ++ img_stats(_img_stats),list_stats(_list_stats),list_median(_list_median),user_macro(0), ++ mem_img_median(~0U),debug_indent(0),result_dim(0),break_type(0),constcache_size(0), ++ is_parallelizable(true),is_fill(_is_fill),need_input_copy(false), ++ calling_function(funcname?funcname:"cimg_math_parser") { ++ if (!expression || !*expression) ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: Empty expression.", ++ pixel_type(),_cimg_mp_calling_function); ++ const char *_expression = expression; ++ while (*_expression && ((signed char)*_expression<=' ' || *_expression==';')) ++_expression; ++ CImg::string(_expression).move_to(expr); ++ char *ps = &expr.back() - 1; ++ while (ps>expr._data && ((signed char)*ps<=' ' || *ps==';')) --ps; ++ *(++ps) = 0; expr._width = (unsigned int)(ps - expr._data + 1); ++ ++ // Ease the retrieval of previous non-space characters afterwards. ++ pexpr.assign(expr._width); ++ char c, *pe = pexpr._data; ++ for (ps = expr._data, c = ' '; *ps; ++ps) { ++ if ((signed char)*ps>' ') c = *ps; else *ps = ' '; ++ *(pe++) = c; ++ } ++ *pe = 0; ++ level = get_level(expr); ++ ++ // Init constant values. ++#define _cimg_mp_interpolation (reserved_label[29]!=~0U?reserved_label[29]:0) ++#define _cimg_mp_boundary (reserved_label[30]!=~0U?reserved_label[30]:0) ++#define _cimg_mp_slot_nan 29 ++#define _cimg_mp_slot_x 30 ++#define _cimg_mp_slot_y 31 ++#define _cimg_mp_slot_z 32 ++#define _cimg_mp_slot_c 33 ++ ++ mem.assign(96); ++ for (unsigned int i = 0; i<=10; ++i) mem[i] = (double)i; // mem[0-10] = 0...10 ++ for (unsigned int i = 1; i<=5; ++i) mem[i + 10] = -(double)i; // mem[11-15] = -1...-5 ++ mem[16] = 0.5; ++ mem[17] = 0; // thread_id ++ mem[18] = (double)imgin._width; // w ++ mem[19] = (double)imgin._height; // h ++ mem[20] = (double)imgin._depth; // d ++ mem[21] = (double)imgin._spectrum; // s ++ mem[22] = (double)imgin._is_shared; // r ++ mem[23] = (double)imgin._width*imgin._height; // wh ++ mem[24] = (double)imgin._width*imgin._height*imgin._depth; // whd ++ mem[25] = (double)imgin._width*imgin._height*imgin._depth*imgin._spectrum; // whds ++ mem[26] = (double)listin._width; // l ++ mem[27] = std::exp(1.0); // e ++ mem[28] = cimg::PI; // pi ++ mem[_cimg_mp_slot_nan] = cimg::type::nan(); // nan ++ ++ // Set value property : ++ // { -2 = other | -1 = variable | 0 = computation value | ++ // 1 = compile-time constant | N>1 = constant ptr to vector[N-1] }. ++ memtype.assign(mem._width,1,1,1,0); ++ for (unsigned int i = 0; i<_cimg_mp_slot_x; ++i) memtype[i] = 1; ++ memtype[17] = 0; ++ memtype[_cimg_mp_slot_x] = memtype[_cimg_mp_slot_y] = memtype[_cimg_mp_slot_z] = memtype[_cimg_mp_slot_c] = -2; ++ mempos = _cimg_mp_slot_c + 1; ++ variable_pos.assign(8); ++ ++ reserved_label.assign(128,1,1,1,~0U); ++ // reserved_label[4-28] are used to store these two-char variables: ++ // [0] = wh, [1] = whd, [2] = whds, [3] = pi, [4] = im, [5] = iM, [6] = ia, [7] = iv, ++ // [8] = is, [9] = ip, [10] = ic, [11] = xm, [12] = ym, [13] = zm, [14] = cm, [15] = xM, ++ // [16] = yM, [17] = zM, [18]=cM, [19]=i0...[28]=i9, [29] = interpolation, [30] = boundary ++ ++ // Compile expression into a serie of opcodes. ++ s_op = ""; ss_op = expr._data; ++ const unsigned int ind_result = compile(expr._data,expr._data + expr._width - 1,0,0,false); ++ if (!_cimg_mp_is_constant(ind_result)) { ++ if (_cimg_mp_is_vector(ind_result)) ++ CImg(&mem[ind_result] + 1,_cimg_mp_size(ind_result),1,1,1,true). ++ fill(cimg::type::nan()); ++ else mem[ind_result] = cimg::type::nan(); ++ } ++ ++ // Free resources used for compiling expression and prepare evaluation. ++ result_dim = _cimg_mp_size(ind_result); ++ if (mem._width>=256 && mem._width - mempos>=mem._width/2) mem.resize(mempos,1,1,1,-1); ++ result = mem._data + ind_result; ++ memtype.assign(); ++ constcache_vals.assign(); ++ constcache_inds.assign(); ++ level.assign(); ++ variable_pos.assign(); ++ reserved_label.assign(); ++ expr.assign(); ++ pexpr.assign(); ++ opcode.assign(); ++ opcode._is_shared = true; ++ ++ // Execute init() bloc if any specified. ++ if (code_init) { ++ mem[_cimg_mp_slot_x] = mem[_cimg_mp_slot_y] = mem[_cimg_mp_slot_z] = mem[_cimg_mp_slot_c] = 0; ++ p_code_end = code_init.end(); ++ for (p_code = code_init; p_code_data; ++ const ulongT target = opcode[1]; ++ mem[target] = _cimg_mp_defunc(*this); ++ } ++ } ++ p_code_end = code.end(); ++ } ++ ++ _cimg_math_parser(): ++ code(_code),p_code_end(0),p_break((CImg*)0 - 2), ++ imgin(CImg::const_empty()),listin(CImgList::const_empty()), ++ imgout(CImg::empty()),listout(CImgList::empty()), ++ img_stats(_img_stats),list_stats(_list_stats),list_median(_list_median),debug_indent(0), ++ result_dim(0),break_type(0),constcache_size(0),is_parallelizable(true),is_fill(false),need_input_copy(false), ++ calling_function(0) { ++ mem.assign(1 + _cimg_mp_slot_c,1,1,1,0); // Allow to skip 'is_empty?' test in operator()() ++ result = mem._data; ++ } ++ ++ _cimg_math_parser(const _cimg_math_parser& mp): ++ mem(mp.mem),code(mp.code),p_code_end(mp.p_code_end),p_break(mp.p_break), ++ imgin(mp.imgin),listin(mp.listin),imgout(mp.imgout),listout(mp.listout),img_stats(mp.img_stats), ++ list_stats(mp.list_stats),list_median(mp.list_median),debug_indent(0),result_dim(mp.result_dim), ++ break_type(0),constcache_size(0),is_parallelizable(mp.is_parallelizable),is_fill(mp.is_fill), ++ need_input_copy(mp.need_input_copy), result(mem._data + (mp.result - mp.mem._data)),calling_function(0) { ++#ifdef cimg_use_openmp ++ mem[17] = omp_get_thread_num(); ++#endif ++ opcode.assign(); ++ opcode._is_shared = true; ++ } ++ ++ // Count parentheses/brackets level of each character of the expression. ++ CImg get_level(CImg& expr) const { ++ bool is_escaped = false, next_is_escaped = false; ++ unsigned int mode = 0, next_mode = 0; // { 0=normal | 1=char-string | 2=vector-string ++ CImg res(expr._width - 1); ++ unsigned int *pd = res._data; ++ int level = 0; ++ for (const char *ps = expr._data; *ps && level>=0; ++ps) { ++ if (!is_escaped && !next_is_escaped && *ps=='\\') next_is_escaped = true; ++ if (!is_escaped && *ps=='\'') { // Non-escaped character ++ if (!mode && ps>expr._data && *(ps - 1)=='[') next_mode = mode = 2; // Start vector-string ++ else if (mode==2 && *(ps + 1)==']') next_mode = !mode; // End vector-string ++ else if (mode<2) next_mode = mode?(mode = 0):1; // Start/end char-string ++ } ++ *(pd++) = (unsigned int)(mode>=1 || is_escaped?level + (mode==1): ++ *ps=='(' || *ps=='['?level++: ++ *ps==')' || *ps==']'?--level: ++ level); ++ mode = next_mode; ++ is_escaped = next_is_escaped; ++ next_is_escaped = false; ++ } ++ if (mode) { ++ cimg::strellipsize(expr,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: Unterminated string literal, in expression '%s'.", ++ pixel_type(),_cimg_mp_calling_function, ++ expr._data); ++ } ++ if (level) { ++ cimg::strellipsize(expr,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: Unbalanced parentheses/brackets, in expression '%s'.", ++ pixel_type(),_cimg_mp_calling_function, ++ expr._data); ++ } ++ return res; ++ } ++ ++ // Tell for each character of an expression if it is inside a string or not. ++ CImg is_inside_string(CImg& expr) const { ++ bool is_escaped = false, next_is_escaped = false; ++ unsigned int mode = 0, next_mode = 0; // { 0=normal | 1=char-string | 2=vector-string ++ CImg res = CImg::string(expr); ++ bool *pd = res._data; ++ for (const char *ps = expr._data; *ps; ++ps) { ++ if (!next_is_escaped && *ps=='\\') next_is_escaped = true; ++ if (!is_escaped && *ps=='\'') { // Non-escaped character ++ if (!mode && ps>expr._data && *(ps - 1)=='[') next_mode = mode = 2; // Start vector-string ++ else if (mode==2 && *(ps + 1)==']') next_mode = !mode; // End vector-string ++ else if (mode<2) next_mode = mode?(mode = 0):1; // Start/end char-string ++ } ++ *(pd++) = mode>=1 || is_escaped; ++ mode = next_mode; ++ is_escaped = next_is_escaped; ++ next_is_escaped = false; ++ } ++ return res; ++ } ++ ++ // Compilation procedure. ++ unsigned int compile(char *ss, char *se, const unsigned int depth, unsigned int *const p_ref, ++ const bool is_single) { ++ if (depth>256) { ++ cimg::strellipsize(expr,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: Call stack overflow (infinite recursion?), " ++ "in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function, ++ (ss - 4)>expr._data?"...":"", ++ (ss - 4)>expr._data?ss - 4:expr._data, ++ se<&expr.back()?"...":""); ++ } ++ char c1, c2, c3, c4; ++ ++ // Simplify expression when possible. ++ do { ++ c2 = 0; ++ if (ssss && ((signed char)(c1 = *(se - 1))<=' ' || c1==';')) --se; ++ } ++ while (*ss=='(' && *(se - 1)==')' && std::strchr(ss,')')==se - 1) { ++ ++ss; --se; c2 = 1; ++ } ++ } while (c2 && ss::%s: %s%s Missing %s, in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,s_op,*s_op?":":"", ++ *s_op=='F'?"argument":"item", ++ (ss_op - 4)>expr._data?"...":"", ++ (ss_op - 4)>expr._data?ss_op - 4:expr._data, ++ ss_op + std::strlen(ss_op)<&expr.back()?"...":""); ++ } ++ ++ const char *const previous_s_op = s_op, *const previous_ss_op = ss_op; ++ const unsigned int depth1 = depth + 1; ++ unsigned int pos, p1, p2, p3, arg1, arg2, arg3, arg4, arg5, arg6; ++ char ++ *const se1 = se - 1, *const se2 = se - 2, *const se3 = se - 3, ++ *const ss1 = ss + 1, *const ss2 = ss + 2, *const ss3 = ss + 3, *const ss4 = ss + 4, ++ *const ss5 = ss + 5, *const ss6 = ss + 6, *const ss7 = ss + 7, *const ss8 = ss + 8, ++ *s, *ps, *ns, *s0, *s1, *s2, *s3, sep = 0, end = 0; ++ double val, val1, val2; ++ mp_func op; ++ ++ // 'p_ref' is a 'unsigned int[7]' used to return a reference to an image or vector value ++ // linked to the returned memory slot (reference that cannot be determined at compile time). ++ // p_ref[0] can be { 0 = scalar (unlinked) | 1 = vector value | 2 = image value (offset) | ++ // 3 = image value (coordinates) | 4 = image value as a vector (offsets) | ++ // 5 = image value as a vector (coordinates) }. ++ // Depending on p_ref[0], the remaining p_ref[k] have the following meaning: ++ // When p_ref[0]==0, p_ref is actually unlinked. ++ // When p_ref[0]==1, p_ref = [ 1, vector_ind, offset ]. ++ // When p_ref[0]==2, p_ref = [ 2, image_ind (or ~0U), is_relative, offset ]. ++ // When p_ref[0]==3, p_ref = [ 3, image_ind (or ~0U), is_relative, x, y, z, c ]. ++ // When p_ref[0]==4, p_ref = [ 4, image_ind (or ~0U), is_relative, offset ]. ++ // When p_ref[0]==5, p_ref = [ 5, image_ind (or ~0U), is_relative, x, y, z ]. ++ if (p_ref) { *p_ref = 0; p_ref[1] = p_ref[2] = p_ref[3] = p_ref[4] = p_ref[5] = p_ref[6] = ~0U; } ++ ++ const char saved_char = *se; *se = 0; ++ const unsigned int clevel = level[ss - expr._data], clevel1 = clevel + 1; ++ bool is_sth, is_relative; ++ CImg ref; ++ CImgList _opcode; ++ CImg variable_name; ++ ++ // Look for a single value or a pre-defined variable. ++ int nb = cimg_sscanf(ss,"%lf%c%c",&val,&(sep=0),&(end=0)); ++ ++#if cimg_OS==2 ++ // Check for +/-NaN and +/-inf as Microsoft's sscanf() version is not able ++ // to read those particular values. ++ if (!nb && (*ss=='+' || *ss=='-' || *ss=='i' || *ss=='I' || *ss=='n' || *ss=='N')) { ++ is_sth = true; ++ s = ss; ++ if (*s=='+') ++s; else if (*s=='-') { ++s; is_sth = false; } ++ if (!cimg::strcasecmp(s,"inf")) { val = cimg::type::inf(); nb = 1; } ++ else if (!cimg::strcasecmp(s,"nan")) { val = cimg::type::nan(); nb = 1; } ++ if (nb==1 && !is_sth) val = -val; ++ } ++#endif ++ if (nb==1) _cimg_mp_constant(val); ++ if (nb==2 && sep=='%') _cimg_mp_constant(val/100); ++ ++ if (ss1==se) switch (*ss) { // One-char reserved variable ++ case 'c' : _cimg_mp_return(reserved_label['c']!=~0U?reserved_label['c']:_cimg_mp_slot_c); ++ case 'd' : _cimg_mp_return(reserved_label['d']!=~0U?reserved_label['d']:20); ++ case 'e' : _cimg_mp_return(reserved_label['e']!=~0U?reserved_label['e']:27); ++ case 'h' : _cimg_mp_return(reserved_label['h']!=~0U?reserved_label['h']:19); ++ case 'l' : _cimg_mp_return(reserved_label['l']!=~0U?reserved_label['l']:26); ++ case 'r' : _cimg_mp_return(reserved_label['r']!=~0U?reserved_label['r']:22); ++ case 's' : _cimg_mp_return(reserved_label['s']!=~0U?reserved_label['s']:21); ++ case 't' : _cimg_mp_return(reserved_label['t']!=~0U?reserved_label['t']:17); ++ case 'w' : _cimg_mp_return(reserved_label['w']!=~0U?reserved_label['w']:18); ++ case 'x' : _cimg_mp_return(reserved_label['x']!=~0U?reserved_label['x']:_cimg_mp_slot_x); ++ case 'y' : _cimg_mp_return(reserved_label['y']!=~0U?reserved_label['y']:_cimg_mp_slot_y); ++ case 'z' : _cimg_mp_return(reserved_label['z']!=~0U?reserved_label['z']:_cimg_mp_slot_z); ++ case 'u' : ++ if (reserved_label['u']!=~0U) _cimg_mp_return(reserved_label['u']); ++ _cimg_mp_scalar2(mp_u,0,1); ++ case 'g' : ++ if (reserved_label['g']!=~0U) _cimg_mp_return(reserved_label['g']); ++ _cimg_mp_scalar0(mp_g); ++ case 'i' : ++ if (reserved_label['i']!=~0U) _cimg_mp_return(reserved_label['i']); ++ _cimg_mp_scalar0(mp_i); ++ case 'I' : ++ _cimg_mp_op("Variable 'I'"); ++ if (reserved_label['I']!=~0U) _cimg_mp_return(reserved_label['I']); ++ _cimg_mp_check_vector0(imgin._spectrum); ++ need_input_copy = true; ++ pos = vector(imgin._spectrum); ++ CImg::vector((ulongT)mp_Joff,pos,0,0,imgin._spectrum).move_to(code); ++ _cimg_mp_return(pos); ++ case 'R' : ++ if (reserved_label['R']!=~0U) _cimg_mp_return(reserved_label['R']); ++ need_input_copy = true; ++ _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,0,0,0); ++ case 'G' : ++ if (reserved_label['G']!=~0U) _cimg_mp_return(reserved_label['G']); ++ need_input_copy = true; ++ _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,1,0,0); ++ case 'B' : ++ if (reserved_label['B']!=~0U) _cimg_mp_return(reserved_label['B']); ++ need_input_copy = true; ++ _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,2,0,0); ++ case 'A' : ++ if (reserved_label['A']!=~0U) _cimg_mp_return(reserved_label['A']); ++ need_input_copy = true; ++ _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,3,0,0); ++ } ++ else if (ss2==se) { // Two-chars reserved variable ++ arg1 = arg2 = ~0U; ++ if (*ss=='w' && *ss1=='h') // wh ++ _cimg_mp_return(reserved_label[0]!=~0U?reserved_label[0]:23); ++ if (*ss=='p' && *ss1=='i') // pi ++ _cimg_mp_return(reserved_label[3]!=~0U?reserved_label[3]:28); ++ if (*ss=='i') { ++ if (*ss1>='0' && *ss1<='9') { // i0...i9 ++ pos = 19 + *ss1 - '0'; ++ if (reserved_label[pos]!=~0U) _cimg_mp_return(reserved_label[pos]); ++ need_input_copy = true; ++ _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,pos - 19,0,0); ++ } ++ switch (*ss1) { ++ case 'm' : arg1 = 4; arg2 = 0; break; // im ++ case 'M' : arg1 = 5; arg2 = 1; break; // iM ++ case 'a' : arg1 = 6; arg2 = 2; break; // ia ++ case 'v' : arg1 = 7; arg2 = 3; break; // iv ++ case 's' : arg1 = 8; arg2 = 12; break; // is ++ case 'p' : arg1 = 9; arg2 = 13; break; // ip ++ case 'c' : // ic ++ if (reserved_label[10]!=~0U) _cimg_mp_return(reserved_label[10]); ++ if (mem_img_median==~0U) mem_img_median = imgin?constant(imgin.median()):0; ++ _cimg_mp_return(mem_img_median); ++ break; ++ } ++ } ++ else if (*ss1=='m') switch (*ss) { ++ case 'x' : arg1 = 11; arg2 = 4; break; // xm ++ case 'y' : arg1 = 12; arg2 = 5; break; // ym ++ case 'z' : arg1 = 13; arg2 = 6; break; // zm ++ case 'c' : arg1 = 14; arg2 = 7; break; // cm ++ } ++ else if (*ss1=='M') switch (*ss) { ++ case 'x' : arg1 = 15; arg2 = 8; break; // xM ++ case 'y' : arg1 = 16; arg2 = 9; break; // yM ++ case 'z' : arg1 = 17; arg2 = 10; break; // zM ++ case 'c' : arg1 = 18; arg2 = 11; break; // cM ++ } ++ if (arg1!=~0U) { ++ if (reserved_label[arg1]!=~0U) _cimg_mp_return(reserved_label[arg1]); ++ if (!img_stats) { ++ img_stats.assign(1,14,1,1,0).fill(imgin.get_stats(),false); ++ mem_img_stats.assign(1,14,1,1,~0U); ++ } ++ if (mem_img_stats[arg2]==~0U) mem_img_stats[arg2] = constant(img_stats[arg2]); ++ _cimg_mp_return(mem_img_stats[arg2]); ++ } ++ } else if (ss3==se) { // Three-chars reserved variable ++ if (*ss=='w' && *ss1=='h' && *ss2=='d') // whd ++ _cimg_mp_return(reserved_label[1]!=~0U?reserved_label[1]:24); ++ } else if (ss4==se) { // Four-chars reserved variable ++ if (*ss=='w' && *ss1=='h' && *ss2=='d' && *ss3=='s') // whds ++ _cimg_mp_return(reserved_label[2]!=~0U?reserved_label[2]:25); ++ } ++ ++ pos = ~0U; ++ is_sth = false; ++ for (s0 = ss, s = ss1; s='i'?1:3,p2); ++ ++ if (p_ref) { ++ *p_ref = _cimg_mp_is_vector(arg2)?4:2; ++ p_ref[1] = p1; ++ p_ref[2] = (unsigned int)is_relative; ++ p_ref[3] = arg1; ++ if (_cimg_mp_is_vector(arg2)) ++ set_variable_vector(arg2); // Prevent from being used in further optimization ++ else if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -2; ++ if (p1!=~0U && _cimg_mp_is_comp(p1)) memtype[p1] = -2; ++ if (_cimg_mp_is_comp(arg1)) memtype[arg1] = -2; ++ } ++ ++ ++ if (p1!=~0U) { ++ if (!listout) _cimg_mp_return(arg2); ++ if (*ss>='i') ++ CImg::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff), ++ arg2,p1,arg1).move_to(code); ++ else if (_cimg_mp_is_scalar(arg2)) ++ CImg::vector((ulongT)(is_relative?mp_list_set_Joff_s:mp_list_set_Ioff_s), ++ arg2,p1,arg1).move_to(code); ++ else ++ CImg::vector((ulongT)(is_relative?mp_list_set_Joff_v:mp_list_set_Ioff_v), ++ arg2,p1,arg1,_cimg_mp_size(arg2)).move_to(code); ++ } else { ++ if (!imgout) _cimg_mp_return(arg2); ++ if (*ss>='i') ++ CImg::vector((ulongT)(is_relative?mp_set_joff:mp_set_ioff), ++ arg2,arg1).move_to(code); ++ else if (_cimg_mp_is_scalar(arg2)) ++ CImg::vector((ulongT)(is_relative?mp_set_Joff_s:mp_set_Ioff_s), ++ arg2,arg1).move_to(code); ++ else ++ CImg::vector((ulongT)(is_relative?mp_set_Joff_v:mp_set_Ioff_v), ++ arg2,arg1,_cimg_mp_size(arg2)).move_to(code); ++ } ++ _cimg_mp_return(arg2); ++ } ++ ++ if (*ss1=='(' && *ve1==')') { // i/j/I/J(_#ind,_x,_y,_z,_c) = value ++ if (!is_single) is_parallelizable = false; ++ if (*ss2=='#') { // Index specified ++ s0 = ss3; while (s01) { ++ arg2 = arg1 + 1; ++ if (p2>2) { ++ arg3 = arg2 + 1; ++ if (p2>3) arg4 = arg3 + 1; ++ } ++ } ++ } else if (s1='i'?1:3,p2); ++ ++ if (p_ref) { ++ *p_ref = _cimg_mp_is_vector(arg5)?5:3; ++ p_ref[1] = p1; ++ p_ref[2] = (unsigned int)is_relative; ++ p_ref[3] = arg1; ++ p_ref[4] = arg2; ++ p_ref[5] = arg3; ++ p_ref[6] = arg4; ++ if (_cimg_mp_is_vector(arg5)) ++ set_variable_vector(arg5); // Prevent from being used in further optimization ++ else if (_cimg_mp_is_comp(arg5)) memtype[arg5] = -2; ++ if (p1!=~0U && _cimg_mp_is_comp(p1)) memtype[p1] = -2; ++ if (_cimg_mp_is_comp(arg1)) memtype[arg1] = -2; ++ if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -2; ++ if (_cimg_mp_is_comp(arg3)) memtype[arg3] = -2; ++ if (_cimg_mp_is_comp(arg4)) memtype[arg4] = -2; ++ } ++ if (p1!=~0U) { ++ if (!listout) _cimg_mp_return(arg5); ++ if (*ss>='i') ++ CImg::vector((ulongT)(is_relative?mp_list_set_jxyzc:mp_list_set_ixyzc), ++ arg5,p1,arg1,arg2,arg3,arg4).move_to(code); ++ else if (_cimg_mp_is_scalar(arg5)) ++ CImg::vector((ulongT)(is_relative?mp_list_set_Jxyz_s:mp_list_set_Ixyz_s), ++ arg5,p1,arg1,arg2,arg3).move_to(code); ++ else ++ CImg::vector((ulongT)(is_relative?mp_list_set_Jxyz_v:mp_list_set_Ixyz_v), ++ arg5,p1,arg1,arg2,arg3,_cimg_mp_size(arg5)).move_to(code); ++ } else { ++ if (!imgout) _cimg_mp_return(arg5); ++ if (*ss>='i') ++ CImg::vector((ulongT)(is_relative?mp_set_jxyzc:mp_set_ixyzc), ++ arg5,arg1,arg2,arg3,arg4).move_to(code); ++ else if (_cimg_mp_is_scalar(arg5)) ++ CImg::vector((ulongT)(is_relative?mp_set_Jxyz_s:mp_set_Ixyz_s), ++ arg5,arg1,arg2,arg3).move_to(code); ++ else ++ CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), ++ arg5,arg1,arg2,arg3,_cimg_mp_size(arg5)).move_to(code); ++ } ++ _cimg_mp_return(arg5); ++ } ++ } ++ ++ // Assign vector value (direct). ++ if (l_variable_name>3 && *ve1==']' && *ss!='[') { ++ s0 = ve1; while (s0>ss && (*s0!='[' || level[s0 - expr._data]!=clevel)) --s0; ++ is_sth = true; // is_valid_variable_name? ++ if (*ss>='0' && *ss<='9') is_sth = false; ++ else for (ns = ss; nsss) { ++ variable_name[s0 - ss] = 0; // Remove brackets in variable name ++ arg1 = ~0U; // Vector slot ++ arg2 = compile(++s0,ve1,depth1,0,is_single); // Index ++ arg3 = compile(s + 1,se,depth1,0,is_single); // Value to assign ++ _cimg_mp_check_type(arg3,2,1,0); ++ ++ if (variable_name[1]) { // Multi-char variable ++ cimglist_for(variable_def,i) if (!std::strcmp(variable_name,variable_def[i])) { ++ arg1 = variable_pos[i]; break; ++ } ++ } else arg1 = reserved_label[*variable_name]; // Single-char variable ++ if (arg1==~0U) compile(ss,s0 - 1,depth1,0,is_single); // Variable does not exist -> error ++ else { // Variable already exists ++ if (_cimg_mp_is_scalar(arg1)) compile(ss,s,depth1,0,is_single); // Variable is not a vector -> error ++ if (_cimg_mp_is_constant(arg2)) { // Constant index -> return corresponding variable slot directly ++ nb = (int)mem[arg2]; ++ if (nb>=0 && nb<(int)_cimg_mp_size(arg1)) { ++ arg1+=nb + 1; ++ CImg::vector((ulongT)mp_copy,arg1,arg3).move_to(code); ++ _cimg_mp_return(arg1); ++ } ++ compile(ss,s,depth1,0,is_single); // Out-of-bounds reference -> error ++ } ++ ++ // Case of non-constant index -> return assigned value + linked reference ++ if (p_ref) { ++ *p_ref = 1; ++ p_ref[1] = arg1; ++ p_ref[2] = arg2; ++ if (_cimg_mp_is_comp(arg3)) memtype[arg3] = -2; // Prevent from being used in further optimization ++ if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -2; ++ } ++ CImg::vector((ulongT)mp_vector_set_off,arg3,arg1,(ulongT)_cimg_mp_size(arg1), ++ arg2,arg3). ++ move_to(code); ++ _cimg_mp_return(arg3); ++ } ++ } ++ } ++ ++ // Assign user-defined macro. ++ if (l_variable_name>2 && *ve1==')' && *ss!='(') { ++ s0 = ve1; while (s0>ss && *s0!='(') --s0; ++ is_sth = std::strncmp(variable_name,"debug(",6) && ++ std::strncmp(variable_name,"print(",6); // is_valid_function_name? ++ if (*ss>='0' && *ss<='9') is_sth = false; ++ else for (ns = ss; nsss) { // Looks like a valid function declaration ++ s0 = variable_name._data + (s0 - ss); ++ *s0 = 0; ++ s1 = variable_name._data + l_variable_name - 1; // Pointer to closing parenthesis ++ CImg(variable_name._data,(unsigned int)(s0 - variable_name._data + 1)).move_to(macro_def,0); ++ ++s; while (*s && (signed char)*s<=' ') ++s; ++ CImg(s,(unsigned int)(se - s + 1)).move_to(macro_body,0); ++ ++ p1 = 1; // Indice of current parsed argument ++ for (s = s0 + 1; s<=s1; ++p1, s = ns + 1) { // Parse function arguments ++ if (p1>24) { ++ *se = saved_char; ++ cimg::strellipsize(variable_name,64); ++ s0 = ss - 4>expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: %s: Too much specified arguments (>24) in macro " ++ "definition '%s()', in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,s_op, ++ variable_name._data, ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } ++ while (*s && (signed char)*s<=' ') ++s; ++ if (*s==')' && p1==1) break; // Function has no arguments ++ ++ s2 = s; // Start of the argument name ++ is_sth = true; // is_valid_argument_name? ++ if (*s>='0' && *s<='9') is_sth = false; ++ else for (ns = s; ns' '; ++ns) ++ if (!is_varchar(*ns)) { is_sth = false; break; } ++ s3 = ns; // End of the argument name ++ while (*ns && (signed char)*ns<=' ') ++ns; ++ if (!is_sth || s2==s3 || (*ns!=',' && ns!=s1)) { ++ *se = saved_char; ++ cimg::strellipsize(variable_name,64); ++ s0 = ss - 4>expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: %s: %s name specified for argument %u when defining " ++ "macro '%s()', in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,s_op, ++ is_sth?"Empty":"Invalid",p1, ++ variable_name._data, ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } ++ if (ns==s1 || *ns==',') { // New argument found ++ *s3 = 0; ++ p2 = (unsigned int)(s3 - s2); // Argument length ++ for (ps = std::strstr(macro_body[0],s2); ps; ps = std::strstr(ps,s2)) { // Replace by arg number ++ if (!((ps>macro_body[0]._data && is_varchar(*(ps - 1))) || ++ (ps + p2macro_body[0]._data && *(ps - 1)=='#') { // Remove pre-number sign ++ *(ps - 1) = (char)p1; ++ if (ps + p26 && !std::strncmp(variable_name,"const ",6); ++ ++ s0 = variable_name._data; ++ if (is_const) { ++ s0+=6; while ((signed char)*s0<=' ') ++s0; ++ variable_name.resize(variable_name.end() - s0,1,1,1,0,0,1); ++ } ++ ++ if (*variable_name>='0' && *variable_name<='9') is_sth = false; ++ else for (ns = variable_name._data; *ns; ++ns) ++ if (!is_varchar(*ns)) { is_sth = false; break; } ++ ++ // Assign variable (direct). ++ if (is_sth) { ++ arg3 = variable_name[1]?~0U:*variable_name; // One-char variable ++ if (variable_name[1] && !variable_name[2]) { // Two-chars variable ++ c1 = variable_name[0]; ++ c2 = variable_name[1]; ++ if (c1=='w' && c2=='h') arg3 = 0; // wh ++ else if (c1=='p' && c2=='i') arg3 = 3; // pi ++ else if (c1=='i') { ++ if (c2>='0' && c2<='9') arg3 = 19 + c2 - '0'; // i0...i9 ++ else if (c2=='m') arg3 = 4; // im ++ else if (c2=='M') arg3 = 5; // iM ++ else if (c2=='a') arg3 = 6; // ia ++ else if (c2=='v') arg3 = 7; // iv ++ else if (c2=='s') arg3 = 8; // is ++ else if (c2=='p') arg3 = 9; // ip ++ else if (c2=='c') arg3 = 10; // ic ++ } else if (c2=='m') { ++ if (c1=='x') arg3 = 11; // xm ++ else if (c1=='y') arg3 = 12; // ym ++ else if (c1=='z') arg3 = 13; // zm ++ else if (c1=='c') arg3 = 14; // cm ++ } else if (c2=='M') { ++ if (c1=='x') arg3 = 15; // xM ++ else if (c1=='y') arg3 = 16; // yM ++ else if (c1=='z') arg3 = 17; // zM ++ else if (c1=='c') arg3 = 18; // cM ++ } ++ } else if (variable_name[1] && variable_name[2] && !variable_name[3]) { // Three-chars variable ++ c1 = variable_name[0]; ++ c2 = variable_name[1]; ++ c3 = variable_name[2]; ++ if (c1=='w' && c2=='h' && c3=='d') arg3 = 1; // whd ++ } else if (variable_name[1] && variable_name[2] && variable_name[3] && ++ !variable_name[4]) { // Four-chars variable ++ c1 = variable_name[0]; ++ c2 = variable_name[1]; ++ c3 = variable_name[2]; ++ c4 = variable_name[3]; ++ if (c1=='w' && c2=='h' && c3=='d' && c4=='s') arg3 = 2; // whds ++ } else if (!std::strcmp(variable_name,"interpolation")) arg3 = 29; // interpolation ++ else if (!std::strcmp(variable_name,"boundary")) arg3 = 30; // boundary ++ ++ arg1 = ~0U; ++ arg2 = compile(s + 1,se,depth1,0,is_single); ++ if (is_const) _cimg_mp_check_constant(arg2,2,0); ++ ++ if (arg3!=~0U) // One-char variable, or variable in reserved_labels ++ arg1 = reserved_label[arg3]; ++ else // Multi-char variable name : check for existing variable with same name ++ cimglist_for(variable_def,i) ++ if (!std::strcmp(variable_name,variable_def[i])) { arg1 = variable_pos[i]; break; } ++ ++ if (arg1==~0U) { // Create new variable ++ if (_cimg_mp_is_vector(arg2)) { // Vector variable ++ arg1 = is_comp_vector(arg2)?arg2:vector_copy(arg2); ++ set_variable_vector(arg1); ++ } else { // Scalar variable ++ if (is_const) arg1 = arg2; ++ else { ++ arg1 = _cimg_mp_is_comp(arg2)?arg2:scalar1(mp_copy,arg2); ++ memtype[arg1] = -1; ++ } ++ } ++ ++ if (arg3!=~0U) reserved_label[arg3] = arg1; ++ else { ++ if (variable_def._width>=variable_pos._width) variable_pos.resize(-200,1,1,1,0); ++ variable_pos[variable_def._width] = arg1; ++ variable_name.move_to(variable_def); ++ } ++ ++ } else { // Variable already exists -> assign a new value ++ if (is_const || _cimg_mp_is_constant(arg1)) { ++ *se = saved_char; ++ cimg::strellipsize(variable_name,64); ++ s0 = ss - 4>expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: %s: Invalid assignment of %sconst variable '%s'%s, " ++ "in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,s_op, ++ _cimg_mp_is_constant(arg1)?"already-defined ":"non-", ++ variable_name._data, ++ !_cimg_mp_is_constant(arg1) && is_const?" as a new const variable":"", ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } ++ _cimg_mp_check_type(arg2,2,_cimg_mp_is_vector(arg1)?3:1,_cimg_mp_size(arg1)); ++ if (_cimg_mp_is_vector(arg1)) { // Vector ++ if (_cimg_mp_is_vector(arg2)) // From vector ++ CImg::vector((ulongT)mp_vector_copy,arg1,arg2,(ulongT)_cimg_mp_size(arg1)). ++ move_to(code); ++ else // From scalar ++ CImg::vector((ulongT)mp_vector_init,arg1,1,(ulongT)_cimg_mp_size(arg1),arg2). ++ move_to(code); ++ } else // Scalar ++ CImg::vector((ulongT)mp_copy,arg1,arg2).move_to(code); ++ } ++ _cimg_mp_return(arg1); ++ } ++ ++ // Assign lvalue (variable name was not valid for a direct assignment). ++ arg1 = ~0U; ++ is_sth = (bool)std::strchr(variable_name,'?'); // Contains_ternary_operator? ++ if (is_sth) break; // Do nothing and make ternary operator prioritary over assignment ++ ++ if (l_variable_name>2 && (std::strchr(variable_name,'(') || std::strchr(variable_name,'['))) { ++ ref.assign(7); ++ arg1 = compile(ss,s,depth1,ref,is_single); // Lvalue slot ++ arg2 = compile(s + 1,se,depth1,0,is_single); // Value to assign ++ ++ if (*ref==1) { // Vector value (scalar): V[k] = scalar ++ _cimg_mp_check_type(arg2,2,1,0); ++ arg3 = ref[1]; // Vector slot ++ arg4 = ref[2]; // Index ++ if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); ++ CImg::vector((ulongT)mp_vector_set_off,arg2,arg3,(ulongT)_cimg_mp_size(arg3),arg4,arg2). ++ move_to(code); ++ _cimg_mp_return(arg2); ++ } ++ ++ if (*ref==2) { // Image value (scalar): i/j[_#ind,off] = scalar ++ if (!is_single) is_parallelizable = false; ++ _cimg_mp_check_type(arg2,2,1,0); ++ p1 = ref[1]; // Index ++ is_relative = (bool)ref[2]; ++ arg3 = ref[3]; // Offset ++ if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); ++ if (p1!=~0U) { ++ if (!listout) _cimg_mp_return(arg2); ++ CImg::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff), ++ arg2,p1,arg3).move_to(code); ++ } else { ++ if (!imgout) _cimg_mp_return(arg2); ++ CImg::vector((ulongT)(is_relative?mp_set_joff:mp_set_ioff), ++ arg2,arg3).move_to(code); ++ } ++ _cimg_mp_return(arg2); ++ } ++ ++ if (*ref==3) { // Image value (scalar): i/j(_#ind,_x,_y,_z,_c) = scalar ++ if (!is_single) is_parallelizable = false; ++ _cimg_mp_check_type(arg2,2,1,0); ++ p1 = ref[1]; // Index ++ is_relative = (bool)ref[2]; ++ arg3 = ref[3]; // X ++ arg4 = ref[4]; // Y ++ arg5 = ref[5]; // Z ++ arg6 = ref[6]; // C ++ if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); ++ if (p1!=~0U) { ++ if (!listout) _cimg_mp_return(arg2); ++ CImg::vector((ulongT)(is_relative?mp_list_set_jxyzc:mp_list_set_ixyzc), ++ arg2,p1,arg3,arg4,arg5,arg6).move_to(code); ++ } else { ++ if (!imgout) _cimg_mp_return(arg2); ++ CImg::vector((ulongT)(is_relative?mp_set_jxyzc:mp_set_ixyzc), ++ arg2,arg3,arg4,arg5,arg6).move_to(code); ++ } ++ _cimg_mp_return(arg2); ++ } ++ ++ if (*ref==4) { // Image value (vector): I/J[_#ind,off] = value ++ if (!is_single) is_parallelizable = false; ++ _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); ++ p1 = ref[1]; // Index ++ is_relative = (bool)ref[2]; ++ arg3 = ref[3]; // Offset ++ if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); ++ if (p1!=~0U) { ++ if (!listout) _cimg_mp_return(arg2); ++ if (_cimg_mp_is_scalar(arg2)) ++ CImg::vector((ulongT)(is_relative?mp_list_set_Joff_s:mp_list_set_Ioff_s), ++ arg2,p1,arg3).move_to(code); ++ else ++ CImg::vector((ulongT)(is_relative?mp_list_set_Joff_v:mp_list_set_Ioff_v), ++ arg2,p1,arg3,_cimg_mp_size(arg2)).move_to(code); ++ } else { ++ if (!imgout) _cimg_mp_return(arg2); ++ if (_cimg_mp_is_scalar(arg2)) ++ CImg::vector((ulongT)(is_relative?mp_set_Joff_s:mp_set_Ioff_s), ++ arg2,arg3).move_to(code); ++ else ++ CImg::vector((ulongT)(is_relative?mp_set_Joff_v:mp_set_Ioff_v), ++ arg2,arg3,_cimg_mp_size(arg2)).move_to(code); ++ } ++ _cimg_mp_return(arg2); ++ } ++ ++ if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c) = value ++ if (!is_single) is_parallelizable = false; ++ _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); ++ p1 = ref[1]; // Index ++ is_relative = (bool)ref[2]; ++ arg3 = ref[3]; // X ++ arg4 = ref[4]; // Y ++ arg5 = ref[5]; // Z ++ if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); ++ if (p1!=~0U) { ++ if (!listout) _cimg_mp_return(arg2); ++ if (_cimg_mp_is_scalar(arg2)) ++ CImg::vector((ulongT)(is_relative?mp_list_set_Jxyz_s:mp_list_set_Ixyz_s), ++ arg2,p1,arg3,arg4,arg5).move_to(code); ++ else ++ CImg::vector((ulongT)(is_relative?mp_list_set_Jxyz_v:mp_list_set_Ixyz_v), ++ arg2,p1,arg3,arg4,arg5,_cimg_mp_size(arg2)).move_to(code); ++ } else { ++ if (!imgout) _cimg_mp_return(arg2); ++ if (_cimg_mp_is_scalar(arg2)) ++ CImg::vector((ulongT)(is_relative?mp_set_Jxyz_s:mp_set_Ixyz_s), ++ arg2,arg3,arg4,arg5).move_to(code); ++ else ++ CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), ++ arg2,arg3,arg4,arg5,_cimg_mp_size(arg2)).move_to(code); ++ } ++ _cimg_mp_return(arg2); ++ } ++ ++ if (_cimg_mp_is_vector(arg1)) { // Vector variable: V = value ++ _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); ++ if (_cimg_mp_is_vector(arg2)) // From vector ++ CImg::vector((ulongT)mp_vector_copy,arg1,arg2,(ulongT)_cimg_mp_size(arg1)). ++ move_to(code); ++ else // From scalar ++ CImg::vector((ulongT)mp_vector_init,arg1,1,(ulongT)_cimg_mp_size(arg1),arg2). ++ move_to(code); ++ _cimg_mp_return(arg1); ++ } ++ ++ if (_cimg_mp_is_variable(arg1)) { // Scalar variable: s = scalar ++ _cimg_mp_check_type(arg2,2,1,0); ++ CImg::vector((ulongT)mp_copy,arg1,arg2).move_to(code); ++ _cimg_mp_return(arg1); ++ } ++ } ++ ++ // No assignment expressions match -> error ++ *se = saved_char; ++ cimg::strellipsize(variable_name,64); ++ s0 = ss - 4>expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: %s: Invalid %slvalue '%s', " ++ "in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,s_op, ++ arg1!=~0U && _cimg_mp_is_constant(arg1)?"const ":"", ++ variable_name._data, ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } ++ ++ // Apply unary/binary/ternary operators. The operator precedences should be the same as in C++. ++ for (s = se2, ps = se3, ns = ps - 1; s>ss1; --s, --ps, --ns) // Here, ns = ps - 1 ++ if (*s=='=' && (*ps=='*' || *ps=='/' || *ps=='^') && *ns==*ps && ++ level[s - expr._data]==clevel) { // Self-operators for complex numbers only (**=,//=,^^=) ++ _cimg_mp_op(*ps=='*'?"Operator '**='":*ps=='/'?"Operator '//='":"Operator '^^='"); ++ ++ ref.assign(7); ++ arg1 = compile(ss,ns,depth1,ref,is_single); // Vector slot ++ arg2 = compile(s + 1,se,depth1,0,is_single); // Right operand ++ _cimg_mp_check_type(arg1,1,2,2); ++ _cimg_mp_check_type(arg2,2,3,2); ++ if (_cimg_mp_is_vector(arg2)) { // Complex **= complex ++ if (*ps=='*') ++ CImg::vector((ulongT)mp_complex_mul,arg1,arg1,arg2).move_to(code); ++ else if (*ps=='/') ++ CImg::vector((ulongT)mp_complex_div_vv,arg1,arg1,arg2).move_to(code); ++ else ++ CImg::vector((ulongT)mp_complex_pow_vv,arg1,arg1,arg2).move_to(code); ++ } else { // Complex **= scalar ++ if (*ps=='*') { ++ if (arg2==1) _cimg_mp_return(arg1); ++ self_vector_s(arg1,mp_self_mul,arg2); ++ } else if (*ps=='/') { ++ if (arg2==1) _cimg_mp_return(arg1); ++ self_vector_s(arg1,mp_self_div,arg2); ++ } else { ++ if (arg2==1) _cimg_mp_return(arg1); ++ CImg::vector((ulongT)mp_complex_pow_vs,arg1,arg1,arg2).move_to(code); ++ } ++ } ++ ++ // Write computed value back in image if necessary. ++ if (*ref==4) { // Image value (vector): I/J[_#ind,off] **= value ++ if (!is_single) is_parallelizable = false; ++ p1 = ref[1]; // Index ++ is_relative = (bool)ref[2]; ++ arg3 = ref[3]; // Offset ++ if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); ++ if (p1!=~0U) { ++ if (!listout) _cimg_mp_return(arg1); ++ CImg::vector((ulongT)(is_relative?mp_list_set_Joff_v:mp_list_set_Ioff_v), ++ arg1,p1,arg3,_cimg_mp_size(arg1)).move_to(code); ++ } else { ++ if (!imgout) _cimg_mp_return(arg1); ++ CImg::vector((ulongT)(is_relative?mp_set_Joff_v:mp_set_Ioff_v), ++ arg1,arg3,_cimg_mp_size(arg1)).move_to(code); ++ } ++ ++ } else if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c) **= value ++ if (!is_single) is_parallelizable = false; ++ p1 = ref[1]; // Index ++ is_relative = (bool)ref[2]; ++ arg3 = ref[3]; // X ++ arg4 = ref[4]; // Y ++ arg5 = ref[5]; // Z ++ if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); ++ if (p1!=~0U) { ++ if (!listout) _cimg_mp_return(arg1); ++ CImg::vector((ulongT)(is_relative?mp_list_set_Jxyz_v:mp_list_set_Ixyz_v), ++ arg1,p1,arg3,arg4,arg5,_cimg_mp_size(arg1)).move_to(code); ++ } else { ++ if (!imgout) _cimg_mp_return(arg1); ++ CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), ++ arg1,arg3,arg4,arg5,_cimg_mp_size(arg1)).move_to(code); ++ } ++ } ++ ++ _cimg_mp_return(arg1); ++ } ++ ++ for (s = se2, ps = se3, ns = ps - 1; s>ss1; --s, --ps, --ns) // Here, ns = ps - 1 ++ if (*s=='=' && (*ps=='+' || *ps=='-' || *ps=='*' || *ps=='/' || *ps=='%' || ++ *ps=='&' || *ps=='^' || *ps=='|' || ++ (*ps=='>' && *ns=='>') || (*ps=='<' && *ns=='<')) && ++ level[s - expr._data]==clevel) { // Self-operators (+=,-=,*=,/=,%=,>>=,<<=,&=,^=,|=) ++ switch (*ps) { ++ case '+' : op = mp_self_add; _cimg_mp_op("Operator '+='"); break; ++ case '-' : op = mp_self_sub; _cimg_mp_op("Operator '-='"); break; ++ case '*' : op = mp_self_mul; _cimg_mp_op("Operator '*='"); break; ++ case '/' : op = mp_self_div; _cimg_mp_op("Operator '/='"); break; ++ case '%' : op = mp_self_modulo; _cimg_mp_op("Operator '%='"); break; ++ case '<' : op = mp_self_bitwise_left_shift; _cimg_mp_op("Operator '<<='"); break; ++ case '>' : op = mp_self_bitwise_right_shift; _cimg_mp_op("Operator '>>='"); break; ++ case '&' : op = mp_self_bitwise_and; _cimg_mp_op("Operator '&='"); break; ++ case '|' : op = mp_self_bitwise_or; _cimg_mp_op("Operator '|='"); break; ++ default : op = mp_self_pow; _cimg_mp_op("Operator '^='"); break; ++ } ++ s1 = *ps=='>' || *ps=='<'?ns:ps; ++ ++ ref.assign(7); ++ arg1 = compile(ss,s1,depth1,ref,is_single); // Variable slot ++ arg2 = compile(s + 1,se,depth1,0,is_single); // Value to apply ++ ++ // Check for particular case to be simplified. ++ if ((op==mp_self_add || op==mp_self_sub) && !arg2) _cimg_mp_return(arg1); ++ if ((op==mp_self_mul || op==mp_self_div) && arg2==1) _cimg_mp_return(arg1); ++ ++ // Apply operator on a copy to prevent modifying a constant or a variable. ++ if (*ref && (_cimg_mp_is_constant(arg1) || _cimg_mp_is_vector(arg1) || _cimg_mp_is_variable(arg1))) { ++ if (_cimg_mp_is_vector(arg1)) arg1 = vector_copy(arg1); ++ else arg1 = scalar1(mp_copy,arg1); ++ } ++ ++ if (*ref==1) { // Vector value (scalar): V[k] += scalar ++ _cimg_mp_check_type(arg2,2,1,0); ++ arg3 = ref[1]; // Vector slot ++ arg4 = ref[2]; // Index ++ if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); ++ CImg::vector((ulongT)op,arg1,arg2).move_to(code); ++ CImg::vector((ulongT)mp_vector_set_off,arg1,arg3,(ulongT)_cimg_mp_size(arg3),arg4,arg1). ++ move_to(code); ++ _cimg_mp_return(arg1); ++ } ++ ++ if (*ref==2) { // Image value (scalar): i/j[_#ind,off] += scalar ++ if (!is_single) is_parallelizable = false; ++ _cimg_mp_check_type(arg2,2,1,0); ++ p1 = ref[1]; // Index ++ is_relative = (bool)ref[2]; ++ arg3 = ref[3]; // Offset ++ if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); ++ CImg::vector((ulongT)op,arg1,arg2).move_to(code); ++ if (p1!=~0U) { ++ if (!listout) _cimg_mp_return(arg1); ++ CImg::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff), ++ arg1,p1,arg3).move_to(code); ++ } else { ++ if (!imgout) _cimg_mp_return(arg1); ++ CImg::vector((ulongT)(is_relative?mp_set_joff:mp_set_ioff), ++ arg1,arg3).move_to(code); ++ } ++ _cimg_mp_return(arg1); ++ } ++ ++ if (*ref==3) { // Image value (scalar): i/j(_#ind,_x,_y,_z,_c) += scalar ++ if (!is_single) is_parallelizable = false; ++ _cimg_mp_check_type(arg2,2,1,0); ++ p1 = ref[1]; // Index ++ is_relative = (bool)ref[2]; ++ arg3 = ref[3]; // X ++ arg4 = ref[4]; // Y ++ arg5 = ref[5]; // Z ++ arg6 = ref[6]; // C ++ if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); ++ CImg::vector((ulongT)op,arg1,arg2).move_to(code); ++ if (p1!=~0U) { ++ if (!listout) _cimg_mp_return(arg1); ++ CImg::vector((ulongT)(is_relative?mp_list_set_jxyzc:mp_list_set_ixyzc), ++ arg1,p1,arg3,arg4,arg5,arg6).move_to(code); ++ } else { ++ if (!imgout) _cimg_mp_return(arg1); ++ CImg::vector((ulongT)(is_relative?mp_set_jxyzc:mp_set_ixyzc), ++ arg1,arg3,arg4,arg5,arg6).move_to(code); ++ } ++ _cimg_mp_return(arg1); ++ } ++ ++ if (*ref==4) { // Image value (vector): I/J[_#ind,off] += value ++ if (!is_single) is_parallelizable = false; ++ _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); ++ p1 = ref[1]; // Index ++ is_relative = (bool)ref[2]; ++ arg3 = ref[3]; // Offset ++ if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); ++ if (_cimg_mp_is_scalar(arg2)) self_vector_s(arg1,op,arg2); else self_vector_v(arg1,op,arg2); ++ if (p1!=~0U) { ++ if (!listout) _cimg_mp_return(arg1); ++ CImg::vector((ulongT)(is_relative?mp_list_set_Joff_v:mp_list_set_Ioff_v), ++ arg1,p1,arg3,_cimg_mp_size(arg1)).move_to(code); ++ } else { ++ if (!imgout) _cimg_mp_return(arg1); ++ CImg::vector((ulongT)(is_relative?mp_set_Joff_v:mp_set_Ioff_v), ++ arg1,arg3,_cimg_mp_size(arg1)).move_to(code); ++ } ++ _cimg_mp_return(arg1); ++ } ++ ++ if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c) += value ++ if (!is_single) is_parallelizable = false; ++ _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); ++ p1 = ref[1]; // Index ++ is_relative = (bool)ref[2]; ++ arg3 = ref[3]; // X ++ arg4 = ref[4]; // Y ++ arg5 = ref[5]; // Z ++ if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); ++ if (_cimg_mp_is_scalar(arg2)) self_vector_s(arg1,op,arg2); else self_vector_v(arg1,op,arg2); ++ if (p1!=~0U) { ++ if (!listout) _cimg_mp_return(arg1); ++ CImg::vector((ulongT)(is_relative?mp_list_set_Jxyz_v:mp_list_set_Ixyz_v), ++ arg1,p1,arg3,arg4,arg5,_cimg_mp_size(arg1)).move_to(code); ++ } else { ++ if (!imgout) _cimg_mp_return(arg1); ++ CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), ++ arg1,arg3,arg4,arg5,_cimg_mp_size(arg1)).move_to(code); ++ } ++ _cimg_mp_return(arg1); ++ } ++ ++ if (_cimg_mp_is_vector(arg1)) { // Vector variable: V += value ++ _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); ++ if (_cimg_mp_is_vector(arg2)) self_vector_v(arg1,op,arg2); // Vector += vector ++ else self_vector_s(arg1,op,arg2); // Vector += scalar ++ _cimg_mp_return(arg1); ++ } ++ ++ if (_cimg_mp_is_variable(arg1)) { // Scalar variable: s += scalar ++ _cimg_mp_check_type(arg2,2,1,0); ++ CImg::vector((ulongT)op,arg1,arg2).move_to(code); ++ _cimg_mp_return(arg1); ++ } ++ ++ variable_name.assign(ss,(unsigned int)(s - ss)).back() = 0; ++ cimg::strpare(variable_name,false,true); ++ *se = saved_char; ++ s0 = ss - 4>expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: %s: Invalid %slvalue '%s', " ++ "in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,s_op, ++ _cimg_mp_is_constant(arg1)?"const ":"", ++ variable_name._data, ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } ++ ++ for (s = ss1; s::vector((ulongT)mp_if,pos,arg1,arg2,arg3, ++ p3 - p2,code._width - p3,arg4).move_to(code,p2); ++ _cimg_mp_return(pos); ++ } ++ ++ for (s = se3, ns = se2; s>ss; --s, --ns) ++ if (*s=='|' && *ns=='|' && level[s - expr._data]==clevel) { // Logical or ('||') ++ _cimg_mp_op("Operator '||'"); ++ arg1 = compile(ss,s,depth1,0,is_single); ++ _cimg_mp_check_type(arg1,1,1,0); ++ if (arg1>0 && arg1<=16) _cimg_mp_return(1); ++ p2 = code._width; ++ arg2 = compile(s + 2,se,depth1,0,is_single); ++ _cimg_mp_check_type(arg2,2,1,0); ++ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) ++ _cimg_mp_constant(mem[arg1] || mem[arg2]); ++ if (!arg1) _cimg_mp_return(arg2); ++ pos = scalar(); ++ CImg::vector((ulongT)mp_logical_or,pos,arg1,arg2,code._width - p2). ++ move_to(code,p2); ++ _cimg_mp_return(pos); ++ } ++ ++ for (s = se3, ns = se2; s>ss; --s, --ns) ++ if (*s=='&' && *ns=='&' && level[s - expr._data]==clevel) { // Logical and ('&&') ++ _cimg_mp_op("Operator '&&'"); ++ arg1 = compile(ss,s,depth1,0,is_single); ++ _cimg_mp_check_type(arg1,1,1,0); ++ if (!arg1) _cimg_mp_return(0); ++ p2 = code._width; ++ arg2 = compile(s + 2,se,depth1,0,is_single); ++ _cimg_mp_check_type(arg2,2,1,0); ++ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) ++ _cimg_mp_constant(mem[arg1] && mem[arg2]); ++ if (arg1>0 && arg1<=16) _cimg_mp_return(arg2); ++ pos = scalar(); ++ CImg::vector((ulongT)mp_logical_and,pos,arg1,arg2,code._width - p2). ++ move_to(code,p2); ++ _cimg_mp_return(pos); ++ } ++ ++ for (s = se2; s>ss; --s) ++ if (*s=='|' && level[s - expr._data]==clevel) { // Bitwise or ('|') ++ _cimg_mp_op("Operator '|'"); ++ arg1 = compile(ss,s,depth1,0,is_single); ++ arg2 = compile(s + 1,se,depth1,0,is_single); ++ _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_bitwise_or,arg1,arg2); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) { ++ if (!arg2) _cimg_mp_return(arg1); ++ _cimg_mp_vector2_vs(mp_bitwise_or,arg1,arg2); ++ } ++ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) { ++ if (!arg1) _cimg_mp_return(arg2); ++ _cimg_mp_vector2_sv(mp_bitwise_or,arg1,arg2); ++ } ++ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) ++ _cimg_mp_constant((longT)mem[arg1] | (longT)mem[arg2]); ++ if (!arg2) _cimg_mp_return(arg1); ++ if (!arg1) _cimg_mp_return(arg2); ++ _cimg_mp_scalar2(mp_bitwise_or,arg1,arg2); ++ } ++ ++ for (s = se2; s>ss; --s) ++ if (*s=='&' && level[s - expr._data]==clevel) { // Bitwise and ('&') ++ _cimg_mp_op("Operator '&'"); ++ arg1 = compile(ss,s,depth1,0,is_single); ++ arg2 = compile(s + 1,se,depth1,0,is_single); ++ _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_bitwise_and,arg1,arg2); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_bitwise_and,arg1,arg2); ++ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_bitwise_and,arg1,arg2); ++ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) ++ _cimg_mp_constant((longT)mem[arg1] & (longT)mem[arg2]); ++ if (!arg1 || !arg2) _cimg_mp_return(0); ++ _cimg_mp_scalar2(mp_bitwise_and,arg1,arg2); ++ } ++ ++ for (s = se3, ns = se2; s>ss; --s, --ns) ++ if (*s=='!' && *ns=='=' && level[s - expr._data]==clevel) { // Not equal to ('!=') ++ _cimg_mp_op("Operator '!='"); ++ arg1 = compile(ss,s,depth1,0,is_single); ++ arg2 = compile(s + 2,se,depth1,0,is_single); ++ if (arg1==arg2) _cimg_mp_return(0); ++ p1 = _cimg_mp_size(arg1); ++ p2 = _cimg_mp_size(arg2); ++ if (p1 || p2) { ++ if (p1 && p2 && p1!=p2) _cimg_mp_return(1); ++ _cimg_mp_scalar6(mp_vector_neq,arg1,p1,arg2,p2,11,1); ++ } ++ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]!=mem[arg2]); ++ _cimg_mp_scalar2(mp_neq,arg1,arg2); ++ } ++ ++ for (s = se3, ns = se2; s>ss; --s, --ns) ++ if (*s=='=' && *ns=='=' && level[s - expr._data]==clevel) { // Equal to ('==') ++ _cimg_mp_op("Operator '=='"); ++ arg1 = compile(ss,s,depth1,0,is_single); ++ arg2 = compile(s + 2,se,depth1,0,is_single); ++ if (arg1==arg2) _cimg_mp_return(1); ++ p1 = _cimg_mp_size(arg1); ++ p2 = _cimg_mp_size(arg2); ++ if (p1 || p2) { ++ if (p1 && p2 && p1!=p2) _cimg_mp_return(0); ++ _cimg_mp_scalar6(mp_vector_eq,arg1,p1,arg2,p2,11,1); ++ } ++ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]==mem[arg2]); ++ _cimg_mp_scalar2(mp_eq,arg1,arg2); ++ } ++ ++ for (s = se3, ns = se2; s>ss; --s, --ns) ++ if (*s=='<' && *ns=='=' && level[s - expr._data]==clevel) { // Less or equal than ('<=') ++ _cimg_mp_op("Operator '<='"); ++ arg1 = compile(ss,s,depth1,0,is_single); ++ arg2 = compile(s + 2,se,depth1,0,is_single); ++ _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_lte,arg1,arg2); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_lte,arg1,arg2); ++ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_lte,arg1,arg2); ++ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]<=mem[arg2]); ++ if (arg1==arg2) _cimg_mp_return(1); ++ _cimg_mp_scalar2(mp_lte,arg1,arg2); ++ } ++ ++ for (s = se3, ns = se2; s>ss; --s, --ns) ++ if (*s=='>' && *ns=='=' && level[s - expr._data]==clevel) { // Greater or equal than ('>=') ++ _cimg_mp_op("Operator '>='"); ++ arg1 = compile(ss,s,depth1,0,is_single); ++ arg2 = compile(s + 2,se,depth1,0,is_single); ++ _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_gte,arg1,arg2); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_gte,arg1,arg2); ++ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_gte,arg1,arg2); ++ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]>=mem[arg2]); ++ if (arg1==arg2) _cimg_mp_return(1); ++ _cimg_mp_scalar2(mp_gte,arg1,arg2); ++ } ++ ++ for (s = se2, ns = se1, ps = se3; s>ss; --s, --ns, --ps) ++ if (*s=='<' && *ns!='<' && *ps!='<' && level[s - expr._data]==clevel) { // Less than ('<') ++ _cimg_mp_op("Operator '<'"); ++ arg1 = compile(ss,s,depth1,0,is_single); ++ arg2 = compile(s + 1,se,depth1,0,is_single); ++ _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_lt,arg1,arg2); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_lt,arg1,arg2); ++ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_lt,arg1,arg2); ++ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]ss; --s, --ns, --ps) ++ if (*s=='>' && *ns!='>' && *ps!='>' && level[s - expr._data]==clevel) { // Greather than ('>') ++ _cimg_mp_op("Operator '>'"); ++ arg1 = compile(ss,s,depth1,0,is_single); ++ arg2 = compile(s + 1,se,depth1,0,is_single); ++ _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_gt,arg1,arg2); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_gt,arg1,arg2); ++ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_gt,arg1,arg2); ++ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]>mem[arg2]); ++ if (arg1==arg2) _cimg_mp_return(0); ++ _cimg_mp_scalar2(mp_gt,arg1,arg2); ++ } ++ ++ for (s = se3, ns = se2; s>ss; --s, --ns) ++ if (*s=='<' && *ns=='<' && level[s - expr._data]==clevel) { // Left bit shift ('<<') ++ _cimg_mp_op("Operator '<<'"); ++ arg1 = compile(ss,s,depth1,0,is_single); ++ arg2 = compile(s + 2,se,depth1,0,is_single); ++ _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) ++ _cimg_mp_vector2_vv(mp_bitwise_left_shift,arg1,arg2); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) { ++ if (!arg2) _cimg_mp_return(arg1); ++ _cimg_mp_vector2_vs(mp_bitwise_left_shift,arg1,arg2); ++ } ++ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) ++ _cimg_mp_vector2_sv(mp_bitwise_left_shift,arg1,arg2); ++ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) ++ _cimg_mp_constant((longT)mem[arg1]<<(unsigned int)mem[arg2]); ++ if (!arg1) _cimg_mp_return(0); ++ if (!arg2) _cimg_mp_return(arg1); ++ _cimg_mp_scalar2(mp_bitwise_left_shift,arg1,arg2); ++ } ++ ++ for (s = se3, ns = se2; s>ss; --s, --ns) ++ if (*s=='>' && *ns=='>' && level[s - expr._data]==clevel) { // Right bit shift ('>>') ++ _cimg_mp_op("Operator '>>'"); ++ arg1 = compile(ss,s,depth1,0,is_single); ++ arg2 = compile(s + 2,se,depth1,0,is_single); ++ _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) ++ _cimg_mp_vector2_vv(mp_bitwise_right_shift,arg1,arg2); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) { ++ if (!arg2) _cimg_mp_return(arg1); ++ _cimg_mp_vector2_vs(mp_bitwise_right_shift,arg1,arg2); ++ } ++ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) ++ _cimg_mp_vector2_sv(mp_bitwise_right_shift,arg1,arg2); ++ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) ++ _cimg_mp_constant((longT)mem[arg1]>>(unsigned int)mem[arg2]); ++ if (!arg1) _cimg_mp_return(0); ++ if (!arg2) _cimg_mp_return(arg1); ++ _cimg_mp_scalar2(mp_bitwise_right_shift,arg1,arg2); ++ } ++ ++ for (ns = se1, s = se2, ps = pexpr._data + (se3 - expr._data); s>ss; --ns, --s, --ps) ++ if (*s=='+' && (*ns!='+' || ns!=se1) && *ps!='-' && *ps!='+' && *ps!='*' && *ps!='/' && *ps!='%' && ++ *ps!='&' && *ps!='|' && *ps!='^' && *ps!='!' && *ps!='~' && *ps!='#' && ++ (*ps!='e' || !(ps - pexpr._data>ss - expr._data && (*(ps - 1)=='.' || (*(ps - 1)>='0' && ++ *(ps - 1)<='9')))) && ++ level[s - expr._data]==clevel) { // Addition ('+') ++ _cimg_mp_op("Operator '+'"); ++ arg1 = compile(ss,s,depth1,0,is_single); ++ arg2 = compile(s + 1,se,depth1,0,is_single); ++ _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); ++ if (!arg2) _cimg_mp_return(arg1); ++ if (!arg1) _cimg_mp_return(arg2); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_add,arg1,arg2); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_add,arg1,arg2); ++ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_add,arg1,arg2); ++ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1] + mem[arg2]); ++ if (code) { // Try to spot linear case 'a*b + c'. ++ CImg &pop = code.back(); ++ if (pop[0]==(ulongT)mp_mul && _cimg_mp_is_comp(pop[1]) && (pop[1]==arg1 || pop[1]==arg2)) { ++ arg3 = (unsigned int)pop[1]; ++ arg4 = (unsigned int)pop[2]; ++ arg5 = (unsigned int)pop[3]; ++ code.remove(); ++ CImg::vector((ulongT)mp_linear_add,arg3,arg4,arg5,arg3==arg2?arg1:arg2).move_to(code); ++ _cimg_mp_return(arg3); ++ } ++ } ++ if (arg2==1) _cimg_mp_scalar1(mp_increment,arg1); ++ if (arg1==1) _cimg_mp_scalar1(mp_increment,arg2); ++ _cimg_mp_scalar2(mp_add,arg1,arg2); ++ } ++ ++ for (ns = se1, s = se2, ps = pexpr._data + (se3 - expr._data); s>ss; --ns, --s, --ps) ++ if (*s=='-' && (*ns!='-' || ns!=se1) && *ps!='-' && *ps!='+' && *ps!='*' && *ps!='/' && *ps!='%' && ++ *ps!='&' && *ps!='|' && *ps!='^' && *ps!='!' && *ps!='~' && *ps!='#' && ++ (*ps!='e' || !(ps - pexpr._data>ss - expr._data && (*(ps - 1)=='.' || (*(ps - 1)>='0' && ++ *(ps - 1)<='9')))) && ++ level[s - expr._data]==clevel) { // Subtraction ('-') ++ _cimg_mp_op("Operator '-'"); ++ arg1 = compile(ss,s,depth1,0,is_single); ++ arg2 = compile(s + 1,se,depth1,0,is_single); ++ _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); ++ if (!arg2) _cimg_mp_return(arg1); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_sub,arg1,arg2); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_sub,arg1,arg2); ++ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) { ++ if (!arg1) _cimg_mp_vector1_v(mp_minus,arg2); ++ _cimg_mp_vector2_sv(mp_sub,arg1,arg2); ++ } ++ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1] - mem[arg2]); ++ if (!arg1) _cimg_mp_scalar1(mp_minus,arg2); ++ if (code) { // Try to spot linear cases 'a*b - c' and 'c - a*b'. ++ CImg &pop = code.back(); ++ if (pop[0]==(ulongT)mp_mul && _cimg_mp_is_comp(pop[1]) && (pop[1]==arg1 || pop[1]==arg2)) { ++ arg3 = (unsigned int)pop[1]; ++ arg4 = (unsigned int)pop[2]; ++ arg5 = (unsigned int)pop[3]; ++ code.remove(); ++ CImg::vector((ulongT)(arg3==arg1?mp_linear_sub_left:mp_linear_sub_right), ++ arg3,arg4,arg5,arg3==arg1?arg2:arg1).move_to(code); ++ _cimg_mp_return(arg3); ++ } ++ } ++ if (arg2==1) _cimg_mp_scalar1(mp_decrement,arg1); ++ _cimg_mp_scalar2(mp_sub,arg1,arg2); ++ } ++ ++ for (s = se3, ns = se2; s>ss; --s, --ns) ++ if (*s=='*' && *ns=='*' && level[s - expr._data]==clevel) { // Complex multiplication ('**') ++ _cimg_mp_op("Operator '**'"); ++ arg1 = compile(ss,s,depth1,0,is_single); ++ arg2 = compile(s + 2,se,depth1,0,is_single); ++ _cimg_mp_check_type(arg1,1,3,2); ++ _cimg_mp_check_type(arg2,2,3,2); ++ if (arg2==1) _cimg_mp_return(arg1); ++ if (arg1==1) _cimg_mp_return(arg2); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) { ++ pos = vector(2); ++ CImg::vector((ulongT)mp_complex_mul,pos,arg1,arg2).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_mul,arg1,arg2); ++ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_mul,arg1,arg2); ++ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]*mem[arg2]); ++ if (!arg1 || !arg2) _cimg_mp_return(0); ++ _cimg_mp_scalar2(mp_mul,arg1,arg2); ++ } ++ ++ for (s = se3, ns = se2; s>ss; --s, --ns) ++ if (*s=='/' && *ns=='/' && level[s - expr._data]==clevel) { // Complex division ('//') ++ _cimg_mp_op("Operator '//'"); ++ arg1 = compile(ss,s,depth1,0,is_single); ++ arg2 = compile(s + 2,se,depth1,0,is_single); ++ _cimg_mp_check_type(arg1,1,3,2); ++ _cimg_mp_check_type(arg2,2,3,2); ++ if (arg2==1) _cimg_mp_return(arg1); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) { ++ pos = vector(2); ++ CImg::vector((ulongT)mp_complex_div_vv,pos,arg1,arg2).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_div,arg1,arg2); ++ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) { ++ pos = vector(2); ++ CImg::vector((ulongT)mp_complex_div_sv,pos,arg1,arg2).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]/mem[arg2]); ++ if (!arg1) _cimg_mp_return(0); ++ _cimg_mp_scalar2(mp_div,arg1,arg2); ++ } ++ ++ for (s = se2; s>ss; --s) if (*s=='*' && level[s - expr._data]==clevel) { // Multiplication ('*') ++ _cimg_mp_op("Operator '*'"); ++ arg1 = compile(ss,s,depth1,0,is_single); ++ arg2 = compile(s + 1,se,depth1,0,is_single); ++ p2 = _cimg_mp_size(arg2); ++ if (p2>0 && _cimg_mp_size(arg1)==p2*p2) { // Particular case of matrix multiplication ++ pos = vector(p2); ++ CImg::vector((ulongT)mp_matrix_mul,pos,arg1,arg2,p2,p2,1).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); ++ if (arg2==1) _cimg_mp_return(arg1); ++ if (arg1==1) _cimg_mp_return(arg2); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_mul,arg1,arg2); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_mul,arg1,arg2); ++ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_mul,arg1,arg2); ++ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]*mem[arg2]); ++ ++ if (code) { // Try to spot double multiplication 'a*b*c'. ++ CImg &pop = code.back(); ++ if (pop[0]==(ulongT)mp_mul && _cimg_mp_is_comp(pop[1]) && (pop[1]==arg1 || pop[1]==arg2)) { ++ arg3 = (unsigned int)pop[1]; ++ arg4 = (unsigned int)pop[2]; ++ arg5 = (unsigned int)pop[3]; ++ code.remove(); ++ CImg::vector((ulongT)mp_mul2,arg3,arg4,arg5,arg3==arg2?arg1:arg2).move_to(code); ++ _cimg_mp_return(arg3); ++ } ++ } ++ if (!arg1 || !arg2) _cimg_mp_return(0); ++ _cimg_mp_scalar2(mp_mul,arg1,arg2); ++ } ++ ++ for (s = se2; s>ss; --s) if (*s=='/' && level[s - expr._data]==clevel) { // Division ('/') ++ _cimg_mp_op("Operator '/'"); ++ arg1 = compile(ss,s,depth1,0,is_single); ++ arg2 = compile(s + 1,se,depth1,0,is_single); ++ _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); ++ if (arg2==1) _cimg_mp_return(arg1); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_div,arg1,arg2); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_div,arg1,arg2); ++ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_div,arg1,arg2); ++ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]/mem[arg2]); ++ if (!arg1) _cimg_mp_return(0); ++ _cimg_mp_scalar2(mp_div,arg1,arg2); ++ } ++ ++ for (s = se2, ns = se1; s>ss; --s, --ns) ++ if (*s=='%' && *ns!='^' && level[s - expr._data]==clevel) { // Modulo ('%') ++ _cimg_mp_op("Operator '%'"); ++ arg1 = compile(ss,s,depth1,0,is_single); ++ arg2 = compile(s + 1,se,depth1,0,is_single); ++ _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_modulo,arg1,arg2); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_modulo,arg1,arg2); ++ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_modulo,arg1,arg2); ++ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) ++ _cimg_mp_constant(cimg::mod(mem[arg1],mem[arg2])); ++ _cimg_mp_scalar2(mp_modulo,arg1,arg2); ++ } ++ ++ if (se1>ss) { ++ if (*ss=='+' && (*ss1!='+' || (ss2='0' && *ss2<='9'))) { // Unary plus ('+') ++ _cimg_mp_op("Operator '+'"); ++ _cimg_mp_return(compile(ss1,se,depth1,0,is_single)); ++ } ++ ++ if (*ss=='-' && (*ss1!='-' || (ss2='0' && *ss2<='9'))) { // Unary minus ('-') ++ _cimg_mp_op("Operator '-'"); ++ arg1 = compile(ss1,se,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_minus,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(-mem[arg1]); ++ _cimg_mp_scalar1(mp_minus,arg1); ++ } ++ ++ if (*ss=='!') { // Logical not ('!') ++ _cimg_mp_op("Operator '!'"); ++ if (*ss1=='!') { // '!!expr' optimized as 'bool(expr)' ++ arg1 = compile(ss2,se,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_bool,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant((bool)mem[arg1]); ++ _cimg_mp_scalar1(mp_bool,arg1); ++ } ++ arg1 = compile(ss1,se,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_logical_not,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(!mem[arg1]); ++ _cimg_mp_scalar1(mp_logical_not,arg1); ++ } ++ ++ if (*ss=='~') { // Bitwise not ('~') ++ _cimg_mp_op("Operator '~'"); ++ arg1 = compile(ss1,se,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_bitwise_not,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(~(unsigned int)mem[arg1]); ++ _cimg_mp_scalar1(mp_bitwise_not,arg1); ++ } ++ } ++ ++ for (s = se3, ns = se2; s>ss; --s, --ns) ++ if (*s=='^' && *ns=='^' && level[s - expr._data]==clevel) { // Complex power ('^^') ++ _cimg_mp_op("Operator '^^'"); ++ arg1 = compile(ss,s,depth1,0,is_single); ++ arg2 = compile(s + 2,se,depth1,0,is_single); ++ _cimg_mp_check_type(arg1,1,3,2); ++ _cimg_mp_check_type(arg2,2,3,2); ++ if (arg2==1) _cimg_mp_return(arg1); ++ pos = vector(2); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) { ++ CImg::vector((ulongT)mp_complex_pow_vv,pos,arg1,arg2).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) { ++ CImg::vector((ulongT)mp_complex_pow_vs,pos,arg1,arg2).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) { ++ CImg::vector((ulongT)mp_complex_pow_sv,pos,arg1,arg2).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ CImg::vector((ulongT)mp_complex_pow_ss,pos,arg1,arg2).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ for (s = se2; s>ss; --s) ++ if (*s=='^' && level[s - expr._data]==clevel) { // Power ('^') ++ _cimg_mp_op("Operator '^'"); ++ arg1 = compile(ss,s,depth1,0,is_single); ++ arg2 = compile(s + 1,se,depth1,0,is_single); ++ _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); ++ if (arg2==1) _cimg_mp_return(arg1); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_pow,arg1,arg2); ++ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_pow,arg1,arg2); ++ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_pow,arg1,arg2); ++ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) ++ _cimg_mp_constant(std::pow(mem[arg1],mem[arg2])); ++ switch (arg2) { ++ case 0 : _cimg_mp_return(1); ++ case 2 : _cimg_mp_scalar1(mp_sqr,arg1); ++ case 3 : _cimg_mp_scalar1(mp_pow3,arg1); ++ case 4 : _cimg_mp_scalar1(mp_pow4,arg1); ++ default : ++ if (_cimg_mp_is_constant(arg2)) { ++ if (mem[arg2]==0.5) { _cimg_mp_scalar1(mp_sqrt,arg1); } ++ else if (mem[arg2]==0.25) { _cimg_mp_scalar1(mp_pow0_25,arg1); } ++ } ++ _cimg_mp_scalar2(mp_pow,arg1,arg2); ++ } ++ } ++ ++ // Percentage computation. ++ if (*se1=='%') { ++ arg1 = compile(ss,se1,depth1,0,is_single); ++ arg2 = _cimg_mp_is_constant(arg1)?0:constant(100); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector2_vs(mp_div,arg1,arg2); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(mem[arg1]/100); ++ _cimg_mp_scalar2(mp_div,arg1,arg2); ++ } ++ ++ is_sth = ss1ss && (*se1=='+' || *se1=='-') && *se2==*se1)) { // Pre/post-decrement and increment ++ if ((is_sth && *ss=='+') || (!is_sth && *se1=='+')) { ++ _cimg_mp_op("Operator '++'"); ++ op = mp_self_increment; ++ } else { ++ _cimg_mp_op("Operator '--'"); ++ op = mp_self_decrement; ++ } ++ ref.assign(7); ++ arg1 = is_sth?compile(ss2,se,depth1,ref,is_single): ++ compile(ss,se2,depth1,ref,is_single); // Variable slot ++ ++ // Apply operator on a copy to prevent modifying a constant or a variable. ++ if (*ref && (_cimg_mp_is_constant(arg1) || _cimg_mp_is_vector(arg1) || _cimg_mp_is_variable(arg1))) { ++ if (_cimg_mp_is_vector(arg1)) arg1 = vector_copy(arg1); ++ else arg1 = scalar1(mp_copy,arg1); ++ } ++ ++ if (is_sth) pos = arg1; // Determine return indice, depending on pre/post action ++ else { ++ if (_cimg_mp_is_vector(arg1)) pos = vector_copy(arg1); ++ else pos = scalar1(mp_copy,arg1); ++ } ++ ++ if (*ref==1) { // Vector value (scalar): V[k]++ ++ arg3 = ref[1]; // Vector slot ++ arg4 = ref[2]; // Index ++ if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); ++ CImg::vector((ulongT)op,arg1,1).move_to(code); ++ CImg::vector((ulongT)mp_vector_set_off,arg1,arg3,(ulongT)_cimg_mp_size(arg3),arg4,arg1). ++ move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (*ref==2) { // Image value (scalar): i/j[_#ind,off]++ ++ if (!is_single) is_parallelizable = false; ++ p1 = ref[1]; // Index ++ is_relative = (bool)ref[2]; ++ arg3 = ref[3]; // Offset ++ if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); ++ CImg::vector((ulongT)op,arg1).move_to(code); ++ if (p1!=~0U) { ++ if (!listout) _cimg_mp_return(pos); ++ CImg::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff), ++ arg1,p1,arg3).move_to(code); ++ } else { ++ if (!imgout) _cimg_mp_return(pos); ++ CImg::vector((ulongT)(is_relative?mp_set_joff:mp_set_ioff), ++ arg1,arg3).move_to(code); ++ } ++ _cimg_mp_return(pos); ++ } ++ ++ if (*ref==3) { // Image value (scalar): i/j(_#ind,_x,_y,_z,_c)++ ++ if (!is_single) is_parallelizable = false; ++ p1 = ref[1]; // Index ++ is_relative = (bool)ref[2]; ++ arg3 = ref[3]; // X ++ arg4 = ref[4]; // Y ++ arg5 = ref[5]; // Z ++ arg6 = ref[6]; // C ++ if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); ++ CImg::vector((ulongT)op,arg1).move_to(code); ++ if (p1!=~0U) { ++ if (!listout) _cimg_mp_return(pos); ++ CImg::vector((ulongT)(is_relative?mp_list_set_jxyzc:mp_list_set_ixyzc), ++ arg1,p1,arg3,arg4,arg5,arg6).move_to(code); ++ } else { ++ if (!imgout) _cimg_mp_return(pos); ++ CImg::vector((ulongT)(is_relative?mp_set_jxyzc:mp_set_ixyzc), ++ arg1,arg3,arg4,arg5,arg6).move_to(code); ++ } ++ _cimg_mp_return(pos); ++ } ++ ++ if (*ref==4) { // Image value (vector): I/J[_#ind,off]++ ++ if (!is_single) is_parallelizable = false; ++ p1 = ref[1]; // Index ++ is_relative = (bool)ref[2]; ++ arg3 = ref[3]; // Offset ++ if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); ++ self_vector_s(arg1,op==mp_self_increment?mp_self_add:mp_self_sub,1); ++ if (p1!=~0U) { ++ if (!listout) _cimg_mp_return(pos); ++ CImg::vector((ulongT)(is_relative?mp_list_set_Joff_v:mp_list_set_Ioff_v), ++ arg1,p1,arg3,_cimg_mp_size(arg1)).move_to(code); ++ } else { ++ if (!imgout) _cimg_mp_return(pos); ++ CImg::vector((ulongT)(is_relative?mp_set_Joff_v:mp_set_Ioff_v), ++ arg1,arg3,_cimg_mp_size(arg1)).move_to(code); ++ } ++ _cimg_mp_return(pos); ++ } ++ ++ if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c)++ ++ if (!is_single) is_parallelizable = false; ++ p1 = ref[1]; // Index ++ is_relative = (bool)ref[2]; ++ arg3 = ref[3]; // X ++ arg4 = ref[4]; // Y ++ arg5 = ref[5]; // Z ++ if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); ++ self_vector_s(arg1,op==mp_self_increment?mp_self_add:mp_self_sub,1); ++ if (p1!=~0U) { ++ if (!listout) _cimg_mp_return(pos); ++ CImg::vector((ulongT)(is_relative?mp_list_set_Jxyz_v:mp_list_set_Ixyz_v), ++ arg1,p1,arg3,arg4,arg5,_cimg_mp_size(arg1)).move_to(code); ++ } else { ++ if (!imgout) _cimg_mp_return(pos); ++ CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), ++ arg1,arg3,arg4,arg5,_cimg_mp_size(arg1)).move_to(code); ++ } ++ _cimg_mp_return(pos); ++ } ++ ++ if (_cimg_mp_is_vector(arg1)) { // Vector variable: V++ ++ self_vector_s(arg1,op==mp_self_increment?mp_self_add:mp_self_sub,1); ++ _cimg_mp_return(pos); ++ } ++ ++ if (_cimg_mp_is_variable(arg1)) { // Scalar variable: s++ ++ CImg::vector((ulongT)op,arg1).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (is_sth) variable_name.assign(ss2,(unsigned int)(se - ss1)); ++ else variable_name.assign(ss,(unsigned int)(se1 - ss)); ++ variable_name.back() = 0; ++ cimg::strpare(variable_name,false,true); ++ *se = saved_char; ++ cimg::strellipsize(variable_name,64); ++ s0 = ss - 4>expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: %s: Invalid %slvalue '%s', " ++ "in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,s_op, ++ _cimg_mp_is_constant(arg1)?"const ":"", ++ variable_name._data, ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } ++ ++ // Array-like access to vectors and image values 'i/j/I/J[_#ind,offset,_boundary]' and 'vector[offset]'. ++ if (*se1==']' && *ss!='[') { ++ _cimg_mp_op("Value accessor '[]'"); ++ is_relative = *ss=='j' || *ss=='J'; ++ s0 = s1 = std::strchr(ss,'['); if (s0) { do { --s1; } while ((signed char)*s1<=' '); cimg::swap(*s0,*++s1); } ++ ++ if ((*ss=='I' || *ss=='J') && *ss1=='[' && ++ (reserved_label[*ss]==~0U || !_cimg_mp_is_vector(reserved_label[*ss]))) { // Image value as a vector ++ if (*ss2=='#') { // Index specified ++ s0 = ss3; while (s0::vector((ulongT)(is_relative?mp_list_Joff:mp_list_Ioff), ++ pos,p1,arg1,arg2==~0U?_cimg_mp_boundary:arg2,p2).move_to(code); ++ } else { ++ need_input_copy = true; ++ CImg::vector((ulongT)(is_relative?mp_Joff:mp_Ioff), ++ pos,arg1,arg2==~0U?_cimg_mp_boundary:arg2,p2).move_to(code); ++ } ++ _cimg_mp_return(pos); ++ } ++ ++ if ((*ss=='i' || *ss=='j') && *ss1=='[' && ++ (reserved_label[*ss]==~0U || !_cimg_mp_is_vector(reserved_label[*ss]))) { // Image value as a scalar ++ if (*ss2=='#') { // Index specified ++ s0 = ss3; while (s0ss && (*s0!='[' || level[s0 - expr._data]!=clevel)) --s0; ++ if (s0>ss) { // Vector value ++ arg1 = compile(ss,s0,depth1,0,is_single); ++ if (_cimg_mp_is_scalar(arg1)) { ++ variable_name.assign(ss,(unsigned int)(s0 - ss + 1)).back() = 0; ++ *se = saved_char; ++ cimg::strellipsize(variable_name,64); ++ s0 = ss - 4>expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: %s: Array brackets used on non-vector variable '%s', " ++ "in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,s_op, ++ variable_name._data, ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ ++ } ++ s1 = s0 + 1; while (s1 sub-vector extraction ++ p1 = _cimg_mp_size(arg1); ++ arg2 = compile(++s0,s1,depth1,0,is_single); // Starting indice ++ arg3 = compile(++s1,se1,depth1,0,is_single); // Length ++ _cimg_mp_check_constant(arg3,2,3); ++ arg3 = (unsigned int)mem[arg3]; ++ pos = vector(arg3); ++ CImg::vector((ulongT)mp_vector_crop,pos,arg1,p1,arg2,arg3).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ // One argument -> vector value reference ++ arg2 = compile(++s0,se1,depth1,0,is_single); ++ if (_cimg_mp_is_constant(arg2)) { // Constant index ++ nb = (int)mem[arg2]; ++ if (nb>=0 && nb<(int)_cimg_mp_size(arg1)) _cimg_mp_return(arg1 + 1 + nb); ++ variable_name.assign(ss,(unsigned int)(s0 - ss)).back() = 0; ++ *se = saved_char; ++ cimg::strellipsize(variable_name,64); ++ s0 = ss - 4>expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: Out-of-bounds reference '%s[%d]' " ++ "(vector '%s' has dimension %u), " ++ "in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function, ++ variable_name._data,nb, ++ variable_name._data,_cimg_mp_size(arg1), ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } ++ if (p_ref) { ++ *p_ref = 1; ++ p_ref[1] = arg1; ++ p_ref[2] = arg2; ++ if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -2; // Prevent from being used in further optimization ++ } ++ pos = scalar3(mp_vector_off,arg1,_cimg_mp_size(arg1),arg2); ++ memtype[pos] = -2; // Prevent from being used in further optimization ++ _cimg_mp_return(pos); ++ } ++ } ++ ++ // Look for a function call, an access to image value, or a parenthesis. ++ if (*se1==')') { ++ if (*ss=='(') _cimg_mp_return(compile(ss1,se1,depth1,p_ref,is_single)); // Simple parentheses ++ _cimg_mp_op("Value accessor '()'"); ++ is_relative = *ss=='j' || *ss=='J'; ++ s0 = s1 = std::strchr(ss,'('); if (s0) { do { --s1; } while ((signed char)*s1<=' '); cimg::swap(*s0,*++s1); } ++ ++ // I/J(_#ind,_x,_y,_z,_interpolation,_boundary_conditions) ++ if ((*ss=='I' || *ss=='J') && *ss1=='(') { // Image value as scalar ++ if (*ss2=='#') { // Index specified ++ s0 = ss3; while (s01) { ++ arg2 = arg1 + 1; ++ if (p2>2) arg3 = arg2 + 1; ++ } ++ if (s1::vector((ulongT)(is_relative?mp_list_Jxyz:mp_list_Ixyz), ++ pos,p1,arg1,arg2,arg3, ++ arg4==~0U?_cimg_mp_interpolation:arg4, ++ arg5==~0U?_cimg_mp_boundary:arg5,p2).move_to(code); ++ else { ++ need_input_copy = true; ++ CImg::vector((ulongT)(is_relative?mp_Jxyz:mp_Ixyz), ++ pos,arg1,arg2,arg3, ++ arg4==~0U?_cimg_mp_interpolation:arg4, ++ arg5==~0U?_cimg_mp_boundary:arg5,p2).move_to(code); ++ } ++ _cimg_mp_return(pos); ++ } ++ ++ // i/j(_#ind,_x,_y,_z,_c,_interpolation,_boundary_conditions) ++ if ((*ss=='i' || *ss=='j') && *ss1=='(') { // Image value as scalar ++ if (*ss2=='#') { // Index specified ++ s0 = ss3; while (s01) { ++ arg2 = arg1 + 1; ++ if (p2>2) { ++ arg3 = arg2 + 1; ++ if (p2>3) arg4 = arg3 + 1; ++ } ++ } ++ if (s1::vector((ulongT)mp_arg,0,0,p2,arg1,arg2).move_to(_opcode); ++ for (s = ++s2; s::vector(arg3).move_to(_opcode); ++ ++p3; ++ s = ns; ++ } ++ (_opcode>'y').move_to(opcode); ++ opcode[2] = opcode._height; ++ if (_cimg_mp_is_constant(arg1)) { ++ p3-=1; // Number of args ++ arg1 = (unsigned int)(mem[arg1]<0?mem[arg1] + p3:mem[arg1]); ++ if (arg1::vector((ulongT)mp_break,_cimg_mp_slot_nan).move_to(code); ++ _cimg_mp_return_nan(); ++ } ++ } ++ ++ if (!std::strncmp(ss,"breakpoint(",11)) { // Break point (for abort test) ++ _cimg_mp_op("Function 'breakpoint()'"); ++ if (pexpr[se2 - expr._data]=='(') { // no arguments? ++ CImg::vector((ulongT)mp_breakpoint,_cimg_mp_slot_nan).move_to(code); ++ _cimg_mp_return_nan(); ++ } ++ } ++ break; ++ ++ case 'c' : ++ if (!std::strncmp(ss,"cabs(",5)) { // Complex absolute value ++ _cimg_mp_op("Function 'cabs()'"); ++ arg1 = compile(ss5,se1,depth1,0,is_single); ++ _cimg_mp_check_type(arg1,0,2,2); ++ _cimg_mp_scalar2(mp_complex_abs,arg1 + 1,arg1 + 2); ++ } ++ ++ if (!std::strncmp(ss,"carg(",5)) { // Complex argument ++ _cimg_mp_op("Function 'carg()'"); ++ arg1 = compile(ss5,se1,depth1,0,is_single); ++ _cimg_mp_check_type(arg1,0,2,2); ++ _cimg_mp_scalar2(mp_atan2,arg1 + 2,arg1 + 1); ++ } ++ ++ if (!std::strncmp(ss,"cats(",5)) { // Concatenate strings ++ _cimg_mp_op("Function 'cats()'"); ++ CImg::vector((ulongT)mp_cats,0,0,0).move_to(_opcode); ++ arg1 = 0; ++ for (s = ss5; s::vector(arg1,_cimg_mp_size(arg1)).move_to(_opcode); ++ s = ns; ++ } ++ _cimg_mp_check_constant(arg1,1,3); // Last argument = output vector size ++ _opcode.remove(); ++ (_opcode>'y').move_to(opcode); ++ p1 = (unsigned int)mem[arg1]; ++ pos = vector(p1); ++ opcode[1] = pos; ++ opcode[2] = p1; ++ opcode[3] = opcode._height; ++ opcode.move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (!std::strncmp(ss,"cbrt(",5)) { // Cubic root ++ _cimg_mp_op("Function 'cbrt()'"); ++ arg1 = compile(ss5,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_cbrt,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::cbrt(mem[arg1])); ++ _cimg_mp_scalar1(mp_cbrt,arg1); ++ } ++ ++ if (!std::strncmp(ss,"cconj(",6)) { // Complex conjugate ++ _cimg_mp_op("Function 'cconj()'"); ++ arg1 = compile(ss6,se1,depth1,0,is_single); ++ _cimg_mp_check_type(arg1,0,2,2); ++ pos = vector(2); ++ CImg::vector((ulongT)mp_complex_conj,pos,arg1).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (!std::strncmp(ss,"ceil(",5)) { // Ceil ++ _cimg_mp_op("Function 'ceil()'"); ++ arg1 = compile(ss5,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_ceil,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::ceil(mem[arg1])); ++ _cimg_mp_scalar1(mp_ceil,arg1); ++ } ++ ++ if (!std::strncmp(ss,"cexp(",5)) { // Complex exponential ++ _cimg_mp_op("Function 'cexp()'"); ++ arg1 = compile(ss5,se1,depth1,0,is_single); ++ _cimg_mp_check_type(arg1,0,2,2); ++ pos = vector(2); ++ CImg::vector((ulongT)mp_complex_exp,pos,arg1).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (!std::strncmp(ss,"clog(",5)) { // Complex logarithm ++ _cimg_mp_op("Function 'clog()'"); ++ arg1 = compile(ss5,se1,depth1,0,is_single); ++ _cimg_mp_check_type(arg1,0,2,2); ++ pos = vector(2); ++ CImg::vector((ulongT)mp_complex_log,pos,arg1).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (!std::strncmp(ss,"continue(",9)) { // Complex absolute value ++ if (pexpr[se2 - expr._data]=='(') { // no arguments? ++ CImg::vector((ulongT)mp_continue,_cimg_mp_slot_nan).move_to(code); ++ _cimg_mp_return_nan(); ++ } ++ } ++ ++ if (!std::strncmp(ss,"copy(",5)) { // Memory copy ++ _cimg_mp_op("Function 'copy()'"); ++ ref.assign(14); ++ s1 = ss5; while (s1(1,22).move_to(code); ++ code.back().get_shared_rows(0,7).fill((ulongT)mp_memcopy,p1,arg1,arg2,arg3,arg4,arg5,arg6); ++ code.back().get_shared_rows(8,21).fill(ref); ++ _cimg_mp_return(p1); ++ } ++ ++ if (!std::strncmp(ss,"cos(",4)) { // Cosine ++ _cimg_mp_op("Function 'cos()'"); ++ arg1 = compile(ss4,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_cos,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::cos(mem[arg1])); ++ _cimg_mp_scalar1(mp_cos,arg1); ++ } ++ ++ if (!std::strncmp(ss,"cosh(",5)) { // Hyperbolic cosine ++ _cimg_mp_op("Function 'cosh()'"); ++ arg1 = compile(ss5,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_cosh,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::cosh(mem[arg1])); ++ _cimg_mp_scalar1(mp_cosh,arg1); ++ } ++ ++ if (!std::strncmp(ss,"critical(",9)) { // Critical section (single thread at a time) ++ _cimg_mp_op("Function 'critical()'"); ++ p1 = code._width; ++ arg1 = compile(ss + 9,se1,depth1,p_ref,true); ++ CImg::vector((ulongT)mp_critical,arg1,code._width - p1).move_to(code,p1); ++ _cimg_mp_return(arg1); ++ } ++ ++ if (!std::strncmp(ss,"crop(",5)) { // Image crop ++ _cimg_mp_op("Function 'crop()'"); ++ if (*ss5=='#') { // Index specified ++ s0 = ss6; while (s0::sequence(_cimg_mp_size(arg1),arg1 + 1, ++ arg1 + (ulongT)_cimg_mp_size(arg1)); ++ opcode.resize(1,std::min(opcode._height,4U),1,1,0).move_to(_opcode); ++ is_sth = true; ++ } else { ++ _cimg_mp_check_type(arg1,pos + 1,1,0); ++ CImg::vector(arg1).move_to(_opcode); ++ } ++ s = ns; ++ } ++ (_opcode>'y').move_to(opcode); ++ ++ arg1 = 0; arg2 = (p1!=~0U); ++ switch (opcode._height) { ++ case 0 : case 1 : ++ CImg::vector(0,0,0,0,~0U,~0U,~0U,~0U,0).move_to(opcode); ++ break; ++ case 2 : ++ CImg::vector(*opcode,0,0,0,opcode[1],~0U,~0U,~0U,_cimg_mp_boundary).move_to(opcode); ++ arg1 = arg2?3:2; ++ break; ++ case 3 : ++ CImg::vector(*opcode,0,0,0,opcode[1],~0U,~0U,~0U,opcode[2]).move_to(opcode); ++ arg1 = arg2?3:2; ++ break; ++ case 4 : ++ CImg::vector(*opcode,opcode[1],0,0,opcode[2],opcode[3],~0U,~0U,_cimg_mp_boundary). ++ move_to(opcode); ++ arg1 = (is_sth?2:1) + arg2; ++ break; ++ case 5 : ++ CImg::vector(*opcode,opcode[1],0,0,opcode[2],opcode[3],~0U,~0U,opcode[4]). ++ move_to(opcode); ++ arg1 = (is_sth?2:1) + arg2; ++ break; ++ case 6 : ++ CImg::vector(*opcode,opcode[1],opcode[2],0,opcode[3],opcode[4],opcode[5],~0U, ++ _cimg_mp_boundary).move_to(opcode); ++ arg1 = (is_sth?2:4) + arg2; ++ break; ++ case 7 : ++ CImg::vector(*opcode,opcode[1],opcode[2],0,opcode[3],opcode[4],opcode[5],~0U, ++ opcode[6]).move_to(opcode); ++ arg1 = (is_sth?2:4) + arg2; ++ break; ++ case 8 : ++ CImg::vector(*opcode,opcode[1],opcode[2],opcode[3],opcode[4],opcode[5],opcode[6], ++ opcode[7],_cimg_mp_boundary).move_to(opcode); ++ arg1 = (is_sth?2:5) + arg2; ++ break; ++ case 9 : ++ arg1 = (is_sth?2:5) + arg2; ++ break; ++ default : // Error -> too much arguments ++ *se = saved_char; ++ s0 = ss - 4>expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: %s: Too much arguments specified, " ++ "in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,s_op, ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } ++ ++ _cimg_mp_check_type((unsigned int)*opcode,arg2 + 1,1,0); ++ _cimg_mp_check_type((unsigned int)opcode[1],arg2 + 1 + (is_sth?0:1),1,0); ++ _cimg_mp_check_type((unsigned int)opcode[2],arg2 + 1 + (is_sth?0:2),1,0); ++ _cimg_mp_check_type((unsigned int)opcode[3],arg2 + 1 + (is_sth?0:3),1,0); ++ if (opcode[4]!=(ulongT)~0U) { ++ _cimg_mp_check_constant((unsigned int)opcode[4],arg1,3); ++ opcode[4] = (ulongT)mem[opcode[4]]; ++ } ++ if (opcode[5]!=(ulongT)~0U) { ++ _cimg_mp_check_constant((unsigned int)opcode[5],arg1 + 1,3); ++ opcode[5] = (ulongT)mem[opcode[5]]; ++ } ++ if (opcode[6]!=(ulongT)~0U) { ++ _cimg_mp_check_constant((unsigned int)opcode[6],arg1 + 2,3); ++ opcode[6] = (ulongT)mem[opcode[6]]; ++ } ++ if (opcode[7]!=(ulongT)~0U) { ++ _cimg_mp_check_constant((unsigned int)opcode[7],arg1 + 3,3); ++ opcode[7] = (ulongT)mem[opcode[7]]; ++ } ++ _cimg_mp_check_type((unsigned int)opcode[8],arg1 + 4,1,0); ++ ++ if (opcode[4]==(ulongT)~0U || opcode[5]==(ulongT)~0U || ++ opcode[6]==(ulongT)~0U || opcode[7]==(ulongT)~0U) { ++ if (p1!=~0U) { ++ _cimg_mp_check_constant(p1,1,1); ++ p1 = (unsigned int)cimg::mod((int)mem[p1],listin.width()); ++ } ++ const CImg &img = p1!=~0U?listin[p1]:imgin; ++ if (!img) { ++ *se = saved_char; ++ s0 = ss - 4>expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: %s: Cannot crop empty image when " ++ "some xyzc-coordinates are unspecified, in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,s_op, ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } ++ if (opcode[4]==(ulongT)~0U) opcode[4] = (ulongT)img._width; ++ if (opcode[5]==(ulongT)~0U) opcode[5] = (ulongT)img._height; ++ if (opcode[6]==(ulongT)~0U) opcode[6] = (ulongT)img._depth; ++ if (opcode[7]==(ulongT)~0U) opcode[7] = (ulongT)img._spectrum; ++ } ++ ++ pos = vector((unsigned int)(opcode[4]*opcode[5]*opcode[6]*opcode[7])); ++ CImg::vector((ulongT)mp_crop, ++ pos,p1, ++ *opcode,opcode[1],opcode[2],opcode[3], ++ opcode[4],opcode[5],opcode[6],opcode[7], ++ opcode[8]).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (!std::strncmp(ss,"cross(",6)) { // Cross product ++ _cimg_mp_op("Function 'cross()'"); ++ s1 = ss6; while (s1::vector((ulongT)mp_cross,pos,arg1,arg2).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (!std::strncmp(ss,"cut(",4)) { // Cut ++ _cimg_mp_op("Function 'cut()'"); ++ s1 = ss4; while (s1val2?val2:val); ++ } ++ _cimg_mp_scalar3(mp_cut,arg1,arg2,arg3); ++ } ++ break; ++ ++ case 'd' : ++ if (*ss1=='(') { // Image depth ++ _cimg_mp_op("Function 'd()'"); ++ if (*ss2=='#') { // Index specified ++ p1 = compile(ss3,se1,depth1,0,is_single); ++ _cimg_mp_check_list(false); ++ } else { if (ss2!=se1) break; p1 = ~0U; } ++ pos = scalar(); ++ CImg::vector((ulongT)mp_image_d,pos,p1).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (!std::strncmp(ss,"date(",5)) { // Current date or file date ++ _cimg_mp_op("Function 'date()'"); ++ s1 = ss5; while (s1::string(s1,true,true).unroll('y'),true); ++ cimg::strpare(variable_name,false,true); ++ ((CImg::vector((ulongT)mp_date,pos,0,arg1,_cimg_mp_size(pos)),variable_name)>'y'). ++ move_to(opcode); ++ *se1 = ')'; ++ } else ++ CImg::vector((ulongT)mp_date,pos,0,arg1,_cimg_mp_size(pos)).move_to(opcode); ++ opcode[2] = opcode._height; ++ opcode.move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (!std::strncmp(ss,"debug(",6)) { // Print debug info ++ _cimg_mp_op("Function 'debug()'"); ++ p1 = code._width; ++ arg1 = compile(ss6,se1,depth1,p_ref,is_single); ++ *se1 = 0; ++ variable_name.assign(CImg::string(ss6,true,true).unroll('y'),true); ++ cimg::strpare(variable_name,false,true); ++ ((CImg::vector((ulongT)mp_debug,arg1,0,code._width - p1), ++ variable_name)>'y').move_to(opcode); ++ opcode[2] = opcode._height; ++ opcode.move_to(code,p1); ++ *se1 = ')'; ++ _cimg_mp_return(arg1); ++ } ++ ++ if (!std::strncmp(ss,"display(",8)) { // Display memory, vector or image ++ _cimg_mp_op("Function 'display()'"); ++ if (pexpr[se2 - expr._data]=='(') { // no arguments? ++ CImg::vector((ulongT)mp_display_memory,_cimg_mp_slot_nan).move_to(code); ++ _cimg_mp_return_nan(); ++ } ++ if (*ss8!='#') { // Vector ++ s1 = ss8; while (s1::string(ss8,true,true).unroll('y'),true); ++ cimg::strpare(variable_name,false,true); ++ if (_cimg_mp_is_vector(arg1)) ++ ((CImg::vector((ulongT)mp_vector_print,arg1,0,(ulongT)_cimg_mp_size(arg1),0), ++ variable_name)>'y').move_to(opcode); ++ else ++ ((CImg::vector((ulongT)mp_print,arg1,0,0), ++ variable_name)>'y').move_to(opcode); ++ opcode[2] = opcode._height; ++ opcode.move_to(code); ++ ++ ((CImg::vector((ulongT)mp_display,arg1,0,(ulongT)_cimg_mp_size(arg1), ++ arg2,arg3,arg4,arg5), ++ variable_name)>'y').move_to(opcode); ++ opcode[2] = opcode._height; ++ opcode.move_to(code); ++ *s1 = c1; ++ _cimg_mp_return(arg1); ++ ++ } else { // Image ++ p1 = compile(ss8 + 1,se1,depth1,0,is_single); ++ _cimg_mp_check_list(true); ++ CImg::vector((ulongT)mp_image_display,_cimg_mp_slot_nan,p1).move_to(code); ++ _cimg_mp_return_nan(); ++ } ++ } ++ ++ if (!std::strncmp(ss,"det(",4)) { // Matrix determinant ++ _cimg_mp_op("Function 'det()'"); ++ arg1 = compile(ss4,se1,depth1,0,is_single); ++ _cimg_mp_check_matrix_square(arg1,1); ++ p1 = (unsigned int)std::sqrt((float)_cimg_mp_size(arg1)); ++ _cimg_mp_scalar2(mp_det,arg1,p1); ++ } ++ ++ if (!std::strncmp(ss,"diag(",5)) { // Diagonal matrix ++ _cimg_mp_op("Function 'diag()'"); ++ arg1 = compile(ss5,se1,depth1,0,is_single); ++ if (_cimg_mp_is_scalar(arg1)) _cimg_mp_return(arg1); ++ p1 = _cimg_mp_size(arg1); ++ pos = vector(p1*p1); ++ CImg::vector((ulongT)mp_diag,pos,arg1,p1).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (!std::strncmp(ss,"dot(",4)) { // Dot product ++ _cimg_mp_op("Function 'dot()'"); ++ s1 = ss4; while (s1::vector((ulongT)mp_dowhile,p1,p2,arg2 - arg1,code._width - arg2,_cimg_mp_size(p1), ++ p1>=arg6 && !_cimg_mp_is_constant(p1), ++ p2>=arg6 && !_cimg_mp_is_constant(p2)).move_to(code,arg1); ++ _cimg_mp_return(p1); ++ } ++ ++ if (!std::strncmp(ss,"draw(",5)) { // Draw image ++ if (!is_single) is_parallelizable = false; ++ _cimg_mp_op("Function 'draw()'"); ++ if (*ss5=='#') { // Index specified ++ s0 = ss6; while (s01) { ++ arg3 = arg2 + 1; ++ if (p2>2) { ++ arg4 = arg3 + 1; ++ if (p2>3) arg5 = arg4 + 1; ++ } ++ } ++ ++s0; ++ is_sth = true; ++ } else { ++ if (s0::vector((ulongT)mp_draw,arg1,(ulongT)_cimg_mp_size(arg1),p1,arg2,arg3,arg4,arg5, ++ 0,0,0,0,1,(ulongT)~0U,0,1).move_to(opcode); ++ ++ arg2 = arg3 = arg4 = arg5 = ~0U; ++ p2 = p1!=~0U?0:1; ++ if (s0::vector((ulongT)mp_echo,_cimg_mp_slot_nan,0).move_to(_opcode); ++ for (s = ss5; s::vector(arg1,_cimg_mp_size(arg1)).move_to(_opcode); ++ s = ns; ++ } ++ (_opcode>'y').move_to(opcode); ++ opcode[2] = opcode._height; ++ opcode.move_to(code); ++ _cimg_mp_return_nan(); ++ } ++ ++ if (!std::strncmp(ss,"eig(",4)) { // Matrix eigenvalues/eigenvector ++ _cimg_mp_op("Function 'eig()'"); ++ arg1 = compile(ss4,se1,depth1,0,is_single); ++ _cimg_mp_check_matrix_square(arg1,1); ++ p1 = (unsigned int)std::sqrt((float)_cimg_mp_size(arg1)); ++ pos = vector((p1 + 1)*p1); ++ CImg::vector((ulongT)mp_matrix_eig,pos,arg1,p1).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (!std::strncmp(ss,"end(",4)) { // End ++ _cimg_mp_op("Function 'end()'"); ++ code.swap(code_end); ++ compile(ss4,se1,depth1,p_ref,true); ++ code.swap(code_end); ++ _cimg_mp_return_nan(); ++ } ++ ++ if (!std::strncmp(ss,"ext(",4)) { // Extern ++ _cimg_mp_op("Function 'ext()'"); ++ if (!is_single) is_parallelizable = false; ++ CImg::vector((ulongT)mp_ext,0,0).move_to(_opcode); ++ pos = 1; ++ for (s = ss4; s::vector(arg1,_cimg_mp_size(arg1)).move_to(_opcode); ++ s = ns; ++ } ++ (_opcode>'y').move_to(opcode); ++ pos = scalar(); ++ opcode[1] = pos; ++ opcode[2] = opcode._height; ++ opcode.move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (!std::strncmp(ss,"exp(",4)) { // Exponential ++ _cimg_mp_op("Function 'exp()'"); ++ arg1 = compile(ss4,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_exp,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::exp(mem[arg1])); ++ _cimg_mp_scalar1(mp_exp,arg1); ++ } ++ ++ if (!std::strncmp(ss,"eye(",4)) { // Identity matrix ++ _cimg_mp_op("Function 'eye()'"); ++ arg1 = compile(ss4,se1,depth1,0,is_single); ++ _cimg_mp_check_constant(arg1,1,3); ++ p1 = (unsigned int)mem[arg1]; ++ pos = vector(p1*p1); ++ CImg::vector((ulongT)mp_eye,pos,p1).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ break; ++ ++ case 'f' : ++ if (!std::strncmp(ss,"fact(",5)) { // Factorial ++ _cimg_mp_op("Function 'fact()'"); ++ arg1 = compile(ss5,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_factorial,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::factorial(mem[arg1])); ++ _cimg_mp_scalar1(mp_factorial,arg1); ++ } ++ ++ if (!std::strncmp(ss,"fibo(",5)) { // Fibonacci ++ _cimg_mp_op("Function 'fibo()'"); ++ arg1 = compile(ss5,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_fibonacci,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::fibonacci(mem[arg1])); ++ _cimg_mp_scalar1(mp_fibonacci,arg1); ++ } ++ ++ if (!std::strncmp(ss,"find(",5)) { // Find ++ _cimg_mp_op("Function 'find()'"); ++ ++ // First argument: data to look at. ++ s0 = ss5; while (s0::vector((ulongT)mp_for,p3,(ulongT)_cimg_mp_size(p3),p2,arg2 - arg1,arg3 - arg2, ++ arg4 - arg3,code._width - arg4, ++ p3>=arg6 && !_cimg_mp_is_constant(p3), ++ p2>=arg6 && !_cimg_mp_is_constant(p2)).move_to(code,arg1); ++ _cimg_mp_return(p3); ++ } ++ ++ if (!std::strncmp(ss,"floor(",6)) { // Floor ++ _cimg_mp_op("Function 'floor()'"); ++ arg1 = compile(ss6,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_floor,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::floor(mem[arg1])); ++ _cimg_mp_scalar1(mp_floor,arg1); ++ } ++ ++ if (!std::strncmp(ss,"fsize(",6)) { // File size ++ _cimg_mp_op("Function 'fsize()'"); ++ *se1 = 0; ++ variable_name.assign(CImg::string(ss6,true,true).unroll('y'),true); ++ cimg::strpare(variable_name,false,true); ++ pos = scalar(); ++ ((CImg::vector((ulongT)mp_fsize,pos,0),variable_name)>'y').move_to(opcode); ++ *se1 = ')'; ++ opcode[2] = opcode._height; ++ opcode.move_to(code); ++ _cimg_mp_return(pos); ++ } ++ break; ++ ++ case 'g' : ++ if (!std::strncmp(ss,"gauss(",6)) { // Gaussian function ++ _cimg_mp_op("Function 'gauss()'"); ++ s1 = ss6; while (s1::vector((ulongT)mp_image_h,pos,p1).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ break; ++ ++ case 'i' : ++ if (*ss1=='c' && *ss2=='(') { // Image median ++ _cimg_mp_op("Function 'ic()'"); ++ if (*ss3=='#') { // Index specified ++ p1 = compile(ss4,se1,depth1,0,is_single); ++ _cimg_mp_check_list(false); ++ } else { if (ss3!=se1) break; p1 = ~0U; } ++ pos = scalar(); ++ CImg::vector((ulongT)mp_image_median,pos,p1).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (*ss1=='f' && *ss2=='(') { // If..then[..else.] ++ _cimg_mp_op("Function 'if()'"); ++ s1 = ss3; while (s1::vector((ulongT)mp_if,pos,arg1,arg2,arg3, ++ p3 - p2,code._width - p3,arg4).move_to(code,p2); ++ _cimg_mp_return(pos); ++ } ++ ++ if (!std::strncmp(ss,"init(",5)) { // Init ++ _cimg_mp_op("Function 'init()'"); ++ code.swap(code_init); ++ arg1 = compile(ss5,se1,depth1,p_ref,true); ++ code.swap(code_init); ++ _cimg_mp_return(arg1); ++ } ++ ++ if (!std::strncmp(ss,"int(",4)) { // Integer cast ++ _cimg_mp_op("Function 'int()'"); ++ arg1 = compile(ss4,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_int,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant((longT)mem[arg1]); ++ _cimg_mp_scalar1(mp_int,arg1); ++ } ++ ++ if (!std::strncmp(ss,"inv(",4)) { // Matrix/scalar inversion ++ _cimg_mp_op("Function 'inv()'"); ++ arg1 = compile(ss4,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) { ++ _cimg_mp_check_matrix_square(arg1,1); ++ p1 = (unsigned int)std::sqrt((float)_cimg_mp_size(arg1)); ++ pos = vector(p1*p1); ++ CImg::vector((ulongT)mp_matrix_inv,pos,arg1,p1).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(1/mem[arg1]); ++ _cimg_mp_scalar2(mp_div,1,arg1); ++ } ++ ++ if (*ss1=='s') { // Family of 'is_?()' functions ++ ++ if (!std::strncmp(ss,"isbool(",7)) { // Is boolean? ++ _cimg_mp_op("Function 'isbool()'"); ++ if (ss7==se1) _cimg_mp_return(0); ++ arg1 = compile(ss7,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_isbool,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_return(mem[arg1]==0.0 || mem[arg1]==1.0); ++ _cimg_mp_scalar1(mp_isbool,arg1); ++ } ++ ++ if (!std::strncmp(ss,"isdir(",6)) { // Is directory? ++ _cimg_mp_op("Function 'isdir()'"); ++ *se1 = 0; ++ is_sth = cimg::is_directory(ss6); ++ *se1 = ')'; ++ _cimg_mp_return(is_sth?1U:0U); ++ } ++ ++ if (!std::strncmp(ss,"isfile(",7)) { // Is file? ++ _cimg_mp_op("Function 'isfile()'"); ++ *se1 = 0; ++ is_sth = cimg::is_file(ss7); ++ *se1 = ')'; ++ _cimg_mp_return(is_sth?1U:0U); ++ } ++ ++ if (!std::strncmp(ss,"isin(",5)) { // Is in sequence/vector? ++ if (ss5>=se1) _cimg_mp_return(0); ++ _cimg_mp_op("Function 'isin()'"); ++ pos = scalar(); ++ CImg::vector((ulongT)mp_isin,pos,0).move_to(_opcode); ++ for (s = ss5; s::sequence(_cimg_mp_size(arg1),arg1 + 1, ++ arg1 + (ulongT)_cimg_mp_size(arg1)). ++ move_to(_opcode); ++ else CImg::vector(arg1).move_to(_opcode); ++ s = ns; ++ } ++ (_opcode>'y').move_to(opcode); ++ opcode[2] = opcode._height; ++ opcode.move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (!std::strncmp(ss,"isinf(",6)) { // Is infinite? ++ _cimg_mp_op("Function 'isinf()'"); ++ if (ss6==se1) _cimg_mp_return(0); ++ arg1 = compile(ss6,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_isinf,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_return((unsigned int)cimg::type::is_inf(mem[arg1])); ++ _cimg_mp_scalar1(mp_isinf,arg1); ++ } ++ ++ if (!std::strncmp(ss,"isint(",6)) { // Is integer? ++ _cimg_mp_op("Function 'isint()'"); ++ if (ss6==se1) _cimg_mp_return(0); ++ arg1 = compile(ss6,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_isint,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_return((unsigned int)(cimg::mod(mem[arg1],1.0)==0)); ++ _cimg_mp_scalar1(mp_isint,arg1); ++ } ++ ++ if (!std::strncmp(ss,"isnan(",6)) { // Is NaN? ++ _cimg_mp_op("Function 'isnan()'"); ++ if (ss6==se1) _cimg_mp_return(0); ++ arg1 = compile(ss6,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_isnan,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_return((unsigned int)cimg::type::is_nan(mem[arg1])); ++ _cimg_mp_scalar1(mp_isnan,arg1); ++ } ++ ++ if (!std::strncmp(ss,"isval(",6)) { // Is value? ++ _cimg_mp_op("Function 'isval()'"); ++ val = 0; ++ if (cimg_sscanf(ss6,"%lf%c%c",&val,&sep,&end)==2 && sep==')') _cimg_mp_return(1); ++ _cimg_mp_return(0); ++ } ++ ++ } ++ break; ++ ++ case 'l' : ++ if (*ss1=='(') { // Size of image list ++ _cimg_mp_op("Function 'l()'"); ++ if (ss2!=se1) break; ++ _cimg_mp_scalar0(mp_list_l); ++ } ++ ++ if (!std::strncmp(ss,"log(",4)) { // Natural logarithm ++ _cimg_mp_op("Function 'log()'"); ++ arg1 = compile(ss4,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_log,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::log(mem[arg1])); ++ _cimg_mp_scalar1(mp_log,arg1); ++ } ++ ++ if (!std::strncmp(ss,"log2(",5)) { // Base-2 logarithm ++ _cimg_mp_op("Function 'log2()'"); ++ arg1 = compile(ss5,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_log2,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::log2(mem[arg1])); ++ _cimg_mp_scalar1(mp_log2,arg1); ++ } ++ ++ if (!std::strncmp(ss,"log10(",6)) { // Base-10 logarithm ++ _cimg_mp_op("Function 'log10()'"); ++ arg1 = compile(ss6,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_log10,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::log10(mem[arg1])); ++ _cimg_mp_scalar1(mp_log10,arg1); ++ } ++ ++ if (!std::strncmp(ss,"lowercase(",10)) { // Lower case ++ _cimg_mp_op("Function 'lowercase()'"); ++ arg1 = compile(ss + 10,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_lowercase,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::lowercase(mem[arg1])); ++ _cimg_mp_scalar1(mp_lowercase,arg1); ++ } ++ break; ++ ++ case 'm' : ++ if (!std::strncmp(ss,"mul(",4)) { // Matrix multiplication ++ _cimg_mp_op("Function 'mul()'"); ++ s1 = ss4; while (s1expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: %s: Types of first and second arguments ('%s' and '%s') " ++ "do not match with third argument 'nb_colsB=%u', " ++ "in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,s_op, ++ s_type(arg1)._data,s_type(arg2)._data,p3, ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } ++ pos = vector(arg4*p3); ++ CImg::vector((ulongT)mp_matrix_mul,pos,arg1,arg2,arg4,arg5,p3).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ break; ++ ++ case 'n' : ++ if (!std::strncmp(ss,"narg(",5)) { // Number of arguments ++ _cimg_mp_op("Function 'narg()'"); ++ if (ss5>=se1) _cimg_mp_return(0); ++ arg1 = 0; ++ for (s = ss5; s::vector((ulongT)mp_norm0,pos,0).move_to(_opcode); break; ++ case 1 : ++ CImg::vector((ulongT)mp_norm1,pos,0).move_to(_opcode); break; ++ case 2 : ++ CImg::vector((ulongT)mp_norm2,pos,0).move_to(_opcode); break; ++ case ~0U : ++ CImg::vector((ulongT)mp_norminf,pos,0).move_to(_opcode); break; ++ default : ++ CImg::vector((ulongT)mp_normp,pos,0,(ulongT)(arg1==~0U?-1:(int)arg1)). ++ move_to(_opcode); ++ } ++ for ( ; s::sequence(_cimg_mp_size(arg2),arg2 + 1, ++ arg2 + (ulongT)_cimg_mp_size(arg2)). ++ move_to(_opcode); ++ else CImg::vector(arg2).move_to(_opcode); ++ s = ns; ++ } ++ ++ (_opcode>'y').move_to(opcode); ++ if (arg1>0 && opcode._height==4) // Special case with one argument and p>=1 ++ _cimg_mp_scalar1(mp_abs,opcode[3]); ++ opcode[2] = opcode._height; ++ opcode.move_to(code); ++ _cimg_mp_return(pos); ++ } ++ break; ++ ++ case 'p' : ++ if (!std::strncmp(ss,"permut(",7)) { // Number of permutations ++ _cimg_mp_op("Function 'permut()'"); ++ s1 = ss7; while (s1expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: %s: Type of first argument ('%s') " ++ "does not match with second argument 'nb_colsA=%u', " ++ "in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,s_op, ++ s_type(arg1)._data,p2, ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } ++ pos = vector(p1); ++ CImg::vector((ulongT)mp_matrix_pseudoinv,pos,arg1,p2,p3).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (!std::strncmp(ss,"print(",6) || !std::strncmp(ss,"prints(",7)) { // Print expressions ++ is_sth = ss[5]=='s'; // is prints() ++ _cimg_mp_op(is_sth?"Function 'prints()'":"Function 'print()'"); ++ s0 = is_sth?ss7:ss6; ++ if (*s0!='#' || is_sth) { // Regular expression ++ for (s = s0; s::string(s,true,true).unroll('y'),true); ++ cimg::strpare(variable_name,false,true); ++ if (_cimg_mp_is_vector(pos)) // Vector ++ ((CImg::vector((ulongT)mp_vector_print,pos,0,(ulongT)_cimg_mp_size(pos),is_sth?1:0), ++ variable_name)>'y').move_to(opcode); ++ else // Scalar ++ ((CImg::vector((ulongT)mp_print,pos,0,is_sth?1:0), ++ variable_name)>'y').move_to(opcode); ++ opcode[2] = opcode._height; ++ opcode.move_to(code); ++ *ns = c1; s = ns; ++ } ++ _cimg_mp_return(pos); ++ } else { // Image ++ p1 = compile(ss7,se1,depth1,0,is_single); ++ _cimg_mp_check_list(true); ++ CImg::vector((ulongT)mp_image_print,_cimg_mp_slot_nan,p1).move_to(code); ++ _cimg_mp_return_nan(); ++ } ++ } ++ break; ++ ++ case 'r' : ++ if (!std::strncmp(ss,"resize(",7)) { // Vector or image resize ++ _cimg_mp_op("Function 'resize()'"); ++ if (*ss7!='#') { // Vector ++ s1 = ss7; while (s1::vector((ulongT)mp_vector_resize,pos,arg2,arg1,(ulongT)_cimg_mp_size(arg1), ++ arg3,arg4).move_to(code); ++ _cimg_mp_return(pos); ++ ++ } else { // Image ++ if (!is_single) is_parallelizable = false; ++ s0 = ss8; while (s0::vector((ulongT)mp_image_resize,_cimg_mp_slot_nan,p1,~0U,~0U,~0U,~0U,1,0,0,0,0,0). ++ move_to(opcode); ++ pos = 0; ++ for (s = s0; s10) { ++ *se = saved_char; ++ s0 = ss - 4>expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: %s: %s arguments, in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,s_op, ++ pos<1?"Missing":"Too much", ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } ++ opcode.move_to(code); ++ _cimg_mp_return_nan(); ++ } ++ } ++ ++ if (!std::strncmp(ss,"reverse(",8)) { // Vector reverse ++ _cimg_mp_op("Function 'reverse()'"); ++ arg1 = compile(ss8,se1,depth1,0,is_single); ++ if (!_cimg_mp_is_vector(arg1)) _cimg_mp_return(arg1); ++ p1 = _cimg_mp_size(arg1); ++ pos = vector(p1); ++ CImg::vector((ulongT)mp_vector_reverse,pos,arg1,p1).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (!std::strncmp(ss,"rol(",4) || !std::strncmp(ss,"ror(",4)) { // Bitwise rotation ++ _cimg_mp_op(ss[2]=='l'?"Function 'rol()'":"Function 'ror()'"); ++ s1 = ss4; while (s11) { ++ arg2 = arg1 + 1; ++ if (p2>2) arg3 = arg2 + 1; ++ } ++ arg4 = compile(++s1,se1,depth1,0,is_single); ++ } else { ++ s2 = s1 + 1; while (s2::vector((ulongT)mp_rot3d,pos,arg1,arg2,arg3,arg4).move_to(code); ++ } else { // 2d rotation ++ _cimg_mp_check_type(arg1,1,1,0); ++ pos = vector(4); ++ CImg::vector((ulongT)mp_rot2d,pos,arg1).move_to(code); ++ } ++ _cimg_mp_return(pos); ++ } ++ ++ if (!std::strncmp(ss,"round(",6)) { // Value rounding ++ _cimg_mp_op("Function 'round()'"); ++ s1 = ss6; while (s1::vector((ulongT)mp_image_s,pos,p1).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (!std::strncmp(ss,"same(",5)) { // Test if operands have the same values ++ _cimg_mp_op("Function 'same()'"); ++ s1 = ss5; while (s1::vector((ulongT)mp_shift,pos,arg1,p1,arg2,arg3).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (!std::strncmp(ss,"sign(",5)) { // Sign ++ _cimg_mp_op("Function 'sign()'"); ++ arg1 = compile(ss5,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sign,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::sign(mem[arg1])); ++ _cimg_mp_scalar1(mp_sign,arg1); ++ } ++ ++ if (!std::strncmp(ss,"sin(",4)) { // Sine ++ _cimg_mp_op("Function 'sin()'"); ++ arg1 = compile(ss4,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sin,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::sin(mem[arg1])); ++ _cimg_mp_scalar1(mp_sin,arg1); ++ } ++ ++ if (!std::strncmp(ss,"sinc(",5)) { // Sine cardinal ++ _cimg_mp_op("Function 'sinc()'"); ++ arg1 = compile(ss5,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sinc,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::sinc(mem[arg1])); ++ _cimg_mp_scalar1(mp_sinc,arg1); ++ } ++ ++ if (!std::strncmp(ss,"sinh(",5)) { // Hyperbolic sine ++ _cimg_mp_op("Function 'sinh()'"); ++ arg1 = compile(ss5,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sinh,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::sinh(mem[arg1])); ++ _cimg_mp_scalar1(mp_sinh,arg1); ++ } ++ ++ if (!std::strncmp(ss,"size(",5)) { // Vector size. ++ _cimg_mp_op("Function 'size()'"); ++ arg1 = compile(ss5,se1,depth1,0,is_single); ++ _cimg_mp_constant(_cimg_mp_is_scalar(arg1)?0:_cimg_mp_size(arg1)); ++ } ++ ++ if (!std::strncmp(ss,"solve(",6)) { // Solve linear system ++ _cimg_mp_op("Function 'solve()'"); ++ s1 = ss6; while (s1expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: %s: Types of first and second arguments ('%s' and '%s') " ++ "do not match with third argument 'nb_colsB=%u', " ++ "in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,s_op, ++ s_type(arg1)._data,s_type(arg2)._data,p3, ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } ++ pos = vector(arg4*p3); ++ CImg::vector((ulongT)mp_solve,pos,arg1,arg2,arg4,arg5,p3).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (!std::strncmp(ss,"sort(",5)) { // Sort vector ++ _cimg_mp_op("Function 'sort()'"); ++ if (*ss5!='#') { // Vector ++ s1 = ss5; while (s1expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: %s: Invalid specified chunk size (%u) for first argument " ++ "('%s'), in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,s_op, ++ arg3,s_type(arg1)._data, ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } ++ pos = vector(p1); ++ CImg::vector((ulongT)mp_sort,pos,arg1,p1,arg2,arg3).move_to(code); ++ _cimg_mp_return(pos); ++ ++ } else { // Image ++ s1 = ss6; while (s1::vector((ulongT)mp_image_sort,_cimg_mp_slot_nan,p1,arg1,arg2).move_to(code); ++ _cimg_mp_return_nan(); ++ } ++ } ++ ++ if (!std::strncmp(ss,"sqr(",4)) { // Square ++ _cimg_mp_op("Function 'sqr()'"); ++ arg1 = compile(ss4,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sqr,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::sqr(mem[arg1])); ++ _cimg_mp_scalar1(mp_sqr,arg1); ++ } ++ ++ if (!std::strncmp(ss,"sqrt(",5)) { // Square root ++ _cimg_mp_op("Function 'sqrt()'"); ++ arg1 = compile(ss5,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sqrt,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::sqrt(mem[arg1])); ++ _cimg_mp_scalar1(mp_sqrt,arg1); ++ } ++ ++ if (!std::strncmp(ss,"srand(",6)) { // Set RNG seed ++ _cimg_mp_op("Function 'srand()'"); ++ arg1 = ss6::vector((ulongT)mp_image_stats,pos,p1).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (!std::strncmp(ss,"stov(",5)) { // String to double ++ _cimg_mp_op("Function 'stov()'"); ++ s1 = ss5; while (s1expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: %s: Type of first argument ('%s') " ++ "does not match with second argument 'nb_colsA=%u', " ++ "in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,s_op, ++ s_type(arg1)._data,p2, ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } ++ pos = vector(p1 + p2 + p2*p2); ++ CImg::vector((ulongT)mp_matrix_svd,pos,arg1,p2,p3).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ break; ++ ++ case 't' : ++ if (!std::strncmp(ss,"tan(",4)) { // Tangent ++ _cimg_mp_op("Function 'tan()'"); ++ arg1 = compile(ss4,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_tan,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::tan(mem[arg1])); ++ _cimg_mp_scalar1(mp_tan,arg1); ++ } ++ ++ if (!std::strncmp(ss,"tanh(",5)) { // Hyperbolic tangent ++ _cimg_mp_op("Function 'tanh()'"); ++ arg1 = compile(ss5,se1,depth1,0,is_single); ++ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_tanh,arg1); ++ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::tanh(mem[arg1])); ++ _cimg_mp_scalar1(mp_tanh,arg1); ++ } ++ ++ if (!std::strncmp(ss,"trace(",6)) { // Matrix trace ++ _cimg_mp_op("Function 'trace()'"); ++ arg1 = compile(ss6,se1,depth1,0,is_single); ++ _cimg_mp_check_matrix_square(arg1,1); ++ p1 = (unsigned int)std::sqrt((float)_cimg_mp_size(arg1)); ++ _cimg_mp_scalar2(mp_trace,arg1,p1); ++ } ++ ++ if (!std::strncmp(ss,"transp(",7)) { // Matrix transpose ++ _cimg_mp_op("Function 'transp()'"); ++ s1 = ss7; while (s1expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: %s: Size of first argument ('%s') does not match " ++ "second argument 'nb_cols=%u', in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,s_op, ++ s_type(arg1)._data,p2, ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } ++ pos = vector(p3*p2); ++ CImg::vector((ulongT)mp_transp,pos,arg1,p2,p3).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ break; ++ ++ case 'u' : ++ if (*ss1=='(') { // Random value with uniform distribution ++ _cimg_mp_op("Function 'u()'"); ++ if (*ss2==')') _cimg_mp_scalar2(mp_u,0,1); ++ s1 = ss2; while (s1ss6 && *s0==',') ++s0; ++ s1 = s0; while (s1s0) { ++ *s1 = 0; ++ arg2 = arg3 = ~0U; ++ if (s0[0]=='w' && s0[1]=='h' && !s0[2]) arg1 = reserved_label[arg3 = 0]; ++ else if (s0[0]=='w' && s0[1]=='h' && s0[2]=='d' && !s0[3]) arg1 = reserved_label[arg3 = 1]; ++ else if (s0[0]=='w' && s0[1]=='h' && s0[2]=='d' && s0[3]=='s' && !s0[4]) ++ arg1 = reserved_label[arg3 = 2]; ++ else if (s0[0]=='p' && s0[1]=='i' && !s0[2]) arg1 = reserved_label[arg3 = 3]; ++ else if (s0[0]=='i' && s0[1]=='m' && !s0[2]) arg1 = reserved_label[arg3 = 4]; ++ else if (s0[0]=='i' && s0[1]=='M' && !s0[2]) arg1 = reserved_label[arg3 = 5]; ++ else if (s0[0]=='i' && s0[1]=='a' && !s0[2]) arg1 = reserved_label[arg3 = 6]; ++ else if (s0[0]=='i' && s0[1]=='v' && !s0[2]) arg1 = reserved_label[arg3 = 7]; ++ else if (s0[0]=='i' && s0[1]=='s' && !s0[2]) arg1 = reserved_label[arg3 = 8]; ++ else if (s0[0]=='i' && s0[1]=='p' && !s0[2]) arg1 = reserved_label[arg3 = 9]; ++ else if (s0[0]=='i' && s0[1]=='c' && !s0[2]) arg1 = reserved_label[arg3 = 10]; ++ else if (s0[0]=='x' && s0[1]=='m' && !s0[2]) arg1 = reserved_label[arg3 = 11]; ++ else if (s0[0]=='y' && s0[1]=='m' && !s0[2]) arg1 = reserved_label[arg3 = 12]; ++ else if (s0[0]=='z' && s0[1]=='m' && !s0[2]) arg1 = reserved_label[arg3 = 13]; ++ else if (s0[0]=='c' && s0[1]=='m' && !s0[2]) arg1 = reserved_label[arg3 = 14]; ++ else if (s0[0]=='x' && s0[1]=='M' && !s0[2]) arg1 = reserved_label[arg3 = 15]; ++ else if (s0[0]=='y' && s0[1]=='M' && !s0[2]) arg1 = reserved_label[arg3 = 16]; ++ else if (s0[0]=='z' && s0[1]=='M' && !s0[2]) arg1 = reserved_label[arg3 = 17]; ++ else if (s0[0]=='c' && s0[1]=='M' && !s0[2]) arg1 = reserved_label[arg3 = 18]; ++ else if (s0[0]=='i' && s0[1]>='0' && s0[1]<='9' && !s0[2]) ++ arg1 = reserved_label[arg3 = 19 + s0[1] - '0']; ++ else if (!std::strcmp(s0,"interpolation")) arg1 = reserved_label[arg3 = 29]; ++ else if (!std::strcmp(s0,"boundary")) arg1 = reserved_label[arg3 = 30]; ++ else if (s0[1]) { // Multi-char variable ++ cimglist_for(variable_def,i) if (!std::strcmp(s0,variable_def[i])) { ++ arg1 = variable_pos[i]; arg2 = i; break; ++ } ++ } else arg1 = reserved_label[arg3 = *s0]; // Single-char variable ++ ++ if (arg1!=~0U) { ++ if (arg2==~0U) { if (arg3!=~0U) reserved_label[arg3] = ~0U; } ++ else { ++ variable_def.remove(arg2); ++ if (arg20) || ++ !std::strncmp(ss,"vector(",7) || ++ (!std::strncmp(ss,"vector",6) && ss7::sequence(arg4,arg3 + 1,arg3 + arg4).move_to(_opcode); ++ arg2+=arg4; ++ } else { CImg::vector(arg3).move_to(_opcode); ++arg2; } ++ s = ns; ++ } ++ if (arg1==~0U) arg1 = arg2; ++ _cimg_mp_check_vector0(arg1); ++ pos = vector(arg1); ++ _opcode.insert(CImg::vector((ulongT)mp_vector_init,pos,0,arg1),0); ++ (_opcode>'y').move_to(opcode); ++ opcode[2] = opcode._height; ++ opcode.move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (!std::strncmp(ss,"vtos(",5)) { // Double(s) to string ++ _cimg_mp_op("Function 'vtos()'"); ++ s1 = ss5; while (s1::vector((ulongT)mp_vtos,pos,p1,arg1,_cimg_mp_size(arg1),arg2).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ break; ++ ++ case 'w' : ++ if (*ss1=='(') { // Image width ++ _cimg_mp_op("Function 'w()'"); ++ if (*ss2=='#') { // Index specified ++ p1 = compile(ss3,se1,depth1,0,is_single); ++ _cimg_mp_check_list(false); ++ } else { if (ss2!=se1) break; p1 = ~0U; } ++ pos = scalar(); ++ CImg::vector((ulongT)mp_image_w,pos,p1).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (*ss1=='h' && *ss2=='(') { // Image width*height ++ _cimg_mp_op("Function 'wh()'"); ++ if (*ss3=='#') { // Index specified ++ p1 = compile(ss4,se1,depth1,0,is_single); ++ _cimg_mp_check_list(false); ++ } else { if (ss3!=se1) break; p1 = ~0U; } ++ pos = scalar(); ++ CImg::vector((ulongT)mp_image_wh,pos,p1).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (*ss1=='h' && *ss2=='d' && *ss3=='(') { // Image width*height*depth ++ _cimg_mp_op("Function 'whd()'"); ++ if (*ss4=='#') { // Index specified ++ p1 = compile(ss5,se1,depth1,0,is_single); ++ _cimg_mp_check_list(false); ++ } else { if (ss4!=se1) break; p1 = ~0U; } ++ pos = scalar(); ++ CImg::vector((ulongT)mp_image_whd,pos,p1).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (*ss1=='h' && *ss2=='d' && *ss3=='s' && *ss4=='(') { // Image width*height*depth*spectrum ++ _cimg_mp_op("Function 'whds()'"); ++ if (*ss5=='#') { // Index specified ++ p1 = compile(ss6,se1,depth1,0,is_single); ++ _cimg_mp_check_list(false); ++ } else { if (ss5!=se1) break; p1 = ~0U; } ++ pos = scalar(); ++ CImg::vector((ulongT)mp_image_whds,pos,p1).move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ if (!std::strncmp(ss,"while(",6) || !std::strncmp(ss,"whiledo(",8)) { // While...do ++ _cimg_mp_op("Function 'whiledo()'"); ++ s0 = *ss5=='('?ss6:ss8; ++ s1 = s0; while (s1::vector((ulongT)mp_whiledo,pos,arg1,p2 - p1,code._width - p2,arg2, ++ pos>=arg6 && !_cimg_mp_is_constant(pos), ++ arg1>=arg6 && !_cimg_mp_is_constant(arg1)).move_to(code,p1); ++ _cimg_mp_return(pos); ++ } ++ break; ++ ++ case 'x' : ++ if (!std::strncmp(ss,"xor(",4)) { // Xor ++ _cimg_mp_op("Function 'xor()'"); ++ s1 = ss4; while (s1::vector((ulongT)op,pos,0).move_to(_opcode); ++ for (s = std::strchr(ss,'(') + 1; s::sequence(_cimg_mp_size(arg2),arg2 + 1, ++ arg2 + (ulongT)_cimg_mp_size(arg2)). ++ move_to(_opcode); ++ else CImg::vector(arg2).move_to(_opcode); ++ is_sth&=_cimg_mp_is_constant(arg2); ++ s = ns; ++ } ++ (_opcode>'y').move_to(opcode); ++ opcode[2] = opcode._height; ++ if (is_sth) _cimg_mp_constant(op(*this)); ++ opcode.move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ // No corresponding built-in function -> Look for a user-defined macro call. ++ s0 = strchr(ss,'('); ++ if (s0) { ++ variable_name.assign(ss,(unsigned int)(s0 - ss + 1)).back() = 0; ++ ++ // Count number of specified arguments. ++ p1 = 0; ++ for (s = s0 + 1; s<=se1; ++p1, s = ns + 1) { ++ while (*s && (signed char)*s<=' ') ++s; ++ if (*s==')' && !p1) break; ++ ns = s; while (ns _expr = macro_body[l]; // Expression to be substituted ++ ++ p1 = 1; // Indice of current parsed argument ++ for (s = s0 + 1; s<=se1; ++p1, s = ns + 1) { // Parse function arguments ++ while (*s && (signed char)*s<=' ') ++s; ++ if (*s==')' && p1==1) break; // Function has no arguments ++ if (p1>p2) { ++p1; break; } ++ ns = s; while (ns _pexpr(_expr._width); ++ ns = _pexpr._data; ++ for (ps = _expr._data, c1 = ' '; *ps; ++ps) { ++ if ((signed char)*ps>' ') c1 = *ps; ++ *(ns++) = c1; ++ } ++ *ns = 0; ++ ++ CImg _level = get_level(_expr); ++ expr.swap(_expr); ++ pexpr.swap(_pexpr); ++ level.swap(_level); ++ s0 = user_macro; ++ user_macro = macro_def[l]; ++ pos = compile(expr._data,expr._data + expr._width - 1,depth1,p_ref,is_single); ++ user_macro = s0; ++ level.swap(_level); ++ pexpr.swap(_pexpr); ++ expr.swap(_expr); ++ _cimg_mp_return(pos); ++ } ++ ++ if (arg3) { // Macro name matched but number of arguments does not ++ CImg sig_nargs(arg3); ++ arg1 = 0; ++ cimglist_for(macro_def,l) if (!std::strcmp(macro_def[l],variable_name)) ++ sig_nargs[arg1++] = (unsigned int)macro_def[l].back(); ++ *se = saved_char; ++ cimg::strellipsize(variable_name,64); ++ s0 = ss - 4>expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ if (sig_nargs._width>1) { ++ sig_nargs.sort(); ++ arg1 = sig_nargs.back(); ++ --sig_nargs._width; ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: Function '%s()': Number of specified arguments (%u) " ++ "does not match macro declaration (defined for %s or %u arguments), " ++ "in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,variable_name._data, ++ p1,sig_nargs.value_string()._data,arg1, ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } else ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: Function '%s()': Number of specified arguments (%u) " ++ "does not match macro declaration (defined for %u argument%s), " ++ "in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,variable_name._data, ++ p1,*sig_nargs,*sig_nargs!=1?"s":"", ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } ++ } ++ } // if (se1==')') ++ ++ // Char / string initializer. ++ if (*se1=='\'' && ++ ((se1>ss && *ss=='\'') || ++ (se1>ss1 && *ss=='_' && *ss1=='\''))) { ++ if (*ss=='_') { _cimg_mp_op("Char initializer"); s1 = ss2; } ++ else { _cimg_mp_op("String initializer"); s1 = ss1; } ++ arg1 = (unsigned int)(se1 - s1); // Original string length. ++ if (arg1) { ++ CImg(s1,arg1 + 1).move_to(variable_name).back() = 0; ++ cimg::strunescape(variable_name); ++ arg1 = (unsigned int)std::strlen(variable_name); ++ } ++ if (!arg1) _cimg_mp_return(0); // Empty string -> 0 ++ if (*ss=='_') { ++ if (arg1==1) _cimg_mp_constant(*variable_name); ++ *se = saved_char; ++ cimg::strellipsize(variable_name,64); ++ s0 = ss - 4>expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: %s: Literal %s contains more than one character, " ++ "in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,s_op, ++ ss1, ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } ++ pos = vector(arg1); ++ CImg::vector((ulongT)mp_string_init,pos,arg1).move_to(_opcode); ++ CImg(1,arg1/sizeof(ulongT) + (arg1%sizeof(ulongT)?1:0)).move_to(_opcode); ++ std::memcpy((char*)_opcode[1]._data,variable_name,arg1); ++ (_opcode>'y').move_to(code); ++ _cimg_mp_return(pos); ++ } ++ ++ // Vector initializer [ ... ]. ++ if (*ss=='[' && *se1==']') { ++ _cimg_mp_op("Vector initializer"); ++ s1 = ss1; while (s1s1 && (signed char)*s2<=' ') --s2; ++ if (s2>s1 && *s1=='\'' && *s2=='\'') { // Vector values provided as a string ++ arg1 = (unsigned int)(s2 - s1 - 1); // Original string length. ++ if (arg1) { ++ CImg(s1 + 1,arg1 + 1).move_to(variable_name).back() = 0; ++ cimg::strunescape(variable_name); ++ arg1 = (unsigned int)std::strlen(variable_name); ++ } ++ if (!arg1) _cimg_mp_return(0); // Empty string -> 0 ++ pos = vector(arg1); ++ CImg::vector((ulongT)mp_string_init,pos,arg1).move_to(_opcode); ++ CImg(1,arg1/sizeof(ulongT) + (arg1%sizeof(ulongT)?1:0)).move_to(_opcode); ++ std::memcpy((char*)_opcode[1]._data,variable_name,arg1); ++ (_opcode>'y').move_to(code); ++ } else { // Vector values provided as list of items ++ arg1 = 0; // Number of specified values. ++ if (*ss1!=']') for (s = ss1; s::sequence(arg3,arg2 + 1,arg2 + arg3).move_to(_opcode); ++ arg1+=arg3; ++ } else { CImg::vector(arg2).move_to(_opcode); ++arg1; } ++ s = ns; ++ } ++ _cimg_mp_check_vector0(arg1); ++ pos = vector(arg1); ++ _opcode.insert(CImg::vector((ulongT)mp_vector_init,pos,0,arg1),0); ++ (_opcode>'y').move_to(opcode); ++ opcode[2] = opcode._height; ++ opcode.move_to(code); ++ } ++ _cimg_mp_return(pos); ++ } ++ ++ // Variables related to the input list of images. ++ if (*ss1=='#' && ss2::vector((ulongT)mp_list_Joff,pos,p1,0,0,p2).move_to(code); ++ _cimg_mp_return(pos); ++ case 'R' : // R#ind ++ if (!listin) _cimg_mp_return(0); ++ _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,0, ++ 0,_cimg_mp_boundary); ++ case 'G' : // G#ind ++ if (!listin) _cimg_mp_return(0); ++ _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,1, ++ 0,_cimg_mp_boundary); ++ case 'B' : // B#ind ++ if (!listin) _cimg_mp_return(0); ++ _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,2, ++ 0,_cimg_mp_boundary); ++ case 'A' : // A#ind ++ if (!listin) _cimg_mp_return(0); ++ _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,3, ++ 0,_cimg_mp_boundary); ++ } ++ } ++ ++ if (*ss1 && *ss2=='#' && ss3::vector(listin[p1].median()).move_to(list_median[p1]); ++ _cimg_mp_constant(*list_median[p1]); ++ } ++ _cimg_mp_scalar1(mp_list_median,arg1); ++ } ++ if (*ss1>='0' && *ss1<='9') { // i0#ind...i9#ind ++ if (!listin) _cimg_mp_return(0); ++ _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,*ss1 - '0', ++ 0,_cimg_mp_boundary); ++ } ++ switch (*ss1) { ++ case 'm' : arg2 = 0; break; // im#ind ++ case 'M' : arg2 = 1; break; // iM#ind ++ case 'a' : arg2 = 2; break; // ia#ind ++ case 'v' : arg2 = 3; break; // iv#ind ++ case 's' : arg2 = 12; break; // is#ind ++ case 'p' : arg2 = 13; break; // ip#ind ++ } ++ } else if (*ss1=='m') switch (*ss) { ++ case 'x' : arg2 = 4; break; // xm#ind ++ case 'y' : arg2 = 5; break; // ym#ind ++ case 'z' : arg2 = 6; break; // zm#ind ++ case 'c' : arg2 = 7; break; // cm#ind ++ } else if (*ss1=='M') switch (*ss) { ++ case 'x' : arg2 = 8; break; // xM#ind ++ case 'y' : arg2 = 9; break; // yM#ind ++ case 'z' : arg2 = 10; break; // zM#ind ++ case 'c' : arg2 = 11; break; // cM#ind ++ } ++ if (arg2!=~0U) { ++ if (!listin) _cimg_mp_return(0); ++ if (_cimg_mp_is_constant(arg1)) { ++ if (!list_stats) list_stats.assign(listin._width); ++ if (!list_stats[p1]) list_stats[p1].assign(1,14,1,1,0).fill(listin[p1].get_stats(),false); ++ _cimg_mp_constant(list_stats(p1,arg2)); ++ } ++ _cimg_mp_scalar2(mp_list_stats,arg1,arg2); ++ } ++ } ++ ++ if (*ss=='w' && *ss1=='h' && *ss2=='d' && *ss3=='#' && ss4 error. ++ is_sth = true; // is_valid_variable_name ++ if (*variable_name>='0' && *variable_name<='9') is_sth = false; ++ else for (ns = variable_name._data; *ns; ++ns) ++ if (!is_varchar(*ns)) { is_sth = false; break; } ++ ++ *se = saved_char; ++ c1 = *se1; ++ cimg::strellipsize(variable_name,64); ++ s0 = ss - 4>expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ if (is_sth) ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: Undefined variable '%s' in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function, ++ variable_name._data, ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ s1 = std::strchr(ss,'('); ++ s_op = s1 && c1==')'?"function call":"item"; ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: Unrecognized %s '%s' in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function, ++ s_op,variable_name._data, ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } ++ ++ // Evaluation procedure. ++ double operator()(const double x, const double y, const double z, const double c) { ++ mem[_cimg_mp_slot_x] = x; mem[_cimg_mp_slot_y] = y; mem[_cimg_mp_slot_z] = z; mem[_cimg_mp_slot_c] = c; ++ for (p_code = code; p_code_data; ++ const ulongT target = opcode[1]; ++ mem[target] = _cimg_mp_defunc(*this); ++ } ++ return *result; ++ } ++ ++ // Evaluation procedure (return output values in vector 'output'). ++ template ++ void operator()(const double x, const double y, const double z, const double c, t *const output) { ++ mem[_cimg_mp_slot_x] = x; mem[_cimg_mp_slot_y] = y; mem[_cimg_mp_slot_z] = z; mem[_cimg_mp_slot_c] = c; ++ for (p_code = code; p_code_data; ++ const ulongT target = opcode[1]; ++ mem[target] = _cimg_mp_defunc(*this); ++ } ++ if (result_dim) { ++ const double *ptrs = result + 1; ++ t *ptrd = output; ++ for (unsigned int k = 0; k_data; ++ const ulongT target = opcode[1]; ++ mem[target] = _cimg_mp_defunc(*this); ++ } ++ } ++ ++ // Return type of a memory element as a string. ++ CImg s_type(const unsigned int arg) const { ++ CImg res; ++ if (_cimg_mp_is_vector(arg)) { // Vector ++ CImg::string("vectorXXXXXXXXXXXXXXXX").move_to(res); ++ std::sprintf(res._data + 6,"%u",_cimg_mp_size(arg)); ++ } else CImg::string("scalar").move_to(res); ++ return res; ++ } ++ ++ // Insert constant value in memory. ++ unsigned int constant(const double val) { ++ ++ // Search for built-in constant. ++ if (val==(double)(int)val) { ++ if (val>=0 && val<=10) return (unsigned int)val; ++ if (val<0 && val>=-5) return (unsigned int)(10 - val); ++ } ++ if (val==0.5) return 16; ++ if (cimg::type::is_nan(val)) return _cimg_mp_slot_nan; ++ ++ // Search for constant already requested before (in const cache). ++ unsigned int ind = ~0U; ++ if (constcache_size<1024) { ++ if (!constcache_size) { ++ constcache_vals.assign(16,1,1,1,0); ++ constcache_inds.assign(16,1,1,1,0); ++ *constcache_vals = val; ++ constcache_size = 1; ++ ind = 0; ++ } else { // Dichotomic search ++ const double val_beg = *constcache_vals, val_end = constcache_vals[constcache_size - 1]; ++ if (val_beg>=val) ind = 0; ++ else if (val_end==val) ind = constcache_size - 1; ++ else if (val_end=constcache_size || constcache_vals[ind]!=val) { ++ ++constcache_size; ++ if (constcache_size>constcache_vals._width) { ++ constcache_vals.resize(-200,1,1,1,0); ++ constcache_inds.resize(-200,1,1,1,0); ++ } ++ const int l = constcache_size - (int)ind - 1; ++ if (l>0) { ++ std::memmove(&constcache_vals[ind + 1],&constcache_vals[ind],l*sizeof(double)); ++ std::memmove(&constcache_inds[ind + 1],&constcache_inds[ind],l*sizeof(unsigned int)); ++ } ++ constcache_vals[ind] = val; ++ constcache_inds[ind] = 0; ++ } ++ } ++ if (constcache_inds[ind]) return constcache_inds[ind]; ++ } ++ ++ // Insert new constant in memory if necessary. ++ if (mempos>=mem._width) { mem.resize(-200,1,1,1,0); memtype.resize(-200,1,1,1,0); } ++ const unsigned int pos = mempos++; ++ mem[pos] = val; ++ memtype[pos] = 1; // Set constant property ++ if (ind!=~0U) constcache_inds[ind] = pos; ++ return pos; ++ } ++ ++ // Insert code instructions for processing scalars. ++ unsigned int scalar() { // Insert new scalar in memory. ++ if (mempos>=mem._width) { mem.resize(-200,1,1,1,0); memtype.resize(mem._width,1,1,1,0); } ++ return mempos++; ++ } ++ ++ unsigned int scalar0(const mp_func op) { ++ const unsigned int pos = scalar(); ++ CImg::vector((ulongT)op,pos).move_to(code); ++ return pos; ++ } ++ ++ unsigned int scalar1(const mp_func op, const unsigned int arg1) { ++ const unsigned int pos = ++ arg1>_cimg_mp_slot_c && _cimg_mp_is_comp(arg1) && op!=mp_copy?arg1:scalar(); ++ CImg::vector((ulongT)op,pos,arg1).move_to(code); ++ return pos; ++ } ++ ++ unsigned int scalar2(const mp_func op, const unsigned int arg1, const unsigned int arg2) { ++ const unsigned int pos = ++ arg1>_cimg_mp_slot_c && _cimg_mp_is_comp(arg1)?arg1: ++ arg2>_cimg_mp_slot_c && _cimg_mp_is_comp(arg2)?arg2:scalar(); ++ CImg::vector((ulongT)op,pos,arg1,arg2).move_to(code); ++ return pos; ++ } ++ ++ unsigned int scalar3(const mp_func op, ++ const unsigned int arg1, const unsigned int arg2, const unsigned int arg3) { ++ const unsigned int pos = ++ arg1>_cimg_mp_slot_c && _cimg_mp_is_comp(arg1)?arg1: ++ arg2>_cimg_mp_slot_c && _cimg_mp_is_comp(arg2)?arg2: ++ arg3>_cimg_mp_slot_c && _cimg_mp_is_comp(arg3)?arg3:scalar(); ++ CImg::vector((ulongT)op,pos,arg1,arg2,arg3).move_to(code); ++ return pos; ++ } ++ ++ unsigned int scalar4(const mp_func op, ++ const unsigned int arg1, const unsigned int arg2, const unsigned int arg3, ++ const unsigned int arg4) { ++ const unsigned int pos = ++ arg1>_cimg_mp_slot_c && _cimg_mp_is_comp(arg1)?arg1: ++ arg2>_cimg_mp_slot_c && _cimg_mp_is_comp(arg2)?arg2: ++ arg3>_cimg_mp_slot_c && _cimg_mp_is_comp(arg3)?arg3: ++ arg4>_cimg_mp_slot_c && _cimg_mp_is_comp(arg4)?arg4:scalar(); ++ CImg::vector((ulongT)op,pos,arg1,arg2,arg3,arg4).move_to(code); ++ return pos; ++ } ++ ++ unsigned int scalar5(const mp_func op, ++ const unsigned int arg1, const unsigned int arg2, const unsigned int arg3, ++ const unsigned int arg4, const unsigned int arg5) { ++ const unsigned int pos = ++ arg1>_cimg_mp_slot_c && _cimg_mp_is_comp(arg1)?arg1: ++ arg2>_cimg_mp_slot_c && _cimg_mp_is_comp(arg2)?arg2: ++ arg3>_cimg_mp_slot_c && _cimg_mp_is_comp(arg3)?arg3: ++ arg4>_cimg_mp_slot_c && _cimg_mp_is_comp(arg4)?arg4: ++ arg5>_cimg_mp_slot_c && _cimg_mp_is_comp(arg5)?arg5:scalar(); ++ CImg::vector((ulongT)op,pos,arg1,arg2,arg3,arg4,arg5).move_to(code); ++ return pos; ++ } ++ ++ unsigned int scalar6(const mp_func op, ++ const unsigned int arg1, const unsigned int arg2, const unsigned int arg3, ++ const unsigned int arg4, const unsigned int arg5, const unsigned int arg6) { ++ const unsigned int pos = ++ arg1>_cimg_mp_slot_c && _cimg_mp_is_comp(arg1)?arg1: ++ arg2>_cimg_mp_slot_c && _cimg_mp_is_comp(arg2)?arg2: ++ arg3>_cimg_mp_slot_c && _cimg_mp_is_comp(arg3)?arg3: ++ arg4>_cimg_mp_slot_c && _cimg_mp_is_comp(arg4)?arg4: ++ arg5>_cimg_mp_slot_c && _cimg_mp_is_comp(arg5)?arg5: ++ arg6>_cimg_mp_slot_c && _cimg_mp_is_comp(arg6)?arg6:scalar(); ++ CImg::vector((ulongT)op,pos,arg1,arg2,arg3,arg4,arg5,arg6).move_to(code); ++ return pos; ++ } ++ ++ unsigned int scalar7(const mp_func op, ++ const unsigned int arg1, const unsigned int arg2, const unsigned int arg3, ++ const unsigned int arg4, const unsigned int arg5, const unsigned int arg6, ++ const unsigned int arg7) { ++ const unsigned int pos = ++ arg1>_cimg_mp_slot_c && _cimg_mp_is_comp(arg1)?arg1: ++ arg2>_cimg_mp_slot_c && _cimg_mp_is_comp(arg2)?arg2: ++ arg3>_cimg_mp_slot_c && _cimg_mp_is_comp(arg3)?arg3: ++ arg4>_cimg_mp_slot_c && _cimg_mp_is_comp(arg4)?arg4: ++ arg5>_cimg_mp_slot_c && _cimg_mp_is_comp(arg5)?arg5: ++ arg6>_cimg_mp_slot_c && _cimg_mp_is_comp(arg6)?arg6: ++ arg7>_cimg_mp_slot_c && _cimg_mp_is_comp(arg7)?arg7:scalar(); ++ CImg::vector((ulongT)op,pos,arg1,arg2,arg3,arg4,arg5,arg6,arg7).move_to(code); ++ return pos; ++ } ++ ++ // Return a string that defines the calling function + the user-defined function scope. ++ CImg calling_function_s() const { ++ CImg res; ++ const unsigned int ++ l1 = calling_function?(unsigned int)std::strlen(calling_function):0U, ++ l2 = user_macro?(unsigned int)std::strlen(user_macro):0U; ++ if (l2) { ++ res.assign(l1 + l2 + 48); ++ cimg_snprintf(res,res._width,"%s(): When substituting function '%s()'",calling_function,user_macro); ++ } else { ++ res.assign(l1 + l2 + 4); ++ cimg_snprintf(res,res._width,"%s()",calling_function); ++ } ++ return res; ++ } ++ ++ // Return true if specified argument can be a part of an allowed variable name. ++ bool is_varchar(const char c) const { ++ return (c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9') || c=='_'; ++ } ++ ++ // Insert code instructions for processing vectors. ++ bool is_comp_vector(const unsigned int arg) const { ++ unsigned int siz = _cimg_mp_size(arg); ++ if (siz>8) return false; ++ const int *ptr = memtype.data(arg + 1); ++ bool is_tmp = true; ++ while (siz-->0) if (*(ptr++)) { is_tmp = false; break; } ++ return is_tmp; ++ } ++ ++ void set_variable_vector(const unsigned int arg) { ++ unsigned int siz = _cimg_mp_size(arg); ++ int *ptr = memtype.data(arg + 1); ++ while (siz-->0) *(ptr++) = -1; ++ } ++ ++ unsigned int vector(const unsigned int siz) { // Insert new vector of specified size in memory ++ if (mempos + siz>=mem._width) { ++ mem.resize(2*mem._width + siz,1,1,1,0); ++ memtype.resize(mem._width,1,1,1,0); ++ } ++ const unsigned int pos = mempos++; ++ mem[pos] = cimg::type::nan(); ++ memtype[pos] = siz + 1; ++ mempos+=siz; ++ return pos; ++ } ++ ++ unsigned int vector(const unsigned int siz, const double value) { // Insert new initialized vector ++ const unsigned int pos = vector(siz); ++ double *ptr = &mem[pos] + 1; ++ for (unsigned int i = 0; i::vector((ulongT)mp_vector_copy,pos,arg,siz).move_to(code); ++ return pos; ++ } ++ ++ void self_vector_s(const unsigned int pos, const mp_func op, const unsigned int arg1) { ++ const unsigned int siz = _cimg_mp_size(pos); ++ if (siz>24) CImg::vector((ulongT)mp_self_map_vector_s,pos,siz,(ulongT)op,arg1).move_to(code); ++ else { ++ code.insert(siz); ++ for (unsigned int k = 1; k<=siz; ++k) ++ CImg::vector((ulongT)op,pos + k,arg1).move_to(code[code._width - 1 - siz + k]); ++ } ++ } ++ ++ void self_vector_v(const unsigned int pos, const mp_func op, const unsigned int arg1) { ++ const unsigned int siz = _cimg_mp_size(pos); ++ if (siz>24) CImg::vector((ulongT)mp_self_map_vector_v,pos,siz,(ulongT)op,arg1).move_to(code); ++ else { ++ code.insert(siz); ++ for (unsigned int k = 1; k<=siz; ++k) ++ CImg::vector((ulongT)op,pos + k,arg1 + k).move_to(code[code._width - 1 - siz + k]); ++ } ++ } ++ ++ unsigned int vector1_v(const mp_func op, const unsigned int arg1) { ++ const unsigned int ++ siz = _cimg_mp_size(arg1), ++ pos = is_comp_vector(arg1)?arg1:vector(siz); ++ if (siz>24) CImg::vector((ulongT)mp_vector_map_v,pos,siz,(ulongT)op,arg1).move_to(code); ++ else { ++ code.insert(siz); ++ for (unsigned int k = 1; k<=siz; ++k) ++ CImg::vector((ulongT)op,pos + k,arg1 + k).move_to(code[code._width - 1 - siz + k]); ++ } ++ return pos; ++ } ++ ++ unsigned int vector2_vv(const mp_func op, const unsigned int arg1, const unsigned int arg2) { ++ const unsigned int ++ siz = _cimg_mp_size(arg1), ++ pos = is_comp_vector(arg1)?arg1:is_comp_vector(arg2)?arg2:vector(siz); ++ if (siz>24) CImg::vector((ulongT)mp_vector_map_vv,pos,siz,(ulongT)op,arg1,arg2).move_to(code); ++ else { ++ code.insert(siz); ++ for (unsigned int k = 1; k<=siz; ++k) ++ CImg::vector((ulongT)op,pos + k,arg1 + k,arg2 + k).move_to(code[code._width - 1 - siz + k]); ++ } ++ return pos; ++ } ++ ++ unsigned int vector2_vs(const mp_func op, const unsigned int arg1, const unsigned int arg2) { ++ const unsigned int ++ siz = _cimg_mp_size(arg1), ++ pos = is_comp_vector(arg1)?arg1:vector(siz); ++ if (siz>24) CImg::vector((ulongT)mp_vector_map_vs,pos,siz,(ulongT)op,arg1,arg2).move_to(code); ++ else { ++ code.insert(siz); ++ for (unsigned int k = 1; k<=siz; ++k) ++ CImg::vector((ulongT)op,pos + k,arg1 + k,arg2).move_to(code[code._width - 1 - siz + k]); ++ } ++ return pos; ++ } ++ ++ unsigned int vector2_sv(const mp_func op, const unsigned int arg1, const unsigned int arg2) { ++ const unsigned int ++ siz = _cimg_mp_size(arg2), ++ pos = is_comp_vector(arg2)?arg2:vector(siz); ++ if (siz>24) CImg::vector((ulongT)mp_vector_map_sv,pos,siz,(ulongT)op,arg1,arg2).move_to(code); ++ else { ++ code.insert(siz); ++ for (unsigned int k = 1; k<=siz; ++k) ++ CImg::vector((ulongT)op,pos + k,arg1,arg2 + k).move_to(code[code._width - 1 - siz + k]); ++ } ++ return pos; ++ } ++ ++ unsigned int vector3_vss(const mp_func op, const unsigned int arg1, const unsigned int arg2, ++ const unsigned int arg3) { ++ const unsigned int ++ siz = _cimg_mp_size(arg1), ++ pos = is_comp_vector(arg1)?arg1:vector(siz); ++ if (siz>24) CImg::vector((ulongT)mp_vector_map_vss,pos,siz,(ulongT)op,arg1,arg2,arg3).move_to(code); ++ else { ++ code.insert(siz); ++ for (unsigned int k = 1; k<=siz; ++k) ++ CImg::vector((ulongT)op,pos + k,arg1 + k,arg2,arg3).move_to(code[code._width - 1 - siz + k]); ++ } ++ return pos; ++ } ++ ++ // Check if a memory slot is a positive integer constant scalar value. ++ // 'mode' can be: ++ // { 0=constant | 1=integer constant | 2=positive integer constant | 3=strictly-positive integer constant } ++ void check_constant(const unsigned int arg, const unsigned int n_arg, ++ const unsigned int mode, ++ char *const ss, char *const se, const char saved_char) { ++ _cimg_mp_check_type(arg,n_arg,1,0); ++ if (!(_cimg_mp_is_constant(arg) && ++ (!mode || (double)(int)mem[arg]==mem[arg]) && ++ (mode<2 || mem[arg]>=(mode==3)))) { ++ const char *s_arg = !n_arg?"":n_arg==1?"First ":n_arg==2?"Second ":n_arg==3?"Third ": ++ n_arg==4?"Fourth ":n_arg==5?"Fifth ":n_arg==6?"Sixth ":n_arg==7?"Seventh ":n_arg==8?"Eighth ": ++ n_arg==9?"Ninth ":"One of the "; ++ *se = saved_char; ++ char *const s0 = ss - 4>expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: %s%s %s%s (of type '%s') is not a%s constant, " ++ "in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,s_op,*s_op?":":"", ++ s_arg,*s_arg?"argument":"Argument",s_type(arg)._data, ++ !mode?"":mode==1?"n integer": ++ mode==2?" positive integer":" strictly positive integer", ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } ++ } ++ ++ // Check a matrix is square. ++ void check_matrix_square(const unsigned int arg, const unsigned int n_arg, ++ char *const ss, char *const se, const char saved_char) { ++ _cimg_mp_check_type(arg,n_arg,2,0); ++ const unsigned int ++ siz = _cimg_mp_size(arg), ++ n = (unsigned int)std::sqrt((float)siz); ++ if (n*n!=siz) { ++ const char *s_arg; ++ if (*s_op!='F') s_arg = !n_arg?"":n_arg==1?"Left-hand ":"Right-hand "; ++ else s_arg = !n_arg?"":n_arg==1?"First ":n_arg==2?"Second ":n_arg==3?"Third ":"One "; ++ *se = saved_char; ++ char *const s0 = ss - 4>expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: %s%s %s%s (of type '%s') " ++ "cannot be considered as a square matrix, in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,s_op,*s_op?":":"", ++ s_arg,*s_op=='F'?(*s_arg?"argument":"Argument"):(*s_arg?"operand":"Operand"), ++ s_type(arg)._data, ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } ++ } ++ ++ // Check type compatibility for one argument. ++ // Bits of 'mode' tells what types are allowed: ++ // { 1 = scalar | 2 = vectorN }. ++ // If 'N' is not zero, it also restricts the vectors to be of size N only. ++ void check_type(const unsigned int arg, const unsigned int n_arg, ++ const unsigned int mode, const unsigned int N, ++ char *const ss, char *const se, const char saved_char) { ++ const bool ++ is_scalar = _cimg_mp_is_scalar(arg), ++ is_vector = _cimg_mp_is_vector(arg) && (!N || _cimg_mp_size(arg)==N); ++ bool cond = false; ++ if (mode&1) cond|=is_scalar; ++ if (mode&2) cond|=is_vector; ++ if (!cond) { ++ const char *s_arg; ++ if (*s_op!='F') s_arg = !n_arg?"":n_arg==1?"Left-hand ":"Right-hand "; ++ else s_arg = !n_arg?"":n_arg==1?"First ":n_arg==2?"Second ":n_arg==3?"Third ": ++ n_arg==4?"Fourth ":n_arg==5?"Fifth ":n_arg==6?"Sixth ":n_arg==7?"Seventh ":n_arg==8?"Eighth": ++ n_arg==9?"Ninth":"One of the "; ++ CImg sb_type(32); ++ if (mode==1) cimg_snprintf(sb_type,sb_type._width,"'scalar'"); ++ else if (mode==2) { ++ if (N) cimg_snprintf(sb_type,sb_type._width,"'vector%u'",N); ++ else cimg_snprintf(sb_type,sb_type._width,"'vector'"); ++ } else { ++ if (N) cimg_snprintf(sb_type,sb_type._width,"'scalar' or 'vector%u'",N); ++ else cimg_snprintf(sb_type,sb_type._width,"'scalar' or 'vector'"); ++ } ++ *se = saved_char; ++ char *const s0 = ss - 4>expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: %s%s %s%s has invalid type '%s' (should be %s), " ++ "in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,s_op,*s_op?":":"", ++ s_arg,*s_op=='F'?(*s_arg?"argument":"Argument"):(*s_arg?"operand":"Operand"), ++ s_type(arg)._data,sb_type._data, ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } ++ } ++ ++ // Check that listin or listout are not empty. ++ void check_list(const bool is_out, ++ char *const ss, char *const se, const char saved_char) { ++ if ((!is_out && !listin) || (is_out && !listout)) { ++ *se = saved_char; ++ char *const s0 = ss - 4>expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: %s%s Invalid call with an empty image list, " ++ "in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,s_op,*s_op?":":"", ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } ++ } ++ ++ // Check a vector is not 0-dimensional, or with unknown dimension at compile time. ++ void check_vector0(const unsigned int dim, ++ char *const ss, char *const se, const char saved_char) { ++ char *s0 = 0; ++ if (!dim) { ++ *se = saved_char; ++ s0 = ss - 4>expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: %s%s Invalid construction of a 0-dimensional vector, " ++ "in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,s_op,*s_op?":":"", ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } else if (dim==~0U) { ++ *se = saved_char; ++ s0 = ss - 4>expr._data?ss - 4:expr._data; ++ cimg::strellipsize(s0,64); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] " ++ "CImg<%s>::%s: %s%s Invalid construction of a vector with possible dynamic size, " ++ "in expression '%s%s%s'.", ++ pixel_type(),_cimg_mp_calling_function,s_op,*s_op?":":"", ++ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); ++ } ++ } ++ ++ // Evaluation functions, known by the parser. ++ // Defining these functions 'static' ensures that sizeof(mp_func)==sizeof(ulongT), ++ // so we can store pointers to them directly in the opcode vectors. ++#ifdef _mp_arg ++#undef _mp_arg ++#endif ++#define _mp_arg(x) mp.mem[mp.opcode[x]] ++ ++ static double mp_abs(_cimg_math_parser& mp) { ++ return cimg::abs(_mp_arg(2)); ++ } ++ ++ static double mp_add(_cimg_math_parser& mp) { ++ return _mp_arg(2) + _mp_arg(3); ++ } ++ ++ static double mp_acos(_cimg_math_parser& mp) { ++ return std::acos(_mp_arg(2)); ++ } ++ ++ static double mp_arg(_cimg_math_parser& mp) { ++ const int _ind = (int)_mp_arg(4); ++ const unsigned int ++ nb_args = (unsigned int)mp.opcode[2] - 4, ++ ind = _ind<0?_ind + nb_args:(unsigned int)_ind, ++ siz = (unsigned int)mp.opcode[3]; ++ if (siz>0) { ++ if (ind>=nb_args) std::memset(&_mp_arg(1) + 1,0,siz*sizeof(double)); ++ else std::memcpy(&_mp_arg(1) + 1,&_mp_arg(ind + 4) + 1,siz*sizeof(double)); ++ return cimg::type::nan(); ++ } ++ if (ind>=nb_args) return 0; ++ return _mp_arg(ind + 4); ++ } ++ ++ static double mp_argkth(_cimg_math_parser& mp) { ++ const unsigned int i_end = (unsigned int)mp.opcode[2]; ++ const double val = mp_kth(mp); ++ for (unsigned int i = 4; ival) { val = _val; argval = i - 3; } ++ } ++ return (double)argval; ++ } ++ ++ static double mp_asin(_cimg_math_parser& mp) { ++ return std::asin(_mp_arg(2)); ++ } ++ ++ static double mp_atan(_cimg_math_parser& mp) { ++ return std::atan(_mp_arg(2)); ++ } ++ ++ static double mp_atan2(_cimg_math_parser& mp) { ++ return std::atan2(_mp_arg(2),_mp_arg(3)); ++ } ++ ++ static double mp_avg(_cimg_math_parser& mp) { ++ const unsigned int i_end = (unsigned int)mp.opcode[2]; ++ double val = _mp_arg(3); ++ for (unsigned int i = 4; i>(unsigned int)_mp_arg(3)); ++ } ++ ++ static double mp_bitwise_xor(_cimg_math_parser& mp) { ++ return (double)((longT)_mp_arg(2) ^ (longT)_mp_arg(3)); ++ } ++ ++ static double mp_bool(_cimg_math_parser& mp) { ++ return (double)(bool)_mp_arg(2); ++ } ++ ++ static double mp_break(_cimg_math_parser& mp) { ++ mp.break_type = 1; ++ mp.p_code = mp.p_break - 1; ++ return cimg::type::nan(); ++ } ++ ++ static double mp_breakpoint(_cimg_math_parser& mp) { ++ cimg_abort_init; ++ cimg_abort_test; ++ cimg::unused(mp); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_cats(_cimg_math_parser& mp) { ++ const double *ptrd = &_mp_arg(1) + 1; ++ const unsigned int ++ sizd = (unsigned int)mp.opcode[2], ++ nb_args = (unsigned int)(mp.opcode[3] - 4)/2; ++ CImgList _str; ++ for (unsigned int n = 0; n(ptrs,l,1,1,1,true).move_to(_str); ++ } else CImg::vector((char)_mp_arg(4 + 2*n)).move_to(_str); // Scalar argument ++ } ++ CImg(1,1,1,1,0).move_to(_str); ++ const CImg str = _str>'x'; ++ const unsigned int l = std::min(str._width,sizd); ++ CImg(ptrd,l,1,1,1,true) = str.get_shared_points(0,l - 1); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_cbrt(_cimg_math_parser& mp) { ++ return cimg::cbrt(_mp_arg(2)); ++ } ++ ++ static double mp_ceil(_cimg_math_parser& mp) { ++ return std::ceil(_mp_arg(2)); ++ } ++ ++ static double mp_complex_abs(_cimg_math_parser& mp) { ++ return cimg::_hypot(_mp_arg(2),_mp_arg(3)); ++ } ++ ++ static double mp_complex_conj(_cimg_math_parser& mp) { ++ const double *ptrs = &_mp_arg(2) + 1; ++ double *ptrd = &_mp_arg(1) + 1; ++ *(ptrd++) = *(ptrs++); ++ *ptrd = -*(ptrs); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_complex_div_sv(_cimg_math_parser& mp) { ++ const double ++ *ptr2 = &_mp_arg(3) + 1, ++ r1 = _mp_arg(2), ++ r2 = *(ptr2++), i2 = *ptr2; ++ double *ptrd = &_mp_arg(1) + 1; ++ const double denom = r2*r2 + i2*i2; ++ *(ptrd++) = r1*r2/denom; ++ *ptrd = -r1*i2/denom; ++ return cimg::type::nan(); ++ } ++ ++ static double mp_complex_div_vv(_cimg_math_parser& mp) { ++ const double ++ *ptr1 = &_mp_arg(2) + 1, *ptr2 = &_mp_arg(3) + 1, ++ r1 = *(ptr1++), i1 = *ptr1, ++ r2 = *(ptr2++), i2 = *ptr2; ++ double *ptrd = &_mp_arg(1) + 1; ++ const double denom = r2*r2 + i2*i2; ++ *(ptrd++) = (r1*r2 + i1*i2)/denom; ++ *ptrd = (r2*i1 - r1*i2)/denom; ++ return cimg::type::nan(); ++ } ++ ++ static double mp_complex_exp(_cimg_math_parser& mp) { ++ double *ptrd = &_mp_arg(1) + 1; ++ const double *ptrs = &_mp_arg(2) + 1, r = *(ptrs++), i = *(ptrs), er = std::exp(r); ++ *(ptrd++) = er*std::cos(i); ++ *(ptrd++) = er*std::sin(i); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_complex_log(_cimg_math_parser& mp) { ++ double *ptrd = &_mp_arg(1) + 1; ++ const double *ptrs = &_mp_arg(2) + 1, r = *(ptrs++), i = *(ptrs); ++ *(ptrd++) = 0.5*std::log(r*r + i*i); ++ *(ptrd++) = std::atan2(i,r); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_complex_mul(_cimg_math_parser& mp) { ++ const double ++ *ptr1 = &_mp_arg(2) + 1, *ptr2 = &_mp_arg(3) + 1, ++ r1 = *(ptr1++), i1 = *ptr1, ++ r2 = *(ptr2++), i2 = *ptr2; ++ double *ptrd = &_mp_arg(1) + 1; ++ *(ptrd++) = r1*r2 - i1*i2; ++ *(ptrd++) = r1*i2 + r2*i1; ++ return cimg::type::nan(); ++ } ++ ++ static void _mp_complex_pow(const double r1, const double i1, ++ const double r2, const double i2, ++ double *ptrd) { ++ double ro, io; ++ if (cimg::abs(i2)<1e-15) { // Exponent is real ++ if (cimg::abs(r1)<1e-15 && cimg::abs(i1)<1e-15) { ++ if (cimg::abs(r2)<1e-15) { ro = 1; io = 0; } ++ else ro = io = 0; ++ } else { ++ const double ++ mod1_2 = r1*r1 + i1*i1, ++ phi1 = std::atan2(i1,r1), ++ modo = std::pow(mod1_2,0.5*r2), ++ phio = r2*phi1; ++ ro = modo*std::cos(phio); ++ io = modo*std::sin(phio); ++ } ++ } else { // Exponent is complex ++ if (cimg::abs(r1)<1e-15 && cimg::abs(i1)<1e-15) ro = io = 0; ++ const double ++ mod1_2 = r1*r1 + i1*i1, ++ phi1 = std::atan2(i1,r1), ++ modo = std::pow(mod1_2,0.5*r2)*std::exp(-i2*phi1), ++ phio = r2*phi1 + 0.5*i2*std::log(mod1_2); ++ ro = modo*std::cos(phio); ++ io = modo*std::sin(phio); ++ } ++ *(ptrd++) = ro; ++ *ptrd = io; ++ } ++ ++ static double mp_complex_pow_ss(_cimg_math_parser& mp) { ++ const double val1 = _mp_arg(2), val2 = _mp_arg(3); ++ double *ptrd = &_mp_arg(1) + 1; ++ _mp_complex_pow(val1,0,val2,0,ptrd); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_complex_pow_sv(_cimg_math_parser& mp) { ++ const double val1 = _mp_arg(2), *ptr2 = &_mp_arg(3) + 1; ++ double *ptrd = &_mp_arg(1) + 1; ++ _mp_complex_pow(val1,0,ptr2[0],ptr2[1],ptrd); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_complex_pow_vs(_cimg_math_parser& mp) { ++ const double *ptr1 = &_mp_arg(2) + 1, val2 = _mp_arg(3); ++ double *ptrd = &_mp_arg(1) + 1; ++ _mp_complex_pow(ptr1[0],ptr1[1],val2,0,ptrd); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_complex_pow_vv(_cimg_math_parser& mp) { ++ const double *ptr1 = &_mp_arg(2) + 1, *ptr2 = &_mp_arg(3) + 1; ++ double *ptrd = &_mp_arg(1) + 1; ++ _mp_complex_pow(ptr1[0],ptr1[1],ptr2[0],ptr2[1],ptrd); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_continue(_cimg_math_parser& mp) { ++ mp.break_type = 2; ++ mp.p_code = mp.p_break - 1; ++ return cimg::type::nan(); ++ } ++ ++ static double mp_cos(_cimg_math_parser& mp) { ++ return std::cos(_mp_arg(2)); ++ } ++ ++ static double mp_cosh(_cimg_math_parser& mp) { ++ return std::cosh(_mp_arg(2)); ++ } ++ ++ static double mp_critical(_cimg_math_parser& mp) { ++ const double res = _mp_arg(1); ++ cimg_pragma_openmp(critical(mp_critical)) ++ { ++ for (const CImg *const p_end = ++mp.p_code + mp.opcode[2]; ++ mp.p_code_data; ++ const ulongT target = mp.opcode[1]; ++ mp.mem[target] = _cimg_mp_defunc(mp); ++ } ++ } ++ --mp.p_code; ++ return res; ++ } ++ ++ static double mp_crop(_cimg_math_parser& mp) { ++ double *ptrd = &_mp_arg(1) + 1; ++ const int x = (int)_mp_arg(3), y = (int)_mp_arg(4), z = (int)_mp_arg(5), c = (int)_mp_arg(6); ++ const unsigned int ++ dx = (unsigned int)mp.opcode[7], ++ dy = (unsigned int)mp.opcode[8], ++ dz = (unsigned int)mp.opcode[9], ++ dc = (unsigned int)mp.opcode[10]; ++ const bool boundary_conditions = (bool)_mp_arg(11); ++ unsigned int ind = (unsigned int)mp.opcode[2]; ++ if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); ++ const CImg &img = ind==~0U?mp.imgin:mp.listin[ind]; ++ if (!img) std::memset(ptrd,0,dx*dy*dz*dc*sizeof(double)); ++ else CImg(ptrd,dx,dy,dz,dc,true) = img.get_crop(x,y,z,c, ++ x + dx - 1,y + dy - 1, ++ z + dz - 1,c + dc - 1, ++ boundary_conditions); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_cross(_cimg_math_parser& mp) { ++ CImg ++ vout(&_mp_arg(1) + 1,1,3,1,1,true), ++ v1(&_mp_arg(2) + 1,1,3,1,1,true), ++ v2(&_mp_arg(3) + 1,1,3,1,1,true); ++ (vout = v1).cross(v2); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_cut(_cimg_math_parser& mp) { ++ double val = _mp_arg(2), cmin = _mp_arg(3), cmax = _mp_arg(4); ++ return valcmax?cmax:val; ++ } ++ ++ static double mp_date(_cimg_math_parser& mp) { ++ const unsigned int ++ _arg = (unsigned int)mp.opcode[3], ++ _siz = (unsigned int)mp.opcode[4], ++ siz = _siz?_siz:1; ++ const double *const arg_in = _arg==~0U?0:&_mp_arg(3) + (_siz?1:0); ++ double *const arg_out = &_mp_arg(1) + (_siz?1:0); ++ if (arg_in) std::memcpy(arg_out,arg_in,siz*sizeof(double)); ++ else for (unsigned int i = 0; i filename(mp.opcode[2] - 5); ++ if (filename) { ++ const ulongT *ptrs = mp.opcode._data + 5; ++ cimg_for(filename,ptrd,char) *ptrd = (char)*(ptrs++); ++ cimg::fdate(filename,arg_out,siz); ++ } else cimg::date(arg_out,siz); ++ return _siz?cimg::type::nan():*arg_out; ++ } ++ ++ static double mp_debug(_cimg_math_parser& mp) { ++ CImg expr(mp.opcode[2] - 4); ++ const ulongT *ptrs = mp.opcode._data + 4; ++ cimg_for(expr,ptrd,char) *ptrd = (char)*(ptrs++); ++ cimg::strellipsize(expr); ++ const ulongT g_target = mp.opcode[1]; ++ ++#ifndef cimg_use_openmp ++ const unsigned int n_thread = 0; ++#else ++ const unsigned int n_thread = omp_get_thread_num(); ++#endif ++ cimg_pragma_openmp(critical(mp_debug)) ++ { ++ std::fprintf(cimg::output(), ++ "\n[" cimg_appname "_math_parser] %p[thread #%u]:%*c" ++ "Start debugging expression '%s', code length %u -> mem[%u] (memsize: %u)", ++ (void*)&mp,n_thread,mp.debug_indent,' ', ++ expr._data,(unsigned int)mp.opcode[3],(unsigned int)g_target,mp.mem._width); ++ std::fflush(cimg::output()); ++ mp.debug_indent+=3; ++ } ++ const CImg *const p_end = (++mp.p_code) + mp.opcode[3]; ++ CImg _op; ++ for ( ; mp.p_code &op = *mp.p_code; ++ mp.opcode._data = op._data; ++ ++ _op.assign(1,op._height - 1); ++ const ulongT *ptrs = op._data + 1; ++ for (ulongT *ptrd = _op._data, *const ptrde = _op._data + _op._height; ptrd mem[%u] = %g", ++ (void*)&mp,n_thread,mp.debug_indent,' ', ++ (void*)mp.opcode._data,(void*)*mp.opcode,_op.value_string().data(), ++ (unsigned int)target,mp.mem[target]); ++ std::fflush(cimg::output()); ++ } ++ } ++ cimg_pragma_openmp(critical(mp_debug)) ++ { ++ mp.debug_indent-=3; ++ std::fprintf(cimg::output(), ++ "\n[" cimg_appname "_math_parser] %p[thread #%u]:%*c" ++ "End debugging expression '%s' -> mem[%u] = %g (memsize: %u)", ++ (void*)&mp,n_thread,mp.debug_indent,' ', ++ expr._data,(unsigned int)g_target,mp.mem[g_target],mp.mem._width); ++ std::fflush(cimg::output()); ++ } ++ --mp.p_code; ++ return mp.mem[g_target]; ++ } ++ ++ static double mp_decrement(_cimg_math_parser& mp) { ++ return _mp_arg(2) - 1; ++ } ++ ++ static double mp_det(_cimg_math_parser& mp) { ++ const double *ptrs = &_mp_arg(2) + 1; ++ const unsigned int k = (unsigned int)mp.opcode[3]; ++ return CImg(ptrs,k,k,1,1,true).det(); ++ } ++ ++ static double mp_diag(_cimg_math_parser& mp) { ++ double *ptrd = &_mp_arg(1) + 1; ++ const double *ptrs = &_mp_arg(2) + 1; ++ const unsigned int k = (unsigned int)mp.opcode[3]; ++ CImg(ptrd,k,k,1,1,true) = CImg(ptrs,1,k,1,1,true).get_diagonal(); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_display_memory(_cimg_math_parser& mp) { ++ cimg::unused(mp); ++ std::fputc('\n',cimg::output()); ++ mp.mem.display("[" cimg_appname "_math_parser] Memory snapshot"); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_display(_cimg_math_parser& mp) { ++ const unsigned int ++ _siz = (unsigned int)mp.opcode[3], ++ siz = _siz?_siz:1; ++ const double *const ptr = &_mp_arg(1) + (_siz?1:0); ++ const int ++ w = (int)_mp_arg(4), ++ h = (int)_mp_arg(5), ++ d = (int)_mp_arg(6), ++ s = (int)_mp_arg(7); ++ CImg img; ++ if (w>0 && h>0 && d>0 && s>0) { ++ if ((unsigned int)w*h*d*s<=siz) img.assign(ptr,w,h,d,s,true); ++ else img.assign(ptr,siz).resize(w,h,d,s,-1); ++ } else img.assign(ptr,1,siz,1,1,true); ++ ++ CImg expr(mp.opcode[2] - 8); ++ const ulongT *ptrs = mp.opcode._data + 8; ++ cimg_for(expr,ptrd,char) *ptrd = (char)*(ptrs++); ++ ((CImg::string("[" cimg_appname "_math_parser] ",false,true),expr)>'x').move_to(expr); ++ cimg::strellipsize(expr); ++ std::fputc('\n',cimg::output()); ++ img.display(expr._data); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_div(_cimg_math_parser& mp) { ++ return _mp_arg(2)/_mp_arg(3); ++ } ++ ++ static double mp_dot(_cimg_math_parser& mp) { ++ const unsigned int siz = (unsigned int)mp.opcode[4]; ++ return CImg(&_mp_arg(2) + 1,1,siz,1,1,true). ++ dot(CImg(&_mp_arg(3) + 1,1,siz,1,1,true)); ++ } ++ ++ static double mp_dowhile(_cimg_math_parser& mp) { ++ const ulongT ++ mem_body = mp.opcode[1], ++ mem_cond = mp.opcode[2]; ++ const CImg ++ *const p_body = ++mp.p_code, ++ *const p_cond = p_body + mp.opcode[3], ++ *const p_end = p_cond + mp.opcode[4]; ++ const unsigned int vsiz = (unsigned int)mp.opcode[5]; ++ if (mp.opcode[6]) { // Set default value for result and condition if necessary ++ if (vsiz) CImg(&mp.mem[mem_body] + 1,vsiz,1,1,1,true).fill(cimg::type::nan()); ++ else mp.mem[mem_body] = cimg::type::nan(); ++ } ++ if (mp.opcode[7]) mp.mem[mem_cond] = 0; ++ ++ const unsigned int _break_type = mp.break_type; ++ mp.break_type = 0; ++ do { ++ for (mp.p_code = p_body; mp.p_code_data; ++ const ulongT target = mp.opcode[1]; ++ mp.mem[target] = _cimg_mp_defunc(mp); ++ } ++ if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0; ++ for (mp.p_code = p_cond; mp.p_code_data; ++ const ulongT target = mp.opcode[1]; ++ mp.mem[target] = _cimg_mp_defunc(mp); ++ } ++ if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0; ++ } while (mp.mem[mem_cond]); ++ mp.break_type = _break_type; ++ mp.p_code = p_end - 1; ++ return mp.mem[mem_body]; ++ } ++ ++ static double mp_draw(_cimg_math_parser& mp) { ++ const int x = (int)_mp_arg(4), y = (int)_mp_arg(5), z = (int)_mp_arg(6), c = (int)_mp_arg(7); ++ unsigned int ind = (unsigned int)mp.opcode[3]; ++ ++ if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(3),mp.listin.width()); ++ CImg &img = ind==~0U?mp.imgout:mp.listout[ind]; ++ unsigned int ++ dx = (unsigned int)mp.opcode[8], ++ dy = (unsigned int)mp.opcode[9], ++ dz = (unsigned int)mp.opcode[10], ++ dc = (unsigned int)mp.opcode[11]; ++ dx = dx==~0U?img._width:(unsigned int)_mp_arg(8); ++ dy = dy==~0U?img._height:(unsigned int)_mp_arg(9); ++ dz = dz==~0U?img._depth:(unsigned int)_mp_arg(10); ++ dc = dc==~0U?img._spectrum:(unsigned int)_mp_arg(11); ++ ++ const ulongT sizS = mp.opcode[2]; ++ if (sizS<(ulongT)dx*dy*dz*dc) ++ throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'draw()': " ++ "Sprite dimension (%lu values) and specified sprite geometry (%u,%u,%u,%u) " ++ "(%lu values) do not match.", ++ mp.imgin.pixel_type(),sizS,dx,dy,dz,dc,(ulongT)dx*dy*dz*dc); ++ CImg S(&_mp_arg(1) + 1,dx,dy,dz,dc,true); ++ const float opacity = (float)_mp_arg(12); ++ ++ if (img._data) { ++ if (mp.opcode[13]!=~0U) { // Opacity mask specified ++ const ulongT sizM = mp.opcode[14]; ++ if (sizM<(ulongT)dx*dy*dz) ++ throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'draw()': " ++ "Mask dimension (%lu values) and specified sprite geometry (%u,%u,%u,%u) " ++ "(%lu values) do not match.", ++ mp.imgin.pixel_type(),sizS,dx,dy,dz,dc,(ulongT)dx*dy*dz*dc); ++ const CImg M(&_mp_arg(13) + 1,dx,dy,dz,(unsigned int)(sizM/(dx*dy*dz)),true); ++ img.draw_image(x,y,z,c,S,M,opacity,(float)_mp_arg(15)); ++ } else img.draw_image(x,y,z,c,S,opacity); ++ } ++ return cimg::type::nan(); ++ } ++ ++ static double mp_echo(_cimg_math_parser& mp) { ++ const unsigned int nb_args = (unsigned int)(mp.opcode[2] - 3)/2; ++ CImgList _str; ++ CImg it; ++ for (unsigned int n = 0; n string ++ const double *ptr = &_mp_arg(3 + 2*n) + 1; ++ unsigned int l = 0; ++ while (l(ptr,l,1,1,1,true).move_to(_str); ++ } else { // Scalar argument -> number ++ it.assign(256); ++ cimg_snprintf(it,it._width,"%.17g",_mp_arg(3 + 2*n)); ++ CImg::string(it,false,true).move_to(_str); ++ } ++ } ++ CImg(1,1,1,1,0).move_to(_str); ++ const CImg str = _str>'x'; ++ std::fprintf(cimg::output(),"\n%s",str._data); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_eq(_cimg_math_parser& mp) { ++ return (double)(_mp_arg(2)==_mp_arg(3)); ++ } ++ ++ static double mp_ext(_cimg_math_parser& mp) { ++ const unsigned int nb_args = (unsigned int)(mp.opcode[2] - 3)/2; ++ CImgList _str; ++ CImg it; ++ for (unsigned int n = 0; n string ++ const double *ptr = &_mp_arg(3 + 2*n) + 1; ++ unsigned int l = 0; ++ while (l(ptr,l,1,1,1,true).move_to(_str); ++ } else { // Scalar argument -> number ++ it.assign(256); ++ cimg_snprintf(it,it._width,"%.17g",_mp_arg(3 + 2*n)); ++ CImg::string(it,false,true).move_to(_str); ++ } ++ } ++ CImg(1,1,1,1,0).move_to(_str); ++ CImg str = _str>'x'; ++#ifdef cimg_mp_ext_function ++ cimg_mp_ext_function(str); ++#endif ++ return cimg::type::nan(); ++ } ++ ++ static double mp_exp(_cimg_math_parser& mp) { ++ return std::exp(_mp_arg(2)); ++ } ++ ++ static double mp_eye(_cimg_math_parser& mp) { ++ double *ptrd = &_mp_arg(1) + 1; ++ const unsigned int k = (unsigned int)mp.opcode[2]; ++ CImg(ptrd,k,k,1,1,true).identity_matrix(); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_factorial(_cimg_math_parser& mp) { ++ return cimg::factorial(_mp_arg(2)); ++ } ++ ++ static double mp_fibonacci(_cimg_math_parser& mp) { ++ return cimg::fibonacci((int)_mp_arg(2)); ++ } ++ ++ static double mp_find(_cimg_math_parser& mp) { ++ const bool is_forward = (bool)_mp_arg(5); ++ const ulongT siz = (ulongT)mp.opcode[3]; ++ longT ind = (longT)(mp.opcode[6]!=_cimg_mp_slot_nan?_mp_arg(6):is_forward?0:siz - 1); ++ if (ind<0 || ind>=(longT)siz) return -1.; ++ const double ++ *const ptrb = &_mp_arg(2) + 1, ++ *const ptre = ptrb + siz, ++ val = _mp_arg(4), ++ *ptr = ptrb + ind; ++ ++ // Forward search ++ if (is_forward) { ++ while (ptr=ptrb && *ptr!=val) --ptr; ++ return ptr=(longT)siz1) return -1.; ++ const double ++ *const ptr1b = &_mp_arg(2) + 1, ++ *const ptr1e = ptr1b + siz1, ++ *const ptr2b = &_mp_arg(4) + 1, ++ *const ptr2e = ptr2b + siz2, ++ *ptr1 = ptr1b + ind, ++ *p1 = 0, ++ *p2 = 0; ++ ++ // Forward search. ++ if (is_forward) { ++ do { ++ while (ptr1=ptr1b && *ptr1!=*ptr2b) --ptr1; ++ p1 = ptr1 + 1; ++ p2 = ptr2b + 1; ++ while (p1=ptr1b); ++ return p2 ++ *const p_init = ++mp.p_code, ++ *const p_cond = p_init + mp.opcode[4], ++ *const p_body = p_cond + mp.opcode[5], ++ *const p_post = p_body + mp.opcode[6], ++ *const p_end = p_post + mp.opcode[7]; ++ const unsigned int vsiz = (unsigned int)mp.opcode[2]; ++ bool is_cond = false; ++ if (mp.opcode[8]) { // Set default value for result and condition if necessary ++ if (vsiz) CImg(&mp.mem[mem_body] + 1,vsiz,1,1,1,true).fill(cimg::type::nan()); ++ else mp.mem[mem_body] = cimg::type::nan(); ++ } ++ if (mp.opcode[9]) mp.mem[mem_cond] = 0; ++ const unsigned int _break_type = mp.break_type; ++ mp.break_type = 0; ++ ++ for (mp.p_code = p_init; mp.p_code_data; ++ const ulongT target = mp.opcode[1]; ++ mp.mem[target] = _cimg_mp_defunc(mp); ++ } ++ ++ if (!mp.break_type) do { ++ for (mp.p_code = p_cond; mp.p_code_data; ++ const ulongT target = mp.opcode[1]; ++ mp.mem[target] = _cimg_mp_defunc(mp); ++ } ++ if (mp.break_type==1) break; ++ ++ is_cond = (bool)mp.mem[mem_cond]; ++ if (is_cond && !mp.break_type) { ++ for (mp.p_code = p_body; mp.p_code_data; ++ const ulongT target = mp.opcode[1]; ++ mp.mem[target] = _cimg_mp_defunc(mp); ++ } ++ if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0; ++ ++ for (mp.p_code = p_post; mp.p_code_data; ++ const ulongT target = mp.opcode[1]; ++ mp.mem[target] = _cimg_mp_defunc(mp); ++ } ++ if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0; ++ } ++ } while (is_cond); ++ ++ mp.break_type = _break_type; ++ mp.p_code = p_end - 1; ++ return mp.mem[mem_body]; ++ } ++ ++ static double mp_fsize(_cimg_math_parser& mp) { ++ const CImg filename(mp.opcode._data + 3,mp.opcode[2] - 3); ++ return (double)cimg::fsize(filename); ++ } ++ ++ static double mp_g(_cimg_math_parser& mp) { ++ cimg::unused(mp); ++ return cimg::grand(); ++ } ++ ++ static double mp_gauss(_cimg_math_parser& mp) { ++ const double x = _mp_arg(2), s = _mp_arg(3); ++ return std::exp(-x*x/(2*s*s))/std::sqrt(2*s*s*cimg::PI); ++ } ++ ++ static double mp_gcd(_cimg_math_parser& mp) { ++ return cimg::gcd((long)_mp_arg(2),(long)_mp_arg(3)); ++ } ++ ++ static double mp_gt(_cimg_math_parser& mp) { ++ return (double)(_mp_arg(2)>_mp_arg(3)); ++ } ++ ++ static double mp_gte(_cimg_math_parser& mp) { ++ return (double)(_mp_arg(2)>=_mp_arg(3)); ++ } ++ ++ static double mp_i(_cimg_math_parser& mp) { ++ return (double)mp.imgin.atXYZC((int)mp.mem[_cimg_mp_slot_x],(int)mp.mem[_cimg_mp_slot_y], ++ (int)mp.mem[_cimg_mp_slot_z],(int)mp.mem[_cimg_mp_slot_c],(T)0); ++ } ++ ++ static double mp_if(_cimg_math_parser& mp) { ++ const bool is_cond = (bool)_mp_arg(2); ++ const ulongT ++ mem_left = mp.opcode[3], ++ mem_right = mp.opcode[4]; ++ const CImg ++ *const p_right = ++mp.p_code + mp.opcode[5], ++ *const p_end = p_right + mp.opcode[6]; ++ const unsigned int vtarget = (unsigned int)mp.opcode[1], vsiz = (unsigned int)mp.opcode[7]; ++ if (is_cond) for ( ; mp.p_code_data; ++ const ulongT target = mp.opcode[1]; ++ mp.mem[target] = _cimg_mp_defunc(mp); ++ } ++ else for (mp.p_code = p_right; mp.p_code_data; ++ const ulongT target = mp.opcode[1]; ++ mp.mem[target] = _cimg_mp_defunc(mp); ++ } ++ if (mp.p_code==mp.p_break) --mp.p_code; ++ else mp.p_code = p_end - 1; ++ if (vsiz) std::memcpy(&mp.mem[vtarget] + 1,&mp.mem[is_cond?mem_left:mem_right] + 1,sizeof(double)*vsiz); ++ return mp.mem[is_cond?mem_left:mem_right]; ++ } ++ ++ static double mp_image_d(_cimg_math_parser& mp) { ++ unsigned int ind = (unsigned int)mp.opcode[2]; ++ if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); ++ const CImg &img = ind==~0U?mp.imgout:mp.listout[ind]; ++ return (double)img.depth(); ++ } ++ ++ static double mp_image_display(_cimg_math_parser& mp) { ++ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listout.width()); ++ cimg::mutex(6); ++ CImg &img = mp.listout[ind]; ++ CImg title(256); ++ std::fputc('\n',cimg::output()); ++ cimg_snprintf(title,title._width,"[ Image #%u ]",ind); ++ img.display(title); ++ cimg::mutex(6,0); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_image_h(_cimg_math_parser& mp) { ++ unsigned int ind = (unsigned int)mp.opcode[2]; ++ if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); ++ const CImg &img = ind==~0U?mp.imgout:mp.listout[ind]; ++ return (double)img.height(); ++ } ++ ++ static double mp_image_median(_cimg_math_parser& mp) { ++ unsigned int ind = (unsigned int)mp.opcode[2]; ++ if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); ++ const CImg &img = ind==~0U?mp.imgout:mp.listout[ind]; ++ return (double)img.median(); ++ } ++ ++ static double mp_image_print(_cimg_math_parser& mp) { ++ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listout.width()); ++ cimg::mutex(6); ++ CImg &img = mp.listout[ind]; ++ CImg title(256); ++ std::fputc('\n',cimg::output()); ++ cimg_snprintf(title,title._width,"[ Image #%u ]",ind); ++ img.print(title); ++ cimg::mutex(6,0); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_image_resize(_cimg_math_parser& mp) { ++ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listout.width()); ++ cimg::mutex(6); ++ CImg &img = mp.listout[ind]; ++ const double ++ _w = mp.opcode[3]==~0U?-100:_mp_arg(3), ++ _h = mp.opcode[4]==~0U?-100:_mp_arg(4), ++ _d = mp.opcode[5]==~0U?-100:_mp_arg(5), ++ _s = mp.opcode[6]==~0U?-100:_mp_arg(6); ++ const unsigned int ++ w = (unsigned int)(_w>=0?_w:-_w*img.width()/100), ++ h = (unsigned int)(_h>=0?_h:-_h*img.height()/100), ++ d = (unsigned int)(_d>=0?_d:-_d*img.depth()/100), ++ s = (unsigned int)(_s>=0?_s:-_s*img.spectrum()/100), ++ interp = (int)_mp_arg(7); ++ if (mp.is_fill && img._data==mp.imgout._data) { ++ cimg::mutex(6,0); ++ throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'resize()': " ++ "Cannot both fill and resize image (%u,%u,%u,%u) " ++ "to new dimensions (%u,%u,%u,%u).", ++ img.pixel_type(),img._width,img._height,img._depth,img._spectrum,w,h,d,s); ++ } ++ const unsigned int ++ boundary = (int)_mp_arg(8); ++ const float ++ cx = (float)_mp_arg(9), ++ cy = (float)_mp_arg(10), ++ cz = (float)_mp_arg(11), ++ cc = (float)_mp_arg(12); ++ img.resize(w,h,d,s,interp,boundary,cx,cy,cz,cc); ++ cimg::mutex(6,0); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_image_s(_cimg_math_parser& mp) { ++ unsigned int ind = (unsigned int)mp.opcode[2]; ++ if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); ++ const CImg &img = ind==~0U?mp.imgout:mp.listout[ind]; ++ return (double)img.spectrum(); ++ } ++ ++ static double mp_image_sort(_cimg_math_parser& mp) { ++ const bool is_increasing = (bool)_mp_arg(3); ++ const unsigned int ++ ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listout.width()), ++ axis = (unsigned int)_mp_arg(4); ++ cimg::mutex(6); ++ CImg &img = mp.listout[ind]; ++ img.sort(is_increasing, ++ axis==0 || axis=='x'?'x': ++ axis==1 || axis=='y'?'y': ++ axis==2 || axis=='z'?'z': ++ axis==3 || axis=='c'?'c':0); ++ cimg::mutex(6,0); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_image_stats(_cimg_math_parser& mp) { ++ double *ptrd = &_mp_arg(1) + 1; ++ unsigned int ind = (unsigned int)mp.opcode[2]; ++ if (ind==~0U) CImg(ptrd,14,1,1,1,true) = mp.imgout.get_stats(); ++ else { ++ ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); ++ CImg(ptrd,14,1,1,1,true) = mp.listout[ind].get_stats(); ++ } ++ return cimg::type::nan(); ++ } ++ ++ static double mp_image_w(_cimg_math_parser& mp) { ++ unsigned int ind = (unsigned int)mp.opcode[2]; ++ if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); ++ const CImg &img = ind==~0U?mp.imgout:mp.listout[ind]; ++ return (double)img.width(); ++ } ++ ++ static double mp_image_wh(_cimg_math_parser& mp) { ++ unsigned int ind = (unsigned int)mp.opcode[2]; ++ if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); ++ const CImg &img = ind==~0U?mp.imgout:mp.listout[ind]; ++ return (double)img.width()*img.height(); ++ } ++ ++ static double mp_image_whd(_cimg_math_parser& mp) { ++ unsigned int ind = (unsigned int)mp.opcode[2]; ++ if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); ++ const CImg &img = ind==~0U?mp.imgout:mp.listout[ind]; ++ return (double)img.width()*img.height()*img.depth(); ++ } ++ ++ static double mp_image_whds(_cimg_math_parser& mp) { ++ unsigned int ind = (unsigned int)mp.opcode[2]; ++ if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); ++ const CImg &img = ind==~0U?mp.imgout:mp.listout[ind]; ++ return (double)img.width()*img.height()*img.depth()*img.spectrum(); ++ } ++ ++ static double mp_increment(_cimg_math_parser& mp) { ++ return _mp_arg(2) + 1; ++ } ++ ++ static double mp_int(_cimg_math_parser& mp) { ++ return (double)(longT)_mp_arg(2); ++ } ++ ++ static double mp_ioff(_cimg_math_parser& mp) { ++ const unsigned int ++ boundary_conditions = (unsigned int)_mp_arg(3); ++ const CImg &img = mp.imgin; ++ const longT ++ off = (longT)_mp_arg(2), ++ whds = (longT)img.size(); ++ if (off>=0 && off::is_inf(_mp_arg(2)); ++ } ++ ++ static double mp_isint(_cimg_math_parser& mp) { ++ return (double)(cimg::mod(_mp_arg(2),1.0)==0); ++ } ++ ++ static double mp_isnan(_cimg_math_parser& mp) { ++ return (double)cimg::type::is_nan(_mp_arg(2)); ++ } ++ ++ static double mp_ixyzc(_cimg_math_parser& mp) { ++ const unsigned int ++ interpolation = (unsigned int)_mp_arg(6), ++ boundary_conditions = (unsigned int)_mp_arg(7); ++ const CImg &img = mp.imgin; ++ const double ++ x = _mp_arg(2), y = _mp_arg(3), ++ z = _mp_arg(4), c = _mp_arg(5); ++ if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation ++ case 3 : { // Mirror ++ const int ++ w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(), ++ mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), ++ mz = cimg::mod((int)z,d2), mc = cimg::mod((int)c,s2); ++ return (double)img(mx &img = mp.imgin; ++ const longT ++ off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(2), ++ whds = (longT)img.size(); ++ if (off>=0 && off &img = mp.imgin; ++ const double ++ ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], ++ oz = mp.mem[_cimg_mp_slot_z], oc = mp.mem[_cimg_mp_slot_c], ++ x = ox + _mp_arg(2), y = oy + _mp_arg(3), ++ z = oz + _mp_arg(4), c = oc + _mp_arg(5); ++ if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation ++ case 3 : { // Mirror ++ const int ++ w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(), ++ mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), ++ mz = cimg::mod((int)z,d2), mc = cimg::mod((int)c,s2); ++ return (double)img(mx vals(i_end - 4); ++ double *p = vals.data(); ++ for (unsigned int i = 4; i &img = mp.listin[indi]; ++ const bool is_forward = (bool)_mp_arg(4); ++ const ulongT siz = (ulongT)img.size(); ++ longT ind = (longT)(mp.opcode[5]!=_cimg_mp_slot_nan?_mp_arg(5):is_forward?0:siz - 1); ++ if (ind<0 || ind>=(longT)siz) return -1.; ++ const T ++ *const ptrb = img.data(), ++ *const ptre = img.end(), ++ *ptr = ptrb + ind; ++ const double val = _mp_arg(3); ++ ++ // Forward search ++ if (is_forward) { ++ while (ptr=ptrb && (double)*ptr!=val) --ptr; ++ return ptr &img = mp.listin[indi]; ++ const bool is_forward = (bool)_mp_arg(5); ++ const ulongT ++ siz1 = (ulongT)img.size(), ++ siz2 = (ulongT)mp.opcode[4]; ++ longT ind = (longT)(mp.opcode[6]!=_cimg_mp_slot_nan?_mp_arg(6):is_forward?0:siz1 - 1); ++ if (ind<0 || ind>=(longT)siz1) return -1.; ++ const T ++ *const ptr1b = img.data(), ++ *const ptr1e = ptr1b + siz1, ++ *ptr1 = ptr1b + ind, ++ *p1 = 0; ++ const double ++ *const ptr2b = &_mp_arg(3) + 1, ++ *const ptr2e = ptr2b + siz2, ++ *p2 = 0; ++ ++ // Forward search. ++ if (is_forward) { ++ do { ++ while (ptr1=ptr1b && *ptr1!=*ptr2b) --ptr1; ++ p1 = ptr1 + 1; ++ p2 = ptr2b + 1; ++ while (p1=ptr1b); ++ return p2 &img = mp.listin[ind]; ++ const longT ++ off = (longT)_mp_arg(3), ++ whds = (longT)img.size(); ++ if (off>=0 && off &img = mp.listin[ind]; ++ const double ++ x = _mp_arg(3), y = _mp_arg(4), ++ z = _mp_arg(5), c = _mp_arg(6); ++ if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation ++ case 3 : { // Mirror ++ const int ++ w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(), ++ mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), ++ mz = cimg::mod((int)z,d2), mc = cimg::mod((int)c,s2); ++ return (double)img(mx &img = mp.listin[ind]; ++ const longT ++ off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(3), ++ whds = (longT)img.size(); ++ if (off>=0 && off &img = mp.listin[ind]; ++ const double ++ ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], ++ oz = mp.mem[_cimg_mp_slot_z], oc = mp.mem[_cimg_mp_slot_c], ++ x = ox + _mp_arg(3), y = oy + _mp_arg(4), ++ z = oz + _mp_arg(5), c = oc + _mp_arg(6); ++ if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation ++ case 3 : { // Mirror ++ const int ++ w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(), ++ mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), ++ mz = cimg::mod((int)z,d2), mc = cimg::mod((int)c,s2); ++ return (double)img(mx::vector(mp.listin[ind].median()).move_to(mp.list_median[ind]); ++ return *mp.list_median[ind]; ++ } ++ ++ static double mp_list_set_ioff(_cimg_math_parser& mp) { ++ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); ++ CImg &img = mp.listout[ind]; ++ const longT ++ off = (longT)_mp_arg(3), ++ whds = (longT)img.size(); ++ const double val = _mp_arg(1); ++ if (off>=0 && off &img = mp.listout[ind]; ++ const int ++ x = (int)_mp_arg(3), y = (int)_mp_arg(4), ++ z = (int)_mp_arg(5), c = (int)_mp_arg(6); ++ const double val = _mp_arg(1); ++ if (x>=0 && x=0 && y=0 && z=0 && c &img = mp.listout[ind]; ++ const int ++ ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], ++ oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; ++ const longT ++ off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(3), ++ whds = (longT)img.size(); ++ const double val = _mp_arg(1); ++ if (off>=0 && off &img = mp.listout[ind]; ++ const double ++ ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], ++ oz = mp.mem[_cimg_mp_slot_z], oc = mp.mem[_cimg_mp_slot_c]; ++ const int ++ x = (int)(ox + _mp_arg(3)), y = (int)(oy + _mp_arg(4)), ++ z = (int)(oz + _mp_arg(5)), c = (int)(oc + _mp_arg(6)); ++ const double val = _mp_arg(1); ++ if (x>=0 && x=0 && y=0 && z=0 && c &img = mp.listout[ind]; ++ const longT ++ off = (longT)_mp_arg(3), ++ whd = (longT)img.width()*img.height()*img.depth(); ++ const T val = (T)_mp_arg(1); ++ if (off>=0 && off &img = mp.listout[ind]; ++ const longT ++ off = (longT)_mp_arg(3), ++ whd = (longT)img.width()*img.height()*img.depth(); ++ const double *ptrs = &_mp_arg(1) + 1; ++ if (off>=0 && off::nan(); ++ } ++ ++ static double mp_list_set_Ixyz_s(_cimg_math_parser& mp) { ++ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); ++ CImg &img = mp.listout[ind]; ++ const int ++ x = (int)_mp_arg(3), ++ y = (int)_mp_arg(4), ++ z = (int)_mp_arg(5); ++ const T val = (T)_mp_arg(1); ++ if (x>=0 && x=0 && y=0 && z &img = mp.listout[ind]; ++ const int ++ x = (int)_mp_arg(3), ++ y = (int)_mp_arg(4), ++ z = (int)_mp_arg(5); ++ const double *ptrs = &_mp_arg(1) + 1; ++ if (x>=0 && x=0 && y=0 && z::nan(); ++ } ++ ++ static double mp_list_set_Joff_s(_cimg_math_parser& mp) { ++ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); ++ CImg &img = mp.listout[ind]; ++ const int ++ ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], ++ oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; ++ const longT ++ off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(3), ++ whd = (longT)img.width()*img.height()*img.depth(); ++ const T val = (T)_mp_arg(1); ++ if (off>=0 && off &img = mp.listout[ind]; ++ const int ++ ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], ++ oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; ++ const longT ++ off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(3), ++ whd = (longT)img.width()*img.height()*img.depth(); ++ const double *ptrs = &_mp_arg(1) + 1; ++ if (off>=0 && off::nan(); ++ } ++ ++ static double mp_list_set_Jxyz_s(_cimg_math_parser& mp) { ++ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); ++ CImg &img = mp.listout[ind]; ++ const double ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z]; ++ const int ++ x = (int)(ox + _mp_arg(3)), ++ y = (int)(oy + _mp_arg(4)), ++ z = (int)(oz + _mp_arg(5)); ++ const T val = (T)_mp_arg(1); ++ if (x>=0 && x=0 && y=0 && z &img = mp.listout[ind]; ++ const double ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z]; ++ const int ++ x = (int)(ox + _mp_arg(3)), ++ y = (int)(oy + _mp_arg(4)), ++ z = (int)(oz + _mp_arg(5)); ++ const double *ptrs = &_mp_arg(1) + 1; ++ if (x>=0 && x=0 && y=0 && z::nan(); ++ } ++ ++ static double mp_list_spectrum(_cimg_math_parser& mp) { ++ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); ++ return (double)mp.listin[ind]._spectrum; ++ } ++ ++ static double mp_list_stats(_cimg_math_parser& mp) { ++ const unsigned int ++ ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), ++ k = (unsigned int)mp.opcode[3]; ++ if (!mp.list_stats) mp.list_stats.assign(mp.listin._width); ++ if (!mp.list_stats[ind]) mp.list_stats[ind].assign(1,14,1,1,0).fill(mp.listin[ind].get_stats(),false); ++ return mp.list_stats(ind,k); ++ } ++ ++ static double mp_list_wh(_cimg_math_parser& mp) { ++ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); ++ return (double)mp.listin[ind]._width*mp.listin[ind]._height; ++ } ++ ++ static double mp_list_whd(_cimg_math_parser& mp) { ++ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); ++ return (double)mp.listin[ind]._width*mp.listin[ind]._height*mp.listin[ind]._depth; ++ } ++ ++ static double mp_list_whds(_cimg_math_parser& mp) { ++ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); ++ return (double)mp.listin[ind]._width*mp.listin[ind]._height*mp.listin[ind]._depth*mp.listin[ind]._spectrum; ++ } ++ ++ static double mp_list_width(_cimg_math_parser& mp) { ++ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); ++ return (double)mp.listin[ind]._width; ++ } ++ ++ static double mp_list_Ioff(_cimg_math_parser& mp) { ++ double *ptrd = &_mp_arg(1) + 1; ++ const unsigned int ++ ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), ++ boundary_conditions = (unsigned int)_mp_arg(4), ++ vsiz = (unsigned int)mp.opcode[5]; ++ const CImg &img = mp.listin[ind]; ++ const longT ++ off = (longT)_mp_arg(3), ++ whd = (longT)img.width()*img.height()*img.depth(); ++ const T *ptrs; ++ if (off>=0 && off::nan(); ++ } ++ if (img._data) switch (boundary_conditions) { ++ case 3 : { // Mirror ++ const longT whd2 = 2*whd, moff = cimg::mod(off,whd2); ++ ptrs = &img[moff::nan(); ++ } ++ case 2 : // Periodic ++ ptrs = &img[cimg::mod(off,whd)]; ++ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } ++ return cimg::type::nan(); ++ case 1 : // Neumann ++ ptrs = off<0?&img[0]:&img[whd - 1]; ++ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } ++ return cimg::type::nan(); ++ default : // Dirichlet ++ std::memset(ptrd,0,vsiz*sizeof(double)); ++ return cimg::type::nan(); ++ } ++ std::memset(ptrd,0,vsiz*sizeof(double)); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_list_Ixyz(_cimg_math_parser& mp) { ++ double *ptrd = &_mp_arg(1) + 1; ++ const unsigned int ++ ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), ++ interpolation = (unsigned int)_mp_arg(6), ++ boundary_conditions = (unsigned int)_mp_arg(7), ++ vsiz = (unsigned int)mp.opcode[8]; ++ const CImg &img = mp.listin[ind]; ++ const double x = _mp_arg(3), y = _mp_arg(4), z = _mp_arg(5); ++ const ulongT whd = (ulongT)img._width*img._height*img._depth; ++ const T *ptrs; ++ if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation ++ case 3 : { // Mirror ++ const int ++ w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), ++ mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), mz = cimg::mod((int)z,d2), ++ cx = mx::nan(); ++ } ++ ++ static double mp_list_Joff(_cimg_math_parser& mp) { ++ double *ptrd = &_mp_arg(1) + 1; ++ const unsigned int ++ ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), ++ boundary_conditions = (unsigned int)_mp_arg(4), ++ vsiz = (unsigned int)mp.opcode[5]; ++ const int ++ ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], oz = (int)mp.mem[_cimg_mp_slot_z]; ++ const CImg &img = mp.listin[ind]; ++ const longT ++ off = img.offset(ox,oy,oz) + (longT)_mp_arg(3), ++ whd = (longT)img.width()*img.height()*img.depth(); ++ const T *ptrs; ++ if (off>=0 && off::nan(); ++ } ++ if (img._data) switch (boundary_conditions) { ++ case 3 : { // Mirror ++ const longT whd2 = 2*whd, moff = cimg::mod(off,whd2); ++ ptrs = &img[moff::nan(); ++ } ++ case 2 : // Periodic ++ ptrs = &img[cimg::mod(off,whd)]; ++ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } ++ return cimg::type::nan(); ++ case 1 : // Neumann ++ ptrs = off<0?&img[0]:&img[whd - 1]; ++ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } ++ return cimg::type::nan(); ++ default : // Dirichlet ++ std::memset(ptrd,0,vsiz*sizeof(double)); ++ return cimg::type::nan(); ++ } ++ std::memset(ptrd,0,vsiz*sizeof(double)); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_list_Jxyz(_cimg_math_parser& mp) { ++ double *ptrd = &_mp_arg(1) + 1; ++ const unsigned int ++ ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), ++ interpolation = (unsigned int)_mp_arg(6), ++ boundary_conditions = (unsigned int)_mp_arg(7), ++ vsiz = (unsigned int)mp.opcode[8]; ++ const CImg &img = mp.listin[ind]; ++ const double ++ ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z], ++ x = ox + _mp_arg(3), y = oy + _mp_arg(4), z = oz + _mp_arg(5); ++ const ulongT whd = (ulongT)img._width*img._height*img._depth; ++ const T *ptrs; ++ if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation ++ case 3 : { // Mirror ++ const int ++ w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), ++ mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), mz = cimg::mod((int)z,d2), ++ cx = mx::nan(); ++ } ++ ++ static double mp_log(_cimg_math_parser& mp) { ++ return std::log(_mp_arg(2)); ++ } ++ ++ static double mp_log10(_cimg_math_parser& mp) { ++ return std::log10(_mp_arg(2)); ++ } ++ ++ static double mp_log2(_cimg_math_parser& mp) { ++ return cimg::log2(_mp_arg(2)); ++ } ++ ++ static double mp_logical_and(_cimg_math_parser& mp) { ++ const bool val_left = (bool)_mp_arg(2); ++ const CImg *const p_end = ++mp.p_code + mp.opcode[4]; ++ if (!val_left) { mp.p_code = p_end - 1; return 0; } ++ const ulongT mem_right = mp.opcode[3]; ++ for ( ; mp.p_code_data; ++ const ulongT target = mp.opcode[1]; ++ mp.mem[target] = _cimg_mp_defunc(mp); ++ } ++ --mp.p_code; ++ return (double)(bool)mp.mem[mem_right]; ++ } ++ ++ static double mp_logical_not(_cimg_math_parser& mp) { ++ return (double)!_mp_arg(2); ++ } ++ ++ static double mp_logical_or(_cimg_math_parser& mp) { ++ const bool val_left = (bool)_mp_arg(2); ++ const CImg *const p_end = ++mp.p_code + mp.opcode[4]; ++ if (val_left) { mp.p_code = p_end - 1; return 1; } ++ const ulongT mem_right = mp.opcode[3]; ++ for ( ; mp.p_code_data; ++ const ulongT target = mp.opcode[1]; ++ mp.mem[target] = _cimg_mp_defunc(mp); ++ } ++ --mp.p_code; ++ return (double)(bool)mp.mem[mem_right]; ++ } ++ ++ static double mp_lowercase(_cimg_math_parser& mp) { ++ return cimg::lowercase(_mp_arg(2)); ++ } ++ ++ static double mp_lt(_cimg_math_parser& mp) { ++ return (double)(_mp_arg(2)<_mp_arg(3)); ++ } ++ ++ static double mp_lte(_cimg_math_parser& mp) { ++ return (double)(_mp_arg(2)<=_mp_arg(3)); ++ } ++ ++ static double mp_matrix_eig(_cimg_math_parser& mp) { ++ double *ptrd = &_mp_arg(1) + 1; ++ const double *ptr1 = &_mp_arg(2) + 1; ++ const unsigned int k = (unsigned int)mp.opcode[3]; ++ CImg val, vec; ++ CImg(ptr1,k,k,1,1,true).symmetric_eigen(val,vec); ++ CImg(ptrd,1,k,1,1,true) = val; ++ CImg(ptrd + k,k,k,1,1,true) = vec.get_transpose(); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_matrix_inv(_cimg_math_parser& mp) { ++ double *ptrd = &_mp_arg(1) + 1; ++ const double *ptr1 = &_mp_arg(2) + 1; ++ const unsigned int k = (unsigned int)mp.opcode[3]; ++ CImg(ptrd,k,k,1,1,true) = CImg(ptr1,k,k,1,1,true).get_invert(); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_matrix_mul(_cimg_math_parser& mp) { ++ double *ptrd = &_mp_arg(1) + 1; ++ const double ++ *ptr1 = &_mp_arg(2) + 1, ++ *ptr2 = &_mp_arg(3) + 1; ++ const unsigned int ++ k = (unsigned int)mp.opcode[4], ++ l = (unsigned int)mp.opcode[5], ++ m = (unsigned int)mp.opcode[6]; ++ CImg(ptrd,m,k,1,1,true) = CImg(ptr1,l,k,1,1,true)*CImg(ptr2,m,l,1,1,true); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_matrix_pseudoinv(_cimg_math_parser& mp) { ++ double *ptrd = &_mp_arg(1) + 1; ++ const double *ptr1 = &_mp_arg(2) + 1; ++ const unsigned int ++ k = (unsigned int)mp.opcode[3], ++ l = (unsigned int)mp.opcode[4]; ++ CImg(ptrd,l,k,1,1,true) = CImg(ptr1,k,l,1,1,true).get_pseudoinvert(); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_matrix_svd(_cimg_math_parser& mp) { ++ double *ptrd = &_mp_arg(1) + 1; ++ const double *ptr1 = &_mp_arg(2) + 1; ++ const unsigned int ++ k = (unsigned int)mp.opcode[3], ++ l = (unsigned int)mp.opcode[4]; ++ CImg U, S, V; ++ CImg(ptr1,k,l,1,1,true).SVD(U,S,V); ++ CImg(ptrd,k,l,1,1,true) = U; ++ CImg(ptrd + k*l,1,k,1,1,true) = S; ++ CImg(ptrd + k*l + k,k,k,1,1,true) = V; ++ return cimg::type::nan(); ++ } ++ ++ static double mp_max(_cimg_math_parser& mp) { ++ const unsigned int i_end = (unsigned int)mp.opcode[2]; ++ double val = _mp_arg(3); ++ for (unsigned int i = 4; i=mp.mem.width()) ++ throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'copy()': " ++ "Out-of-bounds variable pointer " ++ "(length: %ld, increment: %ld, offset start: %ld, " ++ "offset end: %ld, offset max: %u).", ++ mp.imgin.pixel_type(),siz,inc,off,eoff,mp.mem._width - 1); ++ return &mp.mem[off]; ++ } ++ ++ static float* _mp_memcopy_float(_cimg_math_parser& mp, const ulongT *const p_ref, ++ const longT siz, const long inc) { ++ const unsigned ind = (unsigned int)p_ref[1]; ++ const CImg &img = ind==~0U?mp.imgin:mp.listin[cimg::mod((int)mp.mem[ind],mp.listin.width())]; ++ const bool is_relative = (bool)p_ref[2]; ++ int ox, oy, oz, oc; ++ longT off = 0; ++ if (is_relative) { ++ ox = (int)mp.mem[_cimg_mp_slot_x]; ++ oy = (int)mp.mem[_cimg_mp_slot_y]; ++ oz = (int)mp.mem[_cimg_mp_slot_z]; ++ oc = (int)mp.mem[_cimg_mp_slot_c]; ++ off = img.offset(ox,oy,oz,oc); ++ } ++ if ((*p_ref)%2) { ++ const int ++ x = (int)mp.mem[p_ref[3]], ++ y = (int)mp.mem[p_ref[4]], ++ z = (int)mp.mem[p_ref[5]], ++ c = *p_ref==5?0:(int)mp.mem[p_ref[6]]; ++ off+=img.offset(x,y,z,c); ++ } else off+=(longT)mp.mem[p_ref[3]]; ++ const longT eoff = off + (siz - 1)*inc; ++ if (off<0 || eoff>=(longT)img.size()) ++ throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'copy()': " ++ "Out-of-bounds image pointer " ++ "(length: %ld, increment: %ld, offset start: %ld, " ++ "offset end: %ld, offset max: %lu).", ++ mp.imgin.pixel_type(),siz,inc,off,eoff,img.size() - 1); ++ return (float*)&img[off]; ++ } ++ ++ static double mp_memcopy(_cimg_math_parser& mp) { ++ longT siz = (longT)_mp_arg(4); ++ const longT inc_d = (longT)_mp_arg(5), inc_s = (longT)_mp_arg(6); ++ const float ++ _opacity = (float)_mp_arg(7), ++ opacity = (float)cimg::abs(_opacity), ++ omopacity = 1 - std::max(_opacity,0.0f); ++ if (siz>0) { ++ const bool ++ is_doubled = mp.opcode[8]<=1, ++ is_doubles = mp.opcode[15]<=1; ++ if (is_doubled && is_doubles) { // (double*) <- (double*) ++ double *ptrd = _mp_memcopy_double(mp,(unsigned int)mp.opcode[2],&mp.opcode[8],siz,inc_d); ++ const double *ptrs = _mp_memcopy_double(mp,(unsigned int)mp.opcode[3],&mp.opcode[15],siz,inc_s); ++ if (inc_d==1 && inc_s==1 && _opacity>=1) { ++ if (ptrs + siz - 1ptrd + siz - 1) std::memcpy(ptrd,ptrs,siz*sizeof(double)); ++ else std::memmove(ptrd,ptrs,siz*sizeof(double)); ++ } else { ++ if (ptrs + (siz - 1)*inc_sptrd + (siz - 1)*inc_d) { ++ if (_opacity>=1) while (siz-->0) { *ptrd = *ptrs; ptrd+=inc_d; ptrs+=inc_s; } ++ else while (siz-->0) { *ptrd = omopacity**ptrd + opacity**ptrs; ptrd+=inc_d; ptrs+=inc_s; } ++ } else { // Overlapping buffers ++ CImg buf((unsigned int)siz); ++ cimg_for(buf,ptr,double) { *ptr = *ptrs; ptrs+=inc_s; } ++ ptrs = buf; ++ if (_opacity>=1) while (siz-->0) { *ptrd = *(ptrs++); ptrd+=inc_d; } ++ else while (siz-->0) { *ptrd = omopacity**ptrd + opacity**(ptrs++); ptrd+=inc_d; } ++ } ++ } ++ } else if (is_doubled && !is_doubles) { // (double*) <- (float*) ++ double *ptrd = _mp_memcopy_double(mp,(unsigned int)mp.opcode[2],&mp.opcode[8],siz,inc_d); ++ const float *ptrs = _mp_memcopy_float(mp,&mp.opcode[15],siz,inc_s); ++ if (_opacity>=1) while (siz-->0) { *ptrd = *ptrs; ptrd+=inc_d; ptrs+=inc_s; } ++ else while (siz-->0) { *ptrd = omopacity**ptrd + _opacity**ptrs; ptrd+=inc_d; ptrs+=inc_s; } ++ } else if (!is_doubled && is_doubles) { // (float*) <- (double*) ++ float *ptrd = _mp_memcopy_float(mp,&mp.opcode[8],siz,inc_d); ++ const double *ptrs = _mp_memcopy_double(mp,(unsigned int)mp.opcode[3],&mp.opcode[15],siz,inc_s); ++ if (_opacity>=1) while (siz-->0) { *ptrd = (float)*ptrs; ptrd+=inc_d; ptrs+=inc_s; } ++ else while (siz-->0) { *ptrd = (float)(omopacity**ptrd + opacity**ptrs); ptrd+=inc_d; ptrs+=inc_s; } ++ } else { // (float*) <- (float*) ++ float *ptrd = _mp_memcopy_float(mp,&mp.opcode[8],siz,inc_d); ++ const float *ptrs = _mp_memcopy_float(mp,&mp.opcode[15],siz,inc_s); ++ if (inc_d==1 && inc_s==1 && _opacity>=1) { ++ if (ptrs + siz - 1ptrd + siz - 1) std::memcpy(ptrd,ptrs,siz*sizeof(float)); ++ else std::memmove(ptrd,ptrs,siz*sizeof(float)); ++ } else { ++ if (ptrs + (siz - 1)*inc_sptrd + (siz - 1)*inc_d) { ++ if (_opacity>=1) while (siz-->0) { *ptrd = *ptrs; ptrd+=inc_d; ptrs+=inc_s; } ++ else while (siz-->0) { *ptrd = omopacity**ptrd + opacity**ptrs; ptrd+=inc_d; ptrs+=inc_s; } ++ } else { // Overlapping buffers ++ CImg buf((unsigned int)siz); ++ cimg_for(buf,ptr,float) { *ptr = *ptrs; ptrs+=inc_s; } ++ ptrs = buf; ++ if (_opacity>=1) while (siz-->0) { *ptrd = *(ptrs++); ptrd+=inc_d; } ++ else while (siz-->0) { *ptrd = omopacity**ptrd + opacity**(ptrs++); ptrd+=inc_d; } ++ } ++ } ++ } ++ } ++ return _mp_arg(1); ++ } ++ ++ static double mp_min(_cimg_math_parser& mp) { ++ const unsigned int i_end = (unsigned int)mp.opcode[2]; ++ double val = _mp_arg(3); ++ for (unsigned int i = 4; i vals(i_end - 3); ++ double *p = vals.data(); ++ for (unsigned int i = 3; ires) res = val; ++ } ++ return res; ++ } ++ ++ static double mp_normp(_cimg_math_parser& mp) { ++ const unsigned int i_end = (unsigned int)mp.opcode[2]; ++ if (i_end==4) return cimg::abs(_mp_arg(3)); ++ const double p = (double)mp.opcode[3]; ++ double res = 0; ++ for (unsigned int i = 4; i0?res:0.0; ++ } ++ ++ static double mp_permutations(_cimg_math_parser& mp) { ++ return cimg::permutations(_mp_arg(2),_mp_arg(3),(bool)_mp_arg(4)); ++ } ++ ++ static double mp_pow(_cimg_math_parser& mp) { ++ const double v = _mp_arg(2), p = _mp_arg(3); ++ return std::pow(v,p); ++ } ++ ++ static double mp_pow0_25(_cimg_math_parser& mp) { ++ const double val = _mp_arg(2); ++ return std::sqrt(std::sqrt(val)); ++ } ++ ++ static double mp_pow3(_cimg_math_parser& mp) { ++ const double val = _mp_arg(2); ++ return val*val*val; ++ } ++ ++ static double mp_pow4(_cimg_math_parser& mp) { ++ const double val = _mp_arg(2); ++ return val*val*val*val; ++ } ++ ++ static double mp_print(_cimg_math_parser& mp) { ++ const double val = _mp_arg(1); ++ const bool print_char = (bool)mp.opcode[3]; ++ cimg_pragma_openmp(critical(mp_print)) ++ { ++ CImg expr(mp.opcode[2] - 4); ++ const ulongT *ptrs = mp.opcode._data + 4; ++ cimg_for(expr,ptrd,char) *ptrd = (char)*(ptrs++); ++ cimg::strellipsize(expr); ++ cimg::mutex(6); ++ if (print_char) ++ std::fprintf(cimg::output(),"\n[" cimg_appname "_math_parser] %s = %g = '%c'",expr._data,val,(int)val); ++ else ++ std::fprintf(cimg::output(),"\n[" cimg_appname "_math_parser] %s = %g",expr._data,val); ++ std::fflush(cimg::output()); ++ cimg::mutex(6,0); ++ } ++ return val; ++ } ++ ++ static double mp_prod(_cimg_math_parser& mp) { ++ const unsigned int i_end = (unsigned int)mp.opcode[2]; ++ double val = _mp_arg(3); ++ for (unsigned int i = 4; i::nan(); ++ } ++ ++ static double mp_rot3d(_cimg_math_parser& mp) { ++ double *ptrd = &_mp_arg(1) + 1; ++ const float x = (float)_mp_arg(2), y = (float)_mp_arg(3), z = (float)_mp_arg(4), theta = (float)_mp_arg(5); ++ CImg(ptrd,3,3,1,1,true) = CImg::rotation_matrix(x,y,z,theta); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_round(_cimg_math_parser& mp) { ++ return cimg::round(_mp_arg(2),_mp_arg(3),(int)_mp_arg(4)); ++ } ++ ++ static double mp_self_add(_cimg_math_parser& mp) { ++ return _mp_arg(1)+=_mp_arg(2); ++ } ++ ++ static double mp_self_bitwise_and(_cimg_math_parser& mp) { ++ double &val = _mp_arg(1); ++ return val = (double)((longT)val & (longT)_mp_arg(2)); ++ } ++ ++ static double mp_self_bitwise_left_shift(_cimg_math_parser& mp) { ++ double &val = _mp_arg(1); ++ return val = (double)((longT)val<<(unsigned int)_mp_arg(2)); ++ } ++ ++ static double mp_self_bitwise_or(_cimg_math_parser& mp) { ++ double &val = _mp_arg(1); ++ return val = (double)((longT)val | (longT)_mp_arg(2)); ++ } ++ ++ static double mp_self_bitwise_right_shift(_cimg_math_parser& mp) { ++ double &val = _mp_arg(1); ++ return val = (double)((longT)val>>(unsigned int)_mp_arg(2)); ++ } ++ ++ static double mp_self_decrement(_cimg_math_parser& mp) { ++ return --_mp_arg(1); ++ } ++ ++ static double mp_self_increment(_cimg_math_parser& mp) { ++ return ++_mp_arg(1); ++ } ++ ++ static double mp_self_map_vector_s(_cimg_math_parser& mp) { // Vector += scalar ++ unsigned int ++ ptrd = (unsigned int)mp.opcode[1] + 1, ++ siz = (unsigned int)mp.opcode[2]; ++ mp_func op = (mp_func)mp.opcode[3]; ++ CImg l_opcode(1,3); ++ l_opcode[2] = mp.opcode[4]; // Scalar argument. ++ l_opcode.swap(mp.opcode); ++ ulongT &target = mp.opcode[1]; ++ while (siz-->0) { target = ptrd++; (*op)(mp); } ++ l_opcode.swap(mp.opcode); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_self_map_vector_v(_cimg_math_parser& mp) { // Vector += vector ++ unsigned int ++ ptrd = (unsigned int)mp.opcode[1] + 1, ++ siz = (unsigned int)mp.opcode[2], ++ ptrs = (unsigned int)mp.opcode[4] + 1; ++ mp_func op = (mp_func)mp.opcode[3]; ++ CImg l_opcode(1,4); ++ l_opcode.swap(mp.opcode); ++ ulongT &target = mp.opcode[1], &argument = mp.opcode[2]; ++ while (siz-->0) { target = ptrd++; argument = ptrs++; (*op)(mp); } ++ l_opcode.swap(mp.opcode); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_self_mul(_cimg_math_parser& mp) { ++ return _mp_arg(1)*=_mp_arg(2); ++ } ++ ++ static double mp_self_div(_cimg_math_parser& mp) { ++ return _mp_arg(1)/=_mp_arg(2); ++ } ++ ++ static double mp_self_modulo(_cimg_math_parser& mp) { ++ double &val = _mp_arg(1); ++ return val = cimg::mod(val,_mp_arg(2)); ++ } ++ ++ static double mp_self_pow(_cimg_math_parser& mp) { ++ double &val = _mp_arg(1); ++ return val = std::pow(val,_mp_arg(2)); ++ } ++ ++ static double mp_self_sub(_cimg_math_parser& mp) { ++ return _mp_arg(1)-=_mp_arg(2); ++ } ++ ++ static double mp_set_ioff(_cimg_math_parser& mp) { ++ CImg &img = mp.imgout; ++ const longT ++ off = (longT)_mp_arg(2), ++ whds = (longT)img.size(); ++ const double val = _mp_arg(1); ++ if (off>=0 && off &img = mp.imgout; ++ const int ++ x = (int)_mp_arg(2), y = (int)_mp_arg(3), ++ z = (int)_mp_arg(4), c = (int)_mp_arg(5); ++ const double val = _mp_arg(1); ++ if (x>=0 && x=0 && y=0 && z=0 && c &img = mp.imgout; ++ const int ++ ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], ++ oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; ++ const longT ++ off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(2), ++ whds = (longT)img.size(); ++ const double val = _mp_arg(1); ++ if (off>=0 && off &img = mp.imgout; ++ const double ++ ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], ++ oz = mp.mem[_cimg_mp_slot_z], oc = mp.mem[_cimg_mp_slot_c]; ++ const int ++ x = (int)(ox + _mp_arg(2)), y = (int)(oy + _mp_arg(3)), ++ z = (int)(oz + _mp_arg(4)), c = (int)(oc + _mp_arg(5)); ++ const double val = _mp_arg(1); ++ if (x>=0 && x=0 && y=0 && z=0 && c &img = mp.imgout; ++ const longT ++ off = (longT)_mp_arg(2), ++ whd = (longT)img.width()*img.height()*img.depth(); ++ const T val = (T)_mp_arg(1); ++ if (off>=0 && off &img = mp.imgout; ++ const longT ++ off = (longT)_mp_arg(2), ++ whd = (longT)img.width()*img.height()*img.depth(); ++ const double *ptrs = &_mp_arg(1) + 1; ++ if (off>=0 && off::nan(); ++ } ++ ++ static double mp_set_Ixyz_s(_cimg_math_parser& mp) { ++ CImg &img = mp.imgout; ++ const int ++ x = (int)_mp_arg(2), ++ y = (int)_mp_arg(3), ++ z = (int)_mp_arg(4); ++ const T val = (T)_mp_arg(1); ++ if (x>=0 && x=0 && y=0 && z &img = mp.imgout; ++ const int ++ x = (int)_mp_arg(2), ++ y = (int)_mp_arg(3), ++ z = (int)_mp_arg(4); ++ const double *ptrs = &_mp_arg(1) + 1; ++ if (x>=0 && x=0 && y=0 && z::nan(); ++ } ++ ++ static double mp_set_Joff_s(_cimg_math_parser& mp) { ++ CImg &img = mp.imgout; ++ const int ++ ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], ++ oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; ++ const longT ++ off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(2), ++ whd = (longT)img.width()*img.height()*img.depth(); ++ const T val = (T)_mp_arg(1); ++ if (off>=0 && off &img = mp.imgout; ++ const int ++ ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], ++ oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c]; ++ const longT ++ off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(2), ++ whd = (longT)img.width()*img.height()*img.depth(); ++ const double *ptrs = &_mp_arg(1) + 1; ++ if (off>=0 && off::nan(); ++ } ++ ++ static double mp_set_Jxyz_s(_cimg_math_parser& mp) { ++ CImg &img = mp.imgout; ++ const double ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z]; ++ const int ++ x = (int)(ox + _mp_arg(2)), ++ y = (int)(oy + _mp_arg(3)), ++ z = (int)(oz + _mp_arg(4)); ++ const T val = (T)_mp_arg(1); ++ if (x>=0 && x=0 && y=0 && z &img = mp.imgout; ++ const double ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z]; ++ const int ++ x = (int)(ox + _mp_arg(2)), ++ y = (int)(oy + _mp_arg(3)), ++ z = (int)(oz + _mp_arg(4)); ++ const double *ptrs = &_mp_arg(1) + 1; ++ if (x>=0 && x=0 && y=0 && z::nan(); ++ } ++ ++ static double mp_shift(_cimg_math_parser& mp) { ++ double *const ptrd = &_mp_arg(1) + 1; ++ const double *const ptrs = &_mp_arg(2) + 1; ++ const unsigned int siz = (unsigned int)mp.opcode[3]; ++ const int ++ shift = (int)_mp_arg(4), ++ boundary_conditions = (int)_mp_arg(5); ++ CImg(ptrd,siz,1,1,1,true) = CImg(ptrs,siz,1,1,1,true).shift(shift,0,0,0,boundary_conditions); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_sign(_cimg_math_parser& mp) { ++ return cimg::sign(_mp_arg(2)); ++ } ++ ++ static double mp_sin(_cimg_math_parser& mp) { ++ return std::sin(_mp_arg(2)); ++ } ++ ++ static double mp_sinc(_cimg_math_parser& mp) { ++ return cimg::sinc(_mp_arg(2)); ++ } ++ ++ static double mp_sinh(_cimg_math_parser& mp) { ++ return std::sinh(_mp_arg(2)); ++ } ++ ++ static double mp_solve(_cimg_math_parser& mp) { ++ double *ptrd = &_mp_arg(1) + 1; ++ const double ++ *ptr1 = &_mp_arg(2) + 1, ++ *ptr2 = &_mp_arg(3) + 1; ++ const unsigned int ++ k = (unsigned int)mp.opcode[4], ++ l = (unsigned int)mp.opcode[5], ++ m = (unsigned int)mp.opcode[6]; ++ CImg(ptrd,m,k,1,1,true) = CImg(ptr2,m,l,1,1,true).get_solve(CImg(ptr1,k,l,1,1,true)); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_sort(_cimg_math_parser& mp) { ++ double *const ptrd = &_mp_arg(1) + 1; ++ const double *const ptrs = &_mp_arg(2) + 1; ++ const unsigned int ++ siz = (unsigned int)mp.opcode[3], ++ chunk_siz = (unsigned int)mp.opcode[5]; ++ const bool is_increasing = (bool)_mp_arg(4); ++ CImg(ptrd,chunk_siz,siz/chunk_siz,1,1,true) = CImg(ptrs,chunk_siz,siz/chunk_siz,1,1,true). ++ get_sort(is_increasing,chunk_siz>1?'y':0); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_sqr(_cimg_math_parser& mp) { ++ return cimg::sqr(_mp_arg(2)); ++ } ++ ++ static double mp_sqrt(_cimg_math_parser& mp) { ++ return std::sqrt(_mp_arg(2)); ++ } ++ ++ static double mp_srand(_cimg_math_parser& mp) { ++ return cimg::srand((unsigned int)_mp_arg(2)); ++ } ++ ++ static double mp_srand0(_cimg_math_parser& mp) { ++ cimg::unused(mp); ++ return cimg::srand(); ++ } ++ ++ static double mp_std(_cimg_math_parser& mp) { ++ const unsigned int i_end = (unsigned int)mp.opcode[2]; ++ CImg vals(i_end - 3); ++ double *p = vals.data(); ++ for (unsigned int i = 3; i0) mp.mem[ptrd++] = (double)*(ptrs++); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_stov(_cimg_math_parser& mp) { ++ const double *ptrs = &_mp_arg(2); ++ const ulongT siz = (ulongT)mp.opcode[3]; ++ longT ind = (longT)_mp_arg(4); ++ const bool is_strict = (bool)_mp_arg(5); ++ double val = cimg::type::nan(); ++ if (ind<0 || ind>=(longT)siz) return val; ++ if (!siz) return *ptrs>='0' && *ptrs<='9'?*ptrs - '0':val; ++ ++ CImg ss(siz + 1 - ind); ++ char sep; ++ ptrs+=1 + ind; cimg_forX(ss,i) ss[i] = (char)*(ptrs++); ss.back() = 0; ++ ++ int err = std::sscanf(ss,"%lf%c",&val,&sep); ++#if cimg_OS==2 ++ // Check for +/-NaN and +/-inf as Microsoft's sscanf() version is not able ++ // to read those particular values. ++ if (!err && (*ss=='+' || *ss=='-' || *ss=='i' || *ss=='I' || *ss=='n' || *ss=='N')) { ++ bool is_positive = true; ++ const char *s = ss; ++ if (*s=='+') ++s; else if (*s=='-') { ++s; is_positive = false; } ++ if (!cimg::strcasecmp(s,"inf")) { val = cimg::type::inf(); err = 1; } ++ else if (!cimg::strcasecmp(s,"nan")) { val = cimg::type::nan(); err = 1; } ++ if (err==1 && !is_positive) val = -val; ++ } ++#endif ++ if (is_strict && err!=1) return cimg::type::nan(); ++ return val; ++ } ++ ++ static double mp_sub(_cimg_math_parser& mp) { ++ return _mp_arg(2) - _mp_arg(3); ++ } ++ ++ static double mp_sum(_cimg_math_parser& mp) { ++ const unsigned int i_end = (unsigned int)mp.opcode[2]; ++ double val = _mp_arg(3); ++ for (unsigned int i = 4; i(ptrs,k,k,1,1,true).trace(); ++ } ++ ++ static double mp_transp(_cimg_math_parser& mp) { ++ double *ptrd = &_mp_arg(1) + 1; ++ const double *ptrs = &_mp_arg(2) + 1; ++ const unsigned int ++ k = (unsigned int)mp.opcode[3], ++ l = (unsigned int)mp.opcode[4]; ++ CImg(ptrd,l,k,1,1,true) = CImg(ptrs,k,l,1,1,true).get_transpose(); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_u(_cimg_math_parser& mp) { ++ return cimg::rand(_mp_arg(2),_mp_arg(3)); ++ } ++ ++ static double mp_uppercase(_cimg_math_parser& mp) { ++ return cimg::uppercase(_mp_arg(2)); ++ } ++ ++ static double mp_variance(_cimg_math_parser& mp) { ++ const unsigned int i_end = (unsigned int)mp.opcode[2]; ++ CImg vals(i_end - 3); ++ double *p = vals.data(); ++ for (unsigned int i = 3; i::nan(); ++ } ++ ++ static double mp_vector_crop(_cimg_math_parser& mp) { ++ double *const ptrd = &_mp_arg(1) + 1; ++ const double *const ptrs = &_mp_arg(2) + 1; ++ const longT ++ length = (longT)mp.opcode[3], ++ start = (longT)_mp_arg(4), ++ sublength = (longT)mp.opcode[5]; ++ if (start<0 || start + sublength>length) ++ throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Value accessor '[]': " ++ "Out-of-bounds sub-vector request " ++ "(length: %ld, start: %ld, sub-length: %ld).", ++ mp.imgin.pixel_type(),length,start,sublength); ++ std::memcpy(ptrd,ptrs + start,sublength*sizeof(double)); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_vector_init(_cimg_math_parser& mp) { ++ unsigned int ++ ptrs = 4U, ++ ptrd = (unsigned int)mp.opcode[1] + 1, ++ siz = (unsigned int)mp.opcode[3]; ++ switch (mp.opcode[2] - 4) { ++ case 0 : std::memset(mp.mem._data + ptrd,0,siz*sizeof(double)); break; // 0 values given ++ case 1 : { const double val = _mp_arg(ptrs); while (siz-->0) mp.mem[ptrd++] = val; } break; ++ default : while (siz-->0) { mp.mem[ptrd++] = _mp_arg(ptrs++); if (ptrs>=mp.opcode[2]) ptrs = 4U; } ++ } ++ return cimg::type::nan(); ++ } ++ ++ static double mp_vector_eq(_cimg_math_parser& mp) { ++ const double ++ *ptr1 = &_mp_arg(2) + 1, ++ *ptr2 = &_mp_arg(4) + 1; ++ unsigned int p1 = (unsigned int)mp.opcode[3], p2 = (unsigned int)mp.opcode[5], n; ++ const int N = (int)_mp_arg(6); ++ const bool case_sensitive = (bool)_mp_arg(7); ++ bool still_equal = true; ++ double value; ++ if (!N) return true; ++ ++ // Compare all values. ++ if (N<0) { ++ if (p1>0 && p2>0) { // Vector == vector ++ if (p1!=p2) return false; ++ if (case_sensitive) ++ while (still_equal && p1--) still_equal = *(ptr1++)==*(ptr2++); ++ else ++ while (still_equal && p1--) ++ still_equal = cimg::lowercase(*(ptr1++))==cimg::lowercase(*(ptr2++)); ++ return still_equal; ++ } else if (p1>0 && !p2) { // Vector == scalar ++ value = _mp_arg(4); ++ if (!case_sensitive) value = cimg::lowercase(value); ++ while (still_equal && p1--) still_equal = *(ptr1++)==value; ++ return still_equal; ++ } else if (!p1 && p2>0) { // Scalar == vector ++ value = _mp_arg(2); ++ if (!case_sensitive) value = cimg::lowercase(value); ++ while (still_equal && p2--) still_equal = *(ptr2++)==value; ++ return still_equal; ++ } else { // Scalar == scalar ++ if (case_sensitive) return _mp_arg(2)==_mp_arg(4); ++ else return cimg::lowercase(_mp_arg(2))==cimg::lowercase(_mp_arg(4)); ++ } ++ } ++ ++ // Compare only first N values. ++ if (p1>0 && p2>0) { // Vector == vector ++ n = cimg::min((unsigned int)N,p1,p2); ++ if (case_sensitive) ++ while (still_equal && n--) still_equal = *(ptr1++)==(*ptr2++); ++ else ++ while (still_equal && n--) still_equal = cimg::lowercase(*(ptr1++))==cimg::lowercase(*(ptr2++)); ++ return still_equal; ++ } else if (p1>0 && !p2) { // Vector == scalar ++ n = std::min((unsigned int)N,p1); ++ value = _mp_arg(4); ++ if (!case_sensitive) value = cimg::lowercase(value); ++ while (still_equal && n--) still_equal = *(ptr1++)==value; ++ return still_equal; ++ } else if (!p1 && p2>0) { // Scalar == vector ++ n = std::min((unsigned int)N,p2); ++ value = _mp_arg(2); ++ if (!case_sensitive) value = cimg::lowercase(value); ++ while (still_equal && n--) still_equal = *(ptr2++)==value; ++ return still_equal; ++ } // Scalar == scalar ++ if (case_sensitive) return _mp_arg(2)==_mp_arg(4); ++ return cimg::lowercase(_mp_arg(2))==cimg::lowercase(_mp_arg(4)); ++ } ++ ++ static double mp_vector_off(_cimg_math_parser& mp) { ++ const unsigned int ++ ptr = (unsigned int)mp.opcode[2] + 1, ++ siz = (unsigned int)mp.opcode[3]; ++ const int off = (int)_mp_arg(4); ++ return off>=0 && off<(int)siz?mp.mem[ptr + off]:cimg::type::nan(); ++ } ++ ++ static double mp_vector_map_sv(_cimg_math_parser& mp) { // Operator(scalar,vector) ++ unsigned int ++ siz = (unsigned int)mp.opcode[2], ++ ptrs = (unsigned int)mp.opcode[5] + 1; ++ double *ptrd = &_mp_arg(1) + 1; ++ mp_func op = (mp_func)mp.opcode[3]; ++ CImg l_opcode(4); ++ l_opcode[2] = mp.opcode[4]; // Scalar argument1 ++ l_opcode.swap(mp.opcode); ++ ulongT &argument2 = mp.opcode[3]; ++ while (siz-->0) { argument2 = ptrs++; *(ptrd++) = (*op)(mp); } ++ l_opcode.swap(mp.opcode); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_vector_map_v(_cimg_math_parser& mp) { // Operator(vector) ++ unsigned int ++ siz = (unsigned int)mp.opcode[2], ++ ptrs = (unsigned int)mp.opcode[4] + 1; ++ double *ptrd = &_mp_arg(1) + 1; ++ mp_func op = (mp_func)mp.opcode[3]; ++ CImg l_opcode(1,3); ++ l_opcode.swap(mp.opcode); ++ ulongT &argument = mp.opcode[2]; ++ while (siz-->0) { argument = ptrs++; *(ptrd++) = (*op)(mp); } ++ l_opcode.swap(mp.opcode); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_vector_map_vs(_cimg_math_parser& mp) { // Operator(vector,scalar) ++ unsigned int ++ siz = (unsigned int)mp.opcode[2], ++ ptrs = (unsigned int)mp.opcode[4] + 1; ++ double *ptrd = &_mp_arg(1) + 1; ++ mp_func op = (mp_func)mp.opcode[3]; ++ CImg l_opcode(1,4); ++ l_opcode[3] = mp.opcode[5]; // Scalar argument2 ++ l_opcode.swap(mp.opcode); ++ ulongT &argument1 = mp.opcode[2]; ++ while (siz-->0) { argument1 = ptrs++; *(ptrd++) = (*op)(mp); } ++ l_opcode.swap(mp.opcode); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_vector_map_vss(_cimg_math_parser& mp) { // Operator(vector,scalar,scalar) ++ unsigned int ++ siz = (unsigned int)mp.opcode[2], ++ ptrs = (unsigned int)mp.opcode[4] + 1; ++ double *ptrd = &_mp_arg(1) + 1; ++ mp_func op = (mp_func)mp.opcode[3]; ++ CImg l_opcode(1,5); ++ l_opcode[3] = mp.opcode[5]; // Scalar argument2 ++ l_opcode[4] = mp.opcode[6]; // Scalar argument3 ++ l_opcode.swap(mp.opcode); ++ ulongT &argument1 = mp.opcode[2]; ++ while (siz-->0) { argument1 = ptrs++; *(ptrd++) = (*op)(mp); } ++ l_opcode.swap(mp.opcode); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_vector_map_vv(_cimg_math_parser& mp) { // Operator(vector,vector) ++ unsigned int ++ siz = (unsigned int)mp.opcode[2], ++ ptrs1 = (unsigned int)mp.opcode[4] + 1, ++ ptrs2 = (unsigned int)mp.opcode[5] + 1; ++ double *ptrd = &_mp_arg(1) + 1; ++ mp_func op = (mp_func)mp.opcode[3]; ++ CImg l_opcode(1,4); ++ l_opcode.swap(mp.opcode); ++ ulongT &argument1 = mp.opcode[2], &argument2 = mp.opcode[3]; ++ while (siz-->0) { argument1 = ptrs1++; argument2 = ptrs2++; *(ptrd++) = (*op)(mp); } ++ l_opcode.swap(mp.opcode); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_vector_neq(_cimg_math_parser& mp) { ++ return !mp_vector_eq(mp); ++ } ++ ++ static double mp_vector_print(_cimg_math_parser& mp) { ++ const bool print_string = (bool)mp.opcode[4]; ++ cimg_pragma_openmp(critical(mp_vector_print)) ++ { ++ CImg expr(mp.opcode[2] - 5); ++ const ulongT *ptrs = mp.opcode._data + 5; ++ cimg_for(expr,ptrd,char) *ptrd = (char)*(ptrs++); ++ cimg::strellipsize(expr); ++ unsigned int ++ ptr = (unsigned int)mp.opcode[1] + 1, ++ siz0 = (unsigned int)mp.opcode[3], ++ siz = siz0; ++ cimg::mutex(6); ++ std::fprintf(cimg::output(),"\n[" cimg_appname "_math_parser] %s = [ ",expr._data); ++ unsigned int count = 0; ++ while (siz-->0) { ++ if (count>=64 && siz>=64) { ++ std::fprintf(cimg::output(),"...,"); ++ ptr = (unsigned int)mp.opcode[1] + 1 + siz0 - 64; ++ siz = 64; ++ } else std::fprintf(cimg::output(),"%g%s",mp.mem[ptr++],siz?",":""); ++ ++count; ++ } ++ if (print_string) { ++ CImg str(siz0 + 1); ++ ptr = (unsigned int)mp.opcode[1] + 1; ++ for (unsigned int k = 0; k::nan(); ++ } ++ ++ static double mp_vector_resize(_cimg_math_parser& mp) { ++ double *const ptrd = &_mp_arg(1) + 1; ++ const unsigned int p1 = (unsigned int)mp.opcode[2], p2 = (unsigned int)mp.opcode[4]; ++ const int ++ interpolation = (int)_mp_arg(5), ++ boundary_conditions = (int)_mp_arg(6); ++ if (p2) { // Resize vector ++ const double *const ptrs = &_mp_arg(3) + 1; ++ CImg(ptrd,p1,1,1,1,true) = CImg(ptrs,p2,1,1,1,true). ++ get_resize(p1,1,1,1,interpolation,boundary_conditions); ++ } else { // Resize scalar ++ const double value = _mp_arg(3); ++ CImg(ptrd,p1,1,1,1,true) = CImg(1,1,1,1,value).resize(p1,1,1,1,interpolation, ++ boundary_conditions); ++ } ++ return cimg::type::nan(); ++ } ++ ++ static double mp_vector_reverse(_cimg_math_parser& mp) { ++ double *const ptrd = &_mp_arg(1) + 1; ++ const double *const ptrs = &_mp_arg(2) + 1; ++ const unsigned int p1 = (unsigned int)mp.opcode[3]; ++ CImg(ptrd,p1,1,1,1,true) = CImg(ptrs,p1,1,1,1,true).get_mirror('x'); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_vector_set_off(_cimg_math_parser& mp) { ++ const unsigned int ++ ptr = (unsigned int)mp.opcode[2] + 1, ++ siz = (unsigned int)mp.opcode[3]; ++ const int off = (int)_mp_arg(4); ++ if (off>=0 && off<(int)siz) mp.mem[ptr + off] = _mp_arg(5); ++ return _mp_arg(5); ++ } ++ ++ static double mp_vtos(_cimg_math_parser& mp) { ++ double *ptrd = &_mp_arg(1) + 1; ++ const unsigned int ++ sizd = (unsigned int)mp.opcode[2], ++ sizs = (unsigned int)mp.opcode[4]; ++ const int nb_digits = (int)_mp_arg(5); ++ CImg format(8); ++ switch (nb_digits) { ++ case -1 : std::strcpy(format,"%g"); break; ++ case 0 : std::strcpy(format,"%.17g"); break; ++ default : cimg_snprintf(format,format._width,"%%.%dg",nb_digits); ++ } ++ CImg str; ++ if (sizs) { // Vector expression ++ const double *ptrs = &_mp_arg(3) + 1; ++ CImg(ptrs,sizs,1,1,1,true).value_string(',',sizd + 1,format).move_to(str); ++ } else { // Scalar expression ++ str.assign(sizd + 1); ++ cimg_snprintf(str,sizd + 1,format,_mp_arg(3)); ++ } ++ const unsigned int l = std::min(sizd,(unsigned int)std::strlen(str) + 1); ++ CImg(ptrd,l,1,1,1,true) = str.get_shared_points(0,l - 1); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_whiledo(_cimg_math_parser& mp) { ++ const ulongT ++ mem_body = mp.opcode[1], ++ mem_cond = mp.opcode[2]; ++ const CImg ++ *const p_cond = ++mp.p_code, ++ *const p_body = p_cond + mp.opcode[3], ++ *const p_end = p_body + mp.opcode[4]; ++ const unsigned int vsiz = (unsigned int)mp.opcode[5]; ++ bool is_cond = false; ++ if (mp.opcode[6]) { // Set default value for result and condition if necessary ++ if (vsiz) CImg(&mp.mem[mem_body] + 1,vsiz,1,1,1,true).fill(cimg::type::nan()); ++ else mp.mem[mem_body] = cimg::type::nan(); ++ } ++ if (mp.opcode[7]) mp.mem[mem_cond] = 0; ++ const unsigned int _break_type = mp.break_type; ++ mp.break_type = 0; ++ do { ++ for (mp.p_code = p_cond; mp.p_code_data; ++ const ulongT target = mp.opcode[1]; ++ mp.mem[target] = _cimg_mp_defunc(mp); ++ } ++ if (mp.break_type==1) break; ++ is_cond = (bool)mp.mem[mem_cond]; ++ if (is_cond && !mp.break_type) // Evaluate body ++ for (mp.p_code = p_body; mp.p_code_data; ++ const ulongT target = mp.opcode[1]; ++ mp.mem[target] = _cimg_mp_defunc(mp); ++ } ++ if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0; ++ } while (is_cond); ++ ++ mp.break_type = _break_type; ++ mp.p_code = p_end - 1; ++ return mp.mem[mem_body]; ++ } ++ ++ static double mp_Ioff(_cimg_math_parser& mp) { ++ double *ptrd = &_mp_arg(1) + 1; ++ const unsigned int ++ boundary_conditions = (unsigned int)_mp_arg(3), ++ vsiz = (unsigned int)mp.opcode[4]; ++ const CImg &img = mp.imgin; ++ const longT ++ off = (longT)_mp_arg(2), ++ whd = (longT)img.width()*img.height()*img.depth(); ++ const T *ptrs; ++ if (off>=0 && off::nan(); ++ } ++ if (img._data) switch (boundary_conditions) { ++ case 3 : { // Mirror ++ const longT whd2 = 2*whd, moff = cimg::mod(off,whd2); ++ ptrs = &img[moff::nan(); ++ } ++ case 2 : // Periodic ++ ptrs = &img[cimg::mod(off,whd)]; ++ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } ++ return cimg::type::nan(); ++ case 1 : // Neumann ++ ptrs = off<0?&img[0]:&img[whd - 1]; ++ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } ++ return cimg::type::nan(); ++ default : // Dirichlet ++ std::memset(ptrd,0,vsiz*sizeof(double)); ++ return cimg::type::nan(); ++ } ++ std::memset(ptrd,0,vsiz*sizeof(double)); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_Ixyz(_cimg_math_parser& mp) { ++ double *ptrd = &_mp_arg(1) + 1; ++ const unsigned int ++ interpolation = (unsigned int)_mp_arg(5), ++ boundary_conditions = (unsigned int)_mp_arg(6), ++ vsiz = (unsigned int)mp.opcode[7]; ++ const CImg &img = mp.imgin; ++ const double x = _mp_arg(2), y = _mp_arg(3), z = _mp_arg(4); ++ const ulongT whd = (ulongT)img._width*img._height*img._depth; ++ const T *ptrs; ++ if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation ++ case 3 : { // Mirror ++ const int ++ w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), ++ mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), mz = cimg::mod((int)z,d2), ++ cx = mx::nan(); ++ } ++ ++ static double mp_Joff(_cimg_math_parser& mp) { ++ double *ptrd = &_mp_arg(1) + 1; ++ const unsigned int ++ boundary_conditions = (unsigned int)_mp_arg(3), ++ vsiz = (unsigned int)mp.opcode[4]; ++ const CImg &img = mp.imgin; ++ const int ++ ox = (int)mp.mem[_cimg_mp_slot_x], ++ oy = (int)mp.mem[_cimg_mp_slot_y], ++ oz = (int)mp.mem[_cimg_mp_slot_z]; ++ const longT ++ off = img.offset(ox,oy,oz) + (longT)_mp_arg(2), ++ whd = (longT)img.width()*img.height()*img.depth(); ++ const T *ptrs; ++ if (off>=0 && off::nan(); ++ } ++ if (img._data) switch (boundary_conditions) { ++ case 3 : { // Mirror ++ const longT whd2 = 2*whd, moff = cimg::mod(off,whd2); ++ ptrs = &img[moff::nan(); ++ } ++ case 2 : // Periodic ++ ptrs = &img[cimg::mod(off,whd)]; ++ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } ++ return cimg::type::nan(); ++ case 1 : // Neumann ++ ptrs = off<0?&img[0]:&img[whd - 1]; ++ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; } ++ return cimg::type::nan(); ++ default : // Dirichlet ++ std::memset(ptrd,0,vsiz*sizeof(double)); ++ return cimg::type::nan(); ++ } ++ std::memset(ptrd,0,vsiz*sizeof(double)); ++ return cimg::type::nan(); ++ } ++ ++ static double mp_Jxyz(_cimg_math_parser& mp) { ++ double *ptrd = &_mp_arg(1) + 1; ++ const unsigned int ++ interpolation = (unsigned int)_mp_arg(5), ++ boundary_conditions = (unsigned int)_mp_arg(6), ++ vsiz = (unsigned int)mp.opcode[7]; ++ const CImg &img = mp.imgin; ++ const double ++ ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z], ++ x = ox + _mp_arg(2), y = oy + _mp_arg(3), z = oz + _mp_arg(4); ++ const ulongT whd = (ulongT)img._width*img._height*img._depth; ++ const T *ptrs; ++ if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation ++ case 3 : { // Mirror ++ const int ++ w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), ++ mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), mz = cimg::mod((int)z,d2), ++ cx = mx::nan(); ++ } ++ ++#undef _mp_arg ++ ++ }; // struct _cimg_math_parser {} ++ ++ //! Compute the square value of each pixel value. ++ /** ++ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its square value \f$I_{(x,y,z,c)}^2\f$. ++ \note ++ - The \inplace of this method statically casts the computed values to the pixel type \c T. ++ - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. ++ \par Example ++ \code ++ const CImg img("reference.jpg"); ++ (img,img.get_sqr().normalize(0,255)).display(); ++ \endcode ++ \image html ref_sqr.jpg ++ **/ ++ CImg& sqr() { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=524288)) ++ cimg_rof(*this,ptrd,T) { const T val = *ptrd; *ptrd = (T)(val*val); }; ++ return *this; ++ } ++ ++ //! Compute the square value of each pixel value \newinstance. ++ CImg get_sqr() const { ++ return CImg(*this,false).sqr(); ++ } ++ ++ //! Compute the square root of each pixel value. ++ /** ++ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its square root \f$\sqrt{I_{(x,y,z,c)}}\f$. ++ \note ++ - The \inplace of this method statically casts the computed values to the pixel type \c T. ++ - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. ++ \par Example ++ \code ++ const CImg img("reference.jpg"); ++ (img,img.get_sqrt().normalize(0,255)).display(); ++ \endcode ++ \image html ref_sqrt.jpg ++ **/ ++ CImg& sqrt() { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=8192)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)std::sqrt((double)*ptrd); ++ return *this; ++ } ++ ++ //! Compute the square root of each pixel value \newinstance. ++ CImg get_sqrt() const { ++ return CImg(*this,false).sqrt(); ++ } ++ ++ //! Compute the exponential of each pixel value. ++ /** ++ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its exponential \f$e^{I_{(x,y,z,c)}}\f$. ++ \note ++ - The \inplace of this method statically casts the computed values to the pixel type \c T. ++ - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. ++ **/ ++ CImg& exp() { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=4096)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)std::exp((double)*ptrd); ++ return *this; ++ } ++ ++ //! Compute the exponential of each pixel value \newinstance. ++ CImg get_exp() const { ++ return CImg(*this,false).exp(); ++ } ++ ++ //! Compute the logarithm of each pixel value. ++ /** ++ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its logarithm ++ \f$\mathrm{log}_{e}(I_{(x,y,z,c)})\f$. ++ \note ++ - The \inplace of this method statically casts the computed values to the pixel type \c T. ++ - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. ++ **/ ++ CImg& log() { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=262144)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)std::log((double)*ptrd); ++ return *this; ++ } ++ ++ //! Compute the logarithm of each pixel value \newinstance. ++ CImg get_log() const { ++ return CImg(*this,false).log(); ++ } ++ ++ //! Compute the base-2 logarithm of each pixel value. ++ /** ++ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its base-2 logarithm ++ \f$\mathrm{log}_{2}(I_{(x,y,z,c)})\f$. ++ \note ++ - The \inplace of this method statically casts the computed values to the pixel type \c T. ++ - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. ++ **/ ++ CImg& log2() { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=4096)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)cimg::log2((double)*ptrd); ++ return *this; ++ } ++ ++ //! Compute the base-10 logarithm of each pixel value \newinstance. ++ CImg get_log2() const { ++ return CImg(*this,false).log2(); ++ } ++ ++ //! Compute the base-10 logarithm of each pixel value. ++ /** ++ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its base-10 logarithm ++ \f$\mathrm{log}_{10}(I_{(x,y,z,c)})\f$. ++ \note ++ - The \inplace of this method statically casts the computed values to the pixel type \c T. ++ - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. ++ **/ ++ CImg& log10() { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=4096)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)std::log10((double)*ptrd); ++ return *this; ++ } ++ ++ //! Compute the base-10 logarithm of each pixel value \newinstance. ++ CImg get_log10() const { ++ return CImg(*this,false).log10(); ++ } ++ ++ //! Compute the absolute value of each pixel value. ++ /** ++ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its absolute value \f$|I_{(x,y,z,c)}|\f$. ++ \note ++ - The \inplace of this method statically casts the computed values to the pixel type \c T. ++ - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. ++ **/ ++ CImg& abs() { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=524288)) ++ cimg_rof(*this,ptrd,T) *ptrd = cimg::abs(*ptrd); ++ return *this; ++ } ++ ++ //! Compute the absolute value of each pixel value \newinstance. ++ CImg get_abs() const { ++ return CImg(*this,false).abs(); ++ } ++ ++ //! Compute the sign of each pixel value. ++ /** ++ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its sign ++ \f$\mathrm{sign}(I_{(x,y,z,c)})\f$. ++ \note ++ - The sign is set to: ++ - \c 1 if pixel value is strictly positive. ++ - \c -1 if pixel value is strictly negative. ++ - \c 0 if pixel value is equal to \c 0. ++ - The \inplace of this method statically casts the computed values to the pixel type \c T. ++ - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. ++ **/ ++ CImg& sign() { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768)) ++ cimg_rof(*this,ptrd,T) *ptrd = cimg::sign(*ptrd); ++ return *this; ++ } ++ ++ //! Compute the sign of each pixel value \newinstance. ++ CImg get_sign() const { ++ return CImg(*this,false).sign(); ++ } ++ ++ //! Compute the cosine of each pixel value. ++ /** ++ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its cosine \f$\cos(I_{(x,y,z,c)})\f$. ++ \note ++ - Pixel values are regarded as being in \e radian. ++ - The \inplace of this method statically casts the computed values to the pixel type \c T. ++ - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. ++ **/ ++ CImg& cos() { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=8192)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)std::cos((double)*ptrd); ++ return *this; ++ } ++ ++ //! Compute the cosine of each pixel value \newinstance. ++ CImg get_cos() const { ++ return CImg(*this,false).cos(); ++ } ++ ++ //! Compute the sine of each pixel value. ++ /** ++ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its sine \f$\sin(I_{(x,y,z,c)})\f$. ++ \note ++ - Pixel values are regarded as being in \e radian. ++ - The \inplace of this method statically casts the computed values to the pixel type \c T. ++ - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. ++ **/ ++ CImg& sin() { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=8192)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)std::sin((double)*ptrd); ++ return *this; ++ } ++ ++ //! Compute the sine of each pixel value \newinstance. ++ CImg get_sin() const { ++ return CImg(*this,false).sin(); ++ } ++ ++ //! Compute the sinc of each pixel value. ++ /** ++ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its sinc ++ \f$\mathrm{sinc}(I_{(x,y,z,c)})\f$. ++ \note ++ - Pixel values are regarded as being exin \e radian. ++ - The \inplace of this method statically casts the computed values to the pixel type \c T. ++ - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. ++ **/ ++ CImg& sinc() { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=2048)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)cimg::sinc((double)*ptrd); ++ return *this; ++ } ++ ++ //! Compute the sinc of each pixel value \newinstance. ++ CImg get_sinc() const { ++ return CImg(*this,false).sinc(); ++ } ++ ++ //! Compute the tangent of each pixel value. ++ /** ++ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its tangent \f$\tan(I_{(x,y,z,c)})\f$. ++ \note ++ - Pixel values are regarded as being exin \e radian. ++ - The \inplace of this method statically casts the computed values to the pixel type \c T. ++ - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. ++ **/ ++ CImg& tan() { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=2048)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)std::tan((double)*ptrd); ++ return *this; ++ } ++ ++ //! Compute the tangent of each pixel value \newinstance. ++ CImg get_tan() const { ++ return CImg(*this,false).tan(); ++ } ++ ++ //! Compute the hyperbolic cosine of each pixel value. ++ /** ++ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic cosine ++ \f$\mathrm{cosh}(I_{(x,y,z,c)})\f$. ++ \note ++ - The \inplace of this method statically casts the computed values to the pixel type \c T. ++ - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. ++ **/ ++ CImg& cosh() { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=2048)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)std::cosh((double)*ptrd); ++ return *this; ++ } ++ ++ //! Compute the hyperbolic cosine of each pixel value \newinstance. ++ CImg get_cosh() const { ++ return CImg(*this,false).cosh(); ++ } ++ ++ //! Compute the hyperbolic sine of each pixel value. ++ /** ++ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic sine ++ \f$\mathrm{sinh}(I_{(x,y,z,c)})\f$. ++ \note ++ - The \inplace of this method statically casts the computed values to the pixel type \c T. ++ - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. ++ **/ ++ CImg& sinh() { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=2048)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)std::sinh((double)*ptrd); ++ return *this; ++ } ++ ++ //! Compute the hyperbolic sine of each pixel value \newinstance. ++ CImg get_sinh() const { ++ return CImg(*this,false).sinh(); ++ } ++ ++ //! Compute the hyperbolic tangent of each pixel value. ++ /** ++ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic tangent ++ \f$\mathrm{tanh}(I_{(x,y,z,c)})\f$. ++ \note ++ - The \inplace of this method statically casts the computed values to the pixel type \c T. ++ - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. ++ **/ ++ CImg& tanh() { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=2048)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)std::tanh((double)*ptrd); ++ return *this; ++ } ++ ++ //! Compute the hyperbolic tangent of each pixel value \newinstance. ++ CImg get_tanh() const { ++ return CImg(*this,false).tanh(); ++ } ++ ++ //! Compute the arccosine of each pixel value. ++ /** ++ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arccosine ++ \f$\mathrm{acos}(I_{(x,y,z,c)})\f$. ++ \note ++ - The \inplace of this method statically casts the computed values to the pixel type \c T. ++ - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. ++ **/ ++ CImg& acos() { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=8192)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)std::acos((double)*ptrd); ++ return *this; ++ } ++ ++ //! Compute the arccosine of each pixel value \newinstance. ++ CImg get_acos() const { ++ return CImg(*this,false).acos(); ++ } ++ ++ //! Compute the arcsine of each pixel value. ++ /** ++ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arcsine ++ \f$\mathrm{asin}(I_{(x,y,z,c)})\f$. ++ \note ++ - The \inplace of this method statically casts the computed values to the pixel type \c T. ++ - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. ++ **/ ++ CImg& asin() { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=8192)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)std::asin((double)*ptrd); ++ return *this; ++ } ++ ++ //! Compute the arcsine of each pixel value \newinstance. ++ CImg get_asin() const { ++ return CImg(*this,false).asin(); ++ } ++ ++ //! Compute the arctangent of each pixel value. ++ /** ++ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arctangent ++ \f$\mathrm{atan}(I_{(x,y,z,c)})\f$. ++ \note ++ - The \inplace of this method statically casts the computed values to the pixel type \c T. ++ - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. ++ **/ ++ CImg& atan() { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=8192)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)std::atan((double)*ptrd); ++ return *this; ++ } ++ ++ //! Compute the arctangent of each pixel value \newinstance. ++ CImg get_atan() const { ++ return CImg(*this,false).atan(); ++ } ++ ++ //! Compute the arctangent2 of each pixel value. ++ /** ++ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arctangent2 ++ \f$\mathrm{atan2}(I_{(x,y,z,c)})\f$. ++ \param img Image whose pixel values specify the second argument of the \c atan2() function. ++ \note ++ - The \inplace of this method statically casts the computed values to the pixel type \c T. ++ - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. ++ \par Example ++ \code ++ const CImg ++ img_x(100,100,1,1,"x-w/2",false), // Define an horizontal centered gradient, from '-width/2' to 'width/2'. ++ img_y(100,100,1,1,"y-h/2",false), // Define a vertical centered gradient, from '-height/2' to 'height/2'. ++ img_atan2 = img_y.get_atan2(img_x); // Compute atan2(y,x) for each pixel value. ++ (img_x,img_y,img_atan2).display(); ++ \endcode ++ **/ ++ template ++ CImg& atan2(const CImg& img) { ++ const ulongT siz = size(), isiz = img.size(); ++ if (siz && isiz) { ++ if (is_overlapped(img)) return atan2(+img); ++ T *ptrd = _data, *const ptre = _data + siz; ++ if (siz>isiz) for (ulongT n = siz/isiz; n; --n) ++ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs ++ CImg get_atan2(const CImg& img) const { ++ return CImg(*this,false).atan2(img); ++ } ++ ++ //! In-place pointwise multiplication. ++ /** ++ Compute the pointwise multiplication between the image instance and the specified input image \c img. ++ \param img Input image, as the second operand of the multiplication. ++ \note ++ - Similar to operator+=(const CImg&), except that it performs a pointwise multiplication ++ instead of an addition. ++ - It does \e not perform a \e matrix multiplication. For this purpose, use operator*=(const CImg&) instead. ++ \par Example ++ \code ++ CImg ++ img("reference.jpg"), ++ shade(img.width,img.height(),1,1,"-(x-w/2)^2-(y-h/2)^2",false); ++ shade.normalize(0,1); ++ (img,shade,img.get_mul(shade)).display(); ++ \endcode ++ **/ ++ template ++ CImg& mul(const CImg& img) { ++ const ulongT siz = size(), isiz = img.size(); ++ if (siz && isiz) { ++ if (is_overlapped(img)) return mul(+img); ++ T *ptrd = _data, *const ptre = _data + siz; ++ if (siz>isiz) for (ulongT n = siz/isiz; n; --n) ++ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs ++ CImg<_cimg_Tt> get_mul(const CImg& img) const { ++ return CImg<_cimg_Tt>(*this,false).mul(img); ++ } ++ ++ //! In-place pointwise division. ++ /** ++ Similar to mul(const CImg&), except that it performs a pointwise division instead of a multiplication. ++ **/ ++ template ++ CImg& div(const CImg& img) { ++ const ulongT siz = size(), isiz = img.size(); ++ if (siz && isiz) { ++ if (is_overlapped(img)) return div(+img); ++ T *ptrd = _data, *const ptre = _data + siz; ++ if (siz>isiz) for (ulongT n = siz/isiz; n; --n) ++ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs ++ CImg<_cimg_Tt> get_div(const CImg& img) const { ++ return CImg<_cimg_Tt>(*this,false).div(img); ++ } ++ ++ //! Raise each pixel value to a specified power. ++ /** ++ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its power \f$I_{(x,y,z,c)}^p\f$. ++ \param p Exponent value. ++ \note ++ - The \inplace of this method statically casts the computed values to the pixel type \c T. ++ - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. ++ \par Example ++ \code ++ const CImg ++ img0("reference.jpg"), // Load reference color image. ++ img1 = (img0/255).pow(1.8)*=255, // Compute gamma correction, with gamma = 1.8. ++ img2 = (img0/255).pow(0.5)*=255; // Compute gamma correction, with gamma = 0.5. ++ (img0,img1,img2).display(); ++ \endcode ++ **/ ++ CImg& pow(const double p) { ++ if (is_empty()) return *this; ++ if (p==-4) { ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768)) ++ cimg_rof(*this,ptrd,T) { const T val = *ptrd; *ptrd = (T)(1.0/(val*val*val*val)); } ++ return *this; ++ } ++ if (p==-3) { ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768)) ++ cimg_rof(*this,ptrd,T) { const T val = *ptrd; *ptrd = (T)(1.0/(val*val*val)); } ++ return *this; ++ } ++ if (p==-2) { ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768)) ++ cimg_rof(*this,ptrd,T) { const T val = *ptrd; *ptrd = (T)(1.0/(val*val)); } ++ return *this; ++ } ++ if (p==-1) { ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768)) ++ cimg_rof(*this,ptrd,T) { const T val = *ptrd; *ptrd = (T)(1.0/val); } ++ return *this; ++ } ++ if (p==-0.5) { ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=8192)) ++ cimg_rof(*this,ptrd,T) { const T val = *ptrd; *ptrd = (T)(1/std::sqrt((double)val)); } ++ return *this; ++ } ++ if (p==0) return fill((T)1); ++ if (p==0.25) return sqrt().sqrt(); ++ if (p==0.5) return sqrt(); ++ if (p==1) return *this; ++ if (p==2) return sqr(); ++ if (p==3) { ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=262144)) ++ cimg_rof(*this,ptrd,T) { const T val = *ptrd; *ptrd = val*val*val; } ++ return *this; ++ } ++ if (p==4) { ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=131072)) ++ cimg_rof(*this,ptrd,T) { const T val = *ptrd; *ptrd = val*val*val*val; } ++ return *this; ++ } ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=1024)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)std::pow((double)*ptrd,p); ++ return *this; ++ } ++ ++ //! Raise each pixel value to a specified power \newinstance. ++ CImg get_pow(const double p) const { ++ return CImg(*this,false).pow(p); ++ } ++ ++ //! Raise each pixel value to a power, specified from an expression. ++ /** ++ Similar to operator+=(const char*), except it performs a pointwise exponentiation instead of an addition. ++ **/ ++ CImg& pow(const char *const expression) { ++ return pow((+*this)._fill(expression,true,true,0,0,"pow",this)); ++ } ++ ++ //! Raise each pixel value to a power, specified from an expression \newinstance. ++ CImg get_pow(const char *const expression) const { ++ return CImg(*this,false).pow(expression); ++ } ++ ++ //! Raise each pixel value to a power, pointwisely specified from another image. ++ /** ++ Similar to operator+=(const CImg& img), except that it performs an exponentiation instead of an addition. ++ **/ ++ template ++ CImg& pow(const CImg& img) { ++ const ulongT siz = size(), isiz = img.size(); ++ if (siz && isiz) { ++ if (is_overlapped(img)) return pow(+img); ++ T *ptrd = _data, *const ptre = _data + siz; ++ if (siz>isiz) for (ulongT n = siz/isiz; n; --n) ++ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs ++ CImg get_pow(const CImg& img) const { ++ return CImg(*this,false).pow(img); ++ } ++ ++ //! Compute the bitwise left rotation of each pixel value. ++ /** ++ Similar to operator<<=(unsigned int), except that it performs a left rotation instead of a left shift. ++ **/ ++ CImg& rol(const unsigned int n=1) { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)cimg::rol(*ptrd,n); ++ return *this; ++ } ++ ++ //! Compute the bitwise left rotation of each pixel value \newinstance. ++ CImg get_rol(const unsigned int n=1) const { ++ return (+*this).rol(n); ++ } ++ ++ //! Compute the bitwise left rotation of each pixel value. ++ /** ++ Similar to operator<<=(const char*), except that it performs a left rotation instead of a left shift. ++ **/ ++ CImg& rol(const char *const expression) { ++ return rol((+*this)._fill(expression,true,true,0,0,"rol",this)); ++ } ++ ++ //! Compute the bitwise left rotation of each pixel value \newinstance. ++ CImg get_rol(const char *const expression) const { ++ return (+*this).rol(expression); ++ } ++ ++ //! Compute the bitwise left rotation of each pixel value. ++ /** ++ Similar to operator<<=(const CImg&), except that it performs a left rotation instead of a left shift. ++ **/ ++ template ++ CImg& rol(const CImg& img) { ++ const ulongT siz = size(), isiz = img.size(); ++ if (siz && isiz) { ++ if (is_overlapped(img)) return rol(+img); ++ T *ptrd = _data, *const ptre = _data + siz; ++ if (siz>isiz) for (ulongT n = siz/isiz; n; --n) ++ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs ++ CImg get_rol(const CImg& img) const { ++ return (+*this).rol(img); ++ } ++ ++ //! Compute the bitwise right rotation of each pixel value. ++ /** ++ Similar to operator>>=(unsigned int), except that it performs a right rotation instead of a right shift. ++ **/ ++ CImg& ror(const unsigned int n=1) { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768)) ++ cimg_rof(*this,ptrd,T) *ptrd = (T)cimg::ror(*ptrd,n); ++ return *this; ++ } ++ ++ //! Compute the bitwise right rotation of each pixel value \newinstance. ++ CImg get_ror(const unsigned int n=1) const { ++ return (+*this).ror(n); ++ } ++ ++ //! Compute the bitwise right rotation of each pixel value. ++ /** ++ Similar to operator>>=(const char*), except that it performs a right rotation instead of a right shift. ++ **/ ++ CImg& ror(const char *const expression) { ++ return ror((+*this)._fill(expression,true,true,0,0,"ror",this)); ++ } ++ ++ //! Compute the bitwise right rotation of each pixel value \newinstance. ++ CImg get_ror(const char *const expression) const { ++ return (+*this).ror(expression); ++ } ++ ++ //! Compute the bitwise right rotation of each pixel value. ++ /** ++ Similar to operator>>=(const CImg&), except that it performs a right rotation instead of a right shift. ++ **/ ++ template ++ CImg& ror(const CImg& img) { ++ const ulongT siz = size(), isiz = img.size(); ++ if (siz && isiz) { ++ if (is_overlapped(img)) return ror(+img); ++ T *ptrd = _data, *const ptre = _data + siz; ++ if (siz>isiz) for (ulongT n = siz/isiz; n; --n) ++ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs ++ CImg get_ror(const CImg& img) const { ++ return (+*this).ror(img); ++ } ++ ++ //! Pointwise min operator between instance image and a value. ++ /** ++ \param val Value used as the reference argument of the min operator. ++ \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by ++ \f$\mathrm{min}(I_{(x,y,z,c)},\mathrm{val})\f$. ++ **/ ++ CImg& min(const T& val) { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=65536)) ++ cimg_rof(*this,ptrd,T) *ptrd = std::min(*ptrd,val); ++ return *this; ++ } ++ ++ //! Pointwise min operator between instance image and a value \newinstance. ++ CImg get_min(const T& val) const { ++ return (+*this).min(val); ++ } ++ ++ //! Pointwise min operator between two images. ++ /** ++ \param img Image used as the reference argument of the min operator. ++ \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by ++ \f$\mathrm{min}(I_{(x,y,z,c)},\mathrm{img}_{(x,y,z,c)})\f$. ++ **/ ++ template ++ CImg& min(const CImg& img) { ++ const ulongT siz = size(), isiz = img.size(); ++ if (siz && isiz) { ++ if (is_overlapped(img)) return min(+img); ++ T *ptrd = _data, *const ptre = _data + siz; ++ if (siz>isiz) for (ulongT n = siz/isiz; n; --n) ++ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs ++ CImg<_cimg_Tt> get_min(const CImg& img) const { ++ return CImg<_cimg_Tt>(*this,false).min(img); ++ } ++ ++ //! Pointwise min operator between an image and an expression. ++ /** ++ \param expression Math formula as a C-string. ++ \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by ++ \f$\mathrm{min}(I_{(x,y,z,c)},\mathrm{expr}_{(x,y,z,c)})\f$. ++ **/ ++ CImg& min(const char *const expression) { ++ return min((+*this)._fill(expression,true,true,0,0,"min",this)); ++ } ++ ++ //! Pointwise min operator between an image and an expression \newinstance. ++ CImg get_min(const char *const expression) const { ++ return CImg(*this,false).min(expression); ++ } ++ ++ //! Pointwise max operator between instance image and a value. ++ /** ++ \param val Value used as the reference argument of the max operator. ++ \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by ++ \f$\mathrm{max}(I_{(x,y,z,c)},\mathrm{val})\f$. ++ **/ ++ CImg& max(const T& val) { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=65536)) ++ cimg_rof(*this,ptrd,T) *ptrd = std::max(*ptrd,val); ++ return *this; ++ } ++ ++ //! Pointwise max operator between instance image and a value \newinstance. ++ CImg get_max(const T& val) const { ++ return (+*this).max(val); ++ } ++ ++ //! Pointwise max operator between two images. ++ /** ++ \param img Image used as the reference argument of the max operator. ++ \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by ++ \f$\mathrm{max}(I_{(x,y,z,c)},\mathrm{img}_{(x,y,z,c)})\f$. ++ **/ ++ template ++ CImg& max(const CImg& img) { ++ const ulongT siz = size(), isiz = img.size(); ++ if (siz && isiz) { ++ if (is_overlapped(img)) return max(+img); ++ T *ptrd = _data, *const ptre = _data + siz; ++ if (siz>isiz) for (ulongT n = siz/isiz; n; --n) ++ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs ++ CImg<_cimg_Tt> get_max(const CImg& img) const { ++ return CImg<_cimg_Tt>(*this,false).max(img); ++ } ++ ++ //! Pointwise max operator between an image and an expression. ++ /** ++ \param expression Math formula as a C-string. ++ \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by ++ \f$\mathrm{max}(I_{(x,y,z,c)},\mathrm{expr}_{(x,y,z,c)})\f$. ++ **/ ++ CImg& max(const char *const expression) { ++ return max((+*this)._fill(expression,true,true,0,0,"max",this)); ++ } ++ ++ //! Pointwise max operator between an image and an expression \newinstance. ++ CImg get_max(const char *const expression) const { ++ return CImg(*this,false).max(expression); ++ } ++ ++ //! Return a reference to the minimum pixel value. ++ /** ++ **/ ++ T& min() { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "min(): Empty instance.", ++ cimg_instance); ++ T *ptr_min = _data; ++ T min_value = *ptr_min; ++ cimg_for(*this,ptrs,T) if (*ptrsmax_value) max_value = *(ptr_max=ptrs); ++ return *ptr_max; ++ } ++ ++ //! Return a reference to the maximum pixel value \const. ++ const T& max() const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "max(): Empty instance.", ++ cimg_instance); ++ const T *ptr_max = _data; ++ T max_value = *ptr_max; ++ cimg_for(*this,ptrs,T) if (*ptrs>max_value) max_value = *(ptr_max=ptrs); ++ return *ptr_max; ++ } ++ ++ //! Return a reference to the minimum pixel value as well as the maximum pixel value. ++ /** ++ \param[out] max_val Maximum pixel value. ++ **/ ++ template ++ T& min_max(t& max_val) { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "min_max(): Empty instance.", ++ cimg_instance); ++ T *ptr_min = _data; ++ T min_value = *ptr_min, max_value = min_value; ++ cimg_for(*this,ptrs,T) { ++ const T val = *ptrs; ++ if (valmax_value) max_value = val; ++ } ++ max_val = (t)max_value; ++ return *ptr_min; ++ } ++ ++ //! Return a reference to the minimum pixel value as well as the maximum pixel value \const. ++ template ++ const T& min_max(t& max_val) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "min_max(): Empty instance.", ++ cimg_instance); ++ const T *ptr_min = _data; ++ T min_value = *ptr_min, max_value = min_value; ++ cimg_for(*this,ptrs,T) { ++ const T val = *ptrs; ++ if (valmax_value) max_value = val; ++ } ++ max_val = (t)max_value; ++ return *ptr_min; ++ } ++ ++ //! Return a reference to the maximum pixel value as well as the minimum pixel value. ++ /** ++ \param[out] min_val Minimum pixel value. ++ **/ ++ template ++ T& max_min(t& min_val) { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "max_min(): Empty instance.", ++ cimg_instance); ++ T *ptr_max = _data; ++ T max_value = *ptr_max, min_value = max_value; ++ cimg_for(*this,ptrs,T) { ++ const T val = *ptrs; ++ if (val>max_value) { max_value = val; ptr_max = ptrs; } ++ if (val ++ const T& max_min(t& min_val) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "max_min(): Empty instance.", ++ cimg_instance); ++ const T *ptr_max = _data; ++ T max_value = *ptr_max, min_value = max_value; ++ cimg_for(*this,ptrs,T) { ++ const T val = *ptrs; ++ if (val>max_value) { max_value = val; ptr_max = ptrs; } ++ if (val arr(*this,false); ++ ulongT 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]); ++ ulongT 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 the median pixel value. ++ /** ++ **/ ++ T median() const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "median(): Empty instance.", ++ cimg_instance); ++ const ulongT s = size(); ++ switch (s) { ++ case 1 : return _data[0]; ++ case 2 : return cimg::median(_data[0],_data[1]); ++ case 3 : return cimg::median(_data[0],_data[1],_data[2]); ++ case 5 : return cimg::median(_data[0],_data[1],_data[2],_data[3],_data[4]); ++ case 7 : return cimg::median(_data[0],_data[1],_data[2],_data[3],_data[4],_data[5],_data[6]); ++ case 9 : return cimg::median(_data[0],_data[1],_data[2],_data[3],_data[4],_data[5],_data[6],_data[7],_data[8]); ++ case 13 : return cimg::median(_data[0],_data[1],_data[2],_data[3],_data[4],_data[5],_data[6],_data[7],_data[8], ++ _data[9],_data[10],_data[11],_data[12]); ++ } ++ const T res = kth_smallest(s>>1); ++ return (s%2)?res:(T)((res + kth_smallest((s>>1) - 1))/2); ++ } ++ ++ //! Return the product of all the pixel values. ++ /** ++ **/ ++ double product() const { ++ if (is_empty()) return 0; ++ double res = 1; ++ cimg_for(*this,ptrs,T) res*=(double)*ptrs; ++ return res; ++ } ++ ++ //! Return the sum of all the pixel values. ++ /** ++ **/ ++ double sum() const { ++ double res = 0; ++ cimg_for(*this,ptrs,T) res+=(double)*ptrs; ++ return res; ++ } ++ ++ //! Return the average pixel value. ++ /** ++ **/ ++ double mean() const { ++ double res = 0; ++ cimg_for(*this,ptrs,T) res+=(double)*ptrs; ++ return res/size(); ++ } ++ ++ //! Return the variance of the pixel values. ++ /** ++ \param variance_method Method used to estimate the variance. Can be: ++ - \c 0: Second moment, computed as ++ \f$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$. ++ - \c 1: Best unbiased estimator, computed as \f$\frac{1}{N - 1} \sum\limits_{k=1}^{N} (x_k - \bar x)^2 \f$. ++ - \c 2: Least median of squares. ++ - \c 3: Least trimmed of squares. ++ **/ ++ double variance(const unsigned int variance_method=1) const { ++ double foo; ++ return variance_mean(variance_method,foo); ++ } ++ ++ //! Return the variance as well as the average of the pixel values. ++ /** ++ \param variance_method Method used to estimate the variance (see variance(const unsigned int) const). ++ \param[out] mean Average pixel value. ++ **/ ++ template ++ double variance_mean(const unsigned int variance_method, t& mean) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "variance_mean(): Empty instance.", ++ cimg_instance); ++ ++ double variance = 0, average = 0; ++ const ulongT siz = size(); ++ switch (variance_method) { ++ case 0 : { // Least mean square (standard definition) ++ double S = 0, S2 = 0; ++ cimg_for(*this,ptrs,T) { const double val = (double)*ptrs; S+=val; S2+=val*val; } ++ variance = (S2 - S*S/siz)/siz; ++ average = S; ++ } break; ++ case 1 : { // Least mean square (robust definition) ++ double S = 0, S2 = 0; ++ cimg_for(*this,ptrs,T) { const double val = (double)*ptrs; S+=val; S2+=val*val; } ++ variance = siz>1?(S2 - S*S/siz)/(siz - 1):0; ++ average = S; ++ } break; ++ case 2 : { // Least Median of Squares (MAD) ++ CImg buf(*this,false); ++ buf.sort(); ++ const ulongT siz2 = siz>>1; ++ const double med_i = (double)buf[siz2]; ++ cimg_for(buf,ptrs,Tfloat) { ++ const double val = (double)*ptrs; *ptrs = (Tfloat)cimg::abs(val - med_i); average+=val; ++ } ++ buf.sort(); ++ const double sig = (double)(1.4828*buf[siz2]); ++ variance = sig*sig; ++ } break; ++ default : { // Least trimmed of Squares ++ CImg buf(*this,false); ++ const ulongT siz2 = siz>>1; ++ cimg_for(buf,ptrs,Tfloat) { ++ const double val = (double)*ptrs; (*ptrs)=(Tfloat)((*ptrs)*val); average+=val; ++ } ++ buf.sort(); ++ double a = 0; ++ const Tfloat *ptrs = buf._data; ++ for (ulongT j = 0; j0?variance:0; ++ } ++ ++ //! Return estimated variance of the noise. ++ /** ++ \param variance_method Method used to compute the variance (see variance(const unsigned int) const). ++ \note Because of structures such as edges in images it is ++ recommanded to use a robust variance estimation. The variance of the ++ noise is estimated by computing the variance of the Laplacian \f$(\Delta ++ I)^2 \f$ scaled by a factor \f$c\f$ insuring \f$ c E[(\Delta I)^2]= ++ \sigma^2\f$ where \f$\sigma\f$ is the noise variance. ++ **/ ++ double variance_noise(const unsigned int variance_method=2) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "variance_noise(): Empty instance.", ++ cimg_instance); ++ ++ const ulongT siz = size(); ++ if (!siz || !_data) return 0; ++ if (variance_method>1) { // Compute a scaled version of the Laplacian. ++ CImg tmp(*this,false); ++ if (_depth==1) { ++ const double cste = 1.0/std::sqrt(20.0); // Depends on how the Laplacian is computed. ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=262144 && _spectrum>=2)) ++ cimg_forC(*this,c) { ++ CImg_3x3(I,T); ++ cimg_for3x3(*this,x,y,0,c,I,T) { ++ tmp(x,y,c) = cste*((double)Inc + (double)Ipc + (double)Icn + ++ (double)Icp - 4*(double)Icc); ++ } ++ } ++ } else { ++ const double cste = 1.0/std::sqrt(42.0); // Depends on how the Laplacian is computed. ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=262144 && _spectrum>=2)) ++ cimg_forC(*this,c) { ++ CImg_3x3x3(I,T); ++ cimg_for3x3x3(*this,x,y,z,c,I,T) { ++ tmp(x,y,z,c) = cste*( ++ (double)Incc + (double)Ipcc + (double)Icnc + (double)Icpc + ++ (double)Iccn + (double)Iccp - 6*(double)Iccc); ++ } ++ } ++ } ++ return tmp.variance(variance_method); ++ } ++ ++ // Version that doesn't need intermediate images. ++ double variance = 0, S = 0, S2 = 0; ++ if (_depth==1) { ++ const double cste = 1.0/std::sqrt(20.0); ++ CImg_3x3(I,T); ++ cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,T) { ++ const double val = cste*((double)Inc + (double)Ipc + ++ (double)Icn + (double)Icp - 4*(double)Icc); ++ S+=val; S2+=val*val; ++ } ++ } else { ++ const double cste = 1.0/std::sqrt(42.0); ++ CImg_3x3x3(I,T); ++ cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,T) { ++ const double val = cste * ++ ((double)Incc + (double)Ipcc + (double)Icnc + ++ (double)Icpc + ++ (double)Iccn + (double)Iccp - 6*(double)Iccc); ++ S+=val; S2+=val*val; ++ } ++ } ++ if (variance_method) variance = siz>1?(S2 - S*S/siz)/(siz - 1):0; ++ else variance = (S2 - S*S/siz)/siz; ++ return variance>0?variance:0; ++ } ++ ++ //! Compute the MSE (Mean-Squared Error) between two images. ++ /** ++ \param img Image used as the second argument of the MSE operator. ++ **/ ++ template ++ double MSE(const CImg& img) const { ++ if (img.size()!=size()) ++ throw CImgArgumentException(_cimg_instance ++ "MSE(): Instance and specified image (%u,%u,%u,%u,%p) have different dimensions.", ++ cimg_instance, ++ img._width,img._height,img._depth,img._spectrum,img._data); ++ double vMSE = 0; ++ const t* ptr2 = img._data; ++ cimg_for(*this,ptr1,T) { ++ const double diff = (double)*ptr1 - (double)*(ptr2++); ++ vMSE+=diff*diff; ++ } ++ const ulongT siz = img.size(); ++ if (siz) vMSE/=siz; ++ return vMSE; ++ } ++ ++ //! Compute the PSNR (Peak Signal-to-Noise Ratio) between two images. ++ /** ++ \param img Image used as the second argument of the PSNR operator. ++ \param max_value Maximum theoretical value of the signal. ++ **/ ++ template ++ double PSNR(const CImg& img, const double max_value=255) const { ++ const double vMSE = (double)std::sqrt(MSE(img)); ++ return (vMSE!=0)?(double)(20*std::log10(max_value/vMSE)):(double)(cimg::type::max()); ++ } ++ ++ //! Evaluate math formula. ++ /** ++ \param expression Math formula, as a C-string. ++ \param x Value of the pre-defined variable \c x. ++ \param y Value of the pre-defined variable \c y. ++ \param z Value of the pre-defined variable \c z. ++ \param c Value of the pre-defined variable \c c. ++ \param list_inputs A list of input images attached to the specified math formula. ++ \param[out] list_outputs A pointer to a list of output images attached to the specified math formula. ++ **/ ++ double eval(const char *const expression, ++ const double x=0, const double y=0, const double z=0, const double c=0, ++ const CImgList *const list_inputs=0, CImgList *const list_outputs=0) { ++ return _eval(this,expression,x,y,z,c,list_inputs,list_outputs); ++ } ++ ++ //! Evaluate math formula \const. ++ double eval(const char *const expression, ++ const double x=0, const double y=0, const double z=0, const double c=0, ++ const CImgList *const list_inputs=0, CImgList *const list_outputs=0) const { ++ return _eval(0,expression,x,y,z,c,list_inputs,list_outputs); ++ } ++ ++ double _eval(CImg *const img_output, const char *const expression, ++ const double x, const double y, const double z, const double c, ++ const CImgList *const list_inputs, CImgList *const list_outputs) const { ++ if (!expression || !*expression) return 0; ++ if (!expression[1]) switch (*expression) { // Single-char optimization. ++ case 'w' : return (double)_width; ++ case 'h' : return (double)_height; ++ case 'd' : return (double)_depth; ++ case 's' : return (double)_spectrum; ++ case 'r' : return (double)_is_shared; ++ } ++ _cimg_math_parser mp(expression + (*expression=='>' || *expression=='<' || ++ *expression=='*' || *expression==':'),"eval", ++ *this,img_output,list_inputs,list_outputs,false); ++ const double val = mp(x,y,z,c); ++ mp.end(); ++ return val; ++ } ++ ++ //! Evaluate math formula. ++ /** ++ \param[out] output Contains values of output vector returned by the evaluated expression ++ (or is empty if the returned type is scalar). ++ \param expression Math formula, as a C-string. ++ \param x Value of the pre-defined variable \c x. ++ \param y Value of the pre-defined variable \c y. ++ \param z Value of the pre-defined variable \c z. ++ \param c Value of the pre-defined variable \c c. ++ \param list_inputs A list of input images attached to the specified math formula. ++ \param[out] list_outputs A pointer to a list of output images attached to the specified math formula. ++ **/ ++ template ++ void eval(CImg &output, const char *const expression, ++ const double x=0, const double y=0, const double z=0, const double c=0, ++ const CImgList *const list_inputs=0, CImgList *const list_outputs=0) { ++ _eval(output,this,expression,x,y,z,c,list_inputs,list_outputs); ++ } ++ ++ //! Evaluate math formula \const. ++ template ++ void eval(CImg& output, const char *const expression, ++ const double x=0, const double y=0, const double z=0, const double c=0, ++ const CImgList *const list_inputs=0, CImgList *const list_outputs=0) const { ++ _eval(output,0,expression,x,y,z,c,list_inputs,list_outputs); ++ } ++ ++ template ++ void _eval(CImg& output, CImg *const img_output, const char *const expression, ++ const double x, const double y, const double z, const double c, ++ const CImgList *const list_inputs, CImgList *const list_outputs) const { ++ if (!expression || !*expression) { output.assign(1); *output = 0; } ++ if (!expression[1]) switch (*expression) { // Single-char optimization. ++ case 'w' : output.assign(1); *output = (t)_width; break; ++ case 'h' : output.assign(1); *output = (t)_height; break; ++ case 'd' : output.assign(1); *output = (t)_depth; break; ++ case 's' : output.assign(1); *output = (t)_spectrum; break; ++ case 'r' : output.assign(1); *output = (t)_is_shared; break; ++ } ++ _cimg_math_parser mp(expression + (*expression=='>' || *expression=='<' || ++ *expression=='*' || *expression==':'),"eval", ++ *this,img_output,list_inputs,list_outputs,false); ++ output.assign(1,std::max(1U,mp.result_dim)); ++ mp(x,y,z,c,output._data); ++ mp.end(); ++ } ++ ++ //! Evaluate math formula on a set of variables. ++ /** ++ \param expression Math formula, as a C-string. ++ \param xyzc Set of values (x,y,z,c) used for the evaluation. ++ \param list_inputs A list of input images attached to the specified math formula. ++ \param[out] list_outputs A pointer to a list of output images attached to the specified math formula. ++ **/ ++ template ++ CImg eval(const char *const expression, const CImg& xyzc, ++ const CImgList *const list_inputs=0, CImgList *const list_outputs=0) { ++ return _eval(this,expression,xyzc,list_inputs,list_outputs); ++ } ++ ++ //! Evaluate math formula on a set of variables \const. ++ template ++ CImg eval(const char *const expression, const CImg& xyzc, ++ const CImgList *const list_inputs=0, CImgList *const list_outputs=0) const { ++ return _eval(0,expression,xyzc,list_inputs,list_outputs); ++ } ++ ++ template ++ CImg _eval(CImg *const output, const char *const expression, const CImg& xyzc, ++ const CImgList *const list_inputs=0, CImgList *const list_outputs=0) const { ++ CImg res(1,xyzc.size()/4); ++ if (!expression || !*expression) return res.fill(0); ++ _cimg_math_parser mp(expression,"eval",*this,output,list_inputs,list_outputs,false); ++#ifdef cimg_use_openmp ++ cimg_pragma_openmp(parallel if (res._height>=512)) ++ { ++ _cimg_math_parser ++ _mp = omp_get_thread_num()?mp:_cimg_math_parser(), ++ &lmp = omp_get_thread_num()?_mp:mp; ++ cimg_pragma_openmp(for) ++ for (unsigned int i = 0; i[min, max, mean, variance, xmin, ymin, zmin, cmin, xmax, ymax, zmax, cmax, sum, product]. ++ **/ ++ CImg get_stats(const unsigned int variance_method=1) const { ++ if (is_empty()) return CImg(); ++ const T *const p_end = end(), *pm = _data, *pM = _data; ++ double S = 0, S2 = 0, P = 1; ++ const ulongT siz = size(); ++ T m = *pm, M = *pM; ++ ++ cimg_pragma_openmp(parallel reduction(+:S,S2) reduction(*:P) cimg_openmp_if(siz>=131072)) { ++ const T *lpm = _data, *lpM = _data; ++ T lm = *lpm, lM = *lpM; ++ cimg_pragma_openmp(for) ++ for (const T *ptrs = _data; ptrslM) { lM = val; lpM = ptrs; } ++ S+=_val; ++ S2+=_val*_val; ++ P*=_val; ++ } ++ cimg_pragma_openmp(critical(get_stats)) { ++ if (lmM || (lM==M && lpM1?(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, cm = 0, ++ xM = 0, yM = 0, zM = 0, cM = 0; ++ contains(*pm,xm,ym,zm,cm); ++ contains(*pM,xM,yM,zM,cM); ++ return CImg(1,14).fill((double)m,(double)M,mean_value,variance_value, ++ (double)xm,(double)ym,(double)zm,(double)cm, ++ (double)xM,(double)yM,(double)zM,(double)cM, ++ S,P); ++ } ++ ++ //! Compute statistics vector from the pixel values \inplace. ++ CImg& stats(const unsigned int variance_method=1) { ++ return get_stats(variance_method).move_to(*this); ++ } ++ ++ //@} ++ //------------------------------------- ++ // ++ //! \name Vector / Matrix Operations ++ //@{ ++ //------------------------------------- ++ ++ //! Compute norm of the image, viewed as a matrix. ++ /** ++ \param magnitude_type Norm type. Can be: ++ - \c -1: Linf-norm ++ - \c 0: L0-norm ++ - \c 1: L1-norm ++ - \c 2: L2-norm ++ **/ ++ double magnitude(const int magnitude_type=2) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "magnitude(): Empty instance.", ++ cimg_instance); ++ double res = 0; ++ switch (magnitude_type) { ++ case -1 : { ++ cimg_for(*this,ptrs,T) { const double val = (double)cimg::abs(*ptrs); if (val>res) res = val; } ++ } break; ++ case 1 : { ++ cimg_for(*this,ptrs,T) res+=(double)cimg::abs(*ptrs); ++ } break; ++ default : { ++ cimg_for(*this,ptrs,T) res+=(double)cimg::sqr(*ptrs); ++ res = (double)std::sqrt(res); ++ } ++ } ++ return res; ++ } ++ ++ //! Compute the trace of the image, viewed as a matrix. ++ /** ++ **/ ++ double trace() const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "trace(): Empty instance.", ++ cimg_instance); ++ double res = 0; ++ cimg_forX(*this,k) res+=(double)(*this)(k,k); ++ return res; ++ } ++ ++ //! Compute the determinant of the image, viewed as a matrix. ++ /** ++ **/ ++ double det() const { ++ if (is_empty() || _width!=_height || _depth!=1 || _spectrum!=1) ++ throw CImgInstanceException(_cimg_instance ++ "det(): Instance is not a square matrix.", ++ cimg_instance); ++ ++ switch (_width) { ++ case 1 : return (double)((*this)(0,0)); ++ case 2 : return (double)((*this)(0,0))*(double)((*this)(1,1)) - (double)((*this)(0,1))*(double)((*this)(1,0)); ++ case 3 : { ++ const double ++ a = (double)_data[0], d = (double)_data[1], g = (double)_data[2], ++ b = (double)_data[3], e = (double)_data[4], h = (double)_data[5], ++ c = (double)_data[6], f = (double)_data[7], i = (double)_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,false); ++ CImg indx; ++ bool d; ++ lu._LU(indx,d); ++ double res = d?(double)1:(double)-1; ++ cimg_forX(lu,i) res*=lu(i,i); ++ return res; ++ } ++ } ++ } ++ ++ //! Compute the dot product between instance and argument, viewed as matrices. ++ /** ++ \param img Image used as a second argument of the dot product. ++ **/ ++ template ++ double dot(const CImg& img) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "dot(): Empty instance.", ++ cimg_instance); ++ if (!img) ++ throw CImgArgumentException(_cimg_instance ++ "dot(): Empty specified image.", ++ cimg_instance); ++ ++ const ulongT nb = std::min(size(),img.size()); ++ double res = 0; ++ for (ulongT off = 0; off get_vector_at(const unsigned int x, const unsigned int y=0, const unsigned int z=0) const { ++ CImg res; ++ if (res._height!=_spectrum) res.assign(1,_spectrum); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ const T *ptrs = data(x,y,z); ++ T *ptrd = res._data; ++ cimg_forC(*this,c) { *(ptrd++) = *ptrs; ptrs+=whd; } ++ return res; ++ } ++ ++ //! Get (square) matrix-valued pixel located at specified position. ++ /** ++ \param x X-coordinate of the pixel value. ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ \note - The spectrum() of the image must be a square. ++ **/ ++ CImg get_matrix_at(const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) const { ++ const int n = (int)std::sqrt((double)_spectrum); ++ const T *ptrs = data(x,y,z,0); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ CImg res(n,n); ++ T *ptrd = res._data; ++ cimg_forC(*this,c) { *(ptrd++) = *ptrs; ptrs+=whd; } ++ return res; ++ } ++ ++ //! Get tensor-valued pixel located at specified position. ++ /** ++ \param x X-coordinate of the pixel value. ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ **/ ++ CImg get_tensor_at(const unsigned int x, const unsigned int y=0, const unsigned int z=0) const { ++ const T *ptrs = data(x,y,z,0); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ if (_spectrum==6) ++ return tensor(*ptrs,*(ptrs + whd),*(ptrs + 2*whd),*(ptrs + 3*whd),*(ptrs + 4*whd),*(ptrs + 5*whd)); ++ if (_spectrum==3) ++ return tensor(*ptrs,*(ptrs + whd),*(ptrs + 2*whd)); ++ return tensor(*ptrs); ++ } ++ ++ //! Set vector-valued pixel at specified position. ++ /** ++ \param vec Vector to put on the instance image. ++ \param x X-coordinate of the pixel value. ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ **/ ++ template ++ CImg& set_vector_at(const CImg& vec, const unsigned int x, const unsigned int y=0, const unsigned int z=0) { ++ if (x<_width && y<_height && z<_depth) { ++ const t *ptrs = vec._data; ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ T *ptrd = data(x,y,z); ++ for (unsigned int k = std::min((unsigned int)vec.size(),_spectrum); k; --k) { ++ *ptrd = (T)*(ptrs++); ptrd+=whd; ++ } ++ } ++ return *this; ++ } ++ ++ //! Set (square) matrix-valued pixel at specified position. ++ /** ++ \param mat Matrix to put on the instance image. ++ \param x X-coordinate of the pixel value. ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ **/ ++ 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); ++ } ++ ++ //! Set tensor-valued pixel at specified position. ++ /** ++ \param ten Tensor to put on the instance image. ++ \param x X-coordinate of the pixel value. ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ **/ ++ template ++ CImg& set_tensor_at(const CImg& ten, const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) { ++ T *ptrd = data(x,y,z,0); ++ const ulongT siz = (ulongT)_width*_height*_depth; ++ if (ten._height==2) { ++ *ptrd = (T)ten[0]; ptrd+=siz; ++ *ptrd = (T)ten[1]; ptrd+=siz; ++ *ptrd = (T)ten[3]; ++ } ++ else { ++ *ptrd = (T)ten[0]; ptrd+=siz; ++ *ptrd = (T)ten[1]; ptrd+=siz; ++ *ptrd = (T)ten[2]; ptrd+=siz; ++ *ptrd = (T)ten[4]; ptrd+=siz; ++ *ptrd = (T)ten[5]; ptrd+=siz; ++ *ptrd = (T)ten[8]; ++ } ++ return *this; ++ } ++ ++ //! Unroll pixel values along axis \c y. ++ /** ++ \note Equivalent to \code unroll('y'); \endcode. ++ **/ ++ CImg& vector() { ++ return unroll('y'); ++ } ++ ++ //! Unroll pixel values along axis \c y \newinstance. ++ CImg get_vector() const { ++ return get_unroll('y'); ++ } ++ ++ //! Resize image to become a scalar square matrix. ++ /** ++ **/ ++ CImg& matrix() { ++ const ulongT 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 : { ++ ulongT i = 11, i2 = i*i; ++ while (i2 get_matrix() const { ++ return (+*this).matrix(); ++ } ++ ++ //! Resize image to become a symmetric tensor. ++ /** ++ **/ ++ CImg& tensor() { ++ return get_tensor().move_to(*this); ++ } ++ ++ //! Resize image to become a symmetric tensor \newinstance. ++ CImg get_tensor() const { ++ CImg res; ++ const ulongT 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_instance ++ "tensor(): Invalid instance size (does not define a 1x1, 2x2 or 3x3 tensor).", ++ cimg_instance); ++ } ++ return res; ++ } ++ ++ //! Resize image to become a diagonal matrix. ++ /** ++ \note Transform the image as a diagonal matrix so that each of its initial value becomes a diagonal coefficient. ++ **/ ++ CImg& diagonal() { ++ return get_diagonal().move_to(*this); ++ } ++ ++ //! Resize image to become a diagonal matrix \newinstance. ++ CImg get_diagonal() const { ++ if (is_empty()) return *this; ++ const unsigned int siz = (unsigned int)size(); ++ CImg res(siz,siz,1,1,0); ++ cimg_foroff(*this,off) res((unsigned int)off,(unsigned int)off) = (*this)[off]; ++ return res; ++ } ++ ++ //! Replace the image by an identity matrix. ++ /** ++ \note If the instance image is not square, it is resized to a square matrix using its maximum ++ dimension as a reference. ++ **/ ++ CImg& identity_matrix() { ++ return identity_matrix(std::max(_width,_height)).move_to(*this); ++ } ++ ++ //! Replace the image by an identity matrix \newinstance. ++ CImg get_identity_matrix() const { ++ return identity_matrix(std::max(_width,_height)); ++ } ++ ++ //! Fill image with a linear sequence of values. ++ /** ++ \param a0 Starting value of the sequence. ++ \param a1 Ending value of the sequence. ++ **/ ++ CImg& sequence(const T& a0, const T& a1) { ++ if (is_empty()) return *this; ++ const ulongT siz = size() - 1; ++ T* ptr = _data; ++ if (siz) { ++ const double delta = (double)a1 - (double)a0; ++ cimg_foroff(*this,l) *(ptr++) = (T)(a0 + delta*l/siz); ++ } else *ptr = a0; ++ return *this; ++ } ++ ++ //! Fill image with a linear sequence of values \newinstance. ++ CImg get_sequence(const T& a0, const T& a1) const { ++ return (+*this).sequence(a0,a1); ++ } ++ ++ //! Transpose the image, viewed as a matrix. ++ /** ++ \note Equivalent to \code permute_axes("yxzc"); \endcode ++ **/ ++ CImg& transpose() { ++ if (_width==1) { _width = _height; _height = 1; return *this; } ++ if (_height==1) { _height = _width; _width = 1; return *this; } ++ if (_width==_height) { ++ cimg_forYZC(*this,y,z,c) for (int x = y; x get_transpose() const { ++ return get_permute_axes("yxzc"); ++ } ++ ++ //! Compute the cross product between two \c 1x3 images, viewed as 3d vectors. ++ /** ++ \param img Image used as the second argument of the cross product. ++ \note The first argument of the cross product is \c *this. ++ **/ ++ template ++ CImg& cross(const CImg& img) { ++ if (_width!=1 || _height<3 || img._width!=1 || img._height<3) ++ throw CImgInstanceException(_cimg_instance ++ "cross(): Instance and/or specified image (%u,%u,%u,%u,%p) are not 3d vectors.", ++ cimg_instance, ++ img._width,img._height,img._depth,img._spectrum,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; ++ } ++ ++ //! Compute the cross product between two \c 1x3 images, viewed as 3d vectors \newinstance. ++ template ++ CImg<_cimg_Tt> get_cross(const CImg& img) const { ++ return CImg<_cimg_Tt>(*this).cross(img); ++ } ++ ++ //! Invert the instance image, viewed as a matrix. ++ /** ++ \param use_LU Choose the inverting algorithm. Can be: ++ - \c true: LU-based matrix inversion. ++ - \c false: SVD-based matrix inversion. ++ **/ ++ CImg& invert(const bool use_LU=true) { ++ if (_width!=_height || _depth!=1 || _spectrum!=1) ++ throw CImgInstanceException(_cimg_instance ++ "invert(): Instance is not a square matrix.", ++ cimg_instance); ++#ifdef cimg_use_lapack ++ int INFO = (int)use_LU, N = _width, LWORK = 4*N, *const IPIV = new int[N]; ++ Tfloat ++ *const lapA = new Tfloat[N*N], ++ *const 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_instance ++ "invert(): LAPACK function dgetrf_() returned error code %d.", ++ cimg_instance, ++ INFO); ++ else { ++ cimg::getri(N,lapA,IPIV,WORK,LWORK,INFO); ++ if (INFO) ++ cimg::warn(_cimg_instance ++ "invert(): LAPACK function dgetri_() returned error code %d.", ++ cimg_instance, ++ 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,false), 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; ++ } ++ ++ //! Invert the instance image, viewed as a matrix \newinstance. ++ CImg get_invert(const bool use_LU=true) const { ++ return CImg(*this,false).invert(use_LU); ++ } ++ ++ //! Compute the Moore-Penrose pseudo-inverse of the instance image, viewed as a matrix. ++ /** ++ **/ ++ CImg& pseudoinvert() { ++ return get_pseudoinvert().move_to(*this); ++ } ++ ++ //! Compute the Moore-Penrose pseudo-inverse of the instance image, viewed as a matrix \newinstance. ++ CImg get_pseudoinvert() const { ++ CImg U, S, V; ++ SVD(U,S,V); ++ const Tfloat tolerance = (sizeof(Tfloat)<=4?5.96e-8f:1.11e-16f)*std::max(_width,_height)*S.max(); ++ cimg_forX(V,x) { ++ const Tfloat s = S(x), invs = s>tolerance?1/s:0; ++ cimg_forY(V,y) V(x,y)*=invs; ++ } ++ return V*U.transpose(); ++ } ++ ++ //! Solve a system of linear equations. ++ /** ++ \param A Matrix of the linear system. ++ \note Solve \c AX=B where \c B=*this. ++ **/ ++ template ++ CImg& solve(const CImg& A) { ++ if (_depth!=1 || _spectrum!=1 || _height!=A._height || A._depth!=1 || A._spectrum!=1) ++ throw CImgArgumentException(_cimg_instance ++ "solve(): Instance and specified matrix (%u,%u,%u,%u,%p) have " ++ "incompatible dimensions.", ++ cimg_instance, ++ A._width,A._height,A._depth,A._spectrum,A._data); ++ typedef _cimg_Ttfloat Ttfloat; ++ if (A._width==A._height) { // Classical linear system ++ if (_width!=1) { ++ CImg res(_width,A._width); ++ cimg_forX(*this,i) res.draw_image(i,get_column(i).solve(A)); ++ return res.move_to(*this); ++ } ++#ifdef cimg_use_lapack ++ char TRANS = 'N'; ++ int INFO, N = _height, LWORK = 4*N, *const IPIV = new int[N]; ++ Ttfloat ++ *const lapA = new Ttfloat[N*N], ++ *const lapB = new Ttfloat[N], ++ *const 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_instance ++ "solve(): LAPACK library function dgetrf_() returned error code %d.", ++ cimg_instance, ++ INFO); ++ ++ if (!INFO) { ++ cimg::getrs(TRANS,N,lapA,IPIV,lapB,INFO); ++ if (INFO) ++ cimg::warn(_cimg_instance ++ "solve(): LAPACK library function dgetrs_() returned error code %d.", ++ cimg_instance, ++ 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,false); ++ CImg indx; ++ bool d; ++ lu._LU(indx,d); ++ _solve(lu,indx); ++#endif ++ } else { // Least-square solution for non-square systems. ++#ifdef cimg_use_lapack ++ if (_width!=1) { ++ CImg res(_width,A._width); ++ cimg_forX(*this,i) res.draw_image(i,get_column(i).solve(A)); ++ return res.move_to(*this); ++ } ++ char TRANS = 'N'; ++ int INFO, N = A._width, M = A._height, LWORK = -1, LDA = M, LDB = M, NRHS = _width; ++ Ttfloat WORK_QUERY; ++ Ttfloat ++ * const lapA = new Ttfloat[M*N], ++ * const lapB = new Ttfloat[M*NRHS]; ++ cimg::sgels(TRANS, M, N, NRHS, lapA, LDA, lapB, LDB, &WORK_QUERY, LWORK, INFO); ++ LWORK = (int) WORK_QUERY; ++ Ttfloat *const WORK = new Ttfloat[LWORK]; ++ cimg_forXY(A,k,l) lapA[k*M + l] = (Ttfloat)(A(k,l)); ++ cimg_forXY(*this,k,l) lapB[k*M + l] = (Ttfloat)((*this)(k,l)); ++ cimg::sgels(TRANS, M, N, NRHS, lapA, LDA, lapB, LDB, WORK, LWORK, INFO); ++ if (INFO != 0) ++ cimg::warn(_cimg_instance ++ "solve(): LAPACK library function sgels() returned error code %d.", ++ cimg_instance, ++ INFO); ++ assign(NRHS, N); ++ if (!INFO) ++ cimg_forXY(*this,k,l) (*this)(k,l) = (T)lapB[k*M + l]; ++ else ++ assign(A.get_pseudoinvert()*(*this)); ++ delete[] lapA; delete[] lapB; delete[] WORK; ++#else ++ assign(A.get_pseudoinvert()*(*this)); ++#endif ++ } ++ return *this; ++ } ++ ++ //! Solve a system of linear equations \newinstance. ++ template ++ CImg<_cimg_Ttfloat> get_solve(const CImg& A) const { ++ return CImg<_cimg_Ttfloat>(*this,false).solve(A); ++ } ++ ++ template ++ CImg& _solve(const CImg& A, const CImg& indx) { ++ typedef _cimg_Ttfloat Ttfloat; ++ const int N = (int)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 unsigned int siz = (unsigned int)size(); ++ if (A._width!=3 || A._height!=siz) ++ throw CImgArgumentException(_cimg_instance ++ "solve_tridiagonal(): Instance and tridiagonal matrix " ++ "(%u,%u,%u,%u,%p) have incompatible dimensions.", ++ cimg_instance, ++ A._width,A._height,A._depth,A._spectrum,A._data); ++ typedef _cimg_Ttfloat Ttfloat; ++ const Ttfloat epsilon = 1e-4f; ++ CImg B = A.get_column(1), V(*this,false); ++ for (int i = 1; i<(int)siz; ++i) { ++ const Ttfloat m = A(0,i)/(B[i - 1]?B[i - 1]:epsilon); ++ B[i] -= m*A(2,i - 1); ++ V[i] -= m*V[i - 1]; ++ } ++ (*this)[siz - 1] = (T)(V[siz - 1]/(B[siz - 1]?B[siz - 1]:epsilon)); ++ for (int i = (int)siz - 2; i>=0; --i) (*this)[i] = (T)((V[i] - A(2,i)*(*this)[i + 1])/(B[i]?B[i]:epsilon)); ++ return *this; ++ } ++ ++ //! Solve a tridiagonal system of linear equations \newinstance. ++ template ++ CImg<_cimg_Ttfloat> get_solve_tridiagonal(const CImg& A) const { ++ return CImg<_cimg_Ttfloat>(*this,false).solve_tridiagonal(A); ++ } ++ ++ //! Compute eigenvalues and eigenvectors of the instance image, viewed as a matrix. ++ /** ++ \param[out] val Vector of the estimated eigenvalues, in decreasing order. ++ \param[out] vec Matrix of the estimated eigenvectors, sorted by columns. ++ **/ ++ template ++ const CImg& eigen(CImg& val, CImg &vec) const { ++ if (is_empty()) { val.assign(); vec.assign(); } ++ else { ++ if (_width!=_height || _depth>1 || _spectrum>1) ++ throw CImgInstanceException(_cimg_instance ++ "eigen(): Instance is not a square matrix.", ++ cimg_instance); ++ ++ if (val.size()<(ulongT)_width) val.assign(1,_width); ++ if (vec.size()<(ulongT)_width*_width) vec.assign(_width,_width); ++ switch (_width) { ++ case 1 : { val[0] = (t)(*this)[0]; vec[0] = (t)1; } break; ++ case 2 : { ++ const double a = (*this)[0], b = (*this)[1], c = (*this)[2], d = (*this)[3], e = a + d; ++ double f = e*e - 4*(a*d - b*c); ++ if (f<0) ++ cimg::warn(_cimg_instance ++ "eigen(): Complex eigenvalues found.", ++ cimg_instance); ++ ++ f = std::sqrt(f); ++ const double ++ l1 = 0.5*(e - f), ++ l2 = 0.5*(e + f), ++ b2 = b*b, ++ norm1 = std::sqrt(cimg::sqr(l2 - a) + b2), ++ norm2 = std::sqrt(cimg::sqr(l1 - a) + b2); ++ val[0] = (t)l2; ++ val[1] = (t)l1; ++ if (norm1>0) { vec(0,0) = (t)(b/norm1); vec(0,1) = (t)((l2 - a)/norm1); } else { vec(0,0) = 1; vec(0,1) = 0; } ++ if (norm2>0) { vec(1,0) = (t)(b/norm2); vec(1,1) = (t)((l1 - a)/norm2); } else { vec(1,0) = 1; vec(1,1) = 0; } ++ } break; ++ default : ++ throw CImgInstanceException(_cimg_instance ++ "eigen(): Eigenvalues computation of general matrices is limited " ++ "to 2x2 matrices.", ++ cimg_instance); ++ } ++ } ++ return *this; ++ } ++ ++ //! Compute eigenvalues and eigenvectors of the instance image, viewed as a matrix. ++ /** ++ \return A list of two images [val; vec], whose meaning is similar as in eigen(CImg&,CImg&) const. ++ **/ ++ CImgList get_eigen() const { ++ CImgList res(2); ++ eigen(res[0],res[1]); ++ return res; ++ } ++ ++ //! Compute eigenvalues and eigenvectors of the instance image, viewed as a symmetric matrix. ++ /** ++ \param[out] val Vector of the estimated eigenvalues, in decreasing order. ++ \param[out] vec Matrix of the estimated eigenvectors, sorted by columns. ++ **/ ++ 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 ++ *const lapA = new Tfloat[N*N], ++ *const lapW = new Tfloat[N], ++ *const 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_instance ++ "symmetric_eigen(): LAPACK library function dsyev_() returned error code %d.", ++ cimg_instance, ++ 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 || _spectrum>1) ++ throw CImgInstanceException(_cimg_instance ++ "eigen(): Instance is not a square matrix.", ++ cimg_instance); ++ ++ val.assign(1,_width); ++ if (vec._data) vec.assign(_width,_width); ++ if (_width<3) { ++ eigen(val,vec); ++ if (_width==2) { vec[1] = -vec[2]; vec[3] = vec[0]; } // Force orthogonality for 2x2 matrices. ++ return *this; ++ } ++ CImg V(_width,_width); ++ Tfloat M = 0, m = (Tfloat)min_max(M), maxabs = cimg::max((Tfloat)1,cimg::abs(m),cimg::abs(M)); ++ (CImg(*this,false)/=maxabs).SVD(vec,val,V,false); ++ if (maxabs!=1) val*=maxabs; ++ ++ bool is_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) is_ambiguous = true; ++ if (scal<0) val[p] = -val[p]; ++ } ++ if (is_ambiguous) { ++ ++(eig*=2); ++ SVD(vec,val,V,false,40,eig); ++ val-=eig; ++ } ++ CImg permutations; // sort eigenvalues in decreasing order ++ CImg tmp(_width); ++ val.sort(permutations,false); ++ cimg_forY(vec,k) { ++ cimg_forY(permutations,y) tmp(y) = vec(permutations(y),k); ++ std::memcpy(vec.data(0,k),tmp._data,sizeof(t)*_width); ++ } ++#endif ++ } ++ return *this; ++ } ++ ++ //! Compute eigenvalues and eigenvectors of the instance image, viewed as a symmetric matrix. ++ /** ++ \return A list of two images [val; vec], whose meaning are similar as in ++ symmetric_eigen(CImg&,CImg&) const. ++ **/ ++ CImgList get_symmetric_eigen() const { ++ CImgList res(2); ++ symmetric_eigen(res[0],res[1]); ++ return res; ++ } ++ ++ //! Sort pixel values and get sorting permutations. ++ /** ++ \param[out] permutations Permutation map used for the sorting. ++ \param is_increasing Tells if pixel values are sorted in an increasing (\c true) or decreasing (\c false) way. ++ **/ ++ template ++ CImg& sort(CImg& permutations, const bool is_increasing=true) { ++ permutations.assign(_width,_height,_depth,_spectrum); ++ if (is_empty()) return *this; ++ cimg_foroff(permutations,off) permutations[off] = (t)off; ++ return _quicksort(0,size() - 1,permutations,is_increasing,true); ++ } ++ ++ //! Sort pixel values and get sorting permutations \newinstance. ++ template ++ CImg get_sort(CImg& permutations, const bool is_increasing=true) const { ++ return (+*this).sort(permutations,is_increasing); ++ } ++ ++ //! Sort pixel values. ++ /** ++ \param is_increasing Tells if pixel values are sorted in an increasing (\c true) or decreasing (\c false) way. ++ \param axis Tells if the value sorting must be done along a specific axis. Can be: ++ - \c 0: All pixel values are sorted, independently on their initial position. ++ - \c 'x': Image columns are sorted, according to the first value in each column. ++ - \c 'y': Image rows are sorted, according to the first value in each row. ++ - \c 'z': Image slices are sorted, according to the first value in each slice. ++ - \c 'c': Image channels are sorted, according to the first value in each channel. ++ **/ ++ CImg& sort(const bool is_increasing=true, const char axis=0) { ++ if (is_empty()) return *this; ++ CImg perm; ++ switch (cimg::lowercase(axis)) { ++ case 0 : ++ _quicksort(0,size() - 1,perm,is_increasing,false); ++ break; ++ case 'x' : { ++ perm.assign(_width); ++ get_crop(0,0,0,0,_width - 1,0,0,0).sort(perm,is_increasing); ++ CImg img(*this,false); ++ cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(perm[x],y,z,c); ++ } break; ++ case 'y' : { ++ perm.assign(_height); ++ get_crop(0,0,0,0,0,_height - 1,0,0).sort(perm,is_increasing); ++ CImg img(*this,false); ++ cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(x,perm[y],z,c); ++ } break; ++ case 'z' : { ++ perm.assign(_depth); ++ get_crop(0,0,0,0,0,0,_depth - 1,0).sort(perm,is_increasing); ++ CImg img(*this,false); ++ cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(x,y,perm[z],c); ++ } break; ++ case 'c' : { ++ perm.assign(_spectrum); ++ get_crop(0,0,0,0,0,0,0,_spectrum - 1).sort(perm,is_increasing); ++ CImg img(*this,false); ++ cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(x,y,z,perm[c]); ++ } break; ++ default : ++ throw CImgArgumentException(_cimg_instance ++ "sort(): Invalid specified axis '%c' " ++ "(should be { x | y | z | c }).", ++ cimg_instance,axis); ++ } ++ return *this; ++ } ++ ++ //! Sort pixel values \newinstance. ++ CImg get_sort(const bool is_increasing=true, const char axis=0) const { ++ return (+*this).sort(is_increasing,axis); ++ } ++ ++ template ++ CImg& _quicksort(const long indm, const long indM, CImg& permutations, ++ const bool is_increasing, const bool is_permutations) { ++ if (indm(*this)[mid]) { ++ cimg::swap((*this)[indm],(*this)[mid]); ++ if (is_permutations) cimg::swap(permutations[indm],permutations[mid]); ++ } ++ if ((*this)[mid]>(*this)[indM]) { ++ cimg::swap((*this)[indM],(*this)[mid]); ++ if (is_permutations) cimg::swap(permutations[indM],permutations[mid]); ++ } ++ if ((*this)[indm]>(*this)[mid]) { ++ cimg::swap((*this)[indm],(*this)[mid]); ++ if (is_permutations) cimg::swap(permutations[indm],permutations[mid]); ++ } ++ } else { ++ if ((*this)[indm]<(*this)[mid]) { ++ cimg::swap((*this)[indm],(*this)[mid]); ++ if (is_permutations) cimg::swap(permutations[indm],permutations[mid]); ++ } ++ if ((*this)[mid]<(*this)[indM]) { ++ cimg::swap((*this)[indM],(*this)[mid]); ++ if (is_permutations) cimg::swap(permutations[indM],permutations[mid]); ++ } ++ if ((*this)[indm]<(*this)[mid]) { ++ cimg::swap((*this)[indm],(*this)[mid]); ++ if (is_permutations) cimg::swap(permutations[indm],permutations[mid]); ++ } ++ } ++ if (indM - indm>=3) { ++ const T pivot = (*this)[mid]; ++ long i = indm, j = indM; ++ if (is_increasing) { ++ do { ++ while ((*this)[i]pivot) --j; ++ if (i<=j) { ++ if (is_permutations) cimg::swap(permutations[i],permutations[j]); ++ cimg::swap((*this)[i++],(*this)[j--]); ++ } ++ } while (i<=j); ++ } else { ++ do { ++ while ((*this)[i]>pivot) ++i; ++ while ((*this)[j] A; // Input matrix (assumed to contain some values). ++ CImg<> U,S,V; ++ A.SVD(U,S,V) ++ \endcode ++ **/ ++ template ++ const CImg& SVD(CImg& U, CImg& S, CImg& V, const bool sorting=true, ++ const unsigned int max_iteration=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 = std::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)*std::sqrt(s)); h=f*g-s; U(i,i) = f-g; ++ for (int j = l; j=0?-1:1)*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 = cimg::_hypot(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))/std::max((t)1e-25,2*h*y); ++ g = cimg::_hypot(f,(t)1); ++ f = ((x - z)*(x + z)+h*((y/(f + (f>=0?g:-g))) - h))/std::max((t)1e-25,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 = cimg::_hypot(f,h); ++ rv1[j] = z; c = f/std::max((t)1e-25,z); s = h/std::max((t)1e-25,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 = cimg::_hypot(f,h); S[j] = z; ++ if (z) { z = 1/std::max((t)1e-25,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; ++ CImg tmp(_width); ++ S.sort(permutations,false); ++ cimg_forY(U,k) { ++ cimg_forY(permutations,y) tmp(y) = U(permutations(y),k); ++ std::memcpy(U.data(0,k),tmp._data,sizeof(t)*_width); ++ } ++ cimg_forY(V,k) { ++ cimg_forY(permutations,y) tmp(y) = V(permutations(y),k); ++ std::memcpy(V.data(0,k),tmp._data,sizeof(t)*_width); ++ } ++ } ++ } ++ return *this; ++ } ++ ++ //! Compute the SVD of the instance image, viewed as a general matrix. ++ /** ++ \return A list of three images [U; S; V], whose meaning is similar as in ++ SVD(CImg&,CImg&,CImg&,bool,unsigned int,float) const. ++ **/ ++ CImgList get_SVD(const bool sorting=true, ++ const unsigned int max_iteration=40, const float lambda=0) const { ++ CImgList res(3); ++ SVD(res[0],res[1],res[2],sorting,max_iteration,lambda); ++ return res; ++ } ++ ++ // [internal] Compute the LU decomposition of a permuted matrix. ++ template ++ CImg& _LU(CImg& indx, bool& d) { ++ const int N = width(); ++ 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 ++ static CImg dijkstra(const tf& distance, const unsigned int nb_nodes, ++ const unsigned int starting_node, const unsigned int ending_node, ++ CImg& previous_node) { ++ if (starting_node>=nb_nodes) ++ throw CImgArgumentException("CImg<%s>::dijkstra(): Specified indice of starting node %u is higher " ++ "than number of nodes %u.", ++ pixel_type(),starting_node,nb_nodes); ++ CImg dist(1,nb_nodes,1,1,cimg::type::max()); ++ dist(starting_node) = 0; ++ previous_node.assign(1,nb_nodes,1,1,(t)-1); ++ previous_node(starting_node) = (t)starting_node; ++ CImg Q(nb_nodes); ++ cimg_forX(Q,u) Q(u) = (unsigned int)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. ++ /** ++ \param starting_node Indice of the starting node. ++ \param ending_node Indice of the ending node. ++ \param previous_node 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. ++ \note image instance corresponds to the adjacency matrix of the graph. ++ **/ ++ template ++ CImg& dijkstra(const unsigned int starting_node, const unsigned int ending_node, ++ CImg& previous_node) { ++ return get_dijkstra(starting_node,ending_node,previous_node).move_to(*this); ++ } ++ ++ //! Return minimal path in a graph, using the Dijkstra algorithm \newinstance. ++ template ++ CImg get_dijkstra(const unsigned int starting_node, const unsigned int ending_node, ++ CImg& previous_node) const { ++ if (_width!=_height || _depth!=1 || _spectrum!=1) ++ throw CImgInstanceException(_cimg_instance ++ "dijkstra(): Instance is not a graph adjacency matrix.", ++ cimg_instance); ++ ++ return dijkstra(*this,_width,starting_node,ending_node,previous_node); ++ } ++ ++ //! 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).move_to(*this); ++ } ++ ++ //! Return minimal path in a graph, using the Dijkstra algorithm \newinstance. ++ 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); ++ } ++ ++ //! Return an image containing the ascii codes of the specified string. ++ /** ++ \param str input C-string to encode as an image. ++ \param is_last_zero Tells if the ending \c '0' character appear in the resulting image. ++ \param is_shared Return result that shares its buffer with \p str. ++ **/ ++ static CImg string(const char *const str, const bool is_last_zero=true, const bool is_shared=false) { ++ if (!str) return CImg(); ++ return CImg(str,(unsigned int)std::strlen(str) + (is_last_zero?1:0),1,1,1,is_shared); ++ } ++ ++ //! Return a \c 1x1 image containing specified value. ++ /** ++ \param a0 First vector value. ++ **/ ++ static CImg vector(const T& a0) { ++ CImg r(1,1); ++ r[0] = a0; ++ return r; ++ } ++ ++ //! Return a \c 1x2 image containing specified values. ++ /** ++ \param a0 First vector value. ++ \param a1 Second vector value. ++ **/ ++ static CImg vector(const T& a0, const T& a1) { ++ CImg r(1,2); T *ptr = r._data; ++ *(ptr++) = a0; *(ptr++) = a1; ++ return r; ++ } ++ ++ //! Return a \c 1x3 image containing specified values. ++ /** ++ \param a0 First vector value. ++ \param a1 Second vector value. ++ \param a2 Third vector value. ++ **/ ++ static CImg vector(const T& a0, const T& a1, const T& a2) { ++ CImg r(1,3); T *ptr = r._data; ++ *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; ++ return r; ++ } ++ ++ //! Return a \c 1x4 image containing specified values. ++ /** ++ \param a0 First vector value. ++ \param a1 Second vector value. ++ \param a2 Third vector value. ++ \param a3 Fourth vector value. ++ **/ ++ static CImg vector(const T& a0, const T& a1, const T& a2, const T& a3) { ++ CImg r(1,4); T *ptr = r._data; ++ *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; ++ return r; ++ } ++ ++ //! Return a \c 1x5 image containing specified values. ++ static CImg vector(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4) { ++ CImg r(1,5); T *ptr = r._data; ++ *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; ++ return r; ++ } ++ ++ //! Return a \c 1x6 image containing specified values. ++ static CImg vector(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4, const T& a5) { ++ CImg r(1,6); T *ptr = r._data; ++ *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5; ++ return r; ++ } ++ ++ //! Return a \c 1x7 image containing specified values. ++ static CImg vector(const T& a0, const T& a1, const T& a2, const T& a3, ++ const T& a4, const T& a5, const T& a6) { ++ 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 \c 1x8 image containing specified values. ++ 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) { ++ 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 \c 1x9 image containing specified values. ++ 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) { ++ 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 \c 1x10 image containing specified values. ++ 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) { ++ 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 \c 1x11 image containing specified values. ++ 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) { ++ 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 \c 1x12 image containing specified values. ++ 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) { ++ 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 \c 1x13 image containing specified values. ++ 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) { ++ 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 \c 1x14 image containing specified values. ++ 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) { ++ 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 \c 1x15 image containing specified values. ++ 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) { ++ 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 \c 1x16 image containing specified values. ++ 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) { ++ 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 matrix containing specified coefficients. ++ /** ++ \param a0 First matrix value. ++ \note Equivalent to vector(const T&). ++ **/ ++ static CImg matrix(const T& a0) { ++ return vector(a0); ++ } ++ ++ //! Return a 2x2 matrix containing specified coefficients. ++ /** ++ \param a0 First matrix value. ++ \param a1 Second matrix value. ++ \param a2 Third matrix value. ++ \param a3 Fourth matrix value. ++ **/ ++ static CImg matrix(const T& a0, const T& a1, ++ const T& a2, const T& a3) { ++ CImg r(2,2); T *ptr = r._data; ++ *(ptr++) = a0; *(ptr++) = a1; ++ *(ptr++) = a2; *(ptr++) = a3; ++ return r; ++ } ++ ++ //! Return a 3x3 matrix containing specified coefficients. ++ /** ++ \param a0 First matrix value. ++ \param a1 Second matrix value. ++ \param a2 Third matrix value. ++ \param a3 Fourth matrix value. ++ \param a4 Fifth matrix value. ++ \param a5 Sixth matrix value. ++ \param a6 Seventh matrix value. ++ \param a7 Eighth matrix value. ++ \param a8 Nineth matrix value. ++ **/ ++ 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) { ++ 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 matrix containing 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) { ++ 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 matrix containing 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) { ++ 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 containing specified coefficients. ++ /** ++ \param a0 First matrix value. ++ \note Equivalent to vector(const T&). ++ **/ ++ static CImg tensor(const T& a0) { ++ return matrix(a0); ++ } ++ ++ //! Return a 2x2 symmetric matrix tensor containing specified coefficients. ++ static CImg tensor(const T& a0, const T& a1, const T& a2) { ++ return matrix(a0,a1,a1,a2); ++ } ++ ++ //! Return a 3x3 symmetric matrix containing specified coefficients. ++ static CImg tensor(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4, const T& a5) { ++ return matrix(a0,a1,a2,a1,a3,a4,a2,a4,a5); ++ } ++ ++ //! Return a 1x1 diagonal matrix containing specified coefficients. ++ static CImg diagonal(const T& a0) { ++ return matrix(a0); ++ } ++ ++ //! Return a 2x2 diagonal matrix containing specified coefficients. ++ static CImg diagonal(const T& a0, const T& a1) { ++ return matrix(a0,0,0,a1); ++ } ++ ++ //! Return a 3x3 diagonal matrix containing 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 containing 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 containing 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. ++ /** ++ \param N Dimension of the 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. ++ /** ++ \param N Size of the resulting vector. ++ \param a0 Starting value of the sequence. ++ \param a1 Ending value of the sequence. ++ **/ ++ 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 from an { axis + angle } or a quaternion. ++ /** ++ \param x X-coordinate of the rotation axis, or first quaternion coordinate. ++ \param y Y-coordinate of the rotation axis, or second quaternion coordinate. ++ \param z Z-coordinate of the rotation axis, or third quaternion coordinate. ++ \param w Angle of the rotation axis (in degree), or fourth quaternion coordinate. ++ \param is_quaternion Tell is the four arguments denotes a set { axis + angle } or a quaternion (x,y,z,w). ++ **/ ++ static CImg rotation_matrix(const float x, const float y, const float z, const float w, ++ const bool is_quaternion=false) { ++ double X, Y, Z, W, N; ++ if (is_quaternion) { ++ N = std::sqrt((double)x*x + (double)y*y + (double)z*z + (double)w*w); ++ if (N>0) { X = x/N; Y = y/N; Z = z/N; W = w/N; } ++ else { X = Y = Z = 0; W = 1; } ++ return CImg::matrix((T)(X*X + Y*Y - Z*Z - W*W),(T)(2*Y*Z - 2*X*W),(T)(2*X*Z + 2*Y*W), ++ (T)(2*X*W + 2*Y*Z),(T)(X*X - Y*Y + Z*Z - W*W),(T)(2*Z*W - 2*X*Y), ++ (T)(2*Y*W - 2*X*Z),(T)(2*X*Y + 2*Z*W),(T)(X*X - Y*Y - Z*Z + W*W)); ++ } ++ N = cimg::hypot((double)x,(double)y,(double)z); ++ if (N>0) { X = x/N; Y = y/N; Z = z/N; } ++ else { X = Y = 0; Z = 1; } ++ const double ang = w*cimg::PI/180, c = std::cos(ang), omc = 1 - c, s = std::sin(ang); ++ return CImg::matrix((T)(X*X*omc + c),(T)(X*Y*omc - Z*s),(T)(X*Z*omc + Y*s), ++ (T)(X*Y*omc + Z*s),(T)(Y*Y*omc + c),(T)(Y*Z*omc - X*s), ++ (T)(X*Z*omc - Y*s),(T)(Y*Z*omc + X*s),(T)(Z*Z*omc + c)); ++ } ++ ++ //@} ++ //----------------------------------- ++ // ++ //! \name Value Manipulation ++ //@{ ++ //----------------------------------- ++ ++ //! Fill all pixel values with specified value. ++ /** ++ \param val Fill value. ++ **/ ++ CImg& fill(const T& val) { ++ if (is_empty()) return *this; ++ if (val && sizeof(T)!=1) cimg_for(*this,ptrd,T) *ptrd = val; ++ else std::memset(_data,(int)(ulongT)val,sizeof(T)*size()); // Double cast to allow val to be (void*) ++ return *this; ++ } ++ ++ //! Fill all pixel values with specified value \newinstance. ++ CImg get_fill(const T& val) const { ++ return CImg(_width,_height,_depth,_spectrum).fill(val); ++ } ++ ++ //! Fill sequentially all pixel values with specified values. ++ /** ++ \param val0 First fill value. ++ \param val1 Second fill value. ++ **/ ++ CImg& fill(const T& val0, const T& val1) { ++ if (is_empty()) return *this; ++ T *ptrd, *ptre = end() - 1; ++ for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1) const { ++ return CImg(_width,_height,_depth,_spectrum).fill(val0,val1); ++ } ++ ++ //! Fill sequentially all pixel values with specified values \overloading. ++ CImg& fill(const T& val0, const T& val1, const T& val2) { ++ if (is_empty()) return *this; ++ T *ptrd, *ptre = end() - 2; ++ for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2) const { ++ return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2); ++ } ++ ++ //! Fill sequentially all pixel values with specified values \overloading. ++ CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3) { ++ if (is_empty()) return *this; ++ T *ptrd, *ptre = end() - 3; ++ for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3) const { ++ return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3); ++ } ++ ++ //! Fill sequentially all pixel values with specified values \overloading. ++ CImg& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4) { ++ if (is_empty()) return *this; ++ T *ptrd, *ptre = end() - 4; ++ for (ptrd = _data; ptrd get_fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4) const { ++ return CImg(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4); ++ } ++ ++ //! Fill sequentially all pixel values with specified values \overloading. ++ 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 *ptrd, *ptre = end() - 5; ++ for (ptrd = _data; ptrd 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,_spectrum).fill(val0,val1,val2,val3,val4,val5); ++ } ++ ++ //! Fill sequentially all pixel values with specified values \overloading. ++ 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 *ptrd, *ptre = end() - 6; ++ for (ptrd = _data; ptrd 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,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6); ++ } ++ ++ //! Fill sequentially all pixel values with specified values \overloading. ++ 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 *ptrd, *ptre = end() - 7; ++ for (ptrd = _data; ptrd 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,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7); ++ } ++ ++ //! Fill sequentially all pixel values with specified values \overloading. ++ 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 *ptrd, *ptre = end() - 8; ++ for (ptrd = _data; ptrd 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,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8); ++ } ++ ++ //! Fill sequentially all pixel values with specified values \overloading. ++ 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 *ptrd, *ptre = end() - 9; ++ for (ptrd = _data; ptrd 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,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9); ++ } ++ ++ //! Fill sequentially all pixel values with specified values \overloading. ++ 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 *ptrd, *ptre = end() - 10; ++ for (ptrd = _data; ptrd 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,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10); ++ } ++ ++ //! Fill sequentially all pixel values with specified values \overloading. ++ 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 *ptrd, *ptre = end() - 11; ++ for (ptrd = _data; ptrd 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,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10, ++ val11); ++ } ++ ++ //! Fill sequentially all pixel values with specified values \overloading. ++ 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 *ptrd, *ptre = end() - 12; ++ for (ptrd = _data; ptrd 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,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10, ++ val11,val12); ++ } ++ ++ //! Fill sequentially all pixel values with specified values \overloading. ++ 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 *ptrd, *ptre = end() - 13; ++ for (ptrd = _data; ptrd 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,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10, ++ val11,val12,val13); ++ } ++ ++ //! Fill sequentially all pixel values with specified values \overloading. ++ 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 *ptrd, *ptre = end() - 14; ++ for (ptrd = _data; ptrd 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,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10, ++ val11,val12,val13,val14); ++ } ++ ++ //! Fill sequentially all pixel values with specified values \overloading. ++ 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 *ptrd, *ptre = end() - 15; ++ for (ptrd = _data; ptrd 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,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10, ++ val11,val12,val13,val14,val15); ++ } ++ ++ //! Fill sequentially pixel values according to a given expression. ++ /** ++ \param expression C-string describing a math formula, or a sequence of values. ++ \param repeat_values In case a list of values is provided, tells if this list must be repeated for the filling. ++ \param allow_formula Tells that mathematical formulas are authorized for the filling. ++ \param list_inputs In case of a mathematical expression, attach a list of images to the specified expression. ++ \param[out] list_outputs In case of a math expression, list of images atatched to the specified expression. ++ **/ ++ CImg& fill(const char *const expression, const bool repeat_values, const bool allow_formula=true, ++ const CImgList *const list_inputs=0, CImgList *const list_outputs=0) { ++ return _fill(expression,repeat_values,allow_formula,list_inputs,list_outputs,"fill",0); ++ } ++ ++ CImg& _fill(const char *const expression, const bool repeat_values, bool allow_formula, ++ const CImgList *const list_inputs, CImgList *const list_outputs, ++ const char *const calling_function, const CImg *provides_copy) { ++ if (is_empty() || !expression || !*expression) return *this; ++ const unsigned int omode = cimg::exception_mode(); ++ cimg::exception_mode(0); ++ CImg is_error; ++ bool is_value_sequence = false; ++ cimg_abort_init; ++ ++ if (allow_formula) { ++ ++ // Try to pre-detect regular value sequence to avoid exception thrown by _cimg_math_parser. ++ double value; ++ char sep; ++ const int err = cimg_sscanf(expression,"%lf %c",&value,&sep); ++ if (err==1 || (err==2 && sep==',')) { ++ if (err==1) return fill((T)value); ++ else is_value_sequence = true; ++ } ++ ++ // Try to fill values according to a formula. ++ _cimg_abort_init_omp; ++ if (!is_value_sequence) try { ++ CImg base = provides_copy?provides_copy->get_shared():get_shared(); ++ _cimg_math_parser mp(expression + (*expression=='>' || *expression=='<' || ++ *expression=='*' || *expression==':'), ++ calling_function,base,this,list_inputs,list_outputs,true); ++ if (!provides_copy && expression && *expression!='>' && *expression!='<' && *expression!=':' && ++ mp.need_input_copy) ++ base.assign().assign(*this,false); // Needs input copy ++ ++ bool do_in_parallel = false; ++#ifdef cimg_use_openmp ++ cimg_openmp_if(*expression=='*' || *expression==':' || ++ (mp.is_parallelizable && _width>=320 && _height*_depth*_spectrum>=2)) ++ do_in_parallel = true; ++#endif ++ if (mp.result_dim) { // Vector-valued expression ++ const unsigned int N = std::min(mp.result_dim,_spectrum); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ T *ptrd = *expression=='<'?_data + _width*_height*_depth - 1:_data; ++ if (*expression=='<') { ++ CImg res(1,mp.result_dim); ++ cimg_rofYZ(*this,y,z) { ++ cimg_abort_test; ++ cimg_rofX(*this,x) { ++ mp(x,y,z,0,res._data); ++ const double *ptrs = res._data; ++ T *_ptrd = ptrd--; for (unsigned int n = N; n>0; --n) { *_ptrd = (T)(*ptrs++); _ptrd+=whd; } ++ } ++ } ++ } else if (*expression=='>' || !do_in_parallel) { ++ CImg res(1,mp.result_dim); ++ cimg_forYZ(*this,y,z) { ++ cimg_abort_test; ++ cimg_forX(*this,x) { ++ mp(x,y,z,0,res._data); ++ const double *ptrs = res._data; ++ T *_ptrd = ptrd++; for (unsigned int n = N; n>0; --n) { *_ptrd = (T)(*ptrs++); _ptrd+=whd; } ++ } ++ } ++ } else { ++#ifdef cimg_use_openmp ++ cimg_pragma_openmp(parallel) ++ { ++ _cimg_math_parser ++ _mp = omp_get_thread_num()?mp:_cimg_math_parser(), ++ &lmp = omp_get_thread_num()?_mp:mp; ++ lmp.is_fill = true; ++ cimg_pragma_openmp(for collapse(2)) ++ cimg_forYZ(*this,y,z) _cimg_abort_try_omp { ++ cimg_abort_test; ++ CImg res(1,lmp.result_dim); ++ T *ptrd = data(0,y,z,0); ++ cimg_forX(*this,x) { ++ lmp(x,y,z,0,res._data); ++ const double *ptrs = res._data; ++ T *_ptrd = ptrd++; for (unsigned int n = N; n>0; --n) { *_ptrd = (T)(*ptrs++); _ptrd+=whd; } ++ } ++ } _cimg_abort_catch_omp _cimg_abort_catch_fill_omp ++ } ++#endif ++ } ++ ++ } else { // Scalar-valued expression ++ T *ptrd = *expression=='<'?end() - 1:_data; ++ if (*expression=='<') ++ cimg_rofYZC(*this,y,z,c) { cimg_abort_test; cimg_rofX(*this,x) *(ptrd--) = (T)mp(x,y,z,c); } ++ else if (*expression=='>' || !do_in_parallel) ++ cimg_forYZC(*this,y,z,c) { cimg_abort_test; cimg_forX(*this,x) *(ptrd++) = (T)mp(x,y,z,c); } ++ else { ++#ifdef cimg_use_openmp ++ cimg_pragma_openmp(parallel) ++ { ++ _cimg_math_parser ++ _mp = omp_get_thread_num()?mp:_cimg_math_parser(), ++ &lmp = omp_get_thread_num()?_mp:mp; ++ lmp.is_fill = true; ++ cimg_pragma_openmp(for collapse(3)) ++ cimg_forYZC(*this,y,z,c) _cimg_abort_try_omp { ++ cimg_abort_test; ++ T *ptrd = data(0,y,z,c); ++ cimg_forX(*this,x) *ptrd++ = (T)lmp(x,y,z,c); ++ } _cimg_abort_catch_omp _cimg_abort_catch_fill_omp ++ } ++#endif ++ } ++ } ++ mp.end(); ++ } catch (CImgException& e) { CImg::string(e._message).move_to(is_error); } ++ } ++ ++ // Try to fill values according to a value sequence. ++ if (!allow_formula || is_value_sequence || is_error) { ++ CImg item(256); ++ char sep = 0; ++ const char *nexpression = expression; ++ ulongT nb = 0; ++ const ulongT siz = size(); ++ T *ptrd = _data; ++ for (double val = 0; *nexpression && nb0 && cimg_sscanf(item,"%lf",&val)==1 && (sep==',' || sep==';' || err==1)) { ++ nexpression+=std::strlen(item) + (err>1); ++ *(ptrd++) = (T)val; ++ } else break; ++ } ++ cimg::exception_mode(omode); ++ if (nb get_fill(const char *const expression, const bool repeat_values, const bool allow_formula=true, ++ const CImgList *const list_inputs=0, CImgList *const list_outputs=0) const { ++ return (+*this).fill(expression,repeat_values,allow_formula,list_inputs,list_outputs); ++ } ++ ++ //! Fill sequentially pixel values according to the values found in another image. ++ /** ++ \param values Image containing the values used for the filling. ++ \param repeat_values In case there are less values than necessary in \c values, tells if these values must be ++ repeated for the filling. ++ **/ ++ template ++ CImg& fill(const CImg& values, const bool repeat_values=true) { ++ if (is_empty() || !values) return *this; ++ T *ptrd = _data, *ptre = ptrd + size(); ++ for (t *ptrs = values._data, *ptrs_end = ptrs + values.size(); ptrs ++ CImg get_fill(const CImg& values, const bool repeat_values=true) const { ++ return repeat_values?CImg(_width,_height,_depth,_spectrum).fill(values,repeat_values): ++ (+*this).fill(values,repeat_values); ++ } ++ ++ //! Fill pixel values along the X-axis at a specified pixel position. ++ /** ++ \param y Y-coordinate of the filled column. ++ \param z Z-coordinate of the filled column. ++ \param c C-coordinate of the filled column. ++ \param a0 First fill value. ++ **/ ++ CImg& fillX(const unsigned int y, const unsigned int z, const unsigned int c, const int a0, ...) { ++#define _cimg_fill1(x,y,z,c,off,siz,t) { \ ++ va_list ap; va_start(ap,a0); T *ptrd = data(x,y,z,c); *ptrd = (T)a0; \ ++ for (unsigned int k = 1; k& fillX(const unsigned int y, const unsigned int z, const unsigned int c, const double a0, ...) { ++ if (y<_height && z<_depth && c<_spectrum) _cimg_fill1(0,y,z,c,1,_width,double); ++ return *this; ++ } ++ ++ //! Fill pixel values along the Y-axis at a specified pixel position. ++ /** ++ \param x X-coordinate of the filled row. ++ \param z Z-coordinate of the filled row. ++ \param c C-coordinate of the filled row. ++ \param a0 First fill value. ++ **/ ++ CImg& fillY(const unsigned int x, const unsigned int z, const unsigned int c, const int a0, ...) { ++ if (x<_width && z<_depth && c<_spectrum) _cimg_fill1(x,0,z,c,_width,_height,int); ++ return *this; ++ } ++ ++ //! Fill pixel values along the Y-axis at a specified pixel position \overloading. ++ CImg& fillY(const unsigned int x, const unsigned int z, const unsigned int c, const double a0, ...) { ++ if (x<_width && z<_depth && c<_spectrum) _cimg_fill1(x,0,z,c,_width,_height,double); ++ return *this; ++ } ++ ++ //! Fill pixel values along the Z-axis at a specified pixel position. ++ /** ++ \param x X-coordinate of the filled slice. ++ \param y Y-coordinate of the filled slice. ++ \param c C-coordinate of the filled slice. ++ \param a0 First fill value. ++ **/ ++ CImg& fillZ(const unsigned int x, const unsigned int y, const unsigned int c, const int a0, ...) { ++ const ulongT wh = (ulongT)_width*_height; ++ if (x<_width && y<_height && c<_spectrum) _cimg_fill1(x,y,0,c,wh,_depth,int); ++ return *this; ++ } ++ ++ //! Fill pixel values along the Z-axis at a specified pixel position \overloading. ++ CImg& fillZ(const unsigned int x, const unsigned int y, const unsigned int c, const double a0, ...) { ++ const ulongT wh = (ulongT)_width*_height; ++ if (x<_width && y<_height && c<_spectrum) _cimg_fill1(x,y,0,c,wh,_depth,double); ++ return *this; ++ } ++ ++ //! Fill pixel values along the C-axis at a specified pixel position. ++ /** ++ \param x X-coordinate of the filled channel. ++ \param y Y-coordinate of the filled channel. ++ \param z Z-coordinate of the filled channel. ++ \param a0 First filling value. ++ **/ ++ CImg& fillC(const unsigned int x, const unsigned int y, const unsigned int z, const int a0, ...) { ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ if (x<_width && y<_height && z<_depth) _cimg_fill1(x,y,z,0,whd,_spectrum,int); ++ return *this; ++ } ++ ++ //! Fill pixel values along the C-axis at a specified pixel position \overloading. ++ CImg& fillC(const unsigned int x, const unsigned int y, const unsigned int z, const double a0, ...) { ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ if (x<_width && y<_height && z<_depth) _cimg_fill1(x,y,z,0,whd,_spectrum,double); ++ return *this; ++ } ++ ++ //! Discard specified sequence of values in the image buffer, along a specific axis. ++ /** ++ \param values Sequence of values to discard. ++ \param axis Axis along which the values are discarded. If set to \c 0 (default value) ++ the method does it for all the buffer values and returns a one-column vector. ++ \note Discarded values will change the image geometry, so the resulting image ++ is returned as a one-column vector. ++ **/ ++ template ++ CImg& discard(const CImg& values, const char axis=0) { ++ if (is_empty() || !values) return *this; ++ return get_discard(values,axis).move_to(*this); ++ } ++ ++ template ++ CImg get_discard(const CImg& values, const char axis=0) const { ++ CImg res; ++ if (!values) return +*this; ++ if (is_empty()) return res; ++ const ulongT vsiz = values.size(); ++ const char _axis = cimg::lowercase(axis); ++ ulongT j = 0; ++ unsigned int k = 0; ++ int i0 = 0; ++ res.assign(width(),height(),depth(),spectrum()); ++ switch (_axis) { ++ case 'x' : { ++ cimg_forX(*this,i) { ++ if ((*this)(i)!=(T)values[j]) { ++ if (j) --i; ++ res.draw_image(k,get_columns(i0,i)); ++ k+=i - i0 + 1; i0 = i + 1; j = 0; ++ } else { ++j; if (j>=vsiz) { j = 0; i0 = i + 1; } } ++ } ++ if (i0=vsiz) { j = 0; i0 = i + 1; } } ++ } ++ if (i0=vsiz) { j = 0; i0 = i + 1; } } ++ } ++ if (i0=vsiz) { j = 0; i0 = i + 1; } } ++ } ++ if (i0=vsiz) { j = 0; i0 = (int)i + 1; }} ++ } ++ const ulongT siz = size(); ++ if ((ulongT)i0& discard(const char axis=0) { ++ return get_discard(axis).move_to(*this); ++ } ++ ++ //! Discard neighboring duplicates in the image buffer, along the specified axis \newinstance. ++ CImg get_discard(const char axis=0) const { ++ CImg res; ++ if (is_empty()) return res; ++ const char _axis = cimg::lowercase(axis); ++ T current = *_data?(T)0:(T)1; ++ int j = 0; ++ res.assign(width(),height(),depth(),spectrum()); ++ switch (_axis) { ++ case 'x' : { ++ cimg_forX(*this,i) ++ if ((*this)(i)!=current) { res.draw_image(j++,get_column(i)); current = (*this)(i); } ++ res.resize(j,-100,-100,-100,0); ++ } break; ++ case 'y' : { ++ cimg_forY(*this,i) ++ if ((*this)(0,i)!=current) { res.draw_image(0,j++,get_row(i)); current = (*this)(0,i); } ++ res.resize(-100,j,-100,-100,0); ++ } break; ++ case 'z' : { ++ cimg_forZ(*this,i) ++ if ((*this)(0,0,i)!=current) { res.draw_image(0,0,j++,get_slice(i)); current = (*this)(0,0,i); } ++ res.resize(-100,-100,j,-100,0); ++ } break; ++ case 'c' : { ++ cimg_forC(*this,i) ++ if ((*this)(0,0,0,i)!=current) { res.draw_image(0,0,0,j++,get_channel(i)); current = (*this)(0,0,0,i); } ++ res.resize(-100,-100,-100,j,0); ++ } break; ++ default : { ++ res.unroll('y'); ++ cimg_foroff(*this,i) ++ if ((*this)[i]!=current) res[j++] = current = (*this)[i]; ++ res.resize(-100,j,-100,-100,0); ++ } ++ } ++ return res; ++ } ++ ++ //! Invert endianness of all pixel values. ++ /** ++ **/ ++ CImg& invert_endianness() { ++ cimg::invert_endianness(_data,size()); ++ return *this; ++ } ++ ++ //! Invert endianness of all pixel values \newinstance. ++ CImg get_invert_endianness() const { ++ return (+*this).invert_endianness(); ++ } ++ ++ //! Fill image with random values in specified range. ++ /** ++ \param val_min Minimal authorized random value. ++ \param val_max Maximal authorized random value. ++ \note Random variables are uniformely distributed in [val_min,val_max]. ++ **/ ++ CImg& rand(const T& val_min, const T& val_max) { ++ const float delta = (float)val_max - (float)val_min + (cimg::type::is_float()?0:1); ++ if (cimg::type::is_float()) cimg_for(*this,ptrd,T) *ptrd = (T)(val_min + cimg::rand()*delta); ++ else cimg_for(*this,ptrd,T) *ptrd = std::min(val_max,(T)(val_min + cimg::rand()*delta)); ++ return *this; ++ } ++ ++ //! Fill image with random values in specified range \newinstance. ++ CImg get_rand(const T& val_min, const T& val_max) const { ++ return (+*this).rand(val_min,val_max); ++ } ++ ++ //! Round pixel values. ++ /** ++ \param y Rounding precision. ++ \param rounding_type Rounding type. Can be: ++ - \c -1: Backward. ++ - \c 0: Nearest. ++ - \c 1: Forward. ++ **/ ++ CImg& round(const double y=1, const int rounding_type=0) { ++ if (y>0) ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=8192)) ++ cimg_rof(*this,ptrd,T) *ptrd = cimg::round(*ptrd,y,rounding_type); ++ return *this; ++ } ++ ++ //! Round pixel values \newinstance. ++ CImg get_round(const double y=1, const unsigned int rounding_type=0) const { ++ return (+*this).round(y,rounding_type); ++ } ++ ++ //! Add random noise to pixel values. ++ /** ++ \param sigma Amplitude of the random additive noise. If \p sigma<0, it stands for a percentage of the ++ global value range. ++ \param noise_type Type of additive noise (can be \p 0=gaussian, \p 1=uniform, \p 2=Salt and Pepper, ++ \p 3=Poisson or \p 4=Rician). ++ \return A reference to the modified image instance. ++ \note ++ - For Poisson noise (\p noise_type=3), parameter \p sigma is ignored, as Poisson noise only depends on ++ the image value itself. ++ - Function \p CImg::get_noise() is also defined. It returns a non-shared modified copy of the image instance. ++ \par Example ++ \code ++ const CImg img("reference.jpg"), res = img.get_noise(40); ++ (img,res.normalize(0,255)).display(); ++ \endcode ++ \image html ref_noise.jpg ++ **/ ++ CImg& noise(const double sigma, const unsigned int noise_type=0) { ++ if (is_empty()) return *this; ++ const Tfloat vmin = (Tfloat)cimg::type::min(), vmax = (Tfloat)cimg::type::max(); ++ Tfloat nsigma = (Tfloat)sigma, m = 0, M = 0; ++ if (nsigma==0 && noise_type!=3) return *this; ++ if (nsigma<0 || noise_type==2) m = (Tfloat)min_max(M); ++ if (nsigma<0) nsigma = (Tfloat)(-nsigma*(M-m)/100.0); ++ switch (noise_type) { ++ case 0 : { // Gaussian noise ++ cimg_rof(*this,ptrd,T) { ++ Tfloat val = (Tfloat)(*ptrd + nsigma*cimg::grand()); ++ if (val>vmax) val = vmax; ++ if (valvmax) val = vmax; ++ if (val::is_float()?(Tfloat)1:(Tfloat)cimg::type::max(); } ++ cimg_rof(*this,ptrd,T) if (cimg::rand(100)vmax) val = vmax; ++ if (val get_noise(const double sigma, const unsigned int noise_type=0) const { ++ return (+*this).noise(sigma,noise_type); ++ } ++ ++ //! Linearly normalize pixel values. ++ /** ++ \param min_value Minimum desired value of the resulting image. ++ \param max_value Maximum desired value of the resulting image. ++ \par Example ++ \code ++ const CImg img("reference.jpg"), res = img.get_normalize(160,220); ++ (img,res).display(); ++ \endcode ++ \image html ref_normalize2.jpg ++ **/ ++ CImg& normalize(const T& min_value, const T& max_value) { ++ if (is_empty()) return *this; ++ const T a = min_value get_normalize(const T& min_value, const T& max_value) const { ++ return CImg(*this,false).normalize((Tfloat)min_value,(Tfloat)max_value); ++ } ++ ++ //! Normalize multi-valued pixels of the image instance, with respect to their L2-norm. ++ /** ++ \par Example ++ \code ++ const CImg img("reference.jpg"), res = img.get_normalize(); ++ (img,res.normalize(0,255)).display(); ++ \endcode ++ \image html ref_normalize.jpg ++ **/ ++ CImg& normalize() { ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=512 && _height*_depth>=16)) ++ cimg_forYZ(*this,y,z) { ++ T *ptrd = data(0,y,z,0); ++ cimg_forX(*this,x) { ++ const T *ptrs = ptrd; ++ float n = 0; ++ cimg_forC(*this,c) { n+=cimg::sqr((float)*ptrs); ptrs+=whd; } ++ n = (float)std::sqrt(n); ++ T *_ptrd = ptrd++; ++ if (n>0) cimg_forC(*this,c) { *_ptrd = (T)(*_ptrd/n); _ptrd+=whd; } ++ else cimg_forC(*this,c) { *_ptrd = (T)0; _ptrd+=whd; } ++ } ++ } ++ return *this; ++ } ++ ++ //! Normalize multi-valued pixels of the image instance, with respect to their L2-norm \newinstance. ++ CImg get_normalize() const { ++ return CImg(*this,false).normalize(); ++ } ++ ++ //! Compute Lp-norm of each multi-valued pixel of the image instance. ++ /** ++ \param norm_type Type of computed vector norm (can be \p -1=Linf, or \p greater or equal than 0). ++ \par Example ++ \code ++ const CImg img("reference.jpg"), res = img.get_norm(); ++ (img,res.normalize(0,255)).display(); ++ \endcode ++ \image html ref_norm.jpg ++ **/ ++ CImg& norm(const int norm_type=2) { ++ if (_spectrum==1 && norm_type) return abs(); ++ return get_norm(norm_type).move_to(*this); ++ } ++ ++ //! Compute L2-norm of each multi-valued pixel of the image instance \newinstance. ++ CImg get_norm(const int norm_type=2) const { ++ if (is_empty()) return *this; ++ if (_spectrum==1 && norm_type) return get_abs(); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ CImg res(_width,_height,_depth); ++ switch (norm_type) { ++ case -1 : { // Linf-norm. ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=512 && _height*_depth>=16)) ++ cimg_forYZ(*this,y,z) { ++ const ulongT off = (ulongT)offset(0,y,z); ++ const T *ptrs = _data + off; ++ Tfloat *ptrd = res._data + off; ++ cimg_forX(*this,x) { ++ Tfloat n = 0; ++ const T *_ptrs = ptrs++; ++ cimg_forC(*this,c) { const Tfloat val = (Tfloat)cimg::abs(*_ptrs); if (val>n) n = val; _ptrs+=whd; } ++ *(ptrd++) = n; ++ } ++ } ++ } break; ++ case 0 : { // L0-norm. ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=512 && _height*_depth>=16)) ++ cimg_forYZ(*this,y,z) { ++ const ulongT off = (ulongT)offset(0,y,z); ++ const T *ptrs = _data + off; ++ Tfloat *ptrd = res._data + off; ++ cimg_forX(*this,x) { ++ unsigned int n = 0; ++ const T *_ptrs = ptrs++; ++ cimg_forC(*this,c) { n+=*_ptrs==0?0:1; _ptrs+=whd; } ++ *(ptrd++) = (Tfloat)n; ++ } ++ } ++ } break; ++ case 1 : { // L1-norm. ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=512 && _height*_depth>=16)) ++ cimg_forYZ(*this,y,z) { ++ const ulongT off = (ulongT)offset(0,y,z); ++ const T *ptrs = _data + off; ++ Tfloat *ptrd = res._data + off; ++ cimg_forX(*this,x) { ++ Tfloat n = 0; ++ const T *_ptrs = ptrs++; ++ cimg_forC(*this,c) { n+=cimg::abs(*_ptrs); _ptrs+=whd; } ++ *(ptrd++) = n; ++ } ++ } ++ } break; ++ case 2 : { // L2-norm. ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=512 && _height*_depth>=16)) ++ cimg_forYZ(*this,y,z) { ++ const ulongT off = (ulongT)offset(0,y,z); ++ const T *ptrs = _data + off; ++ Tfloat *ptrd = res._data + off; ++ cimg_forX(*this,x) { ++ Tfloat n = 0; ++ const T *_ptrs = ptrs++; ++ cimg_forC(*this,c) { n+=cimg::sqr((Tfloat)*_ptrs); _ptrs+=whd; } ++ *(ptrd++) = (Tfloat)std::sqrt((Tfloat)n); ++ } ++ } ++ } break; ++ default : { // Linf-norm. ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=512 && _height*_depth>=16)) ++ cimg_forYZ(*this,y,z) { ++ const ulongT off = (ulongT)offset(0,y,z); ++ const T *ptrs = _data + off; ++ Tfloat *ptrd = res._data + off; ++ cimg_forX(*this,x) { ++ Tfloat n = 0; ++ const T *_ptrs = ptrs++; ++ cimg_forC(*this,c) { n+=std::pow(cimg::abs((Tfloat)*_ptrs),(Tfloat)norm_type); _ptrs+=whd; } ++ *(ptrd++) = (Tfloat)std::pow((Tfloat)n,1/(Tfloat)norm_type); ++ } ++ } ++ } ++ } ++ return res; ++ } ++ ++ //! Cut pixel values in specified range. ++ /** ++ \param min_value Minimum desired value of the resulting image. ++ \param max_value Maximum desired value of the resulting image. ++ \par Example ++ \code ++ const CImg img("reference.jpg"), res = img.get_cut(160,220); ++ (img,res).display(); ++ \endcode ++ \image html ref_cut.jpg ++ **/ ++ CImg& cut(const T& min_value, const T& max_value) { ++ if (is_empty()) return *this; ++ const T a = min_value=32768)) ++ cimg_rof(*this,ptrd,T) *ptrd = (*ptrdb)?b:*ptrd); ++ return *this; ++ } ++ ++ //! Cut pixel values in specified range \newinstance. ++ CImg get_cut(const T& min_value, const T& max_value) const { ++ return (+*this).cut(min_value,max_value); ++ } ++ ++ //! Uniformly quantize pixel values. ++ /** ++ \param nb_levels Number of quantization levels. ++ \param keep_range Tells if resulting values keep the same range as the original ones. ++ \par Example ++ \code ++ const CImg img("reference.jpg"), res = img.get_quantize(4); ++ (img,res).display(); ++ \endcode ++ \image html ref_quantize.jpg ++ **/ ++ CImg& quantize(const unsigned int nb_levels, const bool keep_range=true) { ++ if (!nb_levels) ++ throw CImgArgumentException(_cimg_instance ++ "quantize(): Invalid quantization request with 0 values.", ++ cimg_instance); ++ ++ if (is_empty()) return *this; ++ Tfloat m, M = (Tfloat)max_min(m), range = M - m; ++ if (range>0) { ++ if (keep_range) ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768)) ++ cimg_rof(*this,ptrd,T) { ++ const unsigned int val = (unsigned int)((*ptrd-m)*nb_levels/range); ++ *ptrd = (T)(m + std::min(val,nb_levels - 1)*range/nb_levels); ++ } else ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768)) ++ cimg_rof(*this,ptrd,T) { ++ const unsigned int val = (unsigned int)((*ptrd-m)*nb_levels/range); ++ *ptrd = (T)std::min(val,nb_levels - 1); ++ } ++ } ++ return *this; ++ } ++ ++ //! Uniformly quantize pixel values \newinstance. ++ CImg get_quantize(const unsigned int n, const bool keep_range=true) const { ++ return (+*this).quantize(n,keep_range); ++ } ++ ++ //! Threshold pixel values. ++ /** ++ \param value Threshold value ++ \param soft_threshold Tells if soft thresholding must be applied (instead of hard one). ++ \param strict_threshold Tells if threshold value is strict. ++ \par Example ++ \code ++ const CImg img("reference.jpg"), res = img.get_threshold(128); ++ (img,res.normalize(0,255)).display(); ++ \endcode ++ \image html ref_threshold.jpg ++ **/ ++ CImg& threshold(const T& value, const bool soft_threshold=false, const bool strict_threshold=false) { ++ if (is_empty()) return *this; ++ if (strict_threshold) { ++ if (soft_threshold) ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768)) ++ cimg_rof(*this,ptrd,T) { ++ const T v = *ptrd; ++ *ptrd = v>value?(T)(v-value):v<-(float)value?(T)(v + value):(T)0; ++ } ++ else ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=65536)) ++ cimg_rof(*this,ptrd,T) *ptrd = *ptrd>value?(T)1:(T)0; ++ } else { ++ if (soft_threshold) ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768)) ++ cimg_rof(*this,ptrd,T) { ++ const T v = *ptrd; ++ *ptrd = v>=value?(T)(v-value):v<=-(float)value?(T)(v + value):(T)0; ++ } ++ else ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=65536)) ++ cimg_rof(*this,ptrd,T) *ptrd = *ptrd>=value?(T)1:(T)0; ++ } ++ return *this; ++ } ++ ++ //! Threshold pixel values \newinstance. ++ CImg get_threshold(const T& value, const bool soft_threshold=false, const bool strict_threshold=false) const { ++ return (+*this).threshold(value,soft_threshold,strict_threshold); ++ } ++ ++ //! Compute the histogram of pixel values. ++ /** ++ \param nb_levels Number of desired histogram levels. ++ \param min_value Minimum pixel value considered for the histogram computation. ++ All pixel values lower than \p min_value will not be counted. ++ \param max_value Maximum pixel value considered for the histogram computation. ++ All pixel values higher than \p max_value will not be counted. ++ \note ++ - The histogram H of an image I is the 1d function where H(x) counts the number of occurences of the value x ++ in the image I. ++ - The resulting histogram is always defined in 1d. Histograms of multi-valued images are not multi-dimensional. ++ \par Example ++ \code ++ const CImg img = CImg("reference.jpg").histogram(256); ++ img.display_graph(0,3); ++ \endcode ++ \image html ref_histogram.jpg ++ **/ ++ CImg& histogram(const unsigned int nb_levels, const T& min_value, const T& max_value) { ++ return get_histogram(nb_levels,min_value,max_value).move_to(*this); ++ } ++ ++ //! Compute the histogram of pixel values \overloading. ++ CImg& histogram(const unsigned int nb_levels) { ++ return get_histogram(nb_levels).move_to(*this); ++ } ++ ++ //! Compute the histogram of pixel values \newinstance. ++ CImg get_histogram(const unsigned int nb_levels, const T& min_value, const T& max_value) const { ++ if (!nb_levels || is_empty()) return CImg(); ++ const double ++ vmin = (double)(min_value res(nb_levels,1,1,1,0); ++ cimg_rof(*this,ptrs,T) { ++ const T val = *ptrs; ++ if (val>=vmin && val<=vmax) ++res[val==vmax?nb_levels - 1:(unsigned int)((val - vmin)*nb_levels/(vmax - vmin))]; ++ } ++ return res; ++ } ++ ++ //! Compute the histogram of pixel values \newinstance. ++ CImg get_histogram(const unsigned int nb_levels) const { ++ if (!nb_levels || is_empty()) return CImg(); ++ T vmax = 0, vmin = min_max(vmax); ++ return get_histogram(nb_levels,vmin,vmax); ++ } ++ ++ //! Equalize histogram of pixel values. ++ /** ++ \param nb_levels Number of histogram levels used for the equalization. ++ \param min_value Minimum pixel value considered for the histogram computation. ++ All pixel values lower than \p min_value will not be counted. ++ \param max_value Maximum pixel value considered for the histogram computation. ++ All pixel values higher than \p max_value will not be counted. ++ \par Example ++ \code ++ const CImg img("reference.jpg"), res = img.get_equalize(256); ++ (img,res).display(); ++ \endcode ++ \image html ref_equalize.jpg ++ **/ ++ CImg& equalize(const unsigned int nb_levels, const T& min_value, const T& max_value) { ++ if (!nb_levels || is_empty()) return *this; ++ const T ++ vmin = min_value hist = get_histogram(nb_levels,vmin,vmax); ++ ulongT cumul = 0; ++ cimg_forX(hist,pos) { cumul+=hist[pos]; hist[pos] = cumul; } ++ if (!cumul) cumul = 1; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=1048576)) ++ cimg_rof(*this,ptrd,T) { ++ const int pos = (int)((*ptrd-vmin)*(nb_levels - 1.)/(vmax-vmin)); ++ if (pos>=0 && pos<(int)nb_levels) *ptrd = (T)(vmin + (vmax-vmin)*hist[pos]/cumul); ++ } ++ return *this; ++ } ++ ++ //! Equalize histogram of pixel values \overloading. ++ CImg& equalize(const unsigned int nb_levels) { ++ if (!nb_levels || is_empty()) return *this; ++ T vmax = 0, vmin = min_max(vmax); ++ return equalize(nb_levels,vmin,vmax); ++ } ++ ++ //! Equalize histogram of pixel values \newinstance. ++ CImg get_equalize(const unsigned int nblevels, const T& val_min, const T& val_max) const { ++ return (+*this).equalize(nblevels,val_min,val_max); ++ } ++ ++ //! Equalize histogram of pixel values \newinstance. ++ CImg get_equalize(const unsigned int nblevels) const { ++ return (+*this).equalize(nblevels); ++ } ++ ++ //! Index multi-valued pixels regarding to a specified colormap. ++ /** ++ \param colormap Multi-valued colormap used as the basis for multi-valued pixel indexing. ++ \param dithering Level of dithering (0=disable, 1=standard level). ++ \param map_indexes Tell if the values of the resulting image are the colormap indices or the colormap vectors. ++ \note ++ - \p img.index(colormap,dithering,1) is equivalent to img.index(colormap,dithering,0).map(colormap). ++ \par Example ++ \code ++ const CImg img("reference.jpg"), colormap(3,1,1,3, 0,128,255, 0,128,255, 0,128,255); ++ const CImg res = img.get_index(colormap,1,true); ++ (img,res).display(); ++ \endcode ++ \image html ref_index.jpg ++ **/ ++ template ++ CImg& index(const CImg& colormap, const float dithering=1, const bool map_indexes=false) { ++ return get_index(colormap,dithering,map_indexes).move_to(*this); ++ } ++ ++ //! Index multi-valued pixels regarding to a specified colormap \newinstance. ++ template ++ CImg::Tuint> ++ get_index(const CImg& colormap, const float dithering=1, const bool map_indexes=true) const { ++ if (colormap._spectrum!=_spectrum) ++ throw CImgArgumentException(_cimg_instance ++ "index(): Instance and specified colormap (%u,%u,%u,%u,%p) " ++ "have incompatible dimensions.", ++ cimg_instance, ++ colormap._width,colormap._height,colormap._depth,colormap._spectrum,colormap._data); ++ ++ typedef typename CImg::Tuint tuint; ++ if (is_empty()) return CImg(); ++ const ulongT ++ whd = (ulongT)_width*_height*_depth, ++ pwhd = (ulongT)colormap._width*colormap._height*colormap._depth; ++ CImg res(_width,_height,_depth,map_indexes?_spectrum:1); ++ tuint *ptrd = res._data; ++ if (dithering>0) { // Dithered versions. ++ const float ndithering = cimg::cut(dithering,0,1)/16; ++ Tfloat valm = 0, valM = (Tfloat)max_min(valm); ++ if (valm==valM && valm>=0 && valM<=255) { valm = 0; valM = 255; } ++ CImg cache = get_crop(-1,0,0,0,_width,1,0,_spectrum - 1); ++ Tfloat *cache_current = cache.data(1,0,0,0), *cache_next = cache.data(1,1,0,0); ++ const ulongT cwhd = (ulongT)cache._width*cache._height*cache._depth; ++ switch (_spectrum) { ++ case 1 : { // Optimized for scalars. ++ cimg_forYZ(*this,y,z) { ++ if (yvalM?valM:_val0; ++ Tfloat distmin = cimg::type::max(); const t *ptrmin0 = colormap._data; ++ for (const t *ptrp0 = colormap._data, *ptrp_end = ptrp0 + pwhd; ptrp0valM?valM:_val0, ++ _val1 = (Tfloat)*ptrs1, val1 = _val1valM?valM:_val1; ++ Tfloat distmin = cimg::type::max(); const t *ptrmin0 = colormap._data; ++ for (const t *ptrp0 = colormap._data, *ptrp1 = ptrp0 + pwhd, *ptrp_end = ptrp1; ptrp0valM?valM:_val0, ++ _val1 = (Tfloat)*ptrs1, val1 = _val1valM?valM:_val1, ++ _val2 = (Tfloat)*ptrs2, val2 = _val2valM?valM:_val2; ++ Tfloat distmin = cimg::type::max(); const t *ptrmin0 = colormap._data; ++ for (const t *ptrp0 = colormap._data, *ptrp1 = ptrp0 + pwhd, *ptrp2 = ptrp1 + pwhd, ++ *ptrp_end = ptrp1; ptrp0::max(); const t *ptrmin = colormap._data; ++ for (const t *ptrp = colormap._data, *ptrp_end = ptrp + pwhd; ptrpvalM?valM:_val; ++ dist+=cimg::sqr((*_ptrs=val) - (Tfloat)*_ptrp); _ptrs+=cwhd; _ptrp+=pwhd; ++ } ++ if (dist=64 && _height*_depth>=16 && pwhd>=16)) ++ cimg_forYZ(*this,y,z) { ++ tuint *ptrd = res.data(0,y,z); ++ for (const T *ptrs0 = data(0,y,z), *ptrs_end = ptrs0 + _width; ptrs0::max(); const t *ptrmin0 = colormap._data; ++ for (const t *ptrp0 = colormap._data, *ptrp_end = ptrp0 + pwhd; ptrp0=64 && _height*_depth>=16 && pwhd>=16)) ++ cimg_forYZ(*this,y,z) { ++ tuint *ptrd = res.data(0,y,z), *ptrd1 = ptrd + whd; ++ for (const T *ptrs0 = data(0,y,z), *ptrs1 = ptrs0 + whd, *ptrs_end = ptrs0 + _width; ptrs0::max(); const t *ptrmin0 = colormap._data; ++ for (const t *ptrp0 = colormap._data, *ptrp1 = ptrp0 + pwhd, *ptrp_end = ptrp1; ptrp0=64 && _height*_depth>=16 && pwhd>=16)) ++ cimg_forYZ(*this,y,z) { ++ tuint *ptrd = res.data(0,y,z), *ptrd1 = ptrd + whd, *ptrd2 = ptrd1 + whd; ++ for (const T *ptrs0 = data(0,y,z), *ptrs1 = ptrs0 + whd, *ptrs2 = ptrs1 + whd, ++ *ptrs_end = ptrs0 + _width; ptrs0::max(); const t *ptrmin0 = colormap._data; ++ for (const t *ptrp0 = colormap._data, *ptrp1 = ptrp0 + pwhd, *ptrp2 = ptrp1 + pwhd, ++ *ptrp_end = ptrp1; ptrp0=64 && _height*_depth>=16 && pwhd>=16)) ++ cimg_forYZ(*this,y,z) { ++ tuint *ptrd = res.data(0,y,z); ++ for (const T *ptrs = data(0,y,z), *ptrs_end = ptrs + _width; ptrs::max(); const t *ptrmin = colormap._data; ++ for (const t *ptrp = colormap._data, *ptrp_end = ptrp + pwhd; ptrp img("reference.jpg"), ++ colormap1(3,1,1,3, 0,128,255, 0,128,255, 0,128,255), ++ colormap2(3,1,1,3, 255,0,0, 0,255,0, 0,0,255), ++ res = img.get_index(colormap1,0).map(colormap2); ++ (img,res).display(); ++ \endcode ++ \image html ref_map.jpg ++ **/ ++ template ++ CImg& map(const CImg& colormap, const unsigned int boundary_conditions=0) { ++ return get_map(colormap,boundary_conditions).move_to(*this); ++ } ++ ++ //! Map predefined colormap on the scalar (indexed) image instance \newinstance. ++ template ++ CImg get_map(const CImg& colormap, const unsigned int boundary_conditions=0) const { ++ if (_spectrum!=1 && colormap._spectrum!=1) ++ throw CImgArgumentException(_cimg_instance ++ "map(): Instance and specified colormap (%u,%u,%u,%u,%p) " ++ "have incompatible dimensions.", ++ cimg_instance, ++ colormap._width,colormap._height,colormap._depth,colormap._spectrum,colormap._data); ++ ++ const ulongT ++ whd = (ulongT)_width*_height*_depth, ++ cwhd = (ulongT)colormap._width*colormap._height*colormap._depth, ++ cwhd2 = 2*cwhd; ++ CImg res(_width,_height,_depth,colormap._spectrum==1?_spectrum:colormap._spectrum); ++ switch (colormap._spectrum) { ++ ++ case 1 : { // Optimized for scalars ++ const T *ptrs = _data; ++ switch (boundary_conditions) { ++ case 3 : // Mirror ++ cimg_for(res,ptrd,t) { ++ const ulongT ind = ((ulongT)*(ptrs++))%cwhd2; ++ *ptrd = colormap[ind& label(const bool is_high_connectivity=false, const Tfloat tolerance=0) { ++ return get_label(is_high_connectivity,tolerance).move_to(*this); ++ } ++ ++ //! Label connected components \newinstance. ++ CImg get_label(const bool is_high_connectivity=false, ++ const Tfloat tolerance=0) const { ++ if (is_empty()) return CImg(); ++ ++ // Create neighborhood tables. ++ int dx[13], dy[13], dz[13], nb = 0; ++ dx[nb] = 1; dy[nb] = 0; dz[nb++] = 0; ++ dx[nb] = 0; dy[nb] = 1; dz[nb++] = 0; ++ if (is_high_connectivity) { ++ dx[nb] = 1; dy[nb] = 1; dz[nb++] = 0; ++ dx[nb] = 1; dy[nb] = -1; dz[nb++] = 0; ++ } ++ if (_depth>1) { // 3d version. ++ dx[nb] = 0; dy[nb] = 0; dz[nb++]=1; ++ if (is_high_connectivity) { ++ dx[nb] = 1; dy[nb] = 1; dz[nb++] = -1; ++ dx[nb] = 1; dy[nb] = 0; dz[nb++] = -1; ++ dx[nb] = 1; dy[nb] = -1; dz[nb++] = -1; ++ dx[nb] = 0; dy[nb] = 1; dz[nb++] = -1; ++ ++ dx[nb] = 0; dy[nb] = 1; dz[nb++] = 1; ++ dx[nb] = 1; dy[nb] = -1; dz[nb++] = 1; ++ dx[nb] = 1; dy[nb] = 0; dz[nb++] = 1; ++ dx[nb] = 1; dy[nb] = 1; dz[nb++] = 1; ++ } ++ } ++ return _label(nb,dx,dy,dz,tolerance); ++ } ++ ++ //! Label connected components \overloading. ++ /** ++ \param connectivity_mask Mask of the neighboring pixels. ++ \param tolerance Tolerance used to determine if two neighboring pixels belong to the same region. ++ **/ ++ template ++ CImg& label(const CImg& connectivity_mask, const Tfloat tolerance=0) { ++ return get_label(connectivity_mask,tolerance).move_to(*this); ++ } ++ ++ //! Label connected components \newinstance. ++ template ++ CImg get_label(const CImg& connectivity_mask, ++ const Tfloat tolerance=0) const { ++ int nb = 0; ++ cimg_for(connectivity_mask,ptr,t) if (*ptr) ++nb; ++ CImg dx(nb,1,1,1,0), dy(nb,1,1,1,0), dz(nb,1,1,1,0); ++ nb = 0; ++ cimg_forXYZ(connectivity_mask,x,y,z) if ((x || y || z) && ++ connectivity_mask(x,y,z)) { ++ dx[nb] = x; dy[nb] = y; dz[nb++] = z; ++ } ++ return _label(nb,dx,dy,dz,tolerance); ++ } ++ ++ CImg _label(const unsigned int nb, const int *const dx, ++ const int *const dy, const int *const dz, ++ const Tfloat tolerance) const { ++ CImg res(_width,_height,_depth,_spectrum); ++ cimg_forC(*this,c) { ++ CImg _res = res.get_shared_channel(c); ++ ++ // Init label numbers. ++ ulongT *ptr = _res.data(); ++ cimg_foroff(_res,p) *(ptr++) = p; ++ ++ // For each neighbour-direction, label. ++ for (unsigned int n = 0; n& _system_strescape() { ++#define cimg_system_strescape(c,s) case c : if (p!=ptrs) CImg(ptrs,(unsigned int)(p-ptrs),1,1,1,false).\ ++ move_to(list); \ ++ CImg(s,(unsigned int)std::strlen(s),1,1,1,false).move_to(list); ptrs = p + 1; break ++ CImgList list; ++ const T *ptrs = _data; ++ cimg_for(*this,p,T) switch ((int)*p) { ++ cimg_system_strescape('\\',"\\\\"); ++ cimg_system_strescape('\"',"\\\""); ++ cimg_system_strescape('!',"\"\\!\""); ++ cimg_system_strescape('`',"\\`"); ++ cimg_system_strescape('$',"\\$"); ++ } ++ if (ptrs(ptrs,(unsigned int)(end()-ptrs),1,1,1,false).move_to(list); ++ return (list>'x').move_to(*this); ++ } ++ ++ //@} ++ //--------------------------------- ++ // ++ //! \name Color Base Management ++ //@{ ++ //--------------------------------- ++ ++ //! Return colormap \e "default", containing 256 colors entries in RGB. ++ /** ++ \return The following \c 256x1x1x3 colormap is returned: ++ \image html ref_colormap_default.jpg ++ **/ ++ static const CImg& default_LUT256() { ++ static CImg colormap; ++ cimg::mutex(8); ++ if (!colormap) { ++ colormap.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) { ++ colormap(0,index,0) = (Tuchar)r; ++ colormap(0,index,1) = (Tuchar)g; ++ colormap(0,index++,2) = (Tuchar)b; ++ } ++ } ++ cimg::mutex(8,0); ++ return colormap; ++ } ++ ++ //! Return colormap \e "HSV", containing 256 colors entries in RGB. ++ /** ++ \return The following \c 256x1x1x3 colormap is returned: ++ \image html ref_colormap_hsv.jpg ++ **/ ++ static const CImg& HSV_LUT256() { ++ static CImg colormap; ++ cimg::mutex(8); ++ if (!colormap) { ++ CImg tmp(1,256,1,3,1); ++ tmp.get_shared_channel(0).sequence(0,359); ++ colormap = tmp.HSVtoRGB(); ++ } ++ cimg::mutex(8,0); ++ return colormap; ++ } ++ ++ //! Return colormap \e "lines", containing 256 colors entries in RGB. ++ /** ++ \return The following \c 256x1x1x3 colormap is returned: ++ \image html ref_colormap_lines.jpg ++ **/ ++ static const CImg& lines_LUT256() { ++ 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 colormap(pal,1,256,1,3,false); ++ return colormap; ++ } ++ ++ //! Return colormap \e "hot", containing 256 colors entries in RGB. ++ /** ++ \return The following \c 256x1x1x3 colormap is returned: ++ \image html ref_colormap_hot.jpg ++ **/ ++ static const CImg& hot_LUT256() { ++ static CImg colormap; ++ cimg::mutex(8); ++ if (!colormap) { ++ colormap.assign(1,4,1,3,(T)0); ++ colormap[1] = colormap[2] = colormap[3] = colormap[6] = colormap[7] = colormap[11] = 255; ++ colormap.resize(1,256,1,3,3); ++ } ++ cimg::mutex(8,0); ++ return colormap; ++ } ++ ++ //! Return colormap \e "cool", containing 256 colors entries in RGB. ++ /** ++ \return The following \c 256x1x1x3 colormap is returned: ++ \image html ref_colormap_cool.jpg ++ **/ ++ static const CImg& cool_LUT256() { ++ static CImg colormap; ++ cimg::mutex(8); ++ if (!colormap) colormap.assign(1,2,1,3).fill((T)0,(T)255,(T)255,(T)0,(T)255,(T)255).resize(1,256,1,3,3); ++ cimg::mutex(8,0); ++ return colormap; ++ } ++ ++ //! Return colormap \e "jet", containing 256 colors entries in RGB. ++ /** ++ \return The following \c 256x1x1x3 colormap is returned: ++ \image html ref_colormap_jet.jpg ++ **/ ++ static const CImg& jet_LUT256() { ++ static CImg colormap; ++ cimg::mutex(8); ++ if (!colormap) { ++ colormap.assign(1,4,1,3,(T)0); ++ colormap[2] = colormap[3] = colormap[5] = colormap[6] = colormap[8] = colormap[9] = 255; ++ colormap.resize(1,256,1,3,3); ++ } ++ cimg::mutex(8,0); ++ return colormap; ++ } ++ ++ //! Return colormap \e "flag", containing 256 colors entries in RGB. ++ /** ++ \return The following \c 256x1x1x3 colormap is returned: ++ \image html ref_colormap_flag.jpg ++ **/ ++ static const CImg& flag_LUT256() { ++ static CImg colormap; ++ cimg::mutex(8); ++ if (!colormap) { ++ colormap.assign(1,4,1,3,(T)0); ++ colormap[0] = colormap[1] = colormap[5] = colormap[9] = colormap[10] = 255; ++ colormap.resize(1,256,1,3,0,2); ++ } ++ cimg::mutex(8,0); ++ return colormap; ++ } ++ ++ //! Return colormap \e "cube", containing 256 colors entries in RGB. ++ /** ++ \return The following \c 256x1x1x3 colormap is returned: ++ \image html ref_colormap_cube.jpg ++ **/ ++ static const CImg& cube_LUT256() { ++ static CImg colormap; ++ cimg::mutex(8); ++ if (!colormap) { ++ colormap.assign(1,8,1,3,(T)0); ++ colormap[1] = colormap[3] = colormap[5] = colormap[7] = ++ colormap[10] = colormap[11] = colormap[12] = colormap[13] = ++ colormap[20] = colormap[21] = colormap[22] = colormap[23] = 255; ++ colormap.resize(1,256,1,3,3); ++ } ++ cimg::mutex(8,0); ++ return colormap; ++ } ++ ++ //! Convert pixel values from sRGB to RGB color spaces. ++ CImg& sRGBtoRGB() { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32)) ++ cimg_rof(*this,ptr,T) { ++ const Tfloat ++ sval = (Tfloat)*ptr/255, ++ val = (Tfloat)(sval<=0.04045f?sval/12.92f:std::pow((sval + 0.055f)/(1.055f),2.4f)); ++ *ptr = (T)cimg::cut(val*255,0,255); ++ } ++ return *this; ++ } ++ ++ //! Convert pixel values from sRGB to RGB color spaces \newinstance. ++ CImg get_sRGBtoRGB() const { ++ return CImg(*this,false).sRGBtoRGB(); ++ } ++ ++ //! Convert pixel values from RGB to sRGB color spaces. ++ CImg& RGBtosRGB() { ++ if (is_empty()) return *this; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32)) ++ cimg_rof(*this,ptr,T) { ++ const Tfloat ++ val = (Tfloat)*ptr/255, ++ sval = (Tfloat)(val<=0.0031308f?val*12.92f:1.055f*std::pow(val,0.416667f) - 0.055f); ++ *ptr = (T)cimg::cut(sval*255,0,255); ++ } ++ return *this; ++ } ++ ++ //! Convert pixel values from RGB to sRGB color spaces \newinstance. ++ CImg get_RGBtosRGB() const { ++ return CImg(*this,false).RGBtosRGB(); ++ } ++ ++ //! Convert pixel values from RGB to HSI color spaces. ++ CImg& RGBtoHSI() { ++ if (_spectrum!=3) ++ throw CImgInstanceException(_cimg_instance ++ "RGBtoHSI(): Instance is not a RGB image.", ++ cimg_instance); ++ ++ T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=256)) ++ for (ulongT N = 0; N0) H = B<=G?theta:360 - theta; ++ if (sum>0) S = 1 - 3*m/sum; ++ I = sum/(3*255); ++ p1[N] = (T)cimg::cut(H,0,360); ++ p2[N] = (T)cimg::cut(S,0,1); ++ p3[N] = (T)cimg::cut(I,0,1); ++ } ++ return *this; ++ } ++ ++ //! Convert pixel values from RGB to HSI color spaces \newinstance. ++ CImg get_RGBtoHSI() const { ++ return CImg(*this,false).RGBtoHSI(); ++ } ++ ++ //! Convert pixel values from HSI to RGB color spaces. ++ CImg& HSItoRGB() { ++ if (_spectrum!=3) ++ throw CImgInstanceException(_cimg_instance ++ "HSItoRGB(): Instance is not a HSI image.", ++ cimg_instance); ++ ++ T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=256)) ++ for (ulongT N = 0; N get_HSItoRGB() const { ++ return CImg< Tuchar>(*this,false).HSItoRGB(); ++ } ++ ++ //! Convert pixel values from RGB to HSL color spaces. ++ CImg& RGBtoHSL() { ++ if (_spectrum!=3) ++ throw CImgInstanceException(_cimg_instance ++ "RGBtoHSL(): Instance is not a RGB image.", ++ cimg_instance); ++ ++ T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=256)) ++ for (ulongT N = 0; N=6) H-=6; ++ H*=60; ++ S = 2*L<=1?(M - m)/(M + m):(M - m)/(2*255 - M - m); ++ } ++ p1[N] = (T)cimg::cut(H,0,360); ++ p2[N] = (T)cimg::cut(S,0,1); ++ p3[N] = (T)cimg::cut(L,0,1); ++ } ++ return *this; ++ } ++ ++ //! Convert pixel values from RGB to HSL color spaces \newinstance. ++ CImg get_RGBtoHSL() const { ++ return CImg(*this,false).RGBtoHSL(); ++ } ++ ++ //! Convert pixel values from HSL to RGB color spaces. ++ CImg& HSLtoRGB() { ++ if (_spectrum!=3) ++ throw CImgInstanceException(_cimg_instance ++ "HSLtoRGB(): Instance is not a HSL image.", ++ cimg_instance); ++ ++ T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=256)) ++ for (ulongT N = 0; N1?tr - 1:(Tfloat)tr, ++ ntg = tg<0?tg + 1:tg>1?tg - 1:(Tfloat)tg, ++ ntb = tb<0?tb + 1:tb>1?tb - 1:(Tfloat)tb, ++ R = 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 = 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 = 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[N] = (T)cimg::cut(255*R,0,255); ++ p2[N] = (T)cimg::cut(255*G,0,255); ++ p3[N] = (T)cimg::cut(255*B,0,255); ++ } ++ return *this; ++ } ++ ++ //! Convert pixel values from HSL to RGB color spaces \newinstance. ++ CImg get_HSLtoRGB() const { ++ return CImg(*this,false).HSLtoRGB(); ++ } ++ ++ //! Convert pixel values from RGB to HSV color spaces. ++ CImg& RGBtoHSV() { ++ if (_spectrum!=3) ++ throw CImgInstanceException(_cimg_instance ++ "RGBtoHSV(): Instance is not a RGB image.", ++ cimg_instance); ++ ++ T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=256)) ++ for (ulongT N = 0; N=6) H-=6; ++ H*=60; ++ S = (M - m)/M; ++ } ++ p1[N] = (T)cimg::cut(H,0,360); ++ p2[N] = (T)cimg::cut(S,0,1); ++ p3[N] = (T)cimg::cut(M/255,0,1); ++ } ++ return *this; ++ } ++ ++ //! Convert pixel values from RGB to HSV color spaces \newinstance. ++ CImg get_RGBtoHSV() const { ++ return CImg(*this,false).RGBtoHSV(); ++ } ++ ++ //! Convert pixel values from HSV to RGB color spaces. ++ CImg& HSVtoRGB() { ++ if (_spectrum!=3) ++ throw CImgInstanceException(_cimg_instance ++ "HSVtoRGB(): Instance is not a HSV image.", ++ cimg_instance); ++ ++ T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=256)) ++ for (ulongT N = 0; N get_HSVtoRGB() const { ++ return CImg(*this,false).HSVtoRGB(); ++ } ++ ++ //! Convert pixel values from RGB to YCbCr color spaces. ++ CImg& RGBtoYCbCr() { ++ if (_spectrum!=3) ++ throw CImgInstanceException(_cimg_instance ++ "RGBtoYCbCr(): Instance is not a RGB image.", ++ cimg_instance); ++ ++ T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=512)) ++ for (ulongT N = 0; N get_RGBtoYCbCr() const { ++ return CImg(*this,false).RGBtoYCbCr(); ++ } ++ ++ //! Convert pixel values from RGB to YCbCr color spaces. ++ CImg& YCbCrtoRGB() { ++ if (_spectrum!=3) ++ throw CImgInstanceException(_cimg_instance ++ "YCbCrtoRGB(): Instance is not a YCbCr image.", ++ cimg_instance); ++ ++ T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=512)) ++ for (ulongT N = 0; N get_YCbCrtoRGB() const { ++ return CImg(*this,false).YCbCrtoRGB(); ++ } ++ ++ //! Convert pixel values from RGB to YUV color spaces. ++ CImg& RGBtoYUV() { ++ if (_spectrum!=3) ++ throw CImgInstanceException(_cimg_instance ++ "RGBtoYUV(): Instance is not a RGB image.", ++ cimg_instance); ++ ++ T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=16384)) ++ for (ulongT N = 0; N get_RGBtoYUV() const { ++ return CImg(*this,false).RGBtoYUV(); ++ } ++ ++ //! Convert pixel values from YUV to RGB color spaces. ++ CImg& YUVtoRGB() { ++ if (_spectrum!=3) ++ throw CImgInstanceException(_cimg_instance ++ "YUVtoRGB(): Instance is not a YUV image.", ++ cimg_instance); ++ ++ T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=16384)) ++ for (ulongT N = 0; N get_YUVtoRGB() const { ++ return CImg< Tuchar>(*this,false).YUVtoRGB(); ++ } ++ ++ //! Convert pixel values from RGB to CMY color spaces. ++ CImg& RGBtoCMY() { ++ if (_spectrum!=3) ++ throw CImgInstanceException(_cimg_instance ++ "RGBtoCMY(): Instance is not a RGB image.", ++ cimg_instance); ++ ++ T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=2048)) ++ for (ulongT N = 0; N get_RGBtoCMY() const { ++ return CImg(*this,false).RGBtoCMY(); ++ } ++ ++ //! Convert pixel values from CMY to RGB color spaces. ++ CImg& CMYtoRGB() { ++ if (_spectrum!=3) ++ throw CImgInstanceException(_cimg_instance ++ "CMYtoRGB(): Instance is not a CMY image.", ++ cimg_instance); ++ ++ T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=2048)) ++ for (ulongT N = 0; N get_CMYtoRGB() const { ++ return CImg(*this,false).CMYtoRGB(); ++ } ++ ++ //! Convert pixel values from CMY to CMYK color spaces. ++ CImg& CMYtoCMYK() { ++ return get_CMYtoCMYK().move_to(*this); ++ } ++ ++ //! Convert pixel values from CMY to CMYK color spaces \newinstance. ++ CImg get_CMYtoCMYK() const { ++ if (_spectrum!=3) ++ throw CImgInstanceException(_cimg_instance ++ "CMYtoCMYK(): Instance is not a CMY image.", ++ cimg_instance); ++ ++ CImg res(_width,_height,_depth,4); ++ const T *ps1 = data(0,0,0,0), *ps2 = data(0,0,0,1), *ps3 = data(0,0,0,2); ++ Tfloat *pd1 = res.data(0,0,0,0), *pd2 = res.data(0,0,0,1), *pd3 = res.data(0,0,0,2), *pd4 = res.data(0,0,0,3); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=1024)) ++ for (ulongT N = 0; N=255) C = M = Y = 0; ++ else { const Tfloat K1 = 255 - K; C = 255*(C - K)/K1; M = 255*(M - K)/K1; Y = 255*(Y - K)/K1; } ++ pd1[N] = (Tfloat)cimg::cut(C,0,255), ++ pd2[N] = (Tfloat)cimg::cut(M,0,255), ++ pd3[N] = (Tfloat)cimg::cut(Y,0,255), ++ pd4[N] = (Tfloat)cimg::cut(K,0,255); ++ } ++ return res; ++ } ++ ++ //! Convert pixel values from CMYK to CMY color spaces. ++ CImg& CMYKtoCMY() { ++ return get_CMYKtoCMY().move_to(*this); ++ } ++ ++ //! Convert pixel values from CMYK to CMY color spaces \newinstance. ++ CImg get_CMYKtoCMY() const { ++ if (_spectrum!=4) ++ throw CImgInstanceException(_cimg_instance ++ "CMYKtoCMY(): Instance is not a CMYK image.", ++ cimg_instance); ++ ++ CImg res(_width,_height,_depth,3); ++ const T *ps1 = data(0,0,0,0), *ps2 = data(0,0,0,1), *ps3 = data(0,0,0,2), *ps4 = data(0,0,0,3); ++ Tfloat *pd1 = res.data(0,0,0,0), *pd2 = res.data(0,0,0,1), *pd3 = res.data(0,0,0,2); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=1024)) ++ for (ulongT N = 0; N& RGBtoXYZ(const bool use_D65=true) { ++ if (_spectrum!=3) ++ throw CImgInstanceException(_cimg_instance ++ "RGBtoXYZ(): Instance is not a RGB image.", ++ cimg_instance); ++ ++ T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=2048)) ++ for (ulongT N = 0; N get_RGBtoXYZ(const bool use_D65=true) const { ++ return CImg(*this,false).RGBtoXYZ(use_D65); ++ } ++ ++ //! Convert pixel values from XYZ to RGB color spaces. ++ /** ++ \param use_D65 Tell to use the D65 illuminant (D50 otherwise). ++ **/ ++ CImg& XYZtoRGB(const bool use_D65=true) { ++ if (_spectrum!=3) ++ throw CImgInstanceException(_cimg_instance ++ "XYZtoRGB(): Instance is not a XYZ image.", ++ cimg_instance); ++ ++ T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=2048)) ++ for (ulongT N = 0; N get_XYZtoRGB(const bool use_D65=true) const { ++ return CImg(*this,false).XYZtoRGB(use_D65); ++ } ++ ++ //! Convert pixel values from XYZ to Lab color spaces. ++ CImg& XYZtoLab(const bool use_D65=true) { ++#define _cimg_Labf(x) (24389*(x)>216?cimg::cbrt(x):(24389*(x)/27 + 16)/116) ++ ++ if (_spectrum!=3) ++ throw CImgInstanceException(_cimg_instance ++ "XYZtoLab(): Instance is not a XYZ image.", ++ cimg_instance); ++ const CImg white = CImg(1,1,1,3,255).RGBtoXYZ(use_D65); ++ T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=128)) ++ for (ulongT N = 0; N get_XYZtoLab(const bool use_D65=true) const { ++ return CImg(*this,false).XYZtoLab(use_D65); ++ } ++ ++ //! Convert pixel values from Lab to XYZ color spaces. ++ CImg& LabtoXYZ(const bool use_D65=true) { ++ if (_spectrum!=3) ++ throw CImgInstanceException(_cimg_instance ++ "LabtoXYZ(): Instance is not a Lab image.", ++ cimg_instance); ++ const CImg white = CImg(1,1,1,3,255).RGBtoXYZ(use_D65); ++ T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=128)) ++ for (ulongT N = 0; N216?cX*cX*cX:(116*cX - 16)*27/24389), ++ Y = (Tfloat)(27*L>216?cY*cY*cY:27*L/24389), ++ Z = (Tfloat)(24389*cZ>216?cZ*cZ*cZ:(116*cZ - 16)*27/24389); ++ p1[N] = (T)(X*white[0]); ++ p2[N] = (T)(Y*white[1]); ++ p3[N] = (T)(Z*white[2]); ++ } ++ return *this; ++ } ++ ++ //! Convert pixel values from Lab to XYZ color spaces \newinstance. ++ CImg get_LabtoXYZ(const bool use_D65=true) const { ++ return CImg(*this,false).LabtoXYZ(use_D65); ++ } ++ ++ //! Convert pixel values from XYZ to xyY color spaces. ++ CImg& XYZtoxyY() { ++ if (_spectrum!=3) ++ throw CImgInstanceException(_cimg_instance ++ "XYZtoxyY(): Instance is not a XYZ image.", ++ cimg_instance); ++ ++ T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=4096)) ++ for (ulongT N = 0; N0?sum:1; ++ p1[N] = (T)(X/nsum); ++ p2[N] = (T)(Y/nsum); ++ p3[N] = (T)Y; ++ } ++ return *this; ++ } ++ ++ //! Convert pixel values from XYZ to xyY color spaces \newinstance. ++ CImg get_XYZtoxyY() const { ++ return CImg(*this,false).XYZtoxyY(); ++ } ++ ++ //! Convert pixel values from xyY pixels to XYZ color spaces. ++ CImg& xyYtoXYZ() { ++ if (_spectrum!=3) ++ throw CImgInstanceException(_cimg_instance ++ "xyYtoXYZ(): Instance is not a xyY image.", ++ cimg_instance); ++ ++ T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=4096)) ++ for (ulongT N = 0; N0?py:1; ++ p1[N] = (T)(px*Y/ny); ++ p2[N] = (T)Y; ++ p3[N] = (T)((1 - px - py)*Y/ny); ++ } ++ return *this; ++ } ++ ++ //! Convert pixel values from xyY pixels to XYZ color spaces \newinstance. ++ CImg get_xyYtoXYZ() const { ++ return CImg(*this,false).xyYtoXYZ(); ++ } ++ ++ //! Convert pixel values from RGB to Lab color spaces. ++ CImg& RGBtoLab(const bool use_D65=true) { ++ return RGBtoXYZ(use_D65).XYZtoLab(use_D65); ++ } ++ ++ //! Convert pixel values from RGB to Lab color spaces \newinstance. ++ CImg get_RGBtoLab(const bool use_D65=true) const { ++ return CImg(*this,false).RGBtoLab(use_D65); ++ } ++ ++ //! Convert pixel values from Lab to RGB color spaces. ++ CImg& LabtoRGB(const bool use_D65=true) { ++ return LabtoXYZ().XYZtoRGB(use_D65); ++ } ++ ++ //! Convert pixel values from Lab to RGB color spaces \newinstance. ++ CImg get_LabtoRGB(const bool use_D65=true) const { ++ return CImg(*this,false).LabtoRGB(use_D65); ++ } ++ ++ //! Convert pixel values from RGB to xyY color spaces. ++ CImg& RGBtoxyY(const bool use_D65=true) { ++ return RGBtoXYZ(use_D65).XYZtoxyY(); ++ } ++ ++ //! Convert pixel values from RGB to xyY color spaces \newinstance. ++ CImg get_RGBtoxyY(const bool use_D65=true) const { ++ return CImg(*this,false).RGBtoxyY(use_D65); ++ } ++ ++ //! Convert pixel values from xyY to RGB color spaces. ++ CImg& xyYtoRGB(const bool use_D65=true) { ++ return xyYtoXYZ().XYZtoRGB(use_D65); ++ } ++ ++ //! Convert pixel values from xyY to RGB color spaces \newinstance. ++ CImg get_xyYtoRGB(const bool use_D65=true) const { ++ return CImg(*this,false).xyYtoRGB(use_D65); ++ } ++ ++ //! Convert pixel values from RGB to CMYK color spaces. ++ CImg& RGBtoCMYK() { ++ return RGBtoCMY().CMYtoCMYK(); ++ } ++ ++ //! Convert pixel values from RGB to CMYK color spaces \newinstance. ++ CImg get_RGBtoCMYK() const { ++ return CImg(*this,false).RGBtoCMYK(); ++ } ++ ++ //! Convert pixel values from CMYK to RGB color spaces. ++ CImg& CMYKtoRGB() { ++ return CMYKtoCMY().CMYtoRGB(); ++ } ++ ++ //! Convert pixel values from CMYK to RGB color spaces \newinstance. ++ CImg get_CMYKtoRGB() const { ++ return CImg(*this,false).CMYKtoRGB(); ++ } ++ ++ //@} ++ //------------------------------------------ ++ // ++ //! \name Geometric / Spatial Manipulation ++ //@{ ++ //------------------------------------------ ++ ++ static float _cimg_lanczos(const float x) { ++ if (x<=-2 || x>=2) return 0; ++ const float a = (float)cimg::PI*x, b = 0.5f*a; ++ return (float)(x?std::sin(a)*std::sin(b)/(a*b):1); ++ } ++ ++ //! Resize image to new dimensions. ++ /** ++ \param size_x Number of columns (new size along the X-axis). ++ \param size_y Number of rows (new size along the Y-axis). ++ \param size_z Number of slices (new size along the Z-axis). ++ \param size_c Number of vector-channels (new size along the C-axis). ++ \param interpolation_type Method of interpolation: ++ - -1 = no interpolation: raw memory resizing. ++ - 0 = no interpolation: additional space is filled according to \p boundary_conditions. ++ - 1 = nearest-neighbor interpolation. ++ - 2 = moving average interpolation. ++ - 3 = linear interpolation. ++ - 4 = grid interpolation. ++ - 5 = cubic interpolation. ++ - 6 = lanczos interpolation. ++ \param boundary_conditions Type of boundary conditions used if necessary. ++ \param centering_x Set centering type (only if \p interpolation_type=0). ++ \param centering_y Set centering type (only if \p interpolation_type=0). ++ \param centering_z Set centering type (only if \p interpolation_type=0). ++ \param centering_c 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 size_x, const int size_y=-100, ++ const int size_z=-100, const int size_c=-100, ++ const int interpolation_type=1, const unsigned int boundary_conditions=0, ++ const float centering_x = 0, const float centering_y = 0, ++ const float centering_z = 0, const float centering_c = 0) { ++ if (!size_x || !size_y || !size_z || !size_c) return assign(); ++ const unsigned int ++ _sx = (unsigned int)(size_x<0?-size_x*width()/100:size_x), ++ _sy = (unsigned int)(size_y<0?-size_y*height()/100:size_y), ++ _sz = (unsigned int)(size_z<0?-size_z*depth()/100:size_z), ++ _sc = (unsigned int)(size_c<0?-size_c*spectrum()/100:size_c), ++ sx = _sx?_sx:1, sy = _sy?_sy:1, sz = _sz?_sz:1, sc = _sc?_sc:1; ++ if (sx==_width && sy==_height && sz==_depth && sc==_spectrum) return *this; ++ if (is_empty()) return assign(sx,sy,sz,sc,(T)0); ++ if (interpolation_type==-1 && sx*sy*sz*sc==size()) { ++ _width = sx; _height = sy; _depth = sz; _spectrum = sc; ++ return *this; ++ } ++ return get_resize(sx,sy,sz,sc,interpolation_type,boundary_conditions, ++ centering_x,centering_y,centering_z,centering_c).move_to(*this); ++ } ++ ++ //! Resize image to new dimensions \newinstance. ++ CImg get_resize(const int size_x, const int size_y = -100, ++ const int size_z = -100, const int size_c = -100, ++ const int interpolation_type=1, const unsigned int boundary_conditions=0, ++ const float centering_x = 0, const float centering_y = 0, ++ const float centering_z = 0, const float centering_c = 0) const { ++ if (centering_x<0 || centering_x>1 || centering_y<0 || centering_y>1 || ++ centering_z<0 || centering_z>1 || centering_c<0 || centering_c>1) ++ throw CImgArgumentException(_cimg_instance ++ "resize(): Specified centering arguments (%g,%g,%g,%g) are outside range [0,1].", ++ cimg_instance, ++ centering_x,centering_y,centering_z,centering_c); ++ ++ if (!size_x || !size_y || !size_z || !size_c) return CImg(); ++ const unsigned int ++ sx = std::max(1U,(unsigned int)(size_x>=0?size_x:-size_x*width()/100)), ++ sy = std::max(1U,(unsigned int)(size_y>=0?size_y:-size_y*height()/100)), ++ sz = std::max(1U,(unsigned int)(size_z>=0?size_z:-size_z*depth()/100)), ++ sc = std::max(1U,(unsigned int)(size_c>=0?size_c:-size_c*spectrum()/100)); ++ if (sx==_width && sy==_height && sz==_depth && sc==_spectrum) return +*this; ++ if (is_empty()) return CImg(sx,sy,sz,sc,(T)0); ++ CImg res; ++ switch (interpolation_type) { ++ ++ // Raw resizing. ++ // ++ case -1 : ++ std::memcpy(res.assign(sx,sy,sz,sc,(T)0)._data,_data,sizeof(T)*std::min(size(),(ulongT)sx*sy*sz*sc)); ++ break; ++ ++ // No interpolation. ++ // ++ case 0 : { ++ const int ++ xc = (int)(centering_x*((int)sx - width())), ++ yc = (int)(centering_y*((int)sy - height())), ++ zc = (int)(centering_z*((int)sz - depth())), ++ cc = (int)(centering_c*((int)sc - spectrum())); ++ ++ switch (boundary_conditions) { ++ case 3 : { // Mirror ++ res.assign(sx,sy,sz,sc); ++ const int w2 = 2*width(), h2 = 2*height(), d2 = 2*depth(), s2 = 2*spectrum(); ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=65536)) ++ cimg_forXYZC(res,x,y,z,c) { ++ const int ++ mx = cimg::mod(x - xc,w2), my = cimg::mod(y - yc,h2), ++ mz = cimg::mod(z - zc,d2), mc = cimg::mod(c - cc,s2); ++ res(x,y,z,c) = (*this)(mx=65536)) ++ for (int c = c0; c<(int)sc; c+=dc) ++ for (int z = z0; z<(int)sz; z+=dz) ++ for (int y = y0; y<(int)sy; y+=dy) ++ for (int x = x0; x<(int)sx; x+=dx) ++ res.draw_image(x,y,z,c,*this); ++ } break; ++ case 1 : { // Neumann ++ res.assign(sx,sy,sz,sc).draw_image(xc,yc,zc,cc,*this); ++ CImg sprite; ++ if (xc>0) { // X-backward ++ res.get_crop(xc,yc,zc,cc,xc,yc + height() - 1,zc + depth() - 1,cc + spectrum() - 1).move_to(sprite); ++ for (int x = xc - 1; x>=0; --x) res.draw_image(x,yc,zc,cc,sprite); ++ } ++ if (xc + width()<(int)sx) { // X-forward ++ res.get_crop(xc + width() - 1,yc,zc,cc,xc + width() - 1,yc + height() - 1, ++ zc + depth() - 1,cc + spectrum() - 1).move_to(sprite); ++ for (int x = xc + width(); x<(int)sx; ++x) res.draw_image(x,yc,zc,cc,sprite); ++ } ++ if (yc>0) { // Y-backward ++ res.get_crop(0,yc,zc,cc,sx - 1,yc,zc + depth() - 1,cc + spectrum() - 1).move_to(sprite); ++ for (int y = yc - 1; y>=0; --y) res.draw_image(0,y,zc,cc,sprite); ++ } ++ if (yc + height()<(int)sy) { // Y-forward ++ res.get_crop(0,yc + height() - 1,zc,cc,sx - 1,yc + height() - 1, ++ zc + depth() - 1,cc + spectrum() - 1).move_to(sprite); ++ for (int y = yc + height(); y<(int)sy; ++y) res.draw_image(0,y,zc,cc,sprite); ++ } ++ if (zc>0) { // Z-backward ++ res.get_crop(0,0,zc,cc,sx - 1,sy - 1,zc,cc + spectrum() - 1).move_to(sprite); ++ for (int z = zc - 1; z>=0; --z) res.draw_image(0,0,z,cc,sprite); ++ } ++ if (zc + depth()<(int)sz) { // Z-forward ++ res.get_crop(0,0,zc +depth() - 1,cc,sx - 1,sy - 1,zc + depth() - 1,cc + spectrum() - 1).move_to(sprite); ++ for (int z = zc + depth(); z<(int)sz; ++z) res.draw_image(0,0,z,cc,sprite); ++ } ++ if (cc>0) { // C-backward ++ res.get_crop(0,0,0,cc,sx - 1,sy - 1,sz - 1,cc).move_to(sprite); ++ for (int c = cc - 1; c>=0; --c) res.draw_image(0,0,0,c,sprite); ++ } ++ if (cc + spectrum()<(int)sc) { // C-forward ++ res.get_crop(0,0,0,cc + spectrum() - 1,sx - 1,sy - 1,sz - 1,cc + spectrum() - 1).move_to(sprite); ++ for (int c = cc + spectrum(); c<(int)sc; ++c) res.draw_image(0,0,0,c,sprite); ++ } ++ } break; ++ default : // Dirichlet ++ res.assign(sx,sy,sz,sc,(T)0).draw_image(xc,yc,zc,cc,*this); ++ } ++ break; ++ } break; ++ ++ // Nearest neighbor interpolation. ++ // ++ case 1 : { ++ res.assign(sx,sy,sz,sc); ++ CImg off_x(sx), off_y(sy + 1), off_z(sz + 1), off_c(sc + 1); ++ const ulongT ++ wh = (ulongT)_width*_height, ++ whd = (ulongT)_width*_height*_depth, ++ sxy = (ulongT)sx*sy, ++ sxyz = (ulongT)sx*sy*sz; ++ if (sx==_width) off_x.fill(1); ++ else { ++ ulongT *poff_x = off_x._data, curr = 0; ++ cimg_forX(res,x) { ++ const ulongT old = curr; ++ curr = (ulongT)((x + 1.0)*_width/sx); ++ *(poff_x++) = curr - old; ++ } ++ } ++ if (sy==_height) off_y.fill(_width); ++ else { ++ ulongT *poff_y = off_y._data, curr = 0; ++ cimg_forY(res,y) { ++ const ulongT old = curr; ++ curr = (ulongT)((y + 1.0)*_height/sy); ++ *(poff_y++) = _width*(curr - old); ++ } ++ *poff_y = 0; ++ } ++ if (sz==_depth) off_z.fill(wh); ++ else { ++ ulongT *poff_z = off_z._data, curr = 0; ++ cimg_forZ(res,z) { ++ const ulongT old = curr; ++ curr = (ulongT)((z + 1.0)*_depth/sz); ++ *(poff_z++) = wh*(curr - old); ++ } ++ *poff_z = 0; ++ } ++ if (sc==_spectrum) off_c.fill(whd); ++ else { ++ ulongT *poff_c = off_c._data, curr = 0; ++ cimg_forC(res,c) { ++ const ulongT old = curr; ++ curr = (ulongT)((c + 1.0)*_spectrum/sc); ++ *(poff_c++) = whd*(curr - old); ++ } ++ *poff_c = 0; ++ } ++ ++ T *ptrd = res._data; ++ const T* ptrc = _data; ++ const ulongT *poff_c = off_c._data; ++ for (unsigned int c = 0; c tmp(sx,_height,_depth,_spectrum,0); ++ for (unsigned int a = _width*sx, b = _width, c = sx, s = 0, t = 0; a; ) { ++ const unsigned int d = std::min(b,c); ++ a-=d; b-=d; c-=d; ++ cimg_forYZC(tmp,y,z,v) tmp(t,y,z,v)+=(Tfloat)(*this)(s,y,z,v)*d; ++ if (!b) { ++ cimg_forYZC(tmp,y,z,v) tmp(t,y,z,v)/=_width; ++ ++t; ++ b = _width; ++ } ++ if (!c) { ++s; c = sx; } ++ } ++ tmp.move_to(res); ++ instance_first = false; ++ } ++ if (sy!=_height) { ++ CImg tmp(sx,sy,_depth,_spectrum,0); ++ for (unsigned int a = _height*sy, b = _height, c = sy, s = 0, t = 0; a; ) { ++ const unsigned int d = std::min(b,c); ++ a-=d; b-=d; c-=d; ++ if (instance_first) ++ cimg_forXZC(tmp,x,z,v) tmp(x,t,z,v)+=(Tfloat)(*this)(x,s,z,v)*d; ++ else ++ cimg_forXZC(tmp,x,z,v) tmp(x,t,z,v)+=(Tfloat)res(x,s,z,v)*d; ++ if (!b) { ++ cimg_forXZC(tmp,x,z,v) tmp(x,t,z,v)/=_height; ++ ++t; ++ b = _height; ++ } ++ if (!c) { ++s; c = sy; } ++ } ++ tmp.move_to(res); ++ instance_first = false; ++ } ++ if (sz!=_depth) { ++ CImg tmp(sx,sy,sz,_spectrum,0); ++ for (unsigned int a = _depth*sz, b = _depth, c = sz, s = 0, t = 0; a; ) { ++ const unsigned int d = std::min(b,c); ++ a-=d; b-=d; c-=d; ++ if (instance_first) ++ cimg_forXYC(tmp,x,y,v) tmp(x,y,t,v)+=(Tfloat)(*this)(x,y,s,v)*d; ++ else ++ cimg_forXYC(tmp,x,y,v) tmp(x,y,t,v)+=(Tfloat)res(x,y,s,v)*d; ++ if (!b) { ++ cimg_forXYC(tmp,x,y,v) tmp(x,y,t,v)/=_depth; ++ ++t; ++ b = _depth; ++ } ++ if (!c) { ++s; c = sz; } ++ } ++ tmp.move_to(res); ++ instance_first = false; ++ } ++ if (sc!=_spectrum) { ++ CImg tmp(sx,sy,sz,sc,0); ++ for (unsigned int a = _spectrum*sc, b = _spectrum, c = sc, s = 0, t = 0; a; ) { ++ const unsigned int d = std::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)/=_spectrum; ++ ++t; ++ b = _spectrum; ++ } ++ if (!c) { ++s; c = sc; } ++ } ++ tmp.move_to(res); ++ instance_first = false; ++ } ++ } break; ++ ++ // Linear interpolation. ++ // ++ case 3 : { ++ CImg off(cimg::max(sx,sy,sz,sc)); ++ CImg foff(off._width); ++ CImg resx, resy, resz, resc; ++ double curr, old; ++ ++ if (sx!=_width) { ++ if (_width==1) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx); ++ else if (_width>sx) get_resize(sx,_height,_depth,_spectrum,2).move_to(resx); ++ else { ++ const double fx = (!boundary_conditions && sx>_width)?(sx>1?(_width - 1.0)/(sx - 1):0): ++ (double)_width/sx; ++ resx.assign(sx,_height,_depth,_spectrum); ++ curr = old = 0; ++ unsigned int *poff = off._data; ++ double *pfoff = foff._data; ++ cimg_forX(resx,x) { ++ *(pfoff++) = curr - (unsigned int)curr; ++ old = curr; ++ curr = std::min(width() - 1.0,curr + fx); ++ *(poff++) = (unsigned int)curr - (unsigned int)old; ++ } ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resx.size()>=65536)) ++ cimg_forYZC(resx,y,z,c) { ++ const T *ptrs = data(0,y,z,c), *const ptrsmax = ptrs + _width - 1; ++ T *ptrd = resx.data(0,y,z,c); ++ const unsigned int *poff = off._data; ++ const double *pfoff = foff._data; ++ cimg_forX(resx,x) { ++ const double alpha = *(pfoff++); ++ const T val1 = *ptrs, val2 = ptrssy) resx.get_resize(sx,sy,_depth,_spectrum,2).move_to(resy); ++ else { ++ const double fy = (!boundary_conditions && sy>_height)?(sy>1?(_height - 1.0)/(sy - 1):0): ++ (double)_height/sy; ++ resy.assign(sx,sy,_depth,_spectrum); ++ curr = old = 0; ++ unsigned int *poff = off._data; ++ double *pfoff = foff._data; ++ cimg_forY(resy,y) { ++ *(pfoff++) = curr - (unsigned int)curr; ++ old = curr; ++ curr = std::min(height() - 1.0,curr + fy); ++ *(poff++) = sx*((unsigned int)curr - (unsigned int)old); ++ } ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resy.size()>=65536)) ++ cimg_forXZC(resy,x,z,c) { ++ const T *ptrs = resx.data(x,0,z,c), *const ptrsmax = ptrs + (_height - 1)*sx; ++ T *ptrd = resy.data(x,0,z,c); ++ const unsigned int *poff = off._data; ++ const double *pfoff = foff._data; ++ cimg_forY(resy,y) { ++ const double alpha = *(pfoff++); ++ const T val1 = *ptrs, val2 = ptrssz) resy.get_resize(sx,sy,sz,_spectrum,2).move_to(resz); ++ else { ++ const double fz = (!boundary_conditions && sz>_depth)?(sz>1?(_depth - 1.0)/(sz - 1):0): ++ (double)_depth/sz; ++ const unsigned int sxy = sx*sy; ++ resz.assign(sx,sy,sz,_spectrum); ++ curr = old = 0; ++ unsigned int *poff = off._data; ++ double *pfoff = foff._data; ++ cimg_forZ(resz,z) { ++ *(pfoff++) = curr - (unsigned int)curr; ++ old = curr; ++ curr = std::min(depth() - 1.0,curr + fz); ++ *(poff++) = sxy*((unsigned int)curr - (unsigned int)old); ++ } ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resz.size()>=65536)) ++ cimg_forXYC(resz,x,y,c) { ++ const T *ptrs = resy.data(x,y,0,c), *const ptrsmax = ptrs + (_depth - 1)*sxy; ++ T *ptrd = resz.data(x,y,0,c); ++ const unsigned int *poff = off._data; ++ const double *pfoff = foff._data; ++ cimg_forZ(resz,z) { ++ const double alpha = *(pfoff++); ++ const T val1 = *ptrs, val2 = ptrssc) resz.get_resize(sx,sy,sz,sc,2).move_to(resc); ++ else { ++ const double fc = (!boundary_conditions && sc>_spectrum)?(sc>1?(_spectrum - 1.0)/(sc - 1):0): ++ (double)_spectrum/sc; ++ const unsigned int sxyz = sx*sy*sz; ++ resc.assign(sx,sy,sz,sc); ++ curr = old = 0; ++ unsigned int *poff = off._data; ++ double *pfoff = foff._data; ++ cimg_forC(resc,c) { ++ *(pfoff++) = curr - (unsigned int)curr; ++ old = curr; ++ curr = std::min(spectrum() - 1.0,curr + fc); ++ *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old); ++ } ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resc.size()>=65536)) ++ cimg_forXYZ(resc,x,y,z) { ++ const T *ptrs = resz.data(x,y,z,0), *const ptrsmax = ptrs + (_spectrum - 1)*sxyz; ++ T *ptrd = resc.data(x,y,z,0); ++ const unsigned int *poff = off._data; ++ const double *pfoff = foff._data; ++ cimg_forC(resc,c) { ++ const double alpha = *(pfoff++); ++ const T val1 = *ptrs, val2 = ptrs resx, resy, resz, resc; ++ if (sx!=_width) { ++ if (sx<_width) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx); ++ else { ++ resx.assign(sx,_height,_depth,_spectrum,(T)0); ++ const int dx = (int)(2*sx), dy = 2*width(); ++ int err = (int)(dy + centering_x*(sx*dy/width() - dy)), xs = 0; ++ cimg_forX(resx,x) if ((err-=dy)<=0) { ++ cimg_forYZC(resx,y,z,c) resx(x,y,z,c) = (*this)(xs,y,z,c); ++ ++xs; ++ err+=dx; ++ } ++ } ++ } else resx.assign(*this,true); ++ ++ if (sy!=_height) { ++ if (sy<_height) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy); ++ else { ++ resy.assign(sx,sy,_depth,_spectrum,(T)0); ++ const int dx = (int)(2*sy), dy = 2*height(); ++ int err = (int)(dy + centering_y*(sy*dy/height() - dy)), ys = 0; ++ cimg_forY(resy,y) if ((err-=dy)<=0) { ++ cimg_forXZC(resy,x,z,c) resy(x,y,z,c) = resx(x,ys,z,c); ++ ++ys; ++ err+=dx; ++ } ++ } ++ resx.assign(); ++ } else resy.assign(resx,true); ++ ++ if (sz!=_depth) { ++ if (sz<_depth) resy.get_resize(sx,sy,sz,_spectrum,1).move_to(resz); ++ else { ++ resz.assign(sx,sy,sz,_spectrum,(T)0); ++ const int dx = (int)(2*sz), dy = 2*depth(); ++ int err = (int)(dy + centering_z*(sz*dy/depth() - dy)), zs = 0; ++ cimg_forZ(resz,z) if ((err-=dy)<=0) { ++ cimg_forXYC(resz,x,y,c) resz(x,y,z,c) = resy(x,y,zs,c); ++ ++zs; ++ err+=dx; ++ } ++ } ++ resy.assign(); ++ } else resz.assign(resy,true); ++ ++ if (sc!=_spectrum) { ++ if (sc<_spectrum) resz.get_resize(sx,sy,sz,sc,1).move_to(resc); ++ else { ++ resc.assign(sx,sy,sz,sc,(T)0); ++ const int dx = (int)(2*sc), dy = 2*spectrum(); ++ int err = (int)(dy + centering_c*(sc*dy/spectrum() - dy)), cs = 0; ++ cimg_forC(resc,c) if ((err-=dy)<=0) { ++ cimg_forXYZ(resc,x,y,z) resc(x,y,z,c) = resz(x,y,z,cs); ++ ++cs; ++ err+=dx; ++ } ++ } ++ resz.assign(); ++ } else resc.assign(resz,true); ++ ++ return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc; ++ } break; ++ ++ // Cubic interpolation. ++ // ++ case 5 : { ++ const Tfloat vmin = (Tfloat)cimg::type::min(), vmax = (Tfloat)cimg::type::max(); ++ CImg off(cimg::max(sx,sy,sz,sc)); ++ CImg foff(off._width); ++ CImg resx, resy, resz, resc; ++ double curr, old; ++ ++ if (sx!=_width) { ++ if (_width==1) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx); ++ else { ++ if (_width>sx) get_resize(sx,_height,_depth,_spectrum,2).move_to(resx); ++ else { ++ const double fx = (!boundary_conditions && sx>_width)?(sx>1?(_width - 1.0)/(sx - 1):0): ++ (double)_width/sx; ++ resx.assign(sx,_height,_depth,_spectrum); ++ curr = old = 0; ++ unsigned int *poff = off._data; ++ double *pfoff = foff._data; ++ cimg_forX(resx,x) { ++ *(pfoff++) = curr - (unsigned int)curr; ++ old = curr; ++ curr = std::min(width() - 1.0,curr + fx); ++ *(poff++) = (unsigned int)curr - (unsigned int)old; ++ } ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resx.size()>=65536)) ++ cimg_forYZC(resx,y,z,c) { ++ const T *const ptrs0 = data(0,y,z,c), *ptrs = ptrs0, *const ptrsmax = ptrs + (_width - 2); ++ T *ptrd = resx.data(0,y,z,c); ++ const unsigned int *poff = off._data; ++ const double *pfoff = foff._data; ++ cimg_forX(resx,x) { ++ const double ++ t = *(pfoff++), ++ val1 = (double)*ptrs, ++ val0 = ptrs>ptrs0?(double)*(ptrs - 1):val1, ++ val2 = ptrs<=ptrsmax?(double)*(ptrs + 1):val1, ++ val3 = ptrsvmax?vmax:val); ++ ptrs+=*(poff++); ++ } ++ } ++ } ++ } ++ } else resx.assign(*this,true); ++ ++ if (sy!=_height) { ++ if (_height==1) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy); ++ else { ++ if (_height>sy) resx.get_resize(sx,sy,_depth,_spectrum,2).move_to(resy); ++ else { ++ const double fy = (!boundary_conditions && sy>_height)?(sy>1?(_height - 1.0)/(sy - 1):0): ++ (double)_height/sy; ++ resy.assign(sx,sy,_depth,_spectrum); ++ curr = old = 0; ++ unsigned int *poff = off._data; ++ double *pfoff = foff._data; ++ cimg_forY(resy,y) { ++ *(pfoff++) = curr - (unsigned int)curr; ++ old = curr; ++ curr = std::min(height() - 1.0,curr + fy); ++ *(poff++) = sx*((unsigned int)curr - (unsigned int)old); ++ } ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resy.size()>=65536)) ++ cimg_forXZC(resy,x,z,c) { ++ const T *const ptrs0 = resx.data(x,0,z,c), *ptrs = ptrs0, *const ptrsmax = ptrs + (_height - 2)*sx; ++ T *ptrd = resy.data(x,0,z,c); ++ const unsigned int *poff = off._data; ++ const double *pfoff = foff._data; ++ cimg_forY(resy,y) { ++ const double ++ t = *(pfoff++), ++ val1 = (double)*ptrs, ++ val0 = ptrs>ptrs0?(double)*(ptrs - sx):val1, ++ val2 = ptrs<=ptrsmax?(double)*(ptrs + sx):val1, ++ val3 = ptrsvmax?vmax:val); ++ ptrd+=sx; ++ ptrs+=*(poff++); ++ } ++ } ++ } ++ } ++ resx.assign(); ++ } else resy.assign(resx,true); ++ ++ if (sz!=_depth) { ++ if (_depth==1) resy.get_resize(sx,sy,sz,_spectrum,1).move_to(resz); ++ else { ++ if (_depth>sz) resy.get_resize(sx,sy,sz,_spectrum,2).move_to(resz); ++ else { ++ const double fz = (!boundary_conditions && sz>_depth)?(sz>1?(_depth - 1.0)/(sz - 1):0): ++ (double)_depth/sz; ++ const unsigned int sxy = sx*sy; ++ resz.assign(sx,sy,sz,_spectrum); ++ curr = old = 0; ++ unsigned int *poff = off._data; ++ double *pfoff = foff._data; ++ cimg_forZ(resz,z) { ++ *(pfoff++) = curr - (unsigned int)curr; ++ old = curr; ++ curr = std::min(depth() - 1.0,curr + fz); ++ *(poff++) = sxy*((unsigned int)curr - (unsigned int)old); ++ } ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resz.size()>=65536)) ++ cimg_forXYC(resz,x,y,c) { ++ const T *const ptrs0 = resy.data(x,y,0,c), *ptrs = ptrs0, *const ptrsmax = ptrs + (_depth - 2)*sxy; ++ T *ptrd = resz.data(x,y,0,c); ++ const unsigned int *poff = off._data; ++ const double *pfoff = foff._data; ++ cimg_forZ(resz,z) { ++ const double ++ t = *(pfoff++), ++ val1 = (double)*ptrs, ++ val0 = ptrs>ptrs0?(double)*(ptrs - sxy):val1, ++ val2 = ptrs<=ptrsmax?(double)*(ptrs + sxy):val1, ++ val3 = ptrsvmax?vmax:val); ++ ptrd+=sxy; ++ ptrs+=*(poff++); ++ } ++ } ++ } ++ } ++ resy.assign(); ++ } else resz.assign(resy,true); ++ ++ if (sc!=_spectrum) { ++ if (_spectrum==1) resz.get_resize(sx,sy,sz,sc,1).move_to(resc); ++ else { ++ if (_spectrum>sc) resz.get_resize(sx,sy,sz,sc,2).move_to(resc); ++ else { ++ const double fc = (!boundary_conditions && sc>_spectrum)?(sc>1?(_spectrum - 1.0)/(sc - 1):0): ++ (double)_spectrum/sc; ++ const unsigned int sxyz = sx*sy*sz; ++ resc.assign(sx,sy,sz,sc); ++ curr = old = 0; ++ unsigned int *poff = off._data; ++ double *pfoff = foff._data; ++ cimg_forC(resc,c) { ++ *(pfoff++) = curr - (unsigned int)curr; ++ old = curr; ++ curr = std::min(spectrum() - 1.0,curr + fc); ++ *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old); ++ } ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resc.size()>=65536)) ++ cimg_forXYZ(resc,x,y,z) { ++ const T *const ptrs0 = resz.data(x,y,z,0), *ptrs = ptrs0, *const ptrsmax = ptrs + (_spectrum - 2)*sxyz; ++ T *ptrd = resc.data(x,y,z,0); ++ const unsigned int *poff = off._data; ++ const double *pfoff = foff._data; ++ cimg_forC(resc,c) { ++ const double ++ t = *(pfoff++), ++ val1 = (double)*ptrs, ++ val0 = ptrs>ptrs0?(double)*(ptrs - sxyz):val1, ++ val2 = ptrs<=ptrsmax?(double)*(ptrs + sxyz):val1, ++ val3 = ptrsvmax?vmax:val); ++ ptrd+=sxyz; ++ ptrs+=*(poff++); ++ } ++ } ++ } ++ } ++ resz.assign(); ++ } else resc.assign(resz,true); ++ ++ return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc; ++ } break; ++ ++ // Lanczos interpolation. ++ // ++ case 6 : { ++ const double vmin = (double)cimg::type::min(), vmax = (double)cimg::type::max(); ++ CImg off(cimg::max(sx,sy,sz,sc)); ++ CImg foff(off._width); ++ CImg resx, resy, resz, resc; ++ double curr, old; ++ ++ if (sx!=_width) { ++ if (_width==1) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx); ++ else { ++ if (_width>sx) get_resize(sx,_height,_depth,_spectrum,2).move_to(resx); ++ else { ++ const double fx = (!boundary_conditions && sx>_width)?(sx>1?(_width - 1.0)/(sx - 1):0): ++ (double)_width/sx; ++ resx.assign(sx,_height,_depth,_spectrum); ++ curr = old = 0; ++ unsigned int *poff = off._data; ++ double *pfoff = foff._data; ++ cimg_forX(resx,x) { ++ *(pfoff++) = curr - (unsigned int)curr; ++ old = curr; ++ curr = std::min(width() - 1.0,curr + fx); ++ *(poff++) = (unsigned int)curr - (unsigned int)old; ++ } ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resx.size()>=65536)) ++ cimg_forYZC(resx,y,z,c) { ++ const T *const ptrs0 = data(0,y,z,c), *ptrs = ptrs0, *const ptrsmin = ptrs0 + 1, ++ *const ptrsmax = ptrs0 + (_width - 2); ++ T *ptrd = resx.data(0,y,z,c); ++ const unsigned int *poff = off._data; ++ const double *pfoff = foff._data; ++ cimg_forX(resx,x) { ++ const double ++ t = *(pfoff++), ++ w0 = _cimg_lanczos(t + 2), ++ w1 = _cimg_lanczos(t + 1), ++ w2 = _cimg_lanczos(t), ++ w3 = _cimg_lanczos(t - 1), ++ w4 = _cimg_lanczos(t - 2), ++ val2 = (double)*ptrs, ++ val1 = ptrs>=ptrsmin?(double)*(ptrs - 1):val2, ++ val0 = ptrs>ptrsmin?(double)*(ptrs - 2):val1, ++ val3 = ptrs<=ptrsmax?(double)*(ptrs + 1):val2, ++ val4 = ptrsvmax?vmax:val); ++ ptrs+=*(poff++); ++ } ++ } ++ } ++ } ++ } else resx.assign(*this,true); ++ ++ if (sy!=_height) { ++ if (_height==1) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy); ++ else { ++ if (_height>sy) resx.get_resize(sx,sy,_depth,_spectrum,2).move_to(resy); ++ else { ++ const double fy = (!boundary_conditions && sy>_height)?(sy>1?(_height - 1.0)/(sy - 1):0): ++ (double)_height/sy; ++ resy.assign(sx,sy,_depth,_spectrum); ++ curr = old = 0; ++ unsigned int *poff = off._data; ++ double *pfoff = foff._data; ++ cimg_forY(resy,y) { ++ *(pfoff++) = curr - (unsigned int)curr; ++ old = curr; ++ curr = std::min(height() - 1.0,curr + fy); ++ *(poff++) = sx*((unsigned int)curr - (unsigned int)old); ++ } ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resy.size()>=65536)) ++ cimg_forXZC(resy,x,z,c) { ++ const T *const ptrs0 = resx.data(x,0,z,c), *ptrs = ptrs0, *const ptrsmin = ptrs0 + sx, ++ *const ptrsmax = ptrs0 + (_height - 2)*sx; ++ T *ptrd = resy.data(x,0,z,c); ++ const unsigned int *poff = off._data; ++ const double *pfoff = foff._data; ++ cimg_forY(resy,y) { ++ const double ++ t = *(pfoff++), ++ w0 = _cimg_lanczos(t + 2), ++ w1 = _cimg_lanczos(t + 1), ++ w2 = _cimg_lanczos(t), ++ w3 = _cimg_lanczos(t - 1), ++ w4 = _cimg_lanczos(t - 2), ++ val2 = (double)*ptrs, ++ val1 = ptrs>=ptrsmin?(double)*(ptrs - sx):val2, ++ val0 = ptrs>ptrsmin?(double)*(ptrs - 2*sx):val1, ++ val3 = ptrs<=ptrsmax?(double)*(ptrs + sx):val2, ++ val4 = ptrsvmax?vmax:val); ++ ptrd+=sx; ++ ptrs+=*(poff++); ++ } ++ } ++ } ++ } ++ resx.assign(); ++ } else resy.assign(resx,true); ++ ++ if (sz!=_depth) { ++ if (_depth==1) resy.get_resize(sx,sy,sz,_spectrum,1).move_to(resz); ++ else { ++ if (_depth>sz) resy.get_resize(sx,sy,sz,_spectrum,2).move_to(resz); ++ else { ++ const double fz = (!boundary_conditions && sz>_depth)?(sz>1?(_depth - 1.0)/(sz - 1):0): ++ (double)_depth/sz; ++ const unsigned int sxy = sx*sy; ++ resz.assign(sx,sy,sz,_spectrum); ++ curr = old = 0; ++ unsigned int *poff = off._data; ++ double *pfoff = foff._data; ++ cimg_forZ(resz,z) { ++ *(pfoff++) = curr - (unsigned int)curr; ++ old = curr; ++ curr = std::min(depth() - 1.0,curr + fz); ++ *(poff++) = sxy*((unsigned int)curr - (unsigned int)old); ++ } ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resz.size()>=65536)) ++ cimg_forXYC(resz,x,y,c) { ++ const T *const ptrs0 = resy.data(x,y,0,c), *ptrs = ptrs0, *const ptrsmin = ptrs0 + sxy, ++ *const ptrsmax = ptrs0 + (_depth - 2)*sxy; ++ T *ptrd = resz.data(x,y,0,c); ++ const unsigned int *poff = off._data; ++ const double *pfoff = foff._data; ++ cimg_forZ(resz,z) { ++ const double ++ t = *(pfoff++), ++ w0 = _cimg_lanczos(t + 2), ++ w1 = _cimg_lanczos(t + 1), ++ w2 = _cimg_lanczos(t), ++ w3 = _cimg_lanczos(t - 1), ++ w4 = _cimg_lanczos(t - 2), ++ val2 = (double)*ptrs, ++ val1 = ptrs>=ptrsmin?(double)*(ptrs - sxy):val2, ++ val0 = ptrs>ptrsmin?(double)*(ptrs - 2*sxy):val1, ++ val3 = ptrs<=ptrsmax?(double)*(ptrs + sxy):val2, ++ val4 = ptrsvmax?vmax:val); ++ ptrd+=sxy; ++ ptrs+=*(poff++); ++ } ++ } ++ } ++ } ++ resy.assign(); ++ } else resz.assign(resy,true); ++ ++ if (sc!=_spectrum) { ++ if (_spectrum==1) resz.get_resize(sx,sy,sz,sc,1).move_to(resc); ++ else { ++ if (_spectrum>sc) resz.get_resize(sx,sy,sz,sc,2).move_to(resc); ++ else { ++ const double fc = (!boundary_conditions && sc>_spectrum)?(sc>1?(_spectrum - 1.0)/(sc - 1):0): ++ (double)_spectrum/sc; ++ const unsigned int sxyz = sx*sy*sz; ++ resc.assign(sx,sy,sz,sc); ++ curr = old = 0; ++ unsigned int *poff = off._data; ++ double *pfoff = foff._data; ++ cimg_forC(resc,c) { ++ *(pfoff++) = curr - (unsigned int)curr; ++ old = curr; ++ curr = std::min(spectrum() - 1.0,curr + fc); ++ *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old); ++ } ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resc.size()>=65536)) ++ cimg_forXYZ(resc,x,y,z) { ++ const T *const ptrs0 = resz.data(x,y,z,0), *ptrs = ptrs0, *const ptrsmin = ptrs0 + sxyz, ++ *const ptrsmax = ptrs + (_spectrum - 2)*sxyz; ++ T *ptrd = resc.data(x,y,z,0); ++ const unsigned int *poff = off._data; ++ const double *pfoff = foff._data; ++ cimg_forC(resc,c) { ++ const double ++ t = *(pfoff++), ++ w0 = _cimg_lanczos(t + 2), ++ w1 = _cimg_lanczos(t + 1), ++ w2 = _cimg_lanczos(t), ++ w3 = _cimg_lanczos(t - 1), ++ w4 = _cimg_lanczos(t - 2), ++ val2 = (double)*ptrs, ++ val1 = ptrs>=ptrsmin?(double)*(ptrs - sxyz):val2, ++ val0 = ptrs>ptrsmin?(double)*(ptrs - 2*sxyz):val1, ++ val3 = ptrs<=ptrsmax?(double)*(ptrs + sxyz):val2, ++ val4 = ptrsvmax?vmax:val); ++ ptrd+=sxyz; ++ ptrs+=*(poff++); ++ } ++ } ++ } ++ } ++ resz.assign(); ++ } else resc.assign(resz,true); ++ ++ return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc; ++ } break; ++ ++ // Unknow interpolation. ++ // ++ default : ++ throw CImgArgumentException(_cimg_instance ++ "resize(): Invalid specified interpolation %d " ++ "(should be { -1=raw | 0=none | 1=nearest | 2=average | 3=linear | 4=grid | " ++ "5=cubic | 6=lanczos }).", ++ cimg_instance, ++ interpolation_type); ++ } ++ return res; ++ } ++ ++ //! Resize image to dimensions of another image. ++ /** ++ \param src Reference image used for dimensions. ++ \param interpolation_type Interpolation method. ++ \param boundary_conditions Boundary conditions. ++ \param centering_x Set centering type (only if \p interpolation_type=0). ++ \param centering_y Set centering type (only if \p interpolation_type=0). ++ \param centering_z Set centering type (only if \p interpolation_type=0). ++ \param centering_c Set centering type (only if \p interpolation_type=0). ++ **/ ++ template ++ CImg& resize(const CImg& src, ++ const int interpolation_type=1, const unsigned int boundary_conditions=0, ++ const float centering_x = 0, const float centering_y = 0, ++ const float centering_z = 0, const float centering_c = 0) { ++ return resize(src._width,src._height,src._depth,src._spectrum,interpolation_type,boundary_conditions, ++ centering_x,centering_y,centering_z,centering_c); ++ } ++ ++ //! Resize image to dimensions of another image \newinstance. ++ template ++ CImg get_resize(const CImg& src, ++ const int interpolation_type=1, const unsigned int boundary_conditions=0, ++ const float centering_x = 0, const float centering_y = 0, ++ const float centering_z = 0, const float centering_c = 0) const { ++ return get_resize(src._width,src._height,src._depth,src._spectrum,interpolation_type,boundary_conditions, ++ centering_x,centering_y,centering_z,centering_c); ++ } ++ ++ //! Resize image to dimensions of a display window. ++ /** ++ \param disp Reference display window used for dimensions. ++ \param interpolation_type Interpolation method. ++ \param boundary_conditions Boundary conditions. ++ \param centering_x Set centering type (only if \p interpolation_type=0). ++ \param centering_y Set centering type (only if \p interpolation_type=0). ++ \param centering_z Set centering type (only if \p interpolation_type=0). ++ \param centering_c Set centering type (only if \p interpolation_type=0). ++ **/ ++ CImg& resize(const CImgDisplay& disp, ++ const int interpolation_type=1, const unsigned int boundary_conditions=0, ++ const float centering_x = 0, const float centering_y = 0, ++ const float centering_z = 0, const float centering_c = 0) { ++ return resize(disp.width(),disp.height(),_depth,_spectrum,interpolation_type,boundary_conditions, ++ centering_x,centering_y,centering_z,centering_c); ++ } ++ ++ //! Resize image to dimensions of a display window \newinstance. ++ CImg get_resize(const CImgDisplay& disp, ++ const int interpolation_type=1, const unsigned int boundary_conditions=0, ++ const float centering_x = 0, const float centering_y = 0, ++ const float centering_z = 0, const float centering_c = 0) const { ++ return get_resize(disp.width(),disp.height(),_depth,_spectrum,interpolation_type,boundary_conditions, ++ centering_x,centering_y,centering_z,centering_c); ++ } ++ ++ //! Resize image to half-size along XY axes, using an optimized filter. ++ CImg& resize_halfXY() { ++ return get_resize_halfXY().move_to(*this); ++ } ++ ++ //! Resize image to half-size along XY axes, using an optimized filter \newinstance. ++ CImg get_resize_halfXY() const { ++ if (is_empty()) return *this; ++ static const Tfloat kernel[9] = { 0.07842776544f, 0.1231940459f, 0.07842776544f, ++ 0.1231940459f, 0.1935127547f, 0.1231940459f, ++ 0.07842776544f, 0.1231940459f, 0.07842776544f }; ++ CImg I(9), res(_width/2,_height/2,_depth,_spectrum); ++ T *ptrd = res._data; ++ cimg_forZC(*this,z,c) cimg_for3x3(*this,x,y,z,c,I,T) ++ if (x%2 && y%2) *(ptrd++) = (T) ++ (I[0]*kernel[0] + I[1]*kernel[1] + I[2]*kernel[2] + ++ I[3]*kernel[3] + I[4]*kernel[4] + I[5]*kernel[5] + ++ I[6]*kernel[6] + I[7]*kernel[7] + I[8]*kernel[8]); ++ return res; ++ } ++ ++ //! Resize image to double-size, using the Scale2X algorithm. ++ /** ++ \note Use anisotropic upscaling algorithm ++ described here. ++ **/ ++ CImg& resize_doubleXY() { ++ return get_resize_doubleXY().move_to(*this); ++ } ++ ++ //! Resize image to double-size, using the Scale2X algorithm \newinstance. ++ 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,c,I,T) \ ++ _cimg_gs2x_for3((img)._height,y) for (int x = 0, \ ++ _p1##x = 0, \ ++ _n1##x = (int)( \ ++ (I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \ ++ (I[3] = I[4] = (T)(img)(0,y,z,c)), \ ++ (I[7] = (T)(img)(0,_n1##y,z,c)), \ ++ 1>=(img)._width?(img).width() - 1:1); \ ++ (_n1##x<(img).width() && ( \ ++ (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ ++ (I[5] = (T)(img)(_n1##x,y,z,c)), \ ++ (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),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(_width<<1,_height<<1,_depth,_spectrum); ++ CImg_3x3(I,T); ++ cimg_forZC(*this,z,c) { ++ T ++ *ptrd1 = res.data(0,0,z,c), ++ *ptrd2 = ptrd1 + res._width; ++ _cimg_gs2x_for3x3(*this,x,y,z,c,I,T) { ++ 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; ++ } ++ ++ //! Resize image to triple-size, using the Scale3X algorithm. ++ /** ++ \note Use anisotropic upscaling algorithm ++ described here. ++ **/ ++ CImg& resize_tripleXY() { ++ return get_resize_tripleXY().move_to(*this); ++ } ++ ++ //! Resize image to triple-size, using the Scale3X algorithm \newinstance. ++ 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,c,I,T) \ ++ _cimg_gs3x_for3((img)._height,y) for (int x = 0, \ ++ _p1##x = 0, \ ++ _n1##x = (int)( \ ++ (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \ ++ (I[3] = I[4] = (T)(img)(0,y,z,c)), \ ++ (I[6] = I[7] = (T)(img)(0,_n1##y,z,c)), \ ++ 1>=(img)._width?(img).width() - 1:1); \ ++ (_n1##x<(img).width() && ( \ ++ (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \ ++ (I[5] = (T)(img)(_n1##x,y,z,c)), \ ++ (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),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,_spectrum); ++ CImg_3x3(I,T); ++ cimg_forZC(*this,z,c) { ++ T ++ *ptrd1 = res.data(0,0,z,c), ++ *ptrd2 = ptrd1 + res._width, ++ *ptrd3 = ptrd2 + res._width; ++ _cimg_gs3x_for3x3(*this,x,y,z,c,I,T) { ++ 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; ++ } ++ ++ //! Mirror image content along specified axis. ++ /** ++ \param axis Mirror axis ++ **/ ++ CImg& mirror(const char axis) { ++ if (is_empty()) return *this; ++ T *pf, *pb, *buf = 0; ++ switch (cimg::lowercase(axis)) { ++ case 'x' : { ++ pf = _data; pb = data(_width - 1); ++ const unsigned int width2 = _width/2; ++ for (unsigned int yzv = 0; yzv<_height*_depth*_spectrum; ++yzv) { ++ for (unsigned int x = 0; x get_mirror(const char axis) const { ++ return (+*this).mirror(axis); ++ } ++ ++ //! Mirror image content along specified axes. ++ /** ++ \param axes Mirror axes, as a C-string. ++ \note \c axes may contains multiple characters, e.g. \c "xyz" ++ **/ ++ CImg& mirror(const char *const axes) { ++ for (const char *s = axes; *s; ++s) mirror(*s); ++ return *this; ++ } ++ ++ //! Mirror image content along specified axes \newinstance. ++ CImg get_mirror(const char *const axes) const { ++ return (+*this).mirror(axes); ++ } ++ ++ //! Shift image content. ++ /** ++ \param delta_x Amount of displacement along the X-axis. ++ \param delta_y Amount of displacement along the Y-axis. ++ \param delta_z Amount of displacement along the Z-axis. ++ \param delta_c Amount of displacement along the C-axis. ++ \param boundary_conditions Border condition. Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. ++ **/ ++ CImg& shift(const int delta_x, const int delta_y=0, const int delta_z=0, const int delta_c=0, ++ const unsigned int boundary_conditions=0) { ++ if (is_empty()) return *this; ++ if (boundary_conditions==3) ++ return get_crop(-delta_x,-delta_y,-delta_z,-delta_c, ++ width() - delta_x - 1, ++ height() - delta_y - 1, ++ depth() - delta_z - 1, ++ spectrum() - delta_c - 1,3).move_to(*this); ++ if (delta_x) // Shift along X-axis ++ switch (boundary_conditions) { ++ case 2 : { // Periodic ++ const int ml = cimg::mod(-delta_x,width()), ndelta_x = (ml<=width()/2)?ml:(ml-width()); ++ if (!ndelta_x) return *this; ++ CImg buf(cimg::abs(ndelta_x)); ++ if (ndelta_x>0) cimg_forYZC(*this,y,z,c) { ++ std::memcpy(buf,data(0,y,z,c),ndelta_x*sizeof(T)); ++ std::memmove(data(0,y,z,c),data(ndelta_x,y,z,c),(_width-ndelta_x)*sizeof(T)); ++ std::memcpy(data(_width-ndelta_x,y,z,c),buf,ndelta_x*sizeof(T)); ++ } else cimg_forYZC(*this,y,z,c) { ++ std::memcpy(buf,data(_width + ndelta_x,y,z,c),-ndelta_x*sizeof(T)); ++ std::memmove(data(-ndelta_x,y,z,c),data(0,y,z,c),(_width + ndelta_x)*sizeof(T)); ++ std::memcpy(data(0,y,z,c),buf,-ndelta_x*sizeof(T)); ++ } ++ } break; ++ case 1 : // Neumann ++ if (delta_x<0) { ++ const int ndelta_x = (-delta_x>=width())?width() - 1:-delta_x; ++ if (!ndelta_x) return *this; ++ cimg_forYZC(*this,y,z,c) { ++ std::memmove(data(0,y,z,c),data(ndelta_x,y,z,c),(_width-ndelta_x)*sizeof(T)); ++ T *ptrd = data(_width - 1,y,z,c); ++ const T val = *ptrd; ++ for (int l = 0; l=width())?width() - 1:delta_x; ++ if (!ndelta_x) return *this; ++ cimg_forYZC(*this,y,z,c) { ++ std::memmove(data(ndelta_x,y,z,c),data(0,y,z,c),(_width-ndelta_x)*sizeof(T)); ++ T *ptrd = data(0,y,z,c); ++ const T val = *ptrd; ++ for (int l = 0; l=width()) return fill((T)0); ++ if (delta_x<0) cimg_forYZC(*this,y,z,c) { ++ std::memmove(data(0,y,z,c),data(-delta_x,y,z,c),(_width + delta_x)*sizeof(T)); ++ std::memset(data(_width + delta_x,y,z,c),0,-delta_x*sizeof(T)); ++ } else cimg_forYZC(*this,y,z,c) { ++ std::memmove(data(delta_x,y,z,c),data(0,y,z,c),(_width-delta_x)*sizeof(T)); ++ std::memset(data(0,y,z,c),0,delta_x*sizeof(T)); ++ } ++ } ++ ++ if (delta_y) // Shift along Y-axis ++ switch (boundary_conditions) { ++ case 2 : { // Periodic ++ const int ml = cimg::mod(-delta_y,height()), ndelta_y = (ml<=height()/2)?ml:(ml-height()); ++ if (!ndelta_y) return *this; ++ CImg buf(width(),cimg::abs(ndelta_y)); ++ if (ndelta_y>0) cimg_forZC(*this,z,c) { ++ std::memcpy(buf,data(0,0,z,c),_width*ndelta_y*sizeof(T)); ++ std::memmove(data(0,0,z,c),data(0,ndelta_y,z,c),_width*(_height-ndelta_y)*sizeof(T)); ++ std::memcpy(data(0,_height-ndelta_y,z,c),buf,_width*ndelta_y*sizeof(T)); ++ } else cimg_forZC(*this,z,c) { ++ std::memcpy(buf,data(0,_height + ndelta_y,z,c),-ndelta_y*_width*sizeof(T)); ++ std::memmove(data(0,-ndelta_y,z,c),data(0,0,z,c),_width*(_height + ndelta_y)*sizeof(T)); ++ std::memcpy(data(0,0,z,c),buf,-ndelta_y*_width*sizeof(T)); ++ } ++ } break; ++ case 1 : // Neumann ++ if (delta_y<0) { ++ const int ndelta_y = (-delta_y>=height())?height() - 1:-delta_y; ++ if (!ndelta_y) return *this; ++ cimg_forZC(*this,z,c) { ++ std::memmove(data(0,0,z,c),data(0,ndelta_y,z,c),_width*(_height-ndelta_y)*sizeof(T)); ++ T *ptrd = data(0,_height-ndelta_y,z,c), *ptrs = data(0,_height - 1,z,c); ++ for (int l = 0; l=height())?height() - 1:delta_y; ++ if (!ndelta_y) return *this; ++ cimg_forZC(*this,z,c) { ++ std::memmove(data(0,ndelta_y,z,c),data(0,0,z,c),_width*(_height-ndelta_y)*sizeof(T)); ++ T *ptrd = data(0,1,z,c), *ptrs = data(0,0,z,c); ++ for (int l = 0; l=height()) return fill((T)0); ++ if (delta_y<0) cimg_forZC(*this,z,c) { ++ std::memmove(data(0,0,z,c),data(0,-delta_y,z,c),_width*(_height + delta_y)*sizeof(T)); ++ std::memset(data(0,_height + delta_y,z,c),0,-delta_y*_width*sizeof(T)); ++ } else cimg_forZC(*this,z,c) { ++ std::memmove(data(0,delta_y,z,c),data(0,0,z,c),_width*(_height-delta_y)*sizeof(T)); ++ std::memset(data(0,0,z,c),0,delta_y*_width*sizeof(T)); ++ } ++ } ++ ++ if (delta_z) // Shift along Z-axis ++ switch (boundary_conditions) { ++ case 2 : { // Periodic ++ const int ml = cimg::mod(-delta_z,depth()), ndelta_z = (ml<=depth()/2)?ml:(ml-depth()); ++ if (!ndelta_z) return *this; ++ CImg buf(width(),height(),cimg::abs(ndelta_z)); ++ if (ndelta_z>0) cimg_forC(*this,c) { ++ std::memcpy(buf,data(0,0,0,c),_width*_height*ndelta_z*sizeof(T)); ++ std::memmove(data(0,0,0,c),data(0,0,ndelta_z,c),_width*_height*(_depth-ndelta_z)*sizeof(T)); ++ std::memcpy(data(0,0,_depth-ndelta_z,c),buf,_width*_height*ndelta_z*sizeof(T)); ++ } else cimg_forC(*this,c) { ++ std::memcpy(buf,data(0,0,_depth + ndelta_z,c),-ndelta_z*_width*_height*sizeof(T)); ++ std::memmove(data(0,0,-ndelta_z,c),data(0,0,0,c),_width*_height*(_depth + ndelta_z)*sizeof(T)); ++ std::memcpy(data(0,0,0,c),buf,-ndelta_z*_width*_height*sizeof(T)); ++ } ++ } break; ++ case 1 : // Neumann ++ if (delta_z<0) { ++ const int ndelta_z = (-delta_z>=depth())?depth() - 1:-delta_z; ++ if (!ndelta_z) return *this; ++ cimg_forC(*this,c) { ++ std::memmove(data(0,0,0,c),data(0,0,ndelta_z,c),_width*_height*(_depth-ndelta_z)*sizeof(T)); ++ T *ptrd = data(0,0,_depth-ndelta_z,c), *ptrs = data(0,0,_depth - 1,c); ++ for (int l = 0; l=depth())?depth() - 1:delta_z; ++ if (!ndelta_z) return *this; ++ cimg_forC(*this,c) { ++ std::memmove(data(0,0,ndelta_z,c),data(0,0,0,c),_width*_height*(_depth-ndelta_z)*sizeof(T)); ++ T *ptrd = data(0,0,1,c), *ptrs = data(0,0,0,c); ++ for (int l = 0; l=depth()) return fill((T)0); ++ if (delta_z<0) cimg_forC(*this,c) { ++ std::memmove(data(0,0,0,c),data(0,0,-delta_z,c),_width*_height*(_depth + delta_z)*sizeof(T)); ++ std::memset(data(0,0,_depth + delta_z,c),0,_width*_height*(-delta_z)*sizeof(T)); ++ } else cimg_forC(*this,c) { ++ std::memmove(data(0,0,delta_z,c),data(0,0,0,c),_width*_height*(_depth-delta_z)*sizeof(T)); ++ std::memset(data(0,0,0,c),0,delta_z*_width*_height*sizeof(T)); ++ } ++ } ++ ++ if (delta_c) // Shift along C-axis ++ switch (boundary_conditions) { ++ case 2 : { // Periodic ++ const int ml = cimg::mod(-delta_c,spectrum()), ndelta_c = (ml<=spectrum()/2)?ml:(ml-spectrum()); ++ if (!ndelta_c) return *this; ++ CImg buf(width(),height(),depth(),cimg::abs(ndelta_c)); ++ if (ndelta_c>0) { ++ std::memcpy(buf,_data,_width*_height*_depth*ndelta_c*sizeof(T)); ++ std::memmove(_data,data(0,0,0,ndelta_c),_width*_height*_depth*(_spectrum-ndelta_c)*sizeof(T)); ++ std::memcpy(data(0,0,0,_spectrum-ndelta_c),buf,_width*_height*_depth*ndelta_c*sizeof(T)); ++ } else { ++ std::memcpy(buf,data(0,0,0,_spectrum + ndelta_c),-ndelta_c*_width*_height*_depth*sizeof(T)); ++ std::memmove(data(0,0,0,-ndelta_c),_data,_width*_height*_depth*(_spectrum + ndelta_c)*sizeof(T)); ++ std::memcpy(_data,buf,-ndelta_c*_width*_height*_depth*sizeof(T)); ++ } ++ } break; ++ case 1 : // Neumann ++ if (delta_c<0) { ++ const int ndelta_c = (-delta_c>=spectrum())?spectrum() - 1:-delta_c; ++ if (!ndelta_c) return *this; ++ std::memmove(_data,data(0,0,0,ndelta_c),_width*_height*_depth*(_spectrum-ndelta_c)*sizeof(T)); ++ T *ptrd = data(0,0,0,_spectrum-ndelta_c), *ptrs = data(0,0,0,_spectrum - 1); ++ for (int l = 0; l=spectrum())?spectrum() - 1:delta_c; ++ if (!ndelta_c) return *this; ++ std::memmove(data(0,0,0,ndelta_c),_data,_width*_height*_depth*(_spectrum-ndelta_c)*sizeof(T)); ++ T *ptrd = data(0,0,0,1); ++ for (int l = 0; l=spectrum()) return fill((T)0); ++ if (delta_c<0) { ++ std::memmove(_data,data(0,0,0,-delta_c),_width*_height*_depth*(_spectrum + delta_c)*sizeof(T)); ++ std::memset(data(0,0,0,_spectrum + delta_c),0,_width*_height*_depth*(-delta_c)*sizeof(T)); ++ } else { ++ std::memmove(data(0,0,0,delta_c),_data,_width*_height*_depth*(_spectrum-delta_c)*sizeof(T)); ++ std::memset(_data,0,delta_c*_width*_height*_depth*sizeof(T)); ++ } ++ } ++ return *this; ++ } ++ ++ //! Shift image content \newinstance. ++ CImg get_shift(const int delta_x, const int delta_y=0, const int delta_z=0, const int delta_c=0, ++ const unsigned int boundary_conditions=0) const { ++ return (+*this).shift(delta_x,delta_y,delta_z,delta_c,boundary_conditions); ++ } ++ ++ //! Permute axes order. ++ /** ++ \param order Axes permutations, as a C-string of 4 characters. ++ This function permutes image content regarding the specified axes permutation. ++ **/ ++ CImg& permute_axes(const char *const order) { ++ return get_permute_axes(order).move_to(*this); ++ } ++ ++ //! Permute axes order \newinstance. ++ CImg get_permute_axes(const char *const order) const { ++ const T foo = (T)0; ++ return _permute_axes(order,foo); ++ } ++ ++ template ++ CImg _permute_axes(const char *const order, const t&) const { ++ if (is_empty() || !order) return CImg(*this,false); ++ CImg res; ++ const T* ptrs = _data; ++ unsigned char s_code[4] = { 0,1,2,3 }, n_code[4] = { 0 }; ++ for (unsigned int l = 0; order[l]; ++l) { ++ int c = cimg::lowercase(order[l]); ++ if (c!='x' && c!='y' && c!='z' && c!='c') { *s_code = 4; break; } ++ else { ++n_code[c%=4]; s_code[l] = c; } ++ } ++ if (*order && *s_code<4 && *n_code<=1 && n_code[1]<=1 && n_code[2]<=1 && n_code[3]<=1) { ++ const unsigned int code = (s_code[0]<<12) | (s_code[1]<<8) | (s_code[2]<<4) | (s_code[3]); ++ ulongT wh, whd; ++ switch (code) { ++ case 0x0123 : // xyzc ++ return +*this; ++ case 0x0132 : // xycz ++ res.assign(_width,_height,_spectrum,_depth); ++ wh = (ulongT)res._width*res._height; whd = wh*res._depth; ++ cimg_forXYZC(*this,x,y,z,c) res(x,y,c,z,wh,whd) = (t)*(ptrs++); ++ break; ++ case 0x0213 : // xzyc ++ res.assign(_width,_depth,_height,_spectrum); ++ wh = (ulongT)res._width*res._height; whd = wh*res._depth; ++ cimg_forXYZC(*this,x,y,z,c) res(x,z,y,c,wh,whd) = (t)*(ptrs++); ++ break; ++ case 0x0231 : // xzcy ++ res.assign(_width,_depth,_spectrum,_height); ++ wh = (ulongT)res._width*res._height; whd = wh*res._depth; ++ cimg_forXYZC(*this,x,y,z,c) res(x,z,c,y,wh,whd) = (t)*(ptrs++); ++ break; ++ case 0x0312 : // xcyz ++ res.assign(_width,_spectrum,_height,_depth); ++ wh = (ulongT)res._width*res._height; whd = wh*res._depth; ++ cimg_forXYZC(*this,x,y,z,c) res(x,c,y,z,wh,whd) = (t)*(ptrs++); ++ break; ++ case 0x0321 : // xczy ++ res.assign(_width,_spectrum,_depth,_height); ++ wh = (ulongT)res._width*res._height; whd = wh*res._depth; ++ cimg_forXYZC(*this,x,y,z,c) res(x,c,z,y,wh,whd) = (t)*(ptrs++); ++ break; ++ case 0x1023 : // yxzc ++ res.assign(_height,_width,_depth,_spectrum); ++ wh = (ulongT)res._width*res._height; whd = wh*res._depth; ++ cimg_forXYZC(*this,x,y,z,c) res(y,x,z,c,wh,whd) = (t)*(ptrs++); ++ break; ++ case 0x1032 : // yxcz ++ res.assign(_height,_width,_spectrum,_depth); ++ wh = (ulongT)res._width*res._height; whd = wh*res._depth; ++ cimg_forXYZC(*this,x,y,z,c) res(y,x,c,z,wh,whd) = (t)*(ptrs++); ++ break; ++ case 0x1203 : // yzxc ++ res.assign(_height,_depth,_width,_spectrum); ++ wh = (ulongT)res._width*res._height; whd = wh*res._depth; ++ cimg_forXYZC(*this,x,y,z,c) res(y,z,x,c,wh,whd) = (t)*(ptrs++); ++ break; ++ case 0x1230 : // yzcx ++ res.assign(_height,_depth,_spectrum,_width); ++ switch (_width) { ++ case 1 : { ++ t *ptr_r = res.data(0,0,0,0); ++ for (unsigned int siz = _height*_depth*_spectrum; siz; --siz) { ++ *(ptr_r++) = (t)*(ptrs++); ++ } ++ } break; ++ case 2 : { ++ t *ptr_r = res.data(0,0,0,0), *ptr_g = res.data(0,0,0,1); ++ for (unsigned int siz = _height*_depth*_spectrum; siz; --siz) { ++ *(ptr_r++) = (t)ptrs[0]; ++ *(ptr_g++) = (t)ptrs[1]; ++ ptrs+=2; ++ } ++ } break; ++ case 3 : { // Optimization for the classical conversion from interleaved RGB to planar RGB ++ t *ptr_r = res.data(0,0,0,0), *ptr_g = res.data(0,0,0,1), *ptr_b = res.data(0,0,0,2); ++ for (unsigned int siz = _height*_depth*_spectrum; siz; --siz) { ++ *(ptr_r++) = (t)ptrs[0]; ++ *(ptr_g++) = (t)ptrs[1]; ++ *(ptr_b++) = (t)ptrs[2]; ++ ptrs+=3; ++ } ++ } break; ++ case 4 : { // Optimization for the classical conversion from interleaved RGBA to planar RGBA ++ t ++ *ptr_r = res.data(0,0,0,0), *ptr_g = res.data(0,0,0,1), ++ *ptr_b = res.data(0,0,0,2), *ptr_a = res.data(0,0,0,3); ++ for (unsigned int siz = _height*_depth*_spectrum; siz; --siz) { ++ *(ptr_r++) = (t)ptrs[0]; ++ *(ptr_g++) = (t)ptrs[1]; ++ *(ptr_b++) = (t)ptrs[2]; ++ *(ptr_a++) = (t)ptrs[3]; ++ ptrs+=4; ++ } ++ } break; ++ default : { ++ wh = (ulongT)res._width*res._height; whd = wh*res._depth; ++ cimg_forXYZC(*this,x,y,z,c) res(y,z,c,x,wh,whd) = *(ptrs++); ++ return res; ++ } ++ } ++ break; ++ case 0x1302 : // ycxz ++ res.assign(_height,_spectrum,_width,_depth); ++ wh = (ulongT)res._width*res._height; whd = wh*res._depth; ++ cimg_forXYZC(*this,x,y,z,c) res(y,c,x,z,wh,whd) = (t)*(ptrs++); ++ break; ++ case 0x1320 : // yczx ++ res.assign(_height,_spectrum,_depth,_width); ++ wh = (ulongT)res._width*res._height; whd = wh*res._depth; ++ cimg_forXYZC(*this,x,y,z,c) res(y,c,z,x,wh,whd) = (t)*(ptrs++); ++ break; ++ case 0x2013 : // zxyc ++ res.assign(_depth,_width,_height,_spectrum); ++ wh = (ulongT)res._width*res._height; whd = wh*res._depth; ++ cimg_forXYZC(*this,x,y,z,c) res(z,x,y,c,wh,whd) = (t)*(ptrs++); ++ break; ++ case 0x2031 : // zxcy ++ res.assign(_depth,_width,_spectrum,_height); ++ wh = (ulongT)res._width*res._height; whd = wh*res._depth; ++ cimg_forXYZC(*this,x,y,z,c) res(z,x,c,y,wh,whd) = (t)*(ptrs++); ++ break; ++ case 0x2103 : // zyxc ++ res.assign(_depth,_height,_width,_spectrum); ++ wh = (ulongT)res._width*res._height; whd = wh*res._depth; ++ cimg_forXYZC(*this,x,y,z,c) res(z,y,x,c,wh,whd) = (t)*(ptrs++); ++ break; ++ case 0x2130 : // zycx ++ res.assign(_depth,_height,_spectrum,_width); ++ wh = (ulongT)res._width*res._height; whd = wh*res._depth; ++ cimg_forXYZC(*this,x,y,z,c) res(z,y,c,x,wh,whd) = (t)*(ptrs++); ++ break; ++ case 0x2301 : // zcxy ++ res.assign(_depth,_spectrum,_width,_height); ++ wh = (ulongT)res._width*res._height; whd = wh*res._depth; ++ cimg_forXYZC(*this,x,y,z,c) res(z,c,x,y,wh,whd) = (t)*(ptrs++); ++ break; ++ case 0x2310 : // zcyx ++ res.assign(_depth,_spectrum,_height,_width); ++ wh = (ulongT)res._width*res._height; whd = wh*res._depth; ++ cimg_forXYZC(*this,x,y,z,c) res(z,c,y,x,wh,whd) = (t)*(ptrs++); ++ break; ++ case 0x3012 : // cxyz ++ res.assign(_spectrum,_width,_height,_depth); ++ switch (_spectrum) { ++ case 1 : { ++ const T *ptr_r = data(0,0,0,0); ++ t *ptrd = res._data; ++ for (ulongT siz = (ulongT)_width*_height*_depth; siz; --siz) *(ptrd++) = (t)*(ptr_r++); ++ } break; ++ case 2 : { ++ const T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1); ++ t *ptrd = res._data; ++ for (ulongT siz = (ulongT)_width*_height*_depth; siz; --siz) { ++ ptrd[0] = (t)*(ptr_r++); ++ ptrd[1] = (t)*(ptr_g++); ++ ptrd+=2; ++ } ++ } break; ++ case 3 : { // Optimization for the classical conversion from planar RGB to interleaved RGB ++ const T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2); ++ t *ptrd = res._data; ++ for (ulongT siz = (ulongT)_width*_height*_depth; siz; --siz) { ++ ptrd[0] = (t)*(ptr_r++); ++ ptrd[1] = (t)*(ptr_g++); ++ ptrd[2] = (t)*(ptr_b++); ++ ptrd+=3; ++ } ++ } break; ++ case 4 : { // Optimization for the classical conversion from planar RGBA to interleaved RGBA ++ const T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2), *ptr_a = data(0,0,0,3); ++ t *ptrd = res._data; ++ for (ulongT siz = (ulongT)_width*_height*_depth; siz; --siz) { ++ ptrd[0] = (t)*(ptr_r++); ++ ptrd[1] = (t)*(ptr_g++); ++ ptrd[2] = (t)*(ptr_b++); ++ ptrd[3] = (t)*(ptr_a++); ++ ptrd+=4; ++ } ++ } break; ++ default : { ++ wh = (ulongT)res._width*res._height; whd = wh*res._depth; ++ cimg_forXYZC(*this,x,y,z,c) res(c,x,y,z,wh,whd) = (t)*(ptrs++); ++ } ++ } ++ break; ++ case 0x3021 : // cxzy ++ res.assign(_spectrum,_width,_depth,_height); ++ wh = (ulongT)res._width*res._height; whd = wh*res._depth; ++ cimg_forXYZC(*this,x,y,z,c) res(c,x,z,y,wh,whd) = (t)*(ptrs++); ++ break; ++ case 0x3102 : // cyxz ++ res.assign(_spectrum,_height,_width,_depth); ++ wh = (ulongT)res._width*res._height; whd = wh*res._depth; ++ cimg_forXYZC(*this,x,y,z,c) res(c,y,x,z,wh,whd) = (t)*(ptrs++); ++ break; ++ case 0x3120 : // cyzx ++ res.assign(_spectrum,_height,_depth,_width); ++ wh = (ulongT)res._width*res._height; whd = wh*res._depth; ++ cimg_forXYZC(*this,x,y,z,c) res(c,y,z,x,wh,whd) = (t)*(ptrs++); ++ break; ++ case 0x3201 : // czxy ++ res.assign(_spectrum,_depth,_width,_height); ++ wh = (ulongT)res._width*res._height; whd = wh*res._depth; ++ cimg_forXYZC(*this,x,y,z,c) res(c,z,x,y,wh,whd) = (t)*(ptrs++); ++ break; ++ case 0x3210 : // czyx ++ res.assign(_spectrum,_depth,_height,_width); ++ wh = (ulongT)res._width*res._height; whd = wh*res._depth; ++ cimg_forXYZC(*this,x,y,z,c) res(c,z,y,x,wh,whd) = (t)*(ptrs++); ++ break; ++ } ++ } ++ if (!res) ++ throw CImgArgumentException(_cimg_instance ++ "permute_axes(): Invalid specified permutation '%s'.", ++ cimg_instance, ++ order); ++ return res; ++ } ++ ++ //! Unroll pixel values along specified axis. ++ /** ++ \param axis Unroll axis (can be \c 'x', \c 'y', \c 'z' or c 'c'). ++ **/ ++ CImg& unroll(const char axis) { ++ const unsigned int siz = (unsigned int)size(); ++ if (siz) switch (cimg::lowercase(axis)) { ++ case 'x' : _width = siz; _height = _depth = _spectrum = 1; break; ++ case 'y' : _height = siz; _width = _depth = _spectrum = 1; break; ++ case 'z' : _depth = siz; _width = _height = _spectrum = 1; break; ++ default : _spectrum = siz; _width = _height = _depth = 1; ++ } ++ return *this; ++ } ++ ++ //! Unroll pixel values along specified axis \newinstance. ++ CImg get_unroll(const char axis) const { ++ return (+*this).unroll(axis); ++ } ++ ++ //! Rotate image with arbitrary angle. ++ /** ++ \param angle Rotation angle, in degrees. ++ \param interpolation Type of interpolation. Can be { 0=nearest | 1=linear | 2=cubic }. ++ \param boundary_conditions Boundary conditions. ++ Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. ++ \note The size of the image is modified. ++ **/ ++ CImg& rotate(const float angle, const unsigned int interpolation=1, ++ const unsigned int boundary_conditions=0) { ++ const float nangle = cimg::mod(angle,360.0f); ++ if (nangle==0.0f) return *this; ++ return get_rotate(nangle,interpolation,boundary_conditions).move_to(*this); ++ } ++ ++ //! Rotate image with arbitrary angle \newinstance. ++ CImg get_rotate(const float angle, const unsigned int interpolation=1, ++ const unsigned int boundary_conditions=0) const { ++ if (is_empty()) return *this; ++ CImg res; ++ const float nangle = cimg::mod(angle,360.0f); ++ if (boundary_conditions!=1 && cimg::mod(nangle,90.0f)==0) { // Optimized version for orthogonal angles. ++ const int wm1 = width() - 1, hm1 = height() - 1; ++ const int iangle = (int)nangle/90; ++ switch (iangle) { ++ case 1 : { // 90 deg ++ res.assign(_height,_width,_depth,_spectrum); ++ T *ptrd = res._data; ++ cimg_forXYZC(res,x,y,z,c) *(ptrd++) = (*this)(y,hm1 - x,z,c); ++ } break; ++ case 2 : { // 180 deg ++ res.assign(_width,_height,_depth,_spectrum); ++ T *ptrd = res._data; ++ cimg_forXYZC(res,x,y,z,c) *(ptrd++) = (*this)(wm1 - x,hm1 - y,z,c); ++ } break; ++ case 3 : { // 270 deg ++ res.assign(_height,_width,_depth,_spectrum); ++ T *ptrd = res._data; ++ cimg_forXYZC(res,x,y,z,c) *(ptrd++) = (*this)(wm1 - y,x,z,c); ++ } break; ++ default : // 0 deg ++ return *this; ++ } ++ } else { // Generic angle ++ const float ++ rad = (float)(nangle*cimg::PI/180.0), ++ ca = (float)std::cos(rad), sa = (float)std::sin(rad), ++ ux = cimg::abs((_width - 1)*ca), uy = cimg::abs((_width - 1)*sa), ++ vx = cimg::abs((_height - 1)*sa), vy = cimg::abs((_height - 1)*ca), ++ w2 = 0.5f*(_width - 1), h2 = 0.5f*(_height - 1); ++ res.assign((int)cimg::round(1 + ux + vx),(int)cimg::round(1 + uy + vy),_depth,_spectrum); ++ const float rw2 = 0.5f*(res._width - 1), rh2 = 0.5f*(res._height - 1); ++ _rotate(res,nangle,interpolation,boundary_conditions,w2,h2,rw2,rh2); ++ } ++ return res; ++ } ++ ++ //! Rotate image with arbitrary angle, around a center point. ++ /** ++ \param angle Rotation angle, in degrees. ++ \param cx X-coordinate of the rotation center. ++ \param cy Y-coordinate of the rotation center. ++ \param interpolation Type of interpolation, { 0=nearest | 1=linear | 2=cubic | 3=mirror }. ++ \param boundary_conditions Boundary conditions, { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. ++ **/ ++ CImg& rotate(const float angle, const float cx, const float cy, ++ const unsigned int interpolation, const unsigned int boundary_conditions=0) { ++ return get_rotate(angle,cx,cy,interpolation,boundary_conditions).move_to(*this); ++ } ++ ++ //! Rotate image with arbitrary angle, around a center point \newinstance. ++ CImg get_rotate(const float angle, const float cx, const float cy, ++ const unsigned int interpolation, const unsigned int boundary_conditions=0) const { ++ if (is_empty()) return *this; ++ CImg res(_width,_height,_depth,_spectrum); ++ _rotate(res,angle,interpolation,boundary_conditions,cx,cy,cx,cy); ++ return res; ++ } ++ ++ // [internal] Perform 2d rotation with arbitrary angle. ++ void _rotate(CImg& res, const float angle, ++ const unsigned int interpolation, const unsigned int boundary_conditions, ++ const float w2, const float h2, ++ const float rw2, const float rh2) const { ++ const float ++ rad = (float)(angle*cimg::PI/180.0), ++ ca = (float)std::cos(rad), sa = (float)std::sin(rad); ++ ++ switch (boundary_conditions) { ++ case 3 : { // Mirror ++ ++ switch (interpolation) { ++ case 2 : { // Cubic interpolation ++ const float ww = 2.0f*width(), hh = 2.0f*height(); ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048)) ++ cimg_forXYZC(res,x,y,z,c) { ++ const float xc = x - rw2, yc = y - rh2, ++ mx = cimg::mod(w2 + xc*ca + yc*sa,ww), ++ my = cimg::mod(h2 - xc*sa + yc*ca,hh); ++ res(x,y,z,c) = _cubic_cut_atXY(mx=2048)) ++ cimg_forXYZC(res,x,y,z,c) { ++ const float xc = x - rw2, yc = y - rh2, ++ mx = cimg::mod(w2 + xc*ca + yc*sa,ww), ++ my = cimg::mod(h2 - xc*sa + yc*ca,hh); ++ res(x,y,z,c) = (T)_linear_atXY(mx=2048)) ++ cimg_forXYZC(res,x,y,z,c) { ++ const float xc = x - rw2, yc = y - rh2, ++ mx = cimg::mod((int)cimg::round(w2 + xc*ca + yc*sa),ww), ++ my = cimg::mod((int)cimg::round(h2 - xc*sa + yc*ca),hh); ++ res(x,y,z,c) = (*this)(mx=2048)) ++ cimg_forXYZC(res,x,y,z,c) { ++ const float xc = x - rw2, yc = y - rh2; ++ res(x,y,z,c) = _cubic_cut_atXY(cimg::mod(w2 + xc*ca + yc*sa,(float)width()), ++ cimg::mod(h2 - xc*sa + yc*ca,(float)height()),z,c); ++ } ++ } break; ++ case 1 : { // Linear interpolation ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048)) ++ cimg_forXYZC(res,x,y,z,c) { ++ const float xc = x - rw2, yc = y - rh2; ++ res(x,y,z,c) = (T)_linear_atXY(cimg::mod(w2 + xc*ca + yc*sa,(float)width()), ++ cimg::mod(h2 - xc*sa + yc*ca,(float)height()),z,c); ++ } ++ } break; ++ default : { // Nearest-neighbor interpolation ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048)) ++ cimg_forXYZC(res,x,y,z,c) { ++ const float xc = x - rw2, yc = y - rh2; ++ res(x,y,z,c) = (*this)(cimg::mod((int)cimg::round(w2 + xc*ca + yc*sa),(float)width()), ++ cimg::mod((int)cimg::round(h2 - xc*sa + yc*ca),(float)height()),z,c); ++ } ++ } ++ } break; ++ ++ case 1 : // Neumann ++ switch (interpolation) { ++ case 2 : { // Cubic interpolation ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048)) ++ cimg_forXYZC(res,x,y,z,c) { ++ const float xc = x - rw2, yc = y - rh2; ++ res(x,y,z,c) = _cubic_cut_atXY(w2 + xc*ca + yc*sa,h2 - xc*sa + yc*ca,z,c); ++ } ++ } break; ++ case 1 : { // Linear interpolation ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048)) ++ cimg_forXYZC(res,x,y,z,c) { ++ const float xc = x - rw2, yc = y - rh2; ++ res(x,y,z,c) = (T)_linear_atXY(w2 + xc*ca + yc*sa,h2 - xc*sa + yc*ca,z,c); ++ } ++ } break; ++ default : { // Nearest-neighbor interpolation ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048)) ++ cimg_forXYZC(res,x,y,z,c) { ++ const float xc = x - rw2, yc = y - rh2; ++ res(x,y,z,c) = _atXY((int)cimg::round(w2 + xc*ca + yc*sa), ++ (int)cimg::round(h2 - xc*sa + yc*ca),z,c); ++ } ++ } ++ } break; ++ ++ default : // Dirichlet ++ switch (interpolation) { ++ case 2 : { // Cubic interpolation ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048)) ++ cimg_forXYZC(res,x,y,z,c) { ++ const float xc = x - rw2, yc = y - rh2; ++ res(x,y,z,c) = cubic_cut_atXY(w2 + xc*ca + yc*sa,h2 - xc*sa + yc*ca,z,c,(T)0); ++ } ++ } break; ++ case 1 : { // Linear interpolation ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048)) ++ cimg_forXYZC(res,x,y,z,c) { ++ const float xc = x - rw2, yc = y - rh2; ++ res(x,y,z,c) = (T)linear_atXY(w2 + xc*ca + yc*sa,h2 - xc*sa + yc*ca,z,c,(T)0); ++ } ++ } break; ++ default : { // Nearest-neighbor interpolation ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048)) ++ cimg_forXYZC(res,x,y,z,c) { ++ const float xc = x - rw2, yc = y - rh2; ++ res(x,y,z,c) = atXY((int)cimg::round(w2 + xc*ca + yc*sa), ++ (int)cimg::round(h2 - xc*sa + yc*ca),z,c,(T)0); ++ } ++ } ++ } ++ } ++ } ++ ++ //! Rotate volumetric image with arbitrary angle and axis. ++ /** ++ \param u X-coordinate of the 3d rotation axis. ++ \param v Y-coordinate of the 3d rotation axis. ++ \param w Z-coordinate of the 3d rotation axis. ++ \param angle Rotation angle, in degrees. ++ \param interpolation Type of interpolation. Can be { 0=nearest | 1=linear | 2=cubic }. ++ \param boundary_conditions Boundary conditions. ++ Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. ++ \note Most of the time, size of the image is modified. ++ **/ ++ CImg rotate(const float u, const float v, const float w, const float angle, ++ const unsigned int interpolation, const unsigned int boundary_conditions) { ++ const float nangle = cimg::mod(angle,360.0f); ++ if (nangle==0.0f) return *this; ++ return get_rotate(u,v,w,nangle,interpolation,boundary_conditions).move_to(*this); ++ } ++ ++ //! Rotate volumetric image with arbitrary angle and axis \newinstance. ++ CImg get_rotate(const float u, const float v, const float w, const float angle, ++ const unsigned int interpolation, const unsigned int boundary_conditions) const { ++ if (is_empty()) return *this; ++ CImg res; ++ const float ++ w1 = _width - 1, h1 = _height - 1, d1 = _depth -1, ++ w2 = 0.5f*w1, h2 = 0.5f*h1, d2 = 0.5f*d1; ++ CImg R = CImg::rotation_matrix(u,v,w,angle); ++ const CImg ++ X = R*CImg(8,3,1,1, ++ 0.0f,w1,w1,0.0f,0.0f,w1,w1,0.0f, ++ 0.0f,0.0f,h1,h1,0.0f,0.0f,h1,h1, ++ 0.0f,0.0f,0.0f,0.0f,d1,d1,d1,d1); ++ float ++ xm, xM = X.get_shared_row(0).max_min(xm), ++ ym, yM = X.get_shared_row(1).max_min(ym), ++ zm, zM = X.get_shared_row(2).max_min(zm); ++ const int ++ dx = (int)cimg::round(xM - xm), ++ dy = (int)cimg::round(yM - ym), ++ dz = (int)cimg::round(zM - zm); ++ R.transpose(); ++ res.assign(1 + dx,1 + dy,1 + dz,_spectrum); ++ const float rw2 = 0.5f*dx, rh2 = 0.5f*dy, rd2 = 0.5f*dz; ++ _rotate(res,R,interpolation,boundary_conditions,w2,h2,d2,rw2,rh2,rd2); ++ return res; ++ } ++ ++ //! Rotate volumetric image with arbitrary angle and axis, around a center point. ++ /** ++ \param u X-coordinate of the 3d rotation axis. ++ \param v Y-coordinate of the 3d rotation axis. ++ \param w Z-coordinate of the 3d rotation axis. ++ \param angle Rotation angle, in degrees. ++ \param cx X-coordinate of the rotation center. ++ \param cy Y-coordinate of the rotation center. ++ \param cz Z-coordinate of the rotation center. ++ \param interpolation Type of interpolation. Can be { 0=nearest | 1=linear | 2=cubic | 3=mirror }. ++ \param boundary_conditions Boundary conditions. Can be { 0=dirichlet | 1=neumann | 2=periodic }. ++ \note Most of the time, size of the image is modified. ++ **/ ++ CImg rotate(const float u, const float v, const float w, const float angle, ++ const float cx, const float cy, const float cz, ++ const unsigned int interpolation=1, const unsigned int boundary_conditions=0) { ++ const float nangle = cimg::mod(angle,360.0f); ++ if (nangle==0.0f) return *this; ++ return get_rotate(u,v,w,nangle,cx,cy,cz,interpolation,boundary_conditions).move_to(*this); ++ } ++ ++ //! Rotate volumetric image with arbitrary angle and axis, around a center point \newinstance. ++ CImg get_rotate(const float u, const float v, const float w, const float angle, ++ const float cx, const float cy, const float cz, ++ const unsigned int interpolation=1, const unsigned int boundary_conditions=0) const { ++ if (is_empty()) return *this; ++ CImg res(_width,_height,_depth,_spectrum); ++ CImg R = CImg::rotation_matrix(u,v,w,-angle); ++ _rotate(res,R,interpolation,boundary_conditions,cx,cy,cz,cx,cy,cz); ++ return res; ++ } ++ ++ // [internal] Perform 3d rotation with arbitrary axis and angle. ++ void _rotate(CImg& res, const CImg& R, ++ const unsigned int interpolation, const unsigned int boundary_conditions, ++ const float w2, const float h2, const float d2, ++ const float rw2, const float rh2, const float rd2) const { ++ switch (boundary_conditions) { ++ case 3 : // Mirror ++ switch (interpolation) { ++ case 2 : { // Cubic interpolation ++ const float ww = 2.0f*width(), hh = 2.0f*height(), dd = 2.0f*depth(); ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048)) ++ cimg_forXYZ(res,x,y,z) { ++ const float ++ xc = x - rw2, yc = y - rh2, zc = z - rd2, ++ X = cimg::mod((float)(w2 + R(0,0)*xc + R(1,0)*yc + R(2,0)*zc),ww), ++ Y = cimg::mod((float)(h2 + R(0,1)*xc + R(1,1)*yc + R(2,1)*zc),hh), ++ Z = cimg::mod((float)(d2 + R(0,2)*xc + R(1,2)*yc + R(2,2)*zc),dd); ++ cimg_forC(res,c) res(x,y,z,c) = _cubic_cut_atXYZ(X=2048)) ++ cimg_forXYZ(res,x,y,z) { ++ const float ++ xc = x - rw2, yc = y - rh2, zc = z - rd2, ++ X = cimg::mod((float)(w2 + R(0,0)*xc + R(1,0)*yc + R(2,0)*zc),ww), ++ Y = cimg::mod((float)(h2 + R(0,1)*xc + R(1,1)*yc + R(2,1)*zc),hh), ++ Z = cimg::mod((float)(d2 + R(0,2)*xc + R(1,2)*yc + R(2,2)*zc),dd); ++ cimg_forC(res,c) res(x,y,z,c) = (T)_linear_atXYZ(X=2048)) ++ cimg_forXYZ(res,x,y,z) { ++ const float xc = x - rw2, yc = y - rh2, zc = z - rd2; ++ const int ++ X = cimg::mod((int)cimg::round(w2 + R(0,0)*xc + R(1,0)*yc + R(2,0)*zc),ww), ++ Y = cimg::mod((int)cimg::round(h2 + R(0,1)*xc + R(1,1)*yc + R(2,1)*zc),hh), ++ Z = cimg::mod((int)cimg::round(d2 + R(0,2)*xc + R(1,2)*yc + R(2,2)*zc),dd); ++ cimg_forC(res,c) res(x,y,z,c) = (*this)(X=2048)) ++ cimg_forXYZ(res,x,y,z) { ++ const float ++ xc = x - rw2, yc = y - rh2, zc = z - rd2, ++ X = cimg::mod((float)(w2 + R(0,0)*xc + R(1,0)*yc + R(2,0)*zc),(float)width()), ++ Y = cimg::mod((float)(h2 + R(0,1)*xc + R(1,1)*yc + R(2,1)*zc),(float)height()), ++ Z = cimg::mod((float)(d2 + R(0,2)*xc + R(1,2)*yc + R(2,2)*zc),(float)depth()); ++ cimg_forC(res,c) res(x,y,z,c) = _cubic_cut_atXYZ(X,Y,Z,c); ++ } ++ } break; ++ case 1 : { // Linear interpolation ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048)) ++ cimg_forXYZ(res,x,y,z) { ++ const float ++ xc = x - rw2, yc = y - rh2, zc = z - rd2, ++ X = cimg::mod((float)(w2 + R(0,0)*xc + R(1,0)*yc + R(2,0)*zc),(float)width()), ++ Y = cimg::mod((float)(h2 + R(0,1)*xc + R(1,1)*yc + R(2,1)*zc),(float)height()), ++ Z = cimg::mod((float)(d2 + R(0,2)*xc + R(1,2)*yc + R(2,2)*zc),(float)depth()); ++ cimg_forC(res,c) res(x,y,z,c) = (T)_linear_atXYZ(X,Y,Z,c); ++ } ++ } break; ++ default : { // Nearest-neighbor interpolation ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048)) ++ cimg_forXYZ(res,x,y,z) { ++ const float xc = x - rw2, yc = y - rh2, zc = z - rd2; ++ const int ++ X = cimg::mod((int)cimg::round(w2 + R(0,0)*xc + R(1,0)*yc + R(2,0)*zc),width()), ++ Y = cimg::mod((int)cimg::round(h2 + R(0,1)*xc + R(1,1)*yc + R(2,1)*zc),height()), ++ Z = cimg::mod((int)cimg::round(d2 + R(0,2)*xc + R(1,2)*yc + R(2,2)*zc),depth()); ++ cimg_forC(res,c) res(x,y,z,c) = (*this)(X,Y,Z,c); ++ } ++ } ++ } break; ++ ++ case 1 : // Neumann ++ switch (interpolation) { ++ case 2 : { // Cubic interpolation ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048)) ++ cimg_forXYZ(res,x,y,z) { ++ const float ++ xc = x - rw2, yc = y - rh2, zc = z - rd2, ++ X = w2 + R(0,0)*xc + R(1,0)*yc + R(2,0)*zc, ++ Y = h2 + R(0,1)*xc + R(1,1)*yc + R(2,1)*zc, ++ Z = d2 + R(0,2)*xc + R(1,2)*yc + R(2,2)*zc; ++ cimg_forC(res,c) res(x,y,z,c) = _cubic_cut_atXYZ(X,Y,Z,c); ++ } ++ } break; ++ case 1 : { // Linear interpolation ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048)) ++ cimg_forXYZ(res,x,y,z) { ++ const float ++ xc = x - rw2, yc = y - rh2, zc = z - rd2, ++ X = w2 + R(0,0)*xc + R(1,0)*yc + R(2,0)*zc, ++ Y = h2 + R(0,1)*xc + R(1,1)*yc + R(2,1)*zc, ++ Z = d2 + R(0,2)*xc + R(1,2)*yc + R(2,2)*zc; ++ cimg_forC(res,c) res(x,y,z,c) = _linear_atXYZ(X,Y,Z,c); ++ } ++ } break; ++ default : { // Nearest-neighbor interpolation ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048)) ++ cimg_forXYZ(res,x,y,z) { ++ const float xc = x - rw2, yc = y - rh2, zc = z - rd2; ++ const int ++ X = (int)cimg::round(w2 + R(0,0)*xc + R(1,0)*yc + R(2,0)*zc), ++ Y = (int)cimg::round(h2 + R(0,1)*xc + R(1,1)*yc + R(2,1)*zc), ++ Z = (int)cimg::round(d2 + R(0,2)*xc + R(1,2)*yc + R(2,2)*zc); ++ cimg_forC(res,c) res(x,y,z,c) = _atXYZ(X,Y,Z,c); ++ } ++ } ++ } break; ++ ++ default : // Dirichlet ++ switch (interpolation) { ++ case 2 : { // Cubic interpolation ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048)) ++ cimg_forXYZ(res,x,y,z) { ++ const float ++ xc = x - rw2, yc = y - rh2, zc = z - rd2, ++ X = w2 + R(0,0)*xc + R(1,0)*yc + R(2,0)*zc, ++ Y = h2 + R(0,1)*xc + R(1,1)*yc + R(2,1)*zc, ++ Z = d2 + R(0,2)*xc + R(1,2)*yc + R(2,2)*zc; ++ cimg_forC(res,c) res(x,y,z,c) = cubic_cut_atXYZ(X,Y,Z,c,(T)0); ++ } ++ } break; ++ case 1 : { // Linear interpolation ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048)) ++ cimg_forXYZ(res,x,y,z) { ++ const float ++ xc = x - rw2, yc = y - rh2, zc = z - rd2, ++ X = w2 + R(0,0)*xc + R(1,0)*yc + R(2,0)*zc, ++ Y = h2 + R(0,1)*xc + R(1,1)*yc + R(2,1)*zc, ++ Z = d2 + R(0,2)*xc + R(1,2)*yc + R(2,2)*zc; ++ cimg_forC(res,c) res(x,y,z,c) = linear_atXYZ(X,Y,Z,c,(T)0); ++ } ++ } break; ++ default : { // Nearest-neighbor interpolation ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048)) ++ cimg_forXYZ(res,x,y,z) { ++ const float xc = x - rw2, yc = y - rh2, zc = z - rd2; ++ const int ++ X = (int)cimg::round(w2 + R(0,0)*xc + R(1,0)*yc + R(2,0)*zc), ++ Y = (int)cimg::round(h2 + R(0,1)*xc + R(1,1)*yc + R(2,1)*zc), ++ Z = (int)cimg::round(d2 + R(0,2)*xc + R(1,2)*yc + R(2,2)*zc); ++ cimg_forC(res,c) res(x,y,z,c) = atXYZ(X,Y,Z,c,(T)0); ++ } ++ } ++ } break; ++ } ++ } ++ ++ //! Warp image content by a warping field. ++ /** ++ \param warp Warping field. ++ \param mode Can be { 0=backward-absolute | 1=backward-relative | 2=forward-absolute | 3=foward-relative } ++ \param interpolation Can be { 0=nearest | 1=linear | 2=cubic }. ++ \param boundary_conditions Boundary conditions { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. ++ **/ ++ template ++ CImg& warp(const CImg& warp, const unsigned int mode=0, ++ const unsigned int interpolation=1, const unsigned int boundary_conditions=0) { ++ return get_warp(warp,mode,interpolation,boundary_conditions).move_to(*this); ++ } ++ ++ //! Warp image content by a warping field \newinstance ++ template ++ CImg get_warp(const CImg& warp, const unsigned int mode=0, ++ const unsigned int interpolation=1, const unsigned int boundary_conditions=0) const { ++ if (is_empty() || !warp) return *this; ++ if (mode && !is_sameXYZ(warp)) ++ throw CImgArgumentException(_cimg_instance ++ "warp(): Instance and specified relative warping field (%u,%u,%u,%u,%p) " ++ "have different XYZ dimensions.", ++ cimg_instance, ++ warp._width,warp._height,warp._depth,warp._spectrum,warp._data); ++ ++ CImg res(warp._width,warp._height,warp._depth,_spectrum); ++ ++ if (warp._spectrum==1) { // 1d warping ++ if (mode>=3) { // Forward-relative warp ++ res.fill((T)0); ++ if (interpolation>=1) // Linear interpolation ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z); const T *ptrs = data(0,y,z,c); ++ cimg_forX(res,x) res.set_linear_atX(*(ptrs++),x + (float)*(ptrs0++),y,z,c); ++ } ++ else // Nearest-neighbor interpolation ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z); const T *ptrs = data(0,y,z,c); ++ cimg_forX(res,x) { ++ const int X = x + (int)cimg::round(*(ptrs0++)); ++ if (X>=0 && X=1) // Linear interpolation ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z); const T *ptrs = data(0,y,z,c); ++ cimg_forX(res,x) res.set_linear_atX(*(ptrs++),(float)*(ptrs0++),y,z,c); ++ } ++ else // Nearest-neighbor interpolation ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z); const T *ptrs = data(0,y,z,c); ++ cimg_forX(res,x) { ++ const int X = (int)cimg::round(*(ptrs0++)); ++ if (X>=0 && X=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) { ++ const float mx = cimg::mod(x - (float)*(ptrs0++),w2); ++ *(ptrd++) = _cubic_cut_atX(mx=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = _cubic_cut_atX(cimg::mod(x - (float)*(ptrs0++),(float)_width),y,z,c); ++ } ++ break; ++ case 1 : // Neumann ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = _cubic_cut_atX(x - (float)*(ptrs0++),y,z,c); ++ } ++ break; ++ default : // Dirichlet ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = cubic_cut_atX(x - (float)*(ptrs0++),y,z,c,(T)0); ++ } ++ } ++ else if (interpolation==1) // Linear interpolation ++ switch (boundary_conditions) { ++ case 3 : { // Mirror ++ const float w2 = 2.0f*width(); ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) { ++ const float mx = cimg::mod(x - (float)*(ptrs0++),w2); ++ *(ptrd++) = (T)_linear_atX(mx=1048576)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = (T)_linear_atX(cimg::mod(x - (float)*(ptrs0++),(float)_width),y,z,c); ++ } ++ break; ++ case 1 : // Neumann ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = (T)_linear_atX(x - (float)*(ptrs0++),y,z,c); ++ } ++ break; ++ default : // Dirichlet ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = (T)linear_atX(x - (float)*(ptrs0++),y,z,c,(T)0); ++ } ++ } ++ else // Nearest-neighbor interpolation ++ switch (boundary_conditions) { ++ case 3 : { // Mirror ++ const int w2 = 2*width(); ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) { ++ const int mx = cimg::mod(x - (int)cimg::round(*(ptrs0++)),w2); ++ *(ptrd++) = (*this)(mx=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) { ++ const float mx = cimg::mod((float)*(ptrs0++),w2); ++ *(ptrd++) = _cubic_cut_atX(mx=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = _cubic_cut_atX(cimg::mod((float)*(ptrs0++),(float)_width),0,0,c); ++ } ++ break; ++ case 1 : // Neumann ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = _cubic_cut_atX((float)*(ptrs0++),0,0,c); ++ } ++ break; ++ default : // Dirichlet ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = cubic_cut_atX((float)*(ptrs0++),0,0,c,(T)0); ++ } ++ } ++ else if (interpolation==1) // Linear interpolation ++ switch (boundary_conditions) { ++ case 3 : { // Mirror ++ const float w2 = 2.0f*width(); ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) { ++ const float mx = cimg::mod((float)*(ptrs0++),w2); ++ *(ptrd++) = (T)_linear_atX(mx=1048576)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = (T)_linear_atX(cimg::mod((float)*(ptrs0++),(float)_width),0,0,c); ++ } ++ break; ++ case 1 : // Neumann ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = (T)_linear_atX((float)*(ptrs0++),0,0,c); ++ } ++ break; ++ default : // Dirichlet ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = (T)linear_atX((float)*(ptrs0++),0,0,c,(T)0); ++ } ++ } ++ else // Nearest-neighbor interpolation ++ switch (boundary_conditions) { ++ case 3 : { // Mirror ++ const int w2 = 2*width(); ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) { ++ const int mx = cimg::mod((int)cimg::round(*(ptrs0++)),w2); ++ *(ptrd++) = (*this)(mx=3) { // Forward-relative warp ++ res.fill((T)0); ++ if (interpolation>=1) // Linear interpolation ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); const T *ptrs = data(0,y,z,c); ++ cimg_forX(res,x) res.set_linear_atXY(*(ptrs++),x + (float)*(ptrs0++),y + (float)*(ptrs1++),z,c); ++ } ++ else // Nearest-neighbor interpolation ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); const T *ptrs = data(0,y,z,c); ++ cimg_forX(res,x) { ++ const int X = x + (int)cimg::round(*(ptrs0++)), Y = y + (int)cimg::round(*(ptrs1++)); ++ if (X>=0 && X=0 && Y=1) // Linear interpolation ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); const T *ptrs = data(0,y,z,c); ++ cimg_forX(res,x) res.set_linear_atXY(*(ptrs++),(float)*(ptrs0++),(float)*(ptrs1++),z,c); ++ } ++ else // Nearest-neighbor interpolation ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); const T *ptrs = data(0,y,z,c); ++ cimg_forX(res,x) { ++ const int X = (int)cimg::round(*(ptrs0++)), Y = (int)cimg::round(*(ptrs1++)); ++ if (X>=0 && X=0 && Y=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) { ++ const float ++ mx = cimg::mod(x - (float)*(ptrs0++),w2), ++ my = cimg::mod(y - (float)*(ptrs1++),h2); ++ *(ptrd++) = _cubic_cut_atXY(mx=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = _cubic_cut_atXY(cimg::mod(x - (float)*(ptrs0++),(float)_width), ++ cimg::mod(y - (float)*(ptrs1++),(float)_height),z,c); ++ } ++ break; ++ case 1 : // Neumann ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = _cubic_cut_atXY(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z,c); ++ } ++ break; ++ default : // Dirichlet ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = cubic_cut_atXY(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z,c,(T)0); ++ } ++ } ++ else if (interpolation==1) // Linear interpolation ++ switch (boundary_conditions) { ++ case 3 : { // Mirror ++ const float w2 = 2.0f*width(), h2 = 2.0f*height(); ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) { ++ const float ++ mx = cimg::mod(x - (float)*(ptrs0++),w2), ++ my = cimg::mod(y - (float)*(ptrs1++),h2); ++ *(ptrd++) = (T)_linear_atXY(mx=1048576)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = (T)_linear_atXY(cimg::mod(x - (float)*(ptrs0++),(float)_width), ++ cimg::mod(y - (float)*(ptrs1++),(float)_height),z,c); ++ } ++ break; ++ case 1 : // Neumann ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = (T)_linear_atXY(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z,c); ++ } ++ break; ++ default : // Dirichlet ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = (T)linear_atXY(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z,c,(T)0); ++ } ++ } ++ else // Nearest-neighbor interpolation ++ switch (boundary_conditions) { ++ case 3 : { // Mirror ++ const int w2 = 2*width(), h2 = 2*height(); ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) { ++ const int ++ mx = cimg::mod(x - (int)cimg::round(*(ptrs0++)),w2), ++ my = cimg::mod(y - (int)cimg::round(*(ptrs1++)),h2); ++ *(ptrd++) = (*this)(mx=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) { ++ const float ++ mx = cimg::mod((float)*(ptrs0++),w2), ++ my = cimg::mod((float)*(ptrs1++),h2); ++ *(ptrd++) = _cubic_cut_atXY(mx=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = _cubic_cut_atXY(cimg::mod((float)*(ptrs0++),(float)_width), ++ cimg::mod((float)*(ptrs1++),(float)_height),0,c); ++ } ++ break; ++ case 1 : // Neumann ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = _cubic_cut_atXY((float)*(ptrs0++),(float)*(ptrs1++),0,c); ++ } ++ break; ++ default : // Dirichlet ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = cubic_cut_atXY((float)*(ptrs0++),(float)*(ptrs1++),0,c,(T)0); ++ } ++ } ++ else if (interpolation==1) // Linear interpolation ++ switch (boundary_conditions) { ++ case 3 : { // Mirror ++ const float w2 = 2.0f*width(), h2 = 2.0f*height(); ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) { ++ const float ++ mx = cimg::mod((float)*(ptrs0++),w2), ++ my = cimg::mod((float)*(ptrs1++),h2); ++ *(ptrd++) = (T)_linear_atXY(mx=1048576)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = (T)_linear_atXY(cimg::mod((float)*(ptrs0++),(float)_width), ++ cimg::mod((float)*(ptrs1++),(float)_height),0,c); ++ } ++ break; ++ case 1 : // Neumann ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = (T)_linear_atXY((float)*(ptrs0++),(float)*(ptrs1++),0,c); ++ } ++ break; ++ default : // Dirichlet ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = (T)linear_atXY((float)*(ptrs0++),(float)*(ptrs1++),0,c,(T)0); ++ } ++ } ++ else // Nearest-neighbor interpolation ++ switch (boundary_conditions) { ++ case 3 : { // Mirror ++ const int w2 = 2*width(), h2 = 2*height(); ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) { ++ const int ++ mx = cimg::mod((int)cimg::round(*(ptrs0++)),w2), ++ my = cimg::mod((int)cimg::round(*(ptrs1++)),h2); ++ *(ptrd++) = (*this)(mx=3) { // Forward-relative warp ++ res.fill((T)0); ++ if (interpolation>=1) // Linear interpolation ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); ++ const T *ptrs = data(0,y,z,c); ++ cimg_forX(res,x) res.set_linear_atXYZ(*(ptrs++),x + (float)*(ptrs0++),y + (float)*(ptrs1++), ++ z + (float)*(ptrs2++),c); ++ } ++ else // Nearest-neighbor interpolation ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); ++ const T *ptrs = data(0,y,z,c); ++ cimg_forX(res,x) { ++ const int ++ X = x + (int)cimg::round(*(ptrs0++)), ++ Y = y + (int)cimg::round(*(ptrs1++)), ++ Z = z + (int)cimg::round(*(ptrs2++)); ++ if (X>=0 && X=0 && Y=0 && Z=1) // Linear interpolation ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); ++ const T *ptrs = data(0,y,z,c); ++ cimg_forX(res,x) res.set_linear_atXYZ(*(ptrs++),(float)*(ptrs0++),(float)*(ptrs1++),(float)*(ptrs2++),c); ++ } ++ else // Nearest-neighbor interpolation ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); ++ const T *ptrs = data(0,y,z,c); ++ cimg_forX(res,x) { ++ const int ++ X = (int)cimg::round(*(ptrs0++)), ++ Y = (int)cimg::round(*(ptrs1++)), ++ Z = (int)cimg::round(*(ptrs2++)); ++ if (X>=0 && X=0 && Y=0 && Z=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); ++ T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) { ++ const float ++ mx = cimg::mod(x - (float)*(ptrs0++),w2), ++ my = cimg::mod(y - (float)*(ptrs1++),h2), ++ mz = cimg::mod(z - (float)*(ptrs2++),d2); ++ *(ptrd++) = _cubic_cut_atXYZ(mx=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); ++ T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = _cubic_cut_atXYZ(cimg::mod(x - (float)*(ptrs0++),(float)_width), ++ cimg::mod(y - (float)*(ptrs1++),(float)_height), ++ cimg::mod(z - (float)*(ptrs2++),(float)_depth),c); ++ } ++ break; ++ case 1 : // Neumann ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); ++ T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) ++ *(ptrd++) = _cubic_cut_atXYZ(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z - (float)*(ptrs2++),c); ++ } ++ break; ++ default : // Dirichlet ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); ++ T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) ++ *(ptrd++) = cubic_cut_atXYZ(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z - (float)*(ptrs2++),c,(T)0); ++ } ++ } ++ else if (interpolation==1) // Linear interpolation ++ switch (boundary_conditions) { ++ case 3 : { // Mirror ++ const float w2 = 2.0f*width(), h2 = 2.0f*height(), d2 = 2.0f*depth(); ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); ++ T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) { ++ const float ++ mx = cimg::mod(x - (float)*(ptrs0++),w2), ++ my = cimg::mod(y - (float)*(ptrs1++),h2), ++ mz = cimg::mod(z - (float)*(ptrs2++),d2); ++ *(ptrd++) = (T)_linear_atXYZ(mx=1048576)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); ++ T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = (T)_linear_atXYZ(cimg::mod(x - (float)*(ptrs0++),(float)_width), ++ cimg::mod(y - (float)*(ptrs1++),(float)_height), ++ cimg::mod(z - (float)*(ptrs2++),(float)_depth),c); ++ } ++ break; ++ case 1 : // Neumann ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); ++ T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) ++ *(ptrd++) = (T)_linear_atXYZ(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z - (float)*(ptrs2++),c); ++ } ++ break; ++ default : // Dirichlet ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); ++ T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) ++ *(ptrd++) = (T)linear_atXYZ(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z - (float)*(ptrs2++),c,(T)0); ++ } ++ } ++ else // Nearest neighbor interpolation ++ switch (boundary_conditions) { ++ case 3 : { // Mirror ++ const int w2 = 2*width(), h2 = 2*height(), d2 = 2*depth(); ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); ++ T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) { ++ const int ++ mx = cimg::mod(x - (int)cimg::round(*(ptrs0++)),w2), ++ my = cimg::mod(y - (int)cimg::round(*(ptrs1++)),h2), ++ mz = cimg::mod(z - (int)cimg::round(*(ptrs2++)),d2); ++ *(ptrd++) = (*this)(mx=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); ++ T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) { ++ const float ++ mx = cimg::mod((float)*(ptrs0++),w2), ++ my = cimg::mod((float)*(ptrs1++),h2), ++ mz = cimg::mod((float)*(ptrs2++),d2); ++ *(ptrd++) = _cubic_cut_atXYZ(mx=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); ++ T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = _cubic_cut_atXYZ(cimg::mod((float)*(ptrs0++),(float)_width), ++ cimg::mod((float)*(ptrs1++),(float)_height), ++ cimg::mod((float)*(ptrs2++),(float)_depth),c); ++ } ++ break; ++ case 1 : // Neumann ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); ++ T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = _cubic_cut_atXYZ((float)*(ptrs0++),(float)*(ptrs1++),(float)*(ptrs2++),c); ++ } ++ break; ++ default : // Dirichlet ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); ++ T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = cubic_cut_atXYZ((float)*(ptrs0++),(float)*(ptrs1++),(float)*(ptrs2++), ++ c,(T)0); ++ } ++ } ++ else if (interpolation==1) // Linear interpolation ++ switch (boundary_conditions) { ++ case 3 : { // Mirror ++ const float w2 = 2.0f*width(), h2 = 2.0f*height(), d2 = 2.0f*depth(); ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); ++ T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) { ++ const float ++ mx = cimg::mod((float)*(ptrs0++),w2), ++ my = cimg::mod((float)*(ptrs1++),h2), ++ mz = cimg::mod((float)*(ptrs2++),d2); ++ *(ptrd++) = (T)_linear_atXYZ(mx=1048576)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); ++ T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = (T)_linear_atXYZ(cimg::mod((float)*(ptrs0++),(float)_width), ++ cimg::mod((float)*(ptrs1++),(float)_height), ++ cimg::mod((float)*(ptrs2++),(float)_depth),c); ++ } ++ break; ++ case 1 : // Neumann ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); ++ T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = (T)_linear_atXYZ((float)*(ptrs0++),(float)*(ptrs1++),(float)*(ptrs2++),c); ++ } ++ break; ++ default : // Dirichlet ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); ++ T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) *(ptrd++) = (T)linear_atXYZ((float)*(ptrs0++),(float)*(ptrs1++),(float)*(ptrs2++), ++ c,(T)0); ++ } ++ } ++ else // Nearest-neighbor interpolation ++ switch (boundary_conditions) { ++ case 3 : { // Mirror ++ const int w2 = 2*width(), h2 = 2*height(), d2 = 2*depth(); ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) ++ cimg_forYZC(res,y,z,c) { ++ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2); ++ T *ptrd = res.data(0,y,z,c); ++ cimg_forX(res,x) { ++ const int ++ mx = cimg::mod((int)cimg::round(*(ptrs0++)),w2), ++ my = cimg::mod((int)cimg::round(*(ptrs1++)),h2), ++ mz = cimg::mod((int)cimg::round(*(ptrs2++)),d2); ++ *(ptrd++) = (*this)(mx get_projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0) const { ++ if (is_empty() || _depth<2) return +*this; ++ const unsigned int ++ _x0 = (x0>=_width)?_width - 1:x0, ++ _y0 = (y0>=_height)?_height - 1:y0, ++ _z0 = (z0>=_depth)?_depth - 1:z0; ++ const CImg ++ img_xy = get_crop(0,0,_z0,0,_width - 1,_height - 1,_z0,_spectrum - 1), ++ img_zy = get_crop(_x0,0,0,0,_x0,_height - 1,_depth - 1,_spectrum - 1).permute_axes("xzyc"). ++ resize(_depth,_height,1,-100,-1), ++ img_xz = get_crop(0,_y0,0,0,_width - 1,_y0,_depth - 1,_spectrum - 1).resize(_width,_depth,1,-100,-1); ++ return CImg(_width + _depth,_height + _depth,1,_spectrum,cimg::min(img_xy.min(),img_zy.min(),img_xz.min())). ++ draw_image(0,0,img_xy).draw_image(img_xy._width,0,img_zy). ++ draw_image(0,img_xy._height,img_xz); ++ } ++ ++ //! Construct a 2d representation of a 3d image, with XY,XZ and YZ views \inplace. ++ CImg& projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0) { ++ if (_depth<2) return *this; ++ return get_projections2d(x0,y0,z0).move_to(*this); ++ } ++ ++ //! Crop image region. ++ /** ++ \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 c0 = C-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 c1 = C-coordinate of the lower-right crop rectangle corner. ++ \param boundary_conditions = Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. ++ **/ ++ CImg& crop(const int x0, const int y0, const int z0, const int c0, ++ const int x1, const int y1, const int z1, const int c1, ++ const unsigned int boundary_conditions=0) { ++ return get_crop(x0,y0,z0,c0,x1,y1,z1,c1,boundary_conditions).move_to(*this); ++ } ++ ++ //! Crop image region \newinstance. ++ CImg get_crop(const int x0, const int y0, const int z0, const int c0, ++ const int x1, const int y1, const int z1, const int c1, ++ const unsigned int boundary_conditions=0) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "crop(): Empty instance.", ++ cimg_instance); ++ const int ++ nx0 = x0 res(1U + nx1 - nx0,1U + ny1 - ny0,1U + nz1 - nz0,1U + nc1 - nc0); ++ if (nx0<0 || nx1>=width() || ny0<0 || ny1>=height() || nz0<0 || nz1>=depth() || nc0<0 || nc1>=spectrum()) ++ switch (boundary_conditions) { ++ case 3 : { // Mirror ++ const int w2 = 2*width(), h2 = 2*height(), d2 = 2*depth(), s2 = 2*spectrum(); ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=16 && _height*_depth*_spectrum>=4)) ++ cimg_forXYZC(res,x,y,z,c) { ++ const int ++ mx = cimg::mod(nx0 + x,w2), ++ my = cimg::mod(ny0 + y,h2), ++ mz = cimg::mod(nz0 + z,d2), ++ mc = cimg::mod(nc0 + c,s2); ++ res(x,y,z,c) = (*this)(mx=16 && _height*_depth*_spectrum>=4)) ++ cimg_forXYZC(res,x,y,z,c) { ++ res(x,y,z,c) = (*this)(cimg::mod(nx0 + x,width()),cimg::mod(ny0 + y,height()), ++ cimg::mod(nz0 + z,depth()),cimg::mod(nc0 + c,spectrum())); ++ } ++ } break; ++ case 1 : // Neumann ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=16 && _height*_depth*_spectrum>=4)) ++ cimg_forXYZC(res,x,y,z,c) res(x,y,z,c) = _atXYZC(nx0 + x,ny0 + y,nz0 + z,nc0 + c); ++ break; ++ default : // Dirichlet ++ res.fill((T)0).draw_image(-nx0,-ny0,-nz0,-nc0,*this); ++ } ++ else res.draw_image(-nx0,-ny0,-nz0,-nc0,*this); ++ return res; ++ } ++ ++ //! Crop image region \overloading. ++ CImg& crop(const int x0, const int y0, const int z0, ++ const int x1, const int y1, const int z1, ++ const unsigned int boundary_conditions=0) { ++ return crop(x0,y0,z0,0,x1,y1,z1,_spectrum - 1,boundary_conditions); ++ } ++ ++ //! Crop image region \newinstance. ++ CImg get_crop(const int x0, const int y0, const int z0, ++ const int x1, const int y1, const int z1, ++ const unsigned int boundary_conditions=0) const { ++ return get_crop(x0,y0,z0,0,x1,y1,z1,_spectrum - 1,boundary_conditions); ++ } ++ ++ //! Crop image region \overloading. ++ CImg& crop(const int x0, const int y0, ++ const int x1, const int y1, ++ const unsigned int boundary_conditions=0) { ++ return crop(x0,y0,0,0,x1,y1,_depth - 1,_spectrum - 1,boundary_conditions); ++ } ++ ++ //! Crop image region \newinstance. ++ CImg get_crop(const int x0, const int y0, ++ const int x1, const int y1, ++ const unsigned int boundary_conditions=0) const { ++ return get_crop(x0,y0,0,0,x1,y1,_depth - 1,_spectrum - 1,boundary_conditions); ++ } ++ ++ //! Crop image region \overloading. ++ CImg& crop(const int x0, const int x1, const unsigned int boundary_conditions=0) { ++ return crop(x0,0,0,0,x1,_height - 1,_depth - 1,_spectrum - 1,boundary_conditions); ++ } ++ ++ //! Crop image region \newinstance. ++ CImg get_crop(const int x0, const int x1, const unsigned int boundary_conditions=0) const { ++ return get_crop(x0,0,0,0,x1,_height - 1,_depth - 1,_spectrum - 1,boundary_conditions); ++ } ++ ++ //! Autocrop image region, regarding the specified background value. ++ CImg& autocrop(const T& value, const char *const axes="czyx") { ++ if (is_empty()) return *this; ++ for (const char *s = axes; *s; ++s) { ++ const char axis = cimg::lowercase(*s); ++ const CImg coords = _autocrop(value,axis); ++ if (coords[0]==-1 && coords[1]==-1) return assign(); // Image has only 'value' pixels. ++ else switch (axis) { ++ case 'x' : { ++ const int x0 = coords[0], x1 = coords[1]; ++ if (x0>=0 && x1>=0) crop(x0,x1); ++ } break; ++ case 'y' : { ++ const int y0 = coords[0], y1 = coords[1]; ++ if (y0>=0 && y1>=0) crop(0,y0,_width - 1,y1); ++ } break; ++ case 'z' : { ++ const int z0 = coords[0], z1 = coords[1]; ++ if (z0>=0 && z1>=0) crop(0,0,z0,_width - 1,_height - 1,z1); ++ } break; ++ default : { ++ const int c0 = coords[0], c1 = coords[1]; ++ if (c0>=0 && c1>=0) crop(0,0,0,c0,_width - 1,_height - 1,_depth - 1,c1); ++ } ++ } ++ } ++ return *this; ++ } ++ ++ //! Autocrop image region, regarding the specified background value \newinstance. ++ CImg get_autocrop(const T& value, const char *const axes="czyx") const { ++ return (+*this).autocrop(value,axes); ++ } ++ ++ //! Autocrop image region, regarding the specified background color. ++ /** ++ \param color Color used for the crop. If \c 0, color is guessed. ++ \param axes Axes used for the crop. ++ **/ ++ CImg& autocrop(const T *const color=0, const char *const axes="zyx") { ++ if (is_empty()) return *this; ++ if (!color) { // Guess color. ++ const CImg col1 = get_vector_at(0,0,0); ++ const unsigned int w = _width, h = _height, d = _depth, s = _spectrum; ++ autocrop(col1,axes); ++ if (_width==w && _height==h && _depth==d && _spectrum==s) { ++ const CImg col2 = get_vector_at(w - 1,h - 1,d - 1); ++ autocrop(col2,axes); ++ } ++ return *this; ++ } ++ for (const char *s = axes; *s; ++s) { ++ const char axis = cimg::lowercase(*s); ++ switch (axis) { ++ case 'x' : { ++ int x0 = width(), x1 = -1; ++ cimg_forC(*this,c) { ++ const CImg coords = get_shared_channel(c)._autocrop(color[c],'x'); ++ const int nx0 = coords[0], nx1 = coords[1]; ++ if (nx0>=0 && nx1>=0) { x0 = std::min(x0,nx0); x1 = std::max(x1,nx1); } ++ } ++ if (x0==width() && x1==-1) return assign(); else crop(x0,x1); ++ } break; ++ case 'y' : { ++ int y0 = height(), y1 = -1; ++ cimg_forC(*this,c) { ++ const CImg coords = get_shared_channel(c)._autocrop(color[c],'y'); ++ const int ny0 = coords[0], ny1 = coords[1]; ++ if (ny0>=0 && ny1>=0) { y0 = std::min(y0,ny0); y1 = std::max(y1,ny1); } ++ } ++ if (y0==height() && y1==-1) return assign(); else crop(0,y0,_width - 1,y1); ++ } break; ++ default : { ++ int z0 = depth(), z1 = -1; ++ cimg_forC(*this,c) { ++ const CImg coords = get_shared_channel(c)._autocrop(color[c],'z'); ++ const int nz0 = coords[0], nz1 = coords[1]; ++ if (nz0>=0 && nz1>=0) { z0 = std::min(z0,nz0); z1 = std::max(z1,nz1); } ++ } ++ if (z0==depth() && z1==-1) return assign(); else crop(0,0,z0,_width - 1,_height - 1,z1); ++ } ++ } ++ } ++ return *this; ++ } ++ ++ //! Autocrop image region, regarding the specified background color \newinstance. ++ CImg get_autocrop(const T *const color=0, const char *const axes="zyx") const { ++ return (+*this).autocrop(color,axes); ++ } ++ ++ //! Autocrop image region, regarding the specified background color \overloading. ++ template CImg& autocrop(const CImg& color, const char *const axes="zyx") { ++ return get_autocrop(color,axes).move_to(*this); ++ } ++ ++ //! Autocrop image region, regarding the specified background color \newinstance. ++ template CImg get_autocrop(const CImg& color, const char *const axes="zyx") const { ++ return get_autocrop(color._data,axes); ++ } ++ ++ CImg _autocrop(const T& value, const char axis) const { ++ CImg res; ++ switch (cimg::lowercase(axis)) { ++ case 'x' : { ++ int x0 = -1, x1 = -1; ++ cimg_forX(*this,x) cimg_forYZC(*this,y,z,c) ++ if ((*this)(x,y,z,c)!=value) { x0 = x; x = width(); y = height(); z = depth(); c = spectrum(); } ++ if (x0>=0) { ++ for (int x = width() - 1; x>=0; --x) cimg_forYZC(*this,y,z,c) ++ if ((*this)(x,y,z,c)!=value) { x1 = x; x = 0; y = height(); z = depth(); c = spectrum(); } ++ } ++ res = CImg::vector(x0,x1); ++ } break; ++ case 'y' : { ++ int y0 = -1, y1 = -1; ++ cimg_forY(*this,y) cimg_forXZC(*this,x,z,c) ++ if ((*this)(x,y,z,c)!=value) { y0 = y; x = width(); y = height(); z = depth(); c = spectrum(); } ++ if (y0>=0) { ++ for (int y = height() - 1; y>=0; --y) cimg_forXZC(*this,x,z,c) ++ if ((*this)(x,y,z,c)!=value) { y1 = y; x = width(); y = 0; z = depth(); c = spectrum(); } ++ } ++ res = CImg::vector(y0,y1); ++ } break; ++ case 'z' : { ++ int z0 = -1, z1 = -1; ++ cimg_forZ(*this,z) cimg_forXYC(*this,x,y,c) ++ if ((*this)(x,y,z,c)!=value) { z0 = z; x = width(); y = height(); z = depth(); c = spectrum(); } ++ if (z0>=0) { ++ for (int z = depth() - 1; z>=0; --z) cimg_forXYC(*this,x,y,c) ++ if ((*this)(x,y,z,c)!=value) { z1 = z; x = width(); y = height(); z = 0; c = spectrum(); } ++ } ++ res = CImg::vector(z0,z1); ++ } break; ++ default : { ++ int c0 = -1, c1 = -1; ++ cimg_forC(*this,c) cimg_forXYZ(*this,x,y,z) ++ if ((*this)(x,y,z,c)!=value) { c0 = c; x = width(); y = height(); z = depth(); c = spectrum(); } ++ if (c0>=0) { ++ for (int c = spectrum() - 1; c>=0; --c) cimg_forXYZ(*this,x,y,z) ++ if ((*this)(x,y,z,c)!=value) { c1 = c; x = width(); y = height(); z = depth(); c = 0; } ++ } ++ res = CImg::vector(c0,c1); ++ } ++ } ++ return res; ++ } ++ ++ //! Return specified image column. ++ /** ++ \param x0 Image column. ++ **/ ++ CImg get_column(const int x0) const { ++ return get_columns(x0,x0); ++ } ++ ++ //! Return specified image column \inplace. ++ CImg& column(const int x0) { ++ return columns(x0,x0); ++ } ++ ++ //! Return specified range of image columns. ++ /** ++ \param x0 Starting image column. ++ \param x1 Ending image column. ++ **/ ++ CImg& columns(const int x0, const int x1) { ++ return get_columns(x0,x1).move_to(*this); ++ } ++ ++ //! Return specified range of image columns \inplace. ++ CImg get_columns(const int x0, const int x1) const { ++ return get_crop(x0,0,0,0,x1,height() - 1,depth() - 1,spectrum() - 1); ++ } ++ ++ //! Return specified image row. ++ CImg get_row(const int y0) const { ++ return get_rows(y0,y0); ++ } ++ ++ //! Return specified image row \inplace. ++ /** ++ \param y0 Image row. ++ **/ ++ CImg& row(const int y0) { ++ return rows(y0,y0); ++ } ++ ++ //! Return specified range of image rows. ++ /** ++ \param y0 Starting image row. ++ \param y1 Ending image row. ++ **/ ++ CImg get_rows(const int y0, const int y1) const { ++ return get_crop(0,y0,0,0,width() - 1,y1,depth() - 1,spectrum() - 1); ++ } ++ ++ //! Return specified range of image rows \inplace. ++ CImg& rows(const int y0, const int y1) { ++ return get_rows(y0,y1).move_to(*this); ++ } ++ ++ //! Return specified image slice. ++ /** ++ \param z0 Image slice. ++ **/ ++ CImg get_slice(const int z0) const { ++ return get_slices(z0,z0); ++ } ++ ++ //! Return specified image slice \inplace. ++ CImg& slice(const int z0) { ++ return slices(z0,z0); ++ } ++ ++ //! Return specified range of image slices. ++ /** ++ \param z0 Starting image slice. ++ \param z1 Ending image slice. ++ **/ ++ CImg get_slices(const int z0, const int z1) const { ++ return get_crop(0,0,z0,0,width() - 1,height() - 1,z1,spectrum() - 1); ++ } ++ ++ //! Return specified range of image slices \inplace. ++ CImg& slices(const int z0, const int z1) { ++ return get_slices(z0,z1).move_to(*this); ++ } ++ ++ //! Return specified image channel. ++ /** ++ \param c0 Image channel. ++ **/ ++ CImg get_channel(const int c0) const { ++ return get_channels(c0,c0); ++ } ++ ++ //! Return specified image channel \inplace. ++ CImg& channel(const int c0) { ++ return channels(c0,c0); ++ } ++ ++ //! Return specified range of image channels. ++ /** ++ \param c0 Starting image channel. ++ \param c1 Ending image channel. ++ **/ ++ CImg get_channels(const int c0, const int c1) const { ++ return get_crop(0,0,0,c0,width() - 1,height() - 1,depth() - 1,c1); ++ } ++ ++ //! Return specified range of image channels \inplace. ++ CImg& channels(const int c0, const int c1) { ++ return get_channels(c0,c1).move_to(*this); ++ } ++ ++ //! Return stream line of a 2d or 3d vector field. ++ CImg get_streamline(const float x, const float y, const float z, ++ const float L=256, const float dl=0.1f, ++ const unsigned int interpolation_type=2, const bool is_backward_tracking=false, ++ const bool is_oriented_only=false) const { ++ if (_spectrum!=2 && _spectrum!=3) ++ throw CImgInstanceException(_cimg_instance ++ "streamline(): Instance is not a 2d or 3d vector field.", ++ cimg_instance); ++ if (_spectrum==2) { ++ if (is_oriented_only) { ++ typename CImg::_functor4d_streamline2d_oriented func(*this); ++ return streamline(func,x,y,z,L,dl,interpolation_type,is_backward_tracking,true, ++ 0,0,0,_width - 1.0f,_height - 1.0f,0.0f); ++ } else { ++ typename CImg::_functor4d_streamline2d_directed func(*this); ++ return streamline(func,x,y,z,L,dl,interpolation_type,is_backward_tracking,false, ++ 0,0,0,_width - 1.0f,_height - 1.0f,0.0f); ++ } ++ } ++ if (is_oriented_only) { ++ typename CImg::_functor4d_streamline3d_oriented func(*this); ++ return streamline(func,x,y,z,L,dl,interpolation_type,is_backward_tracking,true, ++ 0,0,0,_width - 1.0f,_height - 1.0f,_depth - 1.0f); ++ } ++ typename CImg::_functor4d_streamline3d_directed func(*this); ++ return streamline(func,x,y,z,L,dl,interpolation_type,is_backward_tracking,false, ++ 0,0,0,_width - 1.0f,_height - 1.0f,_depth - 1.0f); ++ } ++ ++ //! Return stream line of a 3d vector field. ++ /** ++ \param func Vector field function. ++ \param x X-coordinate of the starting point of the streamline. ++ \param y Y-coordinate of the starting point of the streamline. ++ \param z Z-coordinate of the starting point of the streamline. ++ \param L Streamline length. ++ \param dl Streamline length increment. ++ \param interpolation_type Type of interpolation. ++ Can be { 0=nearest int | 1=linear | 2=2nd-order RK | 3=4th-order RK. }. ++ \param is_backward_tracking Tells if the streamline is estimated forward or backward. ++ \param is_oriented_only Tells if the direction of the vectors must be ignored. ++ \param x0 X-coordinate of the first bounding-box vertex. ++ \param y0 Y-coordinate of the first bounding-box vertex. ++ \param z0 Z-coordinate of the first bounding-box vertex. ++ \param x1 X-coordinate of the second bounding-box vertex. ++ \param y1 Y-coordinate of the second bounding-box vertex. ++ \param z1 Z-coordinate of the second bounding-box vertex. ++ **/ ++ template ++ static CImg streamline(const tfunc& func, ++ const float x, const float y, const float z, ++ const float L=256, const float dl=0.1f, ++ const unsigned int interpolation_type=2, const bool is_backward_tracking=false, ++ const bool is_oriented_only=false, ++ const float x0=0, const float y0=0, const float z0=0, ++ const float x1=0, const float y1=0, const float z1=0) { ++ if (dl<=0) ++ throw CImgArgumentException("CImg<%s>::streamline(): Invalid specified integration length %g " ++ "(should be >0).", ++ pixel_type(), ++ dl); ++ ++ const bool is_bounded = (x0!=x1 || y0!=y1 || z0!=z1); ++ if (L<=0 || (is_bounded && (xx1 || yy1 || zz1))) return CImg(); ++ const unsigned int size_L = (unsigned int)cimg::round(L/dl + 1); ++ CImg coordinates(size_L,3); ++ const float dl2 = dl/2; ++ float ++ *ptr_x = coordinates.data(0,0), ++ *ptr_y = coordinates.data(0,1), ++ *ptr_z = coordinates.data(0,2), ++ pu = (float)(dl*func(x,y,z,0)), ++ pv = (float)(dl*func(x,y,z,1)), ++ pw = (float)(dl*func(x,y,z,2)), ++ X = x, Y = y, Z = z; ++ ++ switch (interpolation_type) { ++ case 0 : { // Nearest integer interpolation. ++ cimg_forX(coordinates,l) { ++ *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z; ++ const int ++ xi = (int)(X>0?X + 0.5f:X - 0.5f), ++ yi = (int)(Y>0?Y + 0.5f:Y - 0.5f), ++ zi = (int)(Z>0?Z + 0.5f:Z - 0.5f); ++ float ++ u = (float)(dl*func((float)xi,(float)yi,(float)zi,0)), ++ v = (float)(dl*func((float)xi,(float)yi,(float)zi,1)), ++ w = (float)(dl*func((float)xi,(float)yi,(float)zi,2)); ++ if (is_oriented_only && u*pu + v*pv + w*pw<0) { u = -u; v = -v; w = -w; } ++ if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); } ++ if (is_bounded && (Xx1 || Yy1 || Zz1)) break; ++ } ++ } break; ++ case 1 : { // First-order interpolation. ++ cimg_forX(coordinates,l) { ++ *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z; ++ float ++ u = (float)(dl*func(X,Y,Z,0)), ++ v = (float)(dl*func(X,Y,Z,1)), ++ w = (float)(dl*func(X,Y,Z,2)); ++ if (is_oriented_only && u*pu + v*pv + w*pw<0) { u = -u; v = -v; w = -w; } ++ if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); } ++ if (is_bounded && (Xx1 || Yy1 || Zz1)) break; ++ } ++ } break; ++ case 2 : { // Second order interpolation. ++ cimg_forX(coordinates,l) { ++ *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z; ++ float ++ u0 = (float)(dl2*func(X,Y,Z,0)), ++ v0 = (float)(dl2*func(X,Y,Z,1)), ++ w0 = (float)(dl2*func(X,Y,Z,2)); ++ if (is_oriented_only && u0*pu + v0*pv + w0*pw<0) { u0 = -u0; v0 = -v0; w0 = -w0; } ++ float ++ u = (float)(dl*func(X + u0,Y + v0,Z + w0,0)), ++ v = (float)(dl*func(X + u0,Y + v0,Z + w0,1)), ++ w = (float)(dl*func(X + u0,Y + v0,Z + w0,2)); ++ if (is_oriented_only && u*pu + v*pv + w*pw<0) { u = -u; v = -v; w = -w; } ++ if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); } ++ if (is_bounded && (Xx1 || Yy1 || Zz1)) break; ++ } ++ } break; ++ default : { // Fourth order interpolation. ++ cimg_forX(coordinates,x) { ++ *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z; ++ float ++ u0 = (float)(dl2*func(X,Y,Z,0)), ++ v0 = (float)(dl2*func(X,Y,Z,1)), ++ w0 = (float)(dl2*func(X,Y,Z,2)); ++ if (is_oriented_only && u0*pu + v0*pv + w0*pw<0) { u0 = -u0; v0 = -v0; w0 = -w0; } ++ float ++ u1 = (float)(dl2*func(X + u0,Y + v0,Z + w0,0)), ++ v1 = (float)(dl2*func(X + u0,Y + v0,Z + w0,1)), ++ w1 = (float)(dl2*func(X + u0,Y + v0,Z + w0,2)); ++ if (is_oriented_only && u1*pu + v1*pv + w1*pw<0) { u1 = -u1; v1 = -v1; w1 = -w1; } ++ float ++ u2 = (float)(dl2*func(X + u1,Y + v1,Z + w1,0)), ++ v2 = (float)(dl2*func(X + u1,Y + v1,Z + w1,1)), ++ w2 = (float)(dl2*func(X + u1,Y + v1,Z + w1,2)); ++ if (is_oriented_only && u2*pu + v2*pv + w2*pw<0) { u2 = -u2; v2 = -v2; w2 = -w2; } ++ float ++ u3 = (float)(dl2*func(X + u2,Y + v2,Z + w2,0)), ++ v3 = (float)(dl2*func(X + u2,Y + v2,Z + w2,1)), ++ w3 = (float)(dl2*func(X + u2,Y + v2,Z + w2,2)); ++ if (is_oriented_only && u2*pu + v2*pv + w2*pw<0) { u3 = -u3; v3 = -v3; w3 = -w3; } ++ const float ++ u = (u0 + u3)/3 + (u1 + u2)/1.5f, ++ v = (v0 + v3)/3 + (v1 + v2)/1.5f, ++ w = (w0 + w3)/3 + (w1 + w2)/1.5f; ++ if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); } ++ if (is_bounded && (Xx1 || Yy1 || Zz1)) break; ++ } ++ } ++ } ++ if (ptr_x!=coordinates.data(0,1)) coordinates.resize((int)(ptr_x-coordinates.data()),3,1,1,0); ++ return coordinates; ++ } ++ ++ //! Return stream line of a 3d vector field \overloading. ++ static CImg streamline(const char *const expression, ++ const float x, const float y, const float z, ++ const float L=256, const float dl=0.1f, ++ const unsigned int interpolation_type=2, const bool is_backward_tracking=true, ++ const bool is_oriented_only=false, ++ const float x0=0, const float y0=0, const float z0=0, ++ const float x1=0, const float y1=0, const float z1=0) { ++ _functor4d_streamline_expr func(expression); ++ return streamline(func,x,y,z,L,dl,interpolation_type,is_backward_tracking,is_oriented_only,x0,y0,z0,x1,y1,z1); ++ } ++ ++ struct _functor4d_streamline2d_directed { ++ const CImg& ref; ++ _functor4d_streamline2d_directed(const CImg& pref):ref(pref) {} ++ float operator()(const float x, const float y, const float z, const unsigned int c) const { ++ return c<2?(float)ref._linear_atXY(x,y,(int)z,c):0; ++ } ++ }; ++ ++ struct _functor4d_streamline3d_directed { ++ const CImg& ref; ++ _functor4d_streamline3d_directed(const CImg& pref):ref(pref) {} ++ float operator()(const float x, const float y, const float z, const unsigned int c) const { ++ return (float)ref._linear_atXYZ(x,y,z,c); ++ } ++ }; ++ ++ struct _functor4d_streamline2d_oriented { ++ const CImg& ref; ++ CImg *pI; ++ _functor4d_streamline2d_oriented(const CImg& pref):ref(pref),pI(0) { pI = new CImg(2,2,1,2); } ++ ~_functor4d_streamline2d_oriented() { delete pI; } ++ float operator()(const float x, const float y, const float z, const unsigned int c) const { ++#define _cimg_vecalign2d(i,j) \ ++ if (I(i,j,0)*I(0,0,0) + I(i,j,1)*I(0,0,1)<0) { I(i,j,0) = -I(i,j,0); I(i,j,1) = -I(i,j,1); } ++ int ++ xi = (int)x - (x>=0?0:1), nxi = xi + 1, ++ yi = (int)y - (y>=0?0:1), nyi = yi + 1, ++ zi = (int)z; ++ const float ++ dx = x - xi, ++ dy = y - yi; ++ if (c==0) { ++ CImg& I = *pI; ++ if (xi<0) xi = 0; ++ if (nxi<0) nxi = 0; ++ if (xi>=ref.width()) xi = ref.width() - 1; ++ if (nxi>=ref.width()) nxi = ref.width() - 1; ++ if (yi<0) yi = 0; ++ if (nyi<0) nyi = 0; ++ if (yi>=ref.height()) yi = ref.height() - 1; ++ if (nyi>=ref.height()) nyi = ref.height() - 1; ++ I(0,0,0) = (float)ref(xi,yi,zi,0); I(0,0,1) = (float)ref(xi,yi,zi,1); ++ I(1,0,0) = (float)ref(nxi,yi,zi,0); I(1,0,1) = (float)ref(nxi,yi,zi,1); ++ I(1,1,0) = (float)ref(nxi,nyi,zi,0); I(1,1,1) = (float)ref(nxi,nyi,zi,1); ++ I(0,1,0) = (float)ref(xi,nyi,zi,0); I(0,1,1) = (float)ref(xi,nyi,zi,1); ++ _cimg_vecalign2d(1,0); _cimg_vecalign2d(1,1); _cimg_vecalign2d(0,1); ++ } ++ return c<2?(float)pI->_linear_atXY(dx,dy,0,c):0; ++ } ++ }; ++ ++ struct _functor4d_streamline3d_oriented { ++ const CImg& ref; ++ CImg *pI; ++ _functor4d_streamline3d_oriented(const CImg& pref):ref(pref),pI(0) { pI = new CImg(2,2,2,3); } ++ ~_functor4d_streamline3d_oriented() { delete pI; } ++ float operator()(const float x, const float y, const float z, const unsigned int c) const { ++#define _cimg_vecalign3d(i,j,k) if (I(i,j,k,0)*I(0,0,0,0) + I(i,j,k,1)*I(0,0,0,1) + I(i,j,k,2)*I(0,0,0,2)<0) { \ ++ I(i,j,k,0) = -I(i,j,k,0); I(i,j,k,1) = -I(i,j,k,1); I(i,j,k,2) = -I(i,j,k,2); } ++ int ++ xi = (int)x - (x>=0?0:1), nxi = xi + 1, ++ yi = (int)y - (y>=0?0:1), nyi = yi + 1, ++ zi = (int)z - (z>=0?0:1), nzi = zi + 1; ++ const float ++ dx = x - xi, ++ dy = y - yi, ++ dz = z - zi; ++ if (c==0) { ++ CImg& I = *pI; ++ if (xi<0) xi = 0; ++ if (nxi<0) nxi = 0; ++ if (xi>=ref.width()) xi = ref.width() - 1; ++ if (nxi>=ref.width()) nxi = ref.width() - 1; ++ if (yi<0) yi = 0; ++ if (nyi<0) nyi = 0; ++ if (yi>=ref.height()) yi = ref.height() - 1; ++ if (nyi>=ref.height()) nyi = ref.height() - 1; ++ if (zi<0) zi = 0; ++ if (nzi<0) nzi = 0; ++ if (zi>=ref.depth()) zi = ref.depth() - 1; ++ if (nzi>=ref.depth()) nzi = ref.depth() - 1; ++ I(0,0,0,0) = (float)ref(xi,yi,zi,0); I(0,0,0,1) = (float)ref(xi,yi,zi,1); ++ I(0,0,0,2) = (float)ref(xi,yi,zi,2); I(1,0,0,0) = (float)ref(nxi,yi,zi,0); ++ I(1,0,0,1) = (float)ref(nxi,yi,zi,1); I(1,0,0,2) = (float)ref(nxi,yi,zi,2); ++ I(1,1,0,0) = (float)ref(nxi,nyi,zi,0); I(1,1,0,1) = (float)ref(nxi,nyi,zi,1); ++ I(1,1,0,2) = (float)ref(nxi,nyi,zi,2); I(0,1,0,0) = (float)ref(xi,nyi,zi,0); ++ I(0,1,0,1) = (float)ref(xi,nyi,zi,1); I(0,1,0,2) = (float)ref(xi,nyi,zi,2); ++ I(0,0,1,0) = (float)ref(xi,yi,nzi,0); I(0,0,1,1) = (float)ref(xi,yi,nzi,1); ++ I(0,0,1,2) = (float)ref(xi,yi,nzi,2); I(1,0,1,0) = (float)ref(nxi,yi,nzi,0); ++ I(1,0,1,1) = (float)ref(nxi,yi,nzi,1); I(1,0,1,2) = (float)ref(nxi,yi,nzi,2); ++ I(1,1,1,0) = (float)ref(nxi,nyi,nzi,0); I(1,1,1,1) = (float)ref(nxi,nyi,nzi,1); ++ I(1,1,1,2) = (float)ref(nxi,nyi,nzi,2); I(0,1,1,0) = (float)ref(xi,nyi,nzi,0); ++ I(0,1,1,1) = (float)ref(xi,nyi,nzi,1); I(0,1,1,2) = (float)ref(xi,nyi,nzi,2); ++ _cimg_vecalign3d(1,0,0); _cimg_vecalign3d(1,1,0); _cimg_vecalign3d(0,1,0); ++ _cimg_vecalign3d(0,0,1); _cimg_vecalign3d(1,0,1); _cimg_vecalign3d(1,1,1); _cimg_vecalign3d(0,1,1); ++ } ++ return (float)pI->_linear_atXYZ(dx,dy,dz,c); ++ } ++ }; ++ ++ struct _functor4d_streamline_expr { ++ _cimg_math_parser *mp; ++ ~_functor4d_streamline_expr() { mp->end(); delete mp; } ++ _functor4d_streamline_expr(const char *const expr):mp(0) { ++ mp = new _cimg_math_parser(expr,"streamline",CImg::const_empty(),0); ++ } ++ float operator()(const float x, const float y, const float z, const unsigned int c) const { ++ return (float)(*mp)(x,y,z,c); ++ } ++ }; ++ ++ //! Return a shared-memory image referencing a range of pixels of the image instance. ++ /** ++ \param x0 X-coordinate of the starting pixel. ++ \param x1 X-coordinate of the ending pixel. ++ \param y0 Y-coordinate. ++ \param z0 Z-coordinate. ++ \param c0 C-coordinate. ++ **/ ++ 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 c0=0) { ++ const unsigned int ++ beg = (unsigned int)offset(x0,y0,z0,c0), ++ end = (unsigned int)offset(x1,y0,z0,c0); ++ if (beg>end || beg>=size() || end>=size()) ++ throw CImgArgumentException(_cimg_instance ++ "get_shared_points(): Invalid request of a shared-memory subset (%u->%u,%u,%u,%u).", ++ cimg_instance, ++ x0,x1,y0,z0,c0); ++ ++ return CImg(_data + beg,x1 - x0 + 1,1,1,1,true); ++ } ++ ++ //! Return a shared-memory image referencing a range of pixels of the image instance \const. ++ 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 c0=0) const { ++ const unsigned int ++ beg = (unsigned int)offset(x0,y0,z0,c0), ++ end = (unsigned int)offset(x1,y0,z0,c0); ++ if (beg>end || beg>=size() || end>=size()) ++ throw CImgArgumentException(_cimg_instance ++ "get_shared_points(): Invalid request of a shared-memory subset (%u->%u,%u,%u,%u).", ++ cimg_instance, ++ x0,x1,y0,z0,c0); ++ ++ return CImg(_data + beg,x1 - x0 + 1,1,1,1,true); ++ } ++ ++ //! Return a shared-memory image referencing a range of rows of the image instance. ++ /** ++ \param y0 Y-coordinate of the starting row. ++ \param y1 Y-coordinate of the ending row. ++ \param z0 Z-coordinate. ++ \param c0 C-coordinate. ++ **/ ++ CImg get_shared_rows(const unsigned int y0, const unsigned int y1, ++ const unsigned int z0=0, const unsigned int c0=0) { ++ const unsigned int ++ beg = (unsigned int)offset(0,y0,z0,c0), ++ end = (unsigned int)offset(0,y1,z0,c0); ++ if (beg>end || beg>=size() || end>=size()) ++ throw CImgArgumentException(_cimg_instance ++ "get_shared_rows(): Invalid request of a shared-memory subset " ++ "(0->%u,%u->%u,%u,%u).", ++ cimg_instance, ++ _width - 1,y0,y1,z0,c0); ++ ++ return CImg(_data + beg,_width,y1 - y0 + 1,1,1,true); ++ } ++ ++ //! Return a shared-memory image referencing a range of rows of the image instance \const. ++ const CImg get_shared_rows(const unsigned int y0, const unsigned int y1, ++ const unsigned int z0=0, const unsigned int c0=0) const { ++ const unsigned int ++ beg = (unsigned int)offset(0,y0,z0,c0), ++ end = (unsigned int)offset(0,y1,z0,c0); ++ if (beg>end || beg>=size() || end>=size()) ++ throw CImgArgumentException(_cimg_instance ++ "get_shared_rows(): Invalid request of a shared-memory subset " ++ "(0->%u,%u->%u,%u,%u).", ++ cimg_instance, ++ _width - 1,y0,y1,z0,c0); ++ ++ return CImg(_data + beg,_width,y1 - y0 + 1,1,1,true); ++ } ++ ++ //! Return a shared-memory image referencing one row of the image instance. ++ /** ++ \param y0 Y-coordinate. ++ \param z0 Z-coordinate. ++ \param c0 C-coordinate. ++ **/ ++ CImg get_shared_row(const unsigned int y0, const unsigned int z0=0, const unsigned int c0=0) { ++ return get_shared_rows(y0,y0,z0,c0); ++ } ++ ++ //! Return a shared-memory image referencing one row of the image instance \const. ++ const CImg get_shared_row(const unsigned int y0, const unsigned int z0=0, const unsigned int c0=0) const { ++ return get_shared_rows(y0,y0,z0,c0); ++ } ++ ++ //! Return a shared memory image referencing a range of slices of the image instance. ++ /** ++ \param z0 Z-coordinate of the starting slice. ++ \param z1 Z-coordinate of the ending slice. ++ \param c0 C-coordinate. ++ **/ ++ CImg get_shared_slices(const unsigned int z0, const unsigned int z1, const unsigned int c0=0) { ++ const unsigned int ++ beg = (unsigned int)offset(0,0,z0,c0), ++ end = (unsigned int)offset(0,0,z1,c0); ++ if (beg>end || beg>=size() || end>=size()) ++ throw CImgArgumentException(_cimg_instance ++ "get_shared_slices(): Invalid request of a shared-memory subset " ++ "(0->%u,0->%u,%u->%u,%u).", ++ cimg_instance, ++ _width - 1,_height - 1,z0,z1,c0); ++ ++ return CImg(_data + beg,_width,_height,z1 - z0 + 1,1,true); ++ } ++ ++ //! Return a shared memory image referencing a range of slices of the image instance \const. ++ const CImg get_shared_slices(const unsigned int z0, const unsigned int z1, const unsigned int c0=0) const { ++ const unsigned int ++ beg = (unsigned int)offset(0,0,z0,c0), ++ end = (unsigned int)offset(0,0,z1,c0); ++ if (beg>end || beg>=size() || end>=size()) ++ throw CImgArgumentException(_cimg_instance ++ "get_shared_slices(): Invalid request of a shared-memory subset " ++ "(0->%u,0->%u,%u->%u,%u).", ++ cimg_instance, ++ _width - 1,_height - 1,z0,z1,c0); ++ ++ return CImg(_data + beg,_width,_height,z1 - z0 + 1,1,true); ++ } ++ ++ //! Return a shared-memory image referencing one slice of the image instance. ++ /** ++ \param z0 Z-coordinate. ++ \param c0 C-coordinate. ++ **/ ++ CImg get_shared_slice(const unsigned int z0, const unsigned int c0=0) { ++ return get_shared_slices(z0,z0,c0); ++ } ++ ++ //! Return a shared-memory image referencing one slice of the image instance \const. ++ const CImg get_shared_slice(const unsigned int z0, const unsigned int c0=0) const { ++ return get_shared_slices(z0,z0,c0); ++ } ++ ++ //! Return a shared-memory image referencing a range of channels of the image instance. ++ /** ++ \param c0 C-coordinate of the starting channel. ++ \param c1 C-coordinate of the ending channel. ++ **/ ++ CImg get_shared_channels(const unsigned int c0, const unsigned int c1) { ++ const unsigned int ++ beg = (unsigned int)offset(0,0,0,c0), ++ end = (unsigned int)offset(0,0,0,c1); ++ if (beg>end || beg>=size() || end>=size()) ++ throw CImgArgumentException(_cimg_instance ++ "get_shared_channels(): Invalid request of a shared-memory subset " ++ "(0->%u,0->%u,0->%u,%u->%u).", ++ cimg_instance, ++ _width - 1,_height - 1,_depth - 1,c0,c1); ++ ++ return CImg(_data + beg,_width,_height,_depth,c1 - c0 + 1,true); ++ } ++ ++ //! Return a shared-memory image referencing a range of channels of the image instance \const. ++ const CImg get_shared_channels(const unsigned int c0, const unsigned int c1) const { ++ const unsigned int ++ beg = (unsigned int)offset(0,0,0,c0), ++ end = (unsigned int)offset(0,0,0,c1); ++ if (beg>end || beg>=size() || end>=size()) ++ throw CImgArgumentException(_cimg_instance ++ "get_shared_channels(): Invalid request of a shared-memory subset " ++ "(0->%u,0->%u,0->%u,%u->%u).", ++ cimg_instance, ++ _width - 1,_height - 1,_depth - 1,c0,c1); ++ ++ return CImg(_data + beg,_width,_height,_depth,c1 - c0 + 1,true); ++ } ++ ++ //! Return a shared-memory image referencing one channel of the image instance. ++ /** ++ \param c0 C-coordinate. ++ **/ ++ CImg get_shared_channel(const unsigned int c0) { ++ return get_shared_channels(c0,c0); ++ } ++ ++ //! Return a shared-memory image referencing one channel of the image instance \const. ++ const CImg get_shared_channel(const unsigned int c0) const { ++ return get_shared_channels(c0,c0); ++ } ++ ++ //! Return a shared-memory version of the image instance. ++ CImg get_shared() { ++ return CImg(_data,_width,_height,_depth,_spectrum,true); ++ } ++ ++ //! Return a shared-memory version of the image instance \const. ++ const CImg get_shared() const { ++ return CImg(_data,_width,_height,_depth,_spectrum,true); ++ } ++ ++ //! Split image into a list along specified axis. ++ /** ++ \param axis Splitting axis. Can be { 'x' | 'y' | 'z' | 'c' }. ++ \param nb Number of splitted parts. ++ \note ++ - If \c nb==0, instance image is splitted into blocs of egal values along the specified axis. ++ - If \c nb<=0, instance image is splitted into blocs of -\c nb pixel wide. ++ - If \c nb>0, instance image is splitted into \c nb blocs. ++ **/ ++ CImgList get_split(const char axis, const int nb=-1) const { ++ CImgList res; ++ if (is_empty()) return res; ++ const char _axis = cimg::lowercase(axis); ++ ++ if (nb<0) { // Split by bloc size. ++ const unsigned int dp = (unsigned int)(nb?-nb:1); ++ switch (_axis) { ++ case 'x': { ++ if (_width>dp) { ++ res.assign(_width/dp + (_width%dp?1:0),1,1); ++ const unsigned int pe = _width - dp; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=128 && _height*_depth*_spectrum>=128)) ++ for (unsigned int p = 0; pdp) { ++ res.assign(_height/dp + (_height%dp?1:0),1,1); ++ const unsigned int pe = _height - dp; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=128 && _width*_depth*_spectrum>=128)) ++ for (unsigned int p = 0; pdp) { ++ res.assign(_depth/dp + (_depth%dp?1:0),1,1); ++ const unsigned int pe = _depth - dp; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=128 && _width*_height*_spectrum>=128)) ++ for (unsigned int p = 0; pdp) { ++ res.assign(_spectrum/dp + (_spectrum%dp?1:0),1,1); ++ const unsigned int pe = _spectrum - dp; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=128 && _width*_height*_depth>=128)) ++ for (unsigned int p = 0; p0) { // Split by number of (non-homogeneous) blocs. ++ const unsigned int siz = _axis=='x'?_width:_axis=='y'?_height:_axis=='z'?_depth:_axis=='c'?_spectrum:0; ++ if ((unsigned int)nb>siz) ++ throw CImgArgumentException(_cimg_instance ++ "get_split(): Instance cannot be split along %c-axis into %u blocs.", ++ cimg_instance, ++ axis,nb); ++ if (nb==1) res.assign(*this); ++ else { ++ int err = (int)siz; ++ unsigned int _p = 0; ++ switch (_axis) { ++ case 'x' : { ++ cimg_forX(*this,p) if ((err-=nb)<=0) { ++ get_crop(_p,0,0,0,p,_height - 1,_depth - 1,_spectrum - 1).move_to(res); ++ err+=(int)siz; ++ _p = p + 1U; ++ } ++ } break; ++ case 'y' : { ++ cimg_forY(*this,p) if ((err-=nb)<=0) { ++ get_crop(0,_p,0,0,_width - 1,p,_depth - 1,_spectrum - 1).move_to(res); ++ err+=(int)siz; ++ _p = p + 1U; ++ } ++ } break; ++ case 'z' : { ++ cimg_forZ(*this,p) if ((err-=nb)<=0) { ++ get_crop(0,0,_p,0,_width - 1,_height - 1,p,_spectrum - 1).move_to(res); ++ err+=(int)siz; ++ _p = p + 1U; ++ } ++ } break; ++ case 'c' : { ++ cimg_forC(*this,p) if ((err-=nb)<=0) { ++ get_crop(0,0,0,_p,_width - 1,_height - 1,_depth - 1,p).move_to(res); ++ err+=(int)siz; ++ _p = p + 1U; ++ } ++ } ++ } ++ } ++ } else { // Split by egal values according to specified axis. ++ T current = *_data; ++ switch (_axis) { ++ case 'x' : { ++ int i0 = 0; ++ cimg_forX(*this,i) ++ if ((*this)(i)!=current) { get_columns(i0,i - 1).move_to(res); i0 = i; current = (*this)(i); } ++ get_columns(i0,width() - 1).move_to(res); ++ } break; ++ case 'y' : { ++ int i0 = 0; ++ cimg_forY(*this,i) ++ if ((*this)(0,i)!=current) { get_rows(i0,i - 1).move_to(res); i0 = i; current = (*this)(0,i); } ++ get_rows(i0,height() - 1).move_to(res); ++ } break; ++ case 'z' : { ++ int i0 = 0; ++ cimg_forZ(*this,i) ++ if ((*this)(0,0,i)!=current) { get_slices(i0,i - 1).move_to(res); i0 = i; current = (*this)(0,0,i); } ++ get_slices(i0,depth() - 1).move_to(res); ++ } break; ++ case 'c' : { ++ int i0 = 0; ++ cimg_forC(*this,i) ++ if ((*this)(0,0,0,i)!=current) { get_channels(i0,i - 1).move_to(res); i0 = i; current = (*this)(0,0,0,i); } ++ get_channels(i0,spectrum() - 1).move_to(res); ++ } break; ++ default : { ++ longT i0 = 0; ++ cimg_foroff(*this,i) ++ if ((*this)[i]!=current) { ++ CImg(_data + i0,1,(unsigned int)(i - i0)).move_to(res); ++ i0 = (longT)i; current = (*this)[i]; ++ } ++ CImg(_data + i0,1,(unsigned int)(size() - i0)).move_to(res); ++ } ++ } ++ } ++ return res; ++ } ++ ++ //! Split image into a list of sub-images, according to a specified splitting value sequence and optionally axis. ++ /** ++ \param values Splitting value sequence. ++ \param axis Axis along which the splitting is performed. Can be '0' to ignore axis. ++ \param keep_values Tells if the splitting sequence must be kept in the splitted blocs. ++ **/ ++ template ++ CImgList get_split(const CImg& values, const char axis=0, const bool keep_values=true) const { ++ CImgList res; ++ if (is_empty()) return res; ++ const ulongT vsiz = values.size(); ++ const char _axis = cimg::lowercase(axis); ++ if (!vsiz) return CImgList(*this); ++ if (vsiz==1) { // Split according to a single value. ++ const T value = (T)*values; ++ switch (_axis) { ++ case 'x' : { ++ unsigned int i0 = 0, i = 0; ++ do { ++ while (i<_width && (*this)(i)==value) ++i; ++ if (i>i0) { if (keep_values) get_columns(i0,i - 1).move_to(res); i0 = i; } ++ while (i<_width && (*this)(i)!=value) ++i; ++ if (i>i0) { get_columns(i0,i - 1).move_to(res); i0 = i; } ++ } while (i<_width); ++ } break; ++ case 'y' : { ++ unsigned int i0 = 0, i = 0; ++ do { ++ while (i<_height && (*this)(0,i)==value) ++i; ++ if (i>i0) { if (keep_values) get_rows(i0,i - 1).move_to(res); i0 = i; } ++ while (i<_height && (*this)(0,i)!=value) ++i; ++ if (i>i0) { get_rows(i0,i - 1).move_to(res); i0 = i; } ++ } while (i<_height); ++ } break; ++ case 'z' : { ++ unsigned int i0 = 0, i = 0; ++ do { ++ while (i<_depth && (*this)(0,0,i)==value) ++i; ++ if (i>i0) { if (keep_values) get_slices(i0,i - 1).move_to(res); i0 = i; } ++ while (i<_depth && (*this)(0,0,i)!=value) ++i; ++ if (i>i0) { get_slices(i0,i - 1).move_to(res); i0 = i; } ++ } while (i<_depth); ++ } break; ++ case 'c' : { ++ unsigned int i0 = 0, i = 0; ++ do { ++ while (i<_spectrum && (*this)(0,0,0,i)==value) ++i; ++ if (i>i0) { if (keep_values) get_channels(i0,i - 1).move_to(res); i0 = i; } ++ while (i<_spectrum && (*this)(0,0,0,i)!=value) ++i; ++ if (i>i0) { get_channels(i0,i - 1).move_to(res); i0 = i; } ++ } while (i<_spectrum); ++ } break; ++ default : { ++ const ulongT siz = size(); ++ ulongT i0 = 0, i = 0; ++ do { ++ while (ii0) { if (keep_values) CImg(_data + i0,1,(unsigned int)(i - i0)).move_to(res); i0 = i; } ++ while (ii0) { CImg(_data + i0,1,(unsigned int)(i - i0)).move_to(res); i0 = i; } ++ } while (i=vsiz) j = 0; } ++ i-=j; ++ if (i>i1) { ++ if (i1>i0) get_columns(i0,i1 - 1).move_to(res); ++ if (keep_values) get_columns(i1,i - 1).move_to(res); ++ i0 = i; ++ } else ++i; ++ } else ++i; ++ } while (i<_width); ++ if (i0<_width) get_columns(i0,width() - 1).move_to(res); ++ } break; ++ case 'y' : { ++ unsigned int i0 = 0, i1 = 0, i = 0; ++ do { ++ if ((*this)(0,i)==*values) { ++ i1 = i; j = 0; ++ while (i<_height && (*this)(0,i)==values[j]) { ++i; if (++j>=vsiz) j = 0; } ++ i-=j; ++ if (i>i1) { ++ if (i1>i0) get_rows(i0,i1 - 1).move_to(res); ++ if (keep_values) get_rows(i1,i - 1).move_to(res); ++ i0 = i; ++ } else ++i; ++ } else ++i; ++ } while (i<_height); ++ if (i0<_height) get_rows(i0,height() - 1).move_to(res); ++ } break; ++ case 'z' : { ++ unsigned int i0 = 0, i1 = 0, i = 0; ++ do { ++ if ((*this)(0,0,i)==*values) { ++ i1 = i; j = 0; ++ while (i<_depth && (*this)(0,0,i)==values[j]) { ++i; if (++j>=vsiz) j = 0; } ++ i-=j; ++ if (i>i1) { ++ if (i1>i0) get_slices(i0,i1 - 1).move_to(res); ++ if (keep_values) get_slices(i1,i - 1).move_to(res); ++ i0 = i; ++ } else ++i; ++ } else ++i; ++ } while (i<_depth); ++ if (i0<_depth) get_slices(i0,depth() - 1).move_to(res); ++ } break; ++ case 'c' : { ++ unsigned int i0 = 0, i1 = 0, i = 0; ++ do { ++ if ((*this)(0,0,0,i)==*values) { ++ i1 = i; j = 0; ++ while (i<_spectrum && (*this)(0,0,0,i)==values[j]) { ++i; if (++j>=vsiz) j = 0; } ++ i-=j; ++ if (i>i1) { ++ if (i1>i0) get_channels(i0,i1 - 1).move_to(res); ++ if (keep_values) get_channels(i1,i - 1).move_to(res); ++ i0 = i; ++ } else ++i; ++ } else ++i; ++ } while (i<_spectrum); ++ if (i0<_spectrum) get_channels(i0,spectrum() - 1).move_to(res); ++ } break; ++ default : { ++ ulongT i0 = 0, i1 = 0, i = 0; ++ const ulongT siz = size(); ++ do { ++ if ((*this)[i]==*values) { ++ i1 = i; j = 0; ++ while (i=vsiz) j = 0; } ++ i-=j; ++ if (i>i1) { ++ if (i1>i0) CImg(_data + i0,1,(unsigned int)(i1 - i0)).move_to(res); ++ if (keep_values) CImg(_data + i1,1,(unsigned int)(i - i1)).move_to(res); ++ i0 = i; ++ } else ++i; ++ } else ++i; ++ } while (i(_data + i0,1,(unsigned int)(siz - i0)).move_to(res); ++ } break; ++ } ++ } ++ return res; ++ } ++ ++ //! Append two images along specified axis. ++ /** ++ \param img Image to append with instance image. ++ \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. ++ \param align Append alignment in \c [0,1]. ++ **/ ++ template ++ CImg& append(const CImg& img, const char axis='x', const float align=0) { ++ if (is_empty()) return assign(img,false); ++ if (!img) return *this; ++ return CImgList(*this,true).insert(img).get_append(axis,align).move_to(*this); ++ } ++ ++ //! Append two images along specified axis \specialization. ++ CImg& append(const CImg& img, const char axis='x', const float align=0) { ++ if (is_empty()) return assign(img,false); ++ if (!img) return *this; ++ return CImgList(*this,img,true).get_append(axis,align).move_to(*this); ++ } ++ ++ //! Append two images along specified axis \const. ++ template ++ CImg<_cimg_Tt> get_append(const CImg& img, const char axis='x', const float align=0) const { ++ if (is_empty()) return +img; ++ if (!img) return +*this; ++ return CImgList<_cimg_Tt>(*this,true).insert(img).get_append(axis,align); ++ } ++ ++ //! Append two images along specified axis \specialization. ++ CImg get_append(const CImg& img, const char axis='x', const float align=0) const { ++ if (is_empty()) return +img; ++ if (!img) return +*this; ++ return CImgList(*this,img,true).get_append(axis,align); ++ } ++ ++ //@} ++ //--------------------------------------- ++ // ++ //! \name Filtering / Transforms ++ //@{ ++ //--------------------------------------- ++ ++ //! Correlate image by a kernel. ++ /** ++ \param kernel = the correlation kernel. ++ \param boundary_conditions boundary conditions can be (false=dirichlet, true=neumann) ++ \param is_normalized = enable local normalization. ++ \note ++ - The correlation of the image instance \p *this by the kernel \p kernel is defined to be: ++ res(x,y,z) = sum_{i,j,k} (*this)(x + i,y + j,z + k)*kernel(i,j,k). ++ **/ ++ template ++ CImg& correlate(const CImg& kernel, const bool boundary_conditions=true, ++ const bool is_normalized=false) { ++ if (is_empty() || !kernel) return *this; ++ return get_correlate(kernel,boundary_conditions,is_normalized).move_to(*this); ++ } ++ ++ template ++ CImg<_cimg_Ttfloat> get_correlate(const CImg& kernel, const bool boundary_conditions=true, ++ const bool is_normalized=false) const { ++ return _correlate(kernel,boundary_conditions,is_normalized,false); ++ } ++ ++ //! Correlate image by a kernel \newinstance. ++ template ++ CImg<_cimg_Ttfloat> _correlate(const CImg& kernel, const bool boundary_conditions, ++ const bool is_normalized, const bool is_convolution) const { ++ if (is_empty() || !kernel) return *this; ++ typedef _cimg_Ttfloat Ttfloat; ++ CImg res; ++ const ulongT ++ res_whd = (ulongT)_width*_height*_depth, ++ res_size = res_whd*std::max(_spectrum,kernel._spectrum); ++ const bool ++ is_inner_parallel = _width*_height*_depth>=32768, ++ is_outer_parallel = res_size>=32768; ++ _cimg_abort_init_omp; ++ cimg_abort_init; ++ ++ if (kernel._width==kernel._height && ++ ((kernel._depth==1 && kernel._width<=6) || (kernel._depth==kernel._width && kernel._width<=3))) { ++ ++ // Special optimization done for 2x2, 3x3, 4x4, 5x5, 6x6, 2x2x2 and 3x3x3 kernel. ++ if (!boundary_conditions && res_whd<=3000*3000) { // Dirichlet boundaries ++ // For relatively small images, adding a zero border then use optimized NxN convolution loops is faster. ++ res = (kernel._depth==1?get_crop(-1,-1,_width,_height):get_crop(-1,-1,-1,_width,_height,_depth)). ++ _correlate(kernel,true,is_normalized,is_convolution); ++ if (kernel._depth==1) res.crop(1,1,res._width - 2,res._height - 2); ++ else res.crop(1,1,1,res._width - 2,res._height - 2,res._depth - 2); ++ ++ } else { // Neumann boundaries ++ res.assign(_width,_height,_depth,std::max(_spectrum,kernel._spectrum)); ++ cimg::unused(is_inner_parallel,is_outer_parallel); ++ CImg _kernel; ++ if (is_convolution) { // Add empty column/row/slice to shift kernel center in case of convolution ++ const int dw = !(kernel.width()%2), dh = !(kernel.height()%2), dd = !(kernel.depth()%2); ++ if (dw || dh || dd) ++ kernel.get_resize(kernel.width() + dw,kernel.height() + dh,kernel.depth() + dd,-100,0,0). ++ move_to(_kernel); ++ } ++ if (!_kernel) _kernel = kernel.get_shared(); ++ ++ switch (_kernel._depth) { ++ case 3 : { ++ cimg_pragma_openmp(parallel for cimg_openmp_if(is_outer_parallel)) ++ cimg_forC(res,c) { ++ cimg_abort_test; ++ const CImg img = get_shared_channel(c%_spectrum); ++ const CImg K = _kernel.get_shared_channel(c%kernel._spectrum); ++ CImg I(27); ++ Ttfloat *ptrd = res.data(0,0,0,c); ++ if (is_normalized) { ++ const Ttfloat _M = (Ttfloat)K.magnitude(2), M = _M*_M; ++ cimg_for3x3x3(img,x,y,z,0,I,T) { ++ const Ttfloat N = M*(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]); ++ *(ptrd++) = (Ttfloat)(N?(I[ 0]*K[ 0] + I[ 1]*K[ 1] + I[ 2]*K[ 2] + ++ I[ 3]*K[ 3] + I[ 4]*K[ 4] + I[ 5]*K[ 5] + ++ I[ 6]*K[ 6] + I[ 7]*K[ 7] + I[ 8]*K[ 8] + ++ I[ 9]*K[ 9] + I[10]*K[10] + I[11]*K[11] + ++ I[12]*K[12] + I[13]*K[13] + I[14]*K[14] + ++ I[15]*K[15] + I[16]*K[16] + I[17]*K[17] + ++ I[18]*K[18] + I[19]*K[19] + I[20]*K[20] + ++ I[21]*K[21] + I[22]*K[22] + I[23]*K[23] + ++ I[24]*K[24] + I[25]*K[25] + I[26]*K[26])/std::sqrt(N):0); ++ } ++ } else cimg_for3x3x3(img,x,y,z,0,I,T) ++ *(ptrd++) = (Ttfloat)(I[ 0]*K[ 0] + I[ 1]*K[ 1] + I[ 2]*K[ 2] + ++ I[ 3]*K[ 3] + I[ 4]*K[ 4] + I[ 5]*K[ 5] + ++ I[ 6]*K[ 6] + I[ 7]*K[ 7] + I[ 8]*K[ 8] + ++ I[ 9]*K[ 9] + I[10]*K[10] + I[11]*K[11] + ++ I[12]*K[12] + I[13]*K[13] + I[14]*K[14] + ++ I[15]*K[15] + I[16]*K[16] + I[17]*K[17] + ++ I[18]*K[18] + I[19]*K[19] + I[20]*K[20] + ++ I[21]*K[21] + I[22]*K[22] + I[23]*K[23] + ++ I[24]*K[24] + I[25]*K[25] + I[26]*K[26]); ++ } ++ } break; ++ case 2 : { ++ cimg_pragma_openmp(parallel for cimg_openmp_if(is_outer_parallel)) ++ cimg_forC(res,c) { ++ cimg_abort_test; ++ const CImg img = get_shared_channel(c%_spectrum); ++ const CImg K = _kernel.get_shared_channel(c%kernel._spectrum); ++ CImg I(8); ++ Ttfloat *ptrd = res.data(0,0,0,c); ++ if (is_normalized) { ++ const Ttfloat _M = (Ttfloat)K.magnitude(2), M = _M*_M; ++ cimg_for2x2x2(img,x,y,z,0,I,T) { ++ const Ttfloat N = M*(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]); ++ *(ptrd++) = (Ttfloat)(N?(I[0]*K[0] + I[1]*K[1] + ++ I[2]*K[2] + I[3]*K[3] + ++ I[4]*K[4] + I[5]*K[5] + ++ I[6]*K[6] + I[7]*K[7])/std::sqrt(N):0); ++ } ++ } else cimg_for2x2x2(img,x,y,z,0,I,T) ++ *(ptrd++) = (Ttfloat)(I[0]*K[0] + I[1]*K[1] + ++ I[2]*K[2] + I[3]*K[3] + ++ I[4]*K[4] + I[5]*K[5] + ++ I[6]*K[6] + I[7]*K[7]); ++ } ++ } break; ++ default : ++ case 1 : ++ switch (_kernel._width) { ++ case 6 : { ++ cimg_pragma_openmp(parallel for cimg_openmp_if(is_outer_parallel)) ++ cimg_forC(res,c) { ++ cimg_abort_test; ++ const CImg img = get_shared_channel(c%_spectrum); ++ const CImg K = _kernel.get_shared_channel(c%kernel._spectrum); ++ CImg I(36); ++ Ttfloat *ptrd = res.data(0,0,0,c); ++ if (is_normalized) { ++ const Ttfloat _M = (Ttfloat)K.magnitude(2), M = _M*_M; ++ cimg_forZ(img,z) cimg_for6x6(img,x,y,z,0,I,T) { ++ const Ttfloat N = M*(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]); ++ *(ptrd++) = (Ttfloat)(N?(I[ 0]*K[ 0] + I[ 1]*K[ 1] + I[ 2]*K[ 2] + I[ 3]*K[ 3] + ++ I[ 4]*K[ 4] + I[ 5]*K[ 5] + I[ 6]*K[ 6] + I[ 7]*K[ 7] + ++ I[ 8]*K[ 8] + I[ 9]*K[ 9] + I[10]*K[10] + I[11]*K[11] + ++ I[12]*K[12] + I[13]*K[13] + I[14]*K[14] + I[15]*K[15] + ++ I[16]*K[16] + I[17]*K[17] + I[18]*K[18] + I[19]*K[19] + ++ I[20]*K[20] + I[21]*K[21] + I[22]*K[22] + I[23]*K[23] + ++ I[24]*K[24] + I[25]*K[25] + I[26]*K[26] + I[27]*K[27] + ++ I[28]*K[28] + I[29]*K[29] + I[30]*K[30] + I[31]*K[31] + ++ I[32]*K[32] + I[33]*K[33] + I[34]*K[34] + I[35]*K[35])/ ++ std::sqrt(N):0); ++ } ++ } else cimg_forZ(img,z) cimg_for6x6(img,x,y,z,0,I,T) ++ *(ptrd++) = (Ttfloat)(I[ 0]*K[ 0] + I[ 1]*K[ 1] + I[ 2]*K[ 2] + I[ 3]*K[ 3] + ++ I[ 4]*K[ 4] + I[ 5]*K[ 5] + I[ 6]*K[ 6] + I[ 7]*K[ 7] + ++ I[ 8]*K[ 8] + I[ 9]*K[ 9] + I[10]*K[10] + I[11]*K[11] + ++ I[12]*K[12] + I[13]*K[13] + I[14]*K[14] + I[15]*K[15] + ++ I[16]*K[16] + I[17]*K[17] + I[18]*K[18] + I[19]*K[19] + ++ I[20]*K[20] + I[21]*K[21] + I[22]*K[22] + I[23]*K[23] + ++ I[24]*K[24] + I[25]*K[25] + I[26]*K[26] + I[27]*K[27] + ++ I[28]*K[28] + I[29]*K[29] + I[30]*K[30] + I[31]*K[31] + ++ I[32]*K[32] + I[33]*K[33] + I[34]*K[34] + I[35]*K[35]); ++ } ++ } break; ++ case 5 : { ++ cimg_pragma_openmp(parallel for cimg_openmp_if(is_outer_parallel)) ++ cimg_forC(res,c) { ++ cimg_abort_test; ++ const CImg img = get_shared_channel(c%_spectrum); ++ const CImg K = _kernel.get_shared_channel(c%kernel._spectrum); ++ CImg I(25); ++ Ttfloat *ptrd = res.data(0,0,0,c); ++ if (is_normalized) { ++ const Ttfloat _M = (Ttfloat)K.magnitude(2), M = _M*_M; ++ cimg_forZ(img,z) cimg_for5x5(img,x,y,z,0,I,T) { ++ const Ttfloat N = M*(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]); ++ *(ptrd++) = (Ttfloat)(N?(I[ 0]*K[ 0] + I[ 1]*K[ 1] + I[ 2]*K[ 2] + I[ 3]*K[ 3] + ++ I[ 4]*K[ 4] + I[ 5]*K[ 5] + I[ 6]*K[ 6] + I[ 7]*K[ 7] + ++ I[ 8]*K[ 8] + I[ 9]*K[ 9] + I[10]*K[10] + I[11]*K[11] + ++ I[12]*K[12] + I[13]*K[13] + I[14]*K[14] + I[15]*K[15] + ++ I[16]*K[16] + I[17]*K[17] + I[18]*K[18] + I[19]*K[19] + ++ I[20]*K[20] + I[21]*K[21] + I[22]*K[22] + I[23]*K[23] + ++ I[24]*K[24])/std::sqrt(N):0); ++ } ++ } else cimg_forZ(img,z) cimg_for5x5(img,x,y,z,0,I,T) ++ *(ptrd++) = (Ttfloat)(I[ 0]*K[ 0] + I[ 1]*K[ 1] + I[ 2]*K[ 2] + I[ 3]*K[ 3] + ++ I[ 4]*K[ 4] + I[ 5]*K[ 5] + I[ 6]*K[ 6] + I[ 7]*K[ 7] + ++ I[ 8]*K[ 8] + I[ 9]*K[ 9] + I[10]*K[10] + I[11]*K[11] + ++ I[12]*K[12] + I[13]*K[13] + I[14]*K[14] + I[15]*K[15] + ++ I[16]*K[16] + I[17]*K[17] + I[18]*K[18] + I[19]*K[19] + ++ I[20]*K[20] + I[21]*K[21] + I[22]*K[22] + I[23]*K[23] + ++ I[24]*K[24]); ++ } ++ } break; ++ case 4 : { ++ cimg_pragma_openmp(parallel for cimg_openmp_if(is_outer_parallel)) ++ cimg_forC(res,c) { ++ cimg_abort_test; ++ const CImg img = get_shared_channel(c%_spectrum); ++ const CImg K = _kernel.get_shared_channel(c%kernel._spectrum); ++ CImg I(16); ++ Ttfloat *ptrd = res.data(0,0,0,c); ++ if (is_normalized) { ++ const Ttfloat _M = (Ttfloat)K.magnitude(2), M = _M*_M; ++ cimg_forZ(img,z) cimg_for4x4(img,x,y,z,0,I,T) { ++ const Ttfloat N = M*(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]); ++ *(ptrd++) = (Ttfloat)(N?(I[ 0]*K[ 0] + I[ 1]*K[ 1] + I[ 2]*K[ 2] + I[ 3]*K[ 3] + ++ I[ 4]*K[ 4] + I[ 5]*K[ 5] + I[ 6]*K[ 6] + I[ 7]*K[ 7] + ++ I[ 8]*K[ 8] + I[ 9]*K[ 9] + I[10]*K[10] + I[11]*K[11] + ++ I[12]*K[12] + I[13]*K[13] + I[14]*K[14] + I[15]*K[15])/ ++ std::sqrt(N):0); ++ } ++ } else cimg_forZ(img,z) cimg_for4x4(img,x,y,z,0,I,T) ++ *(ptrd++) = (Ttfloat)(I[ 0]*K[ 0] + I[ 1]*K[ 1] + I[ 2]*K[ 2] + I[ 3]*K[ 3] + ++ I[ 4]*K[ 4] + I[ 5]*K[ 5] + I[ 6]*K[ 6] + I[ 7]*K[ 7] + ++ I[ 8]*K[ 8] + I[ 9]*K[ 9] + I[10]*K[10] + I[11]*K[11] + ++ I[12]*K[12] + I[13]*K[13] + I[14]*K[14] + I[15]*K[15]); ++ } ++ } break; ++ case 3 : { ++ cimg_pragma_openmp(parallel for cimg_openmp_if(is_outer_parallel)) ++ cimg_forC(res,c) { ++ cimg_abort_test; ++ const CImg img = get_shared_channel(c%_spectrum); ++ const CImg K = _kernel.get_shared_channel(c%kernel._spectrum); ++ CImg I(9); ++ Ttfloat *ptrd = res.data(0,0,0,c); ++ if (is_normalized) { ++ const Ttfloat _M = (Ttfloat)K.magnitude(2), M = _M*_M; ++ cimg_forZ(img,z) cimg_for3x3(img,x,y,z,0,I,T) { ++ const Ttfloat N = M*(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]); ++ *(ptrd++) = (Ttfloat)(N?(I[0]*K[0] + I[1]*K[1] + I[2]*K[2] + ++ I[3]*K[3] + I[4]*K[4] + I[5]*K[5] + ++ I[6]*K[6] + I[7]*K[7] + I[8]*K[8])/std::sqrt(N):0); ++ } ++ } else cimg_forZ(img,z) cimg_for3x3(img,x,y,z,0,I,T) ++ *(ptrd++) = (Ttfloat)(I[0]*K[0] + I[1]*K[1] + I[2]*K[2] + ++ I[3]*K[3] + I[4]*K[4] + I[5]*K[5] + ++ I[6]*K[6] + I[7]*K[7] + I[8]*K[8]); ++ } ++ } break; ++ case 2 : { ++ cimg_pragma_openmp(parallel for cimg_openmp_if(is_outer_parallel)) ++ cimg_forC(res,c) { ++ cimg_abort_test; ++ const CImg img = get_shared_channel(c%_spectrum); ++ const CImg K = _kernel.get_shared_channel(c%kernel._spectrum); ++ CImg I(4); ++ Ttfloat *ptrd = res.data(0,0,0,c); ++ if (is_normalized) { ++ const Ttfloat _M = (Ttfloat)K.magnitude(2), M = _M*_M; ++ cimg_forZ(img,z) cimg_for2x2(img,x,y,z,0,I,T) { ++ const Ttfloat N = M*(I[0]*I[0] + I[1]*I[1] + ++ I[2]*I[2] + I[3]*I[3]); ++ *(ptrd++) = (Ttfloat)(N?(I[0]*K[0] + I[1]*K[1] + ++ I[2]*K[2] + I[3]*K[3])/std::sqrt(N):0); ++ } ++ } else cimg_forZ(img,z) cimg_for2x2(img,x,y,z,0,I,T) ++ *(ptrd++) = (Ttfloat)(I[0]*K[0] + I[1]*K[1] + ++ I[2]*K[2] + I[3]*K[3]); ++ } ++ } break; ++ case 1 : ++ if (is_normalized) res.fill(1); ++ else cimg_forC(res,c) { ++ cimg_abort_test; ++ const CImg img = get_shared_channel(c%_spectrum); ++ const CImg K = _kernel.get_shared_channel(c%kernel._spectrum); ++ res.get_shared_channel(c).assign(img)*=K[0]; ++ } ++ break; ++ } ++ } ++ } ++ } ++ ++ if (!res) { // Generic version for other kernels and boundary conditions. ++ res.assign(_width,_height,_depth,std::max(_spectrum,kernel._spectrum)); ++ int ++ mx2 = kernel.width()/2, my2 = kernel.height()/2, mz2 = kernel.depth()/2, ++ mx1 = kernel.width() - mx2 - 1, my1 = kernel.height() - my2 - 1, mz1 = kernel.depth() - mz2 - 1; ++ if (is_convolution) cimg::swap(mx1,mx2,my1,my2,mz1,mz2); // Shift kernel center in case of convolution ++ const int ++ mxe = width() - mx2, mye = height() - my2, mze = depth() - mz2; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(!is_inner_parallel && is_outer_parallel)) ++ cimg_forC(res,c) _cimg_abort_try_omp { ++ cimg_abort_test; ++ const CImg img = get_shared_channel(c%_spectrum); ++ const CImg K = kernel.get_shared_channel(c%kernel._spectrum); ++ if (is_normalized) { // Normalized correlation. ++ const Ttfloat _M = (Ttfloat)K.magnitude(2), M = _M*_M; ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(is_inner_parallel)) ++ for (int z = mz1; z=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { ++ Ttfloat val = 0, N = 0; ++ for (int zm = -mz1; zm<=mz2; ++zm) ++ for (int ym = -my1; ym<=my2; ++ym) ++ for (int xm = -mx1; xm<=mx2; ++xm) { ++ const Ttfloat _val = (Ttfloat)img._atXYZ(x + xm,y + ym,z + zm); ++ val+=_val*K(mx1 + xm,my1 + ym,mz1 + zm); ++ N+=_val*_val; ++ } ++ N*=M; ++ res(x,y,z,c) = (Ttfloat)(N?val/std::sqrt(N):0); ++ } ++ } _cimg_abort_catch_omp2 ++ else ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(is_inner_parallel)) ++ cimg_forYZ(res,y,z) _cimg_abort_try_omp2 { ++ cimg_abort_test2; ++ for (int x = 0; x=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { ++ Ttfloat val = 0, N = 0; ++ for (int zm = -mz1; zm<=mz2; ++zm) ++ for (int ym = -my1; ym<=my2; ++ym) ++ for (int xm = -mx1; xm<=mx2; ++xm) { ++ const Ttfloat _val = (Ttfloat)img.atXYZ(x + xm,y + ym,z + zm,0,(T)0); ++ val+=_val*K(mx1 + xm,my1 + ym,mz1 + zm); ++ N+=_val*_val; ++ } ++ N*=M; ++ res(x,y,z,c) = (Ttfloat)(N?val/std::sqrt(N):0); ++ } ++ } _cimg_abort_catch_omp2 ++ } else { // Classical correlation. ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(is_inner_parallel)) ++ 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+=img._atXYZ(x + xm,y + ym,z + zm)*K(mx1 + xm,my1 + ym,mz1 + zm); ++ res(x,y,z,c) = (Ttfloat)val; ++ } ++ } _cimg_abort_catch_omp2 ++ else ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(is_inner_parallel)) ++ cimg_forYZ(res,y,z) _cimg_abort_try_omp2 { ++ cimg_abort_test2; ++ 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+=img.atXYZ(x + xm,y + ym,z + zm,0,(T)0)*K(mx1 + xm,my1 + ym,mz1 + zm); ++ res(x,y,z,c) = (Ttfloat)val; ++ } ++ } _cimg_abort_catch_omp2 ++ } ++ } _cimg_abort_catch_omp ++ } ++ cimg_abort_test; ++ return res; ++ } ++ ++ //! Convolve image by a kernel. ++ /** ++ \param kernel = the correlation kernel. ++ \param boundary_conditions boundary conditions can be (false=dirichlet, true=neumann) ++ \param is_normalized = enable local normalization. ++ \note ++ - The result \p res of the convolution of an image \p img by a kernel \p kernel is defined to be: ++ res(x,y,z) = sum_{i,j,k} img(x-i,y-j,z-k)*kernel(i,j,k) ++ **/ ++ template ++ CImg& convolve(const CImg& kernel, const bool boundary_conditions=true, const bool is_normalized=false) { ++ if (is_empty() || !kernel) return *this; ++ return get_convolve(kernel,boundary_conditions,is_normalized).move_to(*this); ++ } ++ ++ //! Convolve image by a kernel \newinstance. ++ template ++ CImg<_cimg_Ttfloat> get_convolve(const CImg& kernel, const bool boundary_conditions=true, ++ const bool is_normalized=false) const { ++ return _correlate(CImg(kernel._data,kernel.size()/kernel._spectrum,1,1,kernel._spectrum,true). ++ get_mirror('x').resize(kernel,-1),boundary_conditions,is_normalized,true); ++ } ++ ++ //! Cumulate image values, optionally along specified axis. ++ /** ++ \param axis Cumulation axis. Set it to 0 to cumulate all values globally without taking axes into account. ++ **/ ++ CImg& cumulate(const char axis=0) { ++ switch (cimg::lowercase(axis)) { ++ case 'x' : ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=512 && _height*_depth*_spectrum>=16)) ++ cimg_forYZC(*this,y,z,c) { ++ T *ptrd = data(0,y,z,c); ++ Tlong cumul = (Tlong)0; ++ cimg_forX(*this,x) { cumul+=(Tlong)*ptrd; *(ptrd++) = (T)cumul; } ++ } ++ break; ++ case 'y' : { ++ const ulongT w = (ulongT)_width; ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_height>=512 && _width*_depth*_spectrum>=16)) ++ cimg_forXZC(*this,x,z,c) { ++ T *ptrd = data(x,0,z,c); ++ Tlong cumul = (Tlong)0; ++ cimg_forY(*this,y) { cumul+=(Tlong)*ptrd; *ptrd = (T)cumul; ptrd+=w; } ++ } ++ } break; ++ case 'z' : { ++ const ulongT wh = (ulongT)_width*_height; ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_depth>=512 && _width*_depth*_spectrum>=16)) ++ cimg_forXYC(*this,x,y,c) { ++ T *ptrd = data(x,y,0,c); ++ Tlong cumul = (Tlong)0; ++ cimg_forZ(*this,z) { cumul+=(Tlong)*ptrd; *ptrd = (T)cumul; ptrd+=wh; } ++ } ++ } break; ++ case 'c' : { ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_spectrum>=512 && _width*_height*_depth>=16)) ++ cimg_forXYZ(*this,x,y,z) { ++ T *ptrd = data(x,y,z,0); ++ Tlong cumul = (Tlong)0; ++ cimg_forC(*this,c) { cumul+=(Tlong)*ptrd; *ptrd = (T)cumul; ptrd+=whd; } ++ } ++ } break; ++ default : { // Global cumulation. ++ Tlong cumul = (Tlong)0; ++ cimg_for(*this,ptrd,T) { cumul+=(Tlong)*ptrd; *ptrd = (T)cumul; } ++ } ++ } ++ return *this; ++ } ++ ++ //! Cumulate image values, optionally along specified axis \newinstance. ++ CImg get_cumulate(const char axis=0) const { ++ return CImg(*this,false).cumulate(axis); ++ } ++ ++ //! Cumulate image values, along specified axes. ++ /** ++ \param axes Cumulation axes, as a C-string. ++ \note \c axes may contains multiple characters, e.g. \c "xyz" ++ **/ ++ CImg& cumulate(const char *const axes) { ++ for (const char *s = axes; *s; ++s) cumulate(*s); ++ return *this; ++ } ++ ++ //! Cumulate image values, along specified axes \newinstance. ++ CImg get_cumulate(const char *const axes) const { ++ return CImg(*this,false).cumulate(axes); ++ } ++ ++ //! Erode image by a structuring element. ++ /** ++ \param kernel Structuring element. ++ \param boundary_conditions Boundary conditions. ++ \param is_real Do the erosion in real (a.k.a 'non-flat') mode (\c true) rather than binary mode (\c false). ++ **/ ++ template ++ CImg& erode(const CImg& kernel, const bool boundary_conditions=true, ++ const bool is_real=false) { ++ if (is_empty() || !kernel) return *this; ++ return get_erode(kernel,boundary_conditions,is_real).move_to(*this); ++ } ++ ++ //! Erode image by a structuring element \newinstance. ++ template ++ CImg<_cimg_Tt> get_erode(const CImg& kernel, const bool boundary_conditions=true, ++ const bool is_real=false) const { ++ if (is_empty() || !kernel) return *this; ++ if (!is_real && kernel==0) return CImg(width(),height(),depth(),spectrum(),0); ++ typedef _cimg_Tt Tt; ++ CImg res(_width,_height,_depth,std::max(_spectrum,kernel._spectrum)); ++ const int ++ mx2 = kernel.width()/2, my2 = kernel.height()/2, mz2 = kernel.depth()/2, ++ mx1 = kernel.width() - mx2 - 1, my1 = kernel.height() - my2 - 1, mz1 = kernel.depth() - mz2 - 1, ++ mxe = width() - mx2, mye = height() - my2, mze = depth() - mz2; ++ const bool ++ is_inner_parallel = _width*_height*_depth>=32768, ++ is_outer_parallel = res.size()>=32768; ++ cimg::unused(is_inner_parallel,is_outer_parallel); ++ _cimg_abort_init_omp; ++ cimg_abort_init; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(!is_inner_parallel && is_outer_parallel)) ++ cimg_forC(res,c) _cimg_abort_try_omp { ++ cimg_abort_test; ++ const CImg img = get_shared_channel(c%_spectrum); ++ const CImg K = kernel.get_shared_channel(c%kernel._spectrum); ++ if (is_real) { // Real erosion ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(is_inner_parallel)) ++ 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 t mval = K(mx1 + xm,my1 + ym,mz1 + zm); ++ const Tt cval = (Tt)(img(x + xm,y + ym,z + zm) - mval); ++ if (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 = K(mx1 + xm,my1 + ym,mz1 + zm); ++ const Tt cval = (Tt)(img._atXYZ(x + xm,y + ym,z + zm) - mval); ++ if (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 = K(mx1 + xm,my1 + ym,mz1 + zm); ++ const Tt cval = (Tt)(img.atXYZ(x + xm,y + ym,z + zm,0,(T)0) - mval); ++ if (cval::max(); ++ for (int zm = -mz1; zm<=mz2; ++zm) ++ for (int ym = -my1; ym<=my2; ++ym) ++ for (int xm = -mx1; xm<=mx2; ++xm) ++ if (K(mx1 + xm,my1 + ym,mz1 + zm)) { ++ const Tt cval = (Tt)img(x + xm,y + ym,z + zm); ++ if (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) ++ if (K(mx1 + xm,my1 + ym,mz1 + zm)) { ++ const T cval = (Tt)img._atXYZ(x + xm,y + ym,z + zm); ++ if (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) ++ if (K(mx1 + xm,my1 + ym,mz1 + zm)) { ++ const T cval = (Tt)img.atXYZ(x + xm,y + ym,z + zm,0,(T)0); ++ if (cval& erode(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) { ++ if (is_empty() || (sx==1 && sy==1 && sz==1)) return *this; ++ if (sx>1 && _width>1) { // Along X-axis. ++ const int L = width(), off = 1, s = (int)sx, _s2 = s/2 + 1, _s1 = s - _s2, s1 = _s1>L?L:_s1, s2 = _s2>L?L:_s2; ++ CImg buf(L); ++ cimg_pragma_openmp(parallel for collapse(3) firstprivate(buf) if (size()>524288)) ++ cimg_forYZC(*this,y,z,c) { ++ T *const ptrdb = buf._data, *ptrd = buf._data, *const ptrde = buf._data + L - 1; ++ const T *const ptrsb = data(0,y,z,c), *ptrs = ptrsb, *const ptrse = ptrs + L*off - off; ++ T cur = *ptrs; ptrs+=off; bool is_first = true; ++ for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { ++ const T val = *ptrs; ptrs+=off; if (val<=cur) { cur = val; is_first = false; }} ++ *(ptrd++) = cur; ++ if (ptrs>=ptrse) { ++ T *pd = data(0,y,z,c); cur = std::min(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } ++ } else { ++ for (int p = s1; p>0 && ptrd<=ptrde; --p) { ++ const T val = *ptrs; if (ptrs0; --p) { ++ const T val = *ptrs; ptrs+=off; ++ if (is_first) { ++ const T *nptrs = ptrs - off; cur = val; ++ for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval0 && ptrs>=ptrsb; --p) { ++ const T val = *ptrs; ptrs-=off; if (val0 && ptrd>=ptrdb; --p) { ++ const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val1 && _height>1) { // Along Y-axis. ++ const int L = height(), off = width(), s = (int)sy, _s2 = s/2 + 1, _s1 = s - _s2, s1 = _s1>L?L:_s1, ++ s2 = _s2>L?L:_s2; ++ CImg buf(L); ++ cimg_pragma_openmp(parallel for collapse(3) firstprivate(buf) if (size()>524288)) ++ cimg_forXZC(*this,x,z,c) { ++ T *const ptrdb = buf._data, *ptrd = ptrdb, *const ptrde = buf._data + L - 1; ++ const T *const ptrsb = data(x,0,z,c), *ptrs = ptrsb, *const ptrse = ptrs + L*off - off; ++ T cur = *ptrs; ptrs+=off; bool is_first = true; ++ for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { ++ const T val = *ptrs; ptrs+=off; if (val<=cur) { cur = val; is_first = false; } ++ } ++ *(ptrd++) = cur; ++ if (ptrs>=ptrse) { ++ T *pd = data(x,0,z,c); cur = std::min(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } ++ } else { ++ for (int p = s1; p>0 && ptrd<=ptrde; --p) { ++ const T val = *ptrs; if (ptrs0; --p) { ++ const T val = *ptrs; ptrs+=off; ++ if (is_first) { ++ const T *nptrs = ptrs - off; cur = val; ++ for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval0 && ptrs>=ptrsb; --p) { ++ const T val = *ptrs; ptrs-=off; if (val0 && ptrd>=ptrdb; --p) { ++ const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val1 && _depth>1) { // Along Z-axis. ++ const int L = depth(), off = width()*height(), s = (int)sz, _s2 = s/2 + 1, _s1 = s - _s2, s1 = _s1>L?L:_s1, ++ s2 = _s2>L?L:_s2; ++ CImg buf(L); ++ cimg_pragma_openmp(parallel for collapse(3) firstprivate(buf) if (size()>524288)) ++ cimg_forXYC(*this,x,y,c) { ++ T *const ptrdb = buf._data, *ptrd = ptrdb, *const ptrde = buf._data + L - 1; ++ const T *const ptrsb = data(x,y,0,c), *ptrs = ptrsb, *const ptrse = ptrs + L*off - off; ++ T cur = *ptrs; ptrs+=off; bool is_first = true; ++ for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { ++ const T val = *ptrs; ptrs+=off; if (val<=cur) { cur = val; is_first = false; } ++ } ++ *(ptrd++) = cur; ++ if (ptrs>=ptrse) { ++ T *pd = data(x,y,0,c); cur = std::min(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } ++ } else { ++ for (int p = s1; p>0 && ptrd<=ptrde; --p) { ++ const T val = *ptrs; if (ptrs0; --p) { ++ const T val = *ptrs; ptrs+=off; ++ if (is_first) { ++ const T *nptrs = ptrs - off; cur = val; ++ for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval0 && ptrs>=ptrsb; --p) { ++ const T val = *ptrs; ptrs-=off; if (val0 && ptrd>=ptrdb; --p) { ++ const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val get_erode(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) const { ++ return (+*this).erode(sx,sy,sz); ++ } ++ ++ //! Erode the image by a square structuring element of specified size. ++ /** ++ \param s Size of the structuring element. ++ **/ ++ CImg& erode(const unsigned int s) { ++ return erode(s,s,s); ++ } ++ ++ //! Erode the image by a square structuring element of specified size \newinstance. ++ CImg get_erode(const unsigned int s) const { ++ return (+*this).erode(s); ++ } ++ ++ //! Dilate image by a structuring element. ++ /** ++ \param kernel Structuring element. ++ \param boundary_conditions Boundary conditions. ++ \param is_real Do the dilation in real (a.k.a 'non-flat') mode (\c true) rather than binary mode (\c false). ++ **/ ++ template ++ CImg& dilate(const CImg& kernel, const bool boundary_conditions=true, ++ const bool is_real=false) { ++ if (is_empty() || !kernel) return *this; ++ return get_dilate(kernel,boundary_conditions,is_real).move_to(*this); ++ } ++ ++ //! Dilate image by a structuring element \newinstance. ++ template ++ CImg<_cimg_Tt> get_dilate(const CImg& kernel, const bool boundary_conditions=true, ++ const bool is_real=false) const { ++ if (is_empty() || !kernel || (!is_real && kernel==0)) return *this; ++ typedef _cimg_Tt Tt; ++ CImg res(_width,_height,_depth,std::max(_spectrum,kernel._spectrum)); ++ const int ++ mx1 = kernel.width()/2, my1 = kernel.height()/2, mz1 = kernel.depth()/2, ++ mx2 = kernel.width() - mx1 - 1, my2 = kernel.height() - my1 - 1, mz2 = kernel.depth() - mz1 - 1, ++ mxe = width() - mx2, mye = height() - my2, mze = depth() - mz2; ++ const bool ++ is_inner_parallel = _width*_height*_depth>=32768, ++ is_outer_parallel = res.size()>=32768; ++ cimg::unused(is_inner_parallel,is_outer_parallel); ++ _cimg_abort_init_omp; ++ cimg_abort_init; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(!is_inner_parallel && is_outer_parallel)) ++ cimg_forC(res,c) _cimg_abort_try_omp { ++ cimg_abort_test; ++ const CImg img = get_shared_channel(c%_spectrum); ++ const CImg K = kernel.get_shared_channel(c%kernel._spectrum); ++ if (is_real) { // Real dilation ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(is_inner_parallel)) ++ 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 = K(mx2 - xm,my2 - ym,mz2 - zm); ++ const Tt cval = (Tt)(img(x + xm,y + ym,z + zm) + mval); ++ if (cval>max_val) max_val = cval; ++ } ++ res(x,y,z,c) = max_val; ++ } _cimg_abort_catch_omp2 ++ if (boundary_conditions) ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(is_inner_parallel)) ++ cimg_forYZ(res,y,z) _cimg_abort_try_omp2 { ++ cimg_abort_test2; ++ 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 = K(mx2 - xm,my2 - ym,mz2 - zm); ++ const Tt cval = (Tt)(img._atXYZ(x + xm,y + ym,z + zm) + mval); ++ if (cval>max_val) max_val = cval; ++ } ++ res(x,y,z,c) = max_val; ++ } ++ } _cimg_abort_catch_omp2 ++ else ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(is_inner_parallel)) ++ cimg_forYZ(*this,y,z) _cimg_abort_try_omp2 { ++ cimg_abort_test2; ++ 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 = K(mx2 - xm,my2 - ym,mz2 - zm); ++ const Tt cval = (Tt)(img.atXYZ(x + xm,y + ym,z + zm,0,(T)0) + mval); ++ if (cval>max_val) max_val = cval; ++ } ++ res(x,y,z,c) = max_val; ++ } ++ } _cimg_abort_catch_omp2 ++ } else { // Binary dilation ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(is_inner_parallel)) ++ 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) ++ if (K(mx2 - xm,my2 - ym,mz2 - zm)) { ++ const Tt cval = (Tt)img(x + xm,y + ym,z + zm); ++ if (cval>max_val) max_val = cval; ++ } ++ res(x,y,z,c) = max_val; ++ } _cimg_abort_catch_omp2 ++ if (boundary_conditions) ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(is_inner_parallel)) ++ cimg_forYZ(res,y,z) _cimg_abort_try_omp2 { ++ cimg_abort_test2; ++ 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) ++ if (K(mx2 - xm,my2 - ym,mz2 - zm)) { ++ const T cval = (Tt)img._atXYZ(x + xm,y + ym,z + zm); ++ if (cval>max_val) max_val = cval; ++ } ++ res(x,y,z,c) = max_val; ++ } ++ } _cimg_abort_catch_omp2 ++ else ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(is_inner_parallel)) ++ cimg_forYZ(res,y,z) _cimg_abort_try_omp2 { ++ cimg_abort_test2; ++ 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) ++ if (K(mx2 - xm,my2 - ym,mz2 - zm)) { ++ const T cval = (Tt)img.atXYZ(x + xm,y + ym,z + zm,0,(T)0); ++ if (cval>max_val) max_val = cval; ++ } ++ res(x,y,z,c) = max_val; ++ } ++ } _cimg_abort_catch_omp2 ++ } ++ } _cimg_abort_catch_omp ++ cimg_abort_test; ++ return res; ++ } ++ ++ //! Dilate image by a rectangular structuring element of specified size. ++ /** ++ \param sx Width of the structuring element. ++ \param sy Height of the structuring element. ++ \param sz Depth of the structuring element. ++ **/ ++ CImg& dilate(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) { ++ if (is_empty() || (sx==1 && sy==1 && sz==1)) return *this; ++ if (sx>1 && _width>1) { // Along X-axis. ++ const int L = width(), off = 1, s = (int)sx, _s1 = s/2, _s2 = s - _s1, s1 = _s1>L?L:_s1, s2 = _s2>L?L:_s2; ++ CImg buf(L); ++ cimg_pragma_openmp(parallel for collapse(3) firstprivate(buf) if (size()>524288)) ++ cimg_forYZC(*this,y,z,c) { ++ T *const ptrdb = buf._data, *ptrd = ptrdb, *const ptrde = buf._data + L - 1; ++ const T *const ptrsb = data(0,y,z,c), *ptrs = ptrsb, *const ptrse = ptrs + L*off - off; ++ T cur = *ptrs; ptrs+=off; bool is_first = true; ++ for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { ++ const T val = *ptrs; ptrs+=off; if (val>=cur) { cur = val; is_first = false; } ++ } ++ *(ptrd++) = cur; ++ if (ptrs>=ptrse) { ++ T *pd = data(0,y,z,c); cur = std::max(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } ++ } else { ++ for (int p = s1; p>0 && ptrd<=ptrde; --p) { ++ const T val = *ptrs; if (ptrs=cur) { cur = val; is_first = false; } ++ *(ptrd++) = cur; ++ } ++ for (int p = L - s - 1; p>0; --p) { ++ const T val = *ptrs; ptrs+=off; ++ if (is_first) { ++ const T *nptrs = ptrs - off; cur = val; ++ for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval>cur) cur = nval; } ++ nptrs-=off; const T nval = *nptrs; if (nval>cur) { cur = nval; is_first = true; } else is_first = false; ++ } else { if (val>=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; } ++ *(ptrd++) = cur; ++ } ++ ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off; ++ for (int p = s1; p>0 && ptrs>=ptrsb; --p) { ++ const T val = *ptrs; ptrs-=off; if (val>cur) cur = val; ++ } ++ *(ptrd--) = cur; ++ for (int p = s2 - 1; p>0 && ptrd>=ptrdb; --p) { ++ const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val>cur) cur = val; *(ptrd--) = cur; ++ } ++ T *pd = data(0,y,z,c); cimg_for(buf,ps,T) { *pd = *ps; pd+=off; } ++ } ++ } ++ } ++ ++ if (sy>1 && _height>1) { // Along Y-axis. ++ const int L = height(), off = width(), s = (int)sy, _s1 = s/2, _s2 = s - _s1, s1 = _s1>L?L:_s1, ++ s2 = _s2>L?L:_s2; ++ CImg buf(L); ++ cimg_pragma_openmp(parallel for collapse(3) firstprivate(buf) if (size()>524288)) ++ cimg_forXZC(*this,x,z,c) { ++ T *const ptrdb = buf._data, *ptrd = ptrdb, *const ptrde = buf._data + L - 1; ++ const T *const ptrsb = data(x,0,z,c), *ptrs = ptrsb, *const ptrse = ptrs + L*off - off; ++ T cur = *ptrs; ptrs+=off; bool is_first = true; ++ for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { ++ const T val = *ptrs; ptrs+=off; if (val>=cur) { cur = val; is_first = false; } ++ } ++ *(ptrd++) = cur; ++ if (ptrs>=ptrse) { ++ T *pd = data(x,0,z,c); cur = std::max(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } ++ } else { ++ for (int p = s1; p>0 && ptrd<=ptrde; --p) { ++ const T val = *ptrs; if (ptrs=cur) { cur = val; is_first = false; } ++ *(ptrd++) = cur; ++ } ++ for (int p = L - s - 1; p>0; --p) { ++ const T val = *ptrs; ptrs+=off; ++ if (is_first) { ++ const T *nptrs = ptrs - off; cur = val; ++ for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval>cur) cur = nval; } ++ nptrs-=off; const T nval = *nptrs; if (nval>cur) { cur = nval; is_first = true; } else is_first = false; ++ } else { if (val>=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; } ++ *(ptrd++) = cur; ++ } ++ ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off; ++ for (int p = s1; p>0 && ptrs>=ptrsb; --p) { ++ const T val = *ptrs; ptrs-=off; if (val>cur) cur = val; ++ } ++ *(ptrd--) = cur; ++ for (int p = s2 - 1; p>0 && ptrd>=ptrdb; --p) { ++ const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val>cur) cur = val; *(ptrd--) = cur; ++ } ++ T *pd = data(x,0,z,c); cimg_for(buf,ps,T) { *pd = *ps; pd+=off; } ++ } ++ } ++ } ++ ++ if (sz>1 && _depth>1) { // Along Z-axis. ++ const int L = depth(), off = width()*height(), s = (int)sz, _s1 = s/2, _s2 = s - _s1, s1 = _s1>L?L:_s1, ++ s2 = _s2>L?L:_s2; ++ CImg buf(L); ++ cimg_pragma_openmp(parallel for collapse(3) firstprivate(buf) if (size()>524288)) ++ cimg_forXYC(*this,x,y,c) { ++ T *const ptrdb = buf._data, *ptrd = ptrdb, *const ptrde = buf._data + L - 1; ++ const T *const ptrsb = data(x,y,0,c), *ptrs = ptrsb, *const ptrse = ptrs + L*off - off; ++ T cur = *ptrs; ptrs+=off; bool is_first = true; ++ for (int p = s2 - 1; p>0 && ptrs<=ptrse; --p) { ++ const T val = *ptrs; ptrs+=off; if (val>=cur) { cur = val; is_first = false; } ++ } ++ *(ptrd++) = cur; ++ if (ptrs>=ptrse) { ++ T *pd = data(x,y,0,c); cur = std::max(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; } ++ } else { ++ for (int p = s1; p>0 && ptrd<=ptrde; --p) { ++ const T val = *ptrs; if (ptrs=cur) { cur = val; is_first = false; } ++ *(ptrd++) = cur; ++ } ++ for (int p = L - s - 1; p>0; --p) { ++ const T val = *ptrs; ptrs+=off; ++ if (is_first) { ++ const T *nptrs = ptrs - off; cur = val; ++ for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval>cur) cur = nval; } ++ nptrs-=off; const T nval = *nptrs; if (nval>cur) { cur = nval; is_first = true; } else is_first = false; ++ } else { if (val>=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; } ++ *(ptrd++) = cur; ++ } ++ ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off; ++ for (int p = s1; p>0 && ptrs>=ptrsb; --p) { ++ const T val = *ptrs; ptrs-=off; if (val>cur) cur = val; ++ } ++ *(ptrd--) = cur; ++ for (int p = s2 - 1; p>0 && ptrd>=ptrdb; --p) { ++ const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val>cur) cur = val; *(ptrd--) = cur; ++ } ++ T *pd = data(x,y,0,c); cimg_for(buf,ps,T) { *pd = *ps; pd+=off; } ++ } ++ } ++ } ++ return *this; ++ } ++ ++ //! Dilate image by a rectangular structuring element of specified size \newinstance. ++ CImg get_dilate(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) const { ++ return (+*this).dilate(sx,sy,sz); ++ } ++ ++ //! Dilate image by a square structuring element of specified size. ++ /** ++ \param s Size of the structuring element. ++ **/ ++ CImg& dilate(const unsigned int s) { ++ return dilate(s,s,s); ++ } ++ ++ //! Dilate image by a square structuring element of specified size \newinstance. ++ CImg get_dilate(const unsigned int s) const { ++ return (+*this).dilate(s); ++ } ++ ++ //! Compute watershed transform. ++ /** ++ \param priority Priority map. ++ \param is_high_connectivity Boolean that choose between 4(false)- or 8(true)-connectivity ++ in 2d case, and between 6(false)- or 26(true)-connectivity in 3d case. ++ \note Non-zero values of the instance instance are propagated to zero-valued ones according to ++ specified the priority map. ++ **/ ++ template ++ CImg& watershed(const CImg& priority, const bool is_high_connectivity=false) { ++#define _cimg_watershed_init(cond,X,Y,Z) \ ++ if (cond && !(*this)(X,Y,Z)) Q._priority_queue_insert(labels,sizeQ,priority(X,Y,Z),X,Y,Z,nb_seeds) ++ ++#define _cimg_watershed_propagate(cond,X,Y,Z) \ ++ if (cond) { \ ++ if ((*this)(X,Y,Z)) { \ ++ ns = labels(X,Y,Z) - 1; xs = seeds(ns,0); ys = seeds(ns,1); zs = seeds(ns,2); \ ++ d = cimg::sqr((float)x - xs) + cimg::sqr((float)y - ys) + cimg::sqr((float)z - zs); \ ++ if (d labels(_width,_height,_depth,1,0), seeds(64,3); ++ CImg::type> Q; ++ unsigned int sizeQ = 0; ++ int px, nx, py, ny, pz, nz; ++ bool is_px, is_nx, is_py, is_ny, is_pz, is_nz; ++ const bool is_3d = _depth>1; ++ ++ // Find seed points and insert them in priority queue. ++ unsigned int nb_seeds = 0; ++ const T *ptrs = _data; ++ cimg_forXYZ(*this,x,y,z) if (*(ptrs++)) { // 3d version ++ if (nb_seeds>=seeds._width) seeds.resize(2*seeds._width,3,1,1,0); ++ seeds(nb_seeds,0) = x; seeds(nb_seeds,1) = y; seeds(nb_seeds++,2) = z; ++ px = x - 1; nx = x + 1; ++ py = y - 1; ny = y + 1; ++ pz = z - 1; nz = z + 1; ++ is_px = px>=0; is_nx = nx=0; is_ny = ny=0; is_nz = nz=0; is_nx = nx=0; is_ny = ny=0; is_nz = nz::inf(); ++ T label = (T)0; ++ _cimg_watershed_propagate(is_px,px,y,z); ++ _cimg_watershed_propagate(is_nx,nx,y,z); ++ _cimg_watershed_propagate(is_py,x,py,z); ++ _cimg_watershed_propagate(is_ny,x,ny,z); ++ if (is_3d) { ++ _cimg_watershed_propagate(is_pz,x,y,pz); ++ _cimg_watershed_propagate(is_nz,x,y,nz); ++ } ++ if (is_high_connectivity) { ++ _cimg_watershed_propagate(is_px && is_py,px,py,z); ++ _cimg_watershed_propagate(is_nx && is_py,nx,py,z); ++ _cimg_watershed_propagate(is_px && is_ny,px,ny,z); ++ _cimg_watershed_propagate(is_nx && is_ny,nx,ny,z); ++ if (is_3d) { ++ _cimg_watershed_propagate(is_px && is_pz,px,y,pz); ++ _cimg_watershed_propagate(is_nx && is_pz,nx,y,pz); ++ _cimg_watershed_propagate(is_px && is_nz,px,y,nz); ++ _cimg_watershed_propagate(is_nx && is_nz,nx,y,nz); ++ _cimg_watershed_propagate(is_py && is_pz,x,py,pz); ++ _cimg_watershed_propagate(is_ny && is_pz,x,ny,pz); ++ _cimg_watershed_propagate(is_py && is_nz,x,py,nz); ++ _cimg_watershed_propagate(is_ny && is_nz,x,ny,nz); ++ _cimg_watershed_propagate(is_px && is_py && is_pz,px,py,pz); ++ _cimg_watershed_propagate(is_nx && is_py && is_pz,nx,py,pz); ++ _cimg_watershed_propagate(is_px && is_ny && is_pz,px,ny,pz); ++ _cimg_watershed_propagate(is_nx && is_ny && is_pz,nx,ny,pz); ++ _cimg_watershed_propagate(is_px && is_py && is_nz,px,py,nz); ++ _cimg_watershed_propagate(is_nx && is_py && is_nz,nx,py,nz); ++ _cimg_watershed_propagate(is_px && is_ny && is_nz,px,ny,nz); ++ _cimg_watershed_propagate(is_nx && is_ny && is_nz,nx,ny,nz); ++ } ++ } ++ (*this)(x,y,z) = label; ++ labels(x,y,z) = ++nmin; ++ } ++ return *this; ++ } ++ ++ //! Compute watershed transform \newinstance. ++ template ++ CImg get_watershed(const CImg& priority, const bool is_high_connectivity=false) const { ++ return (+*this).watershed(priority,is_high_connectivity); ++ } ++ ++ // [internal] Insert/Remove items in priority queue, for watershed/distance transforms. ++ template ++ bool _priority_queue_insert(CImg& is_queued, unsigned int& siz, const tv value, ++ const unsigned int x, const unsigned int y, const unsigned int z, ++ const unsigned int n=1) { ++ if (is_queued(x,y,z)) return false; ++ is_queued(x,y,z) = (tq)n; ++ if (++siz>=_width) { if (!is_empty()) resize(_width*2,4,1,1,0); else assign(64,4); } ++ (*this)(siz - 1,0) = (T)value; ++ (*this)(siz - 1,1) = (T)x; ++ (*this)(siz - 1,2) = (T)y; ++ (*this)(siz - 1,3) = (T)z; ++ for (unsigned int pos = siz - 1, par = 0; pos && value>(*this)(par=(pos + 1)/2 - 1,0); pos = par) { ++ cimg::swap((*this)(pos,0),(*this)(par,0)); ++ cimg::swap((*this)(pos,1),(*this)(par,1)); ++ cimg::swap((*this)(pos,2),(*this)(par,2)); ++ cimg::swap((*this)(pos,3),(*this)(par,3)); ++ } ++ return true; ++ } ++ ++ CImg& _priority_queue_remove(unsigned int& siz) { ++ (*this)(0,0) = (*this)(--siz,0); ++ (*this)(0,1) = (*this)(siz,1); ++ (*this)(0,2) = (*this)(siz,2); ++ (*this)(0,3) = (*this)(siz,3); ++ const float value = (*this)(0,0); ++ for (unsigned int pos = 0, left = 0, right = 0; ++ ((right=2*(pos + 1),(left=right - 1))(*this)(right,0)) { ++ cimg::swap((*this)(pos,0),(*this)(left,0)); ++ cimg::swap((*this)(pos,1),(*this)(left,1)); ++ cimg::swap((*this)(pos,2),(*this)(left,2)); ++ cimg::swap((*this)(pos,3),(*this)(left,3)); ++ pos = left; ++ } else { ++ cimg::swap((*this)(pos,0),(*this)(right,0)); ++ cimg::swap((*this)(pos,1),(*this)(right,1)); ++ cimg::swap((*this)(pos,2),(*this)(right,2)); ++ cimg::swap((*this)(pos,3),(*this)(right,3)); ++ pos = right; ++ } ++ } else { ++ cimg::swap((*this)(pos,0),(*this)(left,0)); ++ cimg::swap((*this)(pos,1),(*this)(left,1)); ++ cimg::swap((*this)(pos,2),(*this)(left,2)); ++ cimg::swap((*this)(pos,3),(*this)(left,3)); ++ pos = left; ++ } ++ } ++ return *this; ++ } ++ ++ //! Apply recursive Deriche filter. ++ /** ++ \param sigma Standard deviation of the filter. ++ \param order Order of the filter. Can be { 0=smooth-filter | 1=1st-derivative | 2=2nd-derivative }. ++ \param axis Axis along which the filter is computed. Can be { 'x' | 'y' | 'z' | 'c' }. ++ \param boundary_conditions Boundary conditions. Can be { 0=dirichlet | 1=neumann }. ++ **/ ++ CImg& deriche(const float sigma, const unsigned int order=0, const char axis='x', ++ const bool boundary_conditions=true) { ++#define _cimg_deriche_apply \ ++ CImg Y(N); \ ++ Tfloat *ptrY = Y._data, yb = 0, yp = 0; \ ++ T xp = (T)0; \ ++ if (boundary_conditions) { 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); \ ++ } ++ const char naxis = cimg::lowercase(axis); ++ const float nsigma = sigma>=0?sigma:-sigma*(naxis=='x'?_width:naxis=='y'?_height:naxis=='z'?_depth:_spectrum)/100; ++ if (is_empty() || (nsigma<0.1f && !order)) return *this; ++ const float ++ nnsigma = nsigma<0.1f?0.1f:nsigma, ++ alpha = 1.695f/nnsigma, ++ ema = (float)std::exp(-alpha), ++ ema2 = (float)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)*(1-ema)/(2*(ema + 1)*ema); ++ a0 = a3 = 0; ++ a1 = k*ema; ++ a2 = -a1; ++ } break; ++ case 2 : { ++ const float ++ ea = (float)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_instance ++ "deriche(): Invalid specified filter order %u " ++ "(should be { 0=smoothing | 1=1st-derivative | 2=2nd-derivative }).", ++ cimg_instance, ++ order); ++ } ++ coefp = (a0 + a1)/(1 + b1 + b2); ++ coefn = (a2 + a3)/(1 + b1 + b2); ++ switch (naxis) { ++ case 'x' : { ++ const int N = width(); ++ const ulongT off = 1U; ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth*_spectrum>=16)) ++ cimg_forYZC(*this,y,z,c) { T *ptrX = data(0,y,z,c); _cimg_deriche_apply; } ++ } break; ++ case 'y' : { ++ const int N = height(); ++ const ulongT off = (ulongT)_width; ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth*_spectrum>=16)) ++ cimg_forXZC(*this,x,z,c) { T *ptrX = data(x,0,z,c); _cimg_deriche_apply; } ++ } break; ++ case 'z' : { ++ const int N = depth(); ++ const ulongT off = (ulongT)_width*_height; ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth*_spectrum>=16)) ++ cimg_forXYC(*this,x,y,c) { T *ptrX = data(x,y,0,c); _cimg_deriche_apply; } ++ } break; ++ default : { ++ const int N = spectrum(); ++ const ulongT off = (ulongT)_width*_height*_depth; ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth*_spectrum>=16)) ++ cimg_forXYZ(*this,x,y,z) { T *ptrX = data(x,y,z,0); _cimg_deriche_apply; } ++ } ++ } ++ return *this; ++ } ++ ++ //! Apply recursive Deriche filter \newinstance. ++ CImg get_deriche(const float sigma, const unsigned int order=0, const char axis='x', ++ const bool boundary_conditions=true) const { ++ return CImg(*this,false).deriche(sigma,order,axis,boundary_conditions); ++ } ++ ++ // [internal] Apply a recursive filter (used by CImg::vanvliet()). ++ /* ++ \param ptr the pointer of the data ++ \param filter the coefficient of the filter in the following order [n,n - 1,n - 2,n - 3]. ++ \param N size of the data ++ \param off the offset between two data point ++ \param order the order of the filter 0 (smoothing), 1st derivtive, 2nd derivative, 3rd derivative ++ \param boundary_conditions Boundary conditions. Can be { 0=dirichlet | 1=neumann }. ++ \note Boundary condition using B. Triggs method (IEEE trans on Sig Proc 2005). ++ */ ++ static void _cimg_recursive_apply(T *data, const double filter[], const int N, const ulongT off, ++ const unsigned int order, const bool boundary_conditions) { ++ double val[4] = { 0 }; // res[n,n - 1,n - 2,n - 3,..] or res[n,n + 1,n + 2,n + 3,..] ++ const double ++ sumsq = filter[0], sum = sumsq * sumsq, ++ a1 = filter[1], a2 = filter[2], a3 = filter[3], ++ scaleM = 1.0 / ( (1.0 + a1 - a2 + a3) * (1.0 - a1 - a2 - a3) * (1.0 + a2 + (a1 - a3) * a3) ); ++ double M[9]; // Triggs matrix ++ M[0] = scaleM * (-a3 * a1 + 1.0 - a3 * a3 - a2); ++ M[1] = scaleM * (a3 + a1) * (a2 + a3 * a1); ++ M[2] = scaleM * a3 * (a1 + a3 * a2); ++ M[3] = scaleM * (a1 + a3 * a2); ++ M[4] = -scaleM * (a2 - 1.0) * (a2 + a3 * a1); ++ M[5] = -scaleM * a3 * (a3 * a1 + a3 * a3 + a2 - 1.0); ++ M[6] = scaleM * (a3 * a1 + a2 + a1 * a1 - a2 * a2); ++ M[7] = scaleM * (a1 * a2 + a3 * a2 * a2 - a1 * a3 * a3 - a3 * a3 * a3 - a3 * a2 + a3); ++ M[8] = scaleM * a3 * (a1 + a3 * a2); ++ switch (order) { ++ case 0 : { ++ const double iplus = (boundary_conditions?data[(N - 1)*off]:(T)0); ++ for (int pass = 0; pass<2; ++pass) { ++ if (!pass) { ++ for (int k = 1; k<4; ++k) val[k] = (boundary_conditions?*data/sumsq:0); ++ } else { ++ // apply Triggs boundary conditions ++ const double ++ uplus = iplus/(1.0 - a1 - a2 - a3), vplus = uplus/(1.0 - a1 - a2 - a3), ++ unp = val[1] - uplus, unp1 = val[2] - uplus, unp2 = val[3] - uplus; ++ val[0] = (M[0] * unp + M[1] * unp1 + M[2] * unp2 + vplus) * sum; ++ val[1] = (M[3] * unp + M[4] * unp1 + M[5] * unp2 + vplus) * sum; ++ val[2] = (M[6] * unp + M[7] * unp1 + M[8] * unp2 + vplus) * sum; ++ *data = (T)val[0]; ++ data -= off; ++ for (int k = 3; k>0; --k) val[k] = val[k - 1]; ++ } ++ for (int n = pass; n0; --k) val[k] = val[k - 1]; ++ } ++ if (!pass) data -= off; ++ } ++ } break; ++ case 1 : { ++ double x[3]; // [front,center,back] ++ for (int pass = 0; pass<2; ++pass) { ++ if (!pass) { ++ for (int k = 0; k<3; ++k) x[k] = (boundary_conditions?*data:(T)0); ++ for (int k = 0; k<4; ++k) val[k] = 0; ++ } else { ++ // apply Triggs boundary conditions ++ const double ++ unp = val[1], unp1 = val[2], unp2 = val[3]; ++ val[0] = (M[0] * unp + M[1] * unp1 + M[2] * unp2) * sum; ++ val[1] = (M[3] * unp + M[4] * unp1 + M[5] * unp2) * sum; ++ val[2] = (M[6] * unp + M[7] * unp1 + M[8] * unp2) * sum; ++ *data = (T)val[0]; ++ data -= off; ++ for (int k = 3; k>0; --k) val[k] = val[k - 1]; ++ } ++ for (int n = pass; n0; --k) x[k] = x[k - 1]; ++ } else { data-=off;} ++ for (int k = 3; k>0; --k) val[k] = val[k - 1]; ++ } ++ *data = (T)0; ++ } ++ } break; ++ case 2: { ++ double x[3]; // [front,center,back] ++ for (int pass = 0; pass<2; ++pass) { ++ if (!pass) { ++ for (int k = 0; k<3; ++k) x[k] = (boundary_conditions?*data:(T)0); ++ for (int k = 0; k<4; ++k) val[k] = 0; ++ } else { ++ // apply Triggs boundary conditions ++ const double ++ unp = val[1], unp1 = val[2], unp2 = val[3]; ++ val[0] = (M[0] * unp + M[1] * unp1 + M[2] * unp2) * sum; ++ val[1] = (M[3] * unp + M[4] * unp1 + M[5] * unp2) * sum; ++ val[2] = (M[6] * unp + M[7] * unp1 + M[8] * unp2) * sum; ++ *data = (T)val[0]; ++ data -= off; ++ for (int k = 3; k>0; --k) val[k] = val[k - 1]; ++ } ++ for (int n = pass; n0; --k) x[k] = x[k - 1]; ++ for (int k = 3; k>0; --k) val[k] = val[k - 1]; ++ } ++ *data = (T)0; ++ } ++ } break; ++ case 3: { ++ double x[3]; // [front,center,back] ++ for (int pass = 0; pass<2; ++pass) { ++ if (!pass) { ++ for (int k = 0; k<3; ++k) x[k] = (boundary_conditions?*data:(T)0); ++ for (int k = 0; k<4; ++k) val[k] = 0; ++ } else { ++ // apply Triggs boundary conditions ++ const double ++ unp = val[1], unp1 = val[2], unp2 = val[3]; ++ val[0] = (M[0] * unp + M[1] * unp1 + M[2] * unp2) * sum; ++ val[1] = (M[3] * unp + M[4] * unp1 + M[5] * unp2) * sum; ++ val[2] = (M[6] * unp + M[7] * unp1 + M[8] * unp2) * sum; ++ *data = (T)val[0]; ++ data -= off; ++ for (int k = 3; k>0; --k) val[k] = val[k - 1]; ++ } ++ for (int n = pass; n0; --k) x[k] = x[k - 1]; ++ for (int k = 3; k>0; --k) val[k] = val[k - 1]; ++ } ++ *data = (T)0; ++ } ++ } break; ++ } ++ } ++ ++ //! Van Vliet recursive Gaussian filter. ++ /** ++ \param sigma standard deviation of the Gaussian filter ++ \param order the order of the filter 0,1,2,3 ++ \param axis Axis along which the filter is computed. Can be { 'x' | 'y' | 'z' | 'c' }. ++ \param boundary_conditions Boundary conditions. Can be { 0=dirichlet | 1=neumann }. ++ \note dirichlet boundary condition has a strange behavior ++ ++ I.T. Young, L.J. van Vliet, M. van Ginkel, Recursive Gabor filtering. ++ IEEE Trans. Sig. Proc., vol. 50, pp. 2799-2805, 2002. ++ ++ (this is an improvement over Young-Van Vliet, Sig. Proc. 44, 1995) ++ ++ Boundary conditions (only for order 0) using Triggs matrix, from ++ B. Triggs and M. Sdika. Boundary conditions for Young-van Vliet ++ recursive filtering. IEEE Trans. Signal Processing, ++ vol. 54, pp. 2365-2367, 2006. ++ **/ ++ CImg& vanvliet(const float sigma, const unsigned int order, const char axis='x', ++ const bool boundary_conditions=true) { ++ if (is_empty()) return *this; ++ if (!cimg::type::is_float()) ++ return CImg(*this,false).vanvliet(sigma,order,axis,boundary_conditions).move_to(*this); ++ const char naxis = cimg::lowercase(axis); ++ const float nsigma = sigma>=0?sigma:-sigma*(naxis=='x'?_width:naxis=='y'?_height:naxis=='z'?_depth:_spectrum)/100; ++ if (is_empty() || (nsigma<0.5f && !order)) return *this; ++ const double ++ nnsigma = nsigma<0.5f?0.5f:nsigma, ++ m0 = 1.16680, m1 = 1.10783, m2 = 1.40586, ++ m1sq = m1 * m1, m2sq = m2 * m2, ++ q = (nnsigma<3.556?-0.2568 + 0.5784*nnsigma + 0.0561*nnsigma*nnsigma:2.5091 + 0.9804*(nnsigma - 3.556)), ++ qsq = q * q, ++ scale = (m0 + q) * (m1sq + m2sq + 2 * m1 * q + qsq), ++ b1 = -q * (2 * m0 * m1 + m1sq + m2sq + (2 * m0 + 4 * m1) * q + 3 * qsq) / scale, ++ b2 = qsq * (m0 + 2 * m1 + 3 * q) / scale, ++ b3 = -qsq * q / scale, ++ B = ( m0 * (m1sq + m2sq) ) / scale; ++ double filter[4]; ++ filter[0] = B; filter[1] = -b1; filter[2] = -b2; filter[3] = -b3; ++ switch (naxis) { ++ case 'x' : { ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth*_spectrum>=16)) ++ cimg_forYZC(*this,y,z,c) ++ _cimg_recursive_apply(data(0,y,z,c),filter,_width,1U,order,boundary_conditions); ++ } break; ++ case 'y' : { ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth*_spectrum>=16)) ++ cimg_forXZC(*this,x,z,c) ++ _cimg_recursive_apply(data(x,0,z,c),filter,_height,(ulongT)_width,order,boundary_conditions); ++ } break; ++ case 'z' : { ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth*_spectrum>=16)) ++ cimg_forXYC(*this,x,y,c) ++ _cimg_recursive_apply(data(x,y,0,c),filter,_depth,(ulongT)_width*_height, ++ order,boundary_conditions); ++ } break; ++ default : { ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth*_spectrum>=16)) ++ cimg_forXYZ(*this,x,y,z) ++ _cimg_recursive_apply(data(x,y,z,0),filter,_spectrum,(ulongT)_width*_height*_depth, ++ order,boundary_conditions); ++ } ++ } ++ return *this; ++ } ++ ++ //! Blur image using Van Vliet recursive Gaussian filter. \newinstance. ++ CImg get_vanvliet(const float sigma, const unsigned int order, const char axis='x', ++ const bool boundary_conditions=true) const { ++ return CImg(*this,false).vanvliet(sigma,order,axis,boundary_conditions); ++ } ++ ++ //! Blur image. ++ /** ++ \param sigma_x Standard deviation of the blur, along the X-axis. ++ \param sigma_y Standard deviation of the blur, along the Y-axis. ++ \param sigma_z Standard deviation of the blur, along the Z-axis. ++ \param boundary_conditions Boundary conditions. Can be { false=dirichlet | true=neumann }. ++ \param is_gaussian Tells if the blur uses a gaussian (\c true) or quasi-gaussian (\c false) kernel. ++ \note ++ - The blur is computed as a 0-order Deriche filter. This is not a gaussian blur. ++ - This is a recursive algorithm, not depending on the values of the standard deviations. ++ \see deriche(), vanvliet(). ++ **/ ++ CImg& blur(const float sigma_x, const float sigma_y, const float sigma_z, ++ const bool boundary_conditions=true, const bool is_gaussian=false) { ++ if (is_empty()) return *this; ++ if (is_gaussian) { ++ if (_width>1) vanvliet(sigma_x,0,'x',boundary_conditions); ++ if (_height>1) vanvliet(sigma_y,0,'y',boundary_conditions); ++ if (_depth>1) vanvliet(sigma_z,0,'z',boundary_conditions); ++ } else { ++ if (_width>1) deriche(sigma_x,0,'x',boundary_conditions); ++ if (_height>1) deriche(sigma_y,0,'y',boundary_conditions); ++ if (_depth>1) deriche(sigma_z,0,'z',boundary_conditions); ++ } ++ return *this; ++ } ++ ++ //! Blur image \newinstance. ++ CImg get_blur(const float sigma_x, const float sigma_y, const float sigma_z, ++ const bool boundary_conditions=true, const bool is_gaussian=false) const { ++ return CImg(*this,false).blur(sigma_x,sigma_y,sigma_z,boundary_conditions,is_gaussian); ++ } ++ ++ //! Blur image isotropically. ++ /** ++ \param sigma Standard deviation of the blur. ++ \param boundary_conditions Boundary conditions. Can be { 0=dirichlet | 1=neumann }.a ++ \param is_gaussian Use a gaussian kernel (VanVliet) is set, a pseudo-gaussian (Deriche) otherwise. ++ \see deriche(), vanvliet(). ++ **/ ++ CImg& blur(const float sigma, const bool boundary_conditions=true, const bool is_gaussian=false) { ++ const float nsigma = sigma>=0?sigma:-sigma*cimg::max(_width,_height,_depth)/100; ++ return blur(nsigma,nsigma,nsigma,boundary_conditions,is_gaussian); ++ } ++ ++ //! Blur image isotropically \newinstance. ++ CImg get_blur(const float sigma, const bool boundary_conditions=true, const bool is_gaussian=false) const { ++ return CImg(*this,false).blur(sigma,boundary_conditions,is_gaussian); ++ } ++ ++ //! Blur image anisotropically, directed by a field of diffusion tensors. ++ /** ++ \param G Field of square roots of diffusion tensors/vectors 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 diffusion process. ++ \param interpolation_type Interpolation scheme. ++ Can be { 0=nearest-neighbor | 1=linear | 2=Runge-Kutta }. ++ \param is_fast_approx Tells if a fast approximation of the gaussian function is used 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 is_fast_approx=1) { ++ ++ // Check arguments and init variables ++ if (!is_sameXYZ(G) || (G._spectrum!=3 && G._spectrum!=6)) ++ throw CImgArgumentException(_cimg_instance ++ "blur_anisotropic(): Invalid specified diffusion tensor field (%u,%u,%u,%u,%p).", ++ cimg_instance, ++ G._width,G._height,G._depth,G._spectrum,G._data); ++ ++ if (is_empty() || amplitude<=0 || dl<0) return *this; ++ const bool is_3d = (G._spectrum==6); ++ T val_min, val_max = max_min(val_min); ++ _cimg_abort_init_omp; ++ cimg_abort_init; ++ ++ if (da<=0) { // Iterated oriented Laplacians ++ CImg velocity(_width,_height,_depth,_spectrum); ++ for (unsigned int iteration = 0; iteration<(unsigned int)amplitude; ++iteration) { ++ Tfloat *ptrd = velocity._data, veloc_max = 0; ++ if (is_3d) // 3d version ++ cimg_forC(*this,c) { ++ cimg_abort_test; ++ CImg_3x3x3(I,Tfloat); ++ cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { ++ const Tfloat ++ ixx = Incc + Ipcc - 2*Iccc, ++ ixy = (Innc + Ippc - Inpc - Ipnc)/4, ++ ixz = (Incn + Ipcp - Incp - Ipcn)/4, ++ iyy = Icnc + Icpc - 2*Iccc, ++ iyz = (Icnn + Icpp - Icnp - Icpn)/4, ++ izz = Iccn + Iccp - 2*Iccc, ++ veloc = (Tfloat)(G(x,y,z,0)*ixx + 2*G(x,y,z,1)*ixy + 2*G(x,y,z,2)*ixz + ++ G(x,y,z,3)*iyy + 2*G(x,y,z,4)*iyz + G(x,y,z,5)*izz); ++ *(ptrd++) = veloc; ++ if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; ++ } ++ } ++ else // 2d version ++ cimg_forZC(*this,z,c) { ++ cimg_abort_test; ++ CImg_3x3(I,Tfloat); ++ cimg_for3x3(*this,x,y,z,c,I,Tfloat) { ++ const Tfloat ++ ixx = Inc + Ipc - 2*Icc, ++ ixy = (Inn + Ipp - Inp - Ipn)/4, ++ iyy = Icn + Icp - 2*Icc, ++ veloc = (Tfloat)(G(x,y,0,0)*ixx + 2*G(x,y,0,1)*ixy + G(x,y,0,2)*iyy); ++ *(ptrd++) = veloc; ++ if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; ++ } ++ } ++ if (veloc_max>0) *this+=(velocity*=dl/veloc_max); ++ } ++ } else { // LIC-based smoothing. ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ const float sqrt2amplitude = (float)std::sqrt(2*amplitude); ++ const int dx1 = width() - 1, dy1 = height() - 1, dz1 = depth() - 1; ++ CImg res(_width,_height,_depth,_spectrum,0), W(_width,_height,_depth,is_3d?4:3), val(_spectrum,1,1,1,0); ++ int N = 0; ++ if (is_3d) { // 3d version ++ for (float phi = cimg::mod(180.0f,da)/2.0f; phi<=180; phi+=da) { ++ const float phir = (float)(phi*cimg::PI/180), datmp = (float)(da/std::cos(phir)), ++ da2 = datmp<1?360.0f:datmp; ++ for (float theta = 0; theta<360; (theta+=da2),++N) { ++ const float ++ thetar = (float)(theta*cimg::PI/180), ++ vx = (float)(std::cos(thetar)*std::cos(phir)), ++ vy = (float)(std::sin(thetar)*std::cos(phir)), ++ vz = (float)std::sin(phir); ++ const t ++ *pa = G.data(0,0,0,0), *pb = G.data(0,0,0,1), *pc = G.data(0,0,0,2), ++ *pd = G.data(0,0,0,3), *pe = G.data(0,0,0,4), *pf = G.data(0,0,0,5); ++ Tfloat *pd0 = W.data(0,0,0,0), *pd1 = W.data(0,0,0,1), *pd2 = W.data(0,0,0,2), *pd3 = W.data(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 = 1e-5f + cimg::hypot(u,v,w), ++ dln = dl/n; ++ *(pd0++) = (Tfloat)(u*dln); ++ *(pd1++) = (Tfloat)(v*dln); ++ *(pd2++) = (Tfloat)(w*dln); ++ *(pd3++) = (Tfloat)n; ++ } ++ ++ cimg_abort_test; ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=256 && _height*_depth>=2) ++ firstprivate(val)) ++ cimg_forYZ(*this,y,z) _cimg_abort_try_omp2 { ++ cimg_abort_test2; ++ cimg_forX(*this,x) { ++ val.fill(0); ++ const float ++ n = (float)W(x,y,z,3), ++ fsigma = (float)(n*sqrt2amplitude), ++ fsigma2 = 2*fsigma*fsigma, ++ length = gauss_prec*fsigma; ++ float ++ S = 0, ++ 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); ++ const float ++ u = (float)W(cx,cy,cz,0), ++ v = (float)W(cx,cy,cz,1), ++ w = (float)W(cx,cy,cz,2); ++ if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)(*this)(cx,cy,cz,c); ++S; } ++ else { ++ const float coef = (float)std::exp(-l*l/fsigma2); ++ cimg_forC(*this,c) val[c]+=(Tfloat)(coef*(*this)(cx,cy,cz,c)); ++ S+=coef; ++ } ++ X+=u; Y+=v; Z+=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 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 (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXYZ(X,Y,Z,c); ++S; } ++ else { ++ const float coef = (float)std::exp(-l*l/fsigma2); ++ cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXYZ(X,Y,Z,c)); ++ S+=coef; ++ } ++ X+=u; Y+=v; Z+=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 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)), ++ 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 (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXYZ(X,Y,Z,c); ++S; } ++ else { ++ const float coef = (float)std::exp(-l*l/fsigma2); ++ cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXYZ(X,Y,Z,c)); ++ S+=coef; ++ } ++ X+=u; Y+=v; Z+=w; ++ } ++ } break; ++ } ++ Tfloat *ptrd = res.data(x,y,z); ++ if (S>0) cimg_forC(res,c) { *ptrd+=val[c]/S; ptrd+=whd; } ++ else cimg_forC(res,c) { *ptrd+=(Tfloat)((*this)(x,y,z,c)); ptrd+=whd; } ++ } ++ } _cimg_abort_catch_omp2 ++ } ++ } ++ } else { // 2d LIC algorithm ++ for (float theta = cimg::mod(360.0f,da)/2.0f; theta<360; (theta+=da),++N) { ++ const float thetar = (float)(theta*cimg::PI/180), ++ vx = (float)(std::cos(thetar)), vy = (float)(std::sin(thetar)); ++ const t *pa = G.data(0,0,0,0), *pb = G.data(0,0,0,1), *pc = G.data(0,0,0,2); ++ Tfloat *pd0 = W.data(0,0,0,0), *pd1 = W.data(0,0,0,1), *pd2 = W.data(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 = std::max(1e-5f,cimg::hypot(u,v)), ++ dln = dl/n; ++ *(pd0++) = (Tfloat)(u*dln); ++ *(pd1++) = (Tfloat)(v*dln); ++ *(pd2++) = (Tfloat)n; ++ } ++ ++ cimg_abort_test; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=256 && _height>=2) firstprivate(val)) ++ cimg_forY(*this,y) _cimg_abort_try_omp2 { ++ cimg_abort_test2; ++ cimg_forX(*this,x) { ++ val.fill(0); ++ const float ++ n = (float)W(x,y,0,2), ++ fsigma = (float)(n*sqrt2amplitude), ++ fsigma2 = 2*fsigma*fsigma, ++ length = gauss_prec*fsigma; ++ float ++ S = 0, ++ X = (float)x, ++ Y = (float)y; ++ switch (interpolation_type) { ++ case 0 : { // Nearest-neighbor ++ 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); ++ const float ++ u = (float)W(cx,cy,0,0), ++ v = (float)W(cx,cy,0,1); ++ if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)(*this)(cx,cy,0,c); ++S; } ++ else { ++ const float coef = (float)std::exp(-l*l/fsigma2); ++ cimg_forC(*this,c) val[c]+=(Tfloat)(coef*(*this)(cx,cy,0,c)); ++ S+=coef; ++ } ++ X+=u; Y+=v; ++ } ++ } break; ++ case 1 : { // Linear interpolation ++ for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) { ++ const float ++ u = (float)(W._linear_atXY(X,Y,0,0)), ++ v = (float)(W._linear_atXY(X,Y,0,1)); ++ if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXY(X,Y,0,c); ++S; } ++ else { ++ const float coef = (float)std::exp(-l*l/fsigma2); ++ cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXY(X,Y,0,c)); ++ S+=coef; ++ } ++ X+=u; Y+=v; ++ } ++ } break; ++ default : { // 2nd-order Runge-kutta interpolation ++ for (float l = 0; l=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) { ++ const float ++ u0 = (float)(0.5f*W._linear_atXY(X,Y,0,0)), ++ v0 = (float)(0.5f*W._linear_atXY(X,Y,0,1)), ++ u = (float)(W._linear_atXY(X + u0,Y + v0,0,0)), ++ v = (float)(W._linear_atXY(X + u0,Y + v0,0,1)); ++ if (is_fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXY(X,Y,0,c); ++S; } ++ else { ++ const float coef = (float)std::exp(-l*l/fsigma2); ++ cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXY(X,Y,0,c)); ++ S+=coef; ++ } ++ X+=u; Y+=v; ++ } ++ } ++ } ++ Tfloat *ptrd = res.data(x,y); ++ if (S>0) cimg_forC(res,c) { *ptrd+=val[c]/S; ptrd+=whd; } ++ else cimg_forC(res,c) { *ptrd+=(Tfloat)((*this)(x,y,0,c)); ptrd+=whd; } ++ } ++ } _cimg_abort_catch_omp2 ++ } ++ } ++ const Tfloat *ptrs = res._data; ++ cimg_for(*this,ptrd,T) { ++ const Tfloat val = *(ptrs++)/N; ++ *ptrd = valval_max?val_max:(T)val); ++ } ++ } ++ cimg_abort_test; ++ return *this; ++ } ++ ++ //! Blur image anisotropically, directed by a field of diffusion tensors \newinstance. ++ 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 is_fast_approx=true) const { ++ return CImg(*this,false).blur_anisotropic(G,amplitude,dl,da,gauss_prec,interpolation_type,is_fast_approx); ++ } ++ ++ //! Blur image anisotropically, in an edge-preserving way. ++ /** ++ \param amplitude Amplitude of the smoothing. ++ \param sharpness Sharpness. ++ \param anisotropy Anisotropy. ++ \param alpha Standard deviation of the gradient blur. ++ \param sigma Standard deviation of the structure tensor blur. ++ \param dl Spatial discretization. ++ \param da Angular discretization. ++ \param gauss_prec Precision of the diffusion process. ++ \param interpolation_type Interpolation scheme. ++ Can be { 0=nearest-neighbor | 1=linear | 2=Runge-Kutta }. ++ \param is_fast_approx Tells if a fast approximation of the gaussian function is used or not. ++ **/ ++ CImg& blur_anisotropic(const float amplitude, const float sharpness=0.7f, const float anisotropy=0.6f, ++ 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 is_fast_approx=true) { ++ return blur_anisotropic(get_diffusion_tensors(sharpness,anisotropy,alpha,sigma,interpolation_type!=3), ++ amplitude,dl,da,gauss_prec,interpolation_type,is_fast_approx); ++ } ++ ++ //! Blur image anisotropically, in an edge-preserving way \newinstance. ++ CImg get_blur_anisotropic(const float amplitude, const float sharpness=0.7f, const float anisotropy=0.6f, ++ 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 is_fast_approx=true) const { ++ return CImg(*this,false).blur_anisotropic(amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec, ++ interpolation_type,is_fast_approx); ++ } ++ ++ //! Blur image, with the joint bilateral filter. ++ /** ++ \param guide Image used to model the smoothing weights. ++ \param sigma_x Amount of blur along the X-axis. ++ \param sigma_y Amount of blur along the Y-axis. ++ \param sigma_z Amount of blur along the Z-axis. ++ \param sigma_r Amount of blur along the value axis. ++ \param sampling_x Amount of downsampling along the X-axis used for the approximation. ++ Defaults (0) to sigma_x. ++ \param sampling_y Amount of downsampling along the Y-axis used for the approximation. ++ Defaults (0) to sigma_y. ++ \param sampling_z Amount of downsampling along the Z-axis used for the approximation. ++ Defaults (0) to sigma_z. ++ \param sampling_r Amount of downsampling along the value axis used for the approximation. ++ Defaults (0) to sigma_r. ++ \note This algorithm uses the optimisation technique proposed by S. Paris and F. Durand, in ECCV'2006 ++ (extended for 3d volumetric images). ++ It is based on the reference implementation http://people.csail.mit.edu/jiawen/software/bilateralFilter.m ++ **/ ++ template ++ CImg& blur_bilateral(const CImg& guide, ++ const float sigma_x, const float sigma_y, ++ const float sigma_z, const float sigma_r, ++ const float sampling_x, const float sampling_y, ++ const float sampling_z, const float sampling_r) { ++ if (!is_sameXYZ(guide)) ++ throw CImgArgumentException(_cimg_instance ++ "blur_bilateral(): Invalid size for specified guide image (%u,%u,%u,%u,%p).", ++ cimg_instance, ++ guide._width,guide._height,guide._depth,guide._spectrum,guide._data); ++ if (is_empty() || (!sigma_x && !sigma_y && !sigma_z)) return *this; ++ T edge_min, edge_max = guide.max_min(edge_min); ++ if (edge_min==edge_max) return blur(sigma_x,sigma_y,sigma_z); ++ const float ++ edge_delta = (float)(edge_max - edge_min), ++ _sigma_x = sigma_x>=0?sigma_x:-sigma_x*_width/100, ++ _sigma_y = sigma_y>=0?sigma_y:-sigma_y*_height/100, ++ _sigma_z = sigma_z>=0?sigma_z:-sigma_z*_depth/100, ++ _sigma_r = sigma_r>=0?sigma_r:-sigma_r*(edge_max - edge_min)/100, ++ _sampling_x = sampling_x?sampling_x:std::max(_sigma_x,1.0f), ++ _sampling_y = sampling_y?sampling_y:std::max(_sigma_y,1.0f), ++ _sampling_z = sampling_z?sampling_z:std::max(_sigma_z,1.0f), ++ _sampling_r = sampling_r?sampling_r:std::max(_sigma_r,edge_delta/256), ++ derived_sigma_x = _sigma_x / _sampling_x, ++ derived_sigma_y = _sigma_y / _sampling_y, ++ derived_sigma_z = _sigma_z / _sampling_z, ++ derived_sigma_r = _sigma_r / _sampling_r; ++ const int ++ padding_x = (int)(2*derived_sigma_x) + 1, ++ padding_y = (int)(2*derived_sigma_y) + 1, ++ padding_z = (int)(2*derived_sigma_z) + 1, ++ padding_r = (int)(2*derived_sigma_r) + 1; ++ const unsigned int ++ bx = (unsigned int)((_width - 1)/_sampling_x + 1 + 2*padding_x), ++ by = (unsigned int)((_height - 1)/_sampling_y + 1 + 2*padding_y), ++ bz = (unsigned int)((_depth - 1)/_sampling_z + 1 + 2*padding_z), ++ br = (unsigned int)(edge_delta/_sampling_r + 1 + 2*padding_r); ++ if (bx>0 || by>0 || bz>0 || br>0) { ++ const bool is_3d = (_depth>1); ++ if (is_3d) { // 3d version of the algorithm ++ CImg bgrid(bx,by,bz,br), bgridw(bx,by,bz,br); ++ cimg_forC(*this,c) { ++ const CImg _guide = guide.get_shared_channel(c%guide._spectrum); ++ bgrid.fill(0); bgridw.fill(0); ++ cimg_forXYZ(*this,x,y,z) { ++ const T val = (*this)(x,y,z,c); ++ const float edge = (float)_guide(x,y,z); ++ const int ++ X = (int)cimg::round(x/_sampling_x) + padding_x, ++ Y = (int)cimg::round(y/_sampling_y) + padding_y, ++ Z = (int)cimg::round(z/_sampling_z) + padding_z, ++ R = (int)cimg::round((edge - edge_min)/_sampling_r) + padding_r; ++ bgrid(X,Y,Z,R)+=(float)val; ++ bgridw(X,Y,Z,R)+=1; ++ } ++ bgrid.blur(derived_sigma_x,derived_sigma_y,derived_sigma_z,true).deriche(derived_sigma_r,0,'c',false); ++ bgridw.blur(derived_sigma_x,derived_sigma_y,derived_sigma_z,true).deriche(derived_sigma_r,0,'c',false); ++ ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(size()>=4096)) ++ cimg_forXYZ(*this,x,y,z) { ++ const float edge = (float)_guide(x,y,z); ++ const float ++ X = x/_sampling_x + padding_x, ++ Y = y/_sampling_y + padding_y, ++ Z = z/_sampling_z + padding_z, ++ R = (edge - edge_min)/_sampling_r + padding_r; ++ const float bval0 = bgrid._linear_atXYZC(X,Y,Z,R), bval1 = bgridw._linear_atXYZC(X,Y,Z,R); ++ (*this)(x,y,z,c) = (T)(bval0/bval1); ++ } ++ } ++ } else { // 2d version of the algorithm ++ CImg bgrid(bx,by,br,2); ++ cimg_forC(*this,c) { ++ const CImg _guide = guide.get_shared_channel(c%guide._spectrum); ++ bgrid.fill(0); ++ cimg_forXY(*this,x,y) { ++ const T val = (*this)(x,y,c); ++ const float edge = (float)_guide(x,y); ++ const int ++ X = (int)cimg::round(x/_sampling_x) + padding_x, ++ Y = (int)cimg::round(y/_sampling_y) + padding_y, ++ R = (int)cimg::round((edge - edge_min)/_sampling_r) + padding_r; ++ bgrid(X,Y,R,0)+=(float)val; ++ bgrid(X,Y,R,1)+=1; ++ } ++ bgrid.blur(derived_sigma_x,derived_sigma_y,0,true).blur(0,0,derived_sigma_r,false); ++ ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(size()>=4096)) ++ cimg_forXY(*this,x,y) { ++ const float edge = (float)_guide(x,y); ++ const float ++ X = x/_sampling_x + padding_x, ++ Y = y/_sampling_y + padding_y, ++ R = (edge - edge_min)/_sampling_r + padding_r; ++ const float bval0 = bgrid._linear_atXYZ(X,Y,R,0), bval1 = bgrid._linear_atXYZ(X,Y,R,1); ++ (*this)(x,y,c) = (T)(bval0/bval1); ++ } ++ } ++ } ++ } ++ return *this; ++ } ++ ++ //! Blur image, with the joint bilateral filter \newinstance. ++ template ++ CImg get_blur_bilateral(const CImg& guide, ++ const float sigma_x, const float sigma_y, ++ const float sigma_z, const float sigma_r, ++ const float sampling_x, const float sampling_y, ++ const float sampling_z, const float sampling_r) const { ++ return CImg(*this,false).blur_bilateral(guide,sigma_x,sigma_y,sigma_z,sigma_r, ++ sampling_x,sampling_y,sampling_z,sampling_r); ++ } ++ ++ //! Blur image using the joint bilateral filter. ++ /** ++ \param guide Image used to model the smoothing weights. ++ \param sigma_s Amount of blur along the XYZ-axes. ++ \param sigma_r Amount of blur along the value axis. ++ \param sampling_s Amount of downsampling along the XYZ-axes used for the approximation. Defaults to sigma_s. ++ \param sampling_r Amount of downsampling along the value axis used for the approximation. Defaults to sigma_r. ++ **/ ++ template ++ CImg& blur_bilateral(const CImg& guide, ++ const float sigma_s, const float sigma_r, ++ const float sampling_s=0, const float sampling_r=0) { ++ const float _sigma_s = sigma_s>=0?sigma_s:-sigma_s*cimg::max(_width,_height,_depth)/100; ++ return blur_bilateral(guide,_sigma_s,_sigma_s,_sigma_s,sigma_r,sampling_s,sampling_s,sampling_s,sampling_r); ++ } ++ ++ //! Blur image using the bilateral filter \newinstance. ++ template ++ CImg get_blur_bilateral(const CImg& guide, ++ const float sigma_s, const float sigma_r, ++ const float sampling_s=0, const float sampling_r=0) const { ++ return CImg(*this,false).blur_bilateral(guide,sigma_s,sigma_r,sampling_s,sampling_r); ++ } ++ ++ // [internal] Apply a box filter (used by CImg::boxfilter() and CImg::blur_box()). ++ /* ++ \param ptr the pointer of the data ++ \param N size of the data ++ \param boxsize Size of the box filter (can be subpixel). ++ \param off the offset between two data point ++ \param order the order of the filter 0 (smoothing), 1st derivtive and 2nd derivative. ++ \param boundary_conditions Boundary conditions. Can be { 0=dirichlet | 1=neumann }. ++ */ ++ static void _cimg_blur_box_apply(T *ptr, const float boxsize, const int N, const ulongT off, ++ const int order, const bool boundary_conditions, ++ const unsigned int nb_iter) { ++ // Smooth. ++ if (boxsize>1 && nb_iter) { ++ const int w2 = (int)(boxsize - 1)/2; ++ const unsigned int winsize = 2*w2 + 1U; ++ const double frac = (boxsize - winsize)/2.; ++ CImg win(winsize); ++ for (unsigned int iter = 0; iter=N) return boundary_conditions?ptr[(N - 1)*off]:T(); ++ return ptr[x*off]; ++ } ++ ++ // Apply box filter of order 0,1,2. ++ /** ++ \param boxsize Size of the box window (can be subpixel) ++ \param order the order of the filter 0,1 or 2. ++ \param axis Axis along which the filter is computed. Can be { 'x' | 'y' | 'z' | 'c' }. ++ \param boundary_conditions Boundary conditions. Can be { 0=dirichlet | 1=neumann }. ++ \param nb_iter Number of filter iterations. ++ **/ ++ CImg& boxfilter(const float boxsize, const int order, const char axis='x', ++ const bool boundary_conditions=true, ++ const unsigned int nb_iter=1) { ++ if (is_empty() || !boxsize || (boxsize<=1 && !order)) return *this; ++ const char naxis = cimg::lowercase(axis); ++ const float nboxsize = boxsize>=0?boxsize:-boxsize* ++ (naxis=='x'?_width:naxis=='y'?_height:naxis=='z'?_depth:_spectrum)/100; ++ switch (naxis) { ++ case 'x' : { ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth*_spectrum>=16)) ++ cimg_forYZC(*this,y,z,c) ++ _cimg_blur_box_apply(data(0,y,z,c),nboxsize,_width,1U,order,boundary_conditions,nb_iter); ++ } break; ++ case 'y' : { ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth*_spectrum>=16)) ++ cimg_forXZC(*this,x,z,c) ++ _cimg_blur_box_apply(data(x,0,z,c),nboxsize,_height,(ulongT)_width,order,boundary_conditions,nb_iter); ++ } break; ++ case 'z' : { ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth*_spectrum>=16)) ++ cimg_forXYC(*this,x,y,c) ++ _cimg_blur_box_apply(data(x,y,0,c),nboxsize,_depth,(ulongT)_width*_height,order,boundary_conditions,nb_iter); ++ } break; ++ default : { ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth*_spectrum>=16)) ++ cimg_forXYZ(*this,x,y,z) ++ _cimg_blur_box_apply(data(x,y,z,0),nboxsize,_spectrum,(ulongT)_width*_height*_depth, ++ order,boundary_conditions,nb_iter); ++ } ++ } ++ return *this; ++ } ++ ++ // Apply box filter of order 0,1 or 2 \newinstance. ++ CImg get_boxfilter(const float boxsize, const int order, const char axis='x', ++ const bool boundary_conditions=true, ++ const unsigned int nb_iter=1) const { ++ return CImg(*this,false).boxfilter(boxsize,order,axis,boundary_conditions,nb_iter); ++ } ++ ++ //! Blur image with a box filter. ++ /** ++ \param boxsize_x Size of the box window, along the X-axis (can be subpixel). ++ \param boxsize_y Size of the box window, along the Y-axis (can be subpixel). ++ \param boxsize_z Size of the box window, along the Z-axis (can be subpixel). ++ \param boundary_conditions Boundary conditions. Can be { false=dirichlet | true=neumann }. ++ \param nb_iter Number of filter iterations. ++ \note ++ - This is a recursive algorithm, not depending on the values of the box kernel size. ++ \see blur(). ++ **/ ++ CImg& blur_box(const float boxsize_x, const float boxsize_y, const float boxsize_z, ++ const bool boundary_conditions=true, ++ const unsigned int nb_iter=1) { ++ if (is_empty()) return *this; ++ if (_width>1) boxfilter(boxsize_x,0,'x',boundary_conditions,nb_iter); ++ if (_height>1) boxfilter(boxsize_y,0,'y',boundary_conditions,nb_iter); ++ if (_depth>1) boxfilter(boxsize_z,0,'z',boundary_conditions,nb_iter); ++ return *this; ++ } ++ ++ //! Blur image with a box filter \newinstance. ++ CImg get_blur_box(const float boxsize_x, const float boxsize_y, const float boxsize_z, ++ const bool boundary_conditions=true) const { ++ return CImg(*this,false).blur_box(boxsize_x,boxsize_y,boxsize_z,boundary_conditions); ++ } ++ ++ //! Blur image with a box filter. ++ /** ++ \param boxsize Size of the box window (can be subpixel). ++ \param boundary_conditions Boundary conditions. Can be { 0=dirichlet | 1=neumann }.a ++ \see deriche(), vanvliet(). ++ **/ ++ CImg& blur_box(const float boxsize, const bool boundary_conditions=true) { ++ const float nboxsize = boxsize>=0?boxsize:-boxsize*cimg::max(_width,_height,_depth)/100; ++ return blur_box(nboxsize,nboxsize,nboxsize,boundary_conditions); ++ } ++ ++ //! Blur image with a box filter \newinstance. ++ CImg get_blur_box(const float boxsize, const bool boundary_conditions=true) const { ++ return CImg(*this,false).blur_box(boxsize,boundary_conditions); ++ } ++ ++ //! Blur image, with the image guided filter. ++ /** ++ \param guide Image used to guide the smoothing process. ++ \param radius Spatial radius. If negative, it is expressed as a percentage of the largest image size. ++ \param regularization Regularization parameter. ++ If negative, it is expressed as a percentage of the guide value range. ++ \note This method implements the filtering algorithm described in: ++ He, Kaiming; Sun, Jian; Tang, Xiaoou, "Guided Image Filtering," Pattern Analysis and Machine Intelligence, ++ IEEE Transactions on , vol.35, no.6, pp.1397,1409, June 2013 ++ **/ ++ template ++ CImg& blur_guided(const CImg& guide, const float radius, const float regularization) { ++ return get_blur_guided(guide,radius,regularization).move_to(*this); ++ } ++ ++ //! Blur image, with the image guided filter \newinstance. ++ template ++ CImg get_blur_guided(const CImg& guide, const float radius, const float regularization) const { ++ if (!is_sameXYZ(guide)) ++ throw CImgArgumentException(_cimg_instance ++ "blur_guided(): Invalid size for specified guide image (%u,%u,%u,%u,%p).", ++ cimg_instance, ++ guide._width,guide._height,guide._depth,guide._spectrum,guide._data); ++ if (is_empty() || !radius) return *this; ++ const int _radius = radius>=0?(int)radius:(int)(-radius*cimg::max(_width,_height,_depth)/100); ++ float _regularization = regularization; ++ if (regularization<0) { ++ T edge_min, edge_max = guide.max_min(edge_min); ++ if (edge_min==edge_max) return *this; ++ _regularization = -regularization*(edge_max - edge_min)/100; ++ } ++ _regularization = std::max(_regularization,0.01f); ++ const unsigned int psize = (unsigned int)(1 + 2*_radius); ++ const CImg N = CImg(_width,_height,_depth,1,1)._blur_guided(psize); ++ CImg ++ mean_I = CImg(guide,false)._blur_guided(psize).div(N), ++ mean_p = CImg(*this,false)._blur_guided(psize).div(N), ++ cov_Ip = CImg(*this,false).mul(guide)._blur_guided(psize).div(N)-=mean_p.get_mul(mean_I), ++ var_I = CImg(guide,false).sqr()._blur_guided(psize).div(N)-=mean_I.get_sqr(), ++ &a = cov_Ip.div(var_I+=_regularization), ++ &b = mean_p-=a.get_mul(mean_I); ++ a._blur_guided(psize).div(N); ++ b._blur_guided(psize).div(N); ++ return a.mul(guide)+=b; ++ } ++ ++ // [internal] Perform box filter with dirichlet boundary conditions. ++ CImg& _blur_guided(const unsigned int psize) { ++ const int p1 = (int)psize/2, p2 = (int)psize - p1; ++ if (_depth!=1) { ++ CImg cumul = get_cumulate('z'), cumul2 = cumul.get_shift(0,0,p2,0,1); ++ (cumul.shift(0,0,-p1,0,1)-=cumul2).move_to(*this); ++ } ++ if (_height!=1) { ++ CImg cumul = get_cumulate('y'), cumul2 = cumul.get_shift(0,p2,0,0,1); ++ (cumul.shift(0,-p1,0,0,1)-=cumul2).move_to(*this); ++ } ++ if (_width!=1) { ++ CImg cumul = get_cumulate('x'), cumul2 = cumul.get_shift(p2,0,0,0,1); ++ (cumul.shift(-p1,0,0,0,1)-=cumul2).move_to(*this); ++ } ++ return *this; ++ } ++ ++ //! Blur image using patch-based space. ++ /** ++ \param sigma_s Amount of blur along the XYZ-axes. ++ \param sigma_p Amount of blur along the value axis. ++ \param patch_size Size of the patchs. ++ \param lookup_size Size of the window to search similar patchs. ++ \param smoothness Smoothness for the patch comparison. ++ \param is_fast_approx Tells if a fast approximation of the gaussian function is used or not. ++ **/ ++ CImg& blur_patch(const float sigma_s, const float sigma_p, const unsigned int patch_size=3, ++ const unsigned int lookup_size=4, const float smoothness=0, const bool is_fast_approx=true) { ++ if (is_empty() || !patch_size || !lookup_size) return *this; ++ return get_blur_patch(sigma_s,sigma_p,patch_size,lookup_size,smoothness,is_fast_approx).move_to(*this); ++ } ++ ++ //! Blur image using patch-based space \newinstance. ++ CImg get_blur_patch(const float sigma_s, const float sigma_p, const unsigned int patch_size=3, ++ const unsigned int lookup_size=4, const float smoothness=0, ++ const bool is_fast_approx=true) const { ++ ++#define _cimg_blur_patch3d_fast(N) \ ++ cimg_for##N##XYZ(res,x,y,z) { \ ++ T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N##x##N(img,x,y,z,c,pP,T); pP+=N3; } \ ++ 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(res,x0,y0,z0,x1,y1,z1,p,q,r) \ ++ if (cimg::abs((Tfloat)img(x,y,z,0) - (Tfloat)img(p,q,r,0))3?0.0f:1.0f; \ ++ sum_weights+=weight; \ ++ cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); \ ++ } \ ++ if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; \ ++ else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); \ ++ } ++ ++#define _cimg_blur_patch3d(N) \ ++ cimg_for##N##XYZ(res,x,y,z) { \ ++ T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N##x##N(img,x,y,z,c,pP,T); pP+=N3; } \ ++ const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, \ ++ x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; \ ++ float sum_weights = 0, weight_max = 0; \ ++ cimg_for_in##N##XYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) if (p!=x || q!=y || r!=z) { \ ++ T *pQ = Q._data; cimg_forC(res,c) { cimg_get##N##x##N##x##N(img,p,q,r,c,pQ,T); pQ+=N3; } \ ++ float distance2 = 0; \ ++ pQ = Q._data; 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)std::exp(-alldist); \ ++ if (weight>weight_max) weight_max = weight; \ ++ sum_weights+=weight; \ ++ cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); \ ++ } \ ++ sum_weights+=weight_max; cimg_forC(res,c) res(x,y,z,c)+=weight_max*(*this)(x,y,z,c); \ ++ if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; \ ++ else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); \ ++ } ++ ++#define _cimg_blur_patch2d_fast(N) \ ++ cimg_for##N##XY(res,x,y) { \ ++ T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N(img,x,y,0,c,pP,T); pP+=N2; } \ ++ const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; \ ++ float sum_weights = 0; \ ++ cimg_for_in##N##XY(res,x0,y0,x1,y1,p,q) \ ++ if (cimg::abs((Tfloat)img(x,y,0,0) - (Tfloat)img(p,q,0,0))3?0.0f:1.0f; \ ++ sum_weights+=weight; \ ++ cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); \ ++ } \ ++ if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; \ ++ else cimg_forC(res,c) res(x,y,c) = (Tfloat)((*this)(x,y,c)); \ ++ } ++ ++#define _cimg_blur_patch2d(N) \ ++ cimg_for##N##XY(res,x,y) { \ ++ T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N(img,x,y,0,c,pP,T); pP+=N2; } \ ++ const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; \ ++ float sum_weights = 0, weight_max = 0; \ ++ cimg_for_in##N##XY(res,x0,y0,x1,y1,p,q) if (p!=x || q!=y) { \ ++ T *pQ = Q._data; cimg_forC(res,c) { cimg_get##N##x##N(img,p,q,0,c,pQ,T); pQ+=N2; } \ ++ float distance2 = 0; \ ++ pQ = Q._data; 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)std::exp(-alldist); \ ++ if (weight>weight_max) weight_max = weight; \ ++ sum_weights+=weight; \ ++ cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); \ ++ } \ ++ sum_weights+=weight_max; cimg_forC(res,c) res(x,y,c)+=weight_max*(*this)(x,y,c); \ ++ if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; \ ++ else cimg_forC(res,c) res(x,y,c) = (Tfloat)((*this)(x,y,c)); \ ++ } ++ ++ if (is_empty() || !patch_size || !lookup_size) return +*this; ++ CImg res(_width,_height,_depth,_spectrum,0); ++ const CImg _img = smoothness>0?get_blur(smoothness):CImg(),&img = smoothness>0?_img:*this; ++ CImg P(patch_size*patch_size*_spectrum), Q(P); ++ const float ++ nsigma_s = sigma_s>=0?sigma_s:-sigma_s*cimg::max(_width,_height,_depth)/100, ++ sigma_s2 = nsigma_s*nsigma_s, sigma_p2 = sigma_p*sigma_p, sigma_p3 = 3*sigma_p, ++ Pnorm = P.size()*sigma_p2; ++ const int rsize2 = (int)lookup_size/2, rsize1 = (int)lookup_size - rsize2 - 1; ++ const unsigned int N2 = patch_size*patch_size, N3 = N2*patch_size; ++ cimg::unused(N2,N3); ++ if (_depth>1) switch (patch_size) { // 3d ++ case 2 : if (is_fast_approx) _cimg_blur_patch3d_fast(2) else _cimg_blur_patch3d(2) break; ++ case 3 : if (is_fast_approx) _cimg_blur_patch3d_fast(3) else _cimg_blur_patch3d(3) break; ++ default : { ++ const int psize2 = (int)patch_size/2, psize1 = (int)patch_size - psize2 - 1; ++ if (is_fast_approx) ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res._width>=32 && res._height*res._depth>=4) ++ private(P,Q)) ++ cimg_forXYZ(res,x,y,z) { // Fast ++ P = img.get_crop(x - psize1,y - psize1,z - psize1,x + psize2,y + psize2,z + psize2,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(res,x0,y0,z0,x1,y1,z1,p,q,r) ++ if (cimg::abs((Tfloat)img(x,y,z,0) - (Tfloat)img(p,q,r,0))3?0.0f:1.0f; ++ sum_weights+=weight; ++ cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); ++ } ++ if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; ++ else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); ++ } else ++ cimg_pragma_openmp(parallel for collapse(2) ++ if (res._width>=32 && res._height*res._depth>=4) firstprivate(P,Q)) ++ cimg_forXYZ(res,x,y,z) { // Exact ++ P = img.get_crop(x - psize1,y - psize1,z - psize1,x + psize2,y + psize2,z + psize2,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, weight_max = 0; ++ cimg_for_inXYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) if (p!=x || q!=y || r!=z) { ++ (Q = img.get_crop(p - psize1,q - psize1,r - psize1,p + psize2,q + psize2,r + psize2,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)std::exp(-distance2); ++ if (weight>weight_max) weight_max = weight; ++ sum_weights+=weight; ++ cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); ++ } ++ sum_weights+=weight_max; cimg_forC(res,c) res(x,y,z,c)+=weight_max*(*this)(x,y,z,c); ++ if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; ++ else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); ++ } ++ } ++ } else switch (patch_size) { // 2d ++ case 2 : if (is_fast_approx) _cimg_blur_patch2d_fast(2) else _cimg_blur_patch2d(2) break; ++ case 3 : if (is_fast_approx) _cimg_blur_patch2d_fast(3) else _cimg_blur_patch2d(3) break; ++ case 4 : if (is_fast_approx) _cimg_blur_patch2d_fast(4) else _cimg_blur_patch2d(4) break; ++ case 5 : if (is_fast_approx) _cimg_blur_patch2d_fast(5) else _cimg_blur_patch2d(5) break; ++ case 6 : if (is_fast_approx) _cimg_blur_patch2d_fast(6) else _cimg_blur_patch2d(6) break; ++ case 7 : if (is_fast_approx) _cimg_blur_patch2d_fast(7) else _cimg_blur_patch2d(7) break; ++ case 8 : if (is_fast_approx) _cimg_blur_patch2d_fast(8) else _cimg_blur_patch2d(8) break; ++ case 9 : if (is_fast_approx) _cimg_blur_patch2d_fast(9) else _cimg_blur_patch2d(9) break; ++ default : { // Fast ++ const int psize2 = (int)patch_size/2, psize1 = (int)patch_size - psize2 - 1; ++ if (is_fast_approx) ++ cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=32 && res._height>=4) firstprivate(P,Q)) ++ cimg_forXY(res,x,y) { // 2d fast approximation. ++ P = img.get_crop(x - psize1,y - psize1,x + psize2,y + psize2,true); ++ const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; ++ float sum_weights = 0; ++ cimg_for_inXY(res,x0,y0,x1,y1,p,q) ++ if ((Tfloat)cimg::abs(img(x,y,0) - (Tfloat)img(p,q,0))3?0.0f:1.0f; ++ sum_weights+=weight; ++ cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); ++ } ++ if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; ++ else cimg_forC(res,c) res(x,y,c) = (Tfloat)((*this)(x,y,c)); ++ } else ++ cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=32 && res._height>=4) firstprivate(P,Q)) ++ cimg_forXY(res,x,y) { // 2d exact algorithm. ++ P = img.get_crop(x - psize1,y - psize1,x + psize2,y + psize2,true); ++ const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; ++ float sum_weights = 0, weight_max = 0; ++ cimg_for_inXY(res,x0,y0,x1,y1,p,q) if (p!=x || q!=y) { ++ (Q = img.get_crop(p - psize1,q - psize1,p + psize2,q + psize2,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)std::exp(-distance2); ++ if (weight>weight_max) weight_max = weight; ++ sum_weights+=weight; ++ cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); ++ } ++ sum_weights+=weight_max; cimg_forC(res,c) res(x,y,c)+=weight_max*(*this)(x,y,c); ++ if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; ++ else cimg_forC(res,c) res(x,y,0,c) = (Tfloat)((*this)(x,y,c)); ++ } ++ } ++ } ++ return res; ++ } ++ ++ //! Blur image with the median filter. ++ /** ++ \param n Size of the median filter. ++ \param threshold Threshold used to discard pixels too far from the current pixel value in the median computation. ++ **/ ++ CImg& blur_median(const unsigned int n, const float threshold=0) { ++ if (!n) return *this; ++ return get_blur_median(n,threshold).move_to(*this); ++ } ++ ++ //! Blur image with the median filter \newinstance. ++ CImg get_blur_median(const unsigned int n, const float threshold=0) const { ++ if (is_empty() || n<=1) return +*this; ++ CImg res(_width,_height,_depth,_spectrum); ++ T *ptrd = res._data; ++ cimg::unused(ptrd); ++ const int hr = (int)n/2, hl = n - hr - 1; ++ if (res._depth!=1) { // 3d ++ if (threshold>0) ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=16 && _height*_depth*_spectrum>=4)) ++ cimg_forXYZC(*this,x,y,z,c) { // With threshold. ++ 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>=width()?width() - 1:x1, ny1 = y1>=height()?height() - 1:y1, nz1 = z1>=depth()?depth() - 1:z1; ++ const Tfloat val0 = (Tfloat)(*this)(x,y,z,c); ++ CImg values(n*n*n); ++ unsigned int nb_values = 0; ++ T *ptrd = values.data(); ++ cimg_for_inXYZ(*this,nx0,ny0,nz0,nx1,ny1,nz1,p,q,r) ++ if (cimg::abs((*this)(p,q,r,c) - val0)<=threshold) { *(ptrd++) = (*this)(p,q,r,c); ++nb_values; } ++ res(x,y,z,c) = nb_values?values.get_shared_points(0,nb_values - 1).median():(*this)(x,y,z,c); ++ } ++ else ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=16 && _height*_depth*_spectrum>=4)) ++ cimg_forXYZC(*this,x,y,z,c) { // Without threshold. ++ 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>=width()?width() - 1:x1, ny1 = y1>=height()?height() - 1:y1, nz1 = z1>=depth()?depth() - 1:z1; ++ res(x,y,z,c) = get_crop(nx0,ny0,nz0,c,nx1,ny1,nz1,c).median(); ++ } ++ } else { ++ if (threshold>0) ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=16 && _height*_spectrum>=4)) ++ cimg_forXYC(*this,x,y,c) { // With threshold. ++ 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>=width()?width() - 1:x1, ny1 = y1>=height()?height() - 1:y1; ++ const Tfloat val0 = (Tfloat)(*this)(x,y,c); ++ CImg values(n*n); ++ unsigned int nb_values = 0; ++ T *ptrd = values.data(); ++ cimg_for_inXY(*this,nx0,ny0,nx1,ny1,p,q) ++ if (cimg::abs((*this)(p,q,c) - val0)<=threshold) { *(ptrd++) = (*this)(p,q,c); ++nb_values; } ++ res(x,y,c) = nb_values?values.get_shared_points(0,nb_values - 1).median():(*this)(x,y,c); ++ } ++ else { ++ const int ++ w1 = width() - 1, h1 = height() - 1, ++ w2 = width() - 2, h2 = height() - 2, ++ w3 = width() - 3, h3 = height() - 3, ++ w4 = width() - 4, h4 = height() - 4; ++ switch (n) { // Without threshold. ++ case 3 : { ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2)) ++ cimg_forC(*this,c) { ++ CImg I(9); ++ cimg_for_in3x3(*this,1,1,w2,h2,x,y,0,c,I,T) ++ res(x,y,c) = cimg::median(I[0],I[1],I[2],I[3],I[4],I[5],I[6],I[7],I[8]); ++ cimg_for_borderXY(*this,x,y,1) ++ res(x,y,c) = get_crop(std::max(0,x - 1),std::max(0,y - 1),0,c, ++ std::min(w1,x + 1),std::min(h1,y + 1),0,c).median(); ++ } ++ } break; ++ case 5 : { ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2)) ++ cimg_forC(*this,c) { ++ CImg I(25); ++ cimg_for_in5x5(*this,2,2,w3,h3,x,y,0,c,I,T) ++ res(x,y,c) = cimg::median(I[0],I[1],I[2],I[3],I[4], ++ I[5],I[6],I[7],I[8],I[9], ++ I[10],I[11],I[12],I[13],I[14], ++ I[15],I[16],I[17],I[18],I[19], ++ I[20],I[21],I[22],I[23],I[24]); ++ cimg_for_borderXY(*this,x,y,2) ++ res(x,y,c) = get_crop(std::max(0,x - 2),std::max(0,y - 2),0,c, ++ std::min(w1,x + 2),std::min(h1,y + 2),0,c).median(); ++ } ++ } break; ++ case 7 : { ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2)) ++ cimg_forC(*this,c) { ++ CImg I(49); ++ cimg_for_in7x7(*this,3,3,w4,h4,x,y,0,c,I,T) ++ res(x,y,c) = cimg::median(I[0],I[1],I[2],I[3],I[4],I[5],I[6], ++ I[7],I[8],I[9],I[10],I[11],I[12],I[13], ++ I[14],I[15],I[16],I[17],I[18],I[19],I[20], ++ I[21],I[22],I[23],I[24],I[25],I[26],I[27], ++ I[28],I[29],I[30],I[31],I[32],I[33],I[34], ++ I[35],I[36],I[37],I[38],I[39],I[40],I[41], ++ I[42],I[43],I[44],I[45],I[46],I[47],I[48]); ++ cimg_for_borderXY(*this,x,y,3) ++ res(x,y,c) = get_crop(std::max(0,x - 3),std::max(0,y - 3),0,c, ++ std::min(w1,x + 3),std::min(h1,y + 3),0,c).median(); ++ } ++ } break; ++ default : { ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=16 && _height*_spectrum>=4)) ++ cimg_forXYC(*this,x,y,c) { ++ 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>=width()?width() - 1:x1, ny1 = y1>=height()?height() - 1:y1; ++ res(x,y,c) = get_crop(nx0,ny0,0,c,nx1,ny1,0,c).median(); ++ } ++ } ++ } ++ } ++ } ++ return res; ++ } ++ ++ //! Sharpen image. ++ /** ++ \param amplitude Sharpening amplitude ++ \param sharpen_type Select sharpening method. Can be { false=inverse diffusion | true=shock filters }. ++ \param edge Edge threshold (shock filters only). ++ \param alpha Gradient smoothness (shock filters only). ++ \param sigma Tensor smoothness (shock filters only). ++ **/ ++ 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 val_min, val_max = max_min(val_min); ++ const float nedge = edge/2; ++ CImg velocity(_width,_height,_depth,_spectrum), _veloc_max(_spectrum); ++ ++ if (_depth>1) { // 3d ++ if (sharpen_type) { // Shock filters. ++ CImg G = (alpha>0?get_blur(alpha).get_structure_tensors():get_structure_tensors()); ++ if (sigma>0) G.blur(sigma); ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=32 && _height*_depth>=16)) ++ cimg_forYZ(G,y,z) { ++ Tfloat *ptrG0 = G.data(0,y,z,0), *ptrG1 = G.data(0,y,z,1), ++ *ptrG2 = G.data(0,y,z,2), *ptrG3 = G.data(0,y,z,3); ++ CImg val, vec; ++ cimg_forX(G,x) { ++ G.get_tensor_at(x,y,z).symmetric_eigen(val,vec); ++ if (val[0]<0) val[0] = 0; ++ if (val[1]<0) val[1] = 0; ++ if (val[2]<0) val[2] = 0; ++ *(ptrG0++) = vec(0,0); ++ *(ptrG1++) = vec(0,1); ++ *(ptrG2++) = vec(0,2); ++ *(ptrG3++) = 1 - (Tfloat)std::pow(1 + val[0] + val[1] + val[2],-(Tfloat)nedge); ++ } ++ } ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=512 && _spectrum>=2)) ++ cimg_forC(*this,c) { ++ Tfloat *ptrd = velocity.data(0,0,0,c), veloc_max = 0; ++ CImg_3x3x3(I,Tfloat); ++ cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { ++ 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 = Incc + Ipcc - 2*Iccc, ++ ixy = (Innc + Ippc - Inpc - Ipnc)/4, ++ ixz = (Incn + Ipcp - Incp - Ipcn)/4, ++ iyy = Icnc + Icpc - 2*Iccc, ++ iyz = (Icnn + Icpp - Icnp - Icpn)/4, ++ izz = Iccn + Iccp - 2*Iccc, ++ ixf = Incc - Iccc, ++ ixb = Iccc - Ipcc, ++ iyf = Icnc - Iccc, ++ iyb = Iccc - Icpc, ++ izf = Iccn - Iccc, ++ izb = 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 = -amp*cimg::sign(itt)*cimg::abs(it); ++ *(ptrd++) = veloc; ++ if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; ++ } ++ _veloc_max[c] = veloc_max; ++ } ++ } else // Inverse diffusion. ++ cimg_forC(*this,c) { ++ Tfloat *ptrd = velocity.data(0,0,0,c), veloc_max = 0; ++ CImg_3x3x3(I,Tfloat); ++ cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { ++ const Tfloat veloc = -Ipcc - Incc - Icpc - Icnc - Iccp - Iccn + 6*Iccc; ++ *(ptrd++) = veloc; ++ if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; ++ } ++ _veloc_max[c] = veloc_max; ++ } ++ } else { // 2d. ++ if (sharpen_type) { // Shock filters. ++ CImg G = (alpha>0?get_blur(alpha).get_structure_tensors():get_structure_tensors()); ++ if (sigma>0) G.blur(sigma); ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=32 && _height>=16)) ++ cimg_forY(G,y) { ++ CImg val, vec; ++ Tfloat *ptrG0 = G.data(0,y,0,0), *ptrG1 = G.data(0,y,0,1), *ptrG2 = G.data(0,y,0,2); ++ cimg_forX(G,x) { ++ G.get_tensor_at(x,y).symmetric_eigen(val,vec); ++ if (val[0]<0) val[0] = 0; ++ if (val[1]<0) val[1] = 0; ++ *(ptrG0++) = vec(0,0); ++ *(ptrG1++) = vec(0,1); ++ *(ptrG2++) = 1 - (Tfloat)std::pow(1 + val[0] + val[1],-(Tfloat)nedge); ++ } ++ } ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=512 && _spectrum>=2)) ++ cimg_forC(*this,c) { ++ Tfloat *ptrd = velocity.data(0,0,0,c), veloc_max = 0; ++ CImg_3x3(I,Tfloat); ++ cimg_for3x3(*this,x,y,0,c,I,Tfloat) { ++ const Tfloat ++ u = G(x,y,0), ++ v = G(x,y,1), ++ amp = G(x,y,2), ++ ixx = Inc + Ipc - 2*Icc, ++ ixy = (Inn + Ipp - Inp - Ipn)/4, ++ iyy = Icn + Icp - 2*Icc, ++ ixf = Inc - Icc, ++ ixb = Icc - Ipc, ++ iyf = Icn - Icc, ++ iyb = 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 = -amp*cimg::sign(itt)*cimg::abs(it); ++ *(ptrd++) = veloc; ++ if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; ++ } ++ _veloc_max[c] = veloc_max; ++ } ++ } else // Inverse diffusion. ++ cimg_forC(*this,c) { ++ Tfloat *ptrd = velocity.data(0,0,0,c), veloc_max = 0; ++ CImg_3x3(I,Tfloat); ++ cimg_for3x3(*this,x,y,0,c,I,Tfloat) { ++ const Tfloat veloc = -Ipc - Inc - Icp - Icn + 4*Icc; ++ *(ptrd++) = veloc; ++ if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; ++ } ++ _veloc_max[c] = veloc_max; ++ } ++ } ++ const Tfloat veloc_max = _veloc_max.max(); ++ if (veloc_max<=0) return *this; ++ return ((velocity*=amplitude/veloc_max)+=*this).cut(val_min,val_max).move_to(*this); ++ } ++ ++ //! Sharpen image \newinstance. ++ 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); ++ } ++ ++ //! Return image gradient. ++ /** ++ \param axes Axes considered for the gradient computation, as a C-string (e.g "xy"). ++ \param scheme = Numerical scheme used for the gradient computation: ++ - -1 = Backward finite differences ++ - 0 = Centered finite differences ++ - 1 = Forward finite differences ++ - 2 = Using Sobel kernels ++ - 3 = Using rotation invariant kernels ++ - 4 = Using Deriche recusrsive filter. ++ - 5 = Using Van Vliet recusrsive filter. ++ **/ ++ CImgList get_gradient(const char *const axes=0, const int scheme=3) const { ++ CImgList grad(2,_width,_height,_depth,_spectrum); ++ bool is_3d = false; ++ if (axes) { ++ for (unsigned int a = 0; axes[a]; ++a) { ++ const char axis = cimg::lowercase(axes[a]); ++ switch (axis) { ++ case 'x' : case 'y' : break; ++ case 'z' : is_3d = true; break; ++ default : ++ throw CImgArgumentException(_cimg_instance ++ "get_gradient(): Invalid specified axis '%c'.", ++ cimg_instance, ++ axis); ++ } ++ } ++ } else is_3d = (_depth>1); ++ if (is_3d) { ++ CImg(_width,_height,_depth,_spectrum).move_to(grad); ++ switch (scheme) { // 3d. ++ case -1 : { // Backward finite differences. ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=1048576 && _spectrum>=2)) ++ cimg_forC(*this,c) { ++ const ulongT off = (ulongT)c*_width*_height*_depth; ++ Tfloat *ptrd0 = grad[0]._data + off, *ptrd1 = grad[1]._data + off, *ptrd2 = grad[2]._data + off; ++ CImg_3x3x3(I,Tfloat); ++ cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { ++ *(ptrd0++) = Iccc - Ipcc; ++ *(ptrd1++) = Iccc - Icpc; ++ *(ptrd2++) = Iccc - Iccp; ++ } ++ } ++ } break; ++ case 1 : { // Forward finite differences. ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=1048576 && _spectrum>=2)) ++ cimg_forC(*this,c) { ++ const ulongT off = (ulongT)c*_width*_height*_depth; ++ Tfloat *ptrd0 = grad[0]._data + off, *ptrd1 = grad[1]._data + off, *ptrd2 = grad[2]._data + off; ++ CImg_2x2x2(I,Tfloat); ++ cimg_for2x2x2(*this,x,y,z,c,I,Tfloat) { ++ *(ptrd0++) = Incc - Iccc; ++ *(ptrd1++) = Icnc - Iccc; ++ *(ptrd2++) = Iccn - Iccc; ++ } ++ } ++ } break; ++ case 4 : { // 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; ++ case 5 : { // Van Vliet filter with low standard variation. ++ grad[0] = get_vanvliet(0,1,'x'); ++ grad[1] = get_vanvliet(0,1,'y'); ++ grad[2] = get_vanvliet(0,1,'z'); ++ } break; ++ default : { // Central finite differences. ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=1048576 && _spectrum>=2)) ++ cimg_forC(*this,c) { ++ const ulongT off = (ulongT)c*_width*_height*_depth; ++ Tfloat *ptrd0 = grad[0]._data + off, *ptrd1 = grad[1]._data + off, *ptrd2 = grad[2]._data + off; ++ CImg_3x3x3(I,Tfloat); ++ cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { ++ *(ptrd0++) = (Incc - Ipcc)/2; ++ *(ptrd1++) = (Icnc - Icpc)/2; ++ *(ptrd2++) = (Iccn - Iccp)/2; ++ } ++ } ++ } ++ } ++ } else switch (scheme) { // 2d. ++ case -1 : { // Backward finite differences. ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width*_height>=1048576 && _depth*_spectrum>=2)) ++ cimg_forZC(*this,z,c) { ++ const ulongT off = (ulongT)c*_width*_height*_depth + z*_width*_height; ++ Tfloat *ptrd0 = grad[0]._data + off, *ptrd1 = grad[1]._data + off; ++ CImg_3x3(I,Tfloat); ++ cimg_for3x3(*this,x,y,z,c,I,Tfloat) { ++ *(ptrd0++) = Icc - Ipc; ++ *(ptrd1++) = Icc - Icp; ++ } ++ } ++ } break; ++ case 1 : { // Forward finite differences. ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width*_height>=1048576 && _depth*_spectrum>=2)) ++ cimg_forZC(*this,z,c) { ++ const ulongT off = (ulongT)c*_width*_height*_depth + z*_width*_height; ++ Tfloat *ptrd0 = grad[0]._data + off, *ptrd1 = grad[1]._data + off; ++ CImg_2x2(I,Tfloat); ++ cimg_for2x2(*this,x,y,z,c,I,Tfloat) { ++ *(ptrd0++) = Inc - Icc; ++ *(ptrd1++) = Icn - Icc; ++ } ++ } ++ } break; ++ case 2 : { // Sobel scheme. ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width*_height>=1048576 && _depth*_spectrum>=2)) ++ cimg_forZC(*this,z,c) { ++ const ulongT off = (ulongT)c*_width*_height*_depth + z*_width*_height; ++ Tfloat *ptrd0 = grad[0]._data + off, *ptrd1 = grad[1]._data + off; ++ CImg_3x3(I,Tfloat); ++ cimg_for3x3(*this,x,y,z,c,I,Tfloat) { ++ *(ptrd0++) = -Ipp - 2*Ipc - Ipn + Inp + 2*Inc + Inn; ++ *(ptrd1++) = -Ipp - 2*Icp - Inp + Ipn + 2*Icn + Inn; ++ } ++ } ++ } break; ++ case 3 : { // Rotation invariant kernel. ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width*_height>=1048576 && _depth*_spectrum>=2)) ++ cimg_forZC(*this,z,c) { ++ const ulongT off = (ulongT)c*_width*_height*_depth + z*_width*_height; ++ Tfloat *ptrd0 = grad[0]._data + off, *ptrd1 = grad[1]._data + off; ++ CImg_3x3(I,Tfloat); ++ const Tfloat a = (Tfloat)(0.25f*(2 - std::sqrt(2.0f))), b = (Tfloat)(0.5f*(std::sqrt(2.0f) - 1)); ++ cimg_for3x3(*this,x,y,z,c,I,Tfloat) { ++ *(ptrd0++) = -a*Ipp - b*Ipc - a*Ipn + a*Inp + b*Inc + a*Inn; ++ *(ptrd1++) = -a*Ipp - b*Icp - a*Inp + a*Ipn + b*Icn + a*Inn; ++ } ++ } ++ } break; ++ case 4 : { // Van Vliet filter with low standard variation ++ grad[0] = get_deriche(0,1,'x'); ++ grad[1] = get_deriche(0,1,'y'); ++ } break; ++ case 5 : { // Deriche filter with low standard variation ++ grad[0] = get_vanvliet(0,1,'x'); ++ grad[1] = get_vanvliet(0,1,'y'); ++ } break; ++ default : { // Central finite differences ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width*_height>=1048576 && _depth*_spectrum>=2)) ++ cimg_forZC(*this,z,c) { ++ const ulongT off = (ulongT)c*_width*_height*_depth + z*_width*_height; ++ Tfloat *ptrd0 = grad[0]._data + off, *ptrd1 = grad[1]._data + off; ++ CImg_3x3(I,Tfloat); ++ cimg_for3x3(*this,x,y,z,c,I,Tfloat) { ++ *(ptrd0++) = (Inc - Ipc)/2; ++ *(ptrd1++) = (Icn - Icp)/2; ++ } ++ } ++ } ++ } ++ if (!axes) return grad; ++ CImgList res; ++ for (unsigned int l = 0; axes[l]; ++l) { ++ const char axis = cimg::lowercase(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; ++ } ++ ++ //! Return image hessian. ++ /** ++ \param axes Axes considered for the hessian computation, as a C-string (e.g "xy"). ++ **/ ++ CImgList get_hessian(const char *const axes=0) const { ++ CImgList res; ++ const char *naxes = axes, *const def_axes2d = "xxxyyy", *const def_axes3d = "xxxyxzyyyzzz"; ++ if (!axes) naxes = _depth>1?def_axes3d:def_axes2d; ++ const unsigned int lmax = (unsigned int)std::strlen(naxes); ++ if (lmax%2) ++ throw CImgArgumentException(_cimg_instance ++ "get_hessian(): Invalid specified axes '%s'.", ++ cimg_instance, ++ naxes); ++ ++ res.assign(lmax/2,_width,_height,_depth,_spectrum); ++ if (!cimg::strcasecmp(naxes,def_axes3d)) { // 3d ++ ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=1048576 && _spectrum>=2)) ++ cimg_forC(*this,c) { ++ const ulongT off = (ulongT)c*_width*_height*_depth; ++ Tfloat ++ *ptrd0 = res[0]._data + off, *ptrd1 = res[1]._data + off, *ptrd2 = res[2]._data + off, ++ *ptrd3 = res[3]._data + off, *ptrd4 = res[4]._data + off, *ptrd5 = res[5]._data + off; ++ CImg_3x3x3(I,Tfloat); ++ cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { ++ *(ptrd0++) = Ipcc + Incc - 2*Iccc; // Ixx ++ *(ptrd1++) = (Ippc + Innc - Ipnc - Inpc)/4; // Ixy ++ *(ptrd2++) = (Ipcp + Incn - Ipcn - Incp)/4; // Ixz ++ *(ptrd3++) = Icpc + Icnc - 2*Iccc; // Iyy ++ *(ptrd4++) = (Icpp + Icnn - Icpn - Icnp)/4; // Iyz ++ *(ptrd5++) = Iccn + Iccp - 2*Iccc; // Izz ++ } ++ } ++ } else if (!cimg::strcasecmp(naxes,def_axes2d)) { // 2d ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width*_height>=1048576 && _depth*_spectrum>=2)) ++ cimg_forZC(*this,z,c) { ++ const ulongT off = (ulongT)c*_width*_height*_depth + z*_width*_height; ++ Tfloat *ptrd0 = res[0]._data + off, *ptrd1 = res[1]._data + off, *ptrd2 = res[2]._data + off; ++ CImg_3x3(I,Tfloat); ++ cimg_for3x3(*this,x,y,z,c,I,Tfloat) { ++ *(ptrd0++) = Ipc + Inc - 2*Icc; // Ixx ++ *(ptrd1++) = (Ipp + Inn - Ipn - Inp)/4; // Ixy ++ *(ptrd2++) = Icp + Icn - 2*Icc; // Iyy ++ } ++ } ++ } else for (unsigned int l = 0; laxis2) cimg::swap(axis1,axis2); ++ bool valid_axis = false; ++ if (axis1=='x' && axis2=='x') { // Ixx ++ valid_axis = true; ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width*_height>=1048576 && _depth*_spectrum>=2)) ++ cimg_forZC(*this,z,c) { ++ Tfloat *ptrd = res[l2].data(0,0,z,c); ++ CImg_3x3(I,Tfloat); ++ cimg_for3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = Ipc + Inc - 2*Icc; ++ } ++ } ++ else if (axis1=='x' && axis2=='y') { // Ixy ++ valid_axis = true; ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width*_height>=1048576 && _depth*_spectrum>=2)) ++ cimg_forZC(*this,z,c) { ++ Tfloat *ptrd = res[l2].data(0,0,z,c); ++ CImg_3x3(I,Tfloat); ++ cimg_for3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = (Ipp + Inn - Ipn - Inp)/4; ++ } ++ } ++ else if (axis1=='x' && axis2=='z') { // Ixz ++ valid_axis = true; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=1048576 && _spectrum>=2)) ++ cimg_forC(*this,c) { ++ Tfloat *ptrd = res[l2].data(0,0,0,c); ++ CImg_3x3x3(I,Tfloat); ++ cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = (Ipcp + Incn - Ipcn - Incp)/4; ++ } ++ } ++ else if (axis1=='y' && axis2=='y') { // Iyy ++ valid_axis = true; ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width*_height>=1048576 && _depth*_spectrum>=2)) ++ cimg_forZC(*this,z,c) { ++ Tfloat *ptrd = res[l2].data(0,0,z,c); ++ CImg_3x3(I,Tfloat); ++ cimg_for3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = Icp + Icn - 2*Icc; ++ } ++ } ++ else if (axis1=='y' && axis2=='z') { // Iyz ++ valid_axis = true; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=1048576 && _spectrum>=2)) ++ cimg_forC(*this,c) { ++ Tfloat *ptrd = res[l2].data(0,0,0,c); ++ CImg_3x3x3(I,Tfloat); ++ cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = (Icpp + Icnn - Icpn - Icnp)/4; ++ } ++ } ++ else if (axis1=='z' && axis2=='z') { // Izz ++ valid_axis = true; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=1048576 && _spectrum>=2)) ++ cimg_forC(*this,c) { ++ Tfloat *ptrd = res[l2].data(0,0,0,c); ++ CImg_3x3x3(I,Tfloat); ++ cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = Iccn + Iccp - 2*Iccc; ++ } ++ } ++ else if (!valid_axis) ++ throw CImgArgumentException(_cimg_instance ++ "get_hessian(): Invalid specified axes '%s'.", ++ cimg_instance, ++ naxes); ++ } ++ return res; ++ } ++ ++ //! Compute image laplacian. ++ CImg& laplacian() { ++ return get_laplacian().move_to(*this); ++ } ++ ++ //! Compute image laplacian \newinstance. ++ CImg get_laplacian() const { ++ if (is_empty()) return CImg(); ++ CImg res(_width,_height,_depth,_spectrum); ++ if (_depth>1) { // 3d ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=1048576 && _spectrum>=2)) ++ cimg_forC(*this,c) { ++ Tfloat *ptrd = res.data(0,0,0,c); ++ CImg_3x3x3(I,Tfloat); ++ cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = Incc + Ipcc + Icnc + Icpc + Iccn + Iccp - 6*Iccc; ++ } ++ } else if (_height>1) { // 2d ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=1048576 && _depth*_spectrum>=2)) ++ cimg_forC(*this,c) { ++ Tfloat *ptrd = res.data(0,0,0,c); ++ CImg_3x3(I,Tfloat); ++ cimg_for3x3(*this,x,y,0,c,I,Tfloat) *(ptrd++) = Inc + Ipc + Icn + Icp - 4*Icc; ++ } ++ } else { // 1d ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=1048576 && _height*_depth*_spectrum>=2)) ++ cimg_forC(*this,c) { ++ Tfloat *ptrd = res.data(0,0,0,c); ++ CImg_3x3(I,Tfloat); ++ cimg_for3x3(*this,x,y,0,c,I,Tfloat) *(ptrd++) = Inc + Ipc - 2*Icc; ++ } ++ } ++ return res; ++ } ++ ++ //! Compute the structure tensor field of an image. ++ /** ++ \param is_fwbw_scheme scheme. Can be { false=centered | true=forward-backward } ++ **/ ++ CImg& structure_tensors(const bool is_fwbw_scheme=false) { ++ return get_structure_tensors(is_fwbw_scheme).move_to(*this); ++ } ++ ++ //! Compute the structure tensor field of an image \newinstance. ++ CImg get_structure_tensors(const bool is_fwbw_scheme=false) const { ++ if (is_empty()) return *this; ++ CImg res; ++ if (_depth>1) { // 3d ++ res.assign(_width,_height,_depth,6,0); ++ if (!is_fwbw_scheme) { // Classical central finite differences ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=1048576 && _spectrum>=2)) ++ cimg_forC(*this,c) { ++ Tfloat ++ *ptrd0 = res.data(0,0,0,0), *ptrd1 = res.data(0,0,0,1), *ptrd2 = res.data(0,0,0,2), ++ *ptrd3 = res.data(0,0,0,3), *ptrd4 = res.data(0,0,0,4), *ptrd5 = res.data(0,0,0,5); ++ CImg_3x3x3(I,Tfloat); ++ cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { ++ const Tfloat ++ ix = (Incc - Ipcc)/2, ++ iy = (Icnc - Icpc)/2, ++ iz = (Iccn - Iccp)/2; ++ *(ptrd0++)+=ix*ix; ++ *(ptrd1++)+=ix*iy; ++ *(ptrd2++)+=ix*iz; ++ *(ptrd3++)+=iy*iy; ++ *(ptrd4++)+=iy*iz; ++ *(ptrd5++)+=iz*iz; ++ } ++ } ++ } else { // Forward/backward finite differences. ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=1048576 && _spectrum>=2)) ++ cimg_forC(*this,c) { ++ Tfloat ++ *ptrd0 = res.data(0,0,0,0), *ptrd1 = res.data(0,0,0,1), *ptrd2 = res.data(0,0,0,2), ++ *ptrd3 = res.data(0,0,0,3), *ptrd4 = res.data(0,0,0,4), *ptrd5 = res.data(0,0,0,5); ++ CImg_3x3x3(I,Tfloat); ++ cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { ++ const Tfloat ++ ixf = Incc - Iccc, ixb = Iccc - Ipcc, ++ iyf = Icnc - Iccc, iyb = Iccc - Icpc, ++ izf = Iccn - Iccc, izb = Iccc - Iccp; ++ *(ptrd0++)+=(ixf*ixf + ixb*ixb)/2; ++ *(ptrd1++)+=(ixf*iyf + ixf*iyb + ixb*iyf + ixb*iyb)/4; ++ *(ptrd2++)+=(ixf*izf + ixf*izb + ixb*izf + ixb*izb)/4; ++ *(ptrd3++)+=(iyf*iyf + iyb*iyb)/2; ++ *(ptrd4++)+=(iyf*izf + iyf*izb + iyb*izf + iyb*izb)/4; ++ *(ptrd5++)+=(izf*izf + izb*izb)/2; ++ } ++ } ++ } ++ } else { // 2d ++ res.assign(_width,_height,_depth,3,0); ++ if (!is_fwbw_scheme) { // Classical central finite differences ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=1048576 && _depth*_spectrum>=2)) ++ cimg_forC(*this,c) { ++ Tfloat *ptrd0 = res.data(0,0,0,0), *ptrd1 = res.data(0,0,0,1), *ptrd2 = res.data(0,0,0,2); ++ CImg_3x3(I,Tfloat); ++ cimg_for3x3(*this,x,y,0,c,I,Tfloat) { ++ const Tfloat ++ ix = (Inc - Ipc)/2, ++ iy = (Icn - Icp)/2; ++ *(ptrd0++)+=ix*ix; ++ *(ptrd1++)+=ix*iy; ++ *(ptrd2++)+=iy*iy; ++ } ++ } ++ } else { // Forward/backward finite differences (version 2). ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=1048576 && _depth*_spectrum>=2)) ++ cimg_forC(*this,c) { ++ Tfloat *ptrd0 = res.data(0,0,0,0), *ptrd1 = res.data(0,0,0,1), *ptrd2 = res.data(0,0,0,2); ++ CImg_3x3(I,Tfloat); ++ cimg_for3x3(*this,x,y,0,c,I,Tfloat) { ++ const Tfloat ++ ixf = Inc - Icc, ixb = Icc - Ipc, ++ iyf = Icn - Icc, iyb = Icc - Icp; ++ *(ptrd0++)+=(ixf*ixf + ixb*ixb)/2; ++ *(ptrd1++)+=(ixf*iyf + ixf*iyb + ixb*iyf + ixb*iyb)/4; ++ *(ptrd2++)+=(iyf*iyf + iyb*iyb)/2; ++ } ++ } ++ } ++ } ++ return res; ++ } ++ ++ //! Compute field of diffusion tensors for edge-preserving smoothing. ++ /** ++ \param sharpness Sharpness ++ \param anisotropy Anisotropy ++ \param alpha Standard deviation of the gradient blur. ++ \param sigma Standard deviation of the structure tensor blur. ++ \param is_sqrt Tells if the square root of the tensor field is computed instead. ++ **/ ++ CImg& diffusion_tensors(const float sharpness=0.7f, const float anisotropy=0.6f, ++ const float alpha=0.6f, const float sigma=1.1f, const bool is_sqrt=false) { ++ CImg res; ++ const float ++ nsharpness = std::max(sharpness,1e-5f), ++ power1 = (is_sqrt?0.5f:1)*nsharpness, ++ power2 = power1/(1e-7f + 1 - anisotropy); ++ blur(alpha).normalize(0,(T)255); ++ ++ if (_depth>1) { // 3d ++ get_structure_tensors().move_to(res).blur(sigma); ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=256 && _height*_depth>=256)) ++ cimg_forYZ(*this,y,z) { ++ Tfloat ++ *ptrd0 = res.data(0,y,z,0), *ptrd1 = res.data(0,y,z,1), *ptrd2 = res.data(0,y,z,2), ++ *ptrd3 = res.data(0,y,z,3), *ptrd4 = res.data(0,y,z,4), *ptrd5 = res.data(0,y,z,5); ++ CImg val(3), vec(3,3); ++ cimg_forX(*this,x) { ++ res.get_tensor_at(x,y,z).symmetric_eigen(val,vec); ++ const float ++ _l1 = val[2], _l2 = val[1], _l3 = val[0], ++ l1 = _l1>0?_l1:0, l2 = _l2>0?_l2:0, l3 = _l3>0?_l3: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)std::pow(1 + l1 + l2 + l3,-power1), ++ n2 = (float)std::pow(1 + l1 + l2 + l3,-power2); ++ *(ptrd0++) = n1*(ux*ux + vx*vx) + n2*wx*wx; ++ *(ptrd1++) = n1*(ux*uy + vx*vy) + n2*wx*wy; ++ *(ptrd2++) = n1*(ux*uz + vx*vz) + n2*wx*wz; ++ *(ptrd3++) = n1*(uy*uy + vy*vy) + n2*wy*wy; ++ *(ptrd4++) = n1*(uy*uz + vy*vz) + n2*wy*wz; ++ *(ptrd5++) = n1*(uz*uz + vz*vz) + n2*wz*wz; ++ } ++ } ++ } else { // for 2d images ++ get_structure_tensors().move_to(res).blur(sigma); ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=256 && _height>=256)) ++ cimg_forY(*this,y) { ++ Tfloat *ptrd0 = res.data(0,y,0,0), *ptrd1 = res.data(0,y,0,1), *ptrd2 = res.data(0,y,0,2); ++ CImg val(2), vec(2,2); ++ cimg_forX(*this,x) { ++ res.get_tensor_at(x,y).symmetric_eigen(val,vec); ++ const float ++ _l1 = val[1], _l2 = val[0], ++ l1 = _l1>0?_l1:0, l2 = _l2>0?_l2:0, ++ ux = vec(1,0), uy = vec(1,1), ++ vx = vec(0,0), vy = vec(0,1), ++ n1 = (float)std::pow(1 + l1 + l2,-power1), ++ n2 = (float)std::pow(1 + l1 + l2,-power2); ++ *(ptrd0++) = n1*ux*ux + n2*vx*vx; ++ *(ptrd1++) = n1*ux*uy + n2*vx*vy; ++ *(ptrd2++) = n1*uy*uy + n2*vy*vy; ++ } ++ } ++ } ++ return res.move_to(*this); ++ } ++ ++ //! Compute field of diffusion tensors for edge-preserving smoothing \newinstance. ++ CImg get_diffusion_tensors(const float sharpness=0.7f, const float anisotropy=0.6f, ++ const float alpha=0.6f, const float sigma=1.1f, const bool is_sqrt=false) const { ++ return CImg(*this,false).diffusion_tensors(sharpness,anisotropy,alpha,sigma,is_sqrt); ++ } ++ ++ //! Estimate displacement field between two images. ++ /** ++ \param source Reference image. ++ \param smoothness Smoothness of estimated displacement field. ++ \param precision Precision required for algorithm convergence. ++ \param nb_scales Number of scales used to estimate the displacement field. ++ \param iteration_max Maximum number of iterations allowed for one scale. ++ \param is_backward If false, match I2(X + U(X)) = I1(X), else match I2(X) = I1(X - U(X)). ++ \param guide Image used as the initial correspondence estimate for the algorithm. ++ 'guide' may have a last channel with boolean values (0=false | other=true) that ++ tells for each pixel if its correspondence vector is constrained to its initial value (constraint mask). ++ **/ ++ CImg& displacement(const CImg& source, const float smoothness=0.1f, const float precision=5.0f, ++ const unsigned int nb_scales=0, const unsigned int iteration_max=10000, ++ const bool is_backward=false, ++ const CImg& guide=CImg::const_empty()) { ++ return get_displacement(source,smoothness,precision,nb_scales,iteration_max,is_backward,guide). ++ move_to(*this); ++ } ++ ++ //! Estimate displacement field between two images \newinstance. ++ CImg get_displacement(const CImg& source, ++ const float smoothness=0.1f, const float precision=5.0f, ++ const unsigned int nb_scales=0, const unsigned int iteration_max=10000, ++ const bool is_backward=false, ++ const CImg& guide=CImg::const_empty()) const { ++ if (is_empty() || !source) return +*this; ++ if (!is_sameXYZC(source)) ++ throw CImgArgumentException(_cimg_instance ++ "displacement(): Instance and source image (%u,%u,%u,%u,%p) have " ++ "different dimensions.", ++ cimg_instance, ++ source._width,source._height,source._depth,source._spectrum,source._data); ++ if (precision<0) ++ throw CImgArgumentException(_cimg_instance ++ "displacement(): Invalid specified precision %g " ++ "(should be >=0)", ++ cimg_instance, ++ precision); ++ ++ const bool is_3d = source._depth>1; ++ const unsigned int constraint = is_3d?3:2; ++ ++ if (guide && ++ (guide._width!=_width || guide._height!=_height || guide._depth!=_depth || guide._spectrum0?nb_scales: ++ (unsigned int)cimg::round(std::log(mins/8.0)/std::log(1.5),1,1); ++ ++ const float _precision = (float)std::pow(10.0,-(double)precision); ++ float sm, sM = source.max_min(sm), tm, tM = max_min(tm); ++ const float sdelta = sm==sM?1:(sM - sm), tdelta = tm==tM?1:(tM - tm); ++ ++ CImg U, V; ++ floatT bound = 0; ++ for (int scale = (int)_nb_scales - 1; scale>=0; --scale) { ++ const float factor = (float)std::pow(1.5,(double)scale); ++ const unsigned int ++ _sw = (unsigned int)(_width/factor), sw = _sw?_sw:1, ++ _sh = (unsigned int)(_height/factor), sh = _sh?_sh:1, ++ _sd = (unsigned int)(_depth/factor), sd = _sd?_sd:1; ++ if (sw<5 && sh<5 && (!is_3d || sd<5)) continue; // skip too small scales. ++ const CImg ++ I1 = (source.get_resize(sw,sh,sd,-100,2)-=sm)/=sdelta, ++ I2 = (get_resize(I1,2)-=tm)/=tdelta; ++ if (guide._spectrum>constraint) guide.get_resize(I2._width,I2._height,I2._depth,-100,1).move_to(V); ++ if (U) (U*=1.5f).resize(I2._width,I2._height,I2._depth,-100,3); ++ else { ++ if (guide) ++ guide.get_shared_channels(0,is_3d?2:1).get_resize(I2._width,I2._height,I2._depth,-100,2).move_to(U); ++ else U.assign(I2._width,I2._height,I2._depth,is_3d?3:2,0); ++ } ++ ++ float dt = 2, energy = cimg::type::max(); ++ const CImgList dI = is_backward?I1.get_gradient():I2.get_gradient(); ++ cimg_abort_init; ++ ++ for (unsigned int iteration = 0; iteration=0) // Isotropic regularization. ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_height*_depth>=8 && _width>=16) ++ reduction(+:_energy)) ++ cimg_forYZ(U,y,z) { ++ const int ++ _p1y = y?y - 1:0, _n1y = yx) U(x,y,z,0) = (float)x; ++ if (U(x,y,z,1)>y) U(x,y,z,1) = (float)y; ++ if (U(x,y,z,2)>z) U(x,y,z,2) = (float)z; ++ bound = (float)x - _width; if (U(x,y,z,0)<=bound) U(x,y,z,0) = bound; ++ bound = (float)y - _height; if (U(x,y,z,1)<=bound) U(x,y,z,1) = bound; ++ bound = (float)z - _depth; if (U(x,y,z,2)<=bound) U(x,y,z,2) = bound; ++ } else { ++ if (U(x,y,z,0)<-x) U(x,y,z,0) = -(float)x; ++ if (U(x,y,z,1)<-y) U(x,y,z,1) = -(float)y; ++ if (U(x,y,z,2)<-z) U(x,y,z,2) = -(float)z; ++ bound = (float)_width - x; if (U(x,y,z,0)>=bound) U(x,y,z,0) = bound; ++ bound = (float)_height - y; if (U(x,y,z,1)>=bound) U(x,y,z,1) = bound; ++ bound = (float)_depth - z; if (U(x,y,z,2)>=bound) U(x,y,z,2) = bound; ++ } ++ _energy+=delta_I*delta_I + smoothness*_energy_regul; ++ } ++ if (V) cimg_forXYZ(V,x,y,z) if (V(x,y,z,3)) { // Apply constraints. ++ U(x,y,z,0) = V(x,y,z,0)/factor; ++ U(x,y,z,1) = V(x,y,z,1)/factor; ++ U(x,y,z,2) = V(x,y,z,2)/factor; ++ } ++ } else { // Anisotropic regularization. ++ const float nsmoothness = -smoothness; ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_height*_depth>=8 && _width>=16) ++ reduction(+:_energy)) ++ cimg_forYZ(U,y,z) { ++ const int ++ _p1y = y?y - 1:0, _n1y = yx) U(x,y,z,0) = (float)x; ++ if (U(x,y,z,1)>y) U(x,y,z,1) = (float)y; ++ if (U(x,y,z,2)>z) U(x,y,z,2) = (float)z; ++ bound = (float)x - _width; if (U(x,y,z,0)<=bound) U(x,y,z,0) = bound; ++ bound = (float)y - _height; if (U(x,y,z,1)<=bound) U(x,y,z,1) = bound; ++ bound = (float)z - _depth; if (U(x,y,z,2)<=bound) U(x,y,z,2) = bound; ++ } else { ++ if (U(x,y,z,0)<-x) U(x,y,z,0) = -(float)x; ++ if (U(x,y,z,1)<-y) U(x,y,z,1) = -(float)y; ++ if (U(x,y,z,2)<-z) U(x,y,z,2) = -(float)z; ++ bound = (float)_width - x; if (U(x,y,z,0)>=bound) U(x,y,z,0) = bound; ++ bound = (float)_height - y; if (U(x,y,z,1)>=bound) U(x,y,z,1) = bound; ++ bound = (float)_depth - z; if (U(x,y,z,2)>=bound) U(x,y,z,2) = bound; ++ } ++ _energy+=delta_I*delta_I + nsmoothness*_energy_regul; ++ } ++ if (V) cimg_forXYZ(V,x,y,z) if (V(x,y,z,3)) { // Apply constraints. ++ U(x,y,z,0) = V(x,y,z,0)/factor; ++ U(x,y,z,1) = V(x,y,z,1)/factor; ++ U(x,y,z,2) = V(x,y,z,2)/factor; ++ } ++ } ++ } ++ } else { // 2d version. ++ if (smoothness>=0) // Isotropic regularization. ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_height>=8 && _width>=16) reduction(+:_energy)) ++ cimg_forY(U,y) { ++ const int _p1y = y?y - 1:0, _n1y = yx) U(x,y,0) = (float)x; ++ if (U(x,y,1)>y) U(x,y,1) = (float)y; ++ bound = (float)x - _width; if (U(x,y,0)<=bound) U(x,y,0) = bound; ++ bound = (float)y - _height; if (U(x,y,1)<=bound) U(x,y,1) = bound; ++ } else { ++ if (U(x,y,0)<-x) U(x,y,0) = -(float)x; ++ if (U(x,y,1)<-y) U(x,y,1) = -(float)y; ++ bound = (float)_width - x; if (U(x,y,0)>=bound) U(x,y,0) = bound; ++ bound = (float)_height - y; if (U(x,y,1)>=bound) U(x,y,1) = bound; ++ } ++ _energy+=delta_I*delta_I + smoothness*_energy_regul; ++ } ++ if (V) cimg_forX(V,x) if (V(x,y,2)) { // Apply constraints. ++ U(x,y,0) = V(x,y,0)/factor; ++ U(x,y,1) = V(x,y,1)/factor; ++ } ++ } else { // Anisotropic regularization. ++ const float nsmoothness = -smoothness; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_height>=8 && _width>=16) reduction(+:_energy)) ++ cimg_forY(U,y) { ++ const int _p1y = y?y - 1:0, _n1y = yx) U(x,y,0) = (float)x; ++ if (U(x,y,1)>y) U(x,y,1) = (float)y; ++ bound = (float)x - _width; if (U(x,y,0)<=bound) U(x,y,0) = bound; ++ bound = (float)y - _height; if (U(x,y,1)<=bound) U(x,y,1) = bound; ++ } else { ++ if (U(x,y,0)<-x) U(x,y,0) = -(float)x; ++ if (U(x,y,1)<-y) U(x,y,1) = -(float)y; ++ bound = (float)_width - x; if (U(x,y,0)>=bound) U(x,y,0) = bound; ++ bound = (float)_height - y; if (U(x,y,1)>=bound) U(x,y,1) = bound; ++ } ++ _energy+=delta_I*delta_I + nsmoothness*_energy_regul; ++ } ++ if (V) cimg_forX(V,x) if (V(x,y,2)) { // Apply constraints. ++ U(x,y,0) = V(x,y,0)/factor; ++ U(x,y,1) = V(x,y,1)/factor; ++ } ++ } ++ } ++ } ++ const float d_energy = (_energy - energy)/(sw*sh*sd); ++ if (d_energy<=0 && -d_energy<_precision) break; ++ if (d_energy>0) dt*=0.5f; ++ energy = _energy; ++ } ++ } ++ return U; ++ } ++ ++ //! Compute correspondence map between two images, using the patch-match algorithm. ++ /** ++ \param patch_image The image containing the reference patches to match with the instance image. ++ \param patch_width Width of the patch used for matching. ++ \param patch_height Height of the patch used for matching. ++ \param patch_depth Depth of the patch used for matching. ++ \param nb_iterations Number of patch-match iterations. ++ \param nb_randoms Number of randomization attempts (per pixel). ++ \param guide Image used as the initial correspondence estimate for the algorithm. ++ 'guide' may have a last channel with boolean values (0=false | other=true) that ++ tells for each pixel if its correspondence vector is constrained to its initial value (constraint mask). ++ \param[out] matching_score Returned as the image of matching scores. ++ \note ++ The patch-match algorithm is described in this paper: ++ Connelly Barnes, Eli Shechtman, Adam Finkelstein, Dan B Goldman(2009), ++ PatchMatch: A Randomized Correspondence Algorithm for Structural Image Editing ++ **/ ++ template ++ CImg& patchmatch(const CImg& patch_image, ++ const unsigned int patch_width, ++ const unsigned int patch_height, ++ const unsigned int patch_depth, ++ const unsigned int nb_iterations, ++ const unsigned int nb_randoms, ++ const CImg &guide, ++ CImg &matching_score) { ++ return get_patchmatch(patch_image,patch_width,patch_height,patch_depth, ++ nb_iterations,nb_randoms,guide,matching_score).move_to(*this); ++ } ++ ++ //! Compute correspondence map between two images, using the patch-match algorithm \newinstance. ++ template ++ CImg get_patchmatch(const CImg& patch_image, ++ const unsigned int patch_width, ++ const unsigned int patch_height, ++ const unsigned int patch_depth, ++ const unsigned int nb_iterations, ++ const unsigned int nb_randoms, ++ const CImg &guide, ++ CImg &matching_score) const { ++ return _patchmatch(patch_image,patch_width,patch_height,patch_depth, ++ nb_iterations,nb_randoms, ++ guide,true,matching_score); ++ } ++ ++ //! Compute correspondence map between two images, using the patch-match algorithm \overloading. ++ template ++ CImg& patchmatch(const CImg& patch_image, ++ const unsigned int patch_width, ++ const unsigned int patch_height, ++ const unsigned int patch_depth, ++ const unsigned int nb_iterations, ++ const unsigned int nb_randoms, ++ const CImg &guide) { ++ return get_patchmatch(patch_image,patch_width,patch_height,patch_depth, ++ nb_iterations,nb_randoms,guide).move_to(*this); ++ } ++ ++ //! Compute correspondence map between two images, using the patch-match algorithm \overloading. ++ template ++ CImg get_patchmatch(const CImg& patch_image, ++ const unsigned int patch_width, ++ const unsigned int patch_height, ++ const unsigned int patch_depth, ++ const unsigned int nb_iterations, ++ const unsigned int nb_randoms, ++ const CImg &guide) const { ++ return _patchmatch(patch_image,patch_width,patch_height,patch_depth, ++ nb_iterations,nb_randoms, ++ guide,false,CImg::empty()); ++ } ++ ++ //! Compute correspondence map between two images, using the patch-match algorithm \overloading. ++ CImg& patchmatch(const CImg& patch_image, ++ const unsigned int patch_width, ++ const unsigned int patch_height, ++ const unsigned int patch_depth=1, ++ const unsigned int nb_iterations=5, ++ const unsigned int nb_randoms=5) { ++ return get_patchmatch(patch_image,patch_width,patch_height,patch_depth, ++ nb_iterations,nb_randoms).move_to(*this); ++ } ++ ++ //! Compute correspondence map between two images, using the patch-match algorithm \overloading. ++ CImg get_patchmatch(const CImg& patch_image, ++ const unsigned int patch_width, ++ const unsigned int patch_height, ++ const unsigned int patch_depth=1, ++ const unsigned int nb_iterations=5, ++ const unsigned int nb_randoms=5) const { ++ return _patchmatch(patch_image,patch_width,patch_height,patch_depth, ++ nb_iterations,nb_randoms, ++ CImg::const_empty(), ++ false,CImg::empty()); ++ } ++ ++ template ++ CImg _patchmatch(const CImg& patch_image, ++ const unsigned int patch_width, ++ const unsigned int patch_height, ++ const unsigned int patch_depth, ++ const unsigned int nb_iterations, ++ const unsigned int nb_randoms, ++ const CImg &guide, ++ const bool is_matching_score, ++ CImg &matching_score) const { ++ if (is_empty()) return CImg::const_empty(); ++ if (patch_image._spectrum!=_spectrum) ++ throw CImgArgumentException(_cimg_instance ++ "patchmatch(): Instance image and specified patch image (%u,%u,%u,%u,%p) " ++ "have different spectrums.", ++ cimg_instance, ++ patch_image._width,patch_image._height,patch_image._depth,patch_image._spectrum, ++ patch_image._data); ++ if (patch_width>_width || patch_height>_height || patch_depth>_depth) ++ throw CImgArgumentException(_cimg_instance ++ "patchmatch(): Specified patch size %ux%ux%u is bigger than the dimensions " ++ "of the instance image.", ++ cimg_instance,patch_width,patch_height,patch_depth); ++ if (patch_width>patch_image._width || patch_height>patch_image._height || patch_depth>patch_image._depth) ++ throw CImgArgumentException(_cimg_instance ++ "patchmatch(): Specified patch size %ux%ux%u is bigger than the dimensions " ++ "of the patch image image (%u,%u,%u,%u,%p).", ++ cimg_instance,patch_width,patch_height,patch_depth, ++ patch_image._width,patch_image._height,patch_image._depth,patch_image._spectrum, ++ patch_image._data); ++ const unsigned int ++ _constraint = patch_image._depth>1?3:2, ++ constraint = guide._spectrum>_constraint?_constraint:0; ++ ++ if (guide && ++ (guide._width!=_width || guide._height!=_height || guide._depth!=_depth || guide._spectrum<_constraint)) ++ throw CImgArgumentException(_cimg_instance ++ "patchmatch(): Specified guide (%u,%u,%u,%u,%p) has invalid dimensions " ++ "considering instance and patch image image (%u,%u,%u,%u,%p).", ++ cimg_instance, ++ guide._width,guide._height,guide._depth,guide._spectrum,guide._data, ++ patch_image._width,patch_image._height,patch_image._depth,patch_image._spectrum, ++ patch_image._data); ++ ++ CImg map(_width,_height,_depth,patch_image._depth>1?3:2); ++ CImg score(_width,_height,_depth); ++ const int ++ psizew = (int)patch_width, psizew1 = psizew/2, psizew2 = psizew - psizew1 - 1, ++ psizeh = (int)patch_height, psizeh1 = psizeh/2, psizeh2 = psizeh - psizeh1 - 1, ++ psized = (int)patch_depth, psized1 = psized/2, psized2 = psized - psized1 - 1; ++ ++ if (_depth>1 || patch_image._depth>1) { // 3d version. ++ ++ // Initialize correspondence map. ++ if (guide) cimg_forXYZ(*this,x,y,z) { // User-defined initialization. ++ const int ++ cx1 = x<=psizew1?x:(x::inf()); ++ } else cimg_forXYZ(*this,x,y,z) { // Random initialization. ++ const int ++ cx1 = x<=psizew1?x:(x::inf()); ++ } ++ ++ // Start iteration loop. ++ cimg_abort_init; ++ for (unsigned int iter = 0; iter64 && iter0) { // Compare with left neighbor. ++ const int u = map(x - 1,y,z,0), v = map(x - 1,y,z,1), w = map(x - 1,y,z,2); ++ if (u>=cx1 - 1 && u=cy1 && v=cz1 && w0) { // Compare with up neighbor. ++ const int u = map(x,y - 1,z,0), v = map(x,y - 1,z,1), w = map(x,y - 1,z,2); ++ if (u>=cx1 && u=cy1 - 1 && v=cz1 && w0) { // Compare with backward neighbor. ++ const int u = map(x,y,z - 1,0), v = map(x,y,z - 1,1), w = map(x,y,z - 1,2); ++ if (u>=cx1 && u=cy1 && v=cz1 - 1 && w=cx1 + 1 && u=cy1 && v=cz1 && w=cx1 && u=cy1 + 1 && v=cz1 && w=cx1 && u=cy1 && v=cz1 + 1 && w::inf()); ++ } else cimg_forXY(*this,x,y) { // Random initialization. ++ const int ++ cx1 = x<=psizew1?x:(x::inf()); ++ } ++ ++ // Start iteration loop. ++ for (unsigned int iter = 0; iter64 && iter0) { // Compare with left neighbor. ++ const int u = map(x - 1,y,0), v = map(x - 1,y,1); ++ if (u>=cx1 - 1 && u=cy1 && v0) { // Compare with up neighbor. ++ const int u = map(x,y - 1,0), v = map(x,y - 1,1); ++ if (u>=cx1 && u=cy1 - 1 && v=cx1 + 1 && u=cy1 && v=cx1 && u=cy1 + 1 && v& img1, const CImg& img2, ++ const unsigned int psizew, const unsigned int psizeh, ++ const int x1, const int y1, ++ const int x2, const int y2, ++ const float max_ssd) { // 2d version. ++ const T *p1 = img1.data(x1,y1), *p2 = img2.data(x2,y2); ++ const ulongT ++ offx1 = (ulongT)img1._width - psizew, ++ offx2 = (ulongT)img2._width - psizew, ++ offy1 = (ulongT)img1._width*img1._height - psizeh*img1._width, ++ offy2 = (ulongT)img2._width*img2._height - psizeh*img2._width; ++ float ssd = 0; ++ cimg_forC(img1,c) { ++ for (unsigned int j = 0; jmax_ssd) return max_ssd; ++ p1+=offx1; p2+=offx2; ++ } ++ p1+=offy1; p2+=offy2; ++ } ++ return ssd; ++ } ++ ++ static float _patchmatch(const CImg& img1, const CImg& img2, ++ const unsigned int psizew, const unsigned int psizeh, const unsigned int psized, ++ const int x1, const int y1, const int z1, ++ const int x2, const int y2, const int z2, ++ const float max_ssd) { // 3d version. ++ const T *p1 = img1.data(x1,y1,z1), *p2 = img2.data(x2,y2,z2); ++ const ulongT ++ offx1 = (ulongT)img1._width - psizew, ++ offx2 = (ulongT)img2._width - psizew, ++ offy1 = (ulongT)img1._width*img1._height - psizeh*img1._width - psizew, ++ offy2 = (ulongT)img2._width*img2._height - psizeh*img2._width - psizew, ++ offz1 = (ulongT)img1._width*img1._height*img1._depth - psized*img1._width*img1._height - ++ psizeh*img1._width - psizew, ++ offz2 = (ulongT)img2._width*img2._height*img2._depth - psized*img2._width*img2._height - ++ psizeh*img2._width - psizew; ++ float ssd = 0; ++ cimg_forC(img1,c) { ++ for (unsigned int k = 0; kmax_ssd) return max_ssd; ++ p1+=offx1; p2+=offx2; ++ } ++ p1+=offy1; p2+=offy2; ++ } ++ p1+=offz1; p2+=offz2; ++ } ++ return ssd; ++ } ++ ++ //! Compute Euclidean distance function to a specified value. ++ /** ++ \param value Reference value. ++ \param metric Type of metric. Can be { 0=Chebyshev | 1=Manhattan | 2=Euclidean | 3=Squared-euclidean }. ++ \note ++ The distance transform implementation has been submitted by A. Meijster, and implements ++ the article 'W.H. Hesselink, A. Meijster, J.B.T.M. Roerdink, ++ "A general algorithm for computing distance transforms in linear time.", ++ In: Mathematical Morphology and its Applications to Image and Signal Processing, ++ J. Goutsias, L. Vincent, and D.S. Bloomberg (eds.), Kluwer, 2000, pp. 331-340.' ++ The submitted code has then been modified to fit CImg coding style and constraints. ++ **/ ++ CImg& distance(const T& value, const unsigned int metric=2) { ++ if (is_empty()) return *this; ++ if (cimg::type::string()!=cimg::type::string()) // For datatype < int. ++ return CImg(*this,false).distance((Tint)value,metric). ++ cut((Tint)cimg::type::min(),(Tint)cimg::type::max()).move_to(*this); ++ bool is_value = false; ++ cimg_for(*this,ptr,T) *ptr = *ptr==value?is_value=true,(T)0:(T)std::max(0,99999999); // (avoid VC++ warning) ++ if (!is_value) return fill(cimg::type::max()); ++ switch (metric) { ++ case 0 : return _distance_core(_distance_sep_cdt,_distance_dist_cdt); // Chebyshev. ++ case 1 : return _distance_core(_distance_sep_mdt,_distance_dist_mdt); // Manhattan. ++ case 3 : return _distance_core(_distance_sep_edt,_distance_dist_edt); // Squared Euclidean. ++ default : return _distance_core(_distance_sep_edt,_distance_dist_edt).sqrt(); // Euclidean. ++ } ++ return *this; ++ } ++ ++ //! Compute distance to a specified value \newinstance. ++ CImg get_distance(const T& value, const unsigned int metric=2) const { ++ return CImg(*this,false).distance((Tfloat)value,metric); ++ } ++ ++ static longT _distance_sep_edt(const longT i, const longT u, const longT *const g) { ++ return (u*u - i*i + g[u] - g[i])/(2*(u - i)); ++ } ++ ++ static longT _distance_dist_edt(const longT x, const longT i, const longT *const g) { ++ return (x - i)*(x - i) + g[i]; ++ } ++ ++ static longT _distance_sep_mdt(const longT i, const longT u, const longT *const g) { ++ return (u - i<=g[u] - g[i]?999999999:(g[u] - g[i] + u + i)/2); ++ } ++ ++ static longT _distance_dist_mdt(const longT x, const longT i, const longT *const g) { ++ return (x=0) && f(t[q],s[q],g)>f(t[q],u,g)) { --q; } ++ if (q<0) { q = 0; s[0] = u; } ++ else { const longT w = 1 + sep(s[q], u, g); if (w<(longT)len) { ++q; s[q] = u; t[q] = w; }} ++ } ++ for (int u = (int)len - 1; u>=0; --u) { dt[u] = f(u,s[q],g); if (u==t[q]) --q; } // Backward scan. ++ } ++ ++ CImg& _distance_core(longT (*const sep)(const longT, const longT, const longT *const), ++ longT (*const f)(const longT, const longT, const longT *const)) { ++ // Check for g++ 4.9.X, as OpenMP seems to crash for this particular function. I have no clues why. ++#define cimg_is_gcc49x (__GNUC__==4 && __GNUC_MINOR__==9) ++ ++ const ulongT wh = (ulongT)_width*_height; ++#if defined(cimg_use_openmp) && !cimg_is_gcc49x ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2)) ++#endif ++ cimg_forC(*this,c) { ++ CImg g(_width), dt(_width), s(_width), t(_width); ++ CImg img = get_shared_channel(c); ++#if defined(cimg_use_openmp) && !cimg_is_gcc49x ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=512 && _height*_depth>=16) ++ firstprivate(g,dt,s,t)) ++#endif ++ cimg_forYZ(*this,y,z) { // Over X-direction. ++ cimg_forX(*this,x) g[x] = (longT)img(x,y,z,0,wh); ++ _distance_scan(_width,g,sep,f,s,t,dt); ++ cimg_forX(*this,x) img(x,y,z,0,wh) = (T)dt[x]; ++ } ++ if (_height>1) { ++ g.assign(_height); dt.assign(_height); s.assign(_height); t.assign(_height); ++#if defined(cimg_use_openmp) && !cimg_is_gcc49x ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_height>=512 && _width*_depth>=16) ++ firstprivate(g,dt,s,t)) ++#endif ++ cimg_forXZ(*this,x,z) { // Over Y-direction. ++ cimg_forY(*this,y) g[y] = (longT)img(x,y,z,0,wh); ++ _distance_scan(_height,g,sep,f,s,t,dt); ++ cimg_forY(*this,y) img(x,y,z,0,wh) = (T)dt[y]; ++ } ++ } ++ if (_depth>1) { ++ g.assign(_depth); dt.assign(_depth); s.assign(_depth); t.assign(_depth); ++#if defined(cimg_use_openmp) && !cimg_is_gcc49x ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_depth>=512 && _width*_height>=16) ++ firstprivate(g,dt,s,t)) ++#endif ++ cimg_forXY(*this,x,y) { // Over Z-direction. ++ cimg_forZ(*this,z) g[z] = (longT)img(x,y,z,0,wh); ++ _distance_scan(_depth,g,sep,f,s,t,dt); ++ cimg_forZ(*this,z) img(x,y,z,0,wh) = (T)dt[z]; ++ } ++ } ++ } ++ return *this; ++ } ++ ++ //! Compute chamfer distance to a specified value, with a custom metric. ++ /** ++ \param value Reference value. ++ \param metric_mask Metric mask. ++ \note The algorithm code has been initially proposed by A. Meijster, and modified by D. Tschumperlé. ++ **/ ++ template ++ CImg& distance(const T& value, const CImg& metric_mask) { ++ if (is_empty()) return *this; ++ bool is_value = false; ++ cimg_for(*this,ptr,T) *ptr = *ptr==value?is_value=true,0:(T)999999999; ++ if (!is_value) return fill(cimg::type::max()); ++ const ulongT wh = (ulongT)_width*_height; ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2)) ++ cimg_forC(*this,c) { ++ CImg img = get_shared_channel(c); ++ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width*_height*_depth>=1024)) ++ cimg_forXYZ(metric_mask,dx,dy,dz) { ++ const t weight = metric_mask(dx,dy,dz); ++ if (weight) { ++ for (int z = dz, nz = 0; z=0; --z,--nz) { // Backward scan. ++ for (int y = height() - 1 - dy, ny = height() - 1; y>=0; --y,--ny) { ++ for (int x = width() - 1 - dx, nx = width() - 1; x>=0; --x,--nx) { ++ const T dd = img(nx,ny,nz,0,wh) + weight; ++ if (dd ++ CImg get_distance(const T& value, const CImg& metric_mask) const { ++ return CImg(*this,false).distance(value,metric_mask); ++ } ++ ++ //! Compute distance to a specified value, according to a custom metric (use dijkstra algorithm). ++ /** ++ \param value Reference value. ++ \param metric Field of distance potentials. ++ \param is_high_connectivity Tells if the algorithm uses low or high connectivity. ++ \param[out] return_path An image containing the nodes of the minimal path. ++ **/ ++ template ++ CImg& distance_dijkstra(const T& value, const CImg& metric, const bool is_high_connectivity, ++ CImg& return_path) { ++ return get_distance_dijkstra(value,metric,is_high_connectivity,return_path).move_to(*this); ++ } ++ ++ //! Compute distance map to a specified value, according to a custom metric (use dijkstra algorithm) \newinstance. ++ template ++ CImg::type> ++ get_distance_dijkstra(const T& value, const CImg& metric, const bool is_high_connectivity, ++ CImg& return_path) const { ++ if (is_empty()) return return_path.assign(); ++ if (!is_sameXYZ(metric)) ++ throw CImgArgumentException(_cimg_instance ++ "distance_dijkstra(): image instance and metric map (%u,%u,%u,%u) " ++ "have incompatible dimensions.", ++ cimg_instance, ++ metric._width,metric._height,metric._depth,metric._spectrum); ++ typedef typename cimg::superset::type td; // Type used for computing cumulative distances. ++ CImg result(_width,_height,_depth,_spectrum), Q; ++ CImg is_queued(_width,_height,_depth,1); ++ if (return_path) return_path.assign(_width,_height,_depth,_spectrum); ++ ++ cimg_forC(*this,c) { ++ const CImg img = get_shared_channel(c); ++ const CImg met = metric.get_shared_channel(c%metric._spectrum); ++ CImg res = result.get_shared_channel(c); ++ CImg path = return_path?return_path.get_shared_channel(c):CImg(); ++ unsigned int sizeQ = 0; ++ ++ // Detect initial seeds. ++ is_queued.fill(0); ++ cimg_forXYZ(img,x,y,z) if (img(x,y,z)==value) { ++ Q._priority_queue_insert(is_queued,sizeQ,0,x,y,z); ++ res(x,y,z) = 0; ++ if (path) path(x,y,z) = (to)0; ++ } ++ ++ // Start distance propagation. ++ while (sizeQ) { ++ ++ // Get and remove point with minimal potential from the queue. ++ const int x = (int)Q(0,1), y = (int)Q(0,2), z = (int)Q(0,3); ++ const td P = (td)-Q(0,0); ++ Q._priority_queue_remove(sizeQ); ++ ++ // Update neighbors. ++ td npot = 0; ++ if (x - 1>=0 && Q._priority_queue_insert(is_queued,sizeQ,-(npot=met(x - 1,y,z) + P),x - 1,y,z)) { ++ res(x - 1,y,z) = npot; if (path) path(x - 1,y,z) = (to)2; ++ } ++ if (x + 1=0 && Q._priority_queue_insert(is_queued,sizeQ,-(npot=met(x,y - 1,z) + P),x,y - 1,z)) { ++ res(x,y - 1,z) = npot; if (path) path(x,y - 1,z) = (to)8; ++ } ++ if (y + 1=0 && Q._priority_queue_insert(is_queued,sizeQ,-(npot=met(x,y,z - 1) + P),x,y,z - 1)) { ++ res(x,y,z - 1) = npot; if (path) path(x,y,z - 1) = (to)32; ++ } ++ if (z + 1=0 && y - 1>=0 && ++ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x - 1,y - 1,z) + P)),x - 1,y - 1,z)) { ++ res(x - 1,y - 1,z) = npot; if (path) path(x - 1,y - 1,z) = (to)10; ++ } ++ if (x + 1=0 && ++ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x + 1,y - 1,z) + P)),x + 1,y - 1,z)) { ++ res(x + 1,y - 1,z) = npot; if (path) path(x + 1,y - 1,z) = (to)9; ++ } ++ if (x - 1>=0 && y + 1=0) { // Diagonal neighbors on slice z - 1. ++ if (x - 1>=0 && ++ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x - 1,y,z - 1) + P)),x - 1,y,z - 1)) { ++ res(x - 1,y,z - 1) = npot; if (path) path(x - 1,y,z - 1) = (to)34; ++ } ++ if (x + 1=0 && ++ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x,y - 1,z - 1) + P)),x,y - 1,z - 1)) { ++ res(x,y - 1,z - 1) = npot; if (path) path(x,y - 1,z - 1) = (to)40; ++ } ++ if (y + 1=0 && y - 1>=0 && ++ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x - 1,y - 1,z - 1) + P)), ++ x - 1,y - 1,z - 1)) { ++ res(x - 1,y - 1,z - 1) = npot; if (path) path(x - 1,y - 1,z - 1) = (to)42; ++ } ++ if (x + 1=0 && ++ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x + 1,y - 1,z - 1) + P)), ++ x + 1,y - 1,z - 1)) { ++ res(x + 1,y - 1,z - 1) = npot; if (path) path(x + 1,y - 1,z - 1) = (to)41; ++ } ++ if (x - 1>=0 && y + 1=0 && ++ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x - 1,y,z + 1) + P)),x - 1,y,z + 1)) { ++ res(x - 1,y,z + 1) = npot; if (path) path(x - 1,y,z + 1) = (to)18; ++ } ++ if (x + 1=0 && ++ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x,y - 1,z + 1) + P)),x,y - 1,z + 1)) { ++ res(x,y - 1,z + 1) = npot; if (path) path(x,y - 1,z + 1) = (to)24; ++ } ++ if (y + 1=0 && y - 1>=0 && ++ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x - 1,y - 1,z + 1) + P)), ++ x - 1,y - 1,z + 1)) { ++ res(x - 1,y - 1,z + 1) = npot; if (path) path(x - 1,y - 1,z + 1) = (to)26; ++ } ++ if (x + 1=0 && ++ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x + 1,y - 1,z + 1) + P)), ++ x + 1,y - 1,z + 1)) { ++ res(x + 1,y - 1,z + 1) = npot; if (path) path(x + 1,y - 1,z + 1) = (to)25; ++ } ++ if (x - 1>=0 && y + 1 ++ CImg& distance_dijkstra(const T& value, const CImg& metric, ++ const bool is_high_connectivity=false) { ++ return get_distance_dijkstra(value,metric,is_high_connectivity).move_to(*this); ++ } ++ ++ //! Compute distance map to a specified value, according to a custom metric (use dijkstra algorithm). \newinstance. ++ template ++ CImg get_distance_dijkstra(const T& value, const CImg& metric, ++ const bool is_high_connectivity=false) const { ++ CImg return_path; ++ return get_distance_dijkstra(value,metric,is_high_connectivity,return_path); ++ } ++ ++ //! Compute distance map to one source point, according to a custom metric (use fast marching algorithm). ++ /** ++ \param value Reference value. ++ \param metric Field of distance potentials. ++ **/ ++ template ++ CImg& distance_eikonal(const T& value, const CImg& metric) { ++ return get_distance_eikonal(value,metric).move_to(*this); ++ } ++ ++ //! Compute distance map to one source point, according to a custom metric (use fast marching algorithm). ++ template ++ CImg get_distance_eikonal(const T& value, const CImg& metric) const { ++ if (is_empty()) return *this; ++ if (!is_sameXYZ(metric)) ++ throw CImgArgumentException(_cimg_instance ++ "distance_eikonal(): image instance and metric map (%u,%u,%u,%u) have " ++ "incompatible dimensions.", ++ cimg_instance, ++ metric._width,metric._height,metric._depth,metric._spectrum); ++ CImg result(_width,_height,_depth,_spectrum,cimg::type::max()), Q; ++ CImg state(_width,_height,_depth); // -1=far away, 0=narrow, 1=frozen. ++ ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2) firstprivate(Q,state)) ++ cimg_forC(*this,c) { ++ const CImg img = get_shared_channel(c); ++ const CImg met = metric.get_shared_channel(c%metric._spectrum); ++ CImg res = result.get_shared_channel(c); ++ unsigned int sizeQ = 0; ++ state.fill(-1); ++ ++ // Detect initial seeds. ++ Tfloat *ptr1 = res._data; char *ptr2 = state._data; ++ cimg_for(img,ptr0,T) { if (*ptr0==value) { *ptr1 = 0; *ptr2 = 1; } ++ptr1; ++ptr2; } ++ ++ // Initialize seeds neighbors. ++ ptr2 = state._data; ++ cimg_forXYZ(img,x,y,z) if (*(ptr2++)==1) { ++ if (x - 1>=0 && state(x - 1,y,z)==-1) { ++ const Tfloat dist = res(x - 1,y,z) = __distance_eikonal(res,met(x - 1,y,z),x - 1,y,z); ++ Q._eik_priority_queue_insert(state,sizeQ,-dist,x - 1,y,z); ++ } ++ if (x + 1=0 && state(x,y - 1,z)==-1) { ++ const Tfloat dist = res(x,y - 1,z) = __distance_eikonal(res,met(x,y - 1,z),x,y - 1,z); ++ Q._eik_priority_queue_insert(state,sizeQ,-dist,x,y - 1,z); ++ } ++ if (y + 1=0 && state(x,y,z - 1)==-1) { ++ const Tfloat dist = res(x,y,z - 1) = __distance_eikonal(res,met(x,y,z - 1),x,y,z - 1); ++ Q._eik_priority_queue_insert(state,sizeQ,-dist,x,y,z - 1); ++ } ++ if (z + 1=0) { ++ if (x - 1>=0 && state(x - 1,y,z)!=1) { ++ const Tfloat dist = __distance_eikonal(res,met(x - 1,y,z),x - 1,y,z); ++ if (dist=0 && state(x,y - 1,z)!=1) { ++ const Tfloat dist = __distance_eikonal(res,met(x,y - 1,z),x,y - 1,z); ++ if (dist=0 && state(x,y,z - 1)!=1) { ++ const Tfloat dist = __distance_eikonal(res,met(x,y,z - 1),x,y,z - 1); ++ if (dist& res, const Tfloat P, ++ const int x=0, const int y=0, const int z=0) const { ++ const Tfloat M = (Tfloat)cimg::type::max(); ++ T T1 = (T)std::min(x - 1>=0?res(x - 1,y,z):M,x + 11) { // 3d. ++ T ++ T2 = (T)std::min(y - 1>=0?res(x,y - 1,z):M,y + 1=0?res(x,y,z - 1):M,z + 1T2) cimg::swap(T1,T2); ++ if (T2>T3) cimg::swap(T2,T3); ++ if (T1>T2) cimg::swap(T1,T2); ++ if (P<=0) return (Tfloat)T1; ++ if (T31) { // 2d. ++ T T2 = (T)std::min(y - 1>=0?res(x,y - 1,z):M,y + 1T2) cimg::swap(T1,T2); ++ if (P<=0) return (Tfloat)T1; ++ if (T2 ++ void _eik_priority_queue_insert(CImg& state, unsigned int& siz, const t value, ++ const unsigned int x, const unsigned int y, const unsigned int z) { ++ if (state(x,y,z)>0) return; ++ state(x,y,z) = 0; ++ if (++siz>=_width) { if (!is_empty()) resize(_width*2,4,1,1,0); else assign(64,4); } ++ (*this)(siz - 1,0) = (T)value; (*this)(siz - 1,1) = (T)x; (*this)(siz - 1,2) = (T)y; (*this)(siz - 1,3) = (T)z; ++ for (unsigned int pos = siz - 1, par = 0; pos && value>(*this)(par=(pos + 1)/2 - 1,0); pos = par) { ++ cimg::swap((*this)(pos,0),(*this)(par,0)); cimg::swap((*this)(pos,1),(*this)(par,1)); ++ cimg::swap((*this)(pos,2),(*this)(par,2)); cimg::swap((*this)(pos,3),(*this)(par,3)); ++ } ++ } ++ ++ //! Compute distance function to 0-valued isophotes, using the Eikonal PDE. ++ /** ++ \param nb_iterations Number of PDE iterations. ++ \param band_size Size of the narrow band. ++ \param time_step Time step of the PDE iterations. ++ **/ ++ CImg& distance_eikonal(const unsigned int nb_iterations, const float band_size=0, const float time_step=0.5f) { ++ if (is_empty()) return *this; ++ CImg velocity(*this,false); ++ for (unsigned int iteration = 0; iteration1) { // 3d ++ CImg_3x3x3(I,Tfloat); ++ cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) if (band_size<=0 || cimg::abs(Iccc)0?(Incc - Iccc):(Iccc - Ipcc), ++ iy = gy*sgn>0?(Icnc - Iccc):(Iccc - Icpc), ++ iz = gz*sgn>0?(Iccn - Iccc):(Iccc - Iccp), ++ ng = 1e-5f + cimg::hypot(gx,gy,gz), ++ ngx = gx/ng, ++ ngy = gy/ng, ++ ngz = gz/ng, ++ veloc = sgn*(ngx*ix + ngy*iy + ngz*iz - 1); ++ *(ptrd++) = veloc; ++ if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; ++ } else *(ptrd++) = 0; ++ } else { // 2d version ++ CImg_3x3(I,Tfloat); ++ cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,Tfloat) if (band_size<=0 || cimg::abs(Icc)0?(Inc - Icc):(Icc - Ipc), ++ iy = gy*sgn>0?(Icn - Icc):(Icc - Icp), ++ ng = std::max((Tfloat)1e-5,cimg::hypot(gx,gy)), ++ ngx = gx/ng, ++ ngy = gy/ng, ++ veloc = sgn*(ngx*ix + ngy*iy - 1); ++ *(ptrd++) = veloc; ++ if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; ++ } else *(ptrd++) = 0; ++ } ++ if (veloc_max>0) *this+=(velocity*=time_step/veloc_max); ++ } ++ return *this; ++ } ++ ++ //! Compute distance function to 0-valued isophotes, using the Eikonal PDE \newinstance. ++ CImg get_distance_eikonal(const unsigned int nb_iterations, const float band_size=0, ++ const float time_step=0.5f) const { ++ return CImg(*this,false).distance_eikonal(nb_iterations,band_size,time_step); ++ } ++ ++ //! Compute Haar multiscale wavelet transform. ++ /** ++ \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).move_to(*this); ++ } ++ ++ //! Compute Haar multiscale wavelet transform \newinstance. ++ 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; ++ const Tfloat sqrt2 = std::sqrt(2.0f); ++ if (nb_scales==1) { ++ switch (cimg::lowercase(axis)) { // Single scale transform ++ case 'x' : { ++ const unsigned int w = _width/2; ++ if (w) { ++ if ((w%2) && w!=1) ++ throw CImgInstanceException(_cimg_instance ++ "haar(): Sub-image width %u is not even.", ++ cimg_instance, ++ w); ++ ++ res.assign(_width,_height,_depth,_spectrum); ++ if (invert) cimg_forYZC(*this,y,z,c) { // Inverse transform along X ++ for (unsigned int x = 0, xw = w, x2 = 0; x& haar(const bool invert=false, const unsigned int nb_scales=1) { ++ return get_haar(invert,nb_scales).move_to(*this); ++ } ++ ++ //! Compute Haar multiscale wavelet transform \newinstance. ++ 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).move_to(res); ++ if (_height>1) { if (res) res.haar('y',invert,1); else get_haar('y',invert,1).move_to(res); } ++ if (_depth>1) { if (res) res.haar('z',invert,1); else get_haar('z',invert,1).move_to(res); } ++ if (res) return res; ++ } else { // Multi-scale transform ++ if (invert) { // Inverse transform ++ res.assign(*this,false); ++ 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 get_FFT(const char axis, const bool is_invert=false) const { ++ CImgList res(*this,CImg()); ++ CImg::FFT(res[0],res[1],axis,is_invert); ++ return res; ++ } ++ ++ //! Compute n-d Fast Fourier Transform. ++ /* ++ \param is_invert Tells if the forward (\c false) or inverse (\c true) FFT is computed. ++ **/ ++ CImgList get_FFT(const bool is_invert=false) const { ++ CImgList res(*this,CImg()); ++ CImg::FFT(res[0],res[1],is_invert); ++ return res; ++ } ++ ++ //! Compute 1d Fast Fourier Transform, along a specified axis. ++ /** ++ \param[in,out] real Real part of the pixel values. ++ \param[in,out] imag Imaginary part of the pixel values. ++ \param axis Axis along which the FFT is computed. ++ \param is_invert Tells if the forward (\c false) or inverse (\c true) FFT is computed. ++ **/ ++ static void FFT(CImg& real, CImg& imag, const char axis, const bool is_invert=false) { ++ if (!real) ++ throw CImgInstanceException("CImg<%s>::FFT(): Specified real part is empty.", ++ pixel_type()); ++ ++ if (!imag) imag.assign(real._width,real._height,real._depth,real._spectrum,(T)0); ++ if (!real.is_sameXYZC(imag)) ++ throw CImgInstanceException("CImg<%s>::FFT(): Specified real part (%u,%u,%u,%u,%p) and " ++ "imaginary part (%u,%u,%u,%u,%p) have different dimensions.", ++ pixel_type(), ++ real._width,real._height,real._depth,real._spectrum,real._data, ++ imag._width,imag._height,imag._depth,imag._spectrum,imag._data); ++#ifdef cimg_use_fftw3 ++ cimg::mutex(12); ++ fftw_complex *data_in; ++ fftw_plan data_plan; ++ ++ switch (cimg::lowercase(axis)) { ++ case 'x' : { // Fourier along X, using FFTW library. ++ data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex)*real._width); ++ if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT(): Failed to allocate memory (%s) " ++ "for computing FFT of image (%u,%u,%u,%u) along the X-axis.", ++ pixel_type(), ++ cimg::strbuffersize(sizeof(fftw_complex)*real._width), ++ real._width,real._height,real._depth,real._spectrum); ++ ++ data_plan = fftw_plan_dft_1d(real._width,data_in,data_in,is_invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); ++ cimg_forYZC(real,y,z,c) { ++ T *ptrr = real.data(0,y,z,c), *ptri = imag.data(0,y,z,c); ++ double *ptrd = (double*)data_in; ++ cimg_forX(real,x) { *(ptrd++) = (double)*(ptrr++); *(ptrd++) = (double)*(ptri++); } ++ fftw_execute(data_plan); ++ const unsigned int fact = real._width; ++ if (is_invert) cimg_forX(real,x) { *(--ptri) = (T)(*(--ptrd)/fact); *(--ptrr) = (T)(*(--ptrd)/fact); } ++ else cimg_forX(real,x) { *(--ptri) = (T)*(--ptrd); *(--ptrr) = (T)*(--ptrd); } ++ } ++ } break; ++ case 'y' : { // Fourier along Y, using FFTW library. ++ data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * real._height); ++ if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT(): Failed to allocate memory (%s) " ++ "for computing FFT of image (%u,%u,%u,%u) along the Y-axis.", ++ pixel_type(), ++ cimg::strbuffersize(sizeof(fftw_complex)*real._height), ++ real._width,real._height,real._depth,real._spectrum); ++ ++ data_plan = fftw_plan_dft_1d(real._height,data_in,data_in,is_invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); ++ const unsigned int off = real._width; ++ cimg_forXZC(real,x,z,c) { ++ T *ptrr = real.data(x,0,z,c), *ptri = imag.data(x,0,z,c); ++ double *ptrd = (double*)data_in; ++ cimg_forY(real,y) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; } ++ fftw_execute(data_plan); ++ const unsigned int fact = real._height; ++ if (is_invert) ++ cimg_forY(real,y) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); } ++ else cimg_forY(real,y) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); } ++ } ++ } break; ++ case 'z' : { // Fourier along Z, using FFTW library. ++ data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * real._depth); ++ if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT(): Failed to allocate memory (%s) " ++ "for computing FFT of image (%u,%u,%u,%u) along the Z-axis.", ++ pixel_type(), ++ cimg::strbuffersize(sizeof(fftw_complex)*real._depth), ++ real._width,real._height,real._depth,real._spectrum); ++ ++ data_plan = fftw_plan_dft_1d(real._depth,data_in,data_in,is_invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); ++ const ulongT off = (ulongT)real._width*real._height; ++ cimg_forXYC(real,x,y,c) { ++ T *ptrr = real.data(x,y,0,c), *ptri = imag.data(x,y,0,c); ++ double *ptrd = (double*)data_in; ++ cimg_forZ(real,z) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; } ++ fftw_execute(data_plan); ++ const unsigned int fact = real._depth; ++ if (is_invert) ++ cimg_forZ(real,z) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); } ++ else cimg_forZ(real,z) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); } ++ } ++ } break; ++ default : ++ throw CImgArgumentException("CImgList<%s>::FFT(): Invalid specified axis '%c' for real and imaginary parts " ++ "(%u,%u,%u,%u) " ++ "(should be { x | y | z }).", ++ pixel_type(),axis, ++ real._width,real._height,real._depth,real._spectrum); ++ } ++ fftw_destroy_plan(data_plan); ++ fftw_free(data_in); ++ cimg::mutex(12,0); ++#else ++ switch (cimg::lowercase(axis)) { ++ case 'x' : { // Fourier along X, using built-in functions. ++ const unsigned int N = real._width, N2 = N>>1; ++ if (((N - 1)&N) && N!=1) ++ throw CImgInstanceException("CImgList<%s>::FFT(): Specified real and imaginary parts (%u,%u,%u,%u) " ++ "have non 2^N dimension along the X-axis.", ++ pixel_type(), ++ real._width,real._height,real._depth,real._spectrum); ++ ++ for (unsigned int i = 0, j = 0; ii) cimg_forYZC(real,y,z,c) { ++ cimg::swap(real(i,y,z,c),real(j,y,z,c)); ++ cimg::swap(imag(i,y,z,c),imag(j,y,z,c)); ++ 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(): Specified real and imaginary parts (%u,%u,%u,%u) " ++ "have non 2^N dimension along the Y-axis.", ++ pixel_type(), ++ real._width,real._height,real._depth,real._spectrum); ++ ++ for (unsigned int i = 0, j = 0; ii) cimg_forXZC(real,x,z,c) { ++ cimg::swap(real(x,i,z,c),real(x,j,z,c)); ++ cimg::swap(imag(x,i,z,c),imag(x,j,z,c)); ++ 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(): Specified real and imaginary parts (%u,%u,%u,%u) " ++ "have non 2^N dimension along the Z-axis.", ++ pixel_type(), ++ real._width,real._height,real._depth,real._spectrum); ++ ++ for (unsigned int i = 0, j = 0; ii) cimg_forXYC(real,x,y,c) { ++ cimg::swap(real(x,y,i,c),real(x,y,j,c)); ++ cimg::swap(imag(x,y,i,c),imag(x,y,j,c)); ++ 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 specified axis '%c' for real and imaginary parts " ++ "(%u,%u,%u,%u) " ++ "(should be { x | y | z }).", ++ pixel_type(),axis, ++ real._width,real._height,real._depth,real._spectrum); ++ } ++#endif ++ } ++ ++ //! Compute n-d Fast Fourier Transform. ++ /** ++ \param[in,out] real Real part of the pixel values. ++ \param[in,out] imag Imaginary part of the pixel values. ++ \param is_invert Tells if the forward (\c false) or inverse (\c true) FFT is computed. ++ \param nb_threads Number of parallel threads used for the computation. ++ Use \c 0 to set this to the number of available cpus. ++ **/ ++ static void FFT(CImg& real, CImg& imag, const bool is_invert=false, const unsigned int nb_threads=0) { ++ if (!real) ++ throw CImgInstanceException("CImgList<%s>::FFT(): Empty specified real part.", ++ pixel_type()); ++ ++ if (!imag) imag.assign(real._width,real._height,real._depth,real._spectrum,(T)0); ++ if (!real.is_sameXYZC(imag)) ++ throw CImgInstanceException("CImgList<%s>::FFT(): Specified real part (%u,%u,%u,%u,%p) and " ++ "imaginary part (%u,%u,%u,%u,%p) have different dimensions.", ++ pixel_type(), ++ real._width,real._height,real._depth,real._spectrum,real._data, ++ imag._width,imag._height,imag._depth,imag._spectrum,imag._data); ++ ++#ifdef cimg_use_fftw3 ++ cimg::mutex(12); ++#ifndef cimg_use_fftw3_singlethread ++ const unsigned int _nb_threads = nb_threads?nb_threads:cimg::nb_cpus(); ++ static int fftw_st = fftw_init_threads(); ++ cimg::unused(fftw_st); ++ fftw_plan_with_nthreads(_nb_threads); ++#else ++ cimg::unused(nb_threads); ++#endif ++ fftw_complex *data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex)*real._width*real._height*real._depth); ++ if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT(): Failed to allocate memory (%s) " ++ "for computing FFT of image (%u,%u,%u,%u).", ++ pixel_type(), ++ cimg::strbuffersize(sizeof(fftw_complex)*real._width* ++ real._height*real._depth*real._spectrum), ++ real._width,real._height,real._depth,real._spectrum); ++ ++ fftw_plan data_plan; ++ const ulongT w = (ulongT)real._width, wh = w*real._height, whd = wh*real._depth; ++ data_plan = fftw_plan_dft_3d(real._width,real._height,real._depth,data_in,data_in, ++ is_invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); ++ cimg_forC(real,c) { ++ T *ptrr = real.data(0,0,0,c), *ptri = imag.data(0,0,0,c); ++ double *ptrd = (double*)data_in; ++ for (unsigned int x = 0; x1) FFT(real,imag,'z',is_invert); ++ if (real._height>1) FFT(real,imag,'y',is_invert); ++ if (real._width>1) FFT(real,imag,'x',is_invert); ++#endif ++ } ++ ++ //@} ++ //------------------------------------- ++ // ++ //! \name 3d Objects Management ++ //@{ ++ //------------------------------------- ++ ++ //! Shift 3d object's vertices. ++ /** ++ \param tx X-coordinate of the 3d displacement vector. ++ \param ty Y-coordinate of the 3d displacement vector. ++ \param tz Z-coordinate of the 3d displacement vector. ++ **/ ++ CImg& shift_object3d(const float tx, const float ty=0, const float tz=0) { ++ if (_height!=3 || _depth>1 || _spectrum>1) ++ throw CImgInstanceException(_cimg_instance ++ "shift_object3d(): Instance is not a set of 3d vertices.", ++ cimg_instance); ++ ++ get_shared_row(0)+=tx; get_shared_row(1)+=ty; get_shared_row(2)+=tz; ++ return *this; ++ } ++ ++ //! Shift 3d object's vertices \newinstance. ++ CImg get_shift_object3d(const float tx, const float ty=0, const float tz=0) const { ++ return CImg(*this,false).shift_object3d(tx,ty,tz); ++ } ++ ++ //! Shift 3d object's vertices, so that it becomes centered. ++ /** ++ \note The object center is computed as its barycenter. ++ **/ ++ CImg& shift_object3d() { ++ if (_height!=3 || _depth>1 || _spectrum>1) ++ throw CImgInstanceException(_cimg_instance ++ "shift_object3d(): Instance is not a set of 3d vertices.", ++ cimg_instance); ++ ++ CImg xcoords = get_shared_row(0), ycoords = get_shared_row(1), zcoords = get_shared_row(2); ++ float ++ xm, xM = (float)xcoords.max_min(xm), ++ ym, yM = (float)ycoords.max_min(ym), ++ zm, zM = (float)zcoords.max_min(zm); ++ xcoords-=(xm + xM)/2; ycoords-=(ym + yM)/2; zcoords-=(zm + zM)/2; ++ return *this; ++ } ++ ++ //! Shift 3d object's vertices, so that it becomes centered \newinstance. ++ CImg get_shift_object3d() const { ++ return CImg(*this,false).shift_object3d(); ++ } ++ ++ //! Resize 3d object. ++ /** ++ \param sx Width of the 3d object's bounding box. ++ \param sy Height of the 3d object's bounding box. ++ \param sz Depth of the 3d object's bounding box. ++ **/ ++ CImg& resize_object3d(const float sx, const float sy=-100, const float sz=-100) { ++ if (_height!=3 || _depth>1 || _spectrum>1) ++ throw CImgInstanceException(_cimg_instance ++ "resize_object3d(): Instance is not a set of 3d vertices.", ++ cimg_instance); ++ ++ CImg xcoords = get_shared_row(0), ycoords = get_shared_row(1), zcoords = get_shared_row(2); ++ float ++ xm, xM = (float)xcoords.max_min(xm), ++ ym, yM = (float)ycoords.max_min(ym), ++ zm, zM = (float)zcoords.max_min(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; ++ } ++ ++ //! Resize 3d object \newinstance. ++ 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 3d object to unit size. ++ CImg resize_object3d() { ++ if (_height!=3 || _depth>1 || _spectrum>1) ++ throw CImgInstanceException(_cimg_instance ++ "resize_object3d(): Instance is not a set of 3d vertices.", ++ cimg_instance); ++ ++ CImg xcoords = get_shared_row(0), ycoords = get_shared_row(1), zcoords = get_shared_row(2); ++ float ++ xm, xM = (float)xcoords.max_min(xm), ++ ym, yM = (float)ycoords.max_min(ym), ++ zm, zM = (float)zcoords.max_min(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; ++ } ++ ++ //! Resize 3d object to unit size \newinstance. ++ CImg get_resize_object3d() const { ++ return CImg(*this,false).resize_object3d(); ++ } ++ ++ //! Merge two 3d objects together. ++ /** ++ \param[in,out] primitives Primitives data of the current 3d object. ++ \param obj_vertices Vertices data of the additional 3d object. ++ \param obj_primitives Primitives data of the additional 3d object. ++ **/ ++ template ++ CImg& append_object3d(CImgList& primitives, const CImg& obj_vertices, ++ const CImgList& obj_primitives) { ++ if (!obj_vertices || !obj_primitives) return *this; ++ if (obj_vertices._height!=3 || obj_vertices._depth>1 || obj_vertices._spectrum>1) ++ throw CImgInstanceException(_cimg_instance ++ "append_object3d(): Specified vertice image (%u,%u,%u,%u,%p) is not a " ++ "set of 3d vertices.", ++ cimg_instance, ++ obj_vertices._width,obj_vertices._height, ++ obj_vertices._depth,obj_vertices._spectrum,obj_vertices._data); ++ ++ if (is_empty()) { primitives.assign(obj_primitives); return assign(obj_vertices); } ++ if (_height!=3 || _depth>1 || _spectrum>1) ++ throw CImgInstanceException(_cimg_instance ++ "append_object3d(): Instance is not a set of 3d vertices.", ++ cimg_instance); ++ ++ const unsigned int P = _width; ++ append(obj_vertices,'x'); ++ const unsigned int N = primitives._width; ++ primitives.insert(obj_primitives); ++ for (unsigned int i = N; i &p = primitives[i]; ++ switch (p.size()) { ++ case 1 : p[0]+=P; break; // Point. ++ case 5 : p[0]+=P; p[1]+=P; break; // Sphere. ++ case 2 : case 6 : p[0]+=P; p[1]+=P; break; // Segment. ++ case 3 : case 9 : p[0]+=P; p[1]+=P; p[2]+=P; break; // Triangle. ++ case 4 : case 12 : p[0]+=P; p[1]+=P; p[2]+=P; p[3]+=P; break; // Rectangle. ++ } ++ } ++ return *this; ++ } ++ ++ //! Texturize primitives of a 3d object. ++ /** ++ \param[in,out] primitives Primitives data of the 3d object. ++ \param[in,out] colors Colors data of the 3d object. ++ \param texture Texture image to map to 3d object. ++ \param coords Texture-mapping coordinates. ++ **/ ++ template ++ const CImg& texturize_object3d(CImgList& primitives, CImgList& colors, ++ const CImg& texture, const CImg& coords=CImg::const_empty()) const { ++ if (is_empty()) return *this; ++ if (_height!=3) ++ throw CImgInstanceException(_cimg_instance ++ "texturize_object3d(): image instance is not a set of 3d points.", ++ cimg_instance); ++ if (coords && (coords._width!=_width || coords._height!=2)) ++ throw CImgArgumentException(_cimg_instance ++ "texturize_object3d(): Invalid specified texture coordinates (%u,%u,%u,%u,%p).", ++ cimg_instance, ++ coords._width,coords._height,coords._depth,coords._spectrum,coords._data); ++ CImg _coords; ++ if (!coords) { // If no texture coordinates specified, do a default XY-projection. ++ _coords.assign(_width,2); ++ float ++ xmin, xmax = (float)get_shared_row(0).max_min(xmin), ++ ymin, ymax = (float)get_shared_row(1).max_min(ymin), ++ dx = xmax>xmin?xmax-xmin:1, ++ dy = ymax>ymin?ymax-ymin:1; ++ cimg_forX(*this,p) { ++ _coords(p,0) = (int)(((*this)(p,0) - xmin)*texture._width/dx); ++ _coords(p,1) = (int)(((*this)(p,1) - ymin)*texture._height/dy); ++ } ++ } else _coords = coords; ++ ++ int texture_ind = -1; ++ cimglist_for(primitives,l) { ++ CImg &p = primitives[l]; ++ const unsigned int siz = p.size(); ++ switch (siz) { ++ case 1 : { // Point. ++ const unsigned int i0 = (unsigned int)p[0]; ++ const int x0 = _coords(i0,0), y0 = _coords(i0,1); ++ texture.get_vector_at(x0<=0?0:x0>=texture.width()?texture.width() - 1:x0, ++ y0<=0?0:y0>=texture.height()?texture.height() - 1:y0).move_to(colors[l]); ++ } break; ++ case 2 : case 6 : { // Line. ++ const unsigned int i0 = (unsigned int)p[0], i1 = (unsigned int)p[1]; ++ const int ++ x0 = _coords(i0,0), y0 = _coords(i0,1), ++ x1 = _coords(i1,0), y1 = _coords(i1,1); ++ if (texture_ind<0) colors[texture_ind=l].assign(texture,false); ++ else colors[l].assign(colors[texture_ind],true); ++ CImg::vector(i0,i1,x0,y0,x1,y1).move_to(p); ++ } break; ++ case 3 : case 9 : { // Triangle. ++ const unsigned int i0 = (unsigned int)p[0], i1 = (unsigned int)p[1], i2 = (unsigned int)p[2]; ++ const int ++ x0 = _coords(i0,0), y0 = _coords(i0,1), ++ x1 = _coords(i1,0), y1 = _coords(i1,1), ++ x2 = _coords(i2,0), y2 = _coords(i2,1); ++ if (texture_ind<0) colors[texture_ind=l].assign(texture,false); ++ else colors[l].assign(colors[texture_ind],true); ++ CImg::vector(i0,i1,i2,x0,y0,x1,y1,x2,y2).move_to(p); ++ } break; ++ case 4 : case 12 : { // Quadrangle. ++ const unsigned int ++ i0 = (unsigned int)p[0], i1 = (unsigned int)p[1], i2 = (unsigned int)p[2], i3 = (unsigned int)p[3]; ++ const int ++ x0 = _coords(i0,0), y0 = _coords(i0,1), ++ x1 = _coords(i1,0), y1 = _coords(i1,1), ++ x2 = _coords(i2,0), y2 = _coords(i2,1), ++ x3 = _coords(i3,0), y3 = _coords(i3,1); ++ if (texture_ind<0) colors[texture_ind=l].assign(texture,false); ++ else colors[l].assign(colors[texture_ind],true); ++ CImg::vector(i0,i1,i2,i3,x0,y0,x1,y1,x2,y2,x3,y3).move_to(p); ++ } break; ++ } ++ } ++ return *this; ++ } ++ ++ //! Generate a 3d elevation of the image instance. ++ /** ++ \param[out] primitives The returned list of the 3d object primitives ++ (template type \e tf should be at least \e unsigned \e int). ++ \param[out] colors The returned list of the 3d object colors. ++ \param elevation The input elevation map. ++ \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg image (0<=i<=N - 1). ++ \par Example ++ \code ++ const CImg img("reference.jpg"); ++ CImgList faces3d; ++ CImgList colors3d; ++ const CImg points3d = img.get_elevation3d(faces3d,colors3d,img.get_norm()*0.2); ++ CImg().display_object3d("Elevation3d",points3d,faces3d,colors3d); ++ \endcode ++ \image html ref_elevation3d.jpg ++ **/ ++ template ++ CImg get_elevation3d(CImgList& primitives, CImgList& colors, const CImg& elevation) const { ++ if (!is_sameXY(elevation) || elevation._depth>1 || elevation._spectrum>1) ++ throw CImgArgumentException(_cimg_instance ++ "get_elevation3d(): Instance and specified elevation (%u,%u,%u,%u,%p) " ++ "have incompatible dimensions.", ++ cimg_instance, ++ elevation._width,elevation._height,elevation._depth, ++ elevation._spectrum,elevation._data); ++ if (is_empty()) return *this; ++ float m, M = (float)max_min(m); ++ if (M==m) ++M; ++ colors.assign(); ++ const unsigned int size_x1 = _width - 1, size_y1 = _height - 1; ++ for (unsigned int y = 0; y1?((*this)(x,y,1) - m)*255/(M-m):r), ++ b = (unsigned char)(_spectrum>2?((*this)(x,y,2) - m)*255/(M-m):_spectrum>1?0:r); ++ CImg::vector((tc)r,(tc)g,(tc)b).move_to(colors); ++ } ++ const typename CImg::_functor2d_int func(elevation); ++ return elevation3d(primitives,func,0,0,_width - 1.0f,_height - 1.0f,_width,_height); ++ } ++ ++ //! Generate the 3d projection planes of the image instance. ++ /** ++ \param[out] primitives Primitives data of the returned 3d object. ++ \param[out] colors Colors data of the returned 3d object. ++ \param x0 X-coordinate of the projection point. ++ \param y0 Y-coordinate of the projection point. ++ \param z0 Z-coordinate of the projection point. ++ \param normalize_colors Tells if the created textures have normalized colors. ++ **/ ++ template ++ CImg get_projections3d(CImgList& primitives, CImgList& colors, ++ const unsigned int x0, const unsigned int y0, const unsigned int z0, ++ const bool normalize_colors=false) const { ++ float m = 0, M = 0, delta = 1; ++ if (normalize_colors) { m = (float)min_max(M); delta = 255/(m==M?1:M-m); } ++ const unsigned int ++ _x0 = (x0>=_width)?_width - 1:x0, ++ _y0 = (y0>=_height)?_height - 1:y0, ++ _z0 = (z0>=_depth)?_depth - 1:z0; ++ CImg img_xy, img_xz, img_yz; ++ if (normalize_colors) { ++ ((get_crop(0,0,_z0,0,_width - 1,_height - 1,_z0,_spectrum - 1)-=m)*=delta).move_to(img_xy); ++ ((get_crop(0,_y0,0,0,_width - 1,_y0,_depth - 1,_spectrum - 1)-=m)*=delta).resize(_width,_depth,1,-100,-1). ++ move_to(img_xz); ++ ((get_crop(_x0,0,0,0,_x0,_height - 1,_depth - 1,_spectrum - 1)-=m)*=delta).resize(_height,_depth,1,-100,-1). ++ move_to(img_yz); ++ } else { ++ get_crop(0,0,_z0,0,_width - 1,_height - 1,_z0,_spectrum - 1).move_to(img_xy); ++ get_crop(0,_y0,0,0,_width - 1,_y0,_depth - 1,_spectrum - 1).resize(_width,_depth,1,-100,-1).move_to(img_xz); ++ get_crop(_x0,0,0,0,_x0,_height - 1,_depth - 1,_spectrum - 1).resize(_height,_depth,1,-100,-1).move_to(img_yz); ++ } ++ CImg points(12,3,1,1, ++ 0,_width - 1,_width - 1,0, 0,_width - 1,_width - 1,0, _x0,_x0,_x0,_x0, ++ 0,0,_height - 1,_height - 1, _y0,_y0,_y0,_y0, 0,_height - 1,_height - 1,0, ++ _z0,_z0,_z0,_z0, 0,0,_depth - 1,_depth - 1, 0,0,_depth - 1,_depth - 1); ++ primitives.assign(); ++ CImg::vector(0,1,2,3,0,0,img_xy._width - 1,0,img_xy._width - 1,img_xy._height - 1,0,img_xy._height - 1). ++ move_to(primitives); ++ CImg::vector(4,5,6,7,0,0,img_xz._width - 1,0,img_xz._width - 1,img_xz._height - 1,0,img_xz._height - 1). ++ move_to(primitives); ++ CImg::vector(8,9,10,11,0,0,img_yz._width - 1,0,img_yz._width - 1,img_yz._height - 1,0,img_yz._height - 1). ++ move_to(primitives); ++ colors.assign(); ++ img_xy.move_to(colors); ++ img_xz.move_to(colors); ++ img_yz.move_to(colors); ++ return points; ++ } ++ ++ //! Generate a isoline of the image instance as a 3d object. ++ /** ++ \param[out] primitives The returned list of the 3d object primitives ++ (template type \e tf should be at least \e unsigned \e int). ++ \param isovalue The returned list of the 3d object colors. ++ \param size_x The number of subdivisions along the X-axis. ++ \param size_y The number of subdisivions along the Y-axis. ++ \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg image (0<=i<=N - 1). ++ \par Example ++ \code ++ const CImg img("reference.jpg"); ++ CImgList faces3d; ++ const CImg points3d = img.get_isoline3d(faces3d,100); ++ CImg().display_object3d("Isoline3d",points3d,faces3d,colors3d); ++ \endcode ++ \image html ref_isoline3d.jpg ++ **/ ++ template ++ CImg get_isoline3d(CImgList& primitives, const float isovalue, ++ const int size_x=-100, const int size_y=-100) const { ++ if (_spectrum>1) ++ throw CImgInstanceException(_cimg_instance ++ "get_isoline3d(): Instance is not a scalar image.", ++ cimg_instance); ++ if (_depth>1) ++ throw CImgInstanceException(_cimg_instance ++ "get_isoline3d(): Instance is not a 2d image.", ++ cimg_instance); ++ primitives.assign(); ++ if (is_empty()) return *this; ++ CImg vertices; ++ if ((size_x==-100 && size_y==-100) || (size_x==width() && size_y==height())) { ++ const _functor2d_int func(*this); ++ vertices = isoline3d(primitives,func,isovalue,0,0,width() - 1.0f,height() - 1.0f,width(),height()); ++ } else { ++ const _functor2d_float func(*this); ++ vertices = isoline3d(primitives,func,isovalue,0,0,width() - 1.0f,height() - 1.0f,size_x,size_y); ++ } ++ return vertices; ++ } ++ ++ //! Generate an isosurface of the image instance as a 3d object. ++ /** ++ \param[out] primitives The returned list of the 3d object primitives ++ (template type \e tf should be at least \e unsigned \e int). ++ \param isovalue The returned list of the 3d object colors. ++ \param size_x Number of subdivisions along the X-axis. ++ \param size_y Number of subdisivions along the Y-axis. ++ \param size_z Number of subdisivions along the Z-axis. ++ \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg image (0<=i<=N - 1). ++ \par Example ++ \code ++ const CImg img = CImg("reference.jpg").resize(-100,-100,20); ++ CImgList faces3d; ++ const CImg points3d = img.get_isosurface3d(faces3d,100); ++ CImg().display_object3d("Isosurface3d",points3d,faces3d,colors3d); ++ \endcode ++ \image html ref_isosurface3d.jpg ++ **/ ++ template ++ CImg get_isosurface3d(CImgList& primitives, const float isovalue, ++ const int size_x=-100, const int size_y=-100, const int size_z=-100) const { ++ if (_spectrum>1) ++ throw CImgInstanceException(_cimg_instance ++ "get_isosurface3d(): Instance is not a scalar image.", ++ cimg_instance); ++ primitives.assign(); ++ if (is_empty()) return *this; ++ CImg vertices; ++ if ((size_x==-100 && size_y==-100 && size_z==-100) || (size_x==width() && size_y==height() && size_z==depth())) { ++ const _functor3d_int func(*this); ++ vertices = isosurface3d(primitives,func,isovalue,0,0,0,width() - 1.0f,height() - 1.0f,depth() - 1.0f, ++ width(),height(),depth()); ++ } else { ++ const _functor3d_float func(*this); ++ vertices = isosurface3d(primitives,func,isovalue,0,0,0,width() - 1.0f,height() - 1.0f,depth() - 1.0f, ++ size_x,size_y,size_z); ++ } ++ return vertices; ++ } ++ ++ //! Compute 3d elevation of a function as a 3d object. ++ /** ++ \param[out] primitives Primitives data of the resulting 3d object. ++ \param func Elevation function. Is of type float (*func)(const float x,const float y). ++ \param x0 X-coordinate of the starting point. ++ \param y0 Y-coordinate of the starting point. ++ \param x1 X-coordinate of the ending point. ++ \param y1 Y-coordinate of the ending point. ++ \param size_x Resolution of the function along the X-axis. ++ \param size_y Resolution of the function along the Y-axis. ++ **/ ++ template ++ static CImg elevation3d(CImgList& primitives, const tfunc& func, ++ const float x0, const float y0, const float x1, const float y1, ++ const int size_x=256, const int size_y=256) { ++ const float ++ nx0 = x0=0?size_x:(nx1-nx0)*-size_x/100), ++ nsize_x = _nsize_x?_nsize_x:1, nsize_x1 = nsize_x - 1, ++ _nsize_y = (unsigned int)(size_y>=0?size_y:(ny1-ny0)*-size_y/100), ++ nsize_y = _nsize_y?_nsize_y:1, nsize_y1 = nsize_y - 1; ++ if (nsize_x<2 || nsize_y<2) ++ throw CImgArgumentException("CImg<%s>::elevation3d(): Invalid specified size (%d,%d).", ++ pixel_type(), ++ nsize_x,nsize_y); ++ ++ CImg vertices(nsize_x*nsize_y,3); ++ floatT *ptr_x = vertices.data(0,0), *ptr_y = vertices.data(0,1), *ptr_z = vertices.data(0,2); ++ for (unsigned int y = 0; y ++ static CImg elevation3d(CImgList& primitives, const char *const expression, ++ const float x0, const float y0, const float x1, const float y1, ++ const int size_x=256, const int size_y=256) { ++ const _functor2d_expr func(expression); ++ return elevation3d(primitives,func,x0,y0,x1,y1,size_x,size_y); ++ } ++ ++ //! Compute 0-isolines of a function, as a 3d object. ++ /** ++ \param[out] primitives Primitives data of the resulting 3d object. ++ \param func Elevation function. Is of type float (*func)(const float x,const float y). ++ \param isovalue Isovalue to extract from function. ++ \param x0 X-coordinate of the starting point. ++ \param y0 Y-coordinate of the starting point. ++ \param x1 X-coordinate of the ending point. ++ \param y1 Y-coordinate of the ending point. ++ \param size_x Resolution of the function along the X-axis. ++ \param size_y Resolution of the function along the Y-axis. ++ \note Use the marching squares algorithm for extracting the isolines. ++ **/ ++ template ++ static CImg isoline3d(CImgList& primitives, const tfunc& func, const float isovalue, ++ const float x0, const float y0, const float x1, const float y1, ++ const int size_x=256, const int size_y=256) { ++ static const unsigned int edges[16] = { 0x0, 0x9, 0x3, 0xa, 0x6, 0xf, 0x5, 0xc, 0xc, ++ 0x5, 0xf, 0x6, 0xa, 0x3, 0x9, 0x0 }; ++ static const 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)(size_x>=0?size_x:cimg::round((x1-x0)*-size_x/100 + 1)), ++ _ny = (unsigned int)(size_y>=0?size_y:cimg::round((y1-y0)*-size_y/100 + 1)), ++ nx = _nx?_nx:1, ++ ny = _ny?_ny:1, ++ nxm1 = nx - 1, ++ nym1 = ny - 1; ++ primitives.assign(); ++ if (!nxm1 || !nym1) return CImg(); ++ const float dx = (x1 - x0)/nxm1, dy = (y1 - y0)/nym1; ++ CImgList vertices; ++ CImg indices1(nx,1,1,2,-1), indices2(nx,1,1,2); ++ CImg values1(nx), values2(nx); ++ float X = x0, Y = y0, nX = X + dx, nY = Y + dy; ++ ++ // Fill first line with values ++ cimg_forX(values1,x) { values1(x) = (float)func(X,Y); X+=dx; } ++ ++ // Run the marching squares algorithm ++ for (unsigned int yi = 0, nyi = 1; yi::vector(Xi,Y,0).move_to(vertices); ++ } ++ if ((edge&2) && indices1(nxi,1)<0) { ++ const float Yi = Y + (isovalue-val1)*dy/(val2-val1); ++ indices1(nxi,1) = vertices.width(); ++ CImg::vector(nX,Yi,0).move_to(vertices); ++ } ++ if ((edge&4) && indices2(xi,0)<0) { ++ const float Xi = X + (isovalue-val3)*dx/(val2-val3); ++ indices2(xi,0) = vertices.width(); ++ CImg::vector(Xi,nY,0).move_to(vertices); ++ } ++ if ((edge&8) && indices1(xi,1)<0) { ++ const float Yi = Y + (isovalue-val0)*dy/(val3-val0); ++ indices1(xi,1) = vertices.width(); ++ CImg::vector(X,Yi,0).move_to(vertices); ++ } ++ ++ // Create segments ++ for (const int *segment = segments[configuration]; *segment!=-1; ) { ++ const unsigned int p0 = (unsigned int)*(segment++), p1 = (unsigned int)*(segment++); ++ const tf ++ i0 = (tf)(_isoline3d_indice(p0,indices1,indices2,xi,nxi)), ++ i1 = (tf)(_isoline3d_indice(p1,indices1,indices2,xi,nxi)); ++ CImg::vector(i0,i1).move_to(primitives); ++ } ++ } ++ } ++ values1.swap(values2); ++ indices1.swap(indices2); ++ } ++ return vertices>'x'; ++ } ++ ++ //! Compute isolines of a function, as a 3d object \overloading. ++ template ++ static CImg isoline3d(CImgList& primitives, const char *const expression, const float isovalue, ++ const float x0, const float y0, const float x1, const float y1, ++ const int size_x=256, const int size_y=256) { ++ const _functor2d_expr func(expression); ++ return isoline3d(primitives,func,isovalue,x0,y0,x1,y1,size_x,size_y); ++ } ++ ++ template ++ static int _isoline3d_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; ++ } ++ ++ //! Compute isosurface of a function, as a 3d object. ++ /** ++ \param[out] primitives Primitives data of the resulting 3d object. ++ \param func Implicit function. Is of type float (*func)(const float x, const float y, const float z). ++ \param isovalue Isovalue to extract. ++ \param x0 X-coordinate of the starting point. ++ \param y0 Y-coordinate of the starting point. ++ \param z0 Z-coordinate of the starting point. ++ \param x1 X-coordinate of the ending point. ++ \param y1 Y-coordinate of the ending point. ++ \param z1 Z-coordinate of the ending point. ++ \param size_x Resolution of the elevation function along the X-axis. ++ \param size_y Resolution of the elevation function along the Y-axis. ++ \param size_z Resolution of the elevation function along the Z-axis. ++ \note Use the marching cubes algorithm for extracting the isosurface. ++ **/ ++ template ++ static CImg isosurface3d(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 int size_x=32, const int size_y=32, const int size_z=32) { ++ static const 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 const 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)(size_x>=0?size_x:cimg::round((x1-x0)*-size_x/100 + 1)), ++ _ny = (unsigned int)(size_y>=0?size_y:cimg::round((y1-y0)*-size_y/100 + 1)), ++ _nz = (unsigned int)(size_z>=0?size_z:cimg::round((z1-z0)*-size_z/100 + 1)), ++ nx = _nx?_nx:1, ++ ny = _ny?_ny:1, ++ nz = _nz?_nz:1, ++ nxm1 = nx - 1, ++ nym1 = ny - 1, ++ nzm1 = nz - 1; ++ primitives.assign(); ++ if (!nxm1 || !nym1 || !nzm1) return CImg(); ++ const float dx = (x1 - x0)/nxm1, dy = (y1 - y0)/nym1, dz = (z1 - z0)/nzm1; ++ CImgList vertices; ++ 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+=dx; } ++ Y+=dy; ++ } ++ ++ // Run Marching Cubes algorithm ++ Z = z0; nZ = Z + dz; ++ for (unsigned int zi = 0; zi::vector(Xi,Y,Z).move_to(vertices); ++ } ++ if ((edge&2) && indices1(nxi,yi,1)<0) { ++ const float Yi = Y + (isovalue-val1)*dy/(val2-val1); ++ indices1(nxi,yi,1) = vertices.width(); ++ CImg::vector(nX,Yi,Z).move_to(vertices); ++ } ++ if ((edge&4) && indices1(xi,nyi,0)<0) { ++ const float Xi = X + (isovalue-val3)*dx/(val2-val3); ++ indices1(xi,nyi,0) = vertices.width(); ++ CImg::vector(Xi,nY,Z).move_to(vertices); ++ } ++ if ((edge&8) && indices1(xi,yi,1)<0) { ++ const float Yi = Y + (isovalue-val0)*dy/(val3-val0); ++ indices1(xi,yi,1) = vertices.width(); ++ CImg::vector(X,Yi,Z).move_to(vertices); ++ } ++ if ((edge&16) && indices2(xi,yi,0)<0) { ++ const float Xi = X + (isovalue-val4)*dx/(val5-val4); ++ indices2(xi,yi,0) = vertices.width(); ++ CImg::vector(Xi,Y,nZ).move_to(vertices); ++ } ++ if ((edge&32) && indices2(nxi,yi,1)<0) { ++ const float Yi = Y + (isovalue-val5)*dy/(val6-val5); ++ indices2(nxi,yi,1) = vertices.width(); ++ CImg::vector(nX,Yi,nZ).move_to(vertices); ++ } ++ if ((edge&64) && indices2(xi,nyi,0)<0) { ++ const float Xi = X + (isovalue-val7)*dx/(val6-val7); ++ indices2(xi,nyi,0) = vertices.width(); ++ CImg::vector(Xi,nY,nZ).move_to(vertices); ++ } ++ if ((edge&128) && indices2(xi,yi,1)<0) { ++ const float Yi = Y + (isovalue-val4)*dy/(val7-val4); ++ indices2(xi,yi,1) = vertices.width(); ++ CImg::vector(X,Yi,nZ).move_to(vertices); ++ } ++ if ((edge&256) && indices1(xi,yi,2)<0) { ++ const float Zi = Z+ (isovalue-val0)*dz/(val4-val0); ++ indices1(xi,yi,2) = vertices.width(); ++ CImg::vector(X,Y,Zi).move_to(vertices); ++ } ++ if ((edge&512) && indices1(nxi,yi,2)<0) { ++ const float Zi = Z + (isovalue-val1)*dz/(val5-val1); ++ indices1(nxi,yi,2) = vertices.width(); ++ CImg::vector(nX,Y,Zi).move_to(vertices); ++ } ++ if ((edge&1024) && indices1(nxi,nyi,2)<0) { ++ const float Zi = Z + (isovalue-val2)*dz/(val6-val2); ++ indices1(nxi,nyi,2) = vertices.width(); ++ CImg::vector(nX,nY,Zi).move_to(vertices); ++ } ++ if ((edge&2048) && indices1(xi,nyi,2)<0) { ++ const float Zi = Z + (isovalue-val3)*dz/(val7-val3); ++ indices1(xi,nyi,2) = vertices.width(); ++ CImg::vector(X,nY,Zi).move_to(vertices); ++ } ++ ++ // Create triangles ++ for (const int *triangle = triangles[configuration]; *triangle!=-1; ) { ++ const unsigned int ++ p0 = (unsigned int)*(triangle++), ++ p1 = (unsigned int)*(triangle++), ++ p2 = (unsigned int)*(triangle++); ++ const tf ++ i0 = (tf)(_isosurface3d_indice(p0,indices1,indices2,xi,yi,nxi,nyi)), ++ i1 = (tf)(_isosurface3d_indice(p1,indices1,indices2,xi,yi,nxi,nyi)), ++ i2 = (tf)(_isosurface3d_indice(p2,indices1,indices2,xi,yi,nxi,nyi)); ++ CImg::vector(i0,i2,i1).move_to(primitives); ++ } ++ } ++ } ++ } ++ cimg::swap(values1,values2); ++ cimg::swap(indices1,indices2); ++ } ++ return vertices>'x'; ++ } ++ ++ //! Compute isosurface of a function, as a 3d object \overloading. ++ template ++ static CImg isosurface3d(CImgList& primitives, const char *const expression, const float isovalue, ++ const float x0, const float y0, const float z0, ++ const float x1, const float y1, const float z1, ++ const int dx=32, const int dy=32, const int dz=32) { ++ const _functor3d_expr func(expression); ++ return isosurface3d(primitives,func,isovalue,x0,y0,z0,x1,y1,z1,dx,dy,dz); ++ } ++ ++ template ++ static int _isosurface3d_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; ++ } ++ ++ // Define functors for accessing image values (used in previous functions). ++ struct _functor2d_int { ++ const CImg& ref; ++ _functor2d_int(const CImg& pref):ref(pref) {} ++ float operator()(const float x, const float y) const { ++ return (float)ref((int)x,(int)y); ++ } ++ }; ++ ++ struct _functor2d_float { ++ const CImg& ref; ++ _functor2d_float(const CImg& pref):ref(pref) {} ++ float operator()(const float x, const float y) const { ++ return (float)ref._linear_atXY(x,y); ++ } ++ }; ++ ++ struct _functor2d_expr { ++ _cimg_math_parser *mp; ++ ~_functor2d_expr() { mp->end(); delete mp; } ++ _functor2d_expr(const char *const expr):mp(0) { ++ mp = new _cimg_math_parser(expr,0,CImg::const_empty(),0); ++ } ++ float operator()(const float x, const float y) const { ++ return (float)(*mp)(x,y,0,0); ++ } ++ }; ++ ++ struct _functor3d_int { ++ const CImg& ref; ++ _functor3d_int(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 _functor3d_float { ++ const CImg& ref; ++ _functor3d_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); ++ } ++ }; ++ ++ struct _functor3d_expr { ++ _cimg_math_parser *mp; ++ ~_functor3d_expr() { mp->end(); delete mp; } ++ _functor3d_expr(const char *const expr):mp(0) { ++ mp = new _cimg_math_parser(expr,0,CImg::const_empty(),0); ++ } ++ float operator()(const float x, const float y, const float z) const { ++ return (float)(*mp)(x,y,z,0); ++ } ++ }; ++ ++ struct _functor4d_int { ++ const CImg& ref; ++ _functor4d_int(const CImg& pref):ref(pref) {} ++ float operator()(const float x, const float y, const float z, const unsigned int c) const { ++ return (float)ref((int)x,(int)y,(int)z,c); ++ } ++ }; ++ ++ //! Generate a 3d box object. ++ /** ++ \param[out] primitives The returned list of the 3d object primitives ++ (template type \e tf should be at least \e unsigned \e int). ++ \param size_x The width of the box (dimension along the X-axis). ++ \param size_y The height of the box (dimension along the Y-axis). ++ \param size_z The depth of the box (dimension along the Z-axis). ++ \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg image (0<=i<=N - 1). ++ \par Example ++ \code ++ CImgList faces3d; ++ const CImg points3d = CImg::box3d(faces3d,10,20,30); ++ CImg().display_object3d("Box3d",points3d,faces3d); ++ \endcode ++ \image html ref_box3d.jpg ++ **/ ++ template ++ static CImg box3d(CImgList& primitives, ++ const float size_x=200, const float size_y=100, const float size_z=100) { ++ 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, ++ 0.,size_x,size_x, 0., 0.,size_x,size_x, 0., ++ 0., 0.,size_y,size_y, 0., 0.,size_y,size_y, ++ 0., 0., 0., 0.,size_z,size_z,size_z,size_z); ++ } ++ ++ //! Generate a 3d cone. ++ /** ++ \param[out] primitives The returned list of the 3d object primitives ++ (template type \e tf should be at least \e unsigned \e int). ++ \param radius The radius of the cone basis. ++ \param size_z The cone's height. ++ \param subdivisions The number of basis angular subdivisions. ++ \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg image (0<=i<=N - 1). ++ \par Example ++ \code ++ CImgList faces3d; ++ const CImg points3d = CImg::cone3d(faces3d,50); ++ CImg().display_object3d("Cone3d",points3d,faces3d); ++ \endcode ++ \image html ref_cone3d.jpg ++ **/ ++ template ++ static CImg cone3d(CImgList& primitives, ++ const float radius=50, const float size_z=100, const unsigned int subdivisions=24) { ++ primitives.assign(); ++ if (!subdivisions) return CImg(); ++ CImgList vertices(2,1,3,1,1, ++ 0.,0.,size_z, ++ 0.,0.,0.); ++ for (float delta = 360.0f/subdivisions, angle = 0; angle<360; angle+=delta) { ++ const float a = (float)(angle*cimg::PI/180); ++ CImg::vector((float)(radius*std::cos(a)),(float)(radius*std::sin(a)),0).move_to(vertices); ++ } ++ const unsigned int nbr = vertices._width - 2; ++ for (unsigned int p = 0; p::vector(1,next,curr).move_to(primitives); ++ CImg::vector(0,curr,next).move_to(primitives); ++ } ++ return vertices>'x'; ++ } ++ ++ //! Generate a 3d cylinder. ++ /** ++ \param[out] primitives The returned list of the 3d object primitives ++ (template type \e tf should be at least \e unsigned \e int). ++ \param radius The radius of the cylinder basis. ++ \param size_z The cylinder's height. ++ \param subdivisions The number of basis angular subdivisions. ++ \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg image (0<=i<=N - 1). ++ \par Example ++ \code ++ CImgList faces3d; ++ const CImg points3d = CImg::cylinder3d(faces3d,50); ++ CImg().display_object3d("Cylinder3d",points3d,faces3d); ++ \endcode ++ \image html ref_cylinder3d.jpg ++ **/ ++ template ++ static CImg cylinder3d(CImgList& primitives, ++ const float radius=50, const float size_z=100, const unsigned int subdivisions=24) { ++ primitives.assign(); ++ if (!subdivisions) return CImg(); ++ CImgList vertices(2,1,3,1,1, ++ 0.,0.,0., ++ 0.,0.,size_z); ++ for (float delta = 360.0f/subdivisions, angle = 0; angle<360; angle+=delta) { ++ const float a = (float)(angle*cimg::PI/180); ++ CImg::vector((float)(radius*std::cos(a)),(float)(radius*std::sin(a)),0.0f).move_to(vertices); ++ CImg::vector((float)(radius*std::cos(a)),(float)(radius*std::sin(a)),size_z).move_to(vertices); ++ } ++ const unsigned int nbr = (vertices._width - 2)/2; ++ for (unsigned int p = 0; p::vector(0,next,curr).move_to(primitives); ++ CImg::vector(1,curr + 1,next + 1).move_to(primitives); ++ CImg::vector(curr,next,next + 1,curr + 1).move_to(primitives); ++ } ++ return vertices>'x'; ++ } ++ ++ //! Generate a 3d torus. ++ /** ++ \param[out] primitives The returned list of the 3d object primitives ++ (template type \e tf should be at least \e unsigned \e int). ++ \param radius1 The large radius. ++ \param radius2 The small radius. ++ \param subdivisions1 The number of angular subdivisions for the large radius. ++ \param subdivisions2 The number of angular subdivisions for the small radius. ++ \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg image (0<=i<=N - 1). ++ \par Example ++ \code ++ CImgList faces3d; ++ const CImg points3d = CImg::torus3d(faces3d,20,4); ++ CImg().display_object3d("Torus3d",points3d,faces3d); ++ \endcode ++ \image html ref_torus3d.jpg ++ **/ ++ 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 vertices; ++ for (unsigned int v = 0; v::vector(x,y,z).move_to(vertices); ++ } ++ } ++ for (unsigned int vv = 0; vv::vector(svv + nu,svv + uu,snv + uu,snv + nu).move_to(primitives); ++ } ++ } ++ return vertices>'x'; ++ } ++ ++ //! Generate a 3d XY-plane. ++ /** ++ \param[out] primitives The returned list of the 3d object primitives ++ (template type \e tf should be at least \e unsigned \e int). ++ \param size_x The width of the plane (dimension along the X-axis). ++ \param size_y The height of the plane (dimensions along the Y-axis). ++ \param subdivisions_x The number of planar subdivisions along the X-axis. ++ \param subdivisions_y The number of planar subdivisions along the Y-axis. ++ \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg image (0<=i<=N - 1). ++ \par Example ++ \code ++ CImgList faces3d; ++ const CImg points3d = CImg::plane3d(faces3d,100,50); ++ CImg().display_object3d("Plane3d",points3d,faces3d); ++ \endcode ++ \image html ref_plane3d.jpg ++ **/ ++ template ++ static CImg plane3d(CImgList& primitives, ++ const float size_x=100, const float size_y=100, ++ const unsigned int subdivisions_x=10, const unsigned int subdivisions_y=10) { ++ primitives.assign(); ++ if (!subdivisions_x || !subdivisions_y) return CImg(); ++ CImgList vertices; ++ const unsigned int w = subdivisions_x + 1, h = subdivisions_y + 1; ++ const float fx = (float)size_x/w, fy = (float)size_y/h; ++ for (unsigned int y = 0; y::vector(fx*x,fy*y,0).move_to(vertices); ++ for (unsigned int y = 0; y::vector(off1,off4,off3,off2).move_to(primitives); ++ } ++ return vertices>'x'; ++ } ++ ++ //! Generate a 3d sphere. ++ /** ++ \param[out] primitives The returned list of the 3d object primitives ++ (template type \e tf should be at least \e unsigned \e int). ++ \param radius The radius of the sphere (dimension along the X-axis). ++ \param subdivisions The number of recursive subdivisions from an initial icosahedron. ++ \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg image (0<=i<=N - 1). ++ \par Example ++ \code ++ CImgList faces3d; ++ const CImg points3d = CImg::sphere3d(faces3d,100,4); ++ CImg().display_object3d("Sphere3d",points3d,faces3d); ++ \endcode ++ \image html ref_sphere3d.jpg ++ **/ ++ template ++ static CImg sphere3d(CImgList& primitives, ++ const float radius=50, const unsigned int subdivisions=3) { ++ ++ // Create initial icosahedron ++ primitives.assign(); ++ const double tmp = (1 + std::sqrt(5.0f))/2, a = 1.0/std::sqrt(1 + tmp*tmp), b = tmp*a; ++ CImgList vertices(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); ++ // edge - length/2 ++ float he = (float)a; ++ ++ // Recurse subdivisions ++ for (unsigned int i = 0; i::vector(nx0,ny0,nz0).move_to(vertices); i0 = vertices.width() - 1; } ++ if (i1<0) { CImg::vector(nx1,ny1,nz1).move_to(vertices); i1 = vertices.width() - 1; } ++ if (i2<0) { CImg::vector(nx2,ny2,nz2).move_to(vertices); i2 = vertices.width() - 1; } ++ primitives.remove(0); ++ CImg::vector(p0,i0,i1).move_to(primitives); ++ CImg::vector((tf)i0,(tf)p1,(tf)i2).move_to(primitives); ++ CImg::vector((tf)i1,(tf)i2,(tf)p2).move_to(primitives); ++ CImg::vector((tf)i1,(tf)i0,(tf)i2).move_to(primitives); ++ } ++ } ++ return (vertices>'x')*=radius; ++ } ++ ++ //! Generate a 3d ellipsoid. ++ /** ++ \param[out] primitives The returned list of the 3d object primitives ++ (template type \e tf should be at least \e unsigned \e int). ++ \param tensor The tensor which gives the shape and size of the ellipsoid. ++ \param subdivisions The number of recursive subdivisions from an initial stretched icosahedron. ++ \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg image (0<=i<=N - 1). ++ \par Example ++ \code ++ CImgList faces3d; ++ const CImg tensor = CImg::diagonal(10,7,3), ++ points3d = CImg::ellipsoid3d(faces3d,tensor,4); ++ CImg().display_object3d("Ellipsoid3d",points3d,faces3d); ++ \endcode ++ \image html ref_ellipsoid3d.jpg ++ **/ ++ template ++ static CImg ellipsoid3d(CImgList& primitives, ++ const CImg& tensor, const unsigned int subdivisions=3) { ++ primitives.assign(); ++ if (!subdivisions) return CImg(); ++ CImg S, V; ++ tensor.symmetric_eigen(S,V); ++ const float orient = ++ (V(0,1)*V(1,2) - V(0,2)*V(1,1))*V(2,0) + ++ (V(0,2)*V(1,0) - V(0,0)*V(1,2))*V(2,1) + ++ (V(0,0)*V(1,1) - V(0,1)*V(1,0))*V(2,2); ++ if (orient<0) { V(2,0) = -V(2,0); V(2,1) = -V(2,1); V(2,2) = -V(2,2); } ++ const float l0 = S[0], l1 = S[1], l2 = S[2]; ++ CImg vertices = sphere3d(primitives,1.0,subdivisions); ++ vertices.get_shared_row(0)*=l0; ++ vertices.get_shared_row(1)*=l1; ++ vertices.get_shared_row(2)*=l2; ++ return V*vertices; ++ } ++ ++ //! Convert 3d object into a CImg3d representation. ++ /** ++ \param primitives Primitives data of the 3d object. ++ \param colors Colors data of the 3d object. ++ \param opacities Opacities data of the 3d object. ++ \param full_check Tells if full checking of the 3d object must be performed. ++ **/ ++ template ++ CImg& object3dtoCImg3d(const CImgList& primitives, ++ const CImgList& colors, ++ const to& opacities, ++ const bool full_check=true) { ++ return get_object3dtoCImg3d(primitives,colors,opacities,full_check).move_to(*this); ++ } ++ ++ //! Convert 3d object into a CImg3d representation \overloading. ++ template ++ CImg& object3dtoCImg3d(const CImgList& primitives, ++ const CImgList& colors, ++ const bool full_check=true) { ++ return get_object3dtoCImg3d(primitives,colors,full_check).move_to(*this); ++ } ++ ++ //! Convert 3d object into a CImg3d representation \overloading. ++ template ++ CImg& object3dtoCImg3d(const CImgList& primitives, ++ const bool full_check=true) { ++ return get_object3dtoCImg3d(primitives,full_check).move_to(*this); ++ } ++ ++ //! Convert 3d object into a CImg3d representation \overloading. ++ CImg& object3dtoCImg3d(const bool full_check=true) { ++ return get_object3dtoCImg3d(full_check).move_to(*this); ++ } ++ ++ //! Convert 3d object into a CImg3d representation \newinstance. ++ template ++ CImg get_object3dtoCImg3d(const CImgList& primitives, ++ const CImgList& colors, ++ const to& opacities, ++ const bool full_check=true) const { ++ CImg error_message(1024); ++ if (!is_object3d(primitives,colors,opacities,full_check,error_message)) ++ throw CImgInstanceException(_cimg_instance ++ "object3dtoCImg3d(): Invalid specified 3d object (%u,%u) (%s).", ++ cimg_instance,_width,primitives._width,error_message.data()); ++ CImg res(1,_size_object3dtoCImg3d(primitives,colors,opacities)); ++ float *ptrd = res._data; ++ ++ // Put magick number. ++ *(ptrd++) = 'C' + 0.5f; *(ptrd++) = 'I' + 0.5f; *(ptrd++) = 'm' + 0.5f; ++ *(ptrd++) = 'g' + 0.5f; *(ptrd++) = '3' + 0.5f; *(ptrd++) = 'd' + 0.5f; ++ ++ // Put number of vertices and primitives. ++ *(ptrd++) = cimg::uint2float(_width); ++ *(ptrd++) = cimg::uint2float(primitives._width); ++ ++ // Put vertex data. ++ if (is_empty() || !primitives) return res; ++ const T *ptrx = data(0,0), *ptry = data(0,1), *ptrz = data(0,2); ++ cimg_forX(*this,p) { ++ *(ptrd++) = (float)*(ptrx++); ++ *(ptrd++) = (float)*(ptry++); ++ *(ptrd++) = (float)*(ptrz++); ++ } ++ ++ // Put primitive data. ++ cimglist_for(primitives,p) { ++ *(ptrd++) = (float)primitives[p].size(); ++ const tp *ptrp = primitives[p]._data; ++ cimg_foroff(primitives[p],i) *(ptrd++) = cimg::uint2float((unsigned int)*(ptrp++)); ++ } ++ ++ // Put color/texture data. ++ const unsigned int csiz = std::min(colors._width,primitives._width); ++ for (int c = 0; c<(int)csiz; ++c) { ++ const CImg& color = colors[c]; ++ const tc *ptrc = color._data; ++ if (color.size()==3) { *(ptrd++) = (float)*(ptrc++); *(ptrd++) = (float)*(ptrc++); *(ptrd++) = (float)*ptrc; } ++ else { ++ *(ptrd++) = -128.0f; ++ int shared_ind = -1; ++ if (color.is_shared()) for (int i = 0; i ++ float* _object3dtoCImg3d(const CImgList& opacities, float *ptrd) const { ++ cimglist_for(opacities,o) { ++ const CImg& opacity = opacities[o]; ++ const to *ptro = opacity._data; ++ if (opacity.size()==1) *(ptrd++) = (float)*ptro; ++ else { ++ *(ptrd++) = -128.0f; ++ int shared_ind = -1; ++ if (opacity.is_shared()) for (int i = 0; i ++ float* _object3dtoCImg3d(const CImg& opacities, float *ptrd) const { ++ const to *ptro = opacities._data; ++ cimg_foroff(opacities,o) *(ptrd++) = (float)*(ptro++); ++ return ptrd; ++ } ++ ++ template ++ unsigned int _size_object3dtoCImg3d(const CImgList& primitives, ++ const CImgList& colors, ++ const CImgList& opacities) const { ++ unsigned int siz = 8U + 3*_width; ++ cimglist_for(primitives,p) siz+=primitives[p].size() + 1; ++ for (int c = std::min(primitives.width(),colors.width()) - 1; c>=0; --c) { ++ if (colors[c].is_shared()) siz+=4; ++ else { const unsigned int csiz = colors[c].size(); siz+=(csiz!=3)?4 + csiz:3; } ++ } ++ if (colors._width ++ unsigned int _size_object3dtoCImg3d(const CImgList& primitives, ++ const CImgList& colors, ++ const CImg& opacities) const { ++ unsigned int siz = 8U + 3*_width; ++ cimglist_for(primitives,p) siz+=primitives[p].size() + 1; ++ for (int c = std::min(primitives.width(),colors.width()) - 1; c>=0; --c) { ++ const unsigned int csiz = colors[c].size(); siz+=(csiz!=3)?4 + csiz:3; ++ } ++ if (colors._width ++ CImg get_object3dtoCImg3d(const CImgList& primitives, ++ const CImgList& colors, ++ const bool full_check=true) const { ++ CImgList opacities; ++ return get_object3dtoCImg3d(primitives,colors,opacities,full_check); ++ } ++ ++ //! Convert 3d object into a CImg3d representation \overloading. ++ template ++ CImg get_object3dtoCImg3d(const CImgList& primitives, ++ const bool full_check=true) const { ++ CImgList colors, opacities; ++ return get_object3dtoCImg3d(primitives,colors,opacities,full_check); ++ } ++ ++ //! Convert 3d object into a CImg3d representation \overloading. ++ CImg get_object3dtoCImg3d(const bool full_check=true) const { ++ CImgList opacities, colors; ++ CImgList primitives(width(),1,1,1,1); ++ cimglist_for(primitives,p) primitives(p,0) = p; ++ return get_object3dtoCImg3d(primitives,colors,opacities,full_check); ++ } ++ ++ //! Convert CImg3d representation into a 3d object. ++ /** ++ \param[out] primitives Primitives data of the 3d object. ++ \param[out] colors Colors data of the 3d object. ++ \param[out] opacities Opacities data of the 3d object. ++ \param full_check Tells if full checking of the 3d object must be performed. ++ **/ ++ template ++ CImg& CImg3dtoobject3d(CImgList& primitives, ++ CImgList& colors, ++ CImgList& opacities, ++ const bool full_check=true) { ++ return get_CImg3dtoobject3d(primitives,colors,opacities,full_check).move_to(*this); ++ } ++ ++ //! Convert CImg3d representation into a 3d object \newinstance. ++ template ++ CImg get_CImg3dtoobject3d(CImgList& primitives, ++ CImgList& colors, ++ CImgList& opacities, ++ const bool full_check=true) const { ++ CImg error_message(1024); ++ if (!is_CImg3d(full_check,error_message)) ++ throw CImgInstanceException(_cimg_instance ++ "CImg3dtoobject3d(): image instance is not a CImg3d (%s).", ++ cimg_instance,error_message.data()); ++ const T *ptrs = _data + 6; ++ const unsigned int ++ nb_points = cimg::float2uint((float)*(ptrs++)), ++ nb_primitives = cimg::float2uint((float)*(ptrs++)); ++ const CImg points = CImg(ptrs,3,nb_points,1,1,true).get_transpose(); ++ ptrs+=3*nb_points; ++ primitives.assign(nb_primitives); ++ cimglist_for(primitives,p) { ++ const unsigned int nb_inds = (unsigned int)*(ptrs++); ++ primitives[p].assign(1,nb_inds); ++ tp *ptrp = primitives[p]._data; ++ for (unsigned int i = 0; i ++ CImg& _draw_scanline(const int x0, const int x1, const int y, ++ const tc *const color, const float opacity, ++ const float brightness, ++ const float nopacity, const float copacity, const ulongT whd) { ++ static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); ++ const int nx0 = x0>0?x0:0, nx1 = x1=0) { ++ const tc *col = color; ++ const ulongT off = whd - dx - 1; ++ T *ptrd = data(nx0,y); ++ if (opacity>=1) { // ** Opaque drawing ** ++ if (brightness==1) { // Brightness==1 ++ if (sizeof(T)!=1) cimg_forC(*this,c) { ++ const T val = (T)*(col++); ++ for (int x = dx; x>=0; --x) *(ptrd++) = val; ++ ptrd+=off; ++ } else cimg_forC(*this,c) { ++ const T val = (T)*(col++); ++ std::memset(ptrd,(int)val,dx + 1); ++ ptrd+=whd; ++ } ++ } else if (brightness<1) { // Brightness<1 ++ if (sizeof(T)!=1) cimg_forC(*this,c) { ++ const T val = (T)(*(col++)*brightness); ++ for (int x = dx; x>=0; --x) *(ptrd++) = val; ++ ptrd+=off; ++ } else cimg_forC(*this,c) { ++ const T val = (T)(*(col++)*brightness); ++ std::memset(ptrd,(int)val,dx + 1); ++ ptrd+=whd; ++ } ++ } else { // Brightness>1 ++ if (sizeof(T)!=1) cimg_forC(*this,c) { ++ const T val = (T)((2-brightness)**(col++) + (brightness - 1)*maxval); ++ for (int x = dx; x>=0; --x) *(ptrd++) = val; ++ ptrd+=off; ++ } else cimg_forC(*this,c) { ++ const T val = (T)((2-brightness)**(col++) + (brightness - 1)*maxval); ++ std::memset(ptrd,(int)val,dx + 1); ++ ptrd+=whd; ++ } ++ } ++ } else { // ** Transparent drawing ** ++ if (brightness==1) { // Brightness==1 ++ cimg_forC(*this,c) { ++ const Tfloat val = *(col++)*nopacity; ++ for (int x = dx; x>=0; --x) { *ptrd = (T)(val + *ptrd*copacity); ++ptrd; } ++ ptrd+=off; ++ } ++ } else if (brightness<=1) { // Brightness<1 ++ cimg_forC(*this,c) { ++ const Tfloat val = *(col++)*brightness*nopacity; ++ for (int x = dx; x>=0; --x) { *ptrd = (T)(val + *ptrd*copacity); ++ptrd; } ++ ptrd+=off; ++ } ++ } else { // Brightness>1 ++ cimg_forC(*this,c) { ++ const Tfloat val = ((2-brightness)**(col++) + (brightness - 1)*maxval)*nopacity; ++ for (int x = dx; x>=0; --x) { *ptrd = (T)(val + *ptrd*copacity); ++ptrd; } ++ ptrd+=off; ++ } ++ } ++ } ++ } ++ return *this; ++ } ++ ++ //! Draw a 3d point. ++ /** ++ \param x0 X-coordinate of the point. ++ \param y0 Y-coordinate of the point. ++ \param z0 Z-coordinate of the point. ++ \param color Pointer to \c spectrum() consecutive values, defining the drawing color. ++ \param opacity Drawing opacity. ++ \note ++ - 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 int z0, ++ const tc *const color, const float opacity=1) { ++ if (is_empty()) return *this; ++ if (!color) ++ throw CImgArgumentException(_cimg_instance ++ "draw_point(): Specified color is (null).", ++ cimg_instance); ++ if (x0>=0 && y0>=0 && z0>=0 && x0=1) cimg_forC(*this,c) { *ptrd = (T)*(col++); ptrd+=whd; } ++ else cimg_forC(*this,c) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whd; } ++ } ++ return *this; ++ } ++ ++ //! Draw a 2d point \simplification. ++ 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 points cloud. ++ /** ++ \param points Image of vertices coordinates. ++ \param color Pointer to \c spectrum() consecutive values, defining the drawing color. ++ \param opacity Drawing opacity. ++ **/ ++ template ++ CImg& draw_point(const CImg& points, ++ const tc *const color, const float opacity=1) { ++ if (is_empty() || !points) return *this; ++ switch (points._height) { ++ case 0 : case 1 : ++ throw CImgArgumentException(_cimg_instance ++ "draw_point(): Invalid specified point set (%u,%u,%u,%u,%p).", ++ cimg_instance, ++ points._width,points._height,points._depth,points._spectrum,points._data); ++ case 2 : { ++ cimg_forX(points,i) draw_point((int)points(i,0),(int)points(i,1),color,opacity); ++ } break; ++ default : { ++ cimg_forX(points,i) draw_point((int)points(i,0),(int)points(i,1),(int)points(i,2),color,opacity); ++ } ++ } ++ return *this; ++ } ++ ++ //! Draw a 2d 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 spectrum() consecutive values of type \c T, defining the drawing color. ++ \param opacity Drawing opacity. ++ \param pattern An integer whose bits describe the line pattern. ++ \param init_hatch Tells if a reinitialization of the hash state must be done. ++ \note ++ - 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_instance ++ "draw_line(): Specified color is (null).", ++ cimg_instance); ++ static unsigned int hatch = ~0U - (~0U>>1); ++ if (init_hatch) hatch = ~0U - (~0U>>1); ++ const bool xdir = x0=width()) return *this; ++ if (xleft<0) { yleft-=(int)((float)xleft*((float)yright - yleft)/((float)xright - xleft)); xleft = 0; } ++ if (xright>=width()) { ++ yright-=(int)(((float)xright - width())*((float)yright - yleft)/((float)xright - xleft)); ++ xright = width() - 1; ++ } ++ if (ydown<0 || yup>=height()) return *this; ++ if (yup<0) { xup-=(int)((float)yup*((float)xdown - xup)/((float)ydown - yup)); yup = 0; } ++ if (ydown>=height()) { ++ xdown-=(int)(((float)ydown - height())*((float)xdown - xup)/((float)ydown - yup)); ++ ydown = height() - 1; ++ } ++ T *ptrd0 = data(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 longT ++ offx = (longT)(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_forC(*this,c) { *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_forC(*this,c) { *ptrd = (T)*(col++); ptrd+=wh; } ++ ptrd0+=offx; ++ if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } ++ } ++ } else { ++ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f); ++ if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { ++ if (pattern&hatch) { ++ T *ptrd = ptrd0; const tc* col = color; ++ cimg_forC(*this,c) { *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_forC(*this,c) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; } ++ ptrd0+=offx; ++ if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } ++ } ++ } ++ return *this; ++ } ++ ++ //! Draw a 2d line, with z-buffering. ++ /** ++ \param zbuffer Zbuffer image. ++ \param x0 X-coordinate of the starting point. ++ \param y0 Y-coordinate of the starting point. ++ \param z0 Z-coordinate of the starting point ++ \param x1 X-coordinate of the ending point. ++ \param y1 Y-coordinate of the ending point. ++ \param z1 Z-coordinate of the ending point. ++ \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. ++ \param opacity Drawing opacity. ++ \param pattern An integer whose bits describe the line pattern. ++ \param init_hatch Tells if a reinitialization of the hash state must be done. ++ **/ ++ template ++ CImg& draw_line(CImg& 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) { ++ typedef typename cimg::superset::type tzfloat; ++ if (is_empty() || z0<=0 || z1<=0) return *this; ++ if (!color) ++ throw CImgArgumentException(_cimg_instance ++ "draw_line(): Specified color is (null).", ++ cimg_instance); ++ if (!is_sameXY(zbuffer)) ++ throw CImgArgumentException(_cimg_instance ++ "draw_line(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " ++ "different dimensions.", ++ cimg_instance, ++ zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); ++ static unsigned int hatch = ~0U - (~0U>>1); ++ if (init_hatch) hatch = ~0U - (~0U>>1); ++ const bool xdir = x0=width()) return *this; ++ if (xleft<0) { ++ const float D = (float)xright - xleft; ++ yleft-=(int)((float)xleft*((float)yright - yleft)/D); ++ zleft-=(tzfloat)xleft*(zright - zleft)/D; ++ xleft = 0; ++ } ++ if (xright>=width()) { ++ const float d = (float)xright - width(), D = (float)xright - xleft; ++ yright-=(int)(d*((float)yright - yleft)/D); ++ zright-=(tzfloat)d*(zright - zleft)/D; ++ xright = width() - 1; ++ } ++ if (ydown<0 || yup>=height()) return *this; ++ if (yup<0) { ++ const float D = (float)ydown - yup; ++ xup-=(int)((float)yup*((float)xdown - xup)/D); ++ zup-=(tzfloat)yup*(zdown - zup)/D; ++ yup = 0; ++ } ++ if (ydown>=height()) { ++ const float d = (float)ydown - height(), D = (float)ydown - yup; ++ xdown-=(int)(d*((float)xdown - xup)/D); ++ zdown-=(tzfloat)d*(zdown - zup)/D; ++ ydown = height() - 1; ++ } ++ T *ptrd0 = data(nx0,ny0); ++ tz *ptrz = zbuffer.data(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 longT ++ offx = (longT)(nx00?dx:1); ++ if (opacity>=1) { ++ if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { ++ const tzfloat z = Z0 + x*dz/ndx; ++ if (z>=(tzfloat)*ptrz && pattern&hatch) { ++ *ptrz = (tz)z; ++ T *ptrd = ptrd0; const tc *col = color; ++ cimg_forC(*this,c) { *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 tzfloat z = Z0 + x*dz/ndx; ++ if (z>=(tzfloat)*ptrz) { ++ *ptrz = (tz)z; ++ T *ptrd = ptrd0; const tc *col = color; ++ cimg_forC(*this,c) { *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 - std::max(opacity,0.0f); ++ if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { ++ const tzfloat z = Z0 + x*dz/ndx; ++ if (z>=(tzfloat)*ptrz && pattern&hatch) { ++ *ptrz = (tz)z; ++ T *ptrd = ptrd0; const tc *col = color; ++ cimg_forC(*this,c) { *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 tzfloat z = Z0 + x*dz/ndx; ++ if (z>=(tzfloat)*ptrz) { ++ *ptrz = (tz)z; ++ T *ptrd = ptrd0; const tc *col = color; ++ cimg_forC(*this,c) { *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 3d line. ++ /** ++ \param x0 X-coordinate of the starting point. ++ \param y0 Y-coordinate of the starting point. ++ \param z0 Z-coordinate of the starting point ++ \param x1 X-coordinate of the ending point. ++ \param y1 Y-coordinate of the ending point. ++ \param z1 Z-coordinate of the ending point. ++ \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. ++ \param opacity Drawing opacity. ++ \param pattern An integer whose bits describe the line pattern. ++ \param init_hatch Tells if a reinitialization of the hash state must be done. ++ **/ ++ 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_instance ++ "draw_line(): Specified color is (null).", ++ cimg_instance); ++ 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>=width()) return *this; ++ if (nx0<0) { ++ const float D = 1.0f + nx1 - nx0; ++ ny0-=(int)((float)nx0*(1.0f + ny1 - ny0)/D); ++ nz0-=(int)((float)nx0*(1.0f + nz1 - nz0)/D); ++ nx0 = 0; ++ } ++ if (nx1>=width()) { ++ const float d = (float)nx1 - width(), D = 1.0f + nx1 - nx0; ++ ny1+=(int)(d*(1.0f + ny0 - ny1)/D); ++ nz1+=(int)(d*(1.0f + nz0 - nz1)/D); ++ nx1 = width() - 1; ++ } ++ if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1); ++ if (ny1<0 || ny0>=height()) return *this; ++ if (ny0<0) { ++ const float D = 1.0f + ny1 - ny0; ++ nx0-=(int)((float)ny0*(1.0f + nx1 - nx0)/D); ++ nz0-=(int)((float)ny0*(1.0f + nz1 - nz0)/D); ++ ny0 = 0; ++ } ++ if (ny1>=height()) { ++ const float d = (float)ny1 - height(), D = 1.0f + ny1 - ny0; ++ nx1+=(int)(d*(1.0f + nx0 - nx1)/D); ++ nz1+=(int)(d*(1.0f + nz0 - nz1)/D); ++ ny1 = height() - 1; ++ } ++ if (nz0>nz1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1); ++ if (nz1<0 || nz0>=depth()) return *this; ++ if (nz0<0) { ++ const float D = 1.0f + nz1 - nz0; ++ nx0-=(int)((float)nz0*(1.0f + nx1 - nx0)/D); ++ ny0-=(int)((float)nz0*(1.0f + ny1 - ny0)/D); ++ nz0 = 0; ++ } ++ if (nz1>=depth()) { ++ const float d = (float)nz1 - depth(), D = 1.0f + nz1 - nz0; ++ nx1+=(int)(d*(1.0f + nx0 - nx1)/D); ++ ny1+=(int)(d*(1.0f + ny0 - ny1)/D); ++ nz1 = depth() - 1; ++ } ++ const unsigned int dmax = (unsigned int)cimg::max(cimg::abs(nx1 - nx0),cimg::abs(ny1 - ny0),nz1 - nz0); ++ const ulongT whd = (ulongT)_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 = data((unsigned int)x,(unsigned int)y,(unsigned int)z); ++ const tc *col = color; cimg_forC(*this,c) { *ptrd = (T)*(col++); ptrd+=whd; } ++ } ++ 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 - std::max(opacity,0.0f); ++ for (unsigned int t = 0; t<=dmax; ++t) { ++ if (!(~pattern) || (~pattern && pattern&hatch)) { ++ T* ptrd = data((unsigned int)x,(unsigned int)y,(unsigned int)z); ++ const tc *col = color; cimg_forC(*this,c) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whd; } ++ } ++ x+=px; y+=py; z+=pz; if (pattern) { hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); } ++ } ++ } ++ return *this; ++ } ++ ++ //! Draw a textured 2d 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. ++ \param pattern An integer whose bits describe the line pattern. ++ \param init_hatch Tells if the hash variable must be reinitialized. ++ \note ++ - 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._depth>1 || texture._spectrum<_spectrum) ++ throw CImgArgumentException(_cimg_instance ++ "draw_line(): Invalid specified texture (%u,%u,%u,%u,%p).", ++ cimg_instance, ++ texture._width,texture._height,texture._depth,texture._spectrum,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=width()) return *this; ++ if (xleft<0) { ++ const float D = (float)xright - xleft; ++ yleft-=(int)((float)xleft*((float)yright - yleft)/D); ++ txleft-=(int)((float)xleft*((float)txright - txleft)/D); ++ tyleft-=(int)((float)xleft*((float)tyright - tyleft)/D); ++ xleft = 0; ++ } ++ if (xright>=width()) { ++ const float d = (float)xright - width(), D = (float)xright - xleft; ++ yright-=(int)(d*((float)yright - yleft)/D); ++ txright-=(int)(d*((float)txright - txleft)/D); ++ tyright-=(int)(d*((float)tyright - tyleft)/D); ++ xright = width() - 1; ++ } ++ if (ydown<0 || yup>=height()) return *this; ++ if (yup<0) { ++ const float D = (float)ydown - yup; ++ xup-=(int)((float)yup*((float)xdown - xup)/D); ++ txup-=(int)((float)yup*((float)txdown - txup)/D); ++ tyup-=(int)((float)yup*((float)tydown - tyup)/D); ++ yup = 0; ++ } ++ if (ydown>=height()) { ++ const float d = (float)ydown - height(), D = (float)ydown - yup; ++ xdown-=(int)(d*((float)xdown - xup)/D); ++ txdown-=(int)(d*((float)txdown - txup)/D); ++ tydown-=(int)(d*((float)tydown - tyup)/D); ++ ydown = height() - 1; ++ } ++ T *ptrd0 = data(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 longT ++ offx = (longT)(nx00?dx:1); ++ const ulongT ++ whd = (ulongT)_width*_height*_depth, ++ twh = (ulongT)texture._width*texture._height; ++ ++ 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; ++ const tc *col = &texture._atXY(tx,ty); ++ cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } ++ } ++ 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; ++ const tc *col = &texture._atXY(tx,ty); ++ cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } ++ ptrd0+=offx; ++ if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } ++ } ++ } else { ++ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f); ++ 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; ++ const tc *col = &texture._atXY(tx,ty); ++ cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } ++ } ++ 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; ++ const tc *col = &texture._atXY(tx,ty); ++ cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } ++ ptrd0+=offx; ++ if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } ++ } ++ } ++ return *this; ++ } ++ ++ //! Draw a textured 2d line, with perspective correction. ++ /** ++ \param x0 X-coordinate of the starting point. ++ \param y0 Y-coordinate of the starting point. ++ \param z0 Z-coordinate of the starting point ++ \param x1 X-coordinate of the ending point. ++ \param y1 Y-coordinate of the ending point. ++ \param z1 Z-coordinate of the ending 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. ++ \param pattern An integer whose bits describe the line pattern. ++ \param init_hatch Tells if the hash variable must be reinitialized. ++ **/ ++ 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._depth>1 || texture._spectrum<_spectrum) ++ throw CImgArgumentException(_cimg_instance ++ "draw_line(): Invalid specified texture (%u,%u,%u,%u,%p).", ++ cimg_instance, ++ texture._width,texture._height,texture._depth,texture._spectrum,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=width()) return *this; ++ if (xleft<0) { ++ const float D = (float)xright - xleft; ++ yleft-=(int)((float)xleft*((float)yright - yleft)/D); ++ zleft-=(float)xleft*(zright - zleft)/D; ++ txleft-=(float)xleft*(txright - txleft)/D; ++ tyleft-=(float)xleft*(tyright - tyleft)/D; ++ xleft = 0; ++ } ++ if (xright>=width()) { ++ const float d = (float)xright - width(), D = (float)xright - xleft; ++ yright-=(int)(d*((float)yright - yleft)/D); ++ zright-=d*(zright - zleft)/D; ++ txright-=d*(txright - txleft)/D; ++ tyright-=d*(tyright - tyleft)/D; ++ xright = width() - 1; ++ } ++ if (ydown<0 || yup>=height()) return *this; ++ if (yup<0) { ++ const float D = (float)ydown - yup; ++ xup-=(int)((float)yup*((float)xdown - xup)/D); ++ zup-=(float)yup*(zdown - zup)/D; ++ txup-=(float)yup*(txdown - txup)/D; ++ tyup-=(float)yup*(tydown - tyup)/D; ++ yup = 0; ++ } ++ if (ydown>=height()) { ++ const float d = (float)ydown - height(), D = (float)ydown - yup; ++ xdown-=(int)(d*((float)xdown - xup)/D); ++ zdown-=d*(zdown - zup)/D; ++ txdown-=d*(txdown - txup)/D; ++ tydown-=d*(tydown - tyup)/D; ++ ydown = height() - 1; ++ } ++ T *ptrd0 = data(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 longT ++ offx = (longT)(nx00?dx:1); ++ const ulongT ++ whd = (ulongT)_width*_height*_depth, ++ twh = (ulongT)texture._width*texture._height; ++ ++ 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; ++ const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); ++ T *ptrd = ptrd0; ++ cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } ++ } ++ 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; ++ const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); ++ T *ptrd = ptrd0; ++ cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } ++ ptrd0+=offx; ++ if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } ++ } ++ } else { ++ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f); ++ 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; ++ const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); ++ T *ptrd = ptrd0; ++ cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } ++ } ++ 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; ++ const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); ++ T *ptrd = ptrd0; ++ cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } ++ ptrd0+=offx; ++ if ((error-=dy)<0) { ptrd0+=offy; error+=dx; } ++ } ++ } ++ return *this; ++ } ++ ++ //! Draw a textured 2d line, with perspective correction and z-buffering. ++ /** ++ \param zbuffer Z-buffer image. ++ \param x0 X-coordinate of the starting point. ++ \param y0 Y-coordinate of the starting point. ++ \param z0 Z-coordinate of the starting point ++ \param x1 X-coordinate of the ending point. ++ \param y1 Y-coordinate of the ending point. ++ \param z1 Z-coordinate of the ending 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. ++ \param pattern An integer whose bits describe the line pattern. ++ \param init_hatch Tells if the hash variable must be reinitialized. ++ **/ ++ template ++ CImg& draw_line(CImg& 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) { ++ typedef typename cimg::superset::type tzfloat; ++ if (is_empty() || z0<=0 || z1<=0) return *this; ++ if (!is_sameXY(zbuffer)) ++ throw CImgArgumentException(_cimg_instance ++ "draw_line(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " ++ "different dimensions.", ++ cimg_instance, ++ zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); ++ if (texture._depth>1 || texture._spectrum<_spectrum) ++ throw CImgArgumentException(_cimg_instance ++ "draw_line(): Invalid specified texture (%u,%u,%u,%u,%p).", ++ cimg_instance, ++ texture._width,texture._height,texture._depth,texture._spectrum,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=width()) return *this; ++ if (xleft<0) { ++ const float D = (float)xright - xleft; ++ yleft-=(int)((float)xleft*((float)yright - yleft)/D); ++ zleft-=(float)xleft*(zright - zleft)/D; ++ txleft-=(float)xleft*(txright - txleft)/D; ++ tyleft-=(float)xleft*(tyright - tyleft)/D; ++ xleft = 0; ++ } ++ if (xright>=width()) { ++ const float d = (float)xright - width(), D = (float)xright - xleft; ++ yright-=(int)(d*((float)yright - yleft)/D); ++ zright-=d*(zright - zleft)/D; ++ txright-=d*(txright - txleft)/D; ++ tyright-=d*(tyright - tyleft)/D; ++ xright = width() - 1; ++ } ++ if (ydown<0 || yup>=height()) return *this; ++ if (yup<0) { ++ const float D = (float)ydown - yup; ++ xup-=(int)((float)yup*((float)xdown - xup)/D); ++ zup-=yup*(zdown - zup)/D; ++ txup-=yup*(txdown - txup)/D; ++ tyup-=yup*(tydown - tyup)/D; ++ yup = 0; ++ } ++ if (ydown>=height()) { ++ const float d = (float)ydown - height(), D = (float)ydown - yup; ++ xdown-=(int)(d*((float)xdown - xup)/D); ++ zdown-=d*(zdown - zup)/D; ++ txdown-=d*(txdown - txup)/D; ++ tydown-=d*(tydown - tyup)/D; ++ ydown = height() - 1; ++ } ++ T *ptrd0 = data(nx0,ny0); ++ tz *ptrz = zbuffer.data(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 longT ++ offx = (longT)(nx00?dx:1); ++ const ulongT ++ whd = (ulongT)_width*_height*_depth, ++ twh = (ulongT)texture._width*texture._height; ++ ++ if (opacity>=1) { ++ if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { ++ if (pattern&hatch) { ++ const tzfloat z = Z0 + x*dz/ndx; ++ if (z>=(tzfloat)*ptrz) { ++ *ptrz = (tz)z; ++ const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; ++ const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); ++ T *ptrd = ptrd0; ++ cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } ++ } ++ } ++ 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 tzfloat z = Z0 + x*dz/ndx; ++ if (z>=(tzfloat)*ptrz) { ++ *ptrz = (tz)z; ++ const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; ++ const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); ++ T *ptrd = ptrd0; ++ cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; } ++ } ++ ptrd0+=offx; ptrz+=offx; ++ if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } ++ } ++ } else { ++ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f); ++ if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) { ++ if (pattern&hatch) { ++ const tzfloat z = Z0 + x*dz/ndx; ++ if (z>=(tzfloat)*ptrz) { ++ *ptrz = (tz)z; ++ const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; ++ const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); ++ T *ptrd = ptrd0; ++ cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } ++ } ++ } ++ 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 tzfloat z = Z0 + x*dz/ndx; ++ if (z>=(tzfloat)*ptrz) { ++ *ptrz = (tz)z; ++ const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx; ++ const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z)); ++ T *ptrd = ptrd0; ++ cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; } ++ } ++ ptrd0+=offx; ptrz+=offx; ++ if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; } ++ } ++ } ++ return *this; ++ } ++ ++ //! Draw a set of consecutive lines. ++ /** ++ \param points Coordinates of vertices, stored as a list of vectors. ++ \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. ++ \param opacity Drawing opacity. ++ \param pattern An integer whose bits describe the line pattern. ++ \param init_hatch If set to true, init hatch motif. ++ \note ++ - This function uses several call to the single CImg::draw_line() procedure, ++ depending on the vectors size in \p points. ++ **/ ++ 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) { ++ if (is_empty() || !points || points._width<2) return *this; ++ bool ninit_hatch = init_hatch; ++ switch (points._height) { ++ case 0 : case 1 : ++ throw CImgArgumentException(_cimg_instance ++ "draw_line(): Invalid specified point set (%u,%u,%u,%u,%p).", ++ cimg_instance, ++ points._width,points._height,points._depth,points._spectrum,points._data); ++ ++ 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_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::PI/180), ang = (sq>0)?(float)std::atan2(v,u):0.0f, ++ l = (length>=0)?length:-length*(float)std::sqrt(sq)/100; ++ if (sq>0) { ++ const float ++ cl = (float)std::cos(ang - deg), sl = (float)std::sin(ang - deg), ++ cr = (float)std::cos(ang + deg), sr = (float)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 2d spline. ++ /** ++ \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 spectrum() consecutive values of type \c T, defining the drawing color. ++ \param precision Curve drawing precision. ++ \param opacity Drawing opacity. ++ \param pattern An integer whose bits describe the line pattern. ++ \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=0.25, const unsigned int pattern=~0U, ++ const bool init_hatch=true) { ++ if (is_empty()) return *this; ++ if (!color) ++ throw CImgArgumentException(_cimg_instance ++ "draw_spline(): Specified color is (null).", ++ cimg_instance); ++ if (x0==x1 && y0==y1) return draw_point(x0,y0,color,opacity); ++ bool ninit_hatch = init_hatch; ++ const float ++ ax = u0 + u1 + 2*(x0 - x1), ++ bx = 3*(x1 - x0) - 2*u0 - u1, ++ ay = v0 + v1 + 2*(y0 - y1), ++ by = 3*(y1 - y0) - 2*v0 - v1, ++ _precision = 1/(cimg::hypot((float)x0 - x1,(float)y0 - y1)*(precision>0?precision:1)); ++ int ox = x0, oy = y0; ++ for (float t = 0; t<1; t+=_precision) { ++ const float t2 = t*t, t3 = t2*t; ++ const int ++ nx = (int)(ax*t3 + bx*t2 + u0*t + x0), ++ ny = (int)(ay*t3 + by*t2 + v0*t + y0); ++ draw_line(ox,oy,nx,ny,color,opacity,pattern,ninit_hatch); ++ ninit_hatch = false; ++ ox = nx; oy = ny; ++ } ++ return draw_line(ox,oy,x1,y1,color,opacity,pattern,false); ++ } ++ ++ //! Draw a 3d spline \overloading. ++ /** ++ \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_instance ++ "draw_spline(): Specified color is (null).", ++ cimg_instance); ++ if (x0==x1 && y0==y1 && z0==z1) return draw_point(x0,y0,z0,color,opacity); ++ bool ninit_hatch = init_hatch; ++ const float ++ ax = u0 + u1 + 2*(x0 - x1), ++ bx = 3*(x1 - x0) - 2*u0 - u1, ++ ay = v0 + v1 + 2*(y0 - y1), ++ by = 3*(y1 - y0) - 2*v0 - v1, ++ az = w0 + w1 + 2*(z0 - z1), ++ bz = 3*(z1 - z0) - 2*w0 - w1, ++ _precision = 1/(cimg::hypot((float)x0 - x1,(float)y0 - y1)*(precision>0?precision:1)); ++ int ox = x0, oy = y0, oz = z0; ++ for (float t = 0; t<1; t+=_precision) { ++ const float t2 = t*t, t3 = t2*t; ++ const int ++ nx = (int)(ax*t3 + bx*t2 + u0*t + x0), ++ ny = (int)(ay*t3 + by*t2 + v0*t + y0), ++ nz = (int)(az*t3 + bz*t2 + w0*t + z0); ++ draw_line(ox,oy,oz,nx,ny,nz,color,opacity,pattern,ninit_hatch); ++ ninit_hatch = false; ++ ox = nx; oy = ny; oz = nz; ++ } ++ return draw_line(ox,oy,oz,x1,y1,z1,color,opacity,pattern,false); ++ } ++ ++ //! Draw a textured 2d spline. ++ /** ++ \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. ++ \param opacity Drawing opacity. ++ \param pattern An integer whose bits describe the line pattern. ++ \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 (texture._depth>1 || texture._spectrum<_spectrum) ++ throw CImgArgumentException(_cimg_instance ++ "draw_spline(): Invalid specified texture (%u,%u,%u,%u,%p).", ++ cimg_instance, ++ texture._width,texture._height,texture._depth,texture._spectrum,texture._data); ++ if (is_empty()) return *this; ++ 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); ++ if (x0==x1 && y0==y1) ++ return draw_point(x0,y0,texture.get_vector_at(x0<=0?0:x0>=texture.width()?texture.width() - 1:x0, ++ y0<=0?0:y0>=texture.height()?texture.height() - 1:y0),opacity); ++ bool ninit_hatch = init_hatch; ++ const float ++ ax = u0 + u1 + 2*(x0 - x1), ++ bx = 3*(x1 - x0) - 2*u0 - u1, ++ ay = v0 + v1 + 2*(y0 - y1), ++ by = 3*(y1 - y0) - 2*v0 - v1, ++ _precision = 1/(cimg::hypot((float)x0 - x1,(float)y0 - y1)*(precision>0?precision:1)); ++ int ox = x0, oy = y0, otx = tx0, oty = ty0; ++ for (float t1 = 0; t1<1; t1+=_precision) { ++ const float t2 = t1*t1, t3 = t2*t1; ++ const int ++ nx = (int)(ax*t3 + bx*t2 + u0*t1 + x0), ++ ny = (int)(ay*t3 + by*t2 + v0*t1 + y0), ++ ntx = tx0 + (int)((tx1 - tx0)*t1), ++ nty = ty0 + (int)((ty1 - ty0)*t1); ++ draw_line(ox,oy,nx,ny,texture,otx,oty,ntx,nty,opacity,pattern,ninit_hatch); ++ ninit_hatch = false; ++ ox = nx; oy = ny; otx = ntx; oty = nty; ++ } ++ return draw_line(ox,oy,x1,y1,texture,otx,oty,tx1,ty1,opacity,pattern,false); ++ } ++ ++ //! Draw a set of consecutive splines. ++ /** ++ \param points Vertices data. ++ \param tangents Tangents data. ++ \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. ++ \param opacity Drawing opacity. ++ \param is_closed_set Tells if the drawn spline set is closed. ++ \param precision Precision of the drawing. ++ \param pattern An integer whose bits describe the line pattern. ++ \param init_hatch If \c true, init hatch motif. ++ **/ ++ template ++ CImg& draw_spline(const CImg& points, const CImg& tangents, ++ const tc *const color, const float opacity=1, ++ const bool is_closed_set=false, const float precision=4, ++ const unsigned int pattern=~0U, const bool init_hatch=true) { ++ if (is_empty() || !points || !tangents || points._width<2 || tangents._width<2) return *this; ++ bool ninit_hatch = init_hatch; ++ switch (points._height) { ++ case 0 : case 1 : ++ throw CImgArgumentException(_cimg_instance ++ "draw_spline(): Invalid specified point set (%u,%u,%u,%u,%p).", ++ cimg_instance, ++ points._width,points._height,points._depth,points._spectrum,points._data); ++ ++ 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 CImg& points, ++ const tc *const color, const float opacity=1, ++ const bool is_closed_set=false, const float precision=4, ++ const unsigned int pattern=~0U, const bool init_hatch=true) { ++ if (is_empty() || !points || points._width<2) return *this; ++ CImg tangents; ++ switch (points._height) { ++ case 0 : case 1 : ++ throw CImgArgumentException(_cimg_instance ++ "draw_spline(): Invalid specified point set (%u,%u,%u,%u,%p).", ++ cimg_instance, ++ points._width,points._height,points._depth,points._spectrum,points._data); ++ case 2 : { ++ tangents.assign(points._width,points._height); ++ cimg_forX(points,p) { ++ const unsigned int ++ p0 = is_closed_set?(p + points._width - 1)%points._width:(p?p - 1:0), ++ p1 = is_closed_set?(p + 1)%points._width:(p + 1=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, \ ++ std::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, \ ++ std::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, \ ++ std::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, \ ++ std::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, \ ++ std::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)) ++ ++ // [internal] Draw a filled 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 float brightness) { ++ cimg_init_scanline(color,opacity); ++ const float nbrightness = cimg::cut(brightness,0,2); ++ 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) ++ cimg_draw_scanline(xl,xr,y,color,opacity,nbrightness); ++ else ++ _cimg_for_triangle1(*this,xl,xr,y,nx0,ny0,nx1,ny1,nx2,ny2) ++ cimg_draw_scanline(xr,xl,y,color,opacity,nbrightness); ++ } ++ return *this; ++ } ++ ++ //! Draw a filled 2d triangle. ++ /** ++ \param x0 X-coordinate of the first vertex. ++ \param y0 Y-coordinate of the first vertex. ++ \param x1 X-coordinate of the second vertex. ++ \param y1 Y-coordinate of the second vertex. ++ \param x2 X-coordinate of the third vertex. ++ \param y2 Y-coordinate of the third vertex. ++ \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. ++ \param opacity Drawing opacity. ++ **/ ++ 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_instance ++ "draw_triangle(): Specified color is (null).", ++ cimg_instance); ++ _draw_triangle(x0,y0,x1,y1,x2,y2,color,opacity,1); ++ return *this; ++ } ++ ++ //! Draw a outlined 2d triangle. ++ /** ++ \param x0 X-coordinate of the first vertex. ++ \param y0 Y-coordinate of the first vertex. ++ \param x1 X-coordinate of the second vertex. ++ \param y1 Y-coordinate of the second vertex. ++ \param x2 X-coordinate of the third vertex. ++ \param y2 Y-coordinate of the third vertex. ++ \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. ++ \param opacity Drawing opacity. ++ \param pattern An integer whose bits describe the outline pattern. ++ **/ ++ 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_instance ++ "draw_triangle(): Specified color is (null).", ++ cimg_instance); ++ 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 filled 2d triangle, with z-buffering. ++ /** ++ \param zbuffer Z-buffer image. ++ \param x0 X-coordinate of the first vertex. ++ \param y0 Y-coordinate of the first vertex. ++ \param z0 Z-coordinate of the first vertex. ++ \param x1 X-coordinate of the second vertex. ++ \param y1 Y-coordinate of the second vertex. ++ \param z1 Z-coordinate of the second vertex. ++ \param x2 X-coordinate of the third vertex. ++ \param y2 Y-coordinate of the third vertex. ++ \param z2 Z-coordinate of the third vertex. ++ \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color. ++ \param opacity Drawing opacity. ++ \param brightness Brightness factor. ++ **/ ++ template ++ CImg& draw_triangle(CImg& 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) { ++ typedef typename cimg::superset::type tzfloat; ++ if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; ++ if (!color) ++ throw CImgArgumentException(_cimg_instance ++ "draw_triangle(): Specified color is (null).", ++ cimg_instance); ++ if (!is_sameXY(zbuffer)) ++ throw CImgArgumentException(_cimg_instance ++ "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " ++ "different dimensions.", ++ cimg_instance, ++ zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); ++ static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); ++ const float ++ nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f), ++ nbrightness = cimg::cut(brightness,0,2); ++ const longT whd = (longT)width()*height()*depth(), offx = spectrum()*whd; ++ int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2; ++ tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)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>=height() || ny2<0) return *this; ++ tzfloat ++ 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; ++ tzfloat zleft = zl, zright = zr; ++ if (xright=width() - 1) xright = width() - 1; ++ T* ptrd = data(xleft,y,0,0); ++ tz *ptrz = xleft<=xright?zbuffer.data(xleft,y):0; ++ if (opacity>=1) { ++ if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { ++ if (zleft>=(tzfloat)*ptrz) { ++ *ptrz = (tz)zleft; ++ const tc *col = color; cimg_forC(*this,c) { *ptrd = (T)*(col++); ptrd+=whd; } ++ ptrd-=offx; ++ } ++ zleft+=pentez; ++ } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { ++ if (zleft>=(tzfloat)*ptrz) { ++ *ptrz = (tz)zleft; ++ const tc *col = color; cimg_forC(*this,c) { *ptrd = (T)(nbrightness*(*col++)); ptrd+=whd; } ++ ptrd-=offx; ++ } ++ zleft+=pentez; ++ } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { ++ if (zleft>=(tzfloat)*ptrz) { ++ *ptrz = (tz)zleft; ++ const tc *col = color; ++ cimg_forC(*this,c) { *ptrd = (T)((2 - nbrightness)**(col++) + (nbrightness - 1)*maxval); ptrd+=whd; } ++ ptrd-=offx; ++ } ++ zleft+=pentez; ++ } ++ } else { ++ if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { ++ if (zleft>=(tzfloat)*ptrz) { ++ *ptrz = (tz)zleft; ++ const tc *col = color; cimg_forC(*this,c) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=whd; } ++ ptrd-=offx; ++ } ++ zleft+=pentez; ++ } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { ++ if (zleft>=(tzfloat)*ptrz) { ++ *ptrz = (tz)zleft; ++ const tc *col = color; ++ cimg_forC(*this,c) { *ptrd = (T)(nopacity*nbrightness**(col++) + *ptrd*copacity); ptrd+=whd; } ++ ptrd-=offx; ++ } ++ zleft+=pentez; ++ } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { ++ if (zleft>=(tzfloat)*ptrz) { ++ *ptrz = (tz)zleft; ++ const tc *col = color; ++ cimg_forC(*this,c) { ++ const T val = (T)((2 - nbrightness)**(col++) + (nbrightness - 1)*maxval); ++ *ptrd = (T)(nopacity*val + *ptrd*copacity); ++ ptrd+=whd; ++ } ++ ptrd-=offx; ++ } ++ zleft+=pentez; ++ } ++ } ++ zr+=pzr; zl+=pzl; ++ } ++ return *this; ++ } ++ ++ //! Draw a Gouraud-shaded 2d triangle. ++ /** ++ \param x0 X-coordinate of the first vertex in the image instance. ++ \param y0 Y-coordinate of the first vertex in the image instance. ++ \param x1 X-coordinate of the second vertex in the image instance. ++ \param y1 Y-coordinate of the second vertex in the image instance. ++ \param x2 X-coordinate of the third vertex in the image instance. ++ \param y2 Y-coordinate of the third vertex in the image instance. ++ \param color Pointer to \c spectrum() consecutive values, defining the drawing color. ++ \param brightness0 Brightness factor of the first vertex (in [0,2]). ++ \param brightness1 brightness factor of the second vertex (in [0,2]). ++ \param brightness2 brightness factor of the third vertex (in [0,2]). ++ \param opacity Drawing opacity. ++ **/ ++ 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_instance ++ "draw_triangle(): Specified color is (null).", ++ cimg_instance); ++ static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); ++ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f); ++ const longT whd = (longT)width()*height()*depth(), offx = spectrum()*whd - 1; ++ int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, ++ nc0 = (int)((brightness0<0.0f?0.0f:(brightness0>2.0f?2.0f:brightness0))*256.0f), ++ nc1 = (int)((brightness1<0.0f?0.0f:(brightness1>2.0f?2.0f:brightness1))*256.0f), ++ nc2 = (int)((brightness2<0.0f?0.0f:(brightness2>2.0f?2.0f:brightness2))*256.0f); ++ 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>=height() || 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>=width() - 1) xright = width() - 1; ++ T* ptrd = data(xleft,y); ++ if (opacity>=1) for (int x = xleft; x<=xright; ++x) { ++ const tc *col = color; ++ cimg_forC(*this,c) { ++ *ptrd = (T)(cleft<256?cleft**(col++)/256:((512 - cleft)**(col++)+(cleft - 256)*maxval)/256); ++ ptrd+=whd; ++ } ++ ptrd-=offx; ++ cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); ++ } else for (int x = xleft; x<=xright; ++x) { ++ const tc *col = color; ++ cimg_forC(*this,c) { ++ const T val = (T)(cleft<256?cleft**(col++)/256:((512 - cleft)**(col++)+(cleft - 256)*maxval)/256); ++ *ptrd = (T)(nopacity*val + *ptrd*copacity); ++ ptrd+=whd; ++ } ++ ptrd-=offx; ++ cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); ++ } ++ } ++ return *this; ++ } ++ ++ //! Draw a Gouraud-shaded 2d triangle, with z-buffering \overloading. ++ template ++ CImg& draw_triangle(CImg& 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) { ++ typedef typename cimg::superset::type tzfloat; ++ if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; ++ if (!color) ++ throw CImgArgumentException(_cimg_instance ++ "draw_triangle(): Specified color is (null).", ++ cimg_instance); ++ if (!is_sameXY(zbuffer)) ++ throw CImgArgumentException(_cimg_instance ++ "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " ++ "different dimensions.", ++ cimg_instance, ++ zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); ++ static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::max()); ++ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f); ++ const longT whd = (longT)width()*height()*depth(), offx = spectrum()*whd; ++ int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, ++ nc0 = (int)((brightness0<0.0f?0.0f:(brightness0>2.0f?2.0f:brightness0))*256.0f), ++ nc1 = (int)((brightness1<0.0f?0.0f:(brightness1>2.0f?2.0f:brightness1))*256.0f), ++ nc2 = (int)((brightness2<0.0f?0.0f:(brightness2>2.0f?2.0f:brightness2))*256.0f); ++ tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)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>=height() || ny2<0) return *this; ++ tzfloat ++ 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; ++ tzfloat 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 tzfloat 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>=width() - 1) xright = width() - 1; ++ T *ptrd = data(xleft,y); ++ tz *ptrz = xleft<=xright?zbuffer.data(xleft,y):0; ++ if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) { ++ if (zleft>=(tzfloat)*ptrz) { ++ *ptrz = (tz)zleft; ++ const tc *col = color; ++ cimg_forC(*this,c) { ++ *ptrd = (T)(cleft<256?cleft**(col++)/256:((512 - cleft)**(col++)+(cleft - 256)*maxval)/256); ++ ptrd+=whd; ++ } ++ 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>=(tzfloat)*ptrz) { ++ *ptrz = (tz)zleft; ++ const tc *col = color; ++ cimg_forC(*this,c) { ++ const T val = (T)(cleft<256?cleft**(col++)/256:((512 - cleft)**(col++)+(cleft - 256)*maxval)/256); ++ *ptrd = (T)(nopacity*val + *ptrd*copacity); ++ ptrd+=whd; ++ } ++ ptrd-=offx; ++ } ++ zleft+=pentez; ++ cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0); ++ } ++ zr+=pzr; zl+=pzl; ++ } ++ return *this; ++ } ++ ++ //! Draw a color-interpolated 2d triangle. ++ /** ++ \param x0 X-coordinate of the first vertex in the image instance. ++ \param y0 Y-coordinate of the first vertex in the image instance. ++ \param x1 X-coordinate of the second vertex in the image instance. ++ \param y1 Y-coordinate of the second vertex in the image instance. ++ \param x2 X-coordinate of the third vertex in the image instance. ++ \param y2 Y-coordinate of the third vertex in the image instance. ++ \param color1 Pointer to \c spectrum() consecutive values of type \c T, defining the color of the first vertex. ++ \param color2 Pointer to \c spectrum() consecutive values of type \c T, defining the color of the seconf vertex. ++ \param color3 Pointer to \c spectrum() consecutive values of type \c T, defining the color of the third vertex. ++ \param opacity Drawing opacity. ++ **/ ++ template ++ CImg& draw_triangle(const int x0, const int y0, ++ const int x1, const int y1, ++ const int x2, const int y2, ++ const tc1 *const color1, ++ const tc2 *const color2, ++ const tc3 *const color3, ++ const float opacity=1) { ++ const unsigned char one = 1; ++ cimg_forC(*this,c) ++ get_shared_channel(c).draw_triangle(x0,y0,x1,y1,x2,y2,&one,color1[c],color2[c],color3[c],opacity); ++ return *this; ++ } ++ ++ //! Draw a textured 2d triangle. ++ /** ++ \param x0 X-coordinate of the first vertex in the image instance. ++ \param y0 Y-coordinate of the first vertex in the image instance. ++ \param x1 X-coordinate of the second vertex in the image instance. ++ \param y1 Y-coordinate of the second vertex in the image instance. ++ \param x2 X-coordinate of the third vertex in the image instance. ++ \param y2 Y-coordinate of the third vertex in the image instance. ++ \param texture Texture image used to fill the triangle. ++ \param tx0 X-coordinate of the first vertex in the texture image. ++ \param ty0 Y-coordinate of the first vertex in the texture image. ++ \param tx1 X-coordinate of the second vertex in the texture image. ++ \param ty1 Y-coordinate of the second vertex in the texture image. ++ \param tx2 X-coordinate of the third vertex in the texture image. ++ \param ty2 Y-coordinate of the third vertex in the texture image. ++ \param opacity Drawing opacity. ++ \param brightness Brightness factor of the drawing (in [0,2]). ++ **/ ++ 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._depth>1 || texture._spectrum<_spectrum) ++ throw CImgArgumentException(_cimg_instance ++ "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", ++ cimg_instance, ++ texture._width,texture._height,texture._depth,texture._spectrum,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)std::min(cimg::type::max(),cimg::type::max()); ++ const float ++ nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f), ++ nbrightness = cimg::cut(brightness,0,2); ++ const ulongT ++ whd = (ulongT)_width*_height*_depth, ++ twh = (ulongT)texture._width*texture._height, ++ offx = _spectrum*whd - 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>=height() || 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>=width() - 1) xright = width() - 1; ++ T* ptrd = data(xleft,y,0,0); ++ if (opacity>=1) { ++ if (nbrightness==1) for (int x = xleft; x<=xright; ++x) { ++ const tc *col = &texture._atXY(txleft,tyleft); ++ cimg_forC(*this,c) { ++ *ptrd = (T)*col; ++ ptrd+=whd; col+=twh; ++ } ++ 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._atXY(txleft,tyleft); ++ cimg_forC(*this,c) { ++ *ptrd = (T)(nbrightness**col); ++ ptrd+=whd; col+=twh; ++ } ++ 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._atXY(txleft,tyleft); ++ cimg_forC(*this,c) { ++ *ptrd = (T)((2 - nbrightness)**(col++) + (nbrightness - 1)*maxval); ++ ptrd+=whd; col+=twh; ++ } ++ 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._atXY(txleft,tyleft); ++ cimg_forC(*this,c) { ++ *ptrd = (T)(nopacity**col + *ptrd*copacity); ++ ptrd+=whd; col+=twh; ++ } ++ 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._atXY(txleft,tyleft); ++ cimg_forC(*this,c) { ++ *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity); ++ ptrd+=whd; col+=twh; ++ } ++ 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._atXY(txleft,tyleft); ++ cimg_forC(*this,c) { ++ const T val = (T)((2 - nbrightness)**(col++) + (nbrightness - 1)*maxval); ++ *ptrd = (T)(nopacity*val + *ptrd*copacity); ++ ptrd+=whd; col+=twh; ++ } ++ 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._depth>1 || texture._spectrum<_spectrum) ++ throw CImgArgumentException(_cimg_instance ++ "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", ++ cimg_instance, ++ texture._width,texture._height,texture._depth,texture._spectrum,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)std::min(cimg::type::max(),(T)cimg::type::max()); ++ const float ++ nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f), ++ nbrightness = cimg::cut(brightness,0,2); ++ const ulongT ++ whd = (ulongT)_width*_height*_depth, ++ twh = (ulongT)texture._width*texture._height, ++ offx = _spectrum*whd - 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>=height() || 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=width() - 1) xright = width() - 1; ++ T* ptrd = data(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._atXY((int)(txleft*invz),(int)(tyleft*invz)); ++ cimg_forC(*this,c) { ++ *ptrd = (T)*col; ++ ptrd+=whd; col+=twh; ++ } ++ 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._atXY((int)(txleft*invz),(int)(tyleft*invz)); ++ cimg_forC(*this,c) { ++ *ptrd = (T)(nbrightness**col); ++ ptrd+=whd; col+=twh; ++ } ++ 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._atXY((int)(txleft*invz),(int)(tyleft*invz)); ++ cimg_forC(*this,c) { ++ *ptrd = (T)((2 - nbrightness)**col + (nbrightness - 1)*maxval); ++ ptrd+=whd; col+=twh; ++ } ++ 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._atXY((int)(txleft*invz),(int)(tyleft*invz)); ++ cimg_forC(*this,c) { ++ *ptrd = (T)(nopacity**col + *ptrd*copacity); ++ ptrd+=whd; col+=twh; ++ } ++ 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._atXY((int)(txleft*invz),(int)(tyleft*invz)); ++ cimg_forC(*this,c) { ++ *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity); ++ ptrd+=whd; col+=twh; ++ } ++ 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._atXY((int)(txleft*invz),(int)(tyleft*invz)); ++ cimg_forC(*this,c) { ++ const T val = (T)((2 - nbrightness)**col + (nbrightness - 1)*maxval); ++ *ptrd = (T)(nopacity*val + *ptrd*copacity); ++ ptrd+=whd; col+=twh; ++ } ++ ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety; ++ } ++ } ++ zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; ++ } ++ return *this; ++ } ++ ++ //! Draw a textured 2d triangle, with perspective correction and z-buffering. ++ template ++ CImg& draw_triangle(CImg& 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) { ++ typedef typename cimg::superset::type tzfloat; ++ if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; ++ if (!is_sameXY(zbuffer)) ++ throw CImgArgumentException(_cimg_instance ++ "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " ++ "different dimensions.", ++ cimg_instance, ++ zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); ++ ++ if (texture._depth>1 || texture._spectrum<_spectrum) ++ throw CImgArgumentException(_cimg_instance ++ "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", ++ cimg_instance, ++ texture._width,texture._height,texture._depth,texture._spectrum,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)std::min(cimg::type::max(),(T)cimg::type::max()); ++ const float ++ nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f), ++ nbrightness = cimg::cut(brightness,0,2); ++ const ulongT ++ whd = (ulongT)_width*_height*_depth, ++ twh = (ulongT)texture._width*texture._height, ++ offx = _spectrum*whd; ++ 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; ++ tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)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>=height() || 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), ++ txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)), ++ tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)), ++ 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))); ++ tzfloat ++ 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; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; } ++ int xleft = xleft0, xright = xright0; ++ float txleft = txl, txright = txr, tyleft = tyl, tyright = tyr; ++ tzfloat zleft = zl, zright = zr; ++ if (xright=width() - 1) xright = width() - 1; ++ T *ptrd = data(xleft,y,0,0); ++ tz *ptrz = zbuffer.data(xleft,y); ++ if (opacity>=1) { ++ if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { ++ if (zleft>=(tzfloat)*ptrz) { ++ *ptrz = (tz)zleft; ++ const tzfloat invz = 1/zleft; ++ const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); ++ cimg_forC(*this,c) { ++ *ptrd = (T)*col; ++ ptrd+=whd; col+=twh; ++ } ++ ptrd-=offx; ++ } ++ zleft+=pentez; txleft+=pentetx; tyleft+=pentety; ++ } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { ++ if (zleft>=(tzfloat)*ptrz) { ++ *ptrz = (tz)zleft; ++ const tzfloat invz = 1/zleft; ++ const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); ++ cimg_forC(*this,c) { ++ *ptrd = (T)(nbrightness**col); ++ ptrd+=whd; col+=twh; ++ } ++ ptrd-=offx; ++ } ++ zleft+=pentez; txleft+=pentetx; tyleft+=pentety; ++ } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { ++ if (zleft>=(tzfloat)*ptrz) { ++ *ptrz = (tz)zleft; ++ const tzfloat invz = 1/zleft; ++ const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); ++ cimg_forC(*this,c) { ++ *ptrd = (T)((2 - nbrightness)**col + (nbrightness - 1)*maxval); ++ ptrd+=whd; col+=twh; ++ } ++ ptrd-=offx; ++ } ++ zleft+=pentez; txleft+=pentetx; tyleft+=pentety; ++ } ++ } else { ++ if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { ++ if (zleft>=(tzfloat)*ptrz) { ++ *ptrz = (tz)zleft; ++ const tzfloat invz = 1/zleft; ++ const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); ++ cimg_forC(*this,c) { ++ *ptrd = (T)(nopacity**col + *ptrd*copacity); ++ ptrd+=whd; col+=twh; ++ } ++ ptrd-=offx; ++ } ++ zleft+=pentez; txleft+=pentetx; tyleft+=pentety; ++ } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { ++ if (zleft>=(tzfloat)*ptrz) { ++ *ptrz = (tz)zleft; ++ const tzfloat invz = 1/zleft; ++ const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); ++ cimg_forC(*this,c) { ++ *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity); ++ ptrd+=whd; col+=twh; ++ } ++ ptrd-=offx; ++ } ++ zleft+=pentez; txleft+=pentetx; tyleft+=pentety; ++ } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { ++ if (zleft>=(tzfloat)*ptrz) { ++ *ptrz = (tz)zleft; ++ const tzfloat invz = 1/zleft; ++ const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); ++ cimg_forC(*this,c) { ++ const T val = (T)((2 - nbrightness)**col + (nbrightness - 1)*maxval); ++ *ptrd = (T)(nopacity*val + *ptrd*copacity); ++ ptrd+=whd; col+=twh; ++ } ++ ptrd-=offx; ++ } ++ zleft+=pentez; txleft+=pentetx; tyleft+=pentety; ++ } ++ } ++ zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; ++ } ++ return *this; ++ } ++ ++ //! Draw a Phong-shaded 2d triangle. ++ /** ++ \param x0 X-coordinate of the first vertex in the image instance. ++ \param y0 Y-coordinate of the first vertex in the image instance. ++ \param x1 X-coordinate of the second vertex in the image instance. ++ \param y1 Y-coordinate of the second vertex in the image instance. ++ \param x2 X-coordinate of the third vertex in the image instance. ++ \param y2 Y-coordinate of the third vertex in the image instance. ++ \param color Pointer to \c spectrum() consecutive values, defining the drawing color. ++ \param light Light image. ++ \param lx0 X-coordinate of the first vertex in the light image. ++ \param ly0 Y-coordinate of the first vertex in the light image. ++ \param lx1 X-coordinate of the second vertex in the light image. ++ \param ly1 Y-coordinate of the second vertex in the light image. ++ \param lx2 X-coordinate of the third vertex in the light image. ++ \param ly2 Y-coordinate of the third vertex in the light image. ++ \param opacity Drawing opacity. ++ **/ ++ 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_instance ++ "draw_triangle(): Specified color is (null).", ++ cimg_instance); ++ if (light._depth>1 || light._spectrum<_spectrum) ++ throw CImgArgumentException(_cimg_instance ++ "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).", ++ cimg_instance,light._width,light._height,light._depth,light._spectrum,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)std::min(cimg::type::max(),(T)cimg::type::max()); ++ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f); ++ 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 ulongT ++ whd = (ulongT)_width*_height*_depth, ++ lwh = (ulongT)light._width*light._height, ++ offx = _spectrum*whd - 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>=height() || 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>=width() - 1) xright = width() - 1; ++ T* ptrd = data(xleft,y,0,0); ++ if (opacity>=1) for (int x = xleft; x<=xright; ++x) { ++ const tc *col = color; ++ const tl *lig = &light._atXY(lxleft,lyleft); ++ cimg_forC(*this,c) { ++ const tl l = *lig; ++ *ptrd = (T)(l<1?l**(col++):((2 - l)**(col++) + (l - 1)*maxval)); ++ ptrd+=whd; lig+=lwh; ++ } ++ 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 tc *col = color; ++ const tl *lig = &light._atXY(lxleft,lyleft); ++ cimg_forC(*this,c) { ++ const tl l = *lig; ++ const T val = (T)(l<1?l**(col++):((2 - l)**(col++) + (l - 1)*maxval)); ++ *ptrd = (T)(nopacity*val + *ptrd*copacity); ++ ptrd+=whd; lig+=lwh; ++ } ++ ptrd-=offx; ++ lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0); ++ lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0); ++ } ++ } ++ return *this; ++ } ++ ++ //! Draw a Phong-shaded 2d triangle, with z-buffering. ++ template ++ CImg& draw_triangle(CImg& 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) { ++ typedef typename cimg::superset::type tzfloat; ++ if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; ++ if (!color) ++ throw CImgArgumentException(_cimg_instance ++ "draw_triangle(): Specified color is (null).", ++ cimg_instance); ++ if (light._depth>1 || light._spectrum<_spectrum) ++ throw CImgArgumentException(_cimg_instance ++ "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).", ++ cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data); ++ if (!is_sameXY(zbuffer)) ++ throw CImgArgumentException(_cimg_instance ++ "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " ++ "different dimensions.", ++ cimg_instance, ++ zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._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)std::min(cimg::type::max(),(T)cimg::type::max()); ++ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f); ++ const ulongT ++ whd = (ulongT)_width*_height*_depth, ++ lwh = (ulongT)light._width*light._height, ++ offx = _spectrum*whd; ++ 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; ++ tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)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>=height() || ny2<0) return *this; ++ tzfloat ++ 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; ++ tzfloat 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 tzfloat 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>=width() - 1) xright = width() - 1; ++ T *ptrd = data(xleft,y,0,0); ++ tz *ptrz = xleft<=xright?zbuffer.data(xleft,y):0; ++ if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { ++ if (zleft>=(tzfloat)*ptrz) { ++ *ptrz = (tz)zleft; ++ const tc *col = color; ++ const tl *lig = &light._atXY(lxleft,lyleft); ++ cimg_forC(*this,c) { ++ const tl l = *lig; ++ const tc cval = *(col++); ++ *ptrd = (T)(l<1?l*cval:(2 - l)*cval + (l - 1)*maxval); ++ ptrd+=whd; lig+=lwh; ++ } ++ 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>=(tzfloat)*ptrz) { ++ *ptrz = (tz)zleft; ++ const tc *col = color; ++ const tl *lig = &light._atXY(lxleft,lyleft); ++ cimg_forC(*this,c) { ++ const tl l = *lig; ++ 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+=whd; lig+=lwh; ++ } ++ 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 textured Gouraud-shaded 2d triangle. ++ /** ++ \param x0 X-coordinate of the first vertex in the image instance. ++ \param y0 Y-coordinate of the first vertex in the image instance. ++ \param x1 X-coordinate of the second vertex in the image instance. ++ \param y1 Y-coordinate of the second vertex in the image instance. ++ \param x2 X-coordinate of the third vertex in the image instance. ++ \param y2 Y-coordinate of the third vertex in the image instance. ++ \param texture Texture image used to fill the triangle. ++ \param tx0 X-coordinate of the first vertex in the texture image. ++ \param ty0 Y-coordinate of the first vertex in the texture image. ++ \param tx1 X-coordinate of the second vertex in the texture image. ++ \param ty1 Y-coordinate of the second vertex in the texture image. ++ \param tx2 X-coordinate of the third vertex in the texture image. ++ \param ty2 Y-coordinate of the third vertex in the texture image. ++ \param brightness0 Brightness factor of the first vertex. ++ \param brightness1 Brightness factor of the second vertex. ++ \param brightness2 Brightness factor of the third vertex. ++ \param opacity Drawing opacity. ++ **/ ++ 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._depth>1 || texture._spectrum<_spectrum) ++ throw CImgArgumentException(_cimg_instance ++ "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", ++ cimg_instance, ++ texture._width,texture._height,texture._depth,texture._spectrum,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)std::min(cimg::type::max(),cimg::type::max()); ++ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f); ++ const ulongT ++ whd = (ulongT)_width*_height*_depth, ++ twh = (ulongT)texture._width*texture._height, ++ offx = _spectrum*whd - 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.0f?0.0f:(brightness0>2.0f?2.0f:brightness0))*256.0f), ++ nc1 = (int)((brightness1<0.0f?0.0f:(brightness1>2.0f?2.0f:brightness1))*256.0f), ++ nc2 = (int)((brightness2<0.0f?0.0f:(brightness2>2.0f?2.0f:brightness2))*256.0f); ++ 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>=height() || 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>=width() - 1) xright = width() - 1; ++ T* ptrd = data(xleft,y,0,0); ++ if (opacity>=1) for (int x = xleft; x<=xright; ++x) { ++ const tc *col = &texture._atXY(txleft,tyleft); ++ cimg_forC(*this,c) { ++ *ptrd = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); ++ ptrd+=whd; col+=twh; ++ } ++ 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._atXY(txleft,tyleft); ++ cimg_forC(*this,c) { ++ const T val = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); ++ *ptrd = (T)(nopacity*val + *ptrd*copacity); ++ ptrd+=whd; col+=twh; ++ } ++ 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 textured Gouraud-shaded 2d triangle, with perspective correction \overloading. ++ 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._depth>1 || texture._spectrum<_spectrum) ++ throw CImgArgumentException(_cimg_instance ++ "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", ++ cimg_instance, ++ texture._width,texture._height,texture._depth,texture._spectrum,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)std::min(cimg::type::max(),(T)cimg::type::max()); ++ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f); ++ const ulongT ++ whd = (ulongT)_width*_height*_depth, ++ twh = (ulongT)texture._width*texture._height, ++ offx = _spectrum*whd - 1; ++ int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, ++ nc0 = (int)((brightness0<0.0f?0.0f:(brightness0>2.0f?2.0f:brightness0))*256.0f), ++ nc1 = (int)((brightness1<0.0f?0.0f:(brightness1>2.0f?2.0f:brightness1))*256.0f), ++ nc2 = (int)((brightness2<0.0f?0.0f:(brightness2>2.0f?2.0f:brightness2))*256.0f); ++ 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>=height() || 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>=width() - 1) xright = width() - 1; ++ T* ptrd = data(xleft,y,0,0); ++ if (opacity>=1) for (int x = xleft; x<=xright; ++x) { ++ const float invz = 1/zleft; ++ const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); ++ cimg_forC(*this,c) { ++ *ptrd = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); ++ ptrd+=whd; col+=twh; ++ } ++ 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._atXY((int)(txleft*invz),(int)(tyleft*invz)); ++ cimg_forC(*this,c) { ++ const T val = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); ++ *ptrd = (T)(nopacity*val + *ptrd*copacity); ++ ptrd+=whd; col+=twh; ++ } ++ 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 textured Gouraud-shaded 2d triangle, with perspective correction and z-buffering \overloading. ++ template ++ CImg& draw_triangle(CImg& 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) { ++ typedef typename cimg::superset::type tzfloat; ++ if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; ++ if (!is_sameXY(zbuffer)) ++ throw CImgArgumentException(_cimg_instance ++ "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " ++ "different dimensions.", ++ cimg_instance, ++ zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); ++ if (texture._depth>1 || texture._spectrum<_spectrum) ++ throw CImgArgumentException(_cimg_instance ++ "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", ++ cimg_instance, ++ texture._width,texture._height,texture._depth,texture._spectrum,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)std::min(cimg::type::max(),(T)cimg::type::max()); ++ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f); ++ const ulongT ++ whd = (ulongT)_width*_height*_depth, ++ twh = (ulongT)texture._width*texture._height, ++ offx = _spectrum*whd; ++ int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2, ++ nc0 = (int)((brightness0<0.0f?0.0f:(brightness0>2.0f?2.0f:brightness0))*256.0f), ++ nc1 = (int)((brightness1<0.0f?0.0f:(brightness1>2.0f?2.0f:brightness1))*256.0f), ++ nc2 = (int)((brightness2<0.0f?0.0f:(brightness2>2.0f?2.0f:brightness2))*256.0f); ++ float ++ ntx0 = tx0/z0, nty0 = ty0/z0, ++ ntx1 = tx1/z1, nty1 = ty1/z1, ++ ntx2 = tx2/z2, nty2 = ty2/z2; ++ tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)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>=height() || 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), ++ txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)), ++ tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)), ++ 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))); ++ tzfloat ++ 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; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; } ++ int xleft = xleft0, xright = xright0, cleft = cleft0, cright = cright0; ++ float txleft = txl, txright = txr, tyleft = tyl, tyright = tyr; ++ tzfloat 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); ++ float pentetx = (txright - txleft)/dx, pentety = (tyright - tyleft)/dx; ++ const tzfloat pentez = (zright - zleft)/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>=width() - 1) xright = width() - 1; ++ T* ptrd = data(xleft,y); ++ tz *ptrz = zbuffer.data(xleft,y); ++ if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) { ++ if (zleft>=(tzfloat)*ptrz) { ++ *ptrz = (tz)zleft; ++ const tzfloat invz = 1/zleft; ++ const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); ++ cimg_forC(*this,c) { ++ *ptrd = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); ++ ptrd+=whd; col+=twh; ++ } ++ 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>=(tzfloat)*ptrz) { ++ *ptrz = (tz)zleft; ++ const tzfloat invz = 1/zleft; ++ const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); ++ cimg_forC(*this,c) { ++ const T val = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); ++ *ptrd = (T)(nopacity*val + *ptrd*copacity); ++ ptrd+=whd; col+=twh; ++ } ++ 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 textured Phong-shaded 2d triangle. ++ /** ++ \param x0 X-coordinate of the first vertex in the image instance. ++ \param y0 Y-coordinate of the first vertex in the image instance. ++ \param x1 X-coordinate of the second vertex in the image instance. ++ \param y1 Y-coordinate of the second vertex in the image instance. ++ \param x2 X-coordinate of the third vertex in the image instance. ++ \param y2 Y-coordinate of the third vertex in the image instance. ++ \param texture Texture image used to fill the triangle. ++ \param tx0 X-coordinate of the first vertex in the texture image. ++ \param ty0 Y-coordinate of the first vertex in the texture image. ++ \param tx1 X-coordinate of the second vertex in the texture image. ++ \param ty1 Y-coordinate of the second vertex in the texture image. ++ \param tx2 X-coordinate of the third vertex in the texture image. ++ \param ty2 Y-coordinate of the third vertex in the texture image. ++ \param light Light image. ++ \param lx0 X-coordinate of the first vertex in the light image. ++ \param ly0 Y-coordinate of the first vertex in the light image. ++ \param lx1 X-coordinate of the second vertex in the light image. ++ \param ly1 Y-coordinate of the second vertex in the light image. ++ \param lx2 X-coordinate of the third vertex in the light image. ++ \param ly2 Y-coordinate of the third vertex in the light image. ++ \param opacity Drawing opacity. ++ **/ ++ 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._depth>1 || texture._spectrum<_spectrum) ++ throw CImgArgumentException(_cimg_instance ++ "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", ++ cimg_instance, ++ texture._width,texture._height,texture._depth,texture._spectrum,texture._data); ++ if (light._depth>1 || light._spectrum<_spectrum) ++ throw CImgArgumentException(_cimg_instance ++ "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).", ++ cimg_instance,light._width,light._height,light._depth,light._spectrum,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)std::min(cimg::type::max(),(T)cimg::type::max()); ++ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f); ++ const ulongT ++ whd = (ulongT)_width*_height*_depth, ++ twh = (ulongT)texture._width*texture._height, ++ lwh = (ulongT)light._width*light._height, ++ offx = _spectrum*whd - 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>=height() || ny2<0) return *this; ++ const bool is_bump = texture._spectrum>=_spectrum + 2; ++ const ulongT obx = twh*_spectrum, oby = twh*(_spectrum + 1); ++ ++ _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>=width() - 1) xright = width() - 1; ++ T* ptrd = data(xleft,y,0,0); ++ if (opacity>=1) for (int x = xleft; x<=xright; ++x) { ++ const tc *col = &texture._atXY(txleft,tyleft); ++ const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0; ++ const tl *lig = &light._atXY(lxleft + bx,lyleft + by); ++ cimg_forC(*this,c) { ++ const tl l = *lig; ++ *ptrd = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); ++ ptrd+=whd; col+=twh; lig+=lwh; ++ } ++ 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 tc *col = &texture._atXY(txleft,tyleft); ++ const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0; ++ const tl *lig = &light._atXY(lxleft + bx,lyleft + by); ++ cimg_forC(*this,c) { ++ const tl l = *lig; ++ const T val = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); ++ *ptrd = (T)(nopacity*val + *ptrd*copacity); ++ ptrd+=whd; col+=twh; lig+=lwh; ++ } ++ 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 textured Phong-shaded 2d 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._depth>1 || texture._spectrum<_spectrum) ++ throw CImgArgumentException(_cimg_instance ++ "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", ++ cimg_instance, ++ texture._width,texture._height,texture._depth,texture._spectrum,texture._data); ++ if (light._depth>1 || light._spectrum<_spectrum) ++ throw CImgArgumentException(_cimg_instance ++ "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).", ++ cimg_instance,light._width,light._height,light._depth,light._spectrum,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)std::min(cimg::type::max(),(T)cimg::type::max()); ++ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f); ++ const ulongT ++ whd = (ulongT)_width*_height*_depth, ++ twh = (ulongT)texture._width*texture._height, ++ lwh = (ulongT)light._width*light._height, ++ offx = _spectrum*whd - 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>=height() || 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))); ++ const bool is_bump = texture._spectrum>=_spectrum + 2; ++ const ulongT obx = twh*_spectrum, oby = twh*(_spectrum + 1); ++ ++ _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>=width() - 1) xright = width() - 1; ++ T* ptrd = data(xleft,y,0,0); ++ if (opacity>=1) for (int x = xleft; x<=xright; ++x) { ++ const float invz = 1/zleft; ++ const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); ++ const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0; ++ const tl *lig = &light._atXY(lxleft + bx,lyleft + by); ++ cimg_forC(*this,c) { ++ const tl l = *lig; ++ *ptrd = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); ++ ptrd+=whd; col+=twh; lig+=lwh; ++ } ++ 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 tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); ++ const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0; ++ const tl *lig = &light._atXY(lxleft + bx,lyleft + by); ++ cimg_forC(*this,c) { ++ const tl l = *lig; ++ const T val = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); ++ *ptrd = (T)(nopacity*val + *ptrd*copacity); ++ ptrd+=whd; col+=twh; lig+=lwh; ++ } ++ 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 textured Phong-shaded 2d triangle, with perspective correction and z-buffering. ++ template ++ CImg& draw_triangle(CImg& 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) { ++ typedef typename cimg::superset::type tzfloat; ++ if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; ++ if (!is_sameXY(zbuffer)) ++ throw CImgArgumentException(_cimg_instance ++ "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " ++ "different dimensions.", ++ cimg_instance, ++ zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); ++ if (texture._depth>1 || texture._spectrum<_spectrum) ++ throw CImgArgumentException(_cimg_instance ++ "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).", ++ cimg_instance, ++ texture._width,texture._height,texture._depth,texture._spectrum,texture._data); ++ if (light._depth>1 || light._spectrum<_spectrum) ++ throw CImgArgumentException(_cimg_instance ++ "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).", ++ cimg_instance,light._width,light._height,light._depth,light._spectrum,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)std::min(cimg::type::max(),(T)cimg::type::max()); ++ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f); ++ const ulongT ++ whd = (ulongT)_width*_height*_depth, ++ twh = (ulongT)texture._width*texture._height, ++ lwh = (ulongT)light._width*light._height, ++ offx = _spectrum*whd; ++ 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; ++ tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)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>=height() || 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), ++ txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)), ++ tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)), ++ 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))); ++ tzfloat ++ 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))); ++ const bool is_bump = texture._spectrum>=_spectrum + 2; ++ const ulongT obx = twh*_spectrum, oby = twh*(_spectrum + 1); ++ ++ _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 txleft = txl, txright = txr, tyleft = tyl, tyright = tyr; ++ tzfloat 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); ++ float pentetx = (txright - txleft)/dx, pentety = (tyright - tyleft)/dx; ++ const tzfloat 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; ++ txleft-=xleft*(txright - txleft)/dx; ++ tyleft-=xleft*(tyright - tyleft)/dx; ++ } ++ if (xleft<0) xleft = 0; ++ if (xright>=width() - 1) xright = width() - 1; ++ T* ptrd = data(xleft,y); ++ tz *ptrz = zbuffer.data(xleft,y); ++ if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) { ++ if (zleft>=(tzfloat)*ptrz) { ++ *ptrz = (tz)zleft; ++ const tzfloat invz = 1/zleft; ++ const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); ++ const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0; ++ const tl *lig = &light._atXY(lxleft + bx,lyleft + by); ++ cimg_forC(*this,c) { ++ const tl l = *lig; ++ *ptrd = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); ++ ptrd+=whd; col+=twh; lig+=lwh; ++ } ++ 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>=(tzfloat)*ptrz) { ++ *ptrz = (tz)zleft; ++ const tzfloat invz = 1/zleft; ++ const tc *col = &texture._atXY((int)(txleft*invz),(int)(tyleft*invz)); ++ const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0; ++ const tl *lig = &light._atXY(lxleft + bx,lyleft + by); ++ cimg_forC(*this,c) { ++ const tl l = *lig; ++ const T val = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval); ++ *ptrd = (T)(nopacity*val + *ptrd*copacity); ++ ptrd+=whd; col+=twh; lig+=lwh; ++ } ++ 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 filled 4d rectangle. ++ /** ++ \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 c0 C-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 c1 C-coordinate of the lower-right rectangle corner. ++ \param val Scalar value used to fill the rectangle area. ++ \param opacity Drawing opacity. ++ **/ ++ CImg& draw_rectangle(const int x0, const int y0, const int z0, const int c0, ++ const int x1, const int y1, const int z1, const int c1, ++ const T val, const float opacity=1) { ++ if (is_empty()) return *this; ++ const int ++ nx0 = x0=width()?width() - 1 - nx1:0) + (nx0<0?nx0:0), ++ lY = (1 + ny1 - ny0) + (ny1>=height()?height() - 1 - ny1:0) + (ny0<0?ny0:0), ++ lZ = (1 + nz1 - nz0) + (nz1>=depth()?depth() - 1 - nz1:0) + (nz0<0?nz0:0), ++ lC = (1 + nc1 - nc0) + (nc1>=spectrum()?spectrum() - 1 - nc1:0) + (nc0<0?nc0:0); ++ const ulongT ++ offX = (ulongT)_width - lX, ++ offY = (ulongT)_width*(_height - lY), ++ offZ = (ulongT)_width*_height*(_depth - lZ); ++ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f); ++ T *ptrd = data(nx0<0?0:nx0,ny0<0?0:ny0,nz0<0?0:nz0,nc0<0?0:nc0); ++ if (lX>0 && lY>0 && lZ>0 && lC>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 (is_empty()) return *this; ++ if (!color) ++ throw CImgArgumentException(_cimg_instance ++ "draw_rectangle(): Specified color is (null).", ++ cimg_instance); ++ cimg_forC(*this,c) draw_rectangle(x0,y0,z0,c,x1,y1,z1,c,(T)color[c],opacity); ++ return *this; ++ } ++ ++ //! Draw an outlined 3d rectangle \overloading. ++ 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 filled 2d rectangle. ++ /** ++ \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 spectrum() consecutive values of type \c T, defining the drawing color. ++ \param opacity Drawing opacity. ++ **/ ++ 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 outlined 2d rectangle \overloading. ++ 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 int ++ nx0 = x0 ++ CImg& draw_polygon(const CImg& points, ++ const tc *const color, const float opacity=1) { ++ if (is_empty() || !points) return *this; ++ if (!color) ++ throw CImgArgumentException(_cimg_instance ++ "draw_polygon(): Specified color is (null).", ++ cimg_instance); ++ if (points._width==1) return draw_point((int)points(0,0),(int)points(0,1),color,opacity); ++ if (points._width==2) return draw_line((int)points(0,0),(int)points(0,1), ++ (int)points(1,0),(int)points(1,1),color,opacity); ++ if (points._width==3) return draw_triangle((int)points(0,0),(int)points(0,1), ++ (int)points(1,0),(int)points(1,1), ++ (int)points(2,0),(int)points(2,1),color,opacity); ++ cimg_init_scanline(color,opacity); ++ int ++ xmin = 0, ymin = 0, ++ xmax = points.get_shared_row(0).max_min(xmin), ++ ymax = points.get_shared_row(1).max_min(ymin); ++ if (xmax<0 || xmin>=width() || ymax<0 || ymin>=height()) return *this; ++ if (ymin==ymax) return draw_line(xmin,ymin,xmax,ymax,color,opacity); ++ ++ ymin = std::max(0,ymin); ++ ymax = std::min(height() - 1,ymax); ++ CImg Xs(points._width,ymax - ymin + 1); ++ CImg count(Xs._height,1,1,1,0); ++ unsigned int n = 0, nn = 1; ++ bool go_on = true; ++ ++ while (go_on) { ++ unsigned int an = (nn + 1)%points._width; ++ const int ++ x0 = (int)points(n,0), ++ y0 = (int)points(n,1); ++ if (points(nn,1)==y0) while (points(an,1)==y0) { nn = an; (an+=1)%=points._width; } ++ const int ++ x1 = (int)points(nn,0), ++ y1 = (int)points(nn,1); ++ unsigned int tn = an; ++ while (points(tn,1)==y1) (tn+=1)%=points._width; ++ ++ if (y0!=y1) { ++ const int ++ y2 = (int)points(tn,1), ++ x01 = x1 - x0, y01 = y1 - y0, y12 = y2 - y1, ++ dy = cimg::sign(y01), ++ tmax = std::max(1,cimg::abs(y01)), ++ tend = tmax - (dy==cimg::sign(y12)); ++ unsigned int y = (unsigned int)y0 - ymin; ++ for (int t = 0; t<=tend; ++t, y+=dy) ++ if (yn; ++ n = nn; ++ nn = an; ++ } ++ ++ cimg_pragma_openmp(parallel for cimg_openmp_if(Xs._height>32)) ++ cimg_forY(Xs,y) { ++ const CImg Xsy = Xs.get_shared_points(0,count[y] - 1,y).sort(); ++ int px = width(); ++ for (unsigned int n = 0; n ++ CImg& draw_polygon(const CImg& points, ++ const tc *const color, const float opacity, const unsigned int pattern) { ++ if (is_empty() || !points || points._width<3) return *this; ++ bool ninit_hatch = true; ++ switch (points._height) { ++ case 0 : case 1 : ++ throw CImgArgumentException(_cimg_instance ++ "draw_polygon(): Invalid specified point set.", ++ cimg_instance); ++ case 2 : { // 2d version. ++ CImg npoints(points._width,2); ++ int x = npoints(0,0) = (int)points(0,0), y = npoints(0,1) = (int)points(0,1); ++ unsigned int nb_points = 1; ++ for (unsigned int p = 1; p npoints(points._width,3); ++ int ++ x = npoints(0,0) = (int)points(0,0), ++ y = npoints(0,1) = (int)points(0,1), ++ z = npoints(0,2) = (int)points(0,2); ++ unsigned int nb_points = 1; ++ for (unsigned int p = 1; p ++ CImg& draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float angle, ++ const tc *const color, const float opacity=1) { ++ return _draw_ellipse(x0,y0,r1,r2,angle,color,opacity,0U); ++ } ++ ++ //! Draw a filled 2d ellipse \overloading. ++ /** ++ \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 Pointer to \c spectrum() consecutive values, defining the drawing color. ++ \param opacity Drawing opacity. ++ **/ ++ 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,std::sqrt(val(0)),std::sqrt(val(1)), ++ std::atan2(vec(0,1),vec(0,0))*180/cimg::PI, ++ color,opacity); ++ } ++ ++ //! Draw an outlined 2d 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 angle Angle of the first radius. ++ \param color Pointer to \c spectrum() consecutive values, defining the drawing color. ++ \param opacity Drawing opacity. ++ \param pattern An integer whose bits describe the outline pattern. ++ **/ ++ template ++ CImg& draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float angle, ++ const tc *const color, const float opacity, const unsigned int pattern) { ++ if (pattern) _draw_ellipse(x0,y0,r1,r2,angle,color,opacity,pattern); ++ return *this; ++ } ++ ++ //! Draw an outlined 2d ellipse \overloading. ++ /** ++ \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 Pointer to \c spectrum() consecutive values, defining the drawing color. ++ \param opacity Drawing opacity. ++ \param pattern An integer whose bits describe the outline pattern. ++ **/ ++ 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,std::sqrt(val(0)),std::sqrt(val(1)), ++ std::atan2(vec(0,1),vec(0,0))*180/cimg::PI, ++ color,opacity,pattern); ++ } ++ ++ template ++ CImg& _draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float angle, ++ const tc *const color, const float opacity, ++ const unsigned int pattern) { ++ if (is_empty()) return *this; ++ if (!color) ++ throw CImgArgumentException(_cimg_instance ++ "draw_ellipse(): Specified color is (null).", ++ cimg_instance); ++ if (r1<=0 || r2<=0) return draw_point(x0,y0,color,opacity); ++ if (r1==r2 && (float)(int)r1==r1) { ++ if (pattern) return draw_circle(x0,y0,r1,color,opacity,pattern); ++ else return draw_circle(x0,y0,r1,color,opacity); ++ } ++ cimg_init_scanline(color,opacity); ++ const float ++ nr1 = cimg::abs(r1) - 0.5, nr2 = cimg::abs(r2) - 0.5, ++ nangle = (float)(angle*cimg::PI/180), ++ u = (float)std::cos(nangle), ++ v = (float)std::sin(nangle), ++ rmax = std::max(nr1,nr2), ++ l1 = (float)std::pow(rmax/(nr1>0?nr1:1e-6),2), ++ l2 = (float)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)std::sqrt(a*rmax*rmax/(a*c - b*b)), ++ tymin = y0 - yb - 1, ++ tymax = y0 + yb + 1, ++ ymin = tymin<0?0:tymin, ++ ymax = tymax>=height()?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)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)cimg::round(fxmin), xmax = (int)cimg::round(fxmax); ++ if (!pattern) cimg_draw_scanline(xmin,xmax,y,color,opacity,1); ++ else { ++ if (first_line) { ++ if (y0 - yb>=0) cimg_draw_scanline(xmin,xmax,y,color,opacity,1); ++ else draw_point(xmin,y,color,opacity).draw_point(xmax,y,color,opacity); ++ first_line = false; ++ } else { ++ if (xmin ++ CImg& draw_circle(const int x0, const int y0, int radius, ++ const tc *const color, const float opacity=1) { ++ if (is_empty()) return *this; ++ if (!color) ++ throw CImgArgumentException(_cimg_instance ++ "draw_circle(): Specified color is (null).", ++ cimg_instance); ++ cimg_init_scanline(color,opacity); ++ if (radius<0 || x0 - radius>=width() || y0 + radius<0 || y0 - radius>=height()) 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 tc *const color, const float opacity, ++ const unsigned int pattern) { ++ cimg::unused(pattern); ++ if (is_empty()) return *this; ++ if (!color) ++ throw CImgArgumentException(_cimg_instance ++ "draw_circle(): Specified color is (null).", ++ cimg_instance); ++ if (radius<0 || x0 - radius>=width() || y0 + radius<0 || y0 - radius>=height()) 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 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 c0 C-coordinate of the sprite position. ++ \param opacity Drawing opacity. ++ **/ ++ template ++ CImg& draw_image(const int x0, const int y0, const int z0, const int c0, ++ const CImg& sprite, const float opacity=1) { ++ if (is_empty() || !sprite) return *this; ++ if (is_overlapped(sprite)) return draw_image(x0,y0,z0,c0,+sprite,opacity); ++ if (x0==0 && y0==0 && z0==0 && c0==0 && is_sameXYZC(sprite) && opacity>=1 && !is_shared()) ++ return assign(sprite,false); ++ const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bc = (c0<0); ++ const int ++ lX = sprite.width() - (x0 + sprite.width()>width()?x0 + sprite.width() - width():0) + (bx?x0:0), ++ lY = sprite.height() - (y0 + sprite.height()>height()?y0 + sprite.height() - height():0) + (by?y0:0), ++ lZ = sprite.depth() - (z0 + sprite.depth()>depth()?z0 + sprite.depth() - depth():0) + (bz?z0:0), ++ lC = sprite.spectrum() - (c0 + sprite.spectrum()>spectrum()?c0 + sprite.spectrum() - spectrum():0) + (bc?c0:0); ++ const t ++ *ptrs = sprite._data + ++ (bx?-x0:0) + ++ (by?-y0*(ulongT)sprite.width():0) + ++ (bz?-z0*(ulongT)sprite.width()*sprite.height():0) + ++ (bc?-c0*(ulongT)sprite.width()*sprite.height()*sprite.depth():0); ++ const ulongT ++ offX = (ulongT)_width - lX, ++ soffX = (ulongT)sprite._width - lX, ++ offY = (ulongT)_width*(_height - lY), ++ soffY = (ulongT)sprite._width*(sprite._height - lY), ++ offZ = (ulongT)_width*_height*(_depth - lZ), ++ soffZ = (ulongT)sprite._width*sprite._height*(sprite._depth - lZ); ++ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f); ++ if (lX>0 && lY>0 && lZ>0 && lC>0) { ++ T *ptrd = data(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,c0<0?0:c0); ++ for (int v = 0; v=1) for (int x = 0; x& draw_image(const int x0, const int y0, const int z0, const int c0, ++ const CImg& sprite, const float opacity=1) { ++ if (is_empty() || !sprite) return *this; ++ if (is_overlapped(sprite)) return draw_image(x0,y0,z0,c0,+sprite,opacity); ++ if (x0==0 && y0==0 && z0==0 && c0==0 && is_sameXYZC(sprite) && opacity>=1 && !is_shared()) ++ return assign(sprite,false); ++ const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bc = (c0<0); ++ const int ++ lX = sprite.width() - (x0 + sprite.width()>width()?x0 + sprite.width() - width():0) + (bx?x0:0), ++ lY = sprite.height() - (y0 + sprite.height()>height()?y0 + sprite.height() - height():0) + (by?y0:0), ++ lZ = sprite.depth() - (z0 + sprite.depth()>depth()?z0 + sprite.depth() - depth():0) + (bz?z0:0), ++ lC = sprite.spectrum() - (c0 + sprite.spectrum()>spectrum()?c0 + sprite.spectrum() - spectrum():0) + (bc?c0:0); ++ const T ++ *ptrs = sprite._data + ++ (bx?-x0:0) + ++ (by?-y0*(ulongT)sprite.width():0) + ++ (bz?-z0*(ulongT)sprite.width()*sprite.height():0) + ++ (bc?-c0*(ulongT)sprite.width()*sprite.height()*sprite.depth():0); ++ const ulongT ++ offX = (ulongT)_width - lX, ++ soffX = (ulongT)sprite._width - lX, ++ offY = (ulongT)_width*(_height - lY), ++ soffY = (ulongT)sprite._width*(sprite._height - lY), ++ offZ = (ulongT)_width*_height*(_depth - lZ), ++ soffZ = (ulongT)sprite._width*sprite._height*(sprite._depth - lZ), ++ slX = lX*sizeof(T); ++ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f); ++ if (lX>0 && lY>0 && lZ>0 && lC>0) { ++ T *ptrd = data(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,c0<0?0:c0); ++ 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 \overloading. ++ 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 \overloading. ++ template ++ CImg& draw_image(const int x0, ++ const CImg& sprite, const float opacity=1) { ++ return draw_image(x0,0,sprite,opacity); ++ } ++ ++ //! Draw an image \overloading. ++ template ++ CImg& draw_image(const CImg& sprite, const float opacity=1) { ++ return draw_image(0,sprite,opacity); ++ } ++ ++ //! Draw a masked image. ++ /** ++ \param sprite Sprite image. ++ \param mask Mask image. ++ \param x0 X-coordinate of the sprite position in the image instance. ++ \param y0 Y-coordinate of the sprite position in the image instance. ++ \param z0 Z-coordinate of the sprite position in the image instance. ++ \param c0 C-coordinate of the sprite position in the image instance. ++ \param mask_max_value Maximum pixel value of the mask image \c mask. ++ \param opacity Drawing opacity. ++ \note ++ - Pixel values of \c mask set the opacity of the corresponding pixels in \c sprite. ++ - 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 c0, ++ const CImg& sprite, const CImg& mask, const float opacity=1, ++ const float mask_max_value=1) { ++ if (is_empty() || !sprite || !mask) return *this; ++ if (is_overlapped(sprite)) return draw_image(x0,y0,z0,c0,+sprite,mask,opacity,mask_max_value); ++ if (is_overlapped(mask)) return draw_image(x0,y0,z0,c0,sprite,+mask,opacity,mask_max_value); ++ if (mask._width!=sprite._width || mask._height!=sprite._height || mask._depth!=sprite._depth) ++ throw CImgArgumentException(_cimg_instance ++ "draw_image(): Sprite (%u,%u,%u,%u,%p) and mask (%u,%u,%u,%u,%p) have " ++ "incompatible dimensions.", ++ cimg_instance, ++ sprite._width,sprite._height,sprite._depth,sprite._spectrum,sprite._data, ++ mask._width,mask._height,mask._depth,mask._spectrum,mask._data); ++ ++ const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bc = (c0<0); ++ const int ++ lX = sprite.width() - (x0 + sprite.width()>width()?x0 + sprite.width() - width():0) + (bx?x0:0), ++ lY = sprite.height() - (y0 + sprite.height()>height()?y0 + sprite.height() - height():0) + (by?y0:0), ++ lZ = sprite.depth() - (z0 + sprite.depth()>depth()?z0 + sprite.depth() - depth():0) + (bz?z0:0), ++ lC = sprite.spectrum() - (c0 + sprite.spectrum()>spectrum()?c0 + sprite.spectrum() - spectrum():0) + (bc?c0:0); ++ const ulongT ++ coff = (bx?-x0:0) + ++ (by?-y0*(ulongT)mask.width():0) + ++ (bz?-z0*(ulongT)mask.width()*mask.height():0) + ++ (bc?-c0*(ulongT)mask.width()*mask.height()*mask.depth():0), ++ ssize = (ulongT)mask.width()*mask.height()*mask.depth()*mask.spectrum(); ++ const ti *ptrs = sprite._data + coff; ++ const tm *ptrm = mask._data + coff; ++ const ulongT ++ offX = (ulongT)_width - lX, ++ soffX = (ulongT)sprite._width - lX, ++ offY = (ulongT)_width*(_height - lY), ++ soffY = (ulongT)sprite._width*(sprite._height - lY), ++ offZ = (ulongT)_width*_height*(_depth - lZ), ++ soffZ = (ulongT)sprite._width*sprite._height*(sprite._depth - lZ); ++ if (lX>0 && lY>0 && lZ>0 && lC>0) { ++ T *ptrd = data(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,c0<0?0:c0); ++ for (int c = 0; c ++ 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_max_value=1) { ++ return draw_image(x0,y0,z0,0,sprite,mask,opacity,mask_max_value); ++ } ++ ++ //! Draw a image \overloading. ++ template ++ CImg& draw_image(const int x0, const int y0, ++ const CImg& sprite, const CImg& mask, const float opacity=1, ++ const float mask_max_value=1) { ++ return draw_image(x0,y0,0,sprite,mask,opacity,mask_max_value); ++ } ++ ++ //! Draw a image \overloading. ++ template ++ CImg& draw_image(const int x0, ++ const CImg& sprite, const CImg& mask, const float opacity=1, ++ const float mask_max_value=1) { ++ return draw_image(x0,0,sprite,mask,opacity,mask_max_value); ++ } ++ ++ //! Draw an image. ++ template ++ CImg& draw_image(const CImg& sprite, const CImg& mask, const float opacity=1, ++ const float mask_max_value=1) { ++ return draw_image(0,sprite,mask,opacity,mask_max_value); ++ } ++ ++ //! Draw a text string. ++ /** ++ \param x0 X-coordinate of the text in the image instance. ++ \param y0 Y-coordinate of the text in the image instance. ++ \param text Format of the text ('printf'-style format string). ++ \param foreground_color Pointer to \c spectrum() consecutive values, defining the foreground drawing color. ++ \param background_color Pointer to \c spectrum() consecutive values, defining the background drawing color. ++ \param opacity Drawing opacity. ++ \param font Font used for drawing text. ++ **/ ++ 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 (!font) return *this; ++ CImg tmp(2048); ++ std::va_list ap; va_start(ap,font); ++ cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap); ++ return _draw_text(x0,y0,tmp,foreground_color,background_color,opacity,font,false); ++ } ++ ++ //! Draw a text string \overloading. ++ /** ++ \note A transparent background is used for the text. ++ **/ ++ template ++ CImg& draw_text(const int x0, const int y0, ++ const char *const text, ++ const tc *const foreground_color, const int, ++ const float opacity, const CImgList& font, ...) { ++ if (!font) return *this; ++ CImg tmp(2048); ++ std::va_list ap; va_start(ap,font); ++ cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap); ++ return _draw_text(x0,y0,tmp,foreground_color,(tc*)0,opacity,font,false); ++ } ++ ++ //! Draw a text string \overloading. ++ /** ++ \note A transparent foreground is used for the text. ++ **/ ++ template ++ CImg& draw_text(const int x0, const int y0, ++ const char *const text, ++ const int, const tc *const background_color, ++ const float opacity, const CImgList& font, ...) { ++ if (!font) return *this; ++ CImg tmp(2048); ++ std::va_list ap; va_start(ap,font); ++ cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap); ++ return _draw_text(x0,y0,tmp,(tc*)0,background_color,opacity,font,false); ++ } ++ ++ //! Draw a text string \overloading. ++ /** ++ \param x0 X-coordinate of the text in the image instance. ++ \param y0 Y-coordinate of the text in the image instance. ++ \param text Format of the text ('printf'-style format string). ++ \param foreground_color Array of spectrum() values of type \c T, ++ defining the foreground color (0 means 'transparent'). ++ \param background_color Array of spectrum() values of type \c T, ++ defining the background color (0 means 'transparent'). ++ \param opacity Drawing opacity. ++ \param font_height Height of the text font (exact match for 13,23,53,103, interpolated otherwise). ++ **/ ++ 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_height=13, ...) { ++ if (!font_height) return *this; ++ CImg tmp(2048); ++ std::va_list ap; va_start(ap,font_height); ++ cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap); ++ const CImgList& font = CImgList::font(font_height,true); ++ _draw_text(x0,y0,tmp,foreground_color,background_color,opacity,font,true); ++ return *this; ++ } ++ ++ //! Draw a text string \overloading. ++ 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_height=13, ...) { ++ if (!font_height) return *this; ++ cimg::unused(background_color); ++ CImg tmp(2048); ++ std::va_list ap; va_start(ap,font_height); ++ cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap); ++ return draw_text(x0,y0,"%s",foreground_color,(const tc*)0,opacity,font_height,tmp._data); ++ } ++ ++ //! Draw a text string \overloading. ++ template ++ CImg& draw_text(const int x0, const int y0, ++ const char *const text, ++ const int, const tc *const background_color, ++ const float opacity=1, const unsigned int font_height=13, ...) { ++ if (!font_height) return *this; ++ CImg tmp(2048); ++ std::va_list ap; va_start(ap,font_height); ++ cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap); ++ return draw_text(x0,y0,"%s",(tc*)0,background_color,opacity,font_height,tmp._data); ++ } ++ ++ 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, ++ const bool is_native_font) { ++ if (!text) return *this; ++ if (!font) ++ throw CImgArgumentException(_cimg_instance ++ "draw_text(): Empty specified font.", ++ cimg_instance); ++ ++ const unsigned int text_length = (unsigned int)std::strlen(text); ++ const bool _is_empty = is_empty(); ++ if (_is_empty) { ++ // If needed, pre-compute necessary size of the image ++ int x = 0, y = 0, w = 0; ++ unsigned char c = 0; ++ for (unsigned int i = 0; iw) w = x; x = 0; break; ++ case '\t' : x+=4*font[' ']._width; break; ++ default : if (cw) w=x; ++ y+=font[0]._height; ++ } ++ assign(x0 + w,y0 + y,1,is_native_font?1:font[0]._spectrum,(T)0); ++ } ++ ++ int x = x0, y = y0; ++ for (unsigned int i = 0; i letter = font[c]; ++ if (letter) { ++ if (is_native_font && _spectrum>letter._spectrum) letter.resize(-100,-100,1,_spectrum,0,2); ++ const unsigned int cmin = std::min(_spectrum,letter._spectrum); ++ if (foreground_color) ++ for (unsigned int c = 0; c ++ CImg& draw_quiver(const CImg& flow, ++ const t2 *const color, const float opacity=1, ++ const unsigned int sampling=25, const float factor=-20, ++ const bool is_arrow=true, const unsigned int pattern=~0U) { ++ return draw_quiver(flow,CImg(color,_spectrum,1,1,1,true),opacity,sampling,factor,is_arrow,pattern); ++ } ++ ++ //! Draw a 2d vector field, using a field of colors. ++ /** ++ \param flow Image of 2d vectors used as input data. ++ \param color Image of spectrum()-D vectors corresponding to the color of each arrow. ++ \param opacity Opacity of the drawing. ++ \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 is_arrow Tells if arrows must be drawn, instead of oriented segments. ++ \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 bool is_arrow=true, const unsigned int pattern=~0U) { ++ if (is_empty()) return *this; ++ if (!flow || flow._spectrum!=2) ++ throw CImgArgumentException(_cimg_instance ++ "draw_quiver(): Invalid dimensions of specified flow (%u,%u,%u,%u,%p).", ++ cimg_instance, ++ flow._width,flow._height,flow._depth,flow._spectrum,flow._data); ++ if (sampling<=0) ++ throw CImgArgumentException(_cimg_instance ++ "draw_quiver(): Invalid sampling value %g " ++ "(should be >0)", ++ cimg_instance, ++ sampling); ++ const bool colorfield = (color._width==flow._width && color._height==flow._height && ++ color._depth==1 && color._spectrum==_spectrum); ++ if (is_overlapped(flow)) return draw_quiver(+flow,color,opacity,sampling,factor,is_arrow,pattern); ++ float vmax,fact; ++ if (factor<=0) { ++ float m, M = (float)flow.get_norm(2).max_min(m); ++ vmax = (float)std::max(cimg::abs(m),cimg::abs(M)); ++ if (!vmax) vmax = 1; ++ fact = -factor; ++ } else { fact = factor; vmax = 1; } ++ ++ for (unsigned int y = sampling/2; y<_height; y+=sampling) ++ for (unsigned int x = sampling/2; x<_width; x+=sampling) { ++ const unsigned int X = x*flow._width/_width, Y = y*flow._height/_height; ++ float u = (float)flow(X,Y,0,0)*fact/vmax, v = (float)flow(X,Y,0,1)*fact/vmax; ++ if (is_arrow) { ++ const int xx = (int)(x + u), yy = (int)(y + v); ++ if (colorfield) draw_arrow(x,y,xx,yy,color.get_vector_at(X,Y)._data,opacity,45,sampling/5.0f,pattern); ++ else draw_arrow(x,y,xx,yy,color._data,opacity,45,sampling/5.0f,pattern); ++ } else { ++ if (colorfield) ++ draw_line((int)(x - 0.5*u),(int)(y - 0.5*v),(int)(x + 0.5*u),(int)(y + 0.5*v), ++ color.get_vector_at(X,Y)._data,opacity,pattern); ++ else draw_line((int)(x - 0.5*u),(int)(y - 0.5*v),(int)(x + 0.5*u),(int)(y + 0.5*v), ++ color._data,opacity,pattern); ++ } ++ } ++ return *this; ++ } ++ ++ //! Draw a labeled horizontal axis. ++ /** ++ \param values_x Values along the horizontal axis. ++ \param y Y-coordinate of the horizontal axis in the image instance. ++ \param color Pointer to \c spectrum() consecutive values, defining the drawing color. ++ \param opacity Drawing opacity. ++ \param pattern Drawing pattern. ++ \param font_height Height of the labels (exact match for 13,23,53,103, interpolated otherwise). ++ \param allow_zero Enable/disable the drawing of label '0' if found. ++ **/ ++ template ++ CImg& draw_axis(const CImg& values_x, const int y, ++ const tc *const color, const float opacity=1, ++ const unsigned int pattern=~0U, const unsigned int font_height=13, ++ const bool allow_zero=true) { ++ if (is_empty()) return *this; ++ const int yt = (y + 3 + font_height)<_height?y + 3:y - 2 - (int)font_height; ++ const int siz = (int)values_x.size() - 1; ++ CImg txt(32); ++ CImg label; ++ if (siz<=0) { // Degenerated case. ++ draw_line(0,y,_width - 1,y,color,opacity,pattern); ++ if (!siz) { ++ cimg_snprintf(txt,txt._width,"%g",(double)*values_x); ++ label.assign().draw_text(0,0,txt,color,(tc*)0,opacity,font_height); ++ const int ++ _xt = (width() - label.width())/2, ++ xt = _xt<3?3:_xt + label.width()>=width() - 2?width() - 3 - label.width():_xt; ++ draw_point(width()/2,y - 1,color,opacity).draw_point(width()/2,y + 1,color,opacity); ++ if (allow_zero || *txt!='0' || txt[1]!=0) ++ draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height); ++ } ++ } else { // Regular case. ++ if (values_x[0]=width() - 2?width() - 3 - label.width():_xt; ++ draw_point(xi,y - 1,color,opacity).draw_point(xi,y + 1,color,opacity); ++ if (allow_zero || *txt!='0' || txt[1]!=0) ++ draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height); ++ } ++ } ++ return *this; ++ } ++ ++ //! Draw a labeled vertical axis. ++ /** ++ \param x X-coordinate of the vertical axis in the image instance. ++ \param values_y Values along the Y-axis. ++ \param color Pointer to \c spectrum() consecutive values, defining the drawing color. ++ \param opacity Drawing opacity. ++ \param pattern Drawing pattern. ++ \param font_height Height of the labels (exact match for 13,23,53,103, interpolated otherwise). ++ \param allow_zero Enable/disable the drawing of label '0' if found. ++ **/ ++ template ++ CImg& draw_axis(const int x, const CImg& values_y, ++ const tc *const color, const float opacity=1, ++ const unsigned int pattern=~0U, const unsigned int font_height=13, ++ const bool allow_zero=true) { ++ if (is_empty()) return *this; ++ int siz = (int)values_y.size() - 1; ++ CImg txt(32); ++ CImg label; ++ if (siz<=0) { // Degenerated case. ++ draw_line(x,0,x,_height - 1,color,opacity,pattern); ++ if (!siz) { ++ cimg_snprintf(txt,txt._width,"%g",(double)*values_y); ++ label.assign().draw_text(0,0,txt,color,(tc*)0,opacity,font_height); ++ const int ++ _yt = (height() - label.height())/2, ++ yt = _yt<0?0:_yt + label.height()>=height()?height() - 1-label.height():_yt, ++ _xt = x - 2 - label.width(), ++ xt = _xt>=0?_xt:x + 3; ++ draw_point(x - 1,height()/2,color,opacity).draw_point(x + 1,height()/2,color,opacity); ++ if (allow_zero || *txt!='0' || txt[1]!=0) ++ draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height); ++ } ++ } else { // Regular case. ++ if (values_y[0]=height()?height() - 1-label.height():_yt, ++ _xt = x - 2 - label.width(), ++ xt = _xt>=0?_xt:x + 3; ++ draw_point(x - 1,yi,color,opacity).draw_point(x + 1,yi,color,opacity); ++ if (allow_zero || *txt!='0' || txt[1]!=0) ++ draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height); ++ } ++ } ++ return *this; ++ } ++ ++ //! Draw labeled horizontal and vertical axes. ++ /** ++ \param values_x Values along the X-axis. ++ \param values_y Values along the Y-axis. ++ \param color Pointer to \c spectrum() consecutive values, defining the drawing color. ++ \param opacity Drawing opacity. ++ \param pattern_x Drawing pattern for the X-axis. ++ \param pattern_y Drawing pattern for the Y-axis. ++ \param font_height Height of the labels (exact match for 13,23,53,103, interpolated otherwise). ++ \param allow_zero Enable/disable the drawing of label '0' if found. ++ **/ ++ template ++ CImg& draw_axes(const CImg& values_x, const CImg& values_y, ++ const tc *const color, const float opacity=1, ++ const unsigned int pattern_x=~0U, const unsigned int pattern_y=~0U, ++ const unsigned int font_height=13, const bool allow_zero=true) { ++ if (is_empty()) return *this; ++ const CImg nvalues_x(values_x._data,values_x.size(),1,1,1,true); ++ const int sizx = (int)values_x.size() - 1, wm1 = width() - 1; ++ if (sizx>=0) { ++ float ox = (float)*nvalues_x; ++ for (unsigned int x = sizx?1U:0U; x<_width; ++x) { ++ const float nx = (float)nvalues_x._linear_atX((float)x*sizx/wm1); ++ if (nx*ox<=0) { draw_axis(nx==0?x:x - 1,values_y,color,opacity,pattern_y,font_height,allow_zero); break; } ++ ox = nx; ++ } ++ } ++ const CImg nvalues_y(values_y._data,values_y.size(),1,1,1,true); ++ const int sizy = (int)values_y.size() - 1, hm1 = height() - 1; ++ if (sizy>0) { ++ float oy = (float)nvalues_y[0]; ++ for (unsigned int y = sizy?1U:0U; y<_height; ++y) { ++ const float ny = (float)nvalues_y._linear_atX((float)y*sizy/hm1); ++ if (ny*oy<=0) { draw_axis(values_x,ny==0?y:y - 1,color,opacity,pattern_x,font_height,allow_zero); break; } ++ oy = ny; ++ } ++ } ++ return *this; ++ } ++ ++ //! Draw labeled horizontal and vertical axes \overloading. ++ template ++ CImg& draw_axes(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 pattern_x=~0U, const unsigned int pattern_y=~0U, ++ const unsigned int font_height=13) { ++ if (is_empty()) return *this; ++ const bool allow_zero = (x0*x1>0) || (y0*y1>0); ++ const float ++ dx = cimg::abs(x1 - x0), dy = cimg::abs(y1 - y0), ++ px = dx<=0?1:precisionx==0?(float)std::pow(10.0,(int)std::log10(dx) - 2.0):precisionx, ++ py = dy<=0?1:precisiony==0?(float)std::pow(10.0,(int)std::log10(dy) - 2.0):precisiony; ++ if (x0!=x1 && y0!=y1) ++ draw_axes(CImg::sequence(subdivisionx>0?subdivisionx:1-width()/subdivisionx,x0,x1).round(px), ++ CImg::sequence(subdivisiony>0?subdivisiony:1-height()/subdivisiony,y0,y1).round(py), ++ color,opacity,pattern_x,pattern_y,font_height,allow_zero); ++ else if (x0==x1 && y0!=y1) ++ draw_axis((int)x0,CImg::sequence(subdivisiony>0?subdivisiony:1-height()/subdivisiony,y0,y1).round(py), ++ color,opacity,pattern_y,font_height); ++ else if (x0!=x1 && y0==y1) ++ draw_axis(CImg::sequence(subdivisionx>0?subdivisionx:1-width()/subdivisionx,x0,x1).round(px),(int)y0, ++ color,opacity,pattern_x,font_height); ++ return *this; ++ } ++ ++ //! Draw 2d grid. ++ /** ++ \param values_x X-coordinates of the vertical lines. ++ \param values_y Y-coordinates of the horizontal lines. ++ \param color Pointer to \c spectrum() consecutive values, defining the drawing color. ++ \param opacity Drawing opacity. ++ \param pattern_x Drawing pattern for vertical lines. ++ \param pattern_y Drawing pattern for horizontal lines. ++ **/ ++ template ++ CImg& draw_grid(const CImg& values_x, const CImg& values_y, ++ const tc *const color, const float opacity=1, ++ const unsigned int pattern_x=~0U, const unsigned int pattern_y=~0U) { ++ if (is_empty()) return *this; ++ if (values_x) cimg_foroff(values_x,x) { ++ const int xi = (int)values_x[x]; ++ if (xi>=0 && xi=0 && yi ++ CImg& draw_grid(const float delta_x, const float delta_y, ++ const float offsetx, const float offsety, ++ const bool invertx, const bool inverty, ++ const tc *const color, const float opacity=1, ++ const unsigned int pattern_x=~0U, const unsigned int pattern_y=~0U) { ++ if (is_empty()) return *this; ++ CImg seqx, seqy; ++ if (delta_x!=0) { ++ const float dx = delta_x>0?delta_x:_width*-delta_x/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 (delta_y!=0) { ++ const float dy = delta_y>0?delta_y:_height*-delta_y/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,pattern_x,pattern_y); ++ } ++ ++ //! Draw 1d graph. ++ /** ++ \param data Image containing the graph values I = f(x). ++ \param color Pointer to \c spectrum() consecutive values, defining the drawing color. ++ \param opacity Drawing opacity. ++ ++ \param plot_type Define the type of the plot: ++ - 0 = No plot. ++ - 1 = Plot using segments. ++ - 2 = Plot using cubic splines. ++ - 3 = Plot with bars. ++ \param vertex_type Define the type of points: ++ - 0 = No points. ++ - 1 = Point. ++ - 2 = Straight cross. ++ - 3 = Diagonal cross. ++ - 4 = Filled circle. ++ - 5 = Outlined circle. ++ - 6 = Square. ++ - 7 = Diamond. ++ \param ymin Lower bound of the y-range. ++ \param ymax Upper bound of the y-range. ++ \param pattern Drawing pattern. ++ \note ++ - if \c ymin==ymax==0, the y-range is computed automatically from the input samples. ++ **/ ++ template ++ CImg& draw_graph(const CImg& data, ++ const tc *const color, const float opacity=1, ++ const unsigned int plot_type=1, const int vertex_type=1, ++ const double ymin=0, const double ymax=0, const unsigned int pattern=~0U) { ++ if (is_empty() || _height<=1) return *this; ++ if (!color) ++ throw CImgArgumentException(_cimg_instance ++ "draw_graph(): Specified color is (null).", ++ cimg_instance); ++ ++ // Create shaded colors for displaying bar plots. ++ CImg color1, color2; ++ if (plot_type==3) { ++ color1.assign(_spectrum); color2.assign(_spectrum); ++ cimg_forC(*this,c) { ++ color1[c] = (tc)std::min((float)cimg::type::max(),(float)color[c]*1.2f); ++ color2[c] = (tc)(color[c]*0.4f); ++ } ++ } ++ ++ // Compute min/max and normalization factors. ++ const ulongT ++ siz = data.size(), ++ _siz1 = siz - (plot_type!=3), ++ siz1 = _siz1?_siz1:1; ++ const unsigned int ++ _width1 = _width - (plot_type!=3), ++ width1 = _width1?_width1:1; ++ double m = ymin, M = ymax; ++ if (ymin==ymax) m = (double)data.max_min(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); ++ if (siz==1) { ++ const int Y = (int)((*data - m)/ca); ++ draw_line(0,Y,width() - 1,Y,color,opacity,pattern); ++ } else { ++ const float fx = (float)_width/siz1; ++ for (ulongT off = 1; off ndata(data._data,siz,1,1,1,true); ++ int oY = (int)((data[0] - m)/ca); ++ cimg_forX(*this,x) { ++ const int Y = (int)((ndata._cubic_atX((float)x*siz1/width1)-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); ++ const float fx = (float)_width/siz1; ++ int oX = 0; ++ cimg_foroff(data,off) { ++ const int ++ X = (int)((off + 1)*fx) - 1, ++ Y = (int)((data[off] - m)/ca); ++ draw_rectangle(oX,Y0,X,Y,color,opacity). ++ draw_line(oX,Y,oX,Y0,color2.data(),opacity). ++ draw_line(oX,Y0,X,Y0,Y<=Y0?color2.data():color1.data(),opacity). ++ draw_line(X,Y,X,Y0,color1.data(),opacity). ++ draw_line(oX,Y,X,Y,Y<=Y0?color1.data():color2.data(),opacity); ++ oX = X + 1; ++ } ++ } break; ++ default : break; // No edges ++ } ++ ++ // Draw graph points ++ const unsigned int wb2 = plot_type==3?_width1/(2*siz):0; ++ const float fx = (float)_width1/siz1; ++ switch (vertex_type%8) { ++ case 1 : { // Point ++ cimg_foroff(data,off) { ++ const int ++ X = (int)(off*fx + wb2), ++ Y = (int)((data[off]-m)/ca); ++ draw_point(X,Y,color,opacity); ++ } ++ } break; ++ case 2 : { // Straight Cross ++ cimg_foroff(data,off) { ++ const int ++ X = (int)(off*fx + wb2), ++ 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 : { // Diagonal Cross ++ cimg_foroff(data,off) { ++ const int ++ X = (int)(off*fx + wb2), ++ 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 = (int)(off*fx + wb2), ++ 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 = (int)(off*fx + wb2), ++ 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 = (int)(off*fx + wb2), ++ 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 = (int)(off*fx + wb2), ++ 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 points ++ } ++ return *this; ++ } ++ ++ bool _draw_fill(const int x, const int y, const int z, ++ const CImg& ref, const float tolerance2) const { ++ const T *ptr1 = data(x,y,z), *ptr2 = ref._data; ++ const unsigned long off = _width*_height*_depth; ++ float diff = 0; ++ cimg_forC(*this,c) { diff += cimg::sqr(*ptr1 - *(ptr2++)); ptr1+=off; } ++ return diff<=tolerance2; ++ } ++ ++ //! Draw filled 3d region with the flood fill algorithm. ++ /** ++ \param x0 X-coordinate of the starting point of the region to fill. ++ \param y0 Y-coordinate of the starting point of the region to fill. ++ \param z0 Z-coordinate of the starting point of the region to fill. ++ \param color Pointer to \c spectrum() consecutive values, defining the drawing color. ++ \param[out] region Image that will contain the mask of the filled region mask, as an output. ++ \param tolerance Tolerance concerning neighborhood values. ++ \param opacity Opacity of the drawing. ++ \param is_high_connectivity Tells if 8-connexity must be used. ++ \return \c region is initialized with the binary mask of the filled region. ++ **/ ++ template ++ CImg& draw_fill(const int x0, const int y0, const int z0, ++ const tc *const color, const float opacity, ++ CImg ®ion, ++ const float tolerance = 0, ++ const bool is_high_connectivity = false) { ++#define _draw_fill_push(x,y,z) if (N>=stack._width) stack.resize(2*N + 1,1,1,3,0); \ ++ stack[N] = x; stack(N,1) = y; stack(N++,2) = z ++#define _draw_fill_pop(x,y,z) x = stack[--N]; y = stack(N,1); z = stack(N,2) ++#define _draw_fill_is_inside(x,y,z) !_region(x,y,z) && _draw_fill(x,y,z,ref,tolerance2) ++ ++ if (!containsXYZC(x0,y0,z0,0)) return *this; ++ const float nopacity = cimg::abs((float)opacity), copacity = 1 - std::max((float)opacity,0.0f); ++ const float tolerance2 = cimg::sqr(tolerance); ++ const CImg ref = get_vector_at(x0,y0,z0); ++ CImg stack(256,1,1,3); ++ CImg _region(_width,_height,_depth,1,0); ++ unsigned int N = 0; ++ int x, y, z; ++ ++ _draw_fill_push(x0,y0,z0); ++ while (N>0) { ++ _draw_fill_pop(x,y,z); ++ if (!_region(x,y,z)) { ++ const int yp = y - 1, yn = y + 1, zp = z - 1, zn = z + 1; ++ int xl = x, xr = x; ++ ++ // Using these booleans reduces the number of pushes drastically. ++ bool is_yp = false, is_yn = false, is_zp = false, is_zn = false; ++ for (int step = -1; step<2; step+=2) { ++ while (x>=0 && x=0 && _draw_fill_is_inside(x,yp,z)) { ++ if (!is_yp) { _draw_fill_push(x,yp,z); is_yp = true; } ++ } else is_yp = false; ++ if (yn1) { ++ if (zp>=0 && _draw_fill_is_inside(x,y,zp)) { ++ if (!is_zp) { _draw_fill_push(x,y,zp); is_zp = true; } ++ } else is_zp = false; ++ if (zn=0 && !is_yp) { ++ if (xp>=0 && _draw_fill_is_inside(xp,yp,z)) { ++ _draw_fill_push(xp,yp,z); if (step<0) is_yp = true; ++ } ++ if (xn0) is_yp = true; ++ } ++ } ++ if (yn=0 && _draw_fill_is_inside(xp,yn,z)) { ++ _draw_fill_push(xp,yn,z); if (step<0) is_yn = true; ++ } ++ if (xn0) is_yn = true; ++ } ++ } ++ if (depth()>1) { ++ if (zp>=0 && !is_zp) { ++ if (xp>=0 && _draw_fill_is_inside(xp,y,zp)) { ++ _draw_fill_push(xp,y,zp); if (step<0) is_zp = true; ++ } ++ if (xn0) is_zp = true; ++ } ++ ++ if (yp>=0 && !is_yp) { ++ if (_draw_fill_is_inside(x,yp,zp)) { _draw_fill_push(x,yp,zp); } ++ if (xp>=0 && _draw_fill_is_inside(xp,yp,zp)) { _draw_fill_push(xp,yp,zp); } ++ if (xn=0 && _draw_fill_is_inside(xp,yn,zp)) { _draw_fill_push(xp,yn,zp); } ++ if (xn=0 && _draw_fill_is_inside(xp,y,zn)) { ++ _draw_fill_push(xp,y,zn); if (step<0) is_zn = true; ++ } ++ if (xn0) is_zn = true; ++ } ++ ++ if (yp>=0 && !is_yp) { ++ if (_draw_fill_is_inside(x,yp,zn)) { _draw_fill_push(x,yp,zn); } ++ if (xp>=0 && _draw_fill_is_inside(xp,yp,zn)) { _draw_fill_push(xp,yp,zn); } ++ if (xn=0 && _draw_fill_is_inside(xp,yn,zn)) { _draw_fill_push(xp,yn,zn); } ++ if (xn ++ CImg& draw_fill(const int x0, const int y0, const int z0, ++ const tc *const color, const float opacity=1, ++ const float tolerance=0, const bool is_high_connexity=false) { ++ CImg tmp; ++ return draw_fill(x0,y0,z0,color,opacity,tmp,tolerance,is_high_connexity); ++ } ++ ++ //! Draw filled 2d region with the flood fill algorithm \simplification. ++ template ++ CImg& draw_fill(const int x0, const int y0, ++ const tc *const color, const float opacity=1, ++ const float tolerance=0, const bool is_high_connexity=false) { ++ CImg tmp; ++ return draw_fill(x0,y0,0,color,opacity,tmp,tolerance,is_high_connexity); ++ } ++ ++ //! Draw a random plasma texture. ++ /** ++ \param alpha Alpha-parameter. ++ \param beta Beta-parameter. ++ \param scale Scale-parameter. ++ \note Use the mid-point algorithm to render. ++ **/ ++ CImg& draw_plasma(const float alpha=1, const float beta=0, const unsigned int scale=8) { ++ if (is_empty()) return *this; ++ const int w = width(), h = height(); ++ const Tfloat m = (Tfloat)cimg::type::min(), M = (Tfloat)cimg::type::max(); ++ cimg_forZC(*this,z,c) { ++ CImg ref = get_shared_slice(z,c); ++ for (int delta = 1<1; delta>>=1) { ++ const int delta2 = delta>>1; ++ const float r = alpha*delta + beta; ++ ++ // Square step. ++ for (int y0 = 0; y0M?M:val); ++ } ++ ++ // Diamond steps. ++ for (int y = -delta2; yM?M:val); ++ } ++ for (int y0 = 0; y0M?M:val); ++ } ++ for (int y = -delta2; yM?M:val); ++ } ++ } ++ } ++ return *this; ++ } ++ ++ //! Draw a quadratic Mandelbrot or Julia 2d fractal. ++ /** ++ \param x0 X-coordinate of the upper-left pixel. ++ \param y0 Y-coordinate of the upper-left pixel. ++ \param x1 X-coordinate of the lower-right pixel. ++ \param y1 Y-coordinate of the lower-right pixel. ++ \param colormap Colormap. ++ \param opacity Drawing opacity. ++ \param z0r Real part of the upper-left fractal vertex. ++ \param z0i Imaginary part of the upper-left fractal vertex. ++ \param z1r Real part of the lower-right fractal vertex. ++ \param z1i Imaginary part of the lower-right fractal vertex. ++ \param iteration_max Maximum number of iterations for each estimated point. ++ \param is_normalized_iteration Tells if iterations are normalized. ++ \param is_julia_set Tells if the Mandelbrot or Julia set is rendered. ++ \param param_r Real part of the Julia set parameter. ++ \param param_i Imaginary part of the Julia set parameter. ++ \note Fractal rendering is done by the Escape Time Algorithm. ++ **/ ++ template ++ CImg& draw_mandelbrot(const int x0, const int y0, const int x1, const int y1, ++ const CImg& colormap, const float opacity=1, ++ const double z0r=-2, const double z0i=-2, const double z1r=2, const double z1i=2, ++ const unsigned int iteration_max=255, ++ const bool is_normalized_iteration=false, ++ const bool is_julia_set=false, ++ const double param_r=0, const double param_i=0) { ++ if (is_empty()) return *this; ++ CImg palette; ++ if (colormap) palette.assign(colormap._data,colormap.size()/colormap._spectrum,1,1,colormap._spectrum,true); ++ if (palette && palette._spectrum!=_spectrum) ++ throw CImgArgumentException(_cimg_instance ++ "draw_mandelbrot(): Instance and specified colormap (%u,%u,%u,%u,%p) have " ++ "incompatible dimensions.", ++ cimg_instance, ++ colormap._width,colormap._height,colormap._depth,colormap._spectrum,colormap._data); ++ ++ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f), ln2 = (float)std::log(2.0); ++ const int ++ _x0 = cimg::cut(x0,0,width() - 1), ++ _y0 = cimg::cut(y0,0,height() - 1), ++ _x1 = cimg::cut(x1,0,width() - 1), ++ _y1 = cimg::cut(y1,0,height() - 1); ++ ++ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if((1 + _x1 - _x0)*(1 + _y1 - _y0)>=2048)) ++ for (int q = _y0; q<=_y1; ++q) ++ for (int p = _x0; p<=_x1; ++p) { ++ unsigned int iteration = 0; ++ const double x = z0r + p*(z1r-z0r)/_width, y = z0i + q*(z1i-z0i)/_height; ++ double zr, zi, cr, ci; ++ if (is_julia_set) { zr = x; zi = y; cr = param_r; ci = param_i; } ++ else { zr = param_r; zi = param_i; cr = x; ci = y; } ++ for (iteration=1; zr*zr + zi*zi<=4 && iteration<=iteration_max; ++iteration) { ++ const double temp = zr*zr - zi*zi + cr; ++ zi = 2*zr*zi + ci; ++ zr = temp; ++ } ++ if (iteration>iteration_max) { ++ if (palette) { ++ if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)palette(0,c); ++ else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(palette(0,c)*nopacity + (*this)(p,q,0,c)*copacity); ++ } else { ++ if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)0; ++ else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)((*this)(p,q,0,c)*copacity); ++ } ++ } else if (is_normalized_iteration) { ++ const float ++ normz = (float)cimg::abs(zr*zr + zi*zi), ++ niteration = (float)(iteration + 1 - std::log(std::log(normz))/ln2); ++ if (palette) { ++ if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)palette._linear_atX(niteration,c); ++ else cimg_forC(*this,c) ++ (*this)(p,q,0,c) = (T)(palette._linear_atX(niteration,c)*nopacity + (*this)(p,q,0,c)*copacity); ++ } else { ++ if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)niteration; ++ else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(niteration*nopacity + (*this)(p,q,0,c)*copacity); ++ } ++ } else { ++ if (palette) { ++ if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)palette._atX(iteration,c); ++ else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(palette(iteration,c)*nopacity + (*this)(p,q,0,c)*copacity); ++ } else { ++ if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)iteration; ++ else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(iteration*nopacity + (*this)(p,q,0,c)*copacity); ++ } ++ } ++ } ++ return *this; ++ } ++ ++ //! Draw a quadratic Mandelbrot or Julia 2d fractal \overloading. ++ template ++ CImg& draw_mandelbrot(const CImg& colormap, const float opacity=1, ++ const double z0r=-2, const double z0i=-2, const double z1r=2, const double z1i=2, ++ const unsigned int iteration_max=255, ++ const bool is_normalized_iteration=false, ++ const bool is_julia_set=false, ++ const double param_r=0, const double param_i=0) { ++ return draw_mandelbrot(0,0,_width - 1,_height - 1,colormap,opacity, ++ z0r,z0i,z1r,z1i,iteration_max,is_normalized_iteration,is_julia_set,param_r,param_i); ++ } ++ ++ //! Draw a 1d gaussian function. ++ /** ++ \param xc X-coordinate of the gaussian center. ++ \param sigma Standard variation of the gaussian distribution. ++ \param color Pointer to \c spectrum() consecutive values, defining the drawing color. ++ \param opacity Drawing opacity. ++ **/ ++ 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_instance ++ "draw_gaussian(): Specified color is (null).", ++ cimg_instance); ++ const float sigma2 = 2*sigma*sigma, nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f); ++ const ulongT whd = (ulongT)_width*_height*_depth; ++ const tc *col = color; ++ cimg_forX(*this,x) { ++ const float dx = (x - xc), val = (float)std::exp(-dx*dx/sigma2); ++ T *ptrd = data(x,0,0,0); ++ if (opacity>=1) cimg_forC(*this,c) { *ptrd = (T)(val*(*col++)); ptrd+=whd; } ++ else cimg_forC(*this,c) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whd; } ++ col-=_spectrum; ++ } ++ return *this; ++ } ++ ++ //! Draw a 2d gaussian function. ++ /** ++ \param xc X-coordinate of the gaussian center. ++ \param yc Y-coordinate of the gaussian center. ++ \param tensor Covariance matrix (must be 2x2). ++ \param color Pointer to \c spectrum() consecutive values, defining the drawing color. ++ \param opacity Drawing opacity. ++ **/ ++ 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; ++ if (tensor._width!=2 || tensor._height!=2 || tensor._depth!=1 || tensor._spectrum!=1) ++ throw CImgArgumentException(_cimg_instance ++ "draw_gaussian(): Specified tensor (%u,%u,%u,%u,%p) is not a 2x2 matrix.", ++ cimg_instance, ++ tensor._width,tensor._height,tensor._depth,tensor._spectrum,tensor._data); ++ if (!color) ++ throw CImgArgumentException(_cimg_instance ++ "draw_gaussian(): Specified color is (null).", ++ cimg_instance); ++ typedef typename CImg::Tfloat tfloat; ++ 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 - std::max(opacity,0.0f); ++ const ulongT whd = (ulongT)_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)std::exp(a*dx*dx + b*dx*dy + c*dy*dy); ++ T *ptrd = data(x,y,0,0); ++ if (opacity>=1) cimg_forC(*this,c) { *ptrd = (T)(val*(*col++)); ptrd+=whd; } ++ else cimg_forC(*this,c) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whd; } ++ col-=_spectrum; ++ ++dx; ++ } ++ ++dy; ++ } ++ return *this; ++ } ++ ++ //! Draw a 2d gaussian function \overloading. ++ 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 a 2d gaussian function \overloading. ++ 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 a 3d gaussian function \overloading. ++ 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::Tfloat tfloat; ++ if (tensor._width!=3 || tensor._height!=3 || tensor._depth!=1 || tensor._spectrum!=1) ++ throw CImgArgumentException(_cimg_instance ++ "draw_gaussian(): Specified tensor (%u,%u,%u,%u,%p) is not a 3x3 matrix.", ++ cimg_instance, ++ tensor._width,tensor._height,tensor._depth,tensor._spectrum,tensor._data); ++ ++ const CImg invT = tensor.get_invert(), invT2 = (invT*invT)/(-2.0); ++ const tfloat a = invT2(0,0), b = 2*invT2(1,0), c = 2*invT2(2,0), d = invT2(1,1), e = 2*invT2(2,1), f = invT2(2,2); ++ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f); ++ const ulongT whd = (ulongT)_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)std::exp(a*dx*dx + b*dx*dy + c*dx*dz + d*dy*dy + e*dy*dz + f*dz*dz); ++ T *ptrd = data(x,y,z,0); ++ if (opacity>=1) cimg_forC(*this,c) { *ptrd = (T)(val*(*col++)); ptrd+=whd; } ++ else cimg_forC(*this,c) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whd; } ++ col-=_spectrum; ++ } ++ return *this; ++ } ++ ++ //! Draw a 3d gaussian function \overloading. ++ 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 a 3d object. ++ /** ++ \param x0 X-coordinate of the 3d object position ++ \param y0 Y-coordinate of the 3d object position ++ \param z0 Z-coordinate of the 3d object position ++ \param vertices Image Nx3 describing 3d point coordinates ++ \param primitives List of P primitives ++ \param colors List of P color (or textures) ++ \param opacities Image or list of P opacities ++ \param render_type d Render type (0=Points, 1=Lines, 2=Faces (no light), 3=Faces (flat), 4=Faces(Gouraud) ++ \param is_double_sided Tells if object faces have two sides or are oriented. ++ \param focale length of the focale (0 for parallel projection) ++ \param lightx X-coordinate of the light ++ \param lighty Y-coordinate of the light ++ \param lightz Z-coordinate of the light ++ \param specular_lightness Amount of specular light. ++ \param specular_shininess Shininess of the object ++ **/ ++ template ++ CImg& draw_object3d(const float x0, const float y0, const float z0, ++ const CImg& vertices, const CImgList& primitives, ++ const CImgList& colors, const CImg& opacities, ++ const unsigned int render_type=4, ++ const bool is_double_sided=false, const float focale=700, ++ const float lightx=0, const float lighty=0, const float lightz=-5e8, ++ const float specular_lightness=0.2f, const float specular_shininess=0.1f) { ++ return draw_object3d(x0,y0,z0,vertices,primitives,colors,opacities,render_type, ++ is_double_sided,focale,lightx,lighty,lightz, ++ specular_lightness,specular_shininess,CImg::empty()); ++ } ++ ++ //! Draw a 3d object \simplification. ++ template ++ CImg& draw_object3d(const float x0, const float y0, const float z0, ++ const CImg& vertices, const CImgList& primitives, ++ const CImgList& colors, const CImg& opacities, ++ const unsigned int render_type, ++ const bool is_double_sided, const float focale, ++ const float lightx, const float lighty, const float lightz, ++ const float specular_lightness, const float specular_shininess, ++ CImg& zbuffer) { ++ return _draw_object3d(0,zbuffer,x0,y0,z0,vertices,primitives,colors,opacities, ++ render_type,is_double_sided,focale,lightx,lighty,lightz, ++ specular_lightness,specular_shininess,1); ++ } ++ ++#ifdef cimg_use_board ++ template ++ CImg& draw_object3d(LibBoard::Board& board, ++ const float x0, const float y0, const float z0, ++ const CImg& vertices, const CImgList& primitives, ++ const CImgList& colors, const CImg& opacities, ++ const unsigned int render_type=4, ++ const bool is_double_sided=false, const float focale=700, ++ const float lightx=0, const float lighty=0, const float lightz=-5e8, ++ const float specular_lightness=0.2f, const float specular_shininess=0.1f) { ++ return draw_object3d(board,x0,y0,z0,vertices,primitives,colors,opacities,render_type, ++ is_double_sided,focale,lightx,lighty,lightz, ++ specular_lightness,specular_shininess,CImg::empty()); ++ } ++ ++ template ++ CImg& draw_object3d(LibBoard::Board& board, ++ const float x0, const float y0, const float z0, ++ const CImg& vertices, const CImgList& primitives, ++ const CImgList& colors, const CImg& opacities, ++ const unsigned int render_type, ++ const bool is_double_sided, const float focale, ++ const float lightx, const float lighty, const float lightz, ++ const float specular_lightness, const float specular_shininess, ++ CImg& zbuffer) { ++ return _draw_object3d((void*)&board,zbuffer,x0,y0,z0,vertices,primitives,colors,opacities, ++ render_type,is_double_sided,focale,lightx,lighty,lightz, ++ specular_lightness,specular_shininess,1); ++ } ++#endif ++ ++ //! Draw a 3d object \simplification. ++ template ++ CImg& draw_object3d(const float x0, const float y0, const float z0, ++ const CImg& vertices, const CImgList& primitives, ++ const CImgList& colors, const CImgList& opacities, ++ const unsigned int render_type=4, ++ const bool is_double_sided=false, const float focale=700, ++ const float lightx=0, const float lighty=0, const float lightz=-5e8, ++ const float specular_lightness=0.2f, const float specular_shininess=0.1f) { ++ return draw_object3d(x0,y0,z0,vertices,primitives,colors,opacities,render_type, ++ is_double_sided,focale,lightx,lighty,lightz, ++ specular_lightness,specular_shininess,CImg::empty()); ++ } ++ ++ //! Draw a 3d object \simplification. ++ template ++ CImg& draw_object3d(const float x0, const float y0, const float z0, ++ const CImg& vertices, const CImgList& primitives, ++ const CImgList& colors, const CImgList& opacities, ++ const unsigned int render_type, ++ const bool is_double_sided, const float focale, ++ const float lightx, const float lighty, const float lightz, ++ const float specular_lightness, const float specular_shininess, ++ CImg& zbuffer) { ++ return _draw_object3d(0,zbuffer,x0,y0,z0,vertices,primitives,colors,opacities, ++ render_type,is_double_sided,focale,lightx,lighty,lightz, ++ specular_lightness,specular_shininess,1); ++ } ++ ++#ifdef cimg_use_board ++ template ++ CImg& draw_object3d(LibBoard::Board& board, ++ const float x0, const float y0, const float z0, ++ const CImg& vertices, const CImgList& primitives, ++ const CImgList& colors, const CImgList& opacities, ++ const unsigned int render_type=4, ++ const bool is_double_sided=false, const float focale=700, ++ const float lightx=0, const float lighty=0, const float lightz=-5e8, ++ const float specular_lightness=0.2f, const float specular_shininess=0.1f) { ++ return draw_object3d(board,x0,y0,z0,vertices,primitives,colors,opacities,render_type, ++ is_double_sided,focale,lightx,lighty,lightz, ++ specular_lightness,specular_shininess,CImg::empty()); ++ } ++ ++ template ++ CImg& draw_object3d(LibBoard::Board& board, ++ const float x0, const float y0, const float z0, ++ const CImg& vertices, const CImgList& primitives, ++ const CImgList& colors, const CImgList& opacities, ++ const unsigned int render_type, ++ const bool is_double_sided, const float focale, ++ const float lightx, const float lighty, const float lightz, ++ const float specular_lightness, const float specular_shininess, ++ CImg& zbuffer) { ++ return _draw_object3d((void*)&board,zbuffer,x0,y0,z0,vertices,primitives,colors,opacities, ++ render_type,is_double_sided,focale,lightx,lighty,lightz, ++ specular_lightness,specular_shininess,1); ++ } ++#endif ++ ++ //! Draw a 3d object \simplification. ++ template ++ CImg& draw_object3d(const float x0, const float y0, const float z0, ++ const CImg& vertices, const CImgList& primitives, ++ const CImgList& colors, ++ const unsigned int render_type=4, ++ const bool is_double_sided=false, const float focale=700, ++ const float lightx=0, const float lighty=0, const float lightz=-5e8, ++ const float specular_lightness=0.2f, const float specular_shininess=0.1f) { ++ return draw_object3d(x0,y0,z0,vertices,primitives,colors,CImg::const_empty(), ++ render_type,is_double_sided,focale,lightx,lighty,lightz, ++ specular_lightness,specular_shininess,CImg::empty()); ++ } ++ ++ //! Draw a 3d object \simplification. ++ template ++ CImg& draw_object3d(const float x0, const float y0, const float z0, ++ const CImg& vertices, const CImgList& primitives, ++ const CImgList& colors, ++ const unsigned int render_type, ++ const bool is_double_sided, const float focale, ++ const float lightx, const float lighty, const float lightz, ++ const float specular_lightness, const float specular_shininess, ++ CImg& zbuffer) { ++ return draw_object3d(x0,y0,z0,vertices,primitives,colors,CImg::const_empty(), ++ render_type,is_double_sided,focale,lightx,lighty,lightz, ++ specular_lightness,specular_shininess,zbuffer); ++ } ++ ++#ifdef cimg_use_board ++ template ++ CImg& draw_object3d(LibBoard::Board& board, ++ const float x0, const float y0, const float z0, ++ const CImg& vertices, const CImgList& primitives, ++ const CImgList& colors, ++ const unsigned int render_type=4, ++ const bool is_double_sided=false, const float focale=700, ++ const float lightx=0, const float lighty=0, const float lightz=-5e8, ++ const float specular_lightness=0.2f, const float specular_shininess=0.1f) { ++ return draw_object3d(x0,y0,z0,vertices,primitives,colors,CImg::const_empty(), ++ render_type,is_double_sided,focale,lightx,lighty,lightz, ++ specular_lightness,specular_shininess,CImg::empty()); ++ } ++ ++ template ++ CImg& draw_object3d(LibBoard::Board& board, ++ const float x0, const float y0, const float z0, ++ const CImg& vertices, const CImgList& primitives, ++ const CImgList& colors, ++ const unsigned int render_type, ++ const bool is_double_sided, const float focale, ++ const float lightx, const float lighty, const float lightz, ++ const float specular_lightness, const float specular_shininess, ++ CImg& zbuffer) { ++ return draw_object3d(x0,y0,z0,vertices,primitives,colors,CImg::const_empty(), ++ render_type,is_double_sided,focale,lightx,lighty,lightz, ++ specular_lightness,specular_shininess,zbuffer); ++ } ++#endif ++ ++ template ++ static float __draw_object3d(const CImgList& opacities, const unsigned int n_primitive, CImg& opacity) { ++ if (n_primitive>=opacities._width || opacities[n_primitive].is_empty()) { opacity.assign(); return 1; } ++ if (opacities[n_primitive].size()==1) { opacity.assign(); return opacities(n_primitive,0); } ++ opacity.assign(opacities[n_primitive],true); ++ return 1.0f; ++ } ++ ++ template ++ static float __draw_object3d(const CImg& opacities, const unsigned int n_primitive, CImg& opacity) { ++ opacity.assign(); ++ return n_primitive>=opacities._width?1.0f:(float)opacities[n_primitive]; ++ } ++ ++ template ++ static float ___draw_object3d(const CImgList& opacities, const unsigned int n_primitive) { ++ return n_primitive ++ static float ___draw_object3d(const CImg& opacities, const unsigned int n_primitive) { ++ return n_primitive ++ CImg& _draw_object3d(void *const pboard, CImg& zbuffer, ++ const float X, const float Y, const float Z, ++ const CImg& vertices, ++ const CImgList& primitives, ++ const CImgList& colors, ++ const to& opacities, ++ const unsigned int render_type, ++ const bool is_double_sided, const float focale, ++ const float lightx, const float lighty, const float lightz, ++ const float specular_lightness, const float specular_shininess, ++ const float sprite_scale) { ++ typedef typename cimg::superset2::type tpfloat; ++ typedef typename to::value_type _to; ++ if (is_empty() || !vertices || !primitives) return *this; ++ CImg error_message(1024); ++ if (!vertices.is_object3d(primitives,colors,opacities,false,error_message)) ++ throw CImgArgumentException(_cimg_instance ++ "draw_object3d(): Invalid specified 3d object (%u,%u) (%s).", ++ cimg_instance,vertices._width,primitives._width,error_message.data()); ++#ifndef cimg_use_board ++ if (pboard) return *this; ++#endif ++ if (render_type==5) cimg::mutex(10); // Static variable used in this case, breaks thread-safety. ++ ++ const float ++ nspec = 1 - (specular_lightness<0.0f?0.0f:(specular_lightness>1.0f?1.0f:specular_lightness)), ++ nspec2 = 1 + (specular_shininess<0.0f?0.0f:specular_shininess), ++ nsl1 = (nspec2 - 1)/cimg::sqr(nspec - 1), ++ nsl2 = 1 - 2*nsl1*nspec, ++ nsl3 = nspec2 - nsl1 - nsl2; ++ ++ // Create light texture for phong-like rendering. ++ CImg light_texture; ++ if (render_type==5) { ++ if (colors._width>primitives._width) { ++ static CImg default_light_texture; ++ static const tc *lptr = 0; ++ static tc ref_values[64] = { 0 }; ++ const CImg& img = colors.back(); ++ bool is_same_texture = (lptr==img._data); ++ if (is_same_texture) ++ for (unsigned int r = 0, j = 0; j<8; ++j) ++ for (unsigned int i = 0; i<8; ++i) ++ if (ref_values[r++]!=img(i*img._width/9,j*img._height/9,0,(i + j)%img._spectrum)) { ++ is_same_texture = false; break; ++ } ++ if (!is_same_texture || default_light_texture._spectrum<_spectrum) { ++ (default_light_texture.assign(img,false)/=255).resize(-100,-100,1,_spectrum); ++ lptr = colors.back().data(); ++ for (unsigned int r = 0, j = 0; j<8; ++j) ++ for (unsigned int i = 0; i<8; ++i) ++ ref_values[r++] = img(i*img._width/9,j*img._height/9,0,(i + j)%img._spectrum); ++ } ++ light_texture.assign(default_light_texture,true); ++ } else { ++ static CImg default_light_texture; ++ static float olightx = 0, olighty = 0, olightz = 0, ospecular_shininess = 0; ++ if (!default_light_texture || ++ lightx!=olightx || lighty!=olighty || lightz!=olightz || ++ specular_shininess!=ospecular_shininess || default_light_texture._spectrum<_spectrum) { ++ default_light_texture.assign(512,512); ++ const float ++ dlx = lightx - X, ++ dly = lighty - Y, ++ dlz = lightz - Z, ++ nl = cimg::hypot(dlx,dly,dlz), ++ nlx = (default_light_texture._width - 1)/2*(1 + dlx/nl), ++ nly = (default_light_texture._height - 1)/2*(1 + dly/nl), ++ white[] = { 1 }; ++ default_light_texture.draw_gaussian(nlx,nly,default_light_texture._width/3.0f,white); ++ cimg_forXY(default_light_texture,x,y) { ++ const float factor = default_light_texture(x,y); ++ if (factor>nspec) default_light_texture(x,y) = std::min(2.0f,nsl1*factor*factor + nsl2*factor + nsl3); ++ } ++ default_light_texture.resize(-100,-100,1,_spectrum); ++ olightx = lightx; olighty = lighty; olightz = lightz; ospecular_shininess = specular_shininess; ++ } ++ light_texture.assign(default_light_texture,true); ++ } ++ } ++ ++ // Compute 3d to 2d projection. ++ CImg projections(vertices._width,2); ++ tpfloat parallzmin = cimg::type::max(); ++ const float absfocale = focale?cimg::abs(focale):0; ++ if (absfocale) { ++ cimg_pragma_openmp(parallel for cimg_openmp_if(projections.size()>4096)) ++ cimg_forX(projections,l) { // Perspective projection ++ const tpfloat ++ x = (tpfloat)vertices(l,0), ++ y = (tpfloat)vertices(l,1), ++ z = (tpfloat)vertices(l,2); ++ const tpfloat projectedz = z + Z + absfocale; ++ projections(l,1) = Y + absfocale*y/projectedz; ++ projections(l,0) = X + absfocale*x/projectedz; ++ } ++ } else { ++ cimg_pragma_openmp(parallel for cimg_openmp_if(projections.size()>4096)) ++ cimg_forX(projections,l) { // Parallel projection ++ const tpfloat ++ x = (tpfloat)vertices(l,0), ++ y = (tpfloat)vertices(l,1), ++ z = (tpfloat)vertices(l,2); ++ if (z visibles(primitives._width,1,1,1,~0U); ++ CImg zrange(primitives._width); ++ const tpfloat zmin = absfocale?(tpfloat)(1.5f - absfocale):cimg::type::min(); ++ bool is_forward = zbuffer?true:false; ++ ++ cimg_pragma_openmp(parallel for cimg_openmp_if(primitives.size()>4096)) ++ cimglist_for(primitives,l) { ++ const CImg& primitive = primitives[l]; ++ switch (primitive.size()) { ++ case 1 : { // Point ++ CImg<_to> _opacity; ++ __draw_object3d(opacities,l,_opacity); ++ if (l<=colors.width() && (colors[l].size()!=_spectrum || _opacity)) is_forward = false; ++ const unsigned int i0 = (unsigned int)primitive(0); ++ const tpfloat z0 = Z + vertices(i0,2); ++ if (z0>zmin) { ++ visibles(l) = (unsigned int)l; ++ zrange(l) = z0; ++ } ++ } break; ++ case 5 : { // Sphere ++ const unsigned int ++ i0 = (unsigned int)primitive(0), ++ i1 = (unsigned int)primitive(1); ++ const tpfloat ++ Xc = 0.5f*((float)vertices(i0,0) + (float)vertices(i1,0)), ++ Yc = 0.5f*((float)vertices(i0,1) + (float)vertices(i1,1)), ++ Zc = 0.5f*((float)vertices(i0,2) + (float)vertices(i1,2)), ++ _zc = Z + Zc, ++ zc = _zc + _focale, ++ xc = X + Xc*(absfocale?absfocale/zc:1), ++ yc = Y + Yc*(absfocale?absfocale/zc:1), ++ radius = 0.5f*cimg::hypot(vertices(i1,0) - vertices(i0,0), ++ vertices(i1,1) - vertices(i0,1), ++ vertices(i1,2) - vertices(i0,2))*(absfocale?absfocale/zc:1), ++ xm = xc - radius, ++ ym = yc - radius, ++ xM = xc + radius, ++ yM = yc + radius; ++ if (xM>=0 && xm<_width && yM>=0 && ym<_height && _zc>zmin) { ++ visibles(l) = (unsigned int)l; ++ zrange(l) = _zc; ++ } ++ is_forward = false; ++ } break; ++ case 2 : // Segment ++ case 6 : { ++ const unsigned int ++ i0 = (unsigned int)primitive(0), ++ i1 = (unsigned int)primitive(1); ++ const tpfloat ++ x0 = projections(i0,0), y0 = projections(i0,1), z0 = Z + vertices(i0,2), ++ x1 = projections(i1,0), y1 = projections(i1,1), z1 = Z + vertices(i1,2); ++ tpfloat xm, xM, ym, yM; ++ if (x0=0 && xm<_width && yM>=0 && ym<_height && z0>zmin && z1>zmin) { ++ visibles(l) = (unsigned int)l; ++ zrange(l) = (z0 + z1)/2; ++ } ++ } break; ++ case 3 : // Triangle ++ case 9 : { ++ const unsigned int ++ i0 = (unsigned int)primitive(0), ++ i1 = (unsigned int)primitive(1), ++ i2 = (unsigned int)primitive(2); ++ const tpfloat ++ x0 = projections(i0,0), y0 = projections(i0,1), z0 = Z + vertices(i0,2), ++ x1 = projections(i1,0), y1 = projections(i1,1), z1 = Z + vertices(i1,2), ++ x2 = projections(i2,0), y2 = projections(i2,1), z2 = Z + vertices(i2,2); ++ tpfloat xm, xM, ym, yM; ++ if (x0xM) xM = x2; ++ if (y0yM) yM = y2; ++ if (xM>=0 && xm<_width && yM>=0 && ym<_height && z0>zmin && z1>zmin && z2>zmin) { ++ const tpfloat d = (x1-x0)*(y2-y0) - (x2-x0)*(y1-y0); ++ if (is_double_sided || d<0) { ++ visibles(l) = (unsigned int)l; ++ zrange(l) = (z0 + z1 + z2)/3; ++ } ++ } ++ } break; ++ case 4 : // Rectangle ++ case 12 : { ++ const unsigned int ++ i0 = (unsigned int)primitive(0), ++ i1 = (unsigned int)primitive(1), ++ i2 = (unsigned int)primitive(2), ++ i3 = (unsigned int)primitive(3); ++ const tpfloat ++ x0 = projections(i0,0), y0 = projections(i0,1), z0 = Z + vertices(i0,2), ++ x1 = projections(i1,0), y1 = projections(i1,1), z1 = Z + vertices(i1,2), ++ x2 = projections(i2,0), y2 = projections(i2,1), z2 = Z + vertices(i2,2), ++ x3 = projections(i3,0), y3 = projections(i3,1), z3 = Z + vertices(i3,2); ++ tpfloat xm, xM, ym, yM; ++ if (x0xM) xM = x2; ++ if (x3xM) xM = x3; ++ if (y0yM) yM = y2; ++ if (y3yM) yM = y3; ++ if (xM>=0 && xm<_width && yM>=0 && ym<_height && z0>zmin && z1>zmin && z2>zmin && z3>zmin) { ++ const float d = (x1 - x0)*(y2 - y0) - (x2 - x0)*(y1 - y0); ++ if (is_double_sided || d<0) { ++ visibles(l) = (unsigned int)l; ++ zrange(l) = (z0 + z1 + z2 + z3)/4; ++ } ++ } ++ } break; ++ default : ++ if (render_type==5) cimg::mutex(10,0); ++ throw CImgArgumentException(_cimg_instance ++ "draw_object3d(): Invalid primitive[%u] with size %u " ++ "(should have size 1,2,3,4,5,6,9 or 12).", ++ cimg_instance, ++ l,primitive.size()); ++ } ++ } ++ ++ // Force transparent primitives to be drawn last when zbuffer is activated ++ // (and if object contains no spheres or sprites). ++ if (is_forward) ++ cimglist_for(primitives,l) ++ if (___draw_object3d(opacities,l)!=1) zrange(l) = 2*zmax - zrange(l); ++ ++ // Sort only visibles primitives. ++ unsigned int *p_visibles = visibles._data; ++ tpfloat *p_zrange = zrange._data; ++ const tpfloat *ptrz = p_zrange; ++ cimg_for(visibles,ptr,unsigned int) { ++ if (*ptr!=~0U) { *(p_visibles++) = *ptr; *(p_zrange++) = *ptrz; } ++ ++ptrz; ++ } ++ const unsigned int nb_visibles = (unsigned int)(p_zrange - zrange._data); ++ if (!nb_visibles) { ++ if (render_type==5) cimg::mutex(10,0); ++ return *this; ++ } ++ CImg permutations; ++ CImg(zrange._data,nb_visibles,1,1,1,true).sort(permutations,is_forward); ++ ++ // Compute light properties ++ CImg lightprops; ++ switch (render_type) { ++ case 3 : { // Flat Shading ++ lightprops.assign(nb_visibles); ++ cimg_pragma_openmp(parallel for cimg_openmp_if(nb_visibles>4096)) ++ cimg_forX(lightprops,l) { ++ const CImg& primitive = primitives(visibles(permutations(l))); ++ const unsigned int psize = (unsigned int)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 tpfloat ++ x0 = (tpfloat)vertices(i0,0), y0 = (tpfloat)vertices(i0,1), z0 = (tpfloat)vertices(i0,2), ++ x1 = (tpfloat)vertices(i1,0), y1 = (tpfloat)vertices(i1,1), z1 = (tpfloat)vertices(i1,2), ++ x2 = (tpfloat)vertices(i2,0), y2 = (tpfloat)vertices(i2,1), z2 = (tpfloat)vertices(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 = 1e-5f + cimg::hypot(nx,ny,nz), ++ lx = X + (x0 + x1 + x2)/3 - lightx, ++ ly = Y + (y0 + y1 + y2)/3 - lighty, ++ lz = Z + (z0 + z1 + z2)/3 - lightz, ++ nl = 1e-5f + cimg::hypot(lx,ly,lz), ++ factor = std::max(cimg::abs(-lx*nx - ly*ny - lz*nz)/(norm*nl),(tpfloat)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 vertices_normals(vertices._width,6,1,1,0); ++ cimg_pragma_openmp(parallel for cimg_openmp_if(nb_visibles>4096)) ++ for (unsigned int l = 0; l& primitive = primitives[visibles(l)]; ++ const unsigned int psize = (unsigned int)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 tpfloat ++ x0 = (tpfloat)vertices(i0,0), y0 = (tpfloat)vertices(i0,1), z0 = (tpfloat)vertices(i0,2), ++ x1 = (tpfloat)vertices(i1,0), y1 = (tpfloat)vertices(i1,1), z1 = (tpfloat)vertices(i1,2), ++ x2 = (tpfloat)vertices(i2,0), y2 = (tpfloat)vertices(i2,1), z2 = (tpfloat)vertices(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 + cimg::hypot(nnx,nny,nnz), ++ nx = nnx/norm, ++ ny = nny/norm, ++ nz = nnz/norm; ++ unsigned int ix = 0, iy = 1, iz = 2; ++ if (is_double_sided && nz>0) { ix = 3; iy = 4; iz = 5; } ++ vertices_normals(i0,ix)+=nx; vertices_normals(i0,iy)+=ny; vertices_normals(i0,iz)+=nz; ++ vertices_normals(i1,ix)+=nx; vertices_normals(i1,iy)+=ny; vertices_normals(i1,iz)+=nz; ++ vertices_normals(i2,ix)+=nx; vertices_normals(i2,iy)+=ny; vertices_normals(i2,iz)+=nz; ++ if (rectangle_flag) { ++ vertices_normals(i3,ix)+=nx; vertices_normals(i3,iy)+=ny; vertices_normals(i3,iz)+=nz; ++ } ++ } ++ } ++ ++ if (is_double_sided) cimg_forX(vertices_normals,p) { ++ const float ++ nx0 = vertices_normals(p,0), ny0 = vertices_normals(p,1), nz0 = vertices_normals(p,2), ++ nx1 = vertices_normals(p,3), ny1 = vertices_normals(p,4), nz1 = vertices_normals(p,5), ++ n0 = nx0*nx0 + ny0*ny0 + nz0*nz0, n1 = nx1*nx1 + ny1*ny1 + nz1*nz1; ++ if (n1>n0) { ++ vertices_normals(p,0) = -nx1; ++ vertices_normals(p,1) = -ny1; ++ vertices_normals(p,2) = -nz1; ++ } ++ } ++ ++ if (render_type==4) { ++ lightprops.assign(vertices._width); ++ cimg_pragma_openmp(parallel for cimg_openmp_if(nb_visibles>4096)) ++ cimg_forX(lightprops,l) { ++ const tpfloat ++ nx = vertices_normals(l,0), ++ ny = vertices_normals(l,1), ++ nz = vertices_normals(l,2), ++ norm = 1e-5f + cimg::hypot(nx,ny,nz), ++ lx = X + vertices(l,0) - lightx, ++ ly = Y + vertices(l,1) - lighty, ++ lz = Z + vertices(l,2) - lightz, ++ nl = 1e-5f + cimg::hypot(lx,ly,lz), ++ factor = std::max((-lx*nx - ly*ny - lz*nz)/(norm*nl),(tpfloat)0); ++ lightprops[l] = 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(vertices._width,2); ++ cimg_pragma_openmp(parallel for cimg_openmp_if(nb_visibles>4096)) ++ cimg_forX(lightprops,l) { ++ const tpfloat ++ nx = vertices_normals(l,0), ++ ny = vertices_normals(l,1), ++ nz = vertices_normals(l,2), ++ norm = 1e-5f + cimg::hypot(nx,ny,nz), ++ nnx = nx/norm, ++ nny = ny/norm; ++ lightprops(l,0) = lw2*(1 + nnx); ++ lightprops(l,1) = lh2*(1 + nny); ++ } ++ } ++ } break; ++ } ++ ++ // Draw visible primitives ++ const CImg default_color(1,_spectrum,1,1,(tc)200); ++ CImg<_to> _opacity; ++ ++ for (unsigned int l = 0; l& primitive = primitives[n_primitive]; ++ const CImg ++ &__color = n_primitive(), ++ _color = (__color && __color.size()!=_spectrum && __color._spectrum<_spectrum)? ++ __color.get_resize(-100,-100,-100,_spectrum,0):CImg(), ++ &color = _color?_color:(__color?__color:default_color); ++ const tc *const pcolor = color._data; ++ const float opacity = __draw_object3d(opacities,n_primitive,_opacity); ++ ++#ifdef cimg_use_board ++ LibBoard::Board &board = *(LibBoard::Board*)pboard; ++#endif ++ ++ switch (primitive.size()) { ++ case 1 : { // Colored point or sprite ++ const unsigned int n0 = (unsigned int)primitive[0]; ++ const int x0 = (int)projections(n0,0), y0 = (int)projections(n0,1); ++ ++ if (_opacity.is_empty()) { // Scalar opacity. ++ ++ if (color.size()==_spectrum) { // Colored point. ++ draw_point(x0,y0,pcolor,opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); ++ board.drawDot((float)x0,height()-(float)y0); ++ } ++#endif ++ } else { // Sprite. ++ const tpfloat z = Z + vertices(n0,2); ++ const float factor = focale<0?1:sprite_scale*(absfocale?absfocale/(z + absfocale):1); ++ const unsigned int ++ _sw = (unsigned int)(color._width*factor), ++ _sh = (unsigned int)(color._height*factor), ++ sw = _sw?_sw:1, sh = _sh?_sh:1; ++ const int nx0 = x0 - (int)sw/2, ny0 = y0 - (int)sh/2; ++ if (sw<=3*_width/2 && sh<=3*_height/2 && ++ (nx0 + (int)sw/2>=0 || nx0 - (int)sw/2=0 || ny0 - (int)sh/2 ++ _sprite = (sw!=color._width || sh!=color._height)? ++ color.get_resize(sw,sh,1,-100,render_type<=3?1:3):CImg(), ++ &sprite = _sprite?_sprite:color; ++ draw_image(nx0,ny0,sprite,opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi(128,128,128); ++ board.setFillColor(LibBoard::Color::Null); ++ board.drawRectangle((float)nx0,height() - (float)ny0,sw,sh); ++ } ++#endif ++ } ++ } ++ } else { // Opacity mask. ++ const tpfloat z = Z + vertices(n0,2); ++ const float factor = focale<0?1:sprite_scale*(absfocale?absfocale/(z + absfocale):1); ++ const unsigned int ++ _sw = (unsigned int)(std::max(color._width,_opacity._width)*factor), ++ _sh = (unsigned int)(std::max(color._height,_opacity._height)*factor), ++ sw = _sw?_sw:1, sh = _sh?_sh:1; ++ const int nx0 = x0 - (int)sw/2, ny0 = y0 - (int)sh/2; ++ if (sw<=3*_width/2 && sh<=3*_height/2 && ++ (nx0 + (int)sw/2>=0 || nx0 - (int)sw/2=0 || ny0 - (int)sh/2 ++ _sprite = (sw!=color._width || sh!=color._height)? ++ color.get_resize(sw,sh,1,-100,render_type<=3?1:3):CImg(), ++ &sprite = _sprite?_sprite:color; ++ const CImg<_to> ++ _nopacity = (sw!=_opacity._width || sh!=_opacity._height)? ++ _opacity.get_resize(sw,sh,1,-100,render_type<=3?1:3):CImg<_to>(), ++ &nopacity = _nopacity?_nopacity:_opacity; ++ draw_image(nx0,ny0,sprite,nopacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi(128,128,128); ++ board.setFillColor(LibBoard::Color::Null); ++ board.drawRectangle((float)nx0,height() - (float)ny0,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 = vertices(n0,2) + Z + _focale, ++ z1 = vertices(n1,2) + Z + _focale; ++ if (render_type) { ++ if (zbuffer) draw_line(zbuffer,x0,y0,z0,x1,y1,z1,pcolor,opacity); ++ else draw_line(x0,y0,x1,y1,pcolor,opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); ++ board.drawLine((float)x0,height() - (float)y0,x1,height() - (float)y1); ++ } ++#endif ++ } else { ++ draw_point(x0,y0,pcolor,opacity).draw_point(x1,y1,pcolor,opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); ++ board.drawDot((float)x0,height() - (float)y0); ++ board.drawDot((float)x1,height() - (float)y1); ++ } ++#endif ++ } ++ } break; ++ case 5 : { // Colored sphere ++ const unsigned int ++ n0 = (unsigned int)primitive[0], ++ n1 = (unsigned int)primitive[1], ++ is_wireframe = (unsigned int)primitive[2]; ++ const float ++ Xc = 0.5f*((float)vertices(n0,0) + (float)vertices(n1,0)), ++ Yc = 0.5f*((float)vertices(n0,1) + (float)vertices(n1,1)), ++ Zc = 0.5f*((float)vertices(n0,2) + (float)vertices(n1,2)), ++ zc = Z + Zc + _focale, ++ xc = X + Xc*(absfocale?absfocale/zc:1), ++ yc = Y + Yc*(absfocale?absfocale/zc:1), ++ radius = 0.5f*cimg::hypot(vertices(n1,0) - vertices(n0,0), ++ vertices(n1,1) - vertices(n0,1), ++ vertices(n1,2) - vertices(n0,2))*(absfocale?absfocale/zc:1); ++ switch (render_type) { ++ case 0 : ++ draw_point((int)xc,(int)yc,pcolor,opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); ++ board.drawDot(xc,height() - yc); ++ } ++#endif ++ break; ++ case 1 : ++ draw_circle((int)xc,(int)yc,(int)radius,pcolor,opacity,~0U); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); ++ board.setFillColor(LibBoard::Color::Null); ++ board.drawCircle(xc,height() - yc,radius); ++ } ++#endif ++ break; ++ default : ++ if (is_wireframe) draw_circle((int)xc,(int)yc,(int)radius,pcolor,opacity,~0U); ++ else draw_circle((int)xc,(int)yc,(int)radius,pcolor,opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); ++ if (!is_wireframe) board.fillCircle(xc,height() - yc,radius); ++ else { ++ board.setFillColor(LibBoard::Color::Null); ++ board.drawCircle(xc,height() - yc,radius); ++ } ++ } ++#endif ++ break; ++ } ++ } break; ++ case 6 : { // Textured line ++ if (!__color) { ++ if (render_type==5) cimg::mutex(10,0); ++ throw CImgArgumentException(_cimg_instance ++ "draw_object3d(): Undefined texture for line primitive [%u].", ++ cimg_instance,n_primitive); ++ } ++ const unsigned int ++ n0 = (unsigned int)primitive[0], ++ n1 = (unsigned int)primitive[1]; ++ const int ++ tx0 = (int)primitive[2], ty0 = (int)primitive[3], ++ tx1 = (int)primitive[4], ty1 = (int)primitive[5], ++ x0 = (int)projections(n0,0), y0 = (int)projections(n0,1), ++ x1 = (int)projections(n1,0), y1 = (int)projections(n1,1); ++ const float ++ z0 = vertices(n0,2) + Z + _focale, ++ z1 = vertices(n1,2) + Z + _focale; ++ if (render_type) { ++ if (zbuffer) draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity); ++ else draw_line(x0,y0,x1,y1,color,tx0,ty0,tx1,ty1,opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); ++ board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1); ++ } ++#endif ++ } else { ++ draw_point(x0,y0,color.get_vector_at(tx0<=0?0:tx0>=color.width()?color.width() - 1:tx0, ++ ty0<=0?0:ty0>=color.height()?color.height() - 1:ty0)._data,opacity). ++ draw_point(x1,y1,color.get_vector_at(tx1<=0?0:tx1>=color.width()?color.width() - 1:tx1, ++ ty1<=0?0:ty1>=color.height()?color.height() - 1:ty1)._data,opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); ++ board.drawDot((float)x0,height() - (float)y0); ++ board.drawDot((float)x1,height() - (float)y1); ++ } ++#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 = vertices(n0,2) + Z + _focale, ++ z1 = vertices(n1,2) + Z + _focale, ++ z2 = vertices(n2,2) + Z + _focale; ++ switch (render_type) { ++ case 0 : ++ draw_point(x0,y0,pcolor,opacity).draw_point(x1,y1,pcolor,opacity).draw_point(x2,y2,pcolor,opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); ++ board.drawDot((float)x0,height() - (float)y0); ++ board.drawDot((float)x1,height() - (float)y1); ++ board.drawDot((float)x2,height() - (float)y2); ++ } ++#endif ++ break; ++ case 1 : ++ if (zbuffer) ++ draw_line(zbuffer,x0,y0,z0,x1,y1,z1,pcolor,opacity).draw_line(zbuffer,x0,y0,z0,x2,y2,z2,pcolor,opacity). ++ draw_line(zbuffer,x1,y1,z1,x2,y2,z2,pcolor,opacity); ++ else ++ draw_line(x0,y0,x1,y1,pcolor,opacity).draw_line(x0,y0,x2,y2,pcolor,opacity). ++ draw_line(x1,y1,x2,y2,pcolor,opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); ++ board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1); ++ board.drawLine((float)x0,height() - (float)y0,(float)x2,height() - (float)y2); ++ board.drawLine((float)x1,height() - (float)y1,(float)x2,height() - (float)y2); ++ } ++#endif ++ break; ++ case 2 : ++ if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opacity); ++ else draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); ++ board.fillTriangle((float)x0,height() - (float)y0, ++ (float)x1,height() - (float)y1, ++ (float)x2,height() - (float)y2); ++ } ++#endif ++ break; ++ case 3 : ++ if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opacity,lightprops(l)); ++ else _draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opacity,lightprops(l)); ++#ifdef cimg_use_board ++ if (pboard) { ++ const float lp = std::min(lightprops(l),1); ++ board.setPenColorRGBi((unsigned char)(color[0]*lp), ++ (unsigned char)(color[1]*lp), ++ (unsigned char)(color[2]*lp), ++ (unsigned char)(opacity*255)); ++ board.fillTriangle((float)x0,height() - (float)y0, ++ (float)x1,height() - (float)y1, ++ (float)x2,height() - (float)y2); ++ } ++#endif ++ break; ++ case 4 : ++ if (zbuffer) ++ draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor, ++ lightprops(n0),lightprops(n1),lightprops(n2),opacity); ++ else draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,lightprops(n0),lightprops(n1),lightprops(n2),opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi((unsigned char)(color[0]), ++ (unsigned char)(color[1]), ++ (unsigned char)(color[2]), ++ (unsigned char)(opacity*255)); ++ board.fillGouraudTriangle((float)x0,height() - (float)y0,lightprops(n0), ++ (float)x1,height() - (float)y1,lightprops(n1), ++ (float)x2,height() - (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,pcolor,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opacity); ++ else draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ const float ++ l0 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n0,0))), ++ (int)(light_texture.height()/2*(1 + lightprops(n0,1)))), ++ l1 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n1,0))), ++ (int)(light_texture.height()/2*(1 + lightprops(n1,1)))), ++ l2 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n2,0))), ++ (int)(light_texture.height()/2*(1 + lightprops(n2,1)))); ++ board.setPenColorRGBi((unsigned char)(color[0]), ++ (unsigned char)(color[1]), ++ (unsigned char)(color[2]), ++ (unsigned char)(opacity*255)); ++ board.fillGouraudTriangle((float)x0,height() - (float)y0,l0, ++ (float)x1,height() - (float)y1,l1, ++ (float)x2,height() - (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 = vertices(n0,2) + Z + _focale, ++ z1 = vertices(n1,2) + Z + _focale, ++ z2 = vertices(n2,2) + Z + _focale, ++ z3 = vertices(n3,2) + Z + _focale; ++ ++ switch (render_type) { ++ case 0 : ++ draw_point(x0,y0,pcolor,opacity).draw_point(x1,y1,pcolor,opacity). ++ draw_point(x2,y2,pcolor,opacity).draw_point(x3,y3,pcolor,opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); ++ board.drawDot((float)x0,height() - (float)y0); ++ board.drawDot((float)x1,height() - (float)y1); ++ board.drawDot((float)x2,height() - (float)y2); ++ board.drawDot((float)x3,height() - (float)y3); ++ } ++#endif ++ break; ++ case 1 : ++ if (zbuffer) ++ draw_line(zbuffer,x0,y0,z0,x1,y1,z1,pcolor,opacity).draw_line(zbuffer,x1,y1,z1,x2,y2,z2,pcolor,opacity). ++ draw_line(zbuffer,x2,y2,z2,x3,y3,z3,pcolor,opacity).draw_line(zbuffer,x3,y3,z3,x0,y0,z0,pcolor,opacity); ++ else ++ draw_line(x0,y0,x1,y1,pcolor,opacity).draw_line(x1,y1,x2,y2,pcolor,opacity). ++ draw_line(x2,y2,x3,y3,pcolor,opacity).draw_line(x3,y3,x0,y0,pcolor,opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); ++ board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1); ++ board.drawLine((float)x1,height() - (float)y1,(float)x2,height() - (float)y2); ++ board.drawLine((float)x2,height() - (float)y2,(float)x3,height() - (float)y3); ++ board.drawLine((float)x3,height() - (float)y3,(float)x0,height() - (float)y0); ++ } ++#endif ++ break; ++ case 2 : ++ if (zbuffer) ++ draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opacity). ++ draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,pcolor,opacity); ++ else ++ draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opacity).draw_triangle(x0,y0,x2,y2,x3,y3,pcolor,opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255)); ++ board.fillTriangle((float)x0,height() - (float)y0, ++ (float)x1,height() - (float)y1, ++ (float)x2,height() - (float)y2); ++ board.fillTriangle((float)x0,height() - (float)y0, ++ (float)x2,height() - (float)y2, ++ (float)x3,height() - (float)y3); ++ } ++#endif ++ break; ++ case 3 : ++ if (zbuffer) ++ draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opacity,lightprops(l)). ++ draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,pcolor,opacity,lightprops(l)); ++ else ++ _draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opacity,lightprops(l)). ++ _draw_triangle(x0,y0,x2,y2,x3,y3,pcolor,opacity,lightprops(l)); ++#ifdef cimg_use_board ++ if (pboard) { ++ const float lp = std::min(lightprops(l),1); ++ board.setPenColorRGBi((unsigned char)(color[0]*lp), ++ (unsigned char)(color[1]*lp), ++ (unsigned char)(color[2]*lp),(unsigned char)(opacity*255)); ++ board.fillTriangle((float)x0,height() - (float)y0, ++ (float)x1,height() - (float)y1, ++ (float)x2,height() - (float)y2); ++ board.fillTriangle((float)x0,height() - (float)y0, ++ (float)x2,height() - (float)y2, ++ (float)x3,height() - (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,pcolor,lightprop0,lightprop1,lightprop2,opacity). ++ draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,pcolor,lightprop0,lightprop2,lightprop3,opacity); ++ else ++ draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,lightprop0,lightprop1,lightprop2,opacity). ++ draw_triangle(x0,y0,x2,y2,x3,y3,pcolor,lightprop0,lightprop2,lightprop3,opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi((unsigned char)(color[0]), ++ (unsigned char)(color[1]), ++ (unsigned char)(color[2]), ++ (unsigned char)(opacity*255)); ++ board.fillGouraudTriangle((float)x0,height() - (float)y0,lightprop0, ++ (float)x1,height() - (float)y1,lightprop1, ++ (float)x2,height() - (float)y2,lightprop2); ++ board.fillGouraudTriangle((float)x0,height() - (float)y0,lightprop0, ++ (float)x2,height() - (float)y2,lightprop2, ++ (float)x3,height() - (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,pcolor,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opacity). ++ draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,pcolor,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opacity); ++ else ++ draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opacity). ++ draw_triangle(x0,y0,x2,y2,x3,y3,pcolor,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ const float ++ l0 = light_texture((int)(light_texture.width()/2*(1 + lx0)), (int)(light_texture.height()/2*(1 + ly0))), ++ l1 = light_texture((int)(light_texture.width()/2*(1 + lx1)), (int)(light_texture.height()/2*(1 + ly1))), ++ l2 = light_texture((int)(light_texture.width()/2*(1 + lx2)), (int)(light_texture.height()/2*(1 + ly2))), ++ l3 = light_texture((int)(light_texture.width()/2*(1 + lx3)), (int)(light_texture.height()/2*(1 + ly3))); ++ board.setPenColorRGBi((unsigned char)(color[0]), ++ (unsigned char)(color[1]), ++ (unsigned char)(color[2]), ++ (unsigned char)(opacity*255)); ++ board.fillGouraudTriangle((float)x0,height() - (float)y0,l0, ++ (float)x1,height() - (float)y1,l1, ++ (float)x2,height() - (float)y2,l2); ++ board.fillGouraudTriangle((float)x0,height() - (float)y0,l0, ++ (float)x2,height() - (float)y2,l2, ++ (float)x3,height() - (float)y3,l3); ++ } ++#endif ++ } break; ++ } ++ } break; ++ case 9 : { // Textured triangle ++ if (!__color) { ++ if (render_type==5) cimg::mutex(10,0); ++ throw CImgArgumentException(_cimg_instance ++ "draw_object3d(): Undefined texture for triangle primitive [%u].", ++ cimg_instance,n_primitive); ++ } ++ const unsigned int ++ n0 = (unsigned int)primitive[0], ++ n1 = (unsigned int)primitive[1], ++ n2 = (unsigned int)primitive[2]; ++ const int ++ tx0 = (int)primitive[3], ty0 = (int)primitive[4], ++ tx1 = (int)primitive[5], ty1 = (int)primitive[6], ++ tx2 = (int)primitive[7], ty2 = (int)primitive[8], ++ 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 = vertices(n0,2) + Z + _focale, ++ z1 = vertices(n1,2) + Z + _focale, ++ z2 = vertices(n2,2) + Z + _focale; ++ switch (render_type) { ++ case 0 : ++ draw_point(x0,y0,color.get_vector_at(tx0<=0?0:tx0>=color.width()?color.width() - 1:tx0, ++ ty0<=0?0:ty0>=color.height()?color.height() - 1:ty0)._data,opacity). ++ draw_point(x1,y1,color.get_vector_at(tx1<=0?0:tx1>=color.width()?color.width() - 1:tx1, ++ ty1<=0?0:ty1>=color.height()?color.height() - 1:ty1)._data,opacity). ++ draw_point(x2,y2,color.get_vector_at(tx2<=0?0:tx2>=color.width()?color.width() - 1:tx2, ++ ty2<=0?0:ty2>=color.height()?color.height() - 1:ty2)._data,opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); ++ board.drawDot((float)x0,height() - (float)y0); ++ board.drawDot((float)x1,height() - (float)y1); ++ board.drawDot((float)x2,height() - (float)y2); ++ } ++#endif ++ break; ++ case 1 : ++ if (zbuffer) ++ draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity). ++ draw_line(zbuffer,x0,y0,z0,x2,y2,z2,color,tx0,ty0,tx2,ty2,opacity). ++ draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opacity); ++ else ++ draw_line(x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity). ++ draw_line(x0,y0,z0,x2,y2,z2,color,tx0,ty0,tx2,ty2,opacity). ++ draw_line(x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); ++ board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1); ++ board.drawLine((float)x0,height() - (float)y0,(float)x2,height() - (float)y2); ++ board.drawLine((float)x1,height() - (float)y1,(float)x2,height() - (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,opacity); ++ else draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); ++ board.fillTriangle((float)x0,height() - (float)y0, ++ (float)x1,height() - (float)y1, ++ (float)x2,height() - (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,opacity,lightprops(l)); ++ else draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity,lightprops(l)); ++#ifdef cimg_use_board ++ if (pboard) { ++ const float lp = std::min(lightprops(l),1); ++ board.setPenColorRGBi((unsigned char)(128*lp), ++ (unsigned char)(128*lp), ++ (unsigned char)(128*lp), ++ (unsigned char)(opacity*255)); ++ board.fillTriangle((float)x0,height() - (float)y0, ++ (float)x1,height() - (float)y1, ++ (float)x2,height() - (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),opacity); ++ 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),opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); ++ board.fillGouraudTriangle((float)x0,height() - (float)y0,lightprops(n0), ++ (float)x1,height() - (float)y1,lightprops(n1), ++ (float)x2,height() - (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), ++ opacity); ++ 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), ++ opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ const float ++ l0 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n0,0))), ++ (int)(light_texture.height()/2*(1 + lightprops(n0,1)))), ++ l1 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n1,0))), ++ (int)(light_texture.height()/2*(1 + lightprops(n1,1)))), ++ l2 = light_texture((int)(light_texture.width()/2*(1 + lightprops(n2,0))), ++ (int)(light_texture.height()/2*(1 + lightprops(n2,1)))); ++ board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); ++ board.fillGouraudTriangle((float)x0,height() - (float)y0,l0, ++ (float)x1,height() - (float)y1,l1, ++ (float)x2,height() - (float)y2,l2); ++ } ++#endif ++ break; ++ } ++ } break; ++ case 12 : { // Textured quadrangle ++ if (!__color) { ++ if (render_type==5) cimg::mutex(10,0); ++ throw CImgArgumentException(_cimg_instance ++ "draw_object3d(): Undefined texture for quadrangle primitive [%u].", ++ cimg_instance,n_primitive); ++ } ++ 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 ++ tx0 = (int)primitive[4], ty0 = (int)primitive[5], ++ tx1 = (int)primitive[6], ty1 = (int)primitive[7], ++ tx2 = (int)primitive[8], ty2 = (int)primitive[9], ++ tx3 = (int)primitive[10], ty3 = (int)primitive[11], ++ 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 = vertices(n0,2) + Z + _focale, ++ z1 = vertices(n1,2) + Z + _focale, ++ z2 = vertices(n2,2) + Z + _focale, ++ z3 = vertices(n3,2) + Z + _focale; ++ ++ switch (render_type) { ++ case 0 : ++ draw_point(x0,y0,color.get_vector_at(tx0<=0?0:tx0>=color.width()?color.width() - 1:tx0, ++ ty0<=0?0:ty0>=color.height()?color.height() - 1:ty0)._data,opacity). ++ draw_point(x1,y1,color.get_vector_at(tx1<=0?0:tx1>=color.width()?color.width() - 1:tx1, ++ ty1<=0?0:ty1>=color.height()?color.height() - 1:ty1)._data,opacity). ++ draw_point(x2,y2,color.get_vector_at(tx2<=0?0:tx2>=color.width()?color.width() - 1:tx2, ++ ty2<=0?0:ty2>=color.height()?color.height() - 1:ty2)._data,opacity). ++ draw_point(x3,y3,color.get_vector_at(tx3<=0?0:tx3>=color.width()?color.width() - 1:tx3, ++ ty3<=0?0:ty3>=color.height()?color.height() - 1:ty3)._data,opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); ++ board.drawDot((float)x0,height() - (float)y0); ++ board.drawDot((float)x1,height() - (float)y1); ++ board.drawDot((float)x2,height() - (float)y2); ++ board.drawDot((float)x3,height() - (float)y3); ++ } ++#endif ++ break; ++ case 1 : ++ if (zbuffer) ++ draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity). ++ draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opacity). ++ draw_line(zbuffer,x2,y2,z2,x3,y3,z3,color,tx2,ty2,tx3,ty3,opacity). ++ draw_line(zbuffer,x3,y3,z3,x0,y0,z0,color,tx3,ty3,tx0,ty0,opacity); ++ else ++ draw_line(x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity). ++ draw_line(x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opacity). ++ draw_line(x2,y2,z2,x3,y3,z3,color,tx2,ty2,tx3,ty3,opacity). ++ draw_line(x3,y3,z3,x0,y0,z0,color,tx3,ty3,tx0,ty0,opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); ++ board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1); ++ board.drawLine((float)x1,height() - (float)y1,(float)x2,height() - (float)y2); ++ board.drawLine((float)x2,height() - (float)y2,(float)x3,height() - (float)y3); ++ board.drawLine((float)x3,height() - (float)y3,(float)x0,height() - (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,opacity). ++ draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opacity); ++ else ++ draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity). ++ draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); ++ board.fillTriangle((float)x0,height() - (float)y0, ++ (float)x1,height() - (float)y1, ++ (float)x2,height() - (float)y2); ++ board.fillTriangle((float)x0,height() - (float)y0, ++ (float)x2,height() - (float)y2, ++ (float)x3,height() - (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,opacity,lightprops(l)). ++ draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opacity,lightprops(l)); ++ else ++ draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity,lightprops(l)). ++ draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opacity,lightprops(l)); ++#ifdef cimg_use_board ++ if (pboard) { ++ const float lp = std::min(lightprops(l),1); ++ board.setPenColorRGBi((unsigned char)(128*lp), ++ (unsigned char)(128*lp), ++ (unsigned char)(128*lp), ++ (unsigned char)(opacity*255)); ++ board.fillTriangle((float)x0,height() - (float)y0, ++ (float)x1,height() - (float)y1, ++ (float)x2,height() - (float)y2); ++ board.fillTriangle((float)x0,height() - (float)y0, ++ (float)x2,height() - (float)y2, ++ (float)x3,height() - (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,opacity). ++ draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3, ++ lightprop0,lightprop2,lightprop3,opacity); ++ else ++ draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2, ++ lightprop0,lightprop1,lightprop2,opacity). ++ draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3, ++ lightprop0,lightprop2,lightprop3,opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); ++ board.fillGouraudTriangle((float)x0,height() - (float)y0,lightprop0, ++ (float)x1,height() - (float)y1,lightprop1, ++ (float)x2,height() - (float)y2,lightprop2); ++ board.fillGouraudTriangle((float)x0,height() -(float)y0,lightprop0, ++ (float)x2,height() - (float)y2,lightprop2, ++ (float)x3,height() - (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,opacity). ++ 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,opacity); ++ 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,opacity). ++ 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,opacity); ++#ifdef cimg_use_board ++ if (pboard) { ++ const float ++ l0 = light_texture((int)(light_texture.width()/2*(1 + lx0)), (int)(light_texture.height()/2*(1 + ly0))), ++ l1 = light_texture((int)(light_texture.width()/2*(1 + lx1)), (int)(light_texture.height()/2*(1 + ly1))), ++ l2 = light_texture((int)(light_texture.width()/2*(1 + lx2)), (int)(light_texture.height()/2*(1 + ly2))), ++ l3 = light_texture((int)(light_texture.width()/2*(1 + lx3)), (int)(light_texture.height()/2*(1 + ly3))); ++ board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); ++ board.fillGouraudTriangle((float)x0,height() - (float)y0,l0, ++ (float)x1,height() - (float)y1,l1, ++ (float)x2,height() - (float)y2,l2); ++ board.fillGouraudTriangle((float)x0,height() -(float)y0,l0, ++ (float)x2,height() - (float)y2,l2, ++ (float)x3,height() - (float)y3,l3); ++ } ++#endif ++ } break; ++ } ++ } break; ++ } ++ } ++ ++ if (render_type==5) cimg::mutex(10,0); ++ return *this; ++ } ++ ++ //@} ++ //--------------------------- ++ // ++ //! \name Data Input ++ //@{ ++ //--------------------------- ++ ++ //! Launch simple interface to select a shape from an image. ++ /** ++ \param disp Display window to use. ++ \param feature_type Type of feature to select. Can be { 0=point | 1=line | 2=rectangle | 3=ellipse }. ++ \param XYZ Pointer to 3 values X,Y,Z which tells about the projection point coordinates, for volumetric images. ++ \param exit_on_anykey Exit function when any key is pressed. ++ **/ ++ CImg& select(CImgDisplay &disp, ++ const unsigned int feature_type=2, unsigned int *const XYZ=0, ++ const bool exit_on_anykey=false) { ++ return get_select(disp,feature_type,XYZ,exit_on_anykey).move_to(*this); ++ } ++ ++ //! Simple interface to select a shape from an image \overloading. ++ CImg& select(const char *const title, ++ const unsigned int feature_type=2, unsigned int *const XYZ=0, ++ const bool exit_on_anykey=false) { ++ return get_select(title,feature_type,XYZ,exit_on_anykey).move_to(*this); ++ } ++ ++ //! Simple interface to select a shape from an image \newinstance. ++ CImg get_select(CImgDisplay &disp, ++ const unsigned int feature_type=2, unsigned int *const XYZ=0, ++ const bool exit_on_anykey=false) const { ++ return _select(disp,0,feature_type,XYZ,0,0,0,exit_on_anykey,true,false); ++ } ++ ++ //! Simple interface to select a shape from an image \newinstance. ++ CImg get_select(const char *const title, ++ const unsigned int feature_type=2, unsigned int *const XYZ=0, ++ const bool exit_on_anykey=false) const { ++ CImgDisplay disp; ++ return _select(disp,title,feature_type,XYZ,0,0,0,exit_on_anykey,true,false); ++ } ++ ++ CImg _select(CImgDisplay &disp, const char *const title, ++ const unsigned int feature_type, unsigned int *const XYZ, ++ const int origX, const int origY, const int origZ, ++ const bool exit_on_anykey, ++ const bool reset_view3d, ++ const bool force_display_z_coord) const { ++ if (is_empty()) return CImg(1,feature_type==0?3:6,1,1,-1); ++ if (!disp) { ++ disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:0,1); ++ if (!title) disp.set_title("CImg<%s> (%ux%ux%ux%u)",pixel_type(),_width,_height,_depth,_spectrum); ++ } else if (title) disp.set_title("%s",title); ++ ++ CImg thumb; ++ if (width()>disp.screen_width() || height()>disp.screen_height()) ++ get_resize(cimg_fitscreen(width(),height(),depth()),depth(),-100).move_to(thumb); ++ ++ const unsigned int old_normalization = disp.normalization(); ++ bool old_is_resized = disp.is_resized(); ++ disp._normalization = 0; ++ disp.show().set_key(0).set_wheel().show_mouse(); ++ ++ static const unsigned char foreground_color[] = { 255,255,255 }, background_color[] = { 0,0,0 }; ++ ++ int area = 0, starting_area = 0, clicked_area = 0, phase = 0, ++ X0 = (int)((XYZ?XYZ[0]:(_width - 1)/2)%_width), ++ Y0 = (int)((XYZ?XYZ[1]:(_height - 1)/2)%_height), ++ Z0 = (int)((XYZ?XYZ[2]:(_depth - 1)/2)%_depth), ++ X1 =-1, Y1 = -1, Z1 = -1, ++ X3d = -1, Y3d = -1, ++ oX3d = X3d, oY3d = -1, ++ omx = -1, omy = -1; ++ float X = -1, Y = -1, Z = -1; ++ unsigned int old_button = 0, key = 0; ++ ++ bool shape_selected = false, text_down = false, visible_cursor = true; ++ static CImg pose3d; ++ static bool is_view3d = false, is_axes = true; ++ if (reset_view3d) { pose3d.assign(); is_view3d = false; } ++ CImg points3d, opacities3d, sel_opacities3d; ++ CImgList primitives3d, sel_primitives3d; ++ CImgList colors3d, sel_colors3d; ++ CImg visu, visu0, view3d; ++ CImg text(1024); *text = 0; ++ ++ while (!key && !disp.is_closed() && !shape_selected) { ++ ++ // Handle mouse motion and selection ++ int ++ mx = disp.mouse_x(), ++ my = disp.mouse_y(); ++ ++ const float ++ mX = mx<0?-1.0f:(float)mx*(width() + (depth()>1?depth():0))/disp.width(), ++ mY = my<0?-1.0f:(float)my*(height() + (depth()>1?depth():0))/disp.height(); ++ ++ area = 0; ++ if (mX>=0 && mY>=0 && mX=0 && mX=height()) { area = 2; X = mX; Z = mY - _height; Y = (float)(phase?Y1:Y0); } ++ if (mY>=0 && mX>=width() && mY=width() && mY>=height()) area = 4; ++ if (disp.button()) { if (!clicked_area) clicked_area = area; } else clicked_area = 0; ++ ++ CImg filename(32); ++ ++ switch (key = disp.key()) { ++#if cimg_OS!=2 ++ case cimg::keyCTRLRIGHT : ++#endif ++ case 0 : case cimg::keyCTRLLEFT : key = 0; break; ++ case cimg::keyPAGEUP : ++ if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { disp.set_wheel(1); key = 0; } break; ++ case cimg::keyPAGEDOWN : ++ if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { disp.set_wheel(-1); key = 0; } break; ++ case cimg::keyA : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ is_axes = !is_axes; disp.set_key(key,false); key = 0; visu0.assign(); ++ } break; ++ case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ disp.set_fullscreen(false). ++ 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.set_key(key,false); key = 0; visu0.assign(); ++ } break; ++ case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ disp.set_fullscreen(false). ++ resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true; ++ disp.set_key(key,false); key = 0; visu0.assign(); ++ } break; ++ case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ disp.set_fullscreen(false).resize(cimg_fitscreen(_width,_height,_depth),false)._is_resized = true; ++ disp.set_key(key,false); key = 0; visu0.assign(); ++ } break; ++ case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ disp.resize(disp.screen_width(),disp.screen_height(),false).toggle_fullscreen()._is_resized = true; ++ disp.set_key(key,false); key = 0; visu0.assign(); ++ } break; ++ case cimg::keyV : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ is_view3d = !is_view3d; disp.set_key(key,false); key = 0; visu0.assign(); ++ } break; ++ case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ static unsigned int snap_number = 0; ++ std::FILE *file; ++ do { ++ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++); ++ if ((file=std_fopen(filename,"r"))!=0) cimg::fclose(file); ++ } while (file); ++ if (visu0) { ++ (+visu0).draw_text(0,0," Saving snapshot... ",foreground_color,background_color,0.7f,13).display(disp); ++ visu0.save(filename); ++ (+visu0).draw_text(0,0," Snapshot '%s' saved. ",foreground_color,background_color,0.7f,13,filename._data). ++ display(disp); ++ } ++ disp.set_key(key,false); key = 0; ++ } break; ++ case cimg::keyO : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ static unsigned int snap_number = 0; ++ std::FILE *file; ++ do { ++#ifdef cimg_use_zlib ++ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); ++#else ++ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); ++#endif ++ if ((file=std_fopen(filename,"r"))!=0) cimg::fclose(file); ++ } while (file); ++ (+visu0).draw_text(0,0," Saving instance... ",foreground_color,background_color,0.7f,13).display(disp); ++ save(filename); ++ (+visu0).draw_text(0,0," Instance '%s' saved. ",foreground_color,background_color,0.7f,13,filename._data). ++ display(disp); ++ disp.set_key(key,false); key = 0; ++ } break; ++ } ++ ++ switch (area) { ++ ++ case 0 : // When mouse is out of image range. ++ mx = my = -1; X = Y = Z = -1; ++ break; ++ ++ case 1 : case 2 : case 3 : // When mouse is over the XY,XZ or YZ projections. ++ if (disp.button()&1 && phase<2 && clicked_area==area) { // When selection has been started (1st step). ++ if (_depth>1 && (X1!=(int)X || Y1!=(int)Y || Z1!=(int)Z)) visu0.assign(); ++ X1 = (int)X; Y1 = (int)Y; Z1 = (int)Z; ++ } ++ if (!(disp.button()&1) && phase>=2 && clicked_area!=area) { // When selection is at 2nd step (for volumes). ++ switch (starting_area) { ++ case 1 : if (Z1!=(int)Z) visu0.assign(); Z1 = (int)Z; break; ++ case 2 : if (Y1!=(int)Y) visu0.assign(); Y1 = (int)Y; break; ++ case 3 : if (X1!=(int)X) visu0.assign(); X1 = (int)X; break; ++ } ++ } ++ if (disp.button()&2 && clicked_area==area) { // When moving through the image/volume. ++ if (phase) { ++ if (_depth>1 && (X1!=(int)X || Y1!=(int)Y || Z1!=(int)Z)) visu0.assign(); ++ X1 = (int)X; Y1 = (int)Y; Z1 = (int)Z; ++ } else { ++ if (_depth>1 && (X0!=(int)X || Y0!=(int)Y || Z0!=(int)Z)) visu0.assign(); ++ X0 = (int)X; Y0 = (int)Y; Z0 = (int)Z; ++ } ++ } ++ if (disp.button()&4) { ++ X = (float)X0; Y = (float)Y0; Z = (float)Z0; phase = area = clicked_area = starting_area = 0; ++ visu0.assign(); ++ } ++ if (disp.wheel()) { // When moving through the slices of the volume (with mouse wheel). ++ if (_depth>1 && !disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT() && ++ !disp.is_keySHIFTLEFT() && !disp.is_keySHIFTRIGHT()) { ++ switch (area) { ++ case 1 : ++ if (phase) Z = (float)(Z1+=disp.wheel()); else Z = (float)(Z0+=disp.wheel()); ++ visu0.assign(); break; ++ case 2 : ++ if (phase) Y = (float)(Y1+=disp.wheel()); else Y = (float)(Y0+=disp.wheel()); ++ visu0.assign(); break; ++ case 3 : ++ if (phase) X = (float)(X1+=disp.wheel()); else X = (float)(X0+=disp.wheel()); ++ visu0.assign(); break; ++ } ++ disp.set_wheel(); ++ } else key = ~0U; ++ } ++ if ((disp.button()&1)!=old_button) { // When left button has just been pressed or released. ++ switch (phase) { ++ case 0 : ++ if (area==clicked_area) { ++ X0 = X1 = (int)X; Y0 = Y1 = (int)Y; Z0 = Z1 = (int)Z; starting_area = area; ++phase; ++ } break; ++ case 1 : ++ if (area==starting_area) { ++ X1 = (int)X; Y1 = (int)Y; Z1 = (int)Z; ++phase; ++ } else if (!(disp.button()&1)) { X = (float)X0; Y = (float)Y0; Z = (float)Z0; phase = 0; visu0.assign(); } ++ break; ++ case 2 : ++phase; break; ++ } ++ old_button = disp.button()&1; ++ } ++ break; ++ ++ case 4 : // When mouse is over the 3d view. ++ if (is_view3d && points3d) { ++ X3d = mx - width()*disp.width()/(width() + (depth()>1?depth():0)); ++ Y3d = my - height()*disp.height()/(height() + (depth()>1?depth():0)); ++ if (oX3d<0) { oX3d = X3d; oY3d = Y3d; } ++ // Left + right buttons: reset. ++ if ((disp.button()&3)==3) { pose3d.assign(); view3d.assign(); oX3d = oY3d = X3d = Y3d = -1; } ++ else if (disp.button()&1 && pose3d && (oX3d!=X3d || oY3d!=Y3d)) { // Left button: rotate. ++ const float ++ R = 0.45f*std::min(view3d._width,view3d._height), ++ R2 = R*R, ++ u0 = (float)(oX3d - view3d.width()/2), ++ v0 = (float)(oY3d - view3d.height()/2), ++ u1 = (float)(X3d - view3d.width()/2), ++ v1 = (float)(Y3d - view3d.height()/2), ++ n0 = cimg::hypot(u0,v0), ++ n1 = cimg::hypot(u1,v1), ++ nu0 = n0>R?(u0*R/n0):u0, ++ nv0 = n0>R?(v0*R/n0):v0, ++ nw0 = (float)std::sqrt(std::max(0.0f,R2 - nu0*nu0 - nv0*nv0)), ++ nu1 = n1>R?(u1*R/n1):u1, ++ nv1 = n1>R?(v1*R/n1):v1, ++ nw1 = (float)std::sqrt(std::max(0.0f,R2 - nu1*nu1 - nv1*nv1)), ++ u = nv0*nw1 - nw0*nv1, ++ v = nw0*nu1 - nu0*nw1, ++ w = nv0*nu1 - nu0*nv1, ++ n = cimg::hypot(u,v,w), ++ alpha = (float)std::asin(n/R2)*180/cimg::PI; ++ pose3d.draw_image(CImg::rotation_matrix(u,v,w,-alpha)*pose3d.get_crop(0,0,2,2)); ++ view3d.assign(); ++ } else if (disp.button()&2 && pose3d && oY3d!=Y3d) { // Right button: zoom. ++ pose3d(3,2)-=(oY3d - Y3d)*1.5f; view3d.assign(); ++ } ++ if (disp.wheel()) { // Wheel: zoom ++ pose3d(3,2)-=disp.wheel()*15; view3d.assign(); disp.set_wheel(); ++ } ++ if (disp.button()&4 && pose3d && (oX3d!=X3d || oY3d!=Y3d)) { // Middle button: shift. ++ pose3d(3,0)-=oX3d - X3d; pose3d(3,1)-=oY3d - Y3d; view3d.assign(); ++ } ++ oX3d = X3d; oY3d = Y3d; ++ } ++ mx = my = -1; X = Y = Z = -1; ++ break; ++ } ++ ++ if (phase) { ++ if (!feature_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>=width()) X0 = width() - 1; ++ if (Y0<0) Y0 = 0; ++ if (Y0>=height()) Y0 = height() - 1; ++ if (Z0<0) Z0 = 0; ++ if (Z0>=depth()) Z0 = depth() - 1; ++ if (X1<1) X1 = 0; ++ if (X1>=width()) X1 = width() - 1; ++ if (Y1<0) Y1 = 0; ++ if (Y1>=height()) Y1 = height() - 1; ++ if (Z1<0) Z1 = 0; ++ if (Z1>=depth()) Z1 = depth() - 1; ++ ++ // Draw visualization image on the display ++ if (mx!=omx || my!=omy || !visu0 || (_depth>1 && !view3d)) { ++ ++ if (!visu0) { // Create image of projected planes. ++ if (thumb) thumb.__get_select(disp,old_normalization,phase?X1:X0,phase?Y1:Y0,phase?Z1:Z0).move_to(visu0); ++ else __get_select(disp,old_normalization,phase?X1:X0,phase?Y1:Y0,phase?Z1:Z0).move_to(visu0); ++ visu0.resize(disp); ++ view3d.assign(); ++ points3d.assign(); ++ } ++ ++ if (is_view3d && _depth>1 && !view3d) { // Create 3d view for volumetric images. ++ const unsigned int ++ _x3d = (unsigned int)cimg::round((float)_width*visu0._width/(_width + _depth),1,1), ++ _y3d = (unsigned int)cimg::round((float)_height*visu0._height/(_height + _depth),1,1), ++ x3d = _x3d>=visu0._width?visu0._width - 1:_x3d, ++ y3d = _y3d>=visu0._height?visu0._height - 1:_y3d; ++ CImg(1,2,1,1,64,128).resize(visu0._width - x3d,visu0._height - y3d,1,visu0._spectrum,3). ++ move_to(view3d); ++ if (!points3d) { ++ get_projections3d(primitives3d,colors3d,phase?X1:X0,phase?Y1:Y0,phase?Z1:Z0,true).move_to(points3d); ++ points3d.append(CImg(8,3,1,1, ++ 0,_width - 1,_width - 1,0,0,_width - 1,_width - 1,0, ++ 0,0,_height - 1,_height - 1,0,0,_height - 1,_height - 1, ++ 0,0,0,0,_depth - 1,_depth - 1,_depth - 1,_depth - 1),'x'); ++ CImg::vector(12,13).move_to(primitives3d); CImg::vector(13,14).move_to(primitives3d); ++ CImg::vector(14,15).move_to(primitives3d); CImg::vector(15,12).move_to(primitives3d); ++ CImg::vector(16,17).move_to(primitives3d); CImg::vector(17,18).move_to(primitives3d); ++ CImg::vector(18,19).move_to(primitives3d); CImg::vector(19,16).move_to(primitives3d); ++ CImg::vector(12,16).move_to(primitives3d); CImg::vector(13,17).move_to(primitives3d); ++ CImg::vector(14,18).move_to(primitives3d); CImg::vector(15,19).move_to(primitives3d); ++ colors3d.insert(12,CImg::vector(255,255,255)); ++ opacities3d.assign(primitives3d.width(),1,1,1,0.5f); ++ if (!phase) { ++ opacities3d[0] = opacities3d[1] = opacities3d[2] = 0.8f; ++ sel_primitives3d.assign(); ++ sel_colors3d.assign(); ++ sel_opacities3d.assign(); ++ } else { ++ if (feature_type==2) { ++ points3d.append(CImg(8,3,1,1, ++ X0,X1,X1,X0,X0,X1,X1,X0, ++ Y0,Y0,Y1,Y1,Y0,Y0,Y1,Y1, ++ Z0,Z0,Z0,Z0,Z1,Z1,Z1,Z1),'x'); ++ sel_primitives3d.assign(); ++ CImg::vector(20,21).move_to(sel_primitives3d); ++ CImg::vector(21,22).move_to(sel_primitives3d); ++ CImg::vector(22,23).move_to(sel_primitives3d); ++ CImg::vector(23,20).move_to(sel_primitives3d); ++ CImg::vector(24,25).move_to(sel_primitives3d); ++ CImg::vector(25,26).move_to(sel_primitives3d); ++ CImg::vector(26,27).move_to(sel_primitives3d); ++ CImg::vector(27,24).move_to(sel_primitives3d); ++ CImg::vector(20,24).move_to(sel_primitives3d); ++ CImg::vector(21,25).move_to(sel_primitives3d); ++ CImg::vector(22,26).move_to(sel_primitives3d); ++ CImg::vector(23,27).move_to(sel_primitives3d); ++ } else { ++ points3d.append(CImg(2,3,1,1, ++ X0,X1, ++ Y0,Y1, ++ Z0,Z1),'x'); ++ sel_primitives3d.assign(CImg::vector(20,21)); ++ } ++ sel_colors3d.assign(sel_primitives3d._width,CImg::vector(255,255,255)); ++ sel_opacities3d.assign(sel_primitives3d._width,1,1,1,0.8f); ++ } ++ points3d.shift_object3d(-0.5f*(_width - 1),-0.5f*(_height - 1),-0.5f*(_depth - 1)).resize_object3d(); ++ points3d*=0.75f*std::min(view3d._width,view3d._height); ++ } ++ ++ if (!pose3d) CImg(4,3,1,1, 1,0,0,0, 0,1,0,0, 0,0,1,0).move_to(pose3d); ++ CImg zbuffer3d(view3d._width,view3d._height,1,1,0); ++ const CImg rotated_points3d = pose3d.get_crop(0,0,2,2)*points3d; ++ if (sel_primitives3d) ++ view3d.draw_object3d(pose3d(3,0) + 0.5f*view3d._width, ++ pose3d(3,1) + 0.5f*view3d._height, ++ pose3d(3,2), ++ rotated_points3d,sel_primitives3d,sel_colors3d,sel_opacities3d, ++ 2,true,500,0,0,0,0,0,zbuffer3d); ++ view3d.draw_object3d(pose3d(3,0) + 0.5f*view3d._width, ++ pose3d(3,1) + 0.5f*view3d._height, ++ pose3d(3,2), ++ rotated_points3d,primitives3d,colors3d,opacities3d, ++ 2,true,500,0,0,0,0,0,zbuffer3d); ++ visu0.draw_image(x3d,y3d,view3d); ++ } ++ visu = visu0; ++ ++ if (X<0 || Y<0 || Z<0) { if (!visible_cursor) { disp.show_mouse(); visible_cursor = true; }} ++ else { ++ if (is_axes) { if (visible_cursor) { disp.hide_mouse(); visible_cursor = false; }} ++ else { if (!visible_cursor) { disp.show_mouse(); visible_cursor = true; }} ++ const int d = (depth()>1)?depth():0; ++ int ++ _vX = (int)X, _vY = (int)Y, _vZ = (int)Z, ++ w = disp.width(), W = width() + d, ++ h = disp.height(), H = height() + d, ++ _xp = (int)(_vX*(float)w/W), xp = _xp + ((int)(_xp*(float)W/w)!=_vX), ++ _yp = (int)(_vY*(float)h/H), yp = _yp + ((int)(_yp*(float)H/h)!=_vY), ++ _xn = (int)((_vX + 1.0f)*w/W - 1), xn = _xn + ((int)((_xn + 1.0f)*W/w)!=_vX + 1), ++ _yn = (int)((_vY + 1.0f)*h/H - 1), yn = _yn + ((int)((_yn + 1.0f)*H/h)!=_vY + 1), ++ _zxp = (int)((_vZ + width())*(float)w/W), zxp = _zxp + ((int)(_zxp*(float)W/w)!=_vZ + width()), ++ _zyp = (int)((_vZ + height())*(float)h/H), zyp = _zyp + ((int)(_zyp*(float)H/h)!=_vZ + height()), ++ _zxn = (int)((_vZ + width() + 1.0f)*w/W - 1), ++ zxn = _zxn + ((int)((_zxn + 1.0f)*W/w)!=_vZ + width() + 1), ++ _zyn = (int)((_vZ + height() + 1.0f)*h/H - 1), ++ zyn = _zyn + ((int)((_zyn + 1.0f)*H/h)!=_vZ + height() + 1), ++ _xM = (int)(width()*(float)w/W - 1), xM = _xM + ((int)((_xM + 1.0f)*W/w)!=width()), ++ _yM = (int)(height()*(float)h/H - 1), yM = _yM + ((int)((_yM + 1.0f)*H/h)!=height()), ++ xc = (xp + xn)/2, ++ yc = (yp + yn)/2, ++ zxc = (zxp + zxn)/2, ++ zyc = (zyp + zyn)/2, ++ xf = (int)(X*w/W), ++ yf = (int)(Y*h/H), ++ zxf = (int)((Z + width())*w/W), ++ zyf = (int)((Z + height())*h/H); ++ ++ if (is_axes) { // Draw axes. ++ visu.draw_line(0,yf,visu.width() - 1,yf,foreground_color,0.7f,0xFF00FF00). ++ draw_line(0,yf,visu.width() - 1,yf,background_color,0.7f,0x00FF00FF). ++ draw_line(xf,0,xf,visu.height() - 1,foreground_color,0.7f,0xFF00FF00). ++ draw_line(xf,0,xf,visu.height() - 1,background_color,0.7f,0x00FF00FF); ++ if (_depth>1) ++ visu.draw_line(zxf,0,zxf,yM,foreground_color,0.7f,0xFF00FF00). ++ draw_line(zxf,0,zxf,yM,background_color,0.7f,0x00FF00FF). ++ draw_line(0,zyf,xM,zyf,foreground_color,0.7f,0xFF00FF00). ++ draw_line(0,zyf,xM,zyf,background_color,0.7f,0x00FF00FF); ++ } ++ ++ // Draw box cursor. ++ if (xn - xp>=4 && yn - yp>=4) visu.draw_rectangle(xp,yp,xn,yn,foreground_color,0.2f). ++ draw_rectangle(xp,yp,xn,yn,foreground_color,1,0xAAAAAAAA). ++ draw_rectangle(xp,yp,xn,yn,background_color,1,0x55555555); ++ if (_depth>1) { ++ if (yn - yp>=4 && zxn - zxp>=4) visu.draw_rectangle(zxp,yp,zxn,yn,background_color,0.2f). ++ draw_rectangle(zxp,yp,zxn,yn,foreground_color,1,0xAAAAAAAA). ++ draw_rectangle(zxp,yp,zxn,yn,background_color,1,0x55555555); ++ if (xn - xp>=4 && zyn - zyp>=4) visu.draw_rectangle(xp,zyp,xn,zyn,background_color,0.2f). ++ draw_rectangle(xp,zyp,xn,zyn,foreground_color,1,0xAAAAAAAA). ++ draw_rectangle(xp,zyp,xn,zyn,background_color,1,0x55555555); ++ } ++ ++ // Draw selection. ++ if (phase) { ++ const int ++ _xp0 = (int)(X0*(float)w/W), xp0 = _xp0 + ((int)(_xp0*(float)W/w)!=X0), ++ _yp0 = (int)(Y0*(float)h/H), yp0 = _yp0 + ((int)(_yp0*(float)H/h)!=Y0), ++ _xn0 = (int)((X0 + 1.0f)*w/W - 1), xn0 = _xn0 + ((int)((_xn0 + 1.0f)*W/w)!=X0 + 1), ++ _yn0 = (int)((Y0 + 1.0f)*h/H - 1), yn0 = _yn0 + ((int)((_yn0 + 1.0f)*H/h)!=Y0 + 1), ++ _zxp0 = (int)((Z0 + width())*(float)w/W), zxp0 = _zxp0 + ((int)(_zxp0*(float)W/w)!=Z0 + width()), ++ _zyp0 = (int)((Z0 + height())*(float)h/H), zyp0 = _zyp0 + ((int)(_zyp0*(float)H/h)!=Z0 + height()), ++ _zxn0 = (int)((Z0 + width() + 1.0f)*w/W - 1), ++ zxn0 = _zxn0 + ((int)((_zxn0 + 1.0f)*W/w)!=Z0 + width() + 1), ++ _zyn0 = (int)((Z0 + height() + 1.0f)*h/H - 1), ++ zyn0 = _zyn0 + ((int)((_zyn0 + 1.0f)*H/h)!=Z0 + height() + 1), ++ xc0 = (xp0 + xn0)/2, ++ yc0 = (yp0 + yn0)/2, ++ zxc0 = (zxp0 + zxn0)/2, ++ zyc0 = (zyp0 + zyn0)/2; ++ ++ switch (feature_type) { ++ case 1 : { ++ visu.draw_arrow(xc0,yc0,xc,yc,background_color,0.9f,30,5,0x55555555). ++ draw_arrow(xc0,yc0,xc,yc,foreground_color,0.9f,30,5,0xAAAAAAAA); ++ if (d) { ++ visu.draw_arrow(zxc0,yc0,zxc,yc,background_color,0.9f,30,5,0x55555555). ++ draw_arrow(zxc0,yc0,zxc,yc,foreground_color,0.9f,30,5,0xAAAAAAAA). ++ draw_arrow(xc0,zyc0,xc,zyc,background_color,0.9f,30,5,0x55555555). ++ draw_arrow(xc0,zyc0,xc,zyc,foreground_color,0.9f,30,5,0xAAAAAAAA); ++ } ++ } break; ++ case 2 : { ++ visu.draw_rectangle(X0=0 && my<13) text_down = true; else if (my>=visu.height() - 13) text_down = false; ++ if (!feature_type || !phase) { ++ if (X>=0 && Y>=0 && Z>=0 && X1 || force_display_z_coord) ++ cimg_snprintf(text,text._width," Point (%d,%d,%d) = [ ",origX + (int)X,origY + (int)Y,origZ + (int)Z); ++ else cimg_snprintf(text,text._width," Point (%d,%d) = [ ",origX + (int)X,origY + (int)Y); ++ CImg values = get_vector_at(X,Y,Z); ++ const bool is_large_spectrum = values._height>16; ++ if (is_large_spectrum) ++ values.draw_image(0,8,values.get_rows(values._height - 8,values._height - 1)).resize(1,16,1,1,0); ++ char *ctext = text._data + std::strlen(text), *const ltext = text._data + 512; ++ for (unsigned int c = 0; c::format_s(), ++ cimg::type::format(values[c])); ++ ctext += std::strlen(ctext); ++ if (c==7 && is_large_spectrum) { ++ cimg_snprintf(ctext,24," (...)"); ++ ctext += std::strlen(ctext); ++ } ++ *(ctext++) = ' '; *ctext = 0; ++ } ++ std::strcpy(text._data + std::strlen(text),"] "); ++ } ++ } else switch (feature_type) { ++ case 1 : { ++ const double dX = (double)(X0 - X1), dY = (double)(Y0 - Y1), dZ = (double)(Z0 - Z1), ++ length = cimg::hypot(dX,dY,dZ); ++ if (_depth>1 || force_display_z_coord) ++ cimg_snprintf(text,text._width," Vect (%d,%d,%d)-(%d,%d,%d), Length = %g ", ++ origX + X0,origY + Y0,origZ + Z0,origX + X1,origY + Y1,origZ + Z1,length); ++ else cimg_snprintf(text,text._width," Vect (%d,%d)-(%d,%d), Length = %g ", ++ origX + X0,origY + Y0,origX + X1,origY + Y1,length); ++ } break; ++ case 2 : { ++ const double dX = (double)(X0 - X1), dY = (double)(Y0 - Y1), dZ = (double)(Z0 - Z1), ++ length = cimg::hypot(dX,dY,dZ); ++ if (_depth>1 || force_display_z_coord) ++ cimg_snprintf(text,text._width," Box (%d,%d,%d)-(%d,%d,%d), Size = (%d,%d,%d), Length = %g ", ++ origX + (X01 || force_display_z_coord) ++ cimg_snprintf(text,text._width," 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_snprintf(text,text._width," 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.height() - 13:0,text,foreground_color,background_color,0.7f,13); ++ } ++ ++ disp.display(visu).wait(); ++ } else if (!shape_selected) disp.wait(); ++ if (disp.is_resized()) { disp.resize(false)._is_resized = false; old_is_resized = true; visu0.assign(); } ++ omx = mx; omy = my; ++ if (!exit_on_anykey && key && key!=cimg::keyESC && ++ (key!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) { ++ key = 0; ++ } ++ } ++ ++ // Return result. ++ CImg res(1,feature_type==0?3: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 (feature_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 (feature_type) { ++ case 1 : case 2 : res[0] = X0; res[1] = Y0; res[2] = Z0; res[3] = X1; res[4] = Y1; res[5] = Z1; break; ++ case 3 : ++ res[3] = cimg::abs(X1 - X0); res[4] = cimg::abs(Y1 - Y0); res[5] = cimg::abs(Z1 - Z0); ++ res[0] = X0; res[1] = Y0; res[2] = Z0; ++ break; ++ default : res[0] = X0; res[1] = Y0; res[2] = Z0; ++ } ++ } ++ if (!exit_on_anykey || !(disp.button()&4)) disp.set_button(); ++ if (!visible_cursor) disp.show_mouse(); ++ disp._normalization = old_normalization; ++ disp._is_resized = old_is_resized; ++ if (key!=~0U) disp.set_key(key); ++ return res; ++ } ++ ++ // Return a visualizable uchar8 image for display routines. ++ CImg __get_select(const CImgDisplay& disp, const int normalization, ++ const int x, const int y, const int z) const { ++ if (is_empty()) return CImg(1,1,1,1,0); ++ const CImg crop = get_shared_channels(0,std::min(2,spectrum() - 1)); ++ CImg img2d; ++ if (_depth>1) { ++ const int mdisp = std::min(disp.screen_width(),disp.screen_height()); ++ if (depth()>mdisp) { ++ crop.get_resize(-100,-100,mdisp,-100,0).move_to(img2d); ++ img2d.projections2d(x,y,z*img2d._depth/_depth); ++ } else crop.get_projections2d(x,y,z).move_to(img2d); ++ } else CImg(crop,false).move_to(img2d); ++ ++ // Check for inf and NaN values. ++ if (cimg::type::is_float() && normalization) { ++ bool is_inf = false, is_nan = false; ++ cimg_for(img2d,ptr,Tuchar) ++ if (cimg::type::is_inf(*ptr)) { is_inf = true; break; } ++ else if (cimg::type::is_nan(*ptr)) { is_nan = true; break; } ++ if (is_inf || is_nan) { ++ Tint m0 = (Tint)cimg::type::max(), M0 = (Tint)cimg::type::min(); ++ if (!normalization) { m0 = 0; M0 = 255; } ++ else if (normalization==2) { m0 = (Tint)disp._min; M0 = (Tint)disp._max; } ++ else ++ cimg_for(img2d,ptr,Tuchar) ++ if (!cimg::type::is_inf(*ptr) && !cimg::type::is_nan(*ptr)) { ++ if (*ptr<(Tuchar)m0) m0 = *ptr; ++ if (*ptr>(Tuchar)M0) M0 = *ptr; ++ } ++ const T ++ val_minf = (T)(normalization==1 || normalization==3?m0 - (M0 - m0)*20 - 1:m0), ++ val_pinf = (T)(normalization==1 || normalization==3?M0 + (M0 - m0)*20 + 1:M0); ++ if (is_nan) ++ cimg_for(img2d,ptr,Tuchar) ++ if (cimg::type::is_nan(*ptr)) *ptr = val_minf; // Replace NaN values. ++ if (is_inf) ++ cimg_for(img2d,ptr,Tuchar) ++ if (cimg::type::is_inf(*ptr)) *ptr = (float)*ptr<0?val_minf:val_pinf; // Replace +-inf values. ++ } ++ } ++ ++ switch (normalization) { ++ case 1 : img2d.normalize((ucharT)0,(ucharT)255); break; ++ case 2 : { ++ const float m = disp._min, M = disp._max; ++ (img2d-=m)*=255.0f/(M - m>0?M - m:1); ++ } break; ++ case 3 : ++ if (cimg::type::is_float()) img2d.normalize((ucharT)0,(ucharT)255); ++ else { ++ const float m = (float)cimg::type::min(), M = (float)cimg::type::max(); ++ (img2d-=m)*=255.0f/(M - m>0?M - m:1); ++ } break; ++ } ++ if (img2d.spectrum()==2) img2d.channels(0,2); ++ return img2d; ++ } ++ ++ //! 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 bool exit_on_anykey=false) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "select_graph(): Empty instance.", ++ cimg_instance); ++ if (!disp) disp.assign(cimg_fitscreen(CImgDisplay::screen_width()/2,CImgDisplay::screen_height()/2,1),0,0). ++ set_title("CImg<%s>",pixel_type()); ++ const ulongT siz = (ulongT)_width*_height*_depth; ++ const unsigned int old_normalization = disp.normalization(); ++ disp.show().set_button().set_wheel()._normalization = 0; ++ ++ double nymin = ymin, nymax = ymax, nxmin = xmin, nxmax = xmax; ++ if (nymin==nymax) { nymin = (Tfloat)min_max(nymax); const double dy = nymax - nymin; nymin-=dy/20; nymax+=dy/20; } ++ if (nymin==nymax) { --nymin; ++nymax; } ++ if (nxmin==nxmax && nxmin==0) { nxmin = 0; nxmax = siz - 1.0; } ++ ++ static const unsigned char black[] = { 0, 0, 0 }, white[] = { 255, 255, 255 }, gray[] = { 220, 220, 220 }; ++ static const unsigned char gray2[] = { 110, 110, 110 }, ngray[] = { 35, 35, 35 }; ++ static unsigned int odimv = 0; ++ static CImg colormap; ++ if (odimv!=_spectrum) { ++ odimv = _spectrum; ++ colormap = CImg(3,_spectrum,1,1,120).noise(70,1); ++ if (_spectrum==1) { colormap[0] = colormap[1] = 120; colormap[2] = 200; } ++ else { ++ colormap(0,0) = 220; colormap(1,0) = 10; colormap(2,0) = 10; ++ if (_spectrum>1) { colormap(0,1) = 10; colormap(1,1) = 220; colormap(2,1) = 10; } ++ if (_spectrum>2) { colormap(0,2) = 10; colormap(1,2) = 10; colormap(2,2) = 220; } ++ } ++ } ++ ++ CImg visu0, visu, graph, text, axes; ++ int x0 = -1, x1 = -1, y0 = -1, y1 = -1, omouse_x = -2, omouse_y = -2; ++ const unsigned int one = plot_type==3?0U:1U; ++ unsigned int okey = 0, obutton = 0; ++ CImg message(1024); ++ 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.width(),disp.height(),1,3,220); ++ const int gdimx = disp.width() - 32, gdimy = disp.height() - 32; ++ if (gdimx>0 && gdimy>0) { ++ graph.assign(gdimx,gdimy,1,3,255); ++ if (siz<32) { ++ if (siz>1) graph.draw_grid(gdimx/(float)(siz - one),gdimy/(float)(siz - one),0,0, ++ false,true,black,0.2f,0x33333333,0x33333333); ++ } else graph.draw_grid(-10,-10,0,0,false,true,black,0.2f,0x33333333,0x33333333); ++ cimg_forC(*this,c) ++ graph.draw_graph(get_shared_channel(c),&colormap(0,c),(plot_type!=3 || _spectrum==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)std::pow(10.0,(int)std::log10(dx?dx:1) - 2.0), ++ py = (float)std::pow(10.0,(int)std::log10(dy?dy:1) - 2.0); ++ const CImg ++ seqx = dx<=0?CImg::vector(nxmin): ++ CImg::sequence(1 + gdimx/60,nxmin,one?nxmax:nxmin + (nxmax - nxmin)*(siz + 1)/siz).round(px), ++ seqy = CImg::sequence(1 + gdimy/60,nymax,nymin).round(py); ++ ++ const bool allow_zero = (nxmin*nxmax>0) || (nymin*nymax>0); ++ axes.draw_axes(seqx,seqy,white,1,~0U,~0U,13,allow_zero); ++ if (nymin>0) axes.draw_axis(seqx,gdimy - 1,gray,1,~0U,13,allow_zero); ++ if (nymax<0) axes.draw_axis(seqx,0,gray,1,~0U,13,allow_zero); ++ if (nxmin>0) axes.draw_axis(0,seqy,gray,1,~0U,13,allow_zero); ++ if (nxmax<0) axes.draw_axis(gdimx - 1,seqy,gray,1,~0U,13,allow_zero); ++ ++ cimg_for3x3(axes,x,y,0,0,I,unsigned char) ++ if (Icc) { ++ if (Icc==255) cimg_forC(graph,c) graph(x,y,c) = 0; ++ else cimg_forC(graph,c) graph(x,y,c) = (unsigned char)(2*graph(x,y,c)/3); ++ } ++ else if (Ipc || Inc || Icp || Icn || Ipp || Inn || Ipn || Inp) ++ cimg_forC(graph,c) graph(x,y,c) = (unsigned char)((graph(x,y,c) + 511)/3); ++ ++ 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,13).resize(-100,-100,1,3); ++ visu0.draw_image((visu0.width() - text.width())/2,visu0.height() - 14,~text); ++ text.assign().draw_text(0,0,labely?labely:"Y-axis",white,ngray,1,13).rotate(-90).resize(-100,-100,1,3); ++ visu0.draw_image(1,(visu0.height() - text.height())/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 = (int)(16 + nx0*(visu.width() - 32)/std::max((ulongT)1,siz - one)), ++ sx1 = (int)(15 + (nx1 + 1)*(visu.width() - 32)/std::max((ulongT)1,siz - one)), ++ 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.height() - 17,gray,0.5f). ++ draw_line(sx0,16,sx0,visu.height() - 17,black,0.5f,0xCCCCCCCCU). ++ draw_line(sx1,16,sx1,visu.height() - 17,black,0.5f,0xCCCCCCCCU); ++ } ++ if (mouse_x>=16 && mouse_y>=16 && mouse_x=7) ++ cimg_snprintf(message,message._width,"Value[%u:%g] = ( %g %g %g ... %g %g %g )",x,cx, ++ (double)(*this)(x,0,0,0),(double)(*this)(x,0,0,1),(double)(*this)(x,0,0,2), ++ (double)(*this)(x,0,0,_spectrum - 4),(double)(*this)(x,0,0,_spectrum - 3), ++ (double)(*this)(x,0,0,_spectrum - 1)); ++ else { ++ cimg_snprintf(message,message._width,"Value[%u:%g] = ( ",x,cx); ++ cimg_forC(*this,c) cimg_sprintf(message._data + std::strlen(message),"%g ",(double)(*this)(x,0,0,c)); ++ cimg_sprintf(message._data + std::strlen(message),")"); ++ } ++ if (x0>=0 && x1>=0) { ++ const unsigned int ++ nx0 = (unsigned int)(x0<=x1?x0:x1), ++ nx1 = (unsigned int)(x0<=x1?x1:x0), ++ ny0 = (unsigned int)(y0<=y1?y0:y1), ++ ny1 = (unsigned int)(y0<=y1?y1:y0); ++ const double ++ cx0 = nxmin + nx0*(nxmax - nxmin)/std::max((ulongT)1,siz - 1), ++ cx1 = nxmin + (nx1 + one)*(nxmax - nxmin)/std::max((ulongT)1,siz - 1), ++ cy0 = nymax - ny0*(nymax - nymin)/(visu._height - 32), ++ cy1 = nymax - ny1*(nymax - nymin)/(visu._height - 32); ++ if (y0>=0 && y1>=0) ++ cimg_sprintf(message._data + std::strlen(message)," - Range ( %u:%g, %g ) - ( %u:%g, %g )", ++ x0,cx0,cy0,x1 + one,cx1,cy1); ++ else ++ cimg_sprintf(message._data + std::strlen(message)," - Range [ %u:%g - %u:%g ]", ++ x0,cx0,x1 + one,cx1); ++ } ++ text.assign().draw_text(0,0,message,white,ngray,1,13).resize(-100,-100,1,3); ++ visu.draw_image((visu.width() - text.width())/2,1,~text); ++ } ++ visu.display(disp); ++ } ++ ++ // Test keys. ++ CImg filename(32); ++ switch (okey = key) { ++#if cimg_OS!=2 ++ case cimg::keyCTRLRIGHT : case cimg::keySHIFTRIGHT : ++#endif ++ case cimg::keyCTRLLEFT : case cimg::keySHIFTLEFT : okey = 0; break; ++ case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ disp.set_fullscreen(false). ++ 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.set_key(key,false); okey = 0; ++ } break; ++ case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ disp.set_fullscreen(false). ++ resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true; ++ disp.set_key(key,false); okey = 0; ++ } break; ++ case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ disp.set_fullscreen(false). ++ resize(cimg_fitscreen(CImgDisplay::screen_width()/2, ++ CImgDisplay::screen_height()/2,1),false)._is_resized = true; ++ disp.set_key(key,false); okey = 0; ++ } break; ++ case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ disp.resize(disp.screen_width(),disp.screen_height(),false).toggle_fullscreen()._is_resized = true; ++ disp.set_key(key,false); okey = 0; ++ } break; ++ case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ static unsigned int snap_number = 0; ++ if (visu || visu0) { ++ CImg &screen = visu?visu:visu0; ++ std::FILE *file; ++ do { ++ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++); ++ if ((file=std_fopen(filename,"r"))!=0) cimg::fclose(file); ++ } while (file); ++ (+screen).draw_text(0,0," Saving snapshot... ",black,gray,1,13).display(disp); ++ screen.save(filename); ++ (+screen).draw_text(0,0," Snapshot '%s' saved. ",black,gray,1,13,filename._data).display(disp); ++ } ++ disp.set_key(key,false); okey = 0; ++ } break; ++ case cimg::keyO : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ static unsigned int snap_number = 0; ++ if (visu || visu0) { ++ CImg &screen = visu?visu:visu0; ++ std::FILE *file; ++ do { ++#ifdef cimg_use_zlib ++ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); ++#else ++ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); ++#endif ++ if ((file=std_fopen(filename,"r"))!=0) cimg::fclose(file); ++ } while (file); ++ (+screen).draw_text(0,0," Saving instance... ",black,gray,1,13).display(disp); ++ save(filename); ++ (+screen).draw_text(0,0," Instance '%s' saved. ",black,gray,1,13,filename._data).display(disp); ++ } ++ disp.set_key(key,false); 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)(siz - one)/(disp.width() - 32), ++ cx = cimg::cut(mx,0,(int)(siz - 1 - one)), ++ my = mouse_y - 16, ++ cy = cimg::cut(my,0,disp.height() - 32); ++ 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 = x1>=0?cx:-1; 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(); ++ if (!exit_on_anykey && okey && okey!=cimg::keyESC && ++ (okey!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) { ++ disp.set_key(key,false); ++ okey = 0; ++ } ++ } ++ ++ disp._normalization = old_normalization; ++ if (x1>=0 && x1(4,1,1,1,x0,y0,x1>=0?x1 + (int)one:-1,y1); ++ } ++ ++ //! Load image from a file. ++ /** ++ \param filename Filename, as a C-string. ++ \note The extension of \c filename defines the file format. If no filename ++ extension is provided, CImg::get_load() will try to load the file as a .cimg or .cimgz file. ++ **/ ++ CImg& load(const char *const filename) { ++ if (!filename) ++ throw CImgArgumentException(_cimg_instance ++ "load(): Specified filename is (null).", ++ cimg_instance); ++ ++ if (!cimg::strncasecmp(filename,"http://",7) || !cimg::strncasecmp(filename,"https://",8)) { ++ CImg filename_local(256); ++ load(cimg::load_network(filename,filename_local)); ++ std::remove(filename_local); ++ return *this; ++ } ++ ++ const char *const ext = cimg::split_filename(filename); ++ const unsigned int omode = cimg::exception_mode(); ++ cimg::exception_mode(0); ++ bool is_loaded = true; ++ 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); ++ else if (!cimg::strcasecmp(ext,"dlm") || ++ !cimg::strcasecmp(ext,"txt")) load_dlm(filename); ++ ++ // 2d binary formats ++ else if (!cimg::strcasecmp(ext,"bmp")) load_bmp(filename); ++ else if (!cimg::strcasecmp(ext,"jpg") || ++ !cimg::strcasecmp(ext,"jpeg") || ++ !cimg::strcasecmp(ext,"jpe") || ++ !cimg::strcasecmp(ext,"jfif") || ++ !cimg::strcasecmp(ext,"jif")) load_jpeg(filename); ++ else if (!cimg::strcasecmp(ext,"png")) load_png(filename); ++ else if (!cimg::strcasecmp(ext,"ppm") || ++ !cimg::strcasecmp(ext,"pgm") || ++ !cimg::strcasecmp(ext,"pnm") || ++ !cimg::strcasecmp(ext,"pbm") || ++ !cimg::strcasecmp(ext,"pnk")) load_pnm(filename); ++ else if (!cimg::strcasecmp(ext,"pfm")) load_pfm(filename); ++ else if (!cimg::strcasecmp(ext,"tif") || ++ !cimg::strcasecmp(ext,"tiff")) load_tiff(filename); ++ else if (!cimg::strcasecmp(ext,"exr")) load_exr(filename); ++ else 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); ++ else if (!cimg::strcasecmp(ext,"gif")) load_gif_external(filename); ++ ++ // 3d binary formats ++ else if (!cimg::strcasecmp(ext,"dcm") || ++ !cimg::strcasecmp(ext,"dicom")) load_medcon_external(filename); ++ else if (!cimg::strcasecmp(ext,"hdr") || ++ !cimg::strcasecmp(ext,"nii")) load_analyze(filename); ++ else if (!cimg::strcasecmp(ext,"par") || ++ !cimg::strcasecmp(ext,"rec")) load_parrec(filename); ++ else if (!cimg::strcasecmp(ext,"mnc")) load_minc2(filename); ++ else if (!cimg::strcasecmp(ext,"inr")) load_inr(filename); ++ else if (!cimg::strcasecmp(ext,"pan")) load_pandore(filename); ++ else if (!cimg::strcasecmp(ext,"cimg") || ++ !cimg::strcasecmp(ext,"cimgz") || ++ !*ext) return load_cimg(filename); ++ ++ // Archive files ++ else if (!cimg::strcasecmp(ext,"gz")) load_gzip_external(filename); ++ ++ // Image sequences ++ else 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,"mp4") || ++ !cimg::strcasecmp(ext,"mkv") || ++ !cimg::strcasecmp(ext,"mpe") || ++ !cimg::strcasecmp(ext,"movie") || ++ !cimg::strcasecmp(ext,"ogm") || ++ !cimg::strcasecmp(ext,"ogg") || ++ !cimg::strcasecmp(ext,"ogv") || ++ !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_video(filename); ++ else is_loaded = false; ++ } catch (CImgIOException&) { is_loaded = false; } ++ ++ // If nothing loaded, try to guess file format from magic number in file. ++ if (!is_loaded) { ++ std::FILE *file = std_fopen(filename,"rb"); ++ if (!file) { ++ cimg::exception_mode(omode); ++ throw CImgIOException(_cimg_instance ++ "load(): Failed to open file '%s'.", ++ cimg_instance, ++ filename); ++ } ++ ++ const char *const f_type = cimg::ftype(file,filename); ++ std::fclose(file); ++ is_loaded = true; ++ try { ++ if (!cimg::strcasecmp(f_type,"pnm")) load_pnm(filename); ++ else if (!cimg::strcasecmp(f_type,"pfm")) load_pfm(filename); ++ else if (!cimg::strcasecmp(f_type,"bmp")) load_bmp(filename); ++ else if (!cimg::strcasecmp(f_type,"inr")) load_inr(filename); ++ else if (!cimg::strcasecmp(f_type,"jpg")) load_jpeg(filename); ++ else if (!cimg::strcasecmp(f_type,"pan")) load_pandore(filename); ++ else if (!cimg::strcasecmp(f_type,"png")) load_png(filename); ++ else if (!cimg::strcasecmp(f_type,"tif")) load_tiff(filename); ++ else if (!cimg::strcasecmp(f_type,"gif")) load_gif_external(filename); ++ else if (!cimg::strcasecmp(f_type,"dcm")) load_medcon_external(filename); ++ else is_loaded = false; ++ } catch (CImgIOException&) { is_loaded = false; } ++ } ++ ++ // If nothing loaded, try to load file with other means. ++ if (!is_loaded) { ++ try { ++ load_other(filename); ++ } catch (CImgIOException&) { ++ cimg::exception_mode(omode); ++ throw CImgIOException(_cimg_instance ++ "load(): Failed to recognize format of file '%s'.", ++ cimg_instance, ++ filename); ++ } ++ } ++ cimg::exception_mode(omode); ++ return *this; ++ } ++ ++ //! Load image from a file \newinstance. ++ static CImg get_load(const char *const filename) { ++ return CImg().load(filename); ++ } ++ ++ //! Load image from an ascii file. ++ /** ++ \param filename Filename, as a C -string. ++ **/ ++ CImg& load_ascii(const char *const filename) { ++ return _load_ascii(0,filename); ++ } ++ ++ //! Load image from an ascii file \inplace. ++ static CImg get_load_ascii(const char *const filename) { ++ return CImg().load_ascii(filename); ++ } ++ ++ //! Load image from an ascii file \overloading. ++ CImg& load_ascii(std::FILE *const file) { ++ return _load_ascii(file,0); ++ } ++ ++ //! Loadimage from an ascii file \newinstance. ++ static CImg get_load_ascii(std::FILE *const file) { ++ return CImg().load_ascii(file); ++ } ++ ++ CImg& _load_ascii(std::FILE *const file, const char *const filename) { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "load_ascii(): Specified filename is (null).", ++ cimg_instance); ++ ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); ++ CImg line(256); *line = 0; ++ int err = std::fscanf(nfile,"%255[^\n]",line._data); ++ unsigned int dx = 0, dy = 1, dz = 1, dc = 1; ++ cimg_sscanf(line,"%u%*c%u%*c%u%*c%u",&dx,&dy,&dz,&dc); ++ err = std::fscanf(nfile,"%*[^0-9.eEinfa+-]"); ++ if (!dx || !dy || !dz || !dc) { ++ if (!file) cimg::fclose(nfile); ++ throw CImgIOException(_cimg_instance ++ "load_ascii(): Invalid ascii header in file '%s', image dimensions are set " ++ "to (%u,%u,%u,%u).", ++ cimg_instance, ++ filename?filename:"(FILE*)",dx,dy,dz,dc); ++ } ++ assign(dx,dy,dz,dc); ++ const ulongT siz = size(); ++ ulongT off = 0; ++ double val; ++ T *ptr = _data; ++ for (err = 1, off = 0; off& load_dlm(const char *const filename) { ++ return _load_dlm(0,filename); ++ } ++ ++ //! Load image from a DLM file \newinstance. ++ static CImg get_load_dlm(const char *const filename) { ++ return CImg().load_dlm(filename); ++ } ++ ++ //! Load image from a DLM file \overloading. ++ CImg& load_dlm(std::FILE *const file) { ++ return _load_dlm(file,0); ++ } ++ ++ //! Load image from a DLM file \newinstance. ++ static CImg get_load_dlm(std::FILE *const file) { ++ return CImg().load_dlm(file); ++ } ++ ++ CImg& _load_dlm(std::FILE *const file, const char *const filename) { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "load_dlm(): Specified filename is (null).", ++ cimg_instance); ++ ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"r"); ++ CImg delimiter(256), tmp(256); *delimiter = *tmp = 0; ++ unsigned int cdx = 0, dx = 0, dy = 0; ++ int err = 0; ++ double val; ++ assign(256,256,1,1,(T)0); ++ while ((err = std::fscanf(nfile,"%lf%255[^0-9eEinfa.+-]",&val,delimiter._data))>0) { ++ if (err>0) (*this)(cdx++,dy) = (T)val; ++ if (cdx>=_width) resize(3*_width/2,_height,1,1,0); ++ char c = 0; ++ if (!cimg_sscanf(delimiter,"%255[^\n]%c",tmp._data,&c) || c=='\n') { ++ dx = std::max(cdx,dx); ++ if (++dy>=_height) resize(_width,3*_height/2,1,1,0); ++ cdx = 0; ++ } ++ } ++ if (cdx && err==1) { dx = cdx; ++dy; } ++ if (!dx || !dy) { ++ if (!file) cimg::fclose(nfile); ++ throw CImgIOException(_cimg_instance ++ "load_dlm(): Invalid DLM file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ } ++ resize(dx,dy,1,1,0); ++ if (!file) cimg::fclose(nfile); ++ return *this; ++ } ++ ++ //! Load image from a BMP file. ++ /** ++ \param filename Filename, as a C-string. ++ **/ ++ CImg& load_bmp(const char *const filename) { ++ return _load_bmp(0,filename); ++ } ++ ++ //! Load image from a BMP file \newinstance. ++ static CImg get_load_bmp(const char *const filename) { ++ return CImg().load_bmp(filename); ++ } ++ ++ //! Load image from a BMP file \overloading. ++ CImg& load_bmp(std::FILE *const file) { ++ return _load_bmp(file,0); ++ } ++ ++ //! Load image from a BMP file \newinstance. ++ static CImg get_load_bmp(std::FILE *const file) { ++ return CImg().load_bmp(file); ++ } ++ ++ CImg& _load_bmp(std::FILE *const file, const char *const filename) { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "load_bmp(): Specified filename is (null).", ++ cimg_instance); ++ ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); ++ CImg header(54); ++ cimg::fread(header._data,54,nfile); ++ if (*header!='B' || header[1]!='M') { ++ if (!file) cimg::fclose(nfile); ++ throw CImgIOException(_cimg_instance ++ "load_bmp(): Invalid BMP file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ } ++ ++ // 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), ++ header_size = header[0x0E] + (header[0x0F]<<8) + (header[0x10]<<16) + (header[0x11]<<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); ++ ++ if (!file_size || file_size==offset) { ++ cimg::fseek(nfile,0,SEEK_END); ++ file_size = (int)cimg::ftell(nfile); ++ cimg::fseek(nfile,54,SEEK_SET); ++ } ++ if (header_size>40) cimg::fseek(nfile,header_size - 40,SEEK_CUR); ++ ++ const int ++ dx_bytes = (bpp==1)?(dx/8 + (dx%8?1:0)):((bpp==4)?(dx/2 + (dx%2)):(int)((longT)dx*bpp/8)), ++ align_bytes = (4 - dx_bytes%4)%4; ++ const ulongT ++ cimg_iobuffer = (ulongT)24*1024*1024, ++ buf_size = std::min((ulongT)cimg::abs(dy)*(dx_bytes + align_bytes),(ulongT)file_size - offset); ++ ++ CImg colormap; ++ if (bpp<16) { if (!nb_colors) nb_colors = 1<0) cimg::fseek(nfile,xoffset,SEEK_CUR); ++ ++ CImg buffer; ++ if (buf_size=2) for (int y = height() - 1; y>=0; --y) { ++ if (buf_size>=cimg_iobuffer) { ++ if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; ++ cimg::fseek(nfile,align_bytes,SEEK_CUR); ++ } ++ unsigned char mask = 0x80, val = 0; ++ cimg_forX(*this,x) { ++ if (mask==0x80) val = *(ptrs++); ++ const unsigned char *col = (unsigned char*)(colormap._data + (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_bytes; ++ } ++ } break; ++ case 4 : { // 16 colors ++ if (colormap._width>=16) for (int y = height() - 1; y>=0; --y) { ++ if (buf_size>=cimg_iobuffer) { ++ if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; ++ cimg::fseek(nfile,align_bytes,SEEK_CUR); ++ } ++ 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)); ++ const unsigned char *col = (unsigned char*)(colormap._data + 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_bytes; ++ } ++ } break; ++ case 8 : { // 256 colors ++ if (colormap._width>=256) for (int y = height() - 1; y>=0; --y) { ++ if (buf_size>=cimg_iobuffer) { ++ if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; ++ cimg::fseek(nfile,align_bytes,SEEK_CUR); ++ } ++ cimg_forX(*this,x) { ++ const unsigned char *col = (unsigned char*)(colormap._data + *(ptrs++)); ++ (*this)(x,y,2) = (T)*(col++); ++ (*this)(x,y,1) = (T)*(col++); ++ (*this)(x,y,0) = (T)*(col++); ++ } ++ ptrs+=align_bytes; ++ } ++ } break; ++ case 16 : { // 16 bits colors ++ for (int y = height() - 1; y>=0; --y) { ++ if (buf_size>=cimg_iobuffer) { ++ if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; ++ cimg::fseek(nfile,align_bytes,SEEK_CUR); ++ } ++ 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_bytes; ++ } ++ } break; ++ case 24 : { // 24 bits colors ++ for (int y = height() - 1; y>=0; --y) { ++ if (buf_size>=cimg_iobuffer) { ++ if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; ++ cimg::fseek(nfile,align_bytes,SEEK_CUR); ++ } ++ cimg_forX(*this,x) { ++ (*this)(x,y,2) = (T)*(ptrs++); ++ (*this)(x,y,1) = (T)*(ptrs++); ++ (*this)(x,y,0) = (T)*(ptrs++); ++ } ++ ptrs+=align_bytes; ++ } ++ } break; ++ case 32 : { // 32 bits colors ++ for (int y = height() - 1; y>=0; --y) { ++ if (buf_size>=cimg_iobuffer) { ++ if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; ++ cimg::fseek(nfile,align_bytes,SEEK_CUR); ++ } ++ 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_bytes; ++ } ++ } break; ++ } ++ if (dy<0) mirror('y'); ++ if (!file) cimg::fclose(nfile); ++ return *this; ++ } ++ ++ //! Load image from a JPEG file. ++ /** ++ \param filename Filename, as a C-string. ++ **/ ++ CImg& load_jpeg(const char *const filename) { ++ return _load_jpeg(0,filename); ++ } ++ ++ //! Load image from a JPEG file \newinstance. ++ static CImg get_load_jpeg(const char *const filename) { ++ return CImg().load_jpeg(filename); ++ } ++ ++ //! Load image from a JPEG file \overloading. ++ CImg& load_jpeg(std::FILE *const file) { ++ return _load_jpeg(file,0); ++ } ++ ++ //! Load image from a JPEG file \newinstance. ++ static CImg get_load_jpeg(std::FILE *const file) { ++ return CImg().load_jpeg(file); ++ } ++ ++ // Custom error handler for libjpeg. ++#ifdef cimg_use_jpeg ++ struct _cimg_error_mgr { ++ struct jpeg_error_mgr original; ++ jmp_buf setjmp_buffer; ++ char message[JMSG_LENGTH_MAX]; ++ }; ++ ++ typedef struct _cimg_error_mgr *_cimg_error_ptr; ++ ++ METHODDEF(void) _cimg_jpeg_error_exit(j_common_ptr cinfo) { ++ _cimg_error_ptr c_err = (_cimg_error_ptr) cinfo->err; // Return control to the setjmp point ++ (*cinfo->err->format_message)(cinfo,c_err->message); ++ jpeg_destroy(cinfo); // Clean memory and temp files. ++ longjmp(c_err->setjmp_buffer,1); ++ } ++#endif ++ ++ CImg& _load_jpeg(std::FILE *const file, const char *const filename) { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "load_jpeg(): Specified filename is (null).", ++ cimg_instance); ++ ++#ifndef cimg_use_jpeg ++ if (file) ++ throw CImgIOException(_cimg_instance ++ "load_jpeg(): Unable to load data from '(FILE*)' unless libjpeg is enabled.", ++ cimg_instance); ++ else return load_other(filename); ++#else ++ ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); ++ struct jpeg_decompress_struct cinfo; ++ struct _cimg_error_mgr jerr; ++ cinfo.err = jpeg_std_error(&jerr.original); ++ jerr.original.error_exit = _cimg_jpeg_error_exit; ++ if (setjmp(jerr.setjmp_buffer)) { // JPEG error ++ if (!file) cimg::fclose(nfile); ++ throw CImgIOException(_cimg_instance ++ "load_jpeg(): Error message returned by libjpeg: %s.", ++ cimg_instance,jerr.message); ++ } ++ ++ 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) { ++ if (!file) { ++ cimg::fclose(nfile); ++ return load_other(filename); ++ } else ++ throw CImgIOException(_cimg_instance ++ "load_jpeg(): Failed to load JPEG data from file '%s'.", ++ cimg_instance,filename?filename:"(FILE*)"); ++ } ++ CImg buffer(cinfo.output_width*cinfo.output_components); ++ JSAMPROW row_pointer[1]; ++ try { assign(cinfo.output_width,cinfo.output_height,1,cinfo.output_components); } ++ catch (...) { if (!file) cimg::fclose(nfile); throw; } ++ T *ptr_r = _data, *ptr_g = _data + 1UL*_width*_height, *ptr_b = _data + 2UL*_width*_height, ++ *ptr_a = _data + 3UL*_width*_height; ++ while (cinfo.output_scanline ++ // This is experimental code, not much tested, use with care. ++ CImg& load_magick(const char *const filename) { ++ if (!filename) ++ throw CImgArgumentException(_cimg_instance ++ "load_magick(): Specified filename is (null).", ++ cimg_instance); ++#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 *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2), *ptr_a = data(0,0,0,3); ++ Magick::PixelPacket *pixels = image.getPixels(0,0,W,H); ++ for (ulongT off = (ulongT)W*H; off; --off) { ++ *(ptr_r++) = (T)(pixels->red); ++ *(ptr_g++) = (T)(pixels->green); ++ *(ptr_b++) = (T)(pixels->blue); ++ *(ptr_a++) = (T)(pixels->opacity); ++ ++pixels; ++ } ++ } break; ++ case Magick::PaletteType : ++ case Magick::TrueColorType : { ++ assign(W,H,1,3); ++ T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2); ++ Magick::PixelPacket *pixels = image.getPixels(0,0,W,H); ++ for (ulongT off = (ulongT)W*H; off; --off) { ++ *(ptr_r++) = (T)(pixels->red); ++ *(ptr_g++) = (T)(pixels->green); ++ *(ptr_b++) = (T)(pixels->blue); ++ ++pixels; ++ } ++ } break; ++ case Magick::GrayscaleMatteType : { ++ assign(W,H,1,2); ++ T *ptr_r = data(0,0,0,0), *ptr_a = data(0,0,0,1); ++ Magick::PixelPacket *pixels = image.getPixels(0,0,W,H); ++ for (ulongT off = (ulongT)W*H; off; --off) { ++ *(ptr_r++) = (T)(pixels->red); ++ *(ptr_a++) = (T)(pixels->opacity); ++ ++pixels; ++ } ++ } break; ++ default : { ++ assign(W,H,1,1); ++ T *ptr_r = data(0,0,0,0); ++ Magick::PixelPacket *pixels = image.getPixels(0,0,W,H); ++ for (ulongT off = (ulongT)W*H; off; --off) { ++ *(ptr_r++) = (T)(pixels->red); ++ ++pixels; ++ } ++ } ++ } ++ return *this; ++#else ++ throw CImgIOException(_cimg_instance ++ "load_magick(): Unable to load file '%s' unless libMagick++ is enabled.", ++ cimg_instance, ++ filename); ++#endif ++ } ++ ++ //! Load image from a file, using Magick++ library \newinstance. ++ static CImg get_load_magick(const char *const filename) { ++ return CImg().load_magick(filename); ++ } ++ ++ //! Load image from a PNG file. ++ /** ++ \param filename Filename, as a C-string. ++ \param[out] bits_per_pixel Number of bits per pixels used to store pixel values in the image file. ++ **/ ++ CImg& load_png(const char *const filename, unsigned int *const bits_per_pixel=0) { ++ return _load_png(0,filename,bits_per_pixel); ++ } ++ ++ //! Load image from a PNG file \newinstance. ++ static CImg get_load_png(const char *const filename, unsigned int *const bits_per_pixel=0) { ++ return CImg().load_png(filename,bits_per_pixel); ++ } ++ ++ //! Load image from a PNG file \overloading. ++ CImg& load_png(std::FILE *const file, unsigned int *const bits_per_pixel=0) { ++ return _load_png(file,0,bits_per_pixel); ++ } ++ ++ //! Load image from a PNG file \newinstance. ++ static CImg get_load_png(std::FILE *const file, unsigned int *const bits_per_pixel=0) { ++ return CImg().load_png(file,bits_per_pixel); ++ } ++ ++ // (Note: Most of this function has been written by Eric Fausett) ++ CImg& _load_png(std::FILE *const file, const char *const filename, unsigned int *const bits_per_pixel) { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "load_png(): Specified filename is (null).", ++ cimg_instance); ++ ++#ifndef cimg_use_png ++ cimg::unused(bits_per_pixel); ++ if (file) ++ throw CImgIOException(_cimg_instance ++ "load_png(): Unable to load data from '(FILE*)' unless libpng is enabled.", ++ cimg_instance); ++ ++ else return load_other(filename); ++#else ++ // Open file and check for PNG validity ++#if defined __GNUC__ ++ const char *volatile nfilename = filename; // Use 'volatile' to avoid (wrong) g++ warning. ++ std::FILE *volatile nfile = file?file:cimg::fopen(nfilename,"rb"); ++#else ++ const char *nfilename = filename; ++ std::FILE *nfile = file?file:cimg::fopen(nfilename,"rb"); ++#endif ++ unsigned char pngCheck[8] = { 0 }; ++ cimg::fread(pngCheck,8,(std::FILE*)nfile); ++ if (png_sig_cmp(pngCheck,0,8)) { ++ if (!file) cimg::fclose(nfile); ++ throw CImgIOException(_cimg_instance ++ "load_png(): Invalid PNG file '%s'.", ++ cimg_instance, ++ 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_instance ++ "load_png(): Failed to initialize 'png_ptr' structure for file '%s'.", ++ cimg_instance, ++ 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_instance ++ "load_png(): Failed to initialize 'info_ptr' structure for file '%s'.", ++ cimg_instance, ++ 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_instance ++ "load_png(): Failed to initialize 'end_info' structure for file '%s'.", ++ cimg_instance, ++ nfilename?nfilename:"(FILE*)"); ++ } ++ ++ // Error handling callback for png file reading ++ if (setjmp(png_jmpbuf(png_ptr))) { ++ if (!file) cimg::fclose((std::FILE*)nfile); ++ png_destroy_read_struct(&png_ptr, &end_info, (png_infopp)0); ++ throw CImgIOException(_cimg_instance ++ "load_png(): Encountered unknown fatal error in libpng for file '%s'.", ++ cimg_instance, ++ 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; ++ bool is_gray = false; ++ png_get_IHDR(png_ptr,info_ptr,&W,&H,&bit_depth,&color_type,&interlace_type,(int*)0,(int*)0); ++ if (bits_per_pixel) *bits_per_pixel = (unsigned int)bit_depth; ++ ++ // Transforms to unify image data ++ if (color_type==PNG_COLOR_TYPE_PALETTE) { ++ png_set_palette_to_rgb(png_ptr); ++ color_type = PNG_COLOR_TYPE_RGB; ++ bit_depth = 8; ++ } ++ if (color_type==PNG_COLOR_TYPE_GRAY && bit_depth<8) { ++ png_set_expand_gray_1_2_4_to_8(png_ptr); ++ is_gray = true; ++ bit_depth = 8; ++ } ++ if (png_get_valid(png_ptr,info_ptr,PNG_INFO_tRNS)) { ++ png_set_tRNS_to_alpha(png_ptr); ++ color_type |= PNG_COLOR_MASK_ALPHA; ++ } ++ if (color_type==PNG_COLOR_TYPE_GRAY || color_type==PNG_COLOR_TYPE_GRAY_ALPHA) { ++ png_set_gray_to_rgb(png_ptr); ++ color_type |= PNG_COLOR_MASK_COLOR; ++ is_gray = true; ++ } ++ if (color_type==PNG_COLOR_TYPE_RGB) ++ png_set_filler(png_ptr,0xffffU,PNG_FILLER_AFTER); ++ ++ png_read_update_info(png_ptr,info_ptr); ++ if (bit_depth!=8 && bit_depth!=16) { ++ if (!file) cimg::fclose(nfile); ++ png_destroy_read_struct(&png_ptr,&end_info,(png_infopp)0); ++ throw CImgIOException(_cimg_instance ++ "load_png(): Invalid bit depth %u in file '%s'.", ++ cimg_instance, ++ bit_depth,nfilename?nfilename:"(FILE*)"); ++ } ++ const int byte_depth = bit_depth>>3; ++ ++ // Allocate Memory for Image Read ++ png_bytep *const imgData = new png_bytep[H]; ++ for (unsigned int row = 0; row& load_pnm(const char *const filename) { ++ return _load_pnm(0,filename); ++ } ++ ++ //! Load image from a PNM file \newinstance. ++ static CImg get_load_pnm(const char *const filename) { ++ return CImg().load_pnm(filename); ++ } ++ ++ //! Load image from a PNM file \overloading. ++ CImg& load_pnm(std::FILE *const file) { ++ return _load_pnm(file,0); ++ } ++ ++ //! Load image from a PNM file \newinstance. ++ static CImg get_load_pnm(std::FILE *const file) { ++ return CImg().load_pnm(file); ++ } ++ ++ CImg& _load_pnm(std::FILE *const file, const char *const filename) { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "load_pnm(): Specified filename is (null).", ++ cimg_instance); ++ ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); ++ unsigned int ppm_type, W, H, D = 1, colormax = 255; ++ CImg item(16384,1,1,1,0); ++ int err, rval, gval, bval; ++ const longT cimg_iobuffer = (longT)24*1024*1024; ++ while ((err=std::fscanf(nfile,"%16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); ++ if (cimg_sscanf(item," P%u",&ppm_type)!=1) { ++ if (!file) cimg::fclose(nfile); ++ throw CImgIOException(_cimg_instance ++ "load_pnm(): PNM header not found in file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ } ++ while ((err=std::fscanf(nfile," %16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); ++ if ((err=cimg_sscanf(item," %u %u %u %u",&W,&H,&D,&colormax))<2) { ++ if (!file) cimg::fclose(nfile); ++ throw CImgIOException(_cimg_instance ++ "load_pnm(): WIDTH and HEIGHT fields undefined in file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ } ++ if (ppm_type!=1 && ppm_type!=4) { ++ if (err==2 || (err==3 && (ppm_type==5 || ppm_type==7 || ppm_type==8 || ppm_type==9))) { ++ while ((err=std::fscanf(nfile," %16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); ++ if (cimg_sscanf(item,"%u",&colormax)!=1) ++ cimg::warn(_cimg_instance ++ "load_pnm(): COLORMAX field is undefined in file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ } else { colormax = D; D = 1; } ++ } ++ std::fgetc(nfile); ++ ++ switch (ppm_type) { ++ case 1 : { // 2d b&w ascii. ++ assign(W,H,1,1); ++ T* ptrd = _data; ++ cimg_foroff(*this,off) { if (std::fscanf(nfile,"%d",&rval)>0) *(ptrd++) = (T)(rval?0:255); else break; } ++ } break; ++ case 2 : { // 2d grey ascii. ++ assign(W,H,1,1); ++ T* ptrd = _data; ++ cimg_foroff(*this,off) { if (std::fscanf(nfile,"%d",&rval)>0) *(ptrd++) = (T)rval; else break; } ++ } break; ++ case 3 : { // 2d color ascii. ++ assign(W,H,1,3); ++ T *ptrd = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2); ++ cimg_forXY(*this,x,y) { ++ if (std::fscanf(nfile,"%d %d %d",&rval,&gval,&bval)==3) { ++ *(ptrd++) = (T)rval; *(ptr_g++) = (T)gval; *(ptr_b++) = (T)bval; ++ } else break; ++ } ++ } break; ++ case 4 : { // 2d b&w binary (support 3D PINK extension). ++ CImg raw; ++ assign(W,H,D,1); ++ T *ptrd = data(0,0,0,0); ++ unsigned int w = 0, h = 0, d = 0; ++ for (longT to_read = (longT)((W/8 + (W%8?1:0))*H*D); to_read>0; ) { ++ raw.assign(std::min(to_read,cimg_iobuffer)); ++ cimg::fread(raw._data,raw._width,nfile); ++ to_read-=raw._width; ++ const unsigned char *ptrs = raw._data; ++ unsigned char mask = 0, val = 0; ++ for (ulongT off = (ulongT)raw._width; off || mask; mask>>=1) { ++ if (!mask) { if (off--) val = *(ptrs++); mask = 128; } ++ *(ptrd++) = (T)((val&mask)?0:255); ++ if (++w==W) { w = 0; mask = 0; if (++h==H) { h = 0; if (++d==D) break; }} ++ } ++ } ++ } break; ++ case 5 : case 7 : { // 2d/3d grey binary (support 3D PINK extension). ++ if (colormax<256) { // 8 bits. ++ CImg raw; ++ assign(W,H,D,1); ++ T *ptrd = data(0,0,0,0); ++ for (longT to_read = (longT)size(); to_read>0; ) { ++ raw.assign(std::min(to_read,cimg_iobuffer)); ++ cimg::fread(raw._data,raw._width,nfile); ++ to_read-=raw._width; ++ const unsigned char *ptrs = raw._data; ++ for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); ++ } ++ } else { // 16 bits. ++ CImg raw; ++ assign(W,H,D,1); ++ T *ptrd = data(0,0,0,0); ++ for (longT to_read = (longT)size(); to_read>0; ) { ++ raw.assign(std::min(to_read,cimg_iobuffer/2)); ++ cimg::fread(raw._data,raw._width,nfile); ++ if (!cimg::endianness()) cimg::invert_endianness(raw._data,raw._width); ++ to_read-=raw._width; ++ const unsigned short *ptrs = raw._data; ++ for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); ++ } ++ } ++ } break; ++ case 6 : { // 2d color binary. ++ if (colormax<256) { // 8 bits. ++ CImg raw; ++ assign(W,H,1,3); ++ T ++ *ptr_r = data(0,0,0,0), ++ *ptr_g = data(0,0,0,1), ++ *ptr_b = data(0,0,0,2); ++ for (longT to_read = (longT)size(); to_read>0; ) { ++ raw.assign(std::min(to_read,cimg_iobuffer)); ++ cimg::fread(raw._data,raw._width,nfile); ++ to_read-=raw._width; ++ const unsigned char *ptrs = raw._data; ++ for (ulongT off = (ulongT)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 = data(0,0,0,0), ++ *ptr_g = data(0,0,0,1), ++ *ptr_b = data(0,0,0,2); ++ for (longT to_read = (longT)size(); to_read>0; ) { ++ raw.assign(std::min(to_read,cimg_iobuffer/2)); ++ cimg::fread(raw._data,raw._width,nfile); ++ if (!cimg::endianness()) cimg::invert_endianness(raw._data,raw._width); ++ to_read-=raw._width; ++ const unsigned short *ptrs = raw._data; ++ for (ulongT off = (ulongT)raw._width/3; off; --off) { ++ *(ptr_r++) = (T)*(ptrs++); ++ *(ptr_g++) = (T)*(ptrs++); ++ *(ptr_b++) = (T)*(ptrs++); ++ } ++ } ++ } ++ } break; ++ case 8 : { // 2d/3d grey binary with int32 integers (PINK extension). ++ CImg raw; ++ assign(W,H,D,1); ++ T *ptrd = data(0,0,0,0); ++ for (longT to_read = (longT)size(); to_read>0; ) { ++ raw.assign(std::min(to_read,cimg_iobuffer)); ++ cimg::fread(raw._data,raw._width,nfile); ++ to_read-=raw._width; ++ const int *ptrs = raw._data; ++ for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); ++ } ++ } break; ++ case 9 : { // 2d/3d grey binary with float values (PINK extension). ++ CImg raw; ++ assign(W,H,D,1); ++ T *ptrd = data(0,0,0,0); ++ for (longT to_read = (longT)size(); to_read>0; ) { ++ raw.assign(std::min(to_read,cimg_iobuffer)); ++ cimg::fread(raw._data,raw._width,nfile); ++ to_read-=raw._width; ++ const float *ptrs = raw._data; ++ for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); ++ } ++ } break; ++ default : ++ assign(); ++ if (!file) cimg::fclose(nfile); ++ throw CImgIOException(_cimg_instance ++ "load_pnm(): PNM type 'P%d' found, but type is not supported.", ++ cimg_instance, ++ filename?filename:"(FILE*)",ppm_type); ++ } ++ if (!file) cimg::fclose(nfile); ++ return *this; ++ } ++ ++ //! Load image from a PFM file. ++ /** ++ \param filename Filename, as a C-string. ++ **/ ++ CImg& load_pfm(const char *const filename) { ++ return _load_pfm(0,filename); ++ } ++ ++ //! Load image from a PFM file \newinstance. ++ static CImg get_load_pfm(const char *const filename) { ++ return CImg().load_pfm(filename); ++ } ++ ++ //! Load image from a PFM file \overloading. ++ CImg& load_pfm(std::FILE *const file) { ++ return _load_pfm(file,0); ++ } ++ ++ //! Load image from a PFM file \newinstance. ++ static CImg get_load_pfm(std::FILE *const file) { ++ return CImg().load_pfm(file); ++ } ++ ++ CImg& _load_pfm(std::FILE *const file, const char *const filename) { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "load_pfm(): Specified filename is (null).", ++ cimg_instance); ++ ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); ++ char pfm_type; ++ CImg item(16384,1,1,1,0); ++ int W = 0, H = 0, err = 0; ++ double scale = 0; ++ while ((err=std::fscanf(nfile,"%16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); ++ if (cimg_sscanf(item," P%c",&pfm_type)!=1) { ++ if (!file) cimg::fclose(nfile); ++ throw CImgIOException(_cimg_instance ++ "load_pfm(): PFM header not found in file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ } ++ while ((err=std::fscanf(nfile," %16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); ++ if ((err=cimg_sscanf(item," %d %d",&W,&H))<2) { ++ if (!file) cimg::fclose(nfile); ++ throw CImgIOException(_cimg_instance ++ "load_pfm(): WIDTH and HEIGHT fields are undefined in file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ } ++ if (err==2) { ++ while ((err=std::fscanf(nfile," %16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile); ++ if (cimg_sscanf(item,"%lf",&scale)!=1) ++ cimg::warn(_cimg_instance ++ "load_pfm(): SCALE field is undefined in file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ } ++ std::fgetc(nfile); ++ const bool is_color = (pfm_type=='F'), is_inverted = (scale>0)!=cimg::endianness(); ++ if (is_color) { ++ assign(W,H,1,3,(T)0); ++ CImg buf(3*W); ++ T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2); ++ cimg_forY(*this,y) { ++ cimg::fread(buf._data,3*W,nfile); ++ if (is_inverted) cimg::invert_endianness(buf._data,3*W); ++ const float *ptrs = buf._data; ++ cimg_forX(*this,x) { ++ *(ptr_r++) = (T)*(ptrs++); ++ *(ptr_g++) = (T)*(ptrs++); ++ *(ptr_b++) = (T)*(ptrs++); ++ } ++ } ++ } else { ++ assign(W,H,1,1,(T)0); ++ CImg buf(W); ++ T *ptrd = data(0,0,0,0); ++ cimg_forY(*this,y) { ++ cimg::fread(buf._data,W,nfile); ++ if (is_inverted) cimg::invert_endianness(buf._data,W); ++ const float *ptrs = buf._data; ++ cimg_forX(*this,x) *(ptrd++) = (T)*(ptrs++); ++ } ++ } ++ if (!file) cimg::fclose(nfile); ++ return mirror('y'); // Most of the .pfm files are flipped along the y-axis. ++ } ++ ++ //! Load image from a RGB file. ++ /** ++ \param filename Filename, as a C-string. ++ \param dimw Width of the image buffer. ++ \param dimh Height of the image buffer. ++ **/ ++ CImg& load_rgb(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) { ++ return _load_rgb(0,filename,dimw,dimh); ++ } ++ ++ //! Load image from a RGB file \newinstance. ++ 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 image from a RGB file \overloading. ++ CImg& load_rgb(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) { ++ return _load_rgb(file,0,dimw,dimh); ++ } ++ ++ //! Load image from a RGB file \newinstance. ++ static CImg get_load_rgb(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) { ++ return CImg().load_rgb(file,dimw,dimh); ++ } ++ ++ CImg& _load_rgb(std::FILE *const file, const char *const filename, ++ const unsigned int dimw, const unsigned int dimh) { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "load_rgb(): Specified filename is (null).", ++ cimg_instance); ++ ++ if (!dimw || !dimh) return assign(); ++ const longT cimg_iobuffer = (longT)24*1024*1024; ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); ++ CImg raw; ++ assign(dimw,dimh,1,3); ++ T ++ *ptr_r = data(0,0,0,0), ++ *ptr_g = data(0,0,0,1), ++ *ptr_b = data(0,0,0,2); ++ for (longT to_read = (longT)size(); to_read>0; ) { ++ raw.assign(std::min(to_read,cimg_iobuffer)); ++ cimg::fread(raw._data,raw._width,nfile); ++ to_read-=raw._width; ++ const unsigned char *ptrs = raw._data; ++ for (ulongT off = raw._width/3UL; off; --off) { ++ *(ptr_r++) = (T)*(ptrs++); ++ *(ptr_g++) = (T)*(ptrs++); ++ *(ptr_b++) = (T)*(ptrs++); ++ } ++ } ++ if (!file) cimg::fclose(nfile); ++ return *this; ++ } ++ ++ //! Load image from a RGBA file. ++ /** ++ \param filename Filename, as a C-string. ++ \param dimw Width of the image buffer. ++ \param dimh Height of the image buffer. ++ **/ ++ CImg& load_rgba(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) { ++ return _load_rgba(0,filename,dimw,dimh); ++ } ++ ++ //! Load image from a RGBA file \newinstance. ++ 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 image from a RGBA file \overloading. ++ CImg& load_rgba(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) { ++ return _load_rgba(file,0,dimw,dimh); ++ } ++ ++ //! Load image from a RGBA file \newinstance. ++ static CImg get_load_rgba(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) { ++ return CImg().load_rgba(file,dimw,dimh); ++ } ++ ++ CImg& _load_rgba(std::FILE *const file, const char *const filename, ++ const unsigned int dimw, const unsigned int dimh) { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "load_rgba(): Specified filename is (null).", ++ cimg_instance); ++ ++ if (!dimw || !dimh) return assign(); ++ const longT cimg_iobuffer = (longT)24*1024*1024; ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); ++ CImg raw; ++ assign(dimw,dimh,1,4); ++ T ++ *ptr_r = data(0,0,0,0), ++ *ptr_g = data(0,0,0,1), ++ *ptr_b = data(0,0,0,2), ++ *ptr_a = data(0,0,0,3); ++ for (longT to_read = (longT)size(); to_read>0; ) { ++ raw.assign(std::min(to_read,cimg_iobuffer)); ++ cimg::fread(raw._data,raw._width,nfile); ++ to_read-=raw._width; ++ const unsigned char *ptrs = raw._data; ++ for (ulongT off = raw._width/4UL; 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 image from a TIFF file. ++ /** ++ \param filename Filename, as a C-string. ++ \param first_frame First frame to read (for multi-pages tiff). ++ \param last_frame Last frame to read (for multi-pages tiff). ++ \param step_frame Step value of frame reading. ++ \param[out] voxel_size Voxel size, as stored in the filename. ++ \param[out] description Description, as stored in the filename. ++ \note ++ - libtiff support is enabled by defining the precompilation ++ directive \c cimg_use_tif. ++ - When libtiff is enabled, 2D and 3D (multipage) several ++ channel per pixel are supported for ++ char,uchar,short,ushort,float and \c double pixel types. ++ - If \c cimg_use_tif is not defined at compile time the ++ function uses CImg& load_other(const char*). ++ **/ ++ 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, ++ float *const voxel_size=0, ++ CImg *const description=0) { ++ if (!filename) ++ throw CImgArgumentException(_cimg_instance ++ "load_tiff(): Specified filename is (null).", ++ cimg_instance); ++ ++ const unsigned int ++ nfirst_frame = first_frame1) ++ throw CImgArgumentException(_cimg_instance ++ "load_tiff(): Unable to read sub-images from file '%s' unless libtiff is enabled.", ++ cimg_instance, ++ filename); ++ return load_other(filename); ++#else ++#if cimg_verbosity<3 ++ TIFFSetWarningHandler(0); ++ TIFFSetErrorHandler(0); ++#endif ++ 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_instance ++ "load_tiff(): File '%s' contains %u image(s) while specified frame range is [%u,%u] (step %u).", ++ cimg_instance, ++ 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,voxel_size,description); ++ if (l==nfirst_frame) ++ assign(frame._width,frame._height,1 + (nlast_frame - nfirst_frame)/nstep_frame,frame._spectrum); ++ if (frame._width>_width || frame._height>_height || frame._spectrum>_spectrum) ++ resize(std::max(frame._width,_width), ++ std::max(frame._height,_height),-100, ++ std::max(frame._spectrum,_spectrum),0); ++ draw_image(0,0,(l - nfirst_frame)/nstep_frame,frame); ++ } ++ TIFFClose(tif); ++ } else throw CImgIOException(_cimg_instance ++ "load_tiff(): Failed to open file '%s'.", ++ cimg_instance, ++ filename); ++ return *this; ++#endif ++ } ++ ++ //! Load image from a TIFF file \newinstance. ++ 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, ++ float *const voxel_size=0, ++ CImg *const description=0) { ++ return CImg().load_tiff(filename,first_frame,last_frame,step_frame,voxel_size,description); ++ } ++ ++ // (Original contribution by Jerome Boulanger). ++#ifdef cimg_use_tiff ++ template ++ void _load_tiff_tiled_contig(TIFF *const tif, const uint16 samplesperpixel, ++ const uint32 nx, const uint32 ny, const uint32 tw, const uint32 th) { ++ t *const buf = (t*)_TIFFmalloc(TIFFTileSize(tif)); ++ if (buf) { ++ for (unsigned int row = 0; row ++ void _load_tiff_tiled_separate(TIFF *const tif, const uint16 samplesperpixel, ++ const uint32 nx, const uint32 ny, const uint32 tw, const uint32 th) { ++ t *const buf = (t*)_TIFFmalloc(TIFFTileSize(tif)); ++ if (buf) { ++ for (unsigned int vv = 0; vv ++ void _load_tiff_contig(TIFF *const tif, const uint16 samplesperpixel, const uint32 nx, const uint32 ny) { ++ t *const buf = (t*)_TIFFmalloc(TIFFStripSize(tif)); ++ if (buf) { ++ uint32 row, rowsperstrip = (uint32)-1; ++ TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip); ++ for (row = 0; rowny?ny - row:rowsperstrip); ++ tstrip_t strip = TIFFComputeStrip(tif, row, 0); ++ if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) { ++ _TIFFfree(buf); TIFFClose(tif); ++ throw CImgIOException(_cimg_instance ++ "load_tiff(): Invalid strip in file '%s'.", ++ cimg_instance, ++ TIFFFileName(tif)); ++ } ++ const t *ptr = buf; ++ for (unsigned int rr = 0; rr ++ void _load_tiff_separate(TIFF *const tif, const uint16 samplesperpixel, const uint32 nx, const uint32 ny) { ++ t *buf = (t*)_TIFFmalloc(TIFFStripSize(tif)); ++ if (buf) { ++ uint32 row, rowsperstrip = (uint32)-1; ++ TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip); ++ for (unsigned int vv = 0; vvny?ny - row:rowsperstrip); ++ tstrip_t strip = TIFFComputeStrip(tif, row, vv); ++ if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) { ++ _TIFFfree(buf); TIFFClose(tif); ++ throw CImgIOException(_cimg_instance ++ "load_tiff(): Invalid strip in file '%s'.", ++ cimg_instance, ++ TIFFFileName(tif)); ++ } ++ const t *ptr = buf; ++ for (unsigned int rr = 0;rr& _load_tiff(TIFF *const tif, const unsigned int directory, ++ float *const voxel_size, CImg *const description) { ++ if (!TIFFSetDirectory(tif,directory)) return assign(); ++ uint16 samplesperpixel = 1, bitspersample = 8, photo = 0; ++ uint16 sampleformat = 1; ++ uint32 nx = 1, ny = 1; ++ const char *const filename = TIFFFileName(tif); ++ const bool is_spp = (bool)TIFFGetField(tif,TIFFTAG_SAMPLESPERPIXEL,&samplesperpixel); ++ TIFFGetField(tif,TIFFTAG_IMAGEWIDTH,&nx); ++ TIFFGetField(tif,TIFFTAG_IMAGELENGTH,&ny); ++ TIFFGetField(tif, TIFFTAG_SAMPLEFORMAT, &sampleformat); ++ TIFFGetFieldDefaulted(tif,TIFFTAG_BITSPERSAMPLE,&bitspersample); ++ TIFFGetField(tif,TIFFTAG_PHOTOMETRIC,&photo); ++ if (voxel_size) { ++ const char *s_description = 0; ++ float vx = 0, vy = 0, vz = 0; ++ if (TIFFGetField(tif,TIFFTAG_IMAGEDESCRIPTION,&s_description) && s_description) { ++ const char *s_desc = std::strstr(s_description,"VX="); ++ if (s_desc && cimg_sscanf(s_desc,"VX=%f VY=%f VZ=%f",&vx,&vy,&vz)==3) { // CImg format. ++ voxel_size[0] = vx; voxel_size[1] = vy; voxel_size[2] = vz; ++ } ++ s_desc = std::strstr(s_description,"spacing="); ++ if (s_desc && cimg_sscanf(s_desc,"spacing=%f",&vz)==1) { // fiji format. ++ voxel_size[2] = vz; ++ } ++ } ++ TIFFGetField(tif,TIFFTAG_XRESOLUTION,voxel_size); ++ TIFFGetField(tif,TIFFTAG_YRESOLUTION,voxel_size + 1); ++ voxel_size[0] = 1.0f/voxel_size[0]; ++ voxel_size[1] = 1.0f/voxel_size[1]; ++ } ++ if (description) { ++ const char *s_description = 0; ++ if (TIFFGetField(tif,TIFFTAG_IMAGEDESCRIPTION,&s_description) && s_description) ++ CImg::string(s_description).move_to(*description); ++ } ++ const unsigned int spectrum = !is_spp || photo>=3?(photo>1?3:1):samplesperpixel; ++ assign(nx,ny,1,spectrum); ++ ++ if ((photo>=3 && sampleformat==1 && ++ (bitspersample==4 || bitspersample==8) && ++ (samplesperpixel==1 || samplesperpixel==3 || samplesperpixel==4)) || ++ (bitspersample==1 && samplesperpixel==1)) { ++ // Special case for unsigned color images. ++ uint32 *const raster = (uint32*)_TIFFmalloc(nx*ny*sizeof(uint32)); ++ if (!raster) { ++ _TIFFfree(raster); TIFFClose(tif); ++ throw CImgException(_cimg_instance ++ "load_tiff(): Failed to allocate memory (%s) for file '%s'.", ++ cimg_instance, ++ cimg::strbuffersize(nx*ny*sizeof(uint32)),filename); ++ } ++ TIFFReadRGBAImage(tif,nx,ny,raster,0); ++ switch (spectrum) { ++ case 1 : ++ cimg_forXY(*this,x,y) ++ (*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny - 1 -y) + x]); ++ 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); ++ } else { // Other cases. ++ uint16 config; ++ TIFFGetField(tif,TIFFTAG_PLANARCONFIG,&config); ++ if (TIFFIsTiled(tif)) { ++ uint32 tw = 1, th = 1; ++ TIFFGetField(tif,TIFFTAG_TILEWIDTH,&tw); ++ TIFFGetField(tif,TIFFTAG_TILELENGTH,&th); ++ if (config==PLANARCONFIG_CONTIG) switch (bitspersample) { ++ case 8 : ++ if (sampleformat==SAMPLEFORMAT_UINT) ++ _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); ++ else _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); ++ break; ++ case 16 : ++ if (sampleformat==SAMPLEFORMAT_UINT) ++ _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); ++ else _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); ++ break; ++ case 32 : ++ if (sampleformat==SAMPLEFORMAT_UINT) ++ _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); ++ else if (sampleformat==SAMPLEFORMAT_INT) ++ _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); ++ else _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); ++ break; ++ case 64 : ++ if (sampleformat==SAMPLEFORMAT_UINT) ++ _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); ++ else if (sampleformat==SAMPLEFORMAT_INT) ++ _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); ++ else _load_tiff_tiled_contig(tif,samplesperpixel,nx,ny,tw,th); ++ break; ++ } else switch (bitspersample) { ++ case 8 : ++ if (sampleformat==SAMPLEFORMAT_UINT) ++ _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); ++ else _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); ++ break; ++ case 16 : ++ if (sampleformat==SAMPLEFORMAT_UINT) ++ _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); ++ else _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); ++ break; ++ case 32 : ++ if (sampleformat==SAMPLEFORMAT_UINT) ++ _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); ++ else if (sampleformat==SAMPLEFORMAT_INT) ++ _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); ++ else _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); ++ break; ++ case 64 : ++ if (sampleformat==SAMPLEFORMAT_UINT) ++ _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); ++ else if (sampleformat==SAMPLEFORMAT_INT) ++ _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); ++ else _load_tiff_tiled_separate(tif,samplesperpixel,nx,ny,tw,th); ++ break; ++ } ++ } else { ++ if (config==PLANARCONFIG_CONTIG) switch (bitspersample) { ++ case 8 : ++ if (sampleformat==SAMPLEFORMAT_UINT) ++ _load_tiff_contig(tif,samplesperpixel,nx,ny); ++ else _load_tiff_contig(tif,samplesperpixel,nx,ny); ++ break; ++ case 16 : ++ if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_contig(tif,samplesperpixel,nx,ny); ++ else _load_tiff_contig(tif,samplesperpixel,nx,ny); ++ break; ++ case 32 : ++ if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_contig(tif,samplesperpixel,nx,ny); ++ else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_contig(tif,samplesperpixel,nx,ny); ++ else _load_tiff_contig(tif,samplesperpixel,nx,ny); ++ break; ++ case 64 : ++ if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_contig(tif,samplesperpixel,nx,ny); ++ else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_contig(tif,samplesperpixel,nx,ny); ++ else _load_tiff_contig(tif,samplesperpixel,nx,ny); ++ break; ++ } else switch (bitspersample) { ++ case 8 : ++ if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate(tif,samplesperpixel,nx,ny); ++ else _load_tiff_separate(tif,samplesperpixel,nx,ny); ++ break; ++ case 16 : ++ if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate(tif,samplesperpixel,nx,ny); ++ else _load_tiff_separate(tif,samplesperpixel,nx,ny); ++ break; ++ case 32 : ++ if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate(tif,samplesperpixel,nx,ny); ++ else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_separate(tif,samplesperpixel,nx,ny); ++ else _load_tiff_separate(tif,samplesperpixel,nx,ny); ++ break; ++ case 64 : ++ if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate(tif,samplesperpixel,nx,ny); ++ else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_separate(tif,samplesperpixel,nx,ny); ++ else _load_tiff_separate(tif,samplesperpixel,nx,ny); ++ break; ++ } ++ } ++ } ++ return *this; ++ } ++#endif ++ ++ //! Load image from a MINC2 file. ++ /** ++ \param filename Filename, as a C-string. ++ **/ ++ // (Original code by Haz-Edine Assemlal). ++ CImg& load_minc2(const char *const filename) { ++ if (!filename) ++ throw CImgArgumentException(_cimg_instance ++ "load_minc2(): Specified filename is (null).", ++ cimg_instance); ++#ifndef cimg_use_minc2 ++ return load_other(filename); ++#else ++ minc::minc_1_reader rdr; ++ rdr.open(filename); ++ assign(rdr.ndim(1)?rdr.ndim(1):1, ++ rdr.ndim(2)?rdr.ndim(2):1, ++ rdr.ndim(3)?rdr.ndim(3):1, ++ rdr.ndim(4)?rdr.ndim(4):1); ++ if (cimg::type::string()==cimg::type::string()) ++ rdr.setup_read_byte(); ++ else if (cimg::type::string()==cimg::type::string()) ++ rdr.setup_read_int(); ++ else if (cimg::type::string()==cimg::type::string()) ++ rdr.setup_read_double(); ++ else ++ rdr.setup_read_float(); ++ minc::load_standard_volume(rdr,this->_data); ++ return *this; ++#endif ++ } ++ ++ //! Load image from a MINC2 file \newinstance. ++ static CImg get_load_minc2(const char *const filename) { ++ return CImg().load_analyze(filename); ++ } ++ ++ //! Load image from an ANALYZE7.5/NIFTI file. ++ /** ++ \param filename Filename, as a C-string. ++ \param[out] voxel_size Pointer to the three voxel sizes read from the file. ++ **/ ++ CImg& load_analyze(const char *const filename, float *const voxel_size=0) { ++ return _load_analyze(0,filename,voxel_size); ++ } ++ ++ //! Load image from an ANALYZE7.5/NIFTI file \newinstance. ++ static CImg get_load_analyze(const char *const filename, float *const voxel_size=0) { ++ return CImg().load_analyze(filename,voxel_size); ++ } ++ ++ //! Load image from an ANALYZE7.5/NIFTI file \overloading. ++ CImg& load_analyze(std::FILE *const file, float *const voxel_size=0) { ++ return _load_analyze(file,0,voxel_size); ++ } ++ ++ //! Load image from an ANALYZE7.5/NIFTI file \newinstance. ++ static CImg get_load_analyze(std::FILE *const file, float *const voxel_size=0) { ++ return CImg().load_analyze(file,voxel_size); ++ } ++ ++ CImg& _load_analyze(std::FILE *const file, const char *const filename, float *const voxel_size=0) { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "load_analyze(): Specified filename is (null).", ++ cimg_instance); ++ ++ std::FILE *nfile_header = 0, *nfile = 0; ++ if (!file) { ++ CImg body(1024); ++ const char *const ext = cimg::split_filename(filename,body); ++ if (!cimg::strcasecmp(ext,"hdr")) { // File is an Analyze header file. ++ nfile_header = cimg::fopen(filename,"rb"); ++ cimg_sprintf(body._data + std::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_sprintf(body._data + std::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_instance ++ "load_analyze(): Invalid Analyze7.5 or NIFTI header in file '%s'.", ++ cimg_instance, ++ 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_instance ++ "load_analyze(): Invalid zero-size header in file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ if (header_size>=4096) { endian = true; cimg::invert_endianness(header_size); } ++ ++ unsigned char *const 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 + 108),1); ++ cimg::invert_endianness((float*)(header + 112),1); ++ } ++ ++ if (nfile_header==nfile) { ++ const unsigned int vox_offset = (unsigned int)*(float*)(header + 108); ++ std::fseek(nfile,vox_offset,SEEK_SET); ++ } ++ ++ unsigned short *dim = (unsigned short*)(header + 40), dimx = 1, dimy = 1, dimz = 1, dimv = 1; ++ if (!dim[0]) ++ cimg::warn(_cimg_instance ++ "load_analyze(): File '%s' defines an image with zero dimensions.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ ++ if (dim[0]>4) ++ cimg::warn(_cimg_instance ++ "load_analyze(): File '%s' defines an image with %u dimensions, reading only the 4 first.", ++ cimg_instance, ++ 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 = *(unsigned short*)(header + 70); ++ if (voxel_size) { ++ const float *vsize = (float*)(header + 76); ++ voxel_size[0] = vsize[1]; voxel_size[1] = vsize[2]; voxel_size[2] = vsize[3]; ++ } ++ delete[] header; ++ ++ // Read pixel data. ++ assign(dimx,dimy,dimz,dimv); ++ const size_t pdim = (size_t)dimx*dimy*dimz*dimv; ++ switch (datatype) { ++ case 2 : { ++ unsigned char *const buffer = new unsigned char[pdim]; ++ cimg::fread(buffer,pdim,nfile); ++ cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor); ++ delete[] buffer; ++ } break; ++ case 4 : { ++ short *const buffer = new short[pdim]; ++ cimg::fread(buffer,pdim,nfile); ++ if (endian) cimg::invert_endianness(buffer,pdim); ++ cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor); ++ delete[] buffer; ++ } break; ++ case 8 : { ++ int *const buffer = new int[pdim]; ++ cimg::fread(buffer,pdim,nfile); ++ if (endian) cimg::invert_endianness(buffer,pdim); ++ cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor); ++ delete[] buffer; ++ } break; ++ case 16 : { ++ float *const buffer = new float[pdim]; ++ cimg::fread(buffer,pdim,nfile); ++ if (endian) cimg::invert_endianness(buffer,pdim); ++ cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor); ++ delete[] buffer; ++ } break; ++ case 64 : { ++ double *const buffer = new double[pdim]; ++ cimg::fread(buffer,pdim,nfile); ++ if (endian) cimg::invert_endianness(buffer,pdim); ++ cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor); ++ delete[] buffer; ++ } break; ++ default : ++ if (!file) cimg::fclose(nfile); ++ throw CImgIOException(_cimg_instance ++ "load_analyze(): Unable to load datatype %d in file '%s'", ++ cimg_instance, ++ datatype,filename?filename:"(FILE*)"); ++ } ++ if (!file) cimg::fclose(nfile); ++ return *this; ++ } ++ ++ //! Load image from a .cimg[z] file. ++ /** ++ \param filename Filename, as a C-string. ++ \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. ++ \param align Appending alignment. ++ **/ ++ CImg& load_cimg(const char *const filename, const char axis='z', const float align=0) { ++ CImgList list; ++ list.load_cimg(filename); ++ if (list._width==1) return list[0].move_to(*this); ++ return assign(list.get_append(axis,align)); ++ } ++ ++ //! Load image from a .cimg[z] file \newinstance ++ static CImg get_load_cimg(const char *const filename, const char axis='z', const float align=0) { ++ return CImg().load_cimg(filename,axis,align); ++ } ++ ++ //! Load image from a .cimg[z] file \overloading. ++ CImg& load_cimg(std::FILE *const file, const char axis='z', const float align=0) { ++ CImgList list; ++ list.load_cimg(file); ++ if (list._width==1) return list[0].move_to(*this); ++ return assign(list.get_append(axis,align)); ++ } ++ ++ //! Load image from a .cimg[z] file \newinstance ++ static CImg get_load_cimg(std::FILE *const file, const char axis='z', const float align=0) { ++ return CImg().load_cimg(file,axis,align); ++ } ++ ++ //! Load sub-images of a .cimg file. ++ /** ++ \param filename Filename, as a C-string. ++ \param n0 Starting frame. ++ \param n1 Ending frame (~0U for max). ++ \param x0 X-coordinate of the starting sub-image vertex. ++ \param y0 Y-coordinate of the starting sub-image vertex. ++ \param z0 Z-coordinate of the starting sub-image vertex. ++ \param c0 C-coordinate of the starting sub-image vertex. ++ \param x1 X-coordinate of the ending sub-image vertex (~0U for max). ++ \param y1 Y-coordinate of the ending sub-image vertex (~0U for max). ++ \param z1 Z-coordinate of the ending sub-image vertex (~0U for max). ++ \param c1 C-coordinate of the ending sub-image vertex (~0U for max). ++ \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. ++ \param align Appending alignment. ++ **/ ++ 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 c0, ++ const unsigned int x1, const unsigned int y1, ++ const unsigned int z1, const unsigned int c1, ++ const char axis='z', const float align=0) { ++ CImgList list; ++ list.load_cimg(filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); ++ if (list._width==1) return list[0].move_to(*this); ++ return assign(list.get_append(axis,align)); ++ } ++ ++ //! Load sub-images of a .cimg file \newinstance. ++ 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 c0, ++ const unsigned int x1, const unsigned int y1, ++ const unsigned int z1, const unsigned int c1, ++ const char axis='z', const float align=0) { ++ return CImg().load_cimg(filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1,axis,align); ++ } ++ ++ //! Load sub-images of a .cimg file \overloading. ++ CImg& load_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 c0, ++ const unsigned int x1, const unsigned int y1, ++ const unsigned int z1, const unsigned int c1, ++ const char axis='z', const float align=0) { ++ CImgList list; ++ list.load_cimg(file,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); ++ if (list._width==1) return list[0].move_to(*this); ++ return assign(list.get_append(axis,align)); ++ } ++ ++ //! Load sub-images of a .cimg file \newinstance. ++ static CImg get_load_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 c0, ++ const unsigned int x1, const unsigned int y1, ++ const unsigned int z1, const unsigned int c1, ++ const char axis='z', const float align=0) { ++ return CImg().load_cimg(file,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1,axis,align); ++ } ++ ++ //! Load image from an INRIMAGE-4 file. ++ /** ++ \param filename Filename, as a C-string. ++ \param[out] voxel_size Pointer to the three voxel sizes read from the file. ++ **/ ++ CImg& load_inr(const char *const filename, float *const voxel_size=0) { ++ return _load_inr(0,filename,voxel_size); ++ } ++ ++ //! Load image from an INRIMAGE-4 file \newinstance. ++ static CImg get_load_inr(const char *const filename, float *const voxel_size=0) { ++ return CImg().load_inr(filename,voxel_size); ++ } ++ ++ //! Load image from an INRIMAGE-4 file \overloading. ++ CImg& load_inr(std::FILE *const file, float *const voxel_size=0) { ++ return _load_inr(file,0,voxel_size); ++ } ++ ++ //! Load image from an INRIMAGE-4 file \newinstance. ++ static CImg get_load_inr(std::FILE *const file, float *voxel_size=0) { ++ return CImg().load_inr(file,voxel_size); ++ } ++ ++ static void _load_inr_header(std::FILE *file, int out[8], float *const voxel_size) { ++ CImg item(1024), tmp1(64), tmp2(64); ++ *item = *tmp1 = *tmp2 = 0; ++ out[0] = std::fscanf(file,"%63s",item._data); ++ 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(): INRIMAGE-4 header not found.", ++ pixel_type()); ++ ++ while (std::fscanf(file," %63[^\n]%*c",item._data)!=EOF && std::strncmp(item,"##}",3)) { ++ cimg_sscanf(item," XDIM%*[^0-9]%d",out); ++ cimg_sscanf(item," YDIM%*[^0-9]%d",out + 1); ++ cimg_sscanf(item," ZDIM%*[^0-9]%d",out + 2); ++ cimg_sscanf(item," VDIM%*[^0-9]%d",out + 3); ++ cimg_sscanf(item," PIXSIZE%*[^0-9]%d",out + 6); ++ if (voxel_size) { ++ cimg_sscanf(item," VX%*[^0-9.+-]%f",voxel_size); ++ cimg_sscanf(item," VY%*[^0-9.+-]%f",voxel_size + 1); ++ cimg_sscanf(item," VZ%*[^0-9.+-]%f",voxel_size + 2); ++ } ++ if (cimg_sscanf(item," CPU%*[ =]%s",tmp1._data)) out[7] = cimg::strncasecmp(tmp1,"sun",3)?0:1; ++ switch (cimg_sscanf(item," TYPE%*[ =]%s %s",tmp1._data,tmp2._data)) { ++ case 0 : break; ++ case 2 : ++ out[5] = cimg::strncasecmp(tmp1,"unsigned",8)?1:0; ++ std::strncpy(tmp1,tmp2,tmp1._width - 1); // fallthrough ++ 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; // fallthrough ++ default : ++ throw CImgIOException("CImg<%s>::load_inr(): Invalid pixel type '%s' defined in header.", ++ pixel_type(), ++ tmp2._data); ++ } ++ } ++ if (out[0]<0 || out[1]<0 || out[2]<0 || out[3]<0) ++ throw CImgIOException("CImg<%s>::load_inr(): Invalid dimensions (%d,%d,%d,%d) defined in header.", ++ pixel_type(), ++ out[0],out[1],out[2],out[3]); ++ if (out[4]<0 || out[5]<0) ++ throw CImgIOException("CImg<%s>::load_inr(): Incomplete pixel type defined in header.", ++ pixel_type()); ++ if (out[6]<0) ++ throw CImgIOException("CImg<%s>::load_inr(): Incomplete PIXSIZE field defined in header.", ++ pixel_type()); ++ if (out[7]<0) ++ throw CImgIOException("CImg<%s>::load_inr(): Big/Little Endian coding type undefined in header.", ++ pixel_type()); ++ } ++ ++ CImg& _load_inr(std::FILE *const file, const char *const filename, float *const voxel_size) { ++#define _cimg_load_inr_case(Tf,sign,pixsize,Ts) \ ++ if (!loaded && fopt[6]==pixsize && fopt[4]==Tf && fopt[5]==sign) { \ ++ Ts *xval, *const val = new Ts[(size_t)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_forC(*this,c) (*this)(x,y,z,c) = (T)*(xval++); \ ++ } \ ++ delete[] val; \ ++ loaded = true; \ ++ } ++ ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "load_inr(): Specified filename is (null).", ++ cimg_instance); ++ ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); ++ int fopt[8], endian = cimg::endianness()?1:0; ++ bool loaded = false; ++ if (voxel_size) voxel_size[0] = voxel_size[1] = voxel_size[2] = 1; ++ _load_inr_header(nfile,fopt,voxel_size); ++ 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_instance ++ "load_inr(): Unknown pixel type defined in file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ } ++ if (!file) cimg::fclose(nfile); ++ return *this; ++ } ++ ++ //! Load image from a EXR file. ++ /** ++ \param filename Filename, as a C-string. ++ **/ ++ CImg& load_exr(const char *const filename) { ++ if (!filename) ++ throw CImgArgumentException(_cimg_instance ++ "load_exr(): Specified filename is (null).", ++ cimg_instance); ++#if defined(cimg_use_openexr) ++ Imf::RgbaInputFile file(filename); ++ Imath::Box2i dw = file.dataWindow(); ++ const int ++ inwidth = dw.max.x - dw.min.x + 1, ++ inheight = dw.max.y - dw.min.y + 1; ++ Imf::Array2D pixels; ++ pixels.resizeErase(inheight,inwidth); ++ file.setFrameBuffer(&pixels[0][0] - dw.min.x - dw.min.y*inwidth, 1, inwidth); ++ file.readPixels(dw.min.y, dw.max.y); ++ assign(inwidth,inheight,1,4); ++ T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2), *ptr_a = data(0,0,0,3); ++ cimg_forXY(*this,x,y) { ++ *(ptr_r++) = (T)pixels[y][x].r; ++ *(ptr_g++) = (T)pixels[y][x].g; ++ *(ptr_b++) = (T)pixels[y][x].b; ++ *(ptr_a++) = (T)pixels[y][x].a; ++ } ++#elif defined(cimg_use_tinyexr) ++ float *res; ++ const char *err = 0; ++ int width = 0, height = 0; ++ const int ret = LoadEXR(&res,&width,&height,filename,&err); ++ if (ret) throw CImgIOException(_cimg_instance ++ "load_exr(): Unable to load EXR file '%s'.", ++ cimg_instance,filename); ++ CImg(out,4,width,height,1,true).get_permute_axes("yzcx").move_to(*this); ++ std::free(res); ++#else ++ return load_other(filename); ++#endif ++ return *this; ++ } ++ ++ //! Load image from a EXR file \newinstance. ++ static CImg get_load_exr(const char *const filename) { ++ return CImg().load_exr(filename); ++ } ++ ++ //! Load image from a PANDORE-5 file. ++ /** ++ \param filename Filename, as a C-string. ++ **/ ++ CImg& load_pandore(const char *const filename) { ++ return _load_pandore(0,filename); ++ } ++ ++ //! Load image from a PANDORE-5 file \newinstance. ++ static CImg get_load_pandore(const char *const filename) { ++ return CImg().load_pandore(filename); ++ } ++ ++ //! Load image from a PANDORE-5 file \overloading. ++ CImg& load_pandore(std::FILE *const file) { ++ return _load_pandore(file,0); ++ } ++ ++ //! Load image from a PANDORE-5 file \newinstance. ++ static CImg get_load_pandore(std::FILE *const file) { ++ return CImg().load_pandore(file); ++ } ++ ++ CImg& _load_pandore(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 size_t 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_instance \ ++ "load_pandore(): Unknown pixel datatype in file '%s'.", \ ++ cimg_instance, \ ++ filename?filename:"(FILE*)"); } ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "load_pandore(): Specified filename is (null).", ++ cimg_instance); ++ ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); ++ CImg header(32); ++ cimg::fread(header._data,12,nfile); ++ if (cimg::strncasecmp("PANDORE",header,7)) { ++ if (!file) cimg::fclose(nfile); ++ throw CImgIOException(_cimg_instance ++ "load_pandore(): PANDORE header not found in file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ } ++ unsigned int imageid, dims[8] = { 0 }; ++ int ptbuf[4] = { 0 }; ++ cimg::fread(&imageid,1,nfile); ++ const bool endian = imageid>255; ++ if (endian) cimg::invert_endianness(imageid); ++ cimg::fread(header._data,20,nfile); ++ ++ switch (imageid) { ++ case 2 : _cimg_load_pandore_case(2,dims[1],1,1,1,unsigned char,unsigned char,unsigned char,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,unsigned char,unsigned char,unsigned char,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,unsigned char,unsigned char,unsigned char,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 size_t 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 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 13 : { // Region 3d ++ cimg::fread(dims,5,nfile); ++ if (endian) cimg::invert_endianness(dims,5); ++ assign(dims[3],dims[2],dims[1],1); ++ const size_t 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,unsigned char,unsigned char,unsigned char,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,unsigned char,unsigned char,unsigned char,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],unsigned char,unsigned char,unsigned char,1); break; ++ case 23 : _cimg_load_pandore_case(2,dims[1],1,1,dims[0],long,int,short,4); break; ++ case 24 : _cimg_load_pandore_case(2,dims[1],1,1,dims[0],unsigned long,unsigned int,unsigned short,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],unsigned char,unsigned char,unsigned char,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],unsigned long,unsigned int,unsigned short,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],unsigned char,unsigned char,unsigned char,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],unsigned long,unsigned int,unsigned short,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 ++ cimg::fread(ptbuf,1,nfile); ++ if (endian) cimg::invert_endianness(ptbuf,1); ++ assign(1); (*this)(0) = (T)ptbuf[0]; ++ } break; ++ case 35 : { // Points 2d ++ 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 ++ 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_instance ++ "load_pandore(): Unable to load data with ID_type %u in file '%s'.", ++ cimg_instance, ++ imageid,filename?filename:"(FILE*)"); ++ } ++ if (!file) cimg::fclose(nfile); ++ return *this; ++ } ++ ++ //! Load image from a PAR-REC (Philips) file. ++ /** ++ \param filename Filename, as a C-string. ++ \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. ++ \param align Appending alignment. ++ **/ ++ CImg& load_parrec(const char *const filename, const char axis='c', const float align=0) { ++ CImgList list; ++ list.load_parrec(filename); ++ if (list._width==1) return list[0].move_to(*this); ++ return assign(list.get_append(axis,align)); ++ } ++ ++ //! Load image from a PAR-REC (Philips) file \newinstance. ++ static CImg get_load_parrec(const char *const filename, const char axis='c', const float align=0) { ++ return CImg().load_parrec(filename,axis,align); ++ } ++ ++ //! Load image from a raw binary file. ++ /** ++ \param filename Filename, as a C-string. ++ \param size_x Width of the image buffer. ++ \param size_y Height of the image buffer. ++ \param size_z Depth of the image buffer. ++ \param size_c Spectrum of the image buffer. ++ \param is_multiplexed Tells if the image values are multiplexed along the C-axis. ++ \param invert_endianness Tells if the endianness of the image buffer must be inverted. ++ \param offset Starting offset of the read in the specified file. ++ **/ ++ CImg& load_raw(const char *const filename, ++ const unsigned int size_x=0, const unsigned int size_y=1, ++ const unsigned int size_z=1, const unsigned int size_c=1, ++ const bool is_multiplexed=false, const bool invert_endianness=false, ++ const ulongT offset=0) { ++ return _load_raw(0,filename,size_x,size_y,size_z,size_c,is_multiplexed,invert_endianness,offset); ++ } ++ ++ //! Load image from a raw binary file \newinstance. ++ static CImg get_load_raw(const char *const filename, ++ const unsigned int size_x=0, const unsigned int size_y=1, ++ const unsigned int size_z=1, const unsigned int size_c=1, ++ const bool is_multiplexed=false, const bool invert_endianness=false, ++ const ulongT offset=0) { ++ return CImg().load_raw(filename,size_x,size_y,size_z,size_c,is_multiplexed,invert_endianness,offset); ++ } ++ ++ //! Load image from a raw binary file \overloading. ++ CImg& load_raw(std::FILE *const file, ++ const unsigned int size_x=0, const unsigned int size_y=1, ++ const unsigned int size_z=1, const unsigned int size_c=1, ++ const bool is_multiplexed=false, const bool invert_endianness=false, ++ const ulongT offset=0) { ++ return _load_raw(file,0,size_x,size_y,size_z,size_c,is_multiplexed,invert_endianness,offset); ++ } ++ ++ //! Load image from a raw binary file \newinstance. ++ static CImg get_load_raw(std::FILE *const file, ++ const unsigned int size_x=0, const unsigned int size_y=1, ++ const unsigned int size_z=1, const unsigned int size_c=1, ++ const bool is_multiplexed=false, const bool invert_endianness=false, ++ const ulongT offset=0) { ++ return CImg().load_raw(file,size_x,size_y,size_z,size_c,is_multiplexed,invert_endianness,offset); ++ } ++ ++ CImg& _load_raw(std::FILE *const file, const char *const filename, ++ const unsigned int size_x, const unsigned int size_y, ++ const unsigned int size_z, const unsigned int size_c, ++ const bool is_multiplexed, const bool invert_endianness, ++ const ulongT offset) { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "load_raw(): Specified filename is (null).", ++ cimg_instance); ++ if (cimg::is_directory(filename)) ++ throw CImgArgumentException(_cimg_instance ++ "load_raw(): Specified filename '%s' is a directory.", ++ cimg_instance,filename); ++ ++ ulongT siz = (ulongT)size_x*size_y*size_z*size_c; ++ unsigned int ++ _size_x = size_x, ++ _size_y = size_y, ++ _size_z = size_z, ++ _size_c = size_c; ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); ++ if (!siz) { // Retrieve file size. ++ const longT fpos = cimg::ftell(nfile); ++ if (fpos<0) throw CImgArgumentException(_cimg_instance ++ "load_raw(): Cannot determine size of input file '%s'.", ++ cimg_instance,filename?filename:"(FILE*)"); ++ cimg::fseek(nfile,0,SEEK_END); ++ siz = cimg::ftell(nfile)/sizeof(T); ++ _size_y = (unsigned int)siz; ++ _size_x = _size_z = _size_c = 1; ++ cimg::fseek(nfile,fpos,SEEK_SET); ++ } ++ cimg::fseek(nfile,offset,SEEK_SET); ++ assign(_size_x,_size_y,_size_z,_size_c,0); ++ if (siz && (!is_multiplexed || size_c==1)) { ++ cimg::fread(_data,siz,nfile); ++ if (invert_endianness) cimg::invert_endianness(_data,siz); ++ } else if (siz) { ++ CImg buf(1,1,1,_size_c); ++ cimg_forXYZ(*this,x,y,z) { ++ cimg::fread(buf._data,_size_c,nfile); ++ if (invert_endianness) cimg::invert_endianness(buf._data,_size_c); ++ set_vector_at(buf,x,y,z); ++ } ++ } ++ if (!file) cimg::fclose(nfile); ++ return *this; ++ } ++ ++ //! Load image sequence from a YUV file. ++ /** ++ \param filename Filename, as a C-string. ++ \param size_x Width of the frames. ++ \param size_y Height of the frames. ++ \param chroma_subsampling Type of chroma subsampling. Can be { 420 | 422 | 444 }. ++ \param first_frame Index of the first frame to read. ++ \param last_frame Index of the last frame to read. ++ \param step_frame Step value for frame reading. ++ \param yuv2rgb Tells if the YUV to RGB transform must be applied. ++ \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. ++ **/ ++ CImg& load_yuv(const char *const filename, ++ const unsigned int size_x, const unsigned int size_y=1, ++ const unsigned int chroma_subsampling=444, ++ 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') { ++ return get_load_yuv(filename,size_x,size_y,chroma_subsampling, ++ first_frame,last_frame,step_frame,yuv2rgb,axis).move_to(*this); ++ } ++ ++ //! Load image sequence from a YUV file \newinstance. ++ static CImg get_load_yuv(const char *const filename, ++ const unsigned int size_x, const unsigned int size_y=1, ++ const unsigned int chroma_subsampling=444, ++ 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') { ++ return CImgList().load_yuv(filename,size_x,size_y,chroma_subsampling, ++ first_frame,last_frame,step_frame,yuv2rgb).get_append(axis); ++ } ++ ++ //! Load image sequence from a YUV file \overloading. ++ CImg& load_yuv(std::FILE *const file, ++ const unsigned int size_x, const unsigned int size_y=1, ++ const unsigned int chroma_subsampling=444, ++ 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') { ++ return get_load_yuv(file,size_x,size_y,chroma_subsampling, ++ first_frame,last_frame,step_frame,yuv2rgb,axis).move_to(*this); ++ } ++ ++ //! Load image sequence from a YUV file \newinstance. ++ static CImg get_load_yuv(std::FILE *const file, ++ const unsigned int size_x, const unsigned int size_y=1, ++ const unsigned int chroma_subsampling=444, ++ 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') { ++ return CImgList().load_yuv(file,size_x,size_y,chroma_subsampling, ++ first_frame,last_frame,step_frame,yuv2rgb).get_append(axis); ++ } ++ ++ //! Load 3d object from a .OFF file. ++ /** ++ \param[out] primitives Primitives data of the 3d object. ++ \param[out] colors Colors data of the 3d object. ++ \param filename Filename, as a C-string. ++ **/ ++ template ++ CImg& load_off(CImgList& primitives, CImgList& colors, const char *const filename) { ++ return _load_off(primitives,colors,0,filename); ++ } ++ ++ //! Load 3d object from a .OFF file \newinstance. ++ template ++ static CImg get_load_off(CImgList& primitives, CImgList& colors, const char *const filename) { ++ return CImg().load_off(primitives,colors,filename); ++ } ++ ++ //! Load 3d object from a .OFF file \overloading. ++ template ++ CImg& load_off(CImgList& primitives, CImgList& colors, std::FILE *const file) { ++ return _load_off(primitives,colors,file,0); ++ } ++ ++ //! Load 3d object from a .OFF file \newinstance. ++ template ++ static CImg get_load_off(CImgList& primitives, CImgList& colors, std::FILE *const file) { ++ return CImg().load_off(primitives,colors,file); ++ } ++ ++ template ++ CImg& _load_off(CImgList& primitives, CImgList& colors, ++ std::FILE *const file, const char *const filename) { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "load_off(): Specified filename is (null).", ++ cimg_instance); ++ ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"r"); ++ unsigned int nb_points = 0, nb_primitives = 0, nb_read = 0; ++ CImg line(256); *line = 0; ++ int err; ++ ++ // Skip comments, and read magic string OFF ++ do { err = std::fscanf(nfile,"%255[^\n] ",line._data); } while (!err || (err==1 && *line=='#')); ++ if (cimg::strncasecmp(line,"OFF",3) && cimg::strncasecmp(line,"COFF",4)) { ++ if (!file) cimg::fclose(nfile); ++ throw CImgIOException(_cimg_instance ++ "load_off(): OFF header not found in file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ } ++ do { err = std::fscanf(nfile,"%255[^\n] ",line._data); } while (!err || (err==1 && *line=='#')); ++ if ((err = cimg_sscanf(line,"%u%u%*[^\n] ",&nb_points,&nb_primitives))!=2) { ++ if (!file) cimg::fclose(nfile); ++ throw CImgIOException(_cimg_instance ++ "load_off(): Invalid number of vertices or primitives specified in file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ } ++ ++ // Read points data ++ assign(nb_points,3); ++ float X = 0, Y = 0, Z = 0; ++ cimg_forX(*this,l) { ++ do { err = std::fscanf(nfile,"%255[^\n] ",line._data); } while (!err || (err==1 && *line=='#')); ++ if ((err = cimg_sscanf(line,"%f%f%f%*[^\n] ",&X,&Y,&Z))!=3) { ++ if (!file) cimg::fclose(nfile); ++ throw CImgIOException(_cimg_instance ++ "load_off(): Failed to read vertex %u/%u in file '%s'.", ++ cimg_instance, ++ l + 1,nb_points,filename?filename:"(FILE*)"); ++ } ++ (*this)(l,0) = (T)X; (*this)(l,1) = (T)Y; (*this)(l,2) = (T)Z; ++ } ++ ++ // Read primitive data ++ primitives.assign(); ++ colors.assign(); ++ bool stop_flag = false; ++ while (!stop_flag) { ++ 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; ++ if ((err = std::fscanf(nfile,"%u",&prim))!=1) stop_flag = true; ++ else { ++ ++nb_read; ++ switch (prim) { ++ case 1 : { ++ if ((err = std::fscanf(nfile,"%u%255[^\n] ",&i0,line._data))<2) { ++ cimg::warn(_cimg_instance ++ "load_off(): Failed to read primitive %u/%u from file '%s'.", ++ cimg_instance, ++ nb_read,nb_primitives,filename?filename:"(FILE*)"); ++ ++ err = std::fscanf(nfile,"%*[^\n] "); ++ } else { ++ err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); ++ CImg::vector(i0).move_to(primitives); ++ CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors); ++ } ++ } break; ++ case 2 : { ++ if ((err = std::fscanf(nfile,"%u%u%255[^\n] ",&i0,&i1,line._data))<2) { ++ cimg::warn(_cimg_instance ++ "load_off(): Failed to read primitive %u/%u from file '%s'.", ++ cimg_instance, ++ nb_read,nb_primitives,filename?filename:"(FILE*)"); ++ ++ err = std::fscanf(nfile,"%*[^\n] "); ++ } else { ++ err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); ++ CImg::vector(i0,i1).move_to(primitives); ++ CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors); ++ } ++ } break; ++ case 3 : { ++ if ((err = std::fscanf(nfile,"%u%u%u%255[^\n] ",&i0,&i1,&i2,line._data))<3) { ++ cimg::warn(_cimg_instance ++ "load_off(): Failed to read primitive %u/%u from file '%s'.", ++ cimg_instance, ++ nb_read,nb_primitives,filename?filename:"(FILE*)"); ++ ++ err = std::fscanf(nfile,"%*[^\n] "); ++ } else { ++ err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); ++ CImg::vector(i0,i2,i1).move_to(primitives); ++ CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors); ++ } ++ } break; ++ case 4 : { ++ if ((err = std::fscanf(nfile,"%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,line._data))<4) { ++ cimg::warn(_cimg_instance ++ "load_off(): Failed to read primitive %u/%u from file '%s'.", ++ cimg_instance, ++ nb_read,nb_primitives,filename?filename:"(FILE*)"); ++ ++ err = std::fscanf(nfile,"%*[^\n] "); ++ } else { ++ err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); ++ CImg::vector(i0,i3,i2,i1).move_to(primitives); ++ CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors); ++ } ++ } break; ++ case 5 : { ++ if ((err = std::fscanf(nfile,"%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,line._data))<5) { ++ cimg::warn(_cimg_instance ++ "load_off(): Failed to read primitive %u/%u from file '%s'.", ++ cimg_instance, ++ nb_read,nb_primitives,filename?filename:"(FILE*)"); ++ ++ err = std::fscanf(nfile,"%*[^\n] "); ++ } else { ++ err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); ++ CImg::vector(i0,i3,i2,i1).move_to(primitives); ++ CImg::vector(i0,i4,i3).move_to(primitives); ++ colors.insert(2,CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255))); ++ ++nb_primitives; ++ } ++ } break; ++ case 6 : { ++ if ((err = std::fscanf(nfile,"%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,line._data))<6) { ++ cimg::warn(_cimg_instance ++ "load_off(): Failed to read primitive %u/%u from file '%s'.", ++ cimg_instance, ++ nb_read,nb_primitives,filename?filename:"(FILE*)"); ++ ++ err = std::fscanf(nfile,"%*[^\n] "); ++ } else { ++ err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); ++ CImg::vector(i0,i3,i2,i1).move_to(primitives); ++ CImg::vector(i0,i5,i4,i3).move_to(primitives); ++ colors.insert(2,CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255))); ++ ++nb_primitives; ++ } ++ } break; ++ case 7 : { ++ if ((err = std::fscanf(nfile,"%u%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,&i6,line._data))<7) { ++ cimg::warn(_cimg_instance ++ "load_off(): Failed to read primitive %u/%u from file '%s'.", ++ cimg_instance, ++ nb_read,nb_primitives,filename?filename:"(FILE*)"); ++ ++ err = std::fscanf(nfile,"%*[^\n] "); ++ } else { ++ err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); ++ CImg::vector(i0,i4,i3,i1).move_to(primitives); ++ CImg::vector(i0,i6,i5,i4).move_to(primitives); ++ CImg::vector(i3,i2,i1).move_to(primitives); ++ colors.insert(3,CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255))); ++ ++(++nb_primitives); ++ } ++ } break; ++ case 8 : { ++ if ((err = std::fscanf(nfile,"%u%u%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,&i6,&i7,line._data))<7) { ++ cimg::warn(_cimg_instance ++ "load_off(): Failed to read primitive %u/%u from file '%s'.", ++ cimg_instance, ++ nb_read,nb_primitives,filename?filename:"(FILE*)"); ++ ++ err = std::fscanf(nfile,"%*[^\n] "); ++ } else { ++ err = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2); ++ CImg::vector(i0,i3,i2,i1).move_to(primitives); ++ CImg::vector(i0,i5,i4,i3).move_to(primitives); ++ CImg::vector(i0,i7,i6,i5).move_to(primitives); ++ colors.insert(3,CImg::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255))); ++ ++(++nb_primitives); ++ } ++ } break; ++ default : ++ cimg::warn(_cimg_instance ++ "load_off(): Failed to read primitive %u/%u (%u vertices) from file '%s'.", ++ cimg_instance, ++ nb_read,nb_primitives,prim,filename?filename:"(FILE*)"); ++ ++ err = std::fscanf(nfile,"%*[^\n] "); ++ } ++ } ++ } ++ if (!file) cimg::fclose(nfile); ++ if (primitives._width!=nb_primitives) ++ cimg::warn(_cimg_instance ++ "load_off(): Only %u/%u primitives read from file '%s'.", ++ cimg_instance, ++ primitives._width,nb_primitives,filename?filename:"(FILE*)"); ++ return *this; ++ } ++ ++ //! Load image sequence from a video file, using OpenCV library. ++ /** ++ \param filename Filename, as a C-string. ++ \param first_frame Index of the first frame to read. ++ \param last_frame Index of the last frame to read. ++ \param step_frame Step value for frame reading. ++ \param axis Alignment axis. ++ \param align Apending alignment. ++ **/ ++ CImg& load_video(const char *const filename, ++ const unsigned int first_frame=0, const unsigned int last_frame=~0U, ++ const unsigned int step_frame=1, ++ const char axis='z', const float align=0) { ++ return get_load_video(filename,first_frame,last_frame,step_frame,axis,align).move_to(*this); ++ } ++ ++ //! Load image sequence from a video file, using OpenCV library \newinstance. ++ static CImg get_load_video(const char *const filename, ++ const unsigned int first_frame=0, const unsigned int last_frame=~0U, ++ const unsigned int step_frame=1, ++ const char axis='z', const float align=0) { ++ return CImgList().load_video(filename,first_frame,last_frame,step_frame).get_append(axis,align); ++ } ++ ++ //! Load image sequence using FFMPEG's external tool 'ffmpeg'. ++ /** ++ \param filename Filename, as a C-string. ++ \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. ++ \param align Appending alignment. ++ **/ ++ CImg& load_ffmpeg_external(const char *const filename, const char axis='z', const float align=0) { ++ return get_load_ffmpeg_external(filename,axis,align).move_to(*this); ++ } ++ ++ //! Load image sequence using FFMPEG's external tool 'ffmpeg' \newinstance. ++ static CImg get_load_ffmpeg_external(const char *const filename, const char axis='z', const float align=0) { ++ return CImgList().load_ffmpeg_external(filename).get_append(axis,align); ++ } ++ ++ //! Load gif file, using Imagemagick or GraphicsMagicks's external tools. ++ /** ++ \param filename Filename, as a C-string. ++ \param axis Appending axis, if file contains multiple images. Can be { 'x' | 'y' | 'z' | 'c' }. ++ \param align Appending alignment. ++ **/ ++ CImg& load_gif_external(const char *const filename, ++ const char axis='z', const float align=0) { ++ return get_load_gif_external(filename,axis,align).move_to(*this); ++ } ++ ++ //! Load gif file, using ImageMagick or GraphicsMagick's external tool 'convert' \newinstance. ++ static CImg get_load_gif_external(const char *const filename, ++ const char axis='z', const float align=0) { ++ return CImgList().load_gif_external(filename).get_append(axis,align); ++ } ++ ++ //! Load image using GraphicsMagick's external tool 'gm'. ++ /** ++ \param filename Filename, as a C-string. ++ **/ ++ CImg& load_graphicsmagick_external(const char *const filename) { ++ if (!filename) ++ throw CImgArgumentException(_cimg_instance ++ "load_graphicsmagick_external(): Specified filename is (null).", ++ cimg_instance); ++ std::fclose(cimg::fopen(filename,"rb")); // Check if file exists. ++ CImg command(1024), filename_tmp(256); ++ std::FILE *file = 0; ++ const CImg s_filename = CImg::string(filename)._system_strescape(); ++#if cimg_OS==1 ++ if (!cimg::system("which gm")) { ++ cimg_snprintf(command,command._width,"%s convert \"%s\" pnm:-", ++ cimg::graphicsmagick_path(),s_filename.data()); ++ file = popen(command,"r"); ++ if (file) { ++ const unsigned int omode = cimg::exception_mode(); ++ cimg::exception_mode(0); ++ try { load_pnm(file); } catch (...) { ++ pclose(file); ++ cimg::exception_mode(omode); ++ throw CImgIOException(_cimg_instance ++ "load_graphicsmagick_external(): Failed to load file '%s' " ++ "with external command 'gm'.", ++ cimg_instance, ++ filename); ++ } ++ pclose(file); ++ return *this; ++ } ++ } ++#endif ++ do { ++ cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.pnm", ++ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); ++ if ((file=std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); ++ } while (file); ++ cimg_snprintf(command,command._width,"%s convert \"%s\" \"%s\"", ++ cimg::graphicsmagick_path(),s_filename.data(), ++ CImg::string(filename_tmp)._system_strescape().data()); ++ cimg::system(command,cimg::graphicsmagick_path()); ++ if (!(file = std_fopen(filename_tmp,"rb"))) { ++ cimg::fclose(cimg::fopen(filename,"r")); ++ throw CImgIOException(_cimg_instance ++ "load_graphicsmagick_external(): Failed to load file '%s' with external command 'gm'.", ++ cimg_instance, ++ filename); ++ ++ } else cimg::fclose(file); ++ load_pnm(filename_tmp); ++ std::remove(filename_tmp); ++ return *this; ++ } ++ ++ //! Load image using GraphicsMagick's external tool 'gm' \newinstance. ++ static CImg get_load_graphicsmagick_external(const char *const filename) { ++ return CImg().load_graphicsmagick_external(filename); ++ } ++ ++ //! Load gzipped image file, using external tool 'gunzip'. ++ /** ++ \param filename Filename, as a C-string. ++ **/ ++ CImg& load_gzip_external(const char *const filename) { ++ if (!filename) ++ throw CImgIOException(_cimg_instance ++ "load_gzip_external(): Specified filename is (null).", ++ cimg_instance); ++ std::fclose(cimg::fopen(filename,"rb")); // Check if file exists. ++ CImg command(1024), filename_tmp(256), body(256); ++ const char ++ *const ext = cimg::split_filename(filename,body), ++ *const ext2 = cimg::split_filename(body,0); ++ ++ std::FILE *file = 0; ++ do { ++ if (!cimg::strcasecmp(ext,"gz")) { ++ if (*ext2) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", ++ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2); ++ else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", ++ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); ++ } else { ++ if (*ext) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", ++ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext); ++ else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", ++ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); ++ } ++ if ((file=std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); ++ } while (file); ++ cimg_snprintf(command,command._width,"%s -c \"%s\" > \"%s\"", ++ cimg::gunzip_path(), ++ CImg::string(filename)._system_strescape().data(), ++ CImg::string(filename_tmp)._system_strescape().data()); ++ cimg::system(command); ++ if (!(file = std_fopen(filename_tmp,"rb"))) { ++ cimg::fclose(cimg::fopen(filename,"r")); ++ throw CImgIOException(_cimg_instance ++ "load_gzip_external(): Failed to load file '%s' with external command 'gunzip'.", ++ cimg_instance, ++ filename); ++ ++ } else cimg::fclose(file); ++ load(filename_tmp); ++ std::remove(filename_tmp); ++ return *this; ++ } ++ ++ //! Load gzipped image file, using external tool 'gunzip' \newinstance. ++ static CImg get_load_gzip_external(const char *const filename) { ++ return CImg().load_gzip_external(filename); ++ } ++ ++ //! Load image using ImageMagick's external tool 'convert'. ++ /** ++ \param filename Filename, as a C-string. ++ **/ ++ CImg& load_imagemagick_external(const char *const filename) { ++ if (!filename) ++ throw CImgArgumentException(_cimg_instance ++ "load_imagemagick_external(): Specified filename is (null).", ++ cimg_instance); ++ std::fclose(cimg::fopen(filename,"rb")); // Check if file exists. ++ CImg command(1024), filename_tmp(256); ++ std::FILE *file = 0; ++ const CImg s_filename = CImg::string(filename)._system_strescape(); ++#if cimg_OS==1 ++ if (!cimg::system("which convert")) { ++ cimg_snprintf(command,command._width,"%s%s \"%s\" pnm:-", ++ cimg::imagemagick_path(), ++ !cimg::strcasecmp(cimg::split_filename(filename),"pdf")?" -density 400x400":"", ++ s_filename.data()); ++ file = popen(command,"r"); ++ if (file) { ++ const unsigned int omode = cimg::exception_mode(); ++ cimg::exception_mode(0); ++ try { load_pnm(file); } catch (...) { ++ pclose(file); ++ cimg::exception_mode(omode); ++ throw CImgIOException(_cimg_instance ++ "load_imagemagick_external(): Failed to load file '%s' with " ++ "external command 'magick/convert'.", ++ cimg_instance, ++ filename); ++ } ++ pclose(file); ++ return *this; ++ } ++ } ++#endif ++ do { ++ cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.pnm", ++ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); ++ if ((file=std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); ++ } while (file); ++ cimg_snprintf(command,command._width,"%s%s \"%s\" \"%s\"", ++ cimg::imagemagick_path(), ++ !cimg::strcasecmp(cimg::split_filename(filename),"pdf")?" -density 400x400":"", ++ s_filename.data(),CImg::string(filename_tmp)._system_strescape().data()); ++ cimg::system(command,cimg::imagemagick_path()); ++ if (!(file = std_fopen(filename_tmp,"rb"))) { ++ cimg::fclose(cimg::fopen(filename,"r")); ++ throw CImgIOException(_cimg_instance ++ "load_imagemagick_external(): Failed to load file '%s' with " ++ "external command 'magick/convert'.", ++ cimg_instance, ++ filename); ++ ++ } else cimg::fclose(file); ++ load_pnm(filename_tmp); ++ std::remove(filename_tmp); ++ return *this; ++ } ++ ++ //! Load image using ImageMagick's external tool 'convert' \newinstance. ++ static CImg get_load_imagemagick_external(const char *const filename) { ++ return CImg().load_imagemagick_external(filename); ++ } ++ ++ //! Load image from a DICOM file, using XMedcon's external tool 'medcon'. ++ /** ++ \param filename Filename, as a C-string. ++ **/ ++ CImg& load_medcon_external(const char *const filename) { ++ if (!filename) ++ throw CImgArgumentException(_cimg_instance ++ "load_medcon_external(): Specified filename is (null).", ++ cimg_instance); ++ std::fclose(cimg::fopen(filename,"rb")); // Check if file exists. ++ CImg command(1024), filename_tmp(256), body(256); ++ cimg::fclose(cimg::fopen(filename,"r")); ++ std::FILE *file = 0; ++ do { ++ cimg_snprintf(filename_tmp,filename_tmp._width,"%s.hdr",cimg::filenamerand()); ++ if ((file=std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); ++ } while (file); ++ cimg_snprintf(command,command._width,"%s -w -c anlz -o \"%s\" -f \"%s\"", ++ cimg::medcon_path(), ++ CImg::string(filename_tmp)._system_strescape().data(), ++ CImg::string(filename)._system_strescape().data()); ++ cimg::system(command); ++ cimg::split_filename(filename_tmp,body); ++ ++ cimg_snprintf(command,command._width,"%s.hdr",body._data); ++ file = std_fopen(command,"rb"); ++ if (!file) { ++ cimg_snprintf(command,command._width,"m000-%s.hdr",body._data); ++ file = std_fopen(command,"rb"); ++ if (!file) { ++ throw CImgIOException(_cimg_instance ++ "load_medcon_external(): Failed to load file '%s' with external command 'medcon'.", ++ cimg_instance, ++ filename); ++ } ++ } ++ cimg::fclose(file); ++ load_analyze(command); ++ std::remove(command); ++ cimg::split_filename(command,body); ++ cimg_snprintf(command,command._width,"%s.img",body._data); ++ std::remove(command); ++ return *this; ++ } ++ ++ //! Load image from a DICOM file, using XMedcon's external tool 'medcon' \newinstance. ++ static CImg get_load_medcon_external(const char *const filename) { ++ return CImg().load_medcon_external(filename); ++ } ++ ++ //! Load image from a RAW Color Camera file, using external tool 'dcraw'. ++ /** ++ \param filename Filename, as a C-string. ++ **/ ++ CImg& load_dcraw_external(const char *const filename) { ++ if (!filename) ++ throw CImgArgumentException(_cimg_instance ++ "load_dcraw_external(): Specified filename is (null).", ++ cimg_instance); ++ std::fclose(cimg::fopen(filename,"rb")); // Check if file exists. ++ CImg command(1024), filename_tmp(256); ++ std::FILE *file = 0; ++ const CImg s_filename = CImg::string(filename)._system_strescape(); ++#if cimg_OS==1 ++ cimg_snprintf(command,command._width,"%s -w -4 -c \"%s\"", ++ cimg::dcraw_path(),s_filename.data()); ++ file = popen(command,"r"); ++ if (file) { ++ const unsigned int omode = cimg::exception_mode(); ++ cimg::exception_mode(0); ++ try { load_pnm(file); } catch (...) { ++ pclose(file); ++ cimg::exception_mode(omode); ++ throw CImgIOException(_cimg_instance ++ "load_dcraw_external(): Failed to load file '%s' with external command 'dcraw'.", ++ cimg_instance, ++ filename); ++ } ++ pclose(file); ++ return *this; ++ } ++#endif ++ do { ++ cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.ppm", ++ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); ++ if ((file=std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); ++ } while (file); ++ cimg_snprintf(command,command._width,"%s -w -4 -c \"%s\" > \"%s\"", ++ cimg::dcraw_path(),s_filename.data(),CImg::string(filename_tmp)._system_strescape().data()); ++ cimg::system(command,cimg::dcraw_path()); ++ if (!(file = std_fopen(filename_tmp,"rb"))) { ++ cimg::fclose(cimg::fopen(filename,"r")); ++ throw CImgIOException(_cimg_instance ++ "load_dcraw_external(): Failed to load file '%s' with external command 'dcraw'.", ++ cimg_instance, ++ filename); ++ ++ } else cimg::fclose(file); ++ load_pnm(filename_tmp); ++ std::remove(filename_tmp); ++ return *this; ++ } ++ ++ //! Load image from a RAW Color Camera file, using external tool 'dcraw' \newinstance. ++ static CImg get_load_dcraw_external(const char *const filename) { ++ return CImg().load_dcraw_external(filename); ++ } ++ ++ //! Load image from a camera stream, using OpenCV. ++ /** ++ \param camera_index Index of the camera to capture images from. ++ \param skip_frames Number of frames to skip before the capture. ++ \param release_camera Tells if the camera ressource must be released at the end of the method. ++ \param capture_width Width of the desired image. ++ \param capture_height Height of the desired image. ++ **/ ++ CImg& load_camera(const unsigned int camera_index=0, const unsigned int skip_frames=0, ++ const bool release_camera=true, const unsigned int capture_width=0, ++ const unsigned int capture_height=0) { ++#ifdef cimg_use_opencv ++ if (camera_index>99) ++ throw CImgArgumentException(_cimg_instance ++ "load_camera(): Invalid request for camera #%u " ++ "(no more than 100 cameras can be managed simultaneously).", ++ cimg_instance, ++ camera_index); ++ static CvCapture *capture[100] = { 0 }; ++ static unsigned int capture_w[100], capture_h[100]; ++ if (release_camera) { ++ cimg::mutex(9); ++ if (capture[camera_index]) cvReleaseCapture(&(capture[camera_index])); ++ capture[camera_index] = 0; ++ capture_w[camera_index] = capture_h[camera_index] = 0; ++ cimg::mutex(9,0); ++ return *this; ++ } ++ if (!capture[camera_index]) { ++ cimg::mutex(9); ++ capture[camera_index] = cvCreateCameraCapture(camera_index); ++ capture_w[camera_index] = 0; ++ capture_h[camera_index] = 0; ++ cimg::mutex(9,0); ++ if (!capture[camera_index]) { ++ throw CImgIOException(_cimg_instance ++ "load_camera(): Failed to initialize camera #%u.", ++ cimg_instance, ++ camera_index); ++ } ++ } ++ cimg::mutex(9); ++ if (capture_width!=capture_w[camera_index]) { ++ cvSetCaptureProperty(capture[camera_index],CV_CAP_PROP_FRAME_WIDTH,capture_width); ++ capture_w[camera_index] = capture_width; ++ } ++ if (capture_height!=capture_h[camera_index]) { ++ cvSetCaptureProperty(capture[camera_index],CV_CAP_PROP_FRAME_HEIGHT,capture_height); ++ capture_h[camera_index] = capture_height; ++ } ++ const IplImage *img = 0; ++ for (unsigned int i = 0; iwidthStep - 3*img->width); ++ assign(img->width,img->height,1,3); ++ const unsigned char* ptrs = (unsigned char*)img->imageData; ++ T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2); ++ if (step>0) cimg_forY(*this,y) { ++ cimg_forX(*this,x) { *(ptr_b++) = (T)*(ptrs++); *(ptr_g++) = (T)*(ptrs++); *(ptr_r++) = (T)*(ptrs++); } ++ ptrs+=step; ++ } else for (ulongT siz = (ulongT)img->width*img->height; siz; --siz) { ++ *(ptr_b++) = (T)*(ptrs++); *(ptr_g++) = (T)*(ptrs++); *(ptr_r++) = (T)*(ptrs++); ++ } ++ } ++ cimg::mutex(9,0); ++ return *this; ++#else ++ cimg::unused(camera_index,skip_frames,release_camera,capture_width,capture_height); ++ throw CImgIOException(_cimg_instance ++ "load_camera(): This function requires the OpenCV library to run " ++ "(macro 'cimg_use_opencv' must be defined).", ++ cimg_instance); ++#endif ++ } ++ ++ //! Load image from a camera stream, using OpenCV \newinstance. ++ static CImg get_load_camera(const unsigned int camera_index=0, const unsigned int skip_frames=0, ++ const bool release_camera=true, ++ const unsigned int capture_width=0, const unsigned int capture_height=0) { ++ return CImg().load_camera(camera_index,skip_frames,release_camera,capture_width,capture_height); ++ } ++ ++ //! Load image using various non-native ways. ++ /** ++ \param filename Filename, as a C-string. ++ **/ ++ CImg& load_other(const char *const filename) { ++ if (!filename) ++ throw CImgArgumentException(_cimg_instance ++ "load_other(): Specified filename is (null).", ++ cimg_instance); ++ ++ const unsigned int omode = 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&) { ++ try { load_cimg(filename); } ++ catch (CImgException&) { ++ try { ++ std::fclose(cimg::fopen(filename,"rb")); ++ } catch (CImgException&) { ++ cimg::exception_mode(omode); ++ throw CImgIOException(_cimg_instance ++ "load_other(): Failed to open file '%s'.", ++ cimg_instance, ++ filename); ++ } ++ cimg::exception_mode(omode); ++ throw CImgIOException(_cimg_instance ++ "load_other(): Failed to recognize format of file '%s'.", ++ cimg_instance, ++ filename); ++ } ++ } ++ } ++ } ++ cimg::exception_mode(omode); ++ return *this; ++ } ++ ++ //! Load image using various non-native ways \newinstance. ++ static CImg get_load_other(const char *const filename) { ++ return CImg().load_other(filename); ++ } ++ ++ //@} ++ //--------------------------- ++ // ++ //! \name Data Output ++ //@{ ++ //--------------------------- ++ ++ //! Display information about the image data. ++ /** ++ \param title Name for the considered image. ++ \param display_stats Tells to compute and display image statistics. ++ **/ ++ const CImg& print(const char *const 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; ++ 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 ulongT siz = size(), msiz = siz*sizeof(T), siz1 = siz - 1, ++ mdisp = msiz<8*1024?0U:msiz<8*1024*1024?1U:2U, width1 = _width - 1; ++ ++ CImg _title(64); ++ if (!title) cimg_snprintf(_title,_title._width,"CImg<%s>",pixel_type()); ++ ++ std::fprintf(cimg::output(),"%s%s%s%s: %sthis%s = %p, %ssize%s = (%u,%u,%u,%u) [%lu %s], %sdata%s = (%s*)%p", ++ cimg::t_magenta,cimg::t_bold,title?title:_title._data,cimg::t_normal, ++ cimg::t_bold,cimg::t_normal,(void*)this, ++ cimg::t_bold,cimg::t_normal,_width,_height,_depth,_spectrum, ++ (unsigned long)(mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20))), ++ mdisp==0?"b":(mdisp==1?"Kio":"Mio"), ++ cimg::t_bold,cimg::t_normal,pixel_type(),(void*)begin()); ++ if (_data) ++ std::fprintf(cimg::output(),"..%p (%s) = [ ",(void*)((char*)end() - 1),_is_shared?"shared":"non-shared"); ++ else std::fprintf(cimg::output()," (%s) = [ ",_is_shared?"shared":"non-shared"); ++ ++ if (!is_empty()) cimg_foroff(*this,off) { ++ std::fprintf(cimg::output(),"%g",(double)_data[off]); ++ if (off!=siz1) std::fprintf(cimg::output(),"%s",off%_width==width1?" ; ":" "); ++ if (off==7 && siz>16) { off = siz1 - 8; std::fprintf(cimg::output(),"... "); } ++ } ++ if (!is_empty() && display_stats) ++ std::fprintf(cimg::output(), ++ " ], %smin%s = %g, %smax%s = %g, %smean%s = %g, %sstd%s = %g, %scoords_min%s = (%u,%u,%u,%u), " ++ "%scoords_max%s = (%u,%u,%u,%u).\n", ++ cimg::t_bold,cimg::t_normal,st[0], ++ cimg::t_bold,cimg::t_normal,st[1], ++ cimg::t_bold,cimg::t_normal,st[2], ++ cimg::t_bold,cimg::t_normal,std::sqrt(st[3]), ++ cimg::t_bold,cimg::t_normal,xm,ym,zm,vm, ++ cimg::t_bold,cimg::t_normal,xM,yM,zM,vM); ++ else std::fprintf(cimg::output(),"%s].\n",is_empty()?"":" "); ++ std::fflush(cimg::output()); ++ return *this; ++ } ++ ++ //! Display image into a CImgDisplay window. ++ /** ++ \param disp Display window. ++ **/ ++ const CImg& display(CImgDisplay& disp) const { ++ disp.display(*this); ++ return *this; ++ } ++ ++ //! Display image into a CImgDisplay window, in an interactive way. ++ /** ++ \param disp Display window. ++ \param display_info Tells if image information are displayed on the standard output. ++ \param[in,out] XYZ Contains the XYZ coordinates at start / exit of the function. ++ \param exit_on_anykey Exit function when any key is pressed. ++ **/ ++ const CImg& display(CImgDisplay &disp, const bool display_info, unsigned int *const XYZ=0, ++ const bool exit_on_anykey=false) const { ++ return _display(disp,0,display_info,XYZ,exit_on_anykey,false); ++ } ++ ++ //! Display image into an interactive window. ++ /** ++ \param title Window title ++ \param display_info Tells if image information are displayed on the standard output. ++ \param[in,out] XYZ Contains the XYZ coordinates at start / exit of the function. ++ \param exit_on_anykey Exit function when any key is pressed. ++ **/ ++ const CImg& display(const char *const title=0, const bool display_info=true, unsigned int *const XYZ=0, ++ const bool exit_on_anykey=false) const { ++ CImgDisplay disp; ++ return _display(disp,title,display_info,XYZ,exit_on_anykey,false); ++ } ++ ++ const CImg& _display(CImgDisplay &disp, const char *const title, const bool display_info, ++ unsigned int *const XYZ, const bool exit_on_anykey, ++ const bool exit_on_simpleclick) const { ++ unsigned int oldw = 0, oldh = 0, _XYZ[3] = { 0 }, key = 0; ++ int x0 = 0, y0 = 0, z0 = 0, x1 = width() - 1, y1 = height() - 1, z1 = depth() - 1, ++ old_mouse_x = -1, old_mouse_y = -1; ++ ++ if (!disp) { ++ disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:0,1); ++ if (!title) disp.set_title("CImg<%s> (%ux%ux%ux%u)",pixel_type(),_width,_height,_depth,_spectrum); ++ else disp.set_title("%s",title); ++ } else if (title) disp.set_title("%s",title); ++ disp.show().flush(); ++ ++ const CImg dtitle = CImg::string(disp.title()); ++ if (display_info) print(dtitle); ++ ++ CImg zoom; ++ for (bool reset_view = true, resize_disp = false, is_first_select = true; !key && !disp.is_closed(); ) { ++ if (reset_view) { ++ if (XYZ) { _XYZ[0] = XYZ[0]; _XYZ[1] = XYZ[1]; _XYZ[2] = XYZ[2]; } ++ else { ++ _XYZ[0] = (unsigned int)(x0 + x1)/2; ++ _XYZ[1] = (unsigned int)(y0 + y1)/2; ++ _XYZ[2] = (unsigned int)(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==width() - 1 && y1==height() - 1 && z1==depth() - 1) { ++ if (is_empty()) zoom.assign(1,1,1,1,(T)0); else zoom.assign(); ++ } else zoom = get_crop(x0,y0,z0,x1,y1,z1); ++ ++ const CImg& visu = zoom?zoom:*this; ++ const unsigned int ++ dx = 1U + x1 - x0, dy = 1U + y1 - y0, dz = 1U + z1 - z0, ++ tw = dx + (dz>1?dz:0U), th = dy + (dz>1?dz:0U); ++ if (!is_empty() && !disp.is_fullscreen() && resize_disp) { ++ const unsigned int ++ ttw = tw*disp.width()/oldw, tth = th*disp.height()/oldh, ++ dM = std::max(ttw,tth), diM = (unsigned int)std::max(disp.width(),disp.height()), ++ imgw = std::max(16U,ttw*diM/dM), imgh = std::max(16U,tth*diM/dM); ++ disp.set_fullscreen(false).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; ++ ++ disp.set_title("%s",dtitle._data); ++ if (_width>1 && visu._width==1) disp.set_title("%s | x=%u",disp._title,x0); ++ if (_height>1 && visu._height==1) disp.set_title("%s | y=%u",disp._title,y0); ++ if (_depth>1 && visu._depth==1) disp.set_title("%s | z=%u",disp._title,z0); ++ ++ disp._mouse_x = old_mouse_x; disp._mouse_y = old_mouse_y; ++ CImg selection = visu._select(disp,0,2,_XYZ,x0,y0,z0,true,is_first_select,_depth>1); ++ old_mouse_x = disp._mouse_x; old_mouse_y = disp._mouse_y; ++ is_first_select = false; ++ ++ if (disp.wheel()) { ++ if ((disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) && ++ (disp.is_keySHIFTLEFT() || disp.is_keySHIFTRIGHT())) { ++ go_left = !(go_right = disp.wheel()>0); ++ } else if (disp.is_keySHIFTLEFT() || disp.is_keySHIFTRIGHT()) { ++ go_down = !(go_up = disp.wheel()>0); ++ } else if (depth()==1 || disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ go_out = !(go_in = disp.wheel()>0); go_in_center = false; ++ } ++ disp.set_wheel(); ++ } ++ ++ if (disp.is_keyCTRLLEFT()) { // Alternative way for zooming and selection. ++ if (selection[2]==selection[5]) { selection[2] = 0; selection[5] = visu.depth() - 1; } ++ else if (selection[1]==selection[4]) { selection[1] = 0; selection[4] = visu.height() - 1; } ++ else if (selection[0]==selection[3]) { selection[0] = 0; selection[3] = visu.width() - 1; } ++ } ++ ++ 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) { ++ if (exit_on_simpleclick && (!zoom || is_empty())) break; else reset_view = true; ++ } ++ resize_disp = true; ++ } else switch (key = disp.key()) { ++#if cimg_OS!=2 ++ case cimg::keyCTRLRIGHT : case cimg::keySHIFTRIGHT : ++#endif ++ case 0 : case cimg::keyCTRLLEFT : case cimg::keySHIFTLEFT : key = 0; break; ++ case cimg::keyP : if (visu._depth>1 && (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT())) { ++ // 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)); ++ float frame_timing = 5; ++ bool is_stopped = false; ++ disp.set_key(key,false).set_wheel().resize(cimg_fitscreen(w1,h1,1),false); key = 0; ++ for (unsigned int timer = 0; !key && !disp.is_closed() && !disp.button(); ) { ++ if (disp.is_resized()) disp.resize(false); ++ if (!timer) { ++ visu.get_slice((int)_XYZ[2]).display(disp.set_title("%s | z=%d",dtitle.data(),_XYZ[2])); ++ (++_XYZ[2])%=visu._depth; ++ } ++ if (!is_stopped) { if (++timer>(unsigned int)frame_timing) timer = 0; } else timer = ~0U; ++ if (disp.wheel()) { frame_timing-=disp.wheel()/3.0f; disp.set_wheel(); } ++ switch (key = disp.key()) { ++#if cimg_OS!=2 ++ case cimg::keyCTRLRIGHT : ++#endif ++ case cimg::keyCTRLLEFT : key = 0; break; ++ case cimg::keyPAGEUP : frame_timing-=0.3f; key = 0; break; ++ case cimg::keyPAGEDOWN : frame_timing+=0.3f; key = 0; break; ++ case cimg::keySPACE : is_stopped = !is_stopped; disp.set_key(key,false); key = 0; break; ++ case cimg::keyARROWLEFT : case cimg::keyARROWUP : is_stopped = true; timer = 0; key = 0; break; ++ case cimg::keyARROWRIGHT : case cimg::keyARROWDOWN : is_stopped = true; ++ (_XYZ[2]+=visu._depth - 2)%=visu._depth; timer = 0; key = 0; break; ++ case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ disp.set_fullscreen(false). ++ 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.set_key(key,false); key = 0; ++ } break; ++ case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ disp.set_fullscreen(false). ++ resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false).set_key(key,false); key = 0; ++ } break; ++ case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ disp.set_fullscreen(false). ++ resize(cimg_fitscreen(_width,_height,_depth),false).set_key(key,false); key = 0; ++ } break; ++ case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ disp.resize(disp.screen_width(),disp.screen_height(),false). ++ toggle_fullscreen().set_key(key,false); key = 0; ++ } break; ++ } ++ frame_timing = frame_timing<1?1:(frame_timing>39?39:frame_timing); ++ 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(dtitle.data()).set_key().set_button().set_wheel(); ++ key = 0; ++ } break; ++ case cimg::keyHOME : 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.width()/2:disp.mouse_x(), ++ my = go_in_center?disp.height()/2:disp.mouse_y(), ++ mX = mx*(width() + (depth()>1?depth():0))/disp.width(), ++ mY = my*(height() + (depth()>1?depth():0))/disp.height(); ++ int X = (int)_XYZ[0], Y = (int)_XYZ[1], Z = (int)_XYZ[2]; ++ if (mX=height()) { ++ X = x0 + mX*(1 + x1 - x0)/width(); Z = z0 + (mY - height())*(1 + z1 - z0)/depth(); ++ } ++ if (mX>=width() && mY4) { x0 = X - 3*(X - x0)/4; x1 = X + 3*(x1 - X)/4; } ++ if (y1 - y0>4) { y0 = Y - 3*(Y - y0)/4; y1 = Y + 3*(y1 - Y)/4; } ++ if (z1 - z0>4) { z0 = Z - 3*(Z - z0)/4; z1 = Z + 3*(z1 - Z)/4; } ++ } ++ if (go_out) { ++ const int ++ delta_x = (x1 - x0)/8, delta_y = (y1 - y0)/8, delta_z = (z1 - z0)/8, ++ ndelta_x = delta_x?delta_x:(_width>1), ++ ndelta_y = delta_y?delta_y:(_height>1), ++ ndelta_z = delta_z?delta_z:(_depth>1); ++ x0-=ndelta_x; y0-=ndelta_y; z0-=ndelta_z; ++ x1+=ndelta_x; y1+=ndelta_y; z1+=ndelta_z; ++ if (x0<0) { x1-=x0; x0 = 0; if (x1>=width()) x1 = width() - 1; } ++ if (y0<0) { y1-=y0; y0 = 0; if (y1>=height()) y1 = height() - 1; } ++ if (z0<0) { z1-=z0; z0 = 0; if (z1>=depth()) z1 = depth() - 1; } ++ if (x1>=width()) { x0-=(x1 - width() + 1); x1 = width() - 1; if (x0<0) x0 = 0; } ++ if (y1>=height()) { y0-=(y1 - height() + 1); y1 = height() - 1; if (y0<0) y0 = 0; } ++ if (z1>=depth()) { z0-=(z1 - depth() + 1); z1 = depth() - 1; if (z0<0) z0 = 0; } ++ const float ++ ratio = (float)(x1-x0)/(y1-y0), ++ ratiow = (float)disp._width/disp._height, ++ sub = std::min(cimg::abs(ratio - ratiow),cimg::abs(1/ratio-1/ratiow)); ++ if (sub>0.01) resize_disp = true; ++ } ++ if (go_left) { ++ const int delta = (x1 - x0)/4, ndelta = delta?delta:(_width>1); ++ if (x0 - ndelta>=0) { x0-=ndelta; x1-=ndelta; } ++ else { x1-=x0; x0 = 0; } ++ } ++ if (go_right) { ++ const int delta = (x1 - x0)/4, ndelta = delta?delta:(_width>1); ++ if (x1+ndelta1); ++ if (y0 - ndelta>=0) { y0-=ndelta; y1-=ndelta; } ++ else { y1-=y0; y0 = 0; } ++ } ++ if (go_down) { ++ const int delta = (y1 - y0)/4, ndelta = delta?delta:(_height>1); ++ if (y1+ndelta1); ++ if (z0 - ndelta>=0) { z0-=ndelta; z1-=ndelta; } ++ else { z1-=z0; z0 = 0; } ++ } ++ if (go_dec) { ++ const int delta = (z1 - z0)/4, ndelta = delta?delta:(_depth>1); ++ if (z1+ndelta ++ const CImg& display_object3d(CImgDisplay& disp, ++ const CImg& vertices, ++ 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 is_double_sided=true, const float focale=700, ++ const float light_x=0, const float light_y=0, const float light_z=-5e8f, ++ const float specular_lightness=0.2f, const float specular_shininess=0.1f, ++ const bool display_axes=true, float *const pose_matrix=0, ++ const bool exit_on_anykey=false) const { ++ return _display_object3d(disp,0,vertices,primitives,colors,opacities,centering,render_static, ++ render_motion,is_double_sided,focale, ++ light_x,light_y,light_z,specular_lightness,specular_shininess, ++ display_axes,pose_matrix,exit_on_anykey); ++ } ++ ++ //! Display object 3d in an interactive window \simplification. ++ template ++ const CImg& display_object3d(const char *const title, ++ const CImg& vertices, ++ 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 is_double_sided=true, const float focale=700, ++ const float light_x=0, const float light_y=0, const float light_z=-5e8f, ++ const float specular_lightness=0.2f, const float specular_shininess=0.1f, ++ const bool display_axes=true, float *const pose_matrix=0, ++ const bool exit_on_anykey=false) const { ++ CImgDisplay disp; ++ return _display_object3d(disp,title,vertices,primitives,colors,opacities,centering,render_static, ++ render_motion,is_double_sided,focale, ++ light_x,light_y,light_z,specular_lightness,specular_shininess, ++ display_axes,pose_matrix,exit_on_anykey); ++ } ++ ++ //! Display object 3d in an interactive window \simplification. ++ template ++ const CImg& display_object3d(CImgDisplay &disp, ++ const CImg& vertices, ++ const CImgList& primitives, ++ const CImgList& colors, ++ const bool centering=true, ++ const int render_static=4, const int render_motion=1, ++ const bool is_double_sided=true, const float focale=700, ++ const float light_x=0, const float light_y=0, const float light_z=-5e8f, ++ const float specular_lightness=0.2f, const float specular_shininess=0.1f, ++ const bool display_axes=true, float *const pose_matrix=0, ++ const bool exit_on_anykey=false) const { ++ return display_object3d(disp,vertices,primitives,colors,CImgList(),centering, ++ render_static,render_motion,is_double_sided,focale, ++ light_x,light_y,light_z,specular_lightness,specular_shininess, ++ display_axes,pose_matrix,exit_on_anykey); ++ } ++ ++ //! Display object 3d in an interactive window \simplification. ++ template ++ const CImg& display_object3d(const char *const title, ++ const CImg& vertices, ++ const CImgList& primitives, ++ const CImgList& colors, ++ const bool centering=true, ++ const int render_static=4, const int render_motion=1, ++ const bool is_double_sided=true, const float focale=700, ++ const float light_x=0, const float light_y=0, const float light_z=-5e8f, ++ const float specular_lightness=0.2f, const float specular_shininess=0.1f, ++ const bool display_axes=true, float *const pose_matrix=0, ++ const bool exit_on_anykey=false) const { ++ return display_object3d(title,vertices,primitives,colors,CImgList(),centering, ++ render_static,render_motion,is_double_sided,focale, ++ light_x,light_y,light_z,specular_lightness,specular_shininess, ++ display_axes,pose_matrix,exit_on_anykey); ++ } ++ ++ //! Display object 3d in an interactive window \simplification. ++ template ++ const CImg& display_object3d(CImgDisplay &disp, ++ const CImg& vertices, ++ const CImgList& primitives, ++ const bool centering=true, ++ const int render_static=4, const int render_motion=1, ++ const bool is_double_sided=true, const float focale=700, ++ const float light_x=0, const float light_y=0, const float light_z=-5e8f, ++ const float specular_lightness=0.2f, const float specular_shininess=0.1f, ++ const bool display_axes=true, float *const pose_matrix=0, ++ const bool exit_on_anykey=false) const { ++ return display_object3d(disp,vertices,primitives,CImgList(),centering, ++ render_static,render_motion,is_double_sided,focale, ++ light_x,light_y,light_z,specular_lightness,specular_shininess, ++ display_axes,pose_matrix,exit_on_anykey); ++ } ++ ++ ++ //! Display object 3d in an interactive window \simplification. ++ template ++ const CImg& display_object3d(const char *const title, ++ const CImg& vertices, ++ const CImgList& primitives, ++ const bool centering=true, ++ const int render_static=4, const int render_motion=1, ++ const bool is_double_sided=true, const float focale=700, ++ const float light_x=0, const float light_y=0, const float light_z=-5e8f, ++ const float specular_lightness=0.2f, const float specular_shininess=0.1f, ++ const bool display_axes=true, float *const pose_matrix=0, ++ const bool exit_on_anykey=false) const { ++ return display_object3d(title,vertices,primitives,CImgList(),centering, ++ render_static,render_motion,is_double_sided,focale, ++ light_x,light_y,light_z,specular_lightness,specular_shininess, ++ display_axes,pose_matrix,exit_on_anykey); ++ } ++ ++ //! Display object 3d in an interactive window \simplification. ++ template ++ const CImg& display_object3d(CImgDisplay &disp, ++ const CImg& vertices, ++ const bool centering=true, ++ const int render_static=4, const int render_motion=1, ++ const bool is_double_sided=true, const float focale=700, ++ const float light_x=0, const float light_y=0, const float light_z=-5e8f, ++ const float specular_lightness=0.2f, const float specular_shininess=0.1f, ++ const bool display_axes=true, float *const pose_matrix=0, ++ const bool exit_on_anykey=false) const { ++ return display_object3d(disp,vertices,CImgList(),centering, ++ render_static,render_motion,is_double_sided,focale, ++ light_x,light_y,light_z,specular_lightness,specular_shininess, ++ display_axes,pose_matrix,exit_on_anykey); ++ } ++ ++ //! Display object 3d in an interactive window \simplification. ++ template ++ const CImg& display_object3d(const char *const title, ++ const CImg& vertices, ++ const bool centering=true, ++ const int render_static=4, const int render_motion=1, ++ const bool is_double_sided=true, const float focale=700, ++ const float light_x=0, const float light_y=0, const float light_z=-5e8f, ++ const float specular_lightness=0.2f, const float specular_shininess=0.1f, ++ const bool display_axes=true, float *const pose_matrix=0, ++ const bool exit_on_anykey=false) const { ++ return display_object3d(title,vertices,CImgList(),centering, ++ render_static,render_motion,is_double_sided,focale, ++ light_x,light_y,light_z,specular_lightness,specular_shininess, ++ display_axes,pose_matrix,exit_on_anykey); ++ } ++ ++ template ++ const CImg& _display_object3d(CImgDisplay& disp, const char *const title, ++ const CImg& vertices, ++ const CImgList& primitives, ++ const CImgList& colors, ++ const to& opacities, ++ const bool centering, ++ const int render_static, const int render_motion, ++ const bool is_double_sided, const float focale, ++ const float light_x, const float light_y, const float light_z, ++ const float specular_lightness, const float specular_shininess, ++ const bool display_axes, float *const pose_matrix, ++ const bool exit_on_anykey) const { ++ typedef typename cimg::superset::type tpfloat; ++ ++ // Check input arguments ++ if (is_empty()) { ++ if (disp) return CImg(disp.width(),disp.height(),1,(colors && colors[0].size()==1)?1:3,0). ++ _display_object3d(disp,title,vertices,primitives,colors,opacities,centering, ++ render_static,render_motion,is_double_sided,focale, ++ light_x,light_y,light_z,specular_lightness,specular_shininess, ++ display_axes,pose_matrix,exit_on_anykey); ++ else return CImg(1,2,1,1,64,128).resize(cimg_fitscreen(CImgDisplay::screen_width()/2, ++ CImgDisplay::screen_height()/2,1), ++ 1,(colors && colors[0].size()==1)?1:3,3). ++ _display_object3d(disp,title,vertices,primitives,colors,opacities,centering, ++ render_static,render_motion,is_double_sided,focale, ++ light_x,light_y,light_z,specular_lightness,specular_shininess, ++ display_axes,pose_matrix,exit_on_anykey); ++ } else { if (disp) disp.resize(*this,false); } ++ CImg error_message(1024); ++ if (!vertices.is_object3d(primitives,colors,opacities,true,error_message)) ++ throw CImgArgumentException(_cimg_instance ++ "display_object3d(): Invalid specified 3d object (%u,%u) (%s).", ++ cimg_instance,vertices._width,primitives._width,error_message.data()); ++ if (vertices._width && !primitives) { ++ CImgList nprimitives(vertices._width,1,1,1,1); ++ cimglist_for(nprimitives,l) nprimitives(l,0) = (tf)l; ++ return _display_object3d(disp,title,vertices,nprimitives,colors,opacities,centering, ++ render_static,render_motion,is_double_sided,focale, ++ light_x,light_y,light_z,specular_lightness,specular_shininess, ++ display_axes,pose_matrix,exit_on_anykey); ++ } ++ if (!disp) { ++ disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:0,3); ++ if (!title) disp.set_title("CImg<%s> (%u vertices, %u primitives)", ++ pixel_type(),vertices._width,primitives._width); ++ } else if (title) disp.set_title("%s",title); ++ ++ // Init 3d objects and compute object statistics ++ CImg ++ pose, ++ rotated_vertices(vertices._width,3), ++ bbox_vertices, rotated_bbox_vertices, ++ axes_vertices, rotated_axes_vertices, ++ bbox_opacities, axes_opacities; ++ CImgList bbox_primitives, axes_primitives; ++ CImgList reverse_primitives; ++ CImgList bbox_colors, bbox_colors2, axes_colors; ++ unsigned int ns_width = 0, ns_height = 0; ++ int _is_double_sided = (int)is_double_sided; ++ bool ndisplay_axes = display_axes; ++ const CImg ++ background_color(1,1,1,_spectrum,0), ++ foreground_color(1,1,1,_spectrum,255); ++ float ++ Xoff = 0, Yoff = 0, Zoff = 0, sprite_scale = 1, ++ xm = 0, xM = vertices?vertices.get_shared_row(0).max_min(xm):0, ++ ym = 0, yM = vertices?vertices.get_shared_row(1).max_min(ym):0, ++ zm = 0, zM = vertices?vertices.get_shared_row(2).max_min(zm):0; ++ const float delta = cimg::max(xM - xm,yM - ym,zM - zm); ++ ++ rotated_bbox_vertices = bbox_vertices.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,_spectrum,1,1,1,background_color[0]); ++ bbox_colors2.assign(6,_spectrum,1,1,1,foreground_color[0]); ++ bbox_opacities.assign(bbox_colors._width,1,1,1,0.3f); ++ ++ rotated_axes_vertices = axes_vertices.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,_spectrum,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,false), visu; ++ CImg zbuffer(visu0.width(),visu0.height(),1,1,0); ++ bool init_pose = true, clicked = false, redraw = true; ++ unsigned int key = 0; ++ int ++ x0 = 0, y0 = 0, x1 = 0, y1 = 0, ++ nrender_static = render_static, ++ nrender_motion = render_motion; ++ disp.show().flush(); ++ ++ while (!disp.is_closed() && !key) { ++ ++ // Init object pose ++ if (init_pose) { ++ const float ++ ratio = delta>0?(2.0f*std::min(disp.width(),disp.height())/(3.0f*delta)):1, ++ dx = (xM + xm)/2, dy = (yM + ym)/2, dz = (zM + zm)/2; ++ if (centering) ++ CImg(4,3,1,1, ratio,0.,0.,-ratio*dx, 0.,ratio,0.,-ratio*dy, 0.,0.,ratio,-ratio*dz).move_to(pose); ++ else CImg(4,3,1,1, 1,0,0,0, 0,1,0,0, 0,0,1,0).move_to(pose); ++ if (pose_matrix) { ++ CImg pose0(pose_matrix,4,3,1,1,false); ++ pose0.resize(4,4,1,1,0); pose.resize(4,4,1,1,0); ++ pose0(3,3) = pose(3,3) = 1; ++ (pose0*pose).get_crop(0,0,3,2).move_to(pose); ++ Xoff = pose_matrix[12]; Yoff = pose_matrix[13]; Zoff = pose_matrix[14]; sprite_scale = pose_matrix[15]; ++ } else { Xoff = Yoff = Zoff = 0; sprite_scale = 1; } ++ init_pose = 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 && nrender_motion>=0) || (!clicked && nrender_static>=0)) ++ cimg_forX(vertices,l) { ++ const float x = (float)vertices(l,0), y = (float)vertices(l,1), z = (float)vertices(l,2); ++ rotated_vertices(l,0) = r00*x + r10*y + r20*z + r30; ++ rotated_vertices(l,1) = r01*x + r11*y + r21*z + r31; ++ rotated_vertices(l,2) = r02*x + r12*y + r22*z + r32; ++ } ++ else cimg_forX(bbox_vertices,l) { ++ const float x = bbox_vertices(l,0), y = bbox_vertices(l,1), z = bbox_vertices(l,2); ++ rotated_bbox_vertices(l,0) = r00*x + r10*y + r20*z + r30; ++ rotated_bbox_vertices(l,1) = r01*x + r11*y + r21*z + r31; ++ rotated_bbox_vertices(l,2) = r02*x + r12*y + r22*z + r32; ++ } ++ ++ // Draw objects ++ const bool render_with_zbuffer = !clicked && nrender_static>0; ++ visu = visu0; ++ if ((clicked && nrender_motion<0) || (!clicked && nrender_static<0)) ++ visu.draw_object3d(Xoff + visu._width/2.0f,Yoff + visu._height/2.0f,Zoff, ++ rotated_bbox_vertices,bbox_primitives,bbox_colors,bbox_opacities,2,false,focale). ++ draw_object3d(Xoff + visu._width/2.0f,Yoff + visu._height/2.0f,Zoff, ++ rotated_bbox_vertices,bbox_primitives,bbox_colors2,1,false,focale); ++ else visu._draw_object3d((void*)0,render_with_zbuffer?zbuffer.fill(0):CImg::empty(), ++ Xoff + visu._width/2.0f,Yoff + visu._height/2.0f,Zoff, ++ rotated_vertices,reverse_primitives?reverse_primitives:primitives, ++ colors,opacities,clicked?nrender_motion:nrender_static,_is_double_sided==1,focale, ++ width()/2.0f + light_x,height()/2.0f + light_y,light_z + Zoff, ++ specular_lightness,specular_shininess,sprite_scale); ++ // Draw axes ++ if (ndisplay_axes) { ++ const float ++ n = 1e-8f + cimg::hypot(r00,r01,r02), ++ _r00 = r00/n, _r10 = r10/n, _r20 = r20/n, ++ _r01 = r01/n, _r11 = r11/n, _r21 = r21/n, ++ _r02 = r01/n, _r12 = r12/n, _r22 = r22/n, ++ Xaxes = 25, Yaxes = visu._height - 38.0f; ++ cimg_forX(axes_vertices,l) { ++ const float ++ x = axes_vertices(l,0), ++ y = axes_vertices(l,1), ++ z = axes_vertices(l,2); ++ rotated_axes_vertices(l,0) = _r00*x + _r10*y + _r20*z; ++ rotated_axes_vertices(l,1) = _r01*x + _r11*y + _r21*z; ++ rotated_axes_vertices(l,2) = _r02*x + _r12*y + _r22*z; ++ } ++ axes_opacities(0,0) = (rotated_axes_vertices(1,2)>0)?0.5f:1.0f; ++ axes_opacities(1,0) = (rotated_axes_vertices(2,2)>0)?0.5f:1.0f; ++ axes_opacities(2,0) = (rotated_axes_vertices(3,2)>0)?0.5f:1.0f; ++ visu.draw_object3d(Xaxes,Yaxes,0,rotated_axes_vertices,axes_primitives, ++ axes_colors,axes_opacities,1,false,focale). ++ draw_text((int)(Xaxes + rotated_axes_vertices(4,0)), ++ (int)(Yaxes + rotated_axes_vertices(4,1)), ++ "X",axes_colors[0]._data,0,axes_opacities(0,0),13). ++ draw_text((int)(Xaxes + rotated_axes_vertices(5,0)), ++ (int)(Yaxes + rotated_axes_vertices(5,1)), ++ "Y",axes_colors[1]._data,0,axes_opacities(1,0),13). ++ draw_text((int)(Xaxes + rotated_axes_vertices(6,0)), ++ (int)(Yaxes + rotated_axes_vertices(6,1)), ++ "Z",axes_colors[2]._data,0,axes_opacities(2,0),13); ++ } ++ visu.display(disp); ++ if (!clicked || nrender_motion==nrender_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*std::min(disp.width(),disp.height()), ++ R2 = R*R, ++ u0 = (float)(x0 - disp.width()/2), ++ v0 = (float)(y0 - disp.height()/2), ++ u1 = (float)(x1 - disp.width()/2), ++ v1 = (float)(y1 - disp.height()/2), ++ n0 = cimg::hypot(u0,v0), ++ n1 = cimg::hypot(u1,v1), ++ nu0 = n0>R?(u0*R/n0):u0, ++ nv0 = n0>R?(v0*R/n0):v0, ++ nw0 = (float)std::sqrt(std::max(0.0f,R2 - nu0*nu0 - nv0*nv0)), ++ nu1 = n1>R?(u1*R/n1):u1, ++ nv1 = n1>R?(v1*R/n1):v1, ++ nw1 = (float)std::sqrt(std::max(0.0f,R2 - nu1*nu1 - nv1*nv1)), ++ u = nv0*nw1 - nw0*nv1, ++ v = nw0*nu1 - nu0*nw1, ++ w = nv0*nu1 - nu0*nv1, ++ n = cimg::hypot(u,v,w), ++ alpha = (float)std::asin(n/R2)*180/cimg::PI; ++ (CImg::rotation_matrix(u,v,w,-alpha)*pose).move_to(pose); ++ x0 = x1; y0 = y1; ++ } ++ if (disp.button()&2) { ++ if (focale>0) Zoff-=(y0 - y1)*focale/400; ++ else { const float s = std::exp((y0 - y1)/400.0f); pose*=s; sprite_scale*=s; } ++ x0 = x1; y0 = y1; ++ } ++ if (disp.wheel()) { ++ if (focale>0) Zoff-=disp.wheel()*focale/20; ++ else { const float s = std::exp(disp.wheel()/20.0f); pose*=s; sprite_scale*=s; } ++ disp.set_wheel(); ++ } ++ if (disp.button()&4) { Xoff+=(x1 - x0); Yoff+=(y1 - y0); x0 = x1; y0 = y1; } ++ if ((disp.button()&1) && (disp.button()&2)) { ++ init_pose = true; disp.set_button(); x0 = x1; y0 = y1; ++ pose = CImg(4,3,1,1, 1,0,0,0, 0,1,0,0, 0,0,1,0); ++ } ++ } else if (clicked) { x0 = x1; y0 = y1; clicked = false; redraw = true; } ++ ++ CImg filename(32); ++ switch (key = disp.key()) { ++#if cimg_OS!=2 ++ case cimg::keyCTRLRIGHT : ++#endif ++ case 0 : case cimg::keyCTRLLEFT : key = 0; break; ++ case cimg::keyD: if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ disp.set_fullscreen(false). ++ 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.set_key(key,false); key = 0; ++ } break; ++ case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ disp.set_fullscreen(false). ++ resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true; ++ disp.set_key(key,false); key = 0; ++ } break; ++ case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ disp.set_fullscreen(false).resize(cimg_fitscreen(_width,_height,_depth),false)._is_resized = true; ++ disp.set_key(key,false); key = 0; ++ } break; ++ case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ if (!ns_width || !ns_height || ++ ns_width>(unsigned int)disp.screen_width() || ns_height>(unsigned int)disp.screen_height()) { ++ ns_width = disp.screen_width()*3U/4; ++ ns_height = disp.screen_height()*3U/4; ++ } ++ if (disp.is_fullscreen()) disp.resize(ns_width,ns_height,false); ++ else { ++ ns_width = disp._width; ns_height = disp._height; ++ disp.resize(disp.screen_width(),disp.screen_height(),false); ++ } ++ disp.toggle_fullscreen()._is_resized = true; ++ disp.set_key(key,false); key = 0; ++ } break; ++ case cimg::keyT : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ // Switch single/double-sided primitives. ++ if (--_is_double_sided==-2) _is_double_sided = 1; ++ if (_is_double_sided>=0) reverse_primitives.assign(); ++ else primitives.get_reverse_object3d().move_to(reverse_primitives); ++ disp.set_key(key,false); key = 0; redraw = true; ++ } break; ++ case cimg::keyZ : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Enable/disable Z-buffer ++ if (zbuffer) zbuffer.assign(); ++ else zbuffer.assign(visu0.width(),visu0.height(),1,1,0); ++ disp.set_key(key,false); key = 0; redraw = true; ++ } break; ++ case cimg::keyA : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Show/hide 3d axes. ++ ndisplay_axes = !ndisplay_axes; ++ disp.set_key(key,false); key = 0; redraw = true; ++ } break; ++ case cimg::keyF1 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to points. ++ nrender_motion = (nrender_static==0 && nrender_motion!=0)?0:-1; nrender_static = 0; ++ disp.set_key(key,false); key = 0; redraw = true; ++ } break; ++ case cimg::keyF2 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to lines. ++ nrender_motion = (nrender_static==1 && nrender_motion!=1)?1:-1; nrender_static = 1; ++ disp.set_key(key,false); key = 0; redraw = true; ++ } break; ++ case cimg::keyF3 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to flat. ++ nrender_motion = (nrender_static==2 && nrender_motion!=2)?2:-1; nrender_static = 2; ++ disp.set_key(key,false); key = 0; redraw = true; ++ } break; ++ case cimg::keyF4 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to flat-shaded. ++ nrender_motion = (nrender_static==3 && nrender_motion!=3)?3:-1; nrender_static = 3; ++ disp.set_key(key,false); key = 0; redraw = true; ++ } break; ++ case cimg::keyF5 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ // Set rendering mode to gouraud-shaded. ++ nrender_motion = (nrender_static==4 && nrender_motion!=4)?4:-1; nrender_static = 4; ++ disp.set_key(key,false); key = 0; redraw = true; ++ } break; ++ case cimg::keyF6 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to phong-shaded. ++ nrender_motion = (nrender_static==5 && nrender_motion!=5)?5:-1; nrender_static = 5; ++ disp.set_key(key,false); key = 0; redraw = true; ++ } break; ++ case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save snapshot ++ static unsigned int snap_number = 0; ++ std::FILE *file; ++ do { ++ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++); ++ if ((file=std_fopen(filename,"r"))!=0) cimg::fclose(file); ++ } while (file); ++ (+visu).draw_text(0,0," Saving snapshot... ", ++ foreground_color._data,background_color._data,0.7f,13).display(disp); ++ visu.save(filename); ++ (+visu).draw_text(0,0," Snapshot '%s' saved. ", ++ foreground_color._data,background_color._data,0.7f,13,filename._data).display(disp); ++ disp.set_key(key,false); key = 0; ++ } break; ++ case cimg::keyG : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save object as a .off file ++ static unsigned int snap_number = 0; ++ std::FILE *file; ++ do { ++ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.off",snap_number++); ++ if ((file=std_fopen(filename,"r"))!=0) cimg::fclose(file); ++ } while (file); ++ (+visu).draw_text(0,0," Saving object... ", ++ foreground_color._data,background_color._data,0.7f,13).display(disp); ++ vertices.save_off(reverse_primitives?reverse_primitives:primitives,colors,filename); ++ (+visu).draw_text(0,0," Object '%s' saved. ", ++ foreground_color._data,background_color._data,0.7f,13,filename._data).display(disp); ++ disp.set_key(key,false); key = 0; ++ } break; ++ case cimg::keyO : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save object as a .cimg file ++ static unsigned int snap_number = 0; ++ std::FILE *file; ++ do { ++#ifdef cimg_use_zlib ++ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); ++#else ++ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); ++#endif ++ if ((file=std_fopen(filename,"r"))!=0) cimg::fclose(file); ++ } while (file); ++ (+visu).draw_text(0,0," Saving object... ", ++ foreground_color._data,background_color._data,0.7f,13).display(disp); ++ vertices.get_object3dtoCImg3d(reverse_primitives?reverse_primitives:primitives,colors,opacities). ++ save(filename); ++ (+visu).draw_text(0,0," Object '%s' saved. ", ++ foreground_color._data,background_color._data,0.7f,13,filename._data).display(disp); ++ disp.set_key(key,false); key = 0; ++ } break; ++#ifdef cimg_use_board ++ case cimg::keyP : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save object as a .EPS file ++ static unsigned int snap_number = 0; ++ std::FILE *file; ++ do { ++ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.eps",snap_number++); ++ if ((file=std_fopen(filename,"r"))!=0) cimg::fclose(file); ++ } while (file); ++ (+visu).draw_text(0,0," Saving EPS snapshot... ", ++ foreground_color._data,background_color._data,0.7f,13).display(disp); ++ LibBoard::Board board; ++ (+visu)._draw_object3d(&board,zbuffer.fill(0), ++ Xoff + visu._width/2.0f,Yoff + visu._height/2.0f,Zoff, ++ rotated_vertices,reverse_primitives?reverse_primitives:primitives, ++ colors,opacities,clicked?nrender_motion:nrender_static, ++ _is_double_sided==1,focale, ++ visu.width()/2.0f + light_x,visu.height()/2.0f + light_y,light_z + Zoff, ++ specular_lightness,specular_shininess, ++ sprite_scale); ++ board.saveEPS(filename); ++ (+visu).draw_text(0,0," Object '%s' saved. ", ++ foreground_color._data,background_color._data,0.7f,13,filename._data).display(disp); ++ disp.set_key(key,false); key = 0; ++ } break; ++ case cimg::keyV : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save object as a .SVG file ++ static unsigned int snap_number = 0; ++ std::FILE *file; ++ do { ++ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.svg",snap_number++); ++ if ((file=std_fopen(filename,"r"))!=0) cimg::fclose(file); ++ } while (file); ++ (+visu).draw_text(0,0," Saving SVG snapshot... ", ++ foreground_color._data,background_color._data,0.7f,13).display(disp); ++ LibBoard::Board board; ++ (+visu)._draw_object3d(&board,zbuffer.fill(0), ++ Xoff + visu._width/2.0f,Yoff + visu._height/2.0f,Zoff, ++ rotated_vertices,reverse_primitives?reverse_primitives:primitives, ++ colors,opacities,clicked?nrender_motion:nrender_static, ++ _is_double_sided==1,focale, ++ visu.width()/2.0f + light_x,visu.height()/2.0f + light_y,light_z + Zoff, ++ specular_lightness,specular_shininess, ++ sprite_scale); ++ board.saveSVG(filename); ++ (+visu).draw_text(0,0," Object '%s' saved. ", ++ foreground_color._data,background_color._data,0.7f,13,filename._data).display(disp); ++ disp.set_key(key,false); 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 (!exit_on_anykey && key && key!=cimg::keyESC && ++ (key!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) { ++ key = 0; ++ } ++ } ++ if (pose_matrix) { ++ std::memcpy(pose_matrix,pose._data,12*sizeof(float)); ++ pose_matrix[12] = Xoff; pose_matrix[13] = Yoff; pose_matrix[14] = Zoff; pose_matrix[15] = sprite_scale; ++ } ++ disp.set_button().set_key(key); ++ return *this; ++ } ++ ++ //! Display 1d graph in an interactive window. ++ /** ++ \param disp Display window. ++ \param plot_type Plot type. Can be { 0=points | 1=segments | 2=splines | 3=bars }. ++ \param vertex_type Vertex type. ++ \param labelx Title for the horizontal axis, as a C-string. ++ \param xmin Minimum value along the X-axis. ++ \param xmax Maximum value along the X-axis. ++ \param labely Title for the vertical axis, as a C-string. ++ \param ymin Minimum value along the X-axis. ++ \param ymax Maximum value along the X-axis. ++ \param exit_on_anykey Exit function when any key is pressed. ++ **/ ++ 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 bool exit_on_anykey=false) const { ++ return _display_graph(disp,0,plot_type,vertex_type,labelx,xmin,xmax,labely,ymin,ymax,exit_on_anykey); ++ } ++ ++ //! Display 1d graph in an interactive window \overloading. ++ 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 bool exit_on_anykey=false) const { ++ CImgDisplay disp; ++ return _display_graph(disp,title,plot_type,vertex_type,labelx,xmin,xmax,labely,ymin,ymax,exit_on_anykey); ++ } ++ ++ const CImg& _display_graph(CImgDisplay &disp, 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 bool exit_on_anykey=false) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "display_graph(): Empty instance.", ++ cimg_instance); ++ if (!disp) disp.assign(cimg_fitscreen(CImgDisplay::screen_width()/2,CImgDisplay::screen_height()/2,1),0,0). ++ set_title(title?"%s":"CImg<%s>",title?title:pixel_type()); ++ const ulongT siz = (ulongT)_width*_height*_depth, siz1 = std::max((ulongT)1,siz - 1); ++ const unsigned int old_normalization = disp.normalization(); ++ disp.show().flush()._normalization = 0; ++ ++ double y0 = ymin, y1 = ymax, nxmin = xmin, nxmax = xmax; ++ if (nxmin==nxmax) { nxmin = 0; nxmax = siz1; } ++ int x0 = 0, x1 = width()*height()*depth() - 1, key = 0; ++ ++ for (bool reset_view = true; !key && !disp.is_closed(); ) { ++ if (reset_view) { x0 = 0; x1 = width()*height()*depth() - 1; y0 = ymin; y1 = ymax; reset_view = false; } ++ CImg zoom(x1 - x0 + 1,1,1,spectrum()); ++ cimg_forC(*this,c) zoom.get_shared_channel(c) = CImg(data(x0,0,0,c),x1 - x0 + 1,1,1,1,true); ++ if (y0==y1) { y0 = zoom.min_max(y1); const double dy = y1 - y0; y0-=dy/20; y1+=dy/20; } ++ if (y0==y1) { --y0; ++y1; } ++ ++ const CImg selection = zoom.get_select_graph(disp,plot_type,vertex_type, ++ labelx, ++ nxmin + x0*(nxmax - nxmin)/siz1, ++ nxmin + x1*(nxmax - nxmin)/siz1, ++ labely,y0,y1,true); ++ const int mouse_x = disp.mouse_x(), mouse_y = disp.mouse_y(); ++ if (selection[0]>=0) { ++ if (selection[2]<0) reset_view = true; ++ else { ++ x1 = x0 + selection[2]; x0+=selection[0]; ++ if (selection[1]>=0 && selection[3]>=0) { ++ y0 = y1 - selection[3]*(y1 - y0)/(disp.height() - 32); ++ y1-=selection[1]*(y1 - y0)/(disp.height() - 32); ++ } ++ } ++ } else { ++ bool go_in = false, go_out = false, go_left = false, go_right = false, go_up = false, go_down = false; ++ switch (key = (int)disp.key()) { ++ case cimg::keyHOME : reset_view = true; key = 0; disp.set_key(); break; ++ case cimg::keyPADADD : go_in = true; go_out = false; key = 0; disp.set_key(); break; ++ case cimg::keyPADSUB : go_out = true; go_in = false; key = 0; disp.set_key(); break; ++ case cimg::keyARROWLEFT : case cimg::keyPAD4 : go_left = true; go_right = false; key = 0; disp.set_key(); ++ break; ++ case cimg::keyARROWRIGHT : case cimg::keyPAD6 : go_right = true; go_left = false; key = 0; disp.set_key(); ++ break; ++ case cimg::keyARROWUP : case cimg::keyPAD8 : go_up = true; go_down = false; key = 0; disp.set_key(); break; ++ case cimg::keyARROWDOWN : case cimg::keyPAD2 : go_down = true; go_up = false; key = 0; disp.set_key(); break; ++ case cimg::keyPAD7 : go_left = true; go_up = true; key = 0; disp.set_key(); break; ++ case cimg::keyPAD9 : go_right = true; go_up = true; key = 0; disp.set_key(); break; ++ case cimg::keyPAD1 : go_left = true; go_down = true; key = 0; disp.set_key(); break; ++ case cimg::keyPAD3 : go_right = true; go_down = true; key = 0; disp.set_key(); break; ++ } ++ if (disp.wheel()) { ++ if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) go_up = !(go_down = disp.wheel()<0); ++ else if (disp.is_keySHIFTLEFT() || disp.is_keySHIFTRIGHT()) go_left = !(go_right = disp.wheel()>0); ++ else go_out = !(go_in = disp.wheel()>0); ++ key = 0; ++ } ++ ++ if (go_in) { ++ const int ++ xsiz = x1 - x0, ++ mx = (mouse_x - 16)*xsiz/(disp.width() - 32), ++ cx = x0 + cimg::cut(mx,0,xsiz); ++ if (x1 - x0>4) { ++ x0 = cx - 7*(cx - x0)/8; x1 = cx + 7*(x1 - cx)/8; ++ if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ const double ++ ysiz = y1 - y0, ++ my = (mouse_y - 16)*ysiz/(disp.height() - 32), ++ cy = y1 - cimg::cut(my,0.0,ysiz); ++ y0 = cy - 7*(cy - y0)/8; y1 = cy + 7*(y1 - cy)/8; ++ } else y0 = y1 = 0; ++ } ++ } ++ if (go_out) { ++ if (x0>0 || x1<(int)siz1) { ++ const int delta_x = (x1 - x0)/8, ndelta_x = delta_x?delta_x:(siz>1); ++ const double ndelta_y = (y1 - y0)/8; ++ x0-=ndelta_x; x1+=ndelta_x; ++ y0-=ndelta_y; y1+=ndelta_y; ++ if (x0<0) { x1-=x0; x0 = 0; if (x1>=(int)siz) x1 = (int)siz1; } ++ if (x1>=(int)siz) { x0-=(x1 - siz1); x1 = (int)siz1; if (x0<0) x0 = 0; } ++ } ++ } ++ 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+=(siz1 - x1); x1 = (int)siz1; } ++ 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; ++ } ++ } ++ if (!exit_on_anykey && key && key!=(int)cimg::keyESC && ++ (key!=(int)cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) { ++ disp.set_key(key,false); ++ key = 0; ++ } ++ } ++ disp._normalization = old_normalization; ++ return *this; ++ } ++ ++ //! Save image as a file. ++ /** ++ \param filename Filename, as a C-string. ++ \param number When positive, represents an index added to the filename. Otherwise, no number is added. ++ \param digits Number of digits used for adding the number to the filename. ++ \note ++ - 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 unsigned int digits=6) const { ++ if (!filename) ++ throw CImgArgumentException(_cimg_instance ++ "save(): Specified filename is (null).", ++ cimg_instance); ++ // Do not test for empty instances, since .cimg format is able to manage empty instances. ++ const bool is_stdout = *filename=='-' && (!filename[1] || filename[1]=='.'); ++ const char *const ext = cimg::split_filename(filename); ++ CImg nfilename(1024); ++ const char *const fn = is_stdout?filename:(number>=0)?cimg::number_filename(filename,number,digits,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); ++ else if (!cimg::strcasecmp(ext,"dlm") || ++ !cimg::strcasecmp(ext,"txt")) return save_dlm(fn); ++ else if (!cimg::strcasecmp(ext,"cpp") || ++ !cimg::strcasecmp(ext,"hpp") || ++ !cimg::strcasecmp(ext,"h") || ++ !cimg::strcasecmp(ext,"c")) return save_cpp(fn); ++ ++ // 2d binary formats ++ else if (!cimg::strcasecmp(ext,"bmp")) return save_bmp(fn); ++ else 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); ++ else if (!cimg::strcasecmp(ext,"rgb")) return save_rgb(fn); ++ else if (!cimg::strcasecmp(ext,"rgba")) return save_rgba(fn); ++ else if (!cimg::strcasecmp(ext,"png")) return save_png(fn); ++ else if (!cimg::strcasecmp(ext,"pgm") || ++ !cimg::strcasecmp(ext,"ppm") || ++ !cimg::strcasecmp(ext,"pnm")) return save_pnm(fn); ++ else if (!cimg::strcasecmp(ext,"pnk")) return save_pnk(fn); ++ else if (!cimg::strcasecmp(ext,"pfm")) return save_pfm(fn); ++ else if (!cimg::strcasecmp(ext,"exr")) return save_exr(fn); ++ else if (!cimg::strcasecmp(ext,"tif") || ++ !cimg::strcasecmp(ext,"tiff")) return save_tiff(fn); ++ ++ // 3d binary formats ++ else if (!cimg::strcasecmp(ext,"cimgz")) return save_cimg(fn,true); ++ else if (!cimg::strcasecmp(ext,"cimg") || !*ext) return save_cimg(fn,false); ++ else if (!cimg::strcasecmp(ext,"dcm")) return save_medcon_external(fn); ++ else if (!cimg::strcasecmp(ext,"hdr") || ++ !cimg::strcasecmp(ext,"nii")) return save_analyze(fn); ++ else if (!cimg::strcasecmp(ext,"inr")) return save_inr(fn); ++ else if (!cimg::strcasecmp(ext,"mnc")) return save_minc2(fn); ++ else if (!cimg::strcasecmp(ext,"pan")) return save_pandore(fn); ++ else if (!cimg::strcasecmp(ext,"raw")) return save_raw(fn); ++ ++ // Archive files ++ else if (!cimg::strcasecmp(ext,"gz")) return save_gzip_external(fn); ++ ++ // Image sequences ++ else if (!cimg::strcasecmp(ext,"yuv")) return save_yuv(fn,444,true); ++ else 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,"mp4") || ++ !cimg::strcasecmp(ext,"mkv") || ++ !cimg::strcasecmp(ext,"mpe") || ++ !cimg::strcasecmp(ext,"movie") || ++ !cimg::strcasecmp(ext,"ogm") || ++ !cimg::strcasecmp(ext,"ogg") || ++ !cimg::strcasecmp(ext,"ogv") || ++ !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_video(fn); ++ return save_other(fn); ++ } ++ ++ //! Save image as an ascii file. ++ /** ++ \param filename Filename, as a C-string. ++ **/ ++ const CImg& save_ascii(const char *const filename) const { ++ return _save_ascii(0,filename); ++ } ++ ++ //! Save image as an ascii file \overloading. ++ const CImg& save_ascii(std::FILE *const file) const { ++ return _save_ascii(file,0); ++ } ++ ++ const CImg& _save_ascii(std::FILE *const file, const char *const filename) const { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_ascii(): Specified filename is (null).", ++ cimg_instance); ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"w"); ++ std::fprintf(nfile,"%u %u %u %u\n",_width,_height,_depth,_spectrum); ++ const T* ptrs = _data; ++ cimg_forYZC(*this,y,z,c) { ++ cimg_forX(*this,x) std::fprintf(nfile,"%.17g ",(double)*(ptrs++)); ++ std::fputc('\n',nfile); ++ } ++ if (!file) cimg::fclose(nfile); ++ return *this; ++ } ++ ++ //! Save image as a .cpp source file. ++ /** ++ \param filename Filename, as a C-string. ++ **/ ++ const CImg& save_cpp(const char *const filename) const { ++ return _save_cpp(0,filename); ++ } ++ ++ //! Save image as a .cpp source file \overloading. ++ const CImg& save_cpp(std::FILE *const file) const { ++ return _save_cpp(file,0); ++ } ++ ++ const CImg& _save_cpp(std::FILE *const file, const char *const filename) const { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_cpp(): Specified filename is (null).", ++ cimg_instance); ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"w"); ++ CImg varname(1024); *varname = 0; ++ if (filename) cimg_sscanf(cimg::basename(filename),"%1023[a-zA-Z0-9_]",varname._data); ++ if (!*varname) cimg_snprintf(varname,varname._width,"unnamed"); ++ std::fprintf(nfile, ++ "/* Define image '%s' of size %ux%ux%ux%u and type '%s' */\n" ++ "%s data_%s[] = { %s\n ", ++ varname._data,_width,_height,_depth,_spectrum,pixel_type(),pixel_type(),varname._data, ++ is_empty()?"};":""); ++ if (!is_empty()) for (ulongT off = 0, siz = size() - 1; off<=siz; ++off) { ++ std::fprintf(nfile,cimg::type::format(),cimg::type::format((*this)[off])); ++ if (off==siz) std::fprintf(nfile," };\n"); ++ else if (!((off + 1)%16)) std::fprintf(nfile,",\n "); ++ else std::fprintf(nfile,", "); ++ } ++ if (!file) cimg::fclose(nfile); ++ return *this; ++ } ++ ++ //! Save image as a DLM file. ++ /** ++ \param filename Filename, as a C-string. ++ **/ ++ const CImg& save_dlm(const char *const filename) const { ++ return _save_dlm(0,filename); ++ } ++ ++ //! Save image as a DLM file \overloading. ++ const CImg& save_dlm(std::FILE *const file) const { ++ return _save_dlm(file,0); ++ } ++ ++ const CImg& _save_dlm(std::FILE *const file, const char *const filename) const { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_dlm(): Specified filename is (null).", ++ cimg_instance); ++ if (is_empty()) { cimg::fempty(file,filename); return *this; } ++ if (_depth>1) ++ cimg::warn(_cimg_instance ++ "save_dlm(): Instance is volumetric, values along Z will be unrolled in file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ if (_spectrum>1) ++ cimg::warn(_cimg_instance ++ "save_dlm(): Instance is multispectral, values along C will be unrolled in file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"w"); ++ const T* ptrs = _data; ++ cimg_forYZC(*this,y,z,c) { ++ cimg_forX(*this,x) std::fprintf(nfile,"%.17g%s",(double)*(ptrs++),(x==width() - 1)?"":","); ++ std::fputc('\n',nfile); ++ } ++ if (!file) cimg::fclose(nfile); ++ return *this; ++ } ++ ++ //! Save image as a BMP file. ++ /** ++ \param filename Filename, as a C-string. ++ **/ ++ const CImg& save_bmp(const char *const filename) const { ++ return _save_bmp(0,filename); ++ } ++ ++ //! Save image as a BMP file \overloading. ++ const CImg& save_bmp(std::FILE *const file) const { ++ return _save_bmp(file,0); ++ } ++ ++ const CImg& _save_bmp(std::FILE *const file, const char *const filename) const { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_bmp(): Specified filename is (null).", ++ cimg_instance); ++ if (is_empty()) { cimg::fempty(file,filename); return *this; } ++ if (_depth>1) ++ cimg::warn(_cimg_instance ++ "save_bmp(): Instance is volumetric, only the first slice will be saved in file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ if (_spectrum>3) ++ cimg::warn(_cimg_instance ++ "save_bmp(): Instance is multispectral, only the three first channels will be saved in file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); ++ CImg header(54,1,1,1,0); ++ unsigned char align_buf[4] = { 0 }; ++ const unsigned int ++ align = (4 - (3*_width)%4)%4, ++ buf_size = (3*_width + align)*height(), ++ 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._data,54,nfile); ++ ++ const T ++ *ptr_r = data(0,_height - 1,0,0), ++ *ptr_g = (_spectrum>=2)?data(0,_height - 1,0,1):0, ++ *ptr_b = (_spectrum>=3)?data(0,_height - 1,0,2):0; ++ ++ switch (_spectrum) { ++ case 1 : { ++ cimg_forY(*this,y) { ++ cimg_forX(*this,x) { ++ const unsigned char val = (unsigned char)*(ptr_r++); ++ std::fputc(val,nfile); std::fputc(val,nfile); std::fputc(val,nfile); ++ } ++ cimg::fwrite(align_buf,align,nfile); ++ ptr_r-=2*_width; ++ } ++ } break; ++ case 2 : { ++ cimg_forY(*this,y) { ++ cimg_forX(*this,x) { ++ std::fputc(0,nfile); ++ std::fputc((unsigned char)(*(ptr_g++)),nfile); ++ std::fputc((unsigned char)(*(ptr_r++)),nfile); ++ } ++ cimg::fwrite(align_buf,align,nfile); ++ ptr_r-=2*_width; ptr_g-=2*_width; ++ } ++ } break; ++ default : { ++ cimg_forY(*this,y) { ++ cimg_forX(*this,x) { ++ std::fputc((unsigned char)(*(ptr_b++)),nfile); ++ std::fputc((unsigned char)(*(ptr_g++)),nfile); ++ std::fputc((unsigned char)(*(ptr_r++)),nfile); ++ } ++ cimg::fwrite(align_buf,align,nfile); ++ ptr_r-=2*_width; ptr_g-=2*_width; ptr_b-=2*_width; ++ } ++ } ++ } ++ if (!file) cimg::fclose(nfile); ++ return *this; ++ } ++ ++ //! Save image as a JPEG file. ++ /** ++ \param filename Filename, as a C-string. ++ \param quality Image quality (in %) ++ **/ ++ const CImg& save_jpeg(const char *const filename, const unsigned int quality=100) const { ++ return _save_jpeg(0,filename,quality); ++ } ++ ++ //! Save image as a JPEG file \overloading. ++ const CImg& save_jpeg(std::FILE *const file, const unsigned int quality=100) const { ++ return _save_jpeg(file,0,quality); ++ } ++ ++ const CImg& _save_jpeg(std::FILE *const file, const char *const filename, const unsigned int quality) const { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_jpeg(): Specified filename is (null).", ++ cimg_instance); ++ if (is_empty()) { cimg::fempty(file,filename); return *this; } ++ if (_depth>1) ++ cimg::warn(_cimg_instance ++ "save_jpeg(): Instance is volumetric, only the first slice will be saved in file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ ++#ifndef cimg_use_jpeg ++ if (!file) return save_other(filename,quality); ++ else throw CImgIOException(_cimg_instance ++ "save_jpeg(): Unable to save data in '(*FILE)' unless libjpeg is enabled.", ++ cimg_instance); ++#else ++ unsigned int dimbuf = 0; ++ J_COLOR_SPACE colortype = JCS_RGB; ++ ++ switch (_spectrum) { ++ case 1 : dimbuf = 1; colortype = JCS_GRAYSCALE; break; ++ case 2 : dimbuf = 3; colortype = JCS_RGB; break; ++ case 3 : dimbuf = 3; colortype = JCS_RGB; break; ++ default : dimbuf = 4; colortype = JCS_CMYK; break; ++ } ++ ++ // Call libjpeg functions ++ struct jpeg_compress_struct cinfo; ++ struct jpeg_error_mgr jerr; ++ cinfo.err = jpeg_std_error(&jerr); ++ jpeg_create_compress(&cinfo); ++ 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); ++ ++ JSAMPROW row_pointer[1]; ++ CImg buffer(_width*dimbuf); ++ ++ while (cinfo.next_scanline& save_magick(const char *const filename, const unsigned int bytes_per_pixel=0) const { ++ if (!filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_magick(): Specified filename is (null).", ++ cimg_instance); ++ if (is_empty()) { cimg::fempty(0,filename); return *this; } ++ ++#ifdef cimg_use_magick ++ double stmin, stmax = (double)max_min(stmin); ++ if (_depth>1) ++ cimg::warn(_cimg_instance ++ "save_magick(): Instance is volumetric, only the first slice will be saved in file '%s'.", ++ cimg_instance, ++ filename); ++ ++ if (_spectrum>3) ++ cimg::warn(_cimg_instance ++ "save_magick(): Instance is multispectral, only the three first channels will be " ++ "saved in file '%s'.", ++ cimg_instance, ++ filename); ++ ++ if (stmin<0 || (bytes_per_pixel==1 && stmax>=256) || stmax>=65536) ++ cimg::warn(_cimg_instance ++ "save_magick(): Instance has pixel values in [%g,%g], probable type overflow in file '%s'.", ++ cimg_instance, ++ filename,stmin,stmax); ++ ++ Magick::Image image(Magick::Geometry(_width,_height),"black"); ++ image.type(Magick::TrueColorType); ++ image.depth(bytes_per_pixel?(8*bytes_per_pixel):(stmax>=256?16:8)); ++ const T ++ *ptr_r = data(0,0,0,0), ++ *ptr_g = _spectrum>1?data(0,0,0,1):0, ++ *ptr_b = _spectrum>2?data(0,0,0,2):0; ++ Magick::PixelPacket *pixels = image.getPixels(0,0,_width,_height); ++ switch (_spectrum) { ++ case 1 : // Scalar images ++ for (ulongT off = (ulongT)_width*_height; off; --off) { ++ pixels->red = pixels->green = pixels->blue = (Magick::Quantum)*(ptr_r++); ++ ++pixels; ++ } ++ break; ++ case 2 : // RG images ++ for (ulongT off = (ulongT)_width*_height; off; --off) { ++ pixels->red = (Magick::Quantum)*(ptr_r++); ++ pixels->green = (Magick::Quantum)*(ptr_g++); ++ pixels->blue = 0; ++pixels; ++ } ++ break; ++ default : // RGB images ++ for (ulongT off = (ulongT)_width*_height; off; --off) { ++ pixels->red = (Magick::Quantum)*(ptr_r++); ++ pixels->green = (Magick::Quantum)*(ptr_g++); ++ pixels->blue = (Magick::Quantum)*(ptr_b++); ++ ++pixels; ++ } ++ } ++ image.syncPixels(); ++ image.write(filename); ++ return *this; ++#else ++ cimg::unused(bytes_per_pixel); ++ throw CImgIOException(_cimg_instance ++ "save_magick(): Unable to save file '%s' unless libMagick++ is enabled.", ++ cimg_instance, ++ filename); ++#endif ++ } ++ ++ //! Save image as a PNG file. ++ /** ++ \param filename Filename, as a C-string. ++ \param bytes_per_pixel Force the number of bytes per pixels for the saving, when possible. ++ **/ ++ const CImg& save_png(const char *const filename, const unsigned int bytes_per_pixel=0) const { ++ return _save_png(0,filename,bytes_per_pixel); ++ } ++ ++ //! Save image as a PNG file \overloading. ++ const CImg& save_png(std::FILE *const file, const unsigned int bytes_per_pixel=0) const { ++ return _save_png(file,0,bytes_per_pixel); ++ } ++ ++ const CImg& _save_png(std::FILE *const file, const char *const filename, ++ const unsigned int bytes_per_pixel=0) const { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_png(): Specified filename is (null).", ++ cimg_instance); ++ if (is_empty()) { cimg::fempty(file,filename); return *this; } ++ ++#ifndef cimg_use_png ++ cimg::unused(bytes_per_pixel); ++ if (!file) return save_other(filename); ++ else throw CImgIOException(_cimg_instance ++ "save_png(): Unable to save data in '(*FILE)' unless libpng is enabled.", ++ cimg_instance); ++#else ++ ++#if defined __GNUC__ ++ const char *volatile nfilename = filename; // Use 'volatile' to avoid (wrong) g++ warning. ++ std::FILE *volatile nfile = file?file:cimg::fopen(nfilename,"wb"); ++ volatile double stmin, stmax = (double)max_min(stmin); ++#else ++ const char *nfilename = filename; ++ std::FILE *nfile = file?file:cimg::fopen(nfilename,"wb"); ++ double stmin, stmax = (double)max_min(stmin); ++#endif ++ ++ if (_depth>1) ++ cimg::warn(_cimg_instance ++ "save_png(): Instance is volumetric, only the first slice will be saved in file '%s'.", ++ cimg_instance, ++ filename); ++ ++ if (_spectrum>4) ++ cimg::warn(_cimg_instance ++ "save_png(): Instance is multispectral, only the three first channels will be saved in file '%s'.", ++ cimg_instance, ++ filename); ++ ++ if (stmin<0 || (bytes_per_pixel==1 && stmax>=256) || stmax>=65536) ++ cimg::warn(_cimg_instance ++ "save_png(): Instance has pixel values in [%g,%g], probable type overflow in file '%s'.", ++ cimg_instance, ++ filename,stmin,stmax); ++ ++ // 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_instance ++ "save_png(): Failed to initialize 'png_ptr' structure when saving file '%s'.", ++ cimg_instance, ++ 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_instance ++ "save_png(): Failed to initialize 'info_ptr' structure when saving file '%s'.", ++ cimg_instance, ++ 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_instance ++ "save_png(): Encountered unknown fatal error in libpng when saving file '%s'.", ++ cimg_instance, ++ nfilename?nfilename:"(FILE*)"); ++ } ++ png_init_io(png_ptr, nfile); ++ ++ const int bit_depth = bytes_per_pixel?(bytes_per_pixel*8):(stmax>=256?16:8); ++ ++ int color_type; ++ switch (spectrum()) { ++ 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 = spectrum()>4?4:spectrum(); ++ const int pixel_bit_depth_flag = numChan * (bit_depth - 1); ++ ++ // Allocate Memory for Image Save and Fill pixel data ++ png_bytep *const imgData = new png_byte*[_height]; ++ for (unsigned int row = 0; row<_height; ++row) imgData[row] = new png_byte[byte_depth*numChan*_width]; ++ const T *pC0 = data(0,0,0,0); ++ switch (pixel_bit_depth_flag) { ++ case 7 : { // Gray 8-bit ++ cimg_forY(*this,y) { ++ unsigned char *ptrd = imgData[y]; ++ cimg_forX(*this,x) *(ptrd++) = (unsigned char)*(pC0++); ++ } ++ } break; ++ case 14 : { // Gray w/ Alpha 8-bit ++ const T *pC1 = data(0,0,0,1); ++ cimg_forY(*this,y) { ++ unsigned char *ptrd = imgData[y]; ++ cimg_forX(*this,x) { ++ *(ptrd++) = (unsigned char)*(pC0++); ++ *(ptrd++) = (unsigned char)*(pC1++); ++ } ++ } ++ } break; ++ case 21 : { // RGB 8-bit ++ const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2); ++ cimg_forY(*this,y) { ++ unsigned char *ptrd = imgData[y]; ++ cimg_forX(*this,x) { ++ *(ptrd++) = (unsigned char)*(pC0++); ++ *(ptrd++) = (unsigned char)*(pC1++); ++ *(ptrd++) = (unsigned char)*(pC2++); ++ } ++ } ++ } break; ++ case 28 : { // RGB x/ Alpha 8-bit ++ const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2), *pC3 = data(0,0,0,3); ++ cimg_forY(*this,y){ ++ unsigned char *ptrd = imgData[y]; ++ cimg_forX(*this,x){ ++ *(ptrd++) = (unsigned char)*(pC0++); ++ *(ptrd++) = (unsigned char)*(pC1++); ++ *(ptrd++) = (unsigned char)*(pC2++); ++ *(ptrd++) = (unsigned char)*(pC3++); ++ } ++ } ++ } break; ++ case 15 : { // Gray 16-bit ++ cimg_forY(*this,y){ ++ unsigned short *ptrd = (unsigned short*)(imgData[y]); ++ cimg_forX(*this,x) *(ptrd++) = (unsigned short)*(pC0++); ++ if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],_width); ++ } ++ } break; ++ case 30 : { // Gray w/ Alpha 16-bit ++ const T *pC1 = data(0,0,0,1); ++ cimg_forY(*this,y){ ++ unsigned short *ptrd = (unsigned short*)(imgData[y]); ++ cimg_forX(*this,x) { ++ *(ptrd++) = (unsigned short)*(pC0++); ++ *(ptrd++) = (unsigned short)*(pC1++); ++ } ++ if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],2*_width); ++ } ++ } break; ++ case 45 : { // RGB 16-bit ++ const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2); ++ cimg_forY(*this,y) { ++ unsigned short *ptrd = (unsigned short*)(imgData[y]); ++ cimg_forX(*this,x) { ++ *(ptrd++) = (unsigned short)*(pC0++); ++ *(ptrd++) = (unsigned short)*(pC1++); ++ *(ptrd++) = (unsigned short)*(pC2++); ++ } ++ if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],3*_width); ++ } ++ } break; ++ case 60 : { // RGB w/ Alpha 16-bit ++ const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2), *pC3 = data(0,0,0,3); ++ cimg_forY(*this,y) { ++ unsigned short *ptrd = (unsigned short*)(imgData[y]); ++ cimg_forX(*this,x) { ++ *(ptrd++) = (unsigned short)*(pC0++); ++ *(ptrd++) = (unsigned short)*(pC1++); ++ *(ptrd++) = (unsigned short)*(pC2++); ++ *(ptrd++) = (unsigned short)*(pC3++); ++ } ++ if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],4*_width); ++ } ++ } break; ++ default : ++ if (!file) cimg::fclose(nfile); ++ throw CImgIOException(_cimg_instance ++ "save_png(): Encountered unknown fatal error in libpng when saving file '%s'.", ++ cimg_instance, ++ 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 image as a PNM file. ++ /** ++ \param filename Filename, as a C-string. ++ \param bytes_per_pixel Force the number of bytes per pixels for the saving. ++ **/ ++ const CImg& save_pnm(const char *const filename, const unsigned int bytes_per_pixel=0) const { ++ return _save_pnm(0,filename,bytes_per_pixel); ++ } ++ ++ //! Save image as a PNM file \overloading. ++ const CImg& save_pnm(std::FILE *const file, const unsigned int bytes_per_pixel=0) const { ++ return _save_pnm(file,0,bytes_per_pixel); ++ } ++ ++ const CImg& _save_pnm(std::FILE *const file, const char *const filename, ++ const unsigned int bytes_per_pixel=0) const { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_pnm(): Specified filename is (null).", ++ cimg_instance); ++ if (is_empty()) { cimg::fempty(file,filename); return *this; } ++ ++ double stmin, stmax = (double)max_min(stmin); ++ if (_depth>1) ++ cimg::warn(_cimg_instance ++ "save_pnm(): Instance is volumetric, only the first slice will be saved in file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ if (_spectrum>3) ++ cimg::warn(_cimg_instance ++ "save_pnm(): Instance is multispectral, only the three first channels will be saved in file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ if (stmin<0 || (bytes_per_pixel==1 && stmax>=256) || stmax>=65536) ++ cimg::warn(_cimg_instance ++ "save_pnm(): Instance has pixel values in [%g,%g], probable type overflow in file '%s'.", ++ cimg_instance, ++ stmin,stmax,filename?filename:"(FILE*)"); ++ ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); ++ const T ++ *ptr_r = data(0,0,0,0), ++ *ptr_g = (_spectrum>=2)?data(0,0,0,1):0, ++ *ptr_b = (_spectrum>=3)?data(0,0,0,2):0; ++ const ulongT buf_size = std::min((ulongT)(1024*1024),(ulongT)(_width*_height*(_spectrum==1?1UL:3UL))); ++ ++ std::fprintf(nfile,"P%c\n%u %u\n%u\n", ++ (_spectrum==1?'5':'6'),_width,_height,stmax<256?255:(stmax<4096?4095:65535)); ++ ++ switch (_spectrum) { ++ case 1 : { // Scalar image ++ if (bytes_per_pixel==1 || (!bytes_per_pixel && stmax<256)) { // Binary PGM 8 bits ++ CImg buf((unsigned int)buf_size); ++ for (longT to_write = (longT)width()*height(); to_write>0; ) { ++ const ulongT N = std::min((ulongT)to_write,buf_size); ++ unsigned char *ptrd = buf._data; ++ for (ulongT i = N; i>0; --i) *(ptrd++) = (unsigned char)*(ptr_r++); ++ cimg::fwrite(buf._data,N,nfile); ++ to_write-=N; ++ } ++ } else { // Binary PGM 16 bits ++ CImg buf((unsigned int)buf_size); ++ for (longT to_write = (longT)width()*height(); to_write>0; ) { ++ const ulongT N = std::min((ulongT)to_write,buf_size); ++ unsigned short *ptrd = buf._data; ++ for (ulongT i = N; i>0; --i) *(ptrd++) = (unsigned short)*(ptr_r++); ++ if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); ++ cimg::fwrite(buf._data,N,nfile); ++ to_write-=N; ++ } ++ } ++ } break; ++ case 2 : { // RG image ++ if (bytes_per_pixel==1 || (!bytes_per_pixel && stmax<256)) { // Binary PPM 8 bits ++ CImg buf((unsigned int)buf_size); ++ for (longT to_write = (longT)width()*height(); to_write>0; ) { ++ const ulongT N = std::min((ulongT)to_write,buf_size/3); ++ unsigned char *ptrd = buf._data; ++ for (ulongT i = N; i>0; --i) { ++ *(ptrd++) = (unsigned char)*(ptr_r++); ++ *(ptrd++) = (unsigned char)*(ptr_g++); ++ *(ptrd++) = 0; ++ } ++ cimg::fwrite(buf._data,3*N,nfile); ++ to_write-=N; ++ } ++ } else { // Binary PPM 16 bits ++ CImg buf((unsigned int)buf_size); ++ for (longT to_write = (longT)width()*height(); to_write>0; ) { ++ const ulongT N = std::min((ulongT)to_write,buf_size/3); ++ unsigned short *ptrd = buf._data; ++ for (ulongT i = N; i>0; --i) { ++ *(ptrd++) = (unsigned short)*(ptr_r++); ++ *(ptrd++) = (unsigned short)*(ptr_g++); ++ *(ptrd++) = 0; ++ } ++ if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); ++ cimg::fwrite(buf._data,3*N,nfile); ++ to_write-=N; ++ } ++ } ++ } break; ++ default : { // RGB image ++ if (bytes_per_pixel==1 || (!bytes_per_pixel && stmax<256)) { // Binary PPM 8 bits ++ CImg buf((unsigned int)buf_size); ++ for (longT to_write = (longT)width()*height(); to_write>0; ) { ++ const ulongT N = std::min((ulongT)to_write,buf_size/3); ++ unsigned char *ptrd = buf._data; ++ for (ulongT i = N; i>0; --i) { ++ *(ptrd++) = (unsigned char)*(ptr_r++); ++ *(ptrd++) = (unsigned char)*(ptr_g++); ++ *(ptrd++) = (unsigned char)*(ptr_b++); ++ } ++ cimg::fwrite(buf._data,3*N,nfile); ++ to_write-=N; ++ } ++ } else { // Binary PPM 16 bits ++ CImg buf((unsigned int)buf_size); ++ for (longT to_write = (longT)width()*height(); to_write>0; ) { ++ const ulongT N = std::min((ulongT)to_write,buf_size/3); ++ unsigned short *ptrd = buf._data; ++ for (ulongT i = N; i>0; --i) { ++ *(ptrd++) = (unsigned short)*(ptr_r++); ++ *(ptrd++) = (unsigned short)*(ptr_g++); ++ *(ptrd++) = (unsigned short)*(ptr_b++); ++ } ++ if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); ++ cimg::fwrite(buf._data,3*N,nfile); ++ to_write-=N; ++ } ++ } ++ } ++ } ++ if (!file) cimg::fclose(nfile); ++ return *this; ++ } ++ ++ //! Save image as a PNK file. ++ /** ++ \param filename Filename, as a C-string. ++ **/ ++ const CImg& save_pnk(const char *const filename) const { ++ return _save_pnk(0,filename); ++ } ++ ++ //! Save image as a PNK file \overloading. ++ const CImg& save_pnk(std::FILE *const file) const { ++ return _save_pnk(file,0); ++ } ++ ++ const CImg& _save_pnk(std::FILE *const file, const char *const filename) const { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_pnk(): Specified filename is (null).", ++ cimg_instance); ++ if (is_empty()) { cimg::fempty(file,filename); return *this; } ++ if (_spectrum>1) ++ cimg::warn(_cimg_instance ++ "save_pnk(): Instance is multispectral, only the first channel will be saved in file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ ++ const ulongT buf_size = std::min((ulongT)1024*1024,(ulongT)_width*_height*_depth); ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); ++ const T *ptr = data(0,0,0,0); ++ ++ if (!cimg::type::is_float() && sizeof(T)==1 && _depth<2) // Can be saved as regular PNM file. ++ _save_pnm(file,filename,0); ++ else if (!cimg::type::is_float() && sizeof(T)==1) { // Save as extended P5 file: Binary byte-valued 3d. ++ std::fprintf(nfile,"P5\n%u %u %u\n255\n",_width,_height,_depth); ++ CImg buf((unsigned int)buf_size); ++ for (longT to_write = (longT)width()*height()*depth(); to_write>0; ) { ++ const ulongT N = std::min((ulongT)to_write,buf_size); ++ unsigned char *ptrd = buf._data; ++ for (ulongT i = N; i>0; --i) *(ptrd++) = (unsigned char)*(ptr++); ++ cimg::fwrite(buf._data,N,nfile); ++ to_write-=N; ++ } ++ } else if (!cimg::type::is_float()) { // Save as P8: Binary int32-valued 3d. ++ if (_depth>1) std::fprintf(nfile,"P8\n%u %u %u\n%d\n",_width,_height,_depth,(int)max()); ++ else std::fprintf(nfile,"P8\n%u %u\n%d\n",_width,_height,(int)max()); ++ CImg buf((unsigned int)buf_size); ++ for (longT to_write = (longT)width()*height()*depth(); to_write>0; ) { ++ const ulongT N = std::min((ulongT)to_write,buf_size); ++ int *ptrd = buf._data; ++ for (ulongT i = N; i>0; --i) *(ptrd++) = (int)*(ptr++); ++ cimg::fwrite(buf._data,N,nfile); ++ to_write-=N; ++ } ++ } else { // Save as P9: Binary float-valued 3d. ++ if (_depth>1) std::fprintf(nfile,"P9\n%u %u %u\n%g\n",_width,_height,_depth,(double)max()); ++ else std::fprintf(nfile,"P9\n%u %u\n%g\n",_width,_height,(double)max()); ++ CImg buf((unsigned int)buf_size); ++ for (longT to_write = (longT)width()*height()*depth(); to_write>0; ) { ++ const ulongT N = std::min((ulongT)to_write,buf_size); ++ float *ptrd = buf._data; ++ for (ulongT i = N; i>0; --i) *(ptrd++) = (float)*(ptr++); ++ cimg::fwrite(buf._data,N,nfile); ++ to_write-=N; ++ } ++ } ++ ++ if (!file) cimg::fclose(nfile); ++ return *this; ++ } ++ ++ //! Save image as a PFM file. ++ /** ++ \param filename Filename, as a C-string. ++ **/ ++ const CImg& save_pfm(const char *const filename) const { ++ get_mirror('y')._save_pfm(0,filename); ++ return *this; ++ } ++ ++ //! Save image as a PFM file \overloading. ++ const CImg& save_pfm(std::FILE *const file) const { ++ get_mirror('y')._save_pfm(file,0); ++ return *this; ++ } ++ ++ const CImg& _save_pfm(std::FILE *const file, const char *const filename) const { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_pfm(): Specified filename is (null).", ++ cimg_instance); ++ if (is_empty()) { cimg::fempty(file,filename); return *this; } ++ if (_depth>1) ++ cimg::warn(_cimg_instance ++ "save_pfm(): Instance is volumetric, only the first slice will be saved in file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ if (_spectrum>3) ++ cimg::warn(_cimg_instance ++ "save_pfm(): image instance is multispectral, only the three first channels will be saved " ++ "in file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); ++ const T ++ *ptr_r = data(0,0,0,0), ++ *ptr_g = (_spectrum>=2)?data(0,0,0,1):0, ++ *ptr_b = (_spectrum>=3)?data(0,0,0,2):0; ++ const unsigned int buf_size = std::min(1024*1024U,_width*_height*(_spectrum==1?1:3)); ++ ++ std::fprintf(nfile,"P%c\n%u %u\n1.0\n", ++ (_spectrum==1?'f':'F'),_width,_height); ++ ++ switch (_spectrum) { ++ case 1 : { // Scalar image ++ CImg buf(buf_size); ++ for (longT to_write = (longT)width()*height(); to_write>0; ) { ++ const ulongT N = std::min((ulongT)to_write,(ulongT)buf_size); ++ float *ptrd = buf._data; ++ for (ulongT i = N; i>0; --i) *(ptrd++) = (float)*(ptr_r++); ++ if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); ++ cimg::fwrite(buf._data,N,nfile); ++ to_write-=N; ++ } ++ } break; ++ case 2 : { // RG image ++ CImg buf(buf_size); ++ for (longT to_write = (longT)width()*height(); to_write>0; ) { ++ const unsigned int N = std::min((unsigned int)to_write,buf_size/3); ++ float *ptrd = buf._data; ++ for (ulongT i = N; i>0; --i) { ++ *(ptrd++) = (float)*(ptr_r++); ++ *(ptrd++) = (float)*(ptr_g++); ++ *(ptrd++) = 0; ++ } ++ if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); ++ cimg::fwrite(buf._data,3*N,nfile); ++ to_write-=N; ++ } ++ } break; ++ default : { // RGB image ++ CImg buf(buf_size); ++ for (longT to_write = (longT)width()*height(); to_write>0; ) { ++ const unsigned int N = std::min((unsigned int)to_write,buf_size/3); ++ float *ptrd = buf._data; ++ for (ulongT i = N; i>0; --i) { ++ *(ptrd++) = (float)*(ptr_r++); ++ *(ptrd++) = (float)*(ptr_g++); ++ *(ptrd++) = (float)*(ptr_b++); ++ } ++ if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size); ++ cimg::fwrite(buf._data,3*N,nfile); ++ to_write-=N; ++ } ++ } ++ } ++ if (!file) cimg::fclose(nfile); ++ return *this; ++ } ++ ++ //! Save image as a RGB file. ++ /** ++ \param filename Filename, as a C-string. ++ **/ ++ const CImg& save_rgb(const char *const filename) const { ++ return _save_rgb(0,filename); ++ } ++ ++ //! Save image as a RGB file \overloading. ++ const CImg& save_rgb(std::FILE *const file) const { ++ return _save_rgb(file,0); ++ } ++ ++ const CImg& _save_rgb(std::FILE *const file, const char *const filename) const { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_rgb(): Specified filename is (null).", ++ cimg_instance); ++ if (is_empty()) { cimg::fempty(file,filename); return *this; } ++ if (_spectrum!=3) ++ cimg::warn(_cimg_instance ++ "save_rgb(): image instance has not exactly 3 channels, for file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); ++ const ulongT wh = (ulongT)_width*_height; ++ unsigned char *const buffer = new unsigned char[3*wh], *nbuffer = buffer; ++ const T ++ *ptr1 = data(0,0,0,0), ++ *ptr2 = _spectrum>1?data(0,0,0,1):0, ++ *ptr3 = _spectrum>2?data(0,0,0,2):0; ++ switch (_spectrum) { ++ case 1 : { // Scalar image ++ for (ulongT k = 0; k& save_rgba(const char *const filename) const { ++ return _save_rgba(0,filename); ++ } ++ ++ //! Save image as a RGBA file \overloading. ++ const CImg& save_rgba(std::FILE *const file) const { ++ return _save_rgba(file,0); ++ } ++ ++ const CImg& _save_rgba(std::FILE *const file, const char *const filename) const { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_rgba(): Specified filename is (null).", ++ cimg_instance); ++ if (is_empty()) { cimg::fempty(file,filename); return *this; } ++ if (_spectrum!=4) ++ cimg::warn(_cimg_instance ++ "save_rgba(): image instance has not exactly 4 channels, for file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); ++ const ulongT wh = (ulongT)_width*_height; ++ unsigned char *const buffer = new unsigned char[4*wh], *nbuffer = buffer; ++ const T ++ *ptr1 = data(0,0,0,0), ++ *ptr2 = _spectrum>1?data(0,0,0,1):0, ++ *ptr3 = _spectrum>2?data(0,0,0,2):0, ++ *ptr4 = _spectrum>3?data(0,0,0,3):0; ++ switch (_spectrum) { ++ case 1 : { // Scalar images ++ for (ulongT k = 0; k{ 0=None | 1=LZW | 2=JPEG }. ++ \param voxel_size Voxel size, to be stored in the filename. ++ \param description Description, to be stored in the filename. ++ \param use_bigtiff Allow to save big tiff files (>4Gb). ++ \note ++ - libtiff support is enabled by defining the precompilation ++ directive \c cimg_use_tif. ++ - When libtiff is enabled, 2D and 3D (multipage) several ++ channel per pixel are supported for ++ char,uchar,short,ushort,float and \c double pixel types. ++ - If \c cimg_use_tif is not defined at compile time the ++ function uses CImg&save_other(const char*). ++ **/ ++ const CImg& save_tiff(const char *const filename, const unsigned int compression_type=0, ++ const float *const voxel_size=0, const char *const description=0, ++ const bool use_bigtiff=true) const { ++ if (!filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_tiff(): Specified filename is (null).", ++ cimg_instance); ++ if (is_empty()) { cimg::fempty(0,filename); return *this; } ++ ++#ifdef cimg_use_tiff ++ const bool ++ _use_bigtiff = use_bigtiff && sizeof(ulongT)>=8 && size()*sizeof(T)>=1UL<<31; // No bigtiff for small images. ++ TIFF *tif = TIFFOpen(filename,_use_bigtiff?"w8":"w4"); ++ if (tif) { ++ cimg_forZ(*this,z) _save_tiff(tif,z,z,compression_type,voxel_size,description); ++ TIFFClose(tif); ++ } else throw CImgIOException(_cimg_instance ++ "save_tiff(): Failed to open file '%s' for writing.", ++ cimg_instance, ++ filename); ++ return *this; ++#else ++ cimg::unused(compression_type,voxel_size,description,use_bigtiff); ++ return save_other(filename); ++#endif ++ } ++ ++#ifdef cimg_use_tiff ++ ++#define _cimg_save_tiff(types,typed,compression_type) if (!std::strcmp(types,pixel_type())) { \ ++ const typed foo = (typed)0; return _save_tiff(tif,directory,z,foo,compression_type,voxel_size,description); } ++ ++ // [internal] Save a plane into a tiff file ++ template ++ const CImg& _save_tiff(TIFF *tif, const unsigned int directory, const unsigned int z, const t& pixel_t, ++ const unsigned int compression_type, const float *const voxel_size, ++ const char *const description) const { ++ if (is_empty() || !tif || pixel_t) return *this; ++ const char *const filename = TIFFFileName(tif); ++ uint32 rowsperstrip = (uint32)-1; ++ uint16 spp = _spectrum, bpp = sizeof(t)*8, photometric; ++ 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); ++ if (voxel_size) { ++ const float vx = voxel_size[0], vy = voxel_size[1], vz = voxel_size[2]; ++ TIFFSetField(tif,TIFFTAG_RESOLUTIONUNIT,RESUNIT_NONE); ++ TIFFSetField(tif,TIFFTAG_XRESOLUTION,1.0f/vx); ++ TIFFSetField(tif,TIFFTAG_YRESOLUTION,1.0f/vy); ++ CImg s_description(256); ++ cimg_snprintf(s_description,s_description._width,"VX=%g VY=%g VZ=%g spacing=%g",vx,vy,vz,vz); ++ TIFFSetField(tif,TIFFTAG_IMAGEDESCRIPTION,s_description.data()); ++ } ++ if (description) TIFFSetField(tif,TIFFTAG_IMAGEDESCRIPTION,description); ++ 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); ++ double valm, valM = max_min(valm); ++ TIFFSetField(tif,TIFFTAG_SMINSAMPLEVALUE,valm); ++ TIFFSetField(tif,TIFFTAG_SMAXSAMPLEVALUE,valM); ++ TIFFSetField(tif,TIFFTAG_BITSPERSAMPLE,bpp); ++ TIFFSetField(tif,TIFFTAG_PLANARCONFIG,PLANARCONFIG_CONTIG); ++ TIFFSetField(tif,TIFFTAG_PHOTOMETRIC,photometric); ++ TIFFSetField(tif,TIFFTAG_COMPRESSION,compression_type==2?COMPRESSION_JPEG: ++ compression_type==1?COMPRESSION_LZW:COMPRESSION_NONE); ++ rowsperstrip = TIFFDefaultStripSize(tif,rowsperstrip); ++ TIFFSetField(tif,TIFFTAG_ROWSPERSTRIP,rowsperstrip); ++ TIFFSetField(tif,TIFFTAG_FILLORDER,FILLORDER_MSB2LSB); ++ TIFFSetField(tif,TIFFTAG_SOFTWARE,"CImg"); ++ ++ t *const buf = (t*)_TIFFmalloc(TIFFStripSize(tif)); ++ if (buf) { ++ for (unsigned int row = 0; row<_height; row+=rowsperstrip) { ++ uint32 nrow = (row + rowsperstrip>_height?_height - row:rowsperstrip); ++ tstrip_t strip = TIFFComputeStrip(tif,row,0); ++ tsize_t i = 0; ++ for (unsigned int rr = 0; rr& _save_tiff(TIFF *tif, const unsigned int directory, const unsigned int z, ++ const unsigned int compression_type, const float *const voxel_size, ++ const char *const description) const { ++ _cimg_save_tiff("bool",unsigned char,compression_type); ++ _cimg_save_tiff("unsigned char",unsigned char,compression_type); ++ _cimg_save_tiff("char",char,compression_type); ++ _cimg_save_tiff("unsigned short",unsigned short,compression_type); ++ _cimg_save_tiff("short",short,compression_type); ++ _cimg_save_tiff("unsigned int",unsigned int,compression_type); ++ _cimg_save_tiff("int",int,compression_type); ++ _cimg_save_tiff("unsigned int64",unsigned int,compression_type); ++ _cimg_save_tiff("int64",int,compression_type); ++ _cimg_save_tiff("float",float,compression_type); ++ _cimg_save_tiff("double",float,compression_type); ++ const char *const filename = TIFFFileName(tif); ++ throw CImgInstanceException(_cimg_instance ++ "save_tiff(): Unsupported pixel type '%s' for file '%s'.", ++ cimg_instance, ++ pixel_type(),filename?filename:"(FILE*)"); ++ return *this; ++ } ++#endif ++ ++ //! Save image as a MINC2 file. ++ /** ++ \param filename Filename, as a C-string. ++ \param imitate_file If non-zero, reference filename, as a C-string, to borrow header from. ++ **/ ++ const CImg& save_minc2(const char *const filename, ++ const char *const imitate_file=0) const { ++ if (!filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_minc2(): Specified filename is (null).", ++ cimg_instance); ++ if (is_empty()) { cimg::fempty(0,filename); return *this; } ++ ++#ifndef cimg_use_minc2 ++ cimg::unused(imitate_file); ++ return save_other(filename); ++#else ++ minc::minc_1_writer wtr; ++ if (imitate_file) ++ wtr.open(filename, imitate_file); ++ else { ++ minc::minc_info di; ++ if (width()) di.push_back(minc::dim_info(width(),width()*0.5,-1,minc::dim_info::DIM_X)); ++ if (height()) di.push_back(minc::dim_info(height(),height()*0.5,-1,minc::dim_info::DIM_Y)); ++ if (depth()) di.push_back(minc::dim_info(depth(),depth()*0.5,-1,minc::dim_info::DIM_Z)); ++ if (spectrum()) di.push_back(minc::dim_info(spectrum(),spectrum()*0.5,-1,minc::dim_info::DIM_TIME)); ++ wtr.open(filename,di,1,NC_FLOAT,0); ++ } ++ if (cimg::type::string()==cimg::type::string()) ++ wtr.setup_write_byte(); ++ else if (cimg::type::string()==cimg::type::string()) ++ wtr.setup_write_int(); ++ else if (cimg::type::string()==cimg::type::string()) ++ wtr.setup_write_double(); ++ else ++ wtr.setup_write_float(); ++ minc::save_standard_volume(wtr, this->_data); ++ return *this; ++#endif ++ } ++ ++ //! Save image as an ANALYZE7.5 or NIFTI file. ++ /** ++ \param filename Filename, as a C-string. ++ \param voxel_size Pointer to 3 consecutive values that tell about the voxel sizes along the X,Y and Z dimensions. ++ **/ ++ const CImg& save_analyze(const char *const filename, const float *const voxel_size=0) const { ++ if (!filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_analyze(): Specified filename is (null).", ++ cimg_instance); ++ if (is_empty()) { cimg::fempty(0,filename); return *this; } ++ ++ std::FILE *file; ++ CImg hname(1024), iname(1024); ++ const char *const ext = cimg::split_filename(filename); ++ short datatype = -1; ++ if (!*ext) { ++ cimg_snprintf(hname,hname._width,"%s.hdr",filename); ++ cimg_snprintf(iname,iname._width,"%s.img",filename); ++ } ++ if (!cimg::strncasecmp(ext,"hdr",3)) { ++ std::strcpy(hname,filename); ++ std::strncpy(iname,filename,iname._width - 1); ++ cimg_sprintf(iname._data + std::strlen(iname) - 3,"img"); ++ } ++ if (!cimg::strncasecmp(ext,"img",3)) { ++ std::strcpy(hname,filename); ++ std::strncpy(iname,filename,iname._width - 1); ++ cimg_sprintf(hname._data + std::strlen(iname) - 3,"hdr"); ++ } ++ if (!cimg::strncasecmp(ext,"nii",3)) { ++ std::strncpy(hname,filename,hname._width - 1); *iname = 0; ++ } ++ ++ CImg header(*iname?348:352,1,1,1,0); ++ int *const iheader = (int*)header._data; ++ *iheader = 348; ++ std::strcpy(header._data + 4,"CImg"); ++ std::strcpy(header._data + 14," "); ++ ((short*)&(header[36]))[0] = 4096; ++ ((char*)&(header[38]))[0] = 114; ++ ((short*)&(header[40]))[0] = 4; ++ ((short*)&(header[40]))[1] = (short)_width; ++ ((short*)&(header[40]))[2] = (short)_height; ++ ((short*)&(header[40]))[3] = (short)_depth; ++ ((short*)&(header[40]))[4] = (short)_spectrum; ++ 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 int64")) datatype = 8; ++ if (!cimg::strcasecmp(pixel_type(),"int64")) datatype = 8; ++ if (!cimg::strcasecmp(pixel_type(),"float")) datatype = 16; ++ if (!cimg::strcasecmp(pixel_type(),"double")) datatype = 64; ++ if (datatype<0) ++ throw CImgIOException(_cimg_instance ++ "save_analyze(): Unsupported pixel type '%s' for file '%s'.", ++ cimg_instance, ++ pixel_type(),filename); ++ ++ ((short*)&(header[70]))[0] = datatype; ++ ((short*)&(header[72]))[0] = sizeof(T); ++ ((float*)&(header[108]))[0] = (float)(*iname?0:header.width()); ++ ((float*)&(header[112]))[0] = 1; ++ ((float*)&(header[76]))[0] = 0; ++ if (voxel_size) { ++ ((float*)&(header[76]))[1] = voxel_size[0]; ++ ((float*)&(header[76]))[2] = voxel_size[1]; ++ ((float*)&(header[76]))[3] = voxel_size[2]; ++ } else ((float*)&(header[76]))[1] = ((float*)&(header[76]))[2] = ((float*)&(header[76]))[3] = 1; ++ file = cimg::fopen(hname,"wb"); ++ cimg::fwrite(header._data,header.width(),file); ++ if (*iname) { cimg::fclose(file); file = cimg::fopen(iname,"wb"); } ++ cimg::fwrite(_data,size(),file); ++ cimg::fclose(file); ++ return *this; ++ } ++ ++ //! Save image as a .cimg file. ++ /** ++ \param filename Filename, as a C-string. ++ \param is_compressed Tells if the file contains compressed image data. ++ **/ ++ const CImg& save_cimg(const char *const filename, const bool is_compressed=false) const { ++ CImgList(*this,true).save_cimg(filename,is_compressed); ++ return *this; ++ } ++ ++ //! Save image as a .cimg file \overloading. ++ const CImg& save_cimg(std::FILE *const file, const bool is_compressed=false) const { ++ CImgList(*this,true).save_cimg(file,is_compressed); ++ return *this; ++ } ++ ++ //! Save image as a sub-image into an existing .cimg file. ++ /** ++ \param filename Filename, as a C-string. ++ \param n0 Index of the image inside the file. ++ \param x0 X-coordinate of the sub-image location. ++ \param y0 Y-coordinate of the sub-image location. ++ \param z0 Z-coordinate of the sub-image location. ++ \param c0 C-coordinate of the sub-image location. ++ **/ ++ 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 c0) const { ++ CImgList(*this,true).save_cimg(filename,n0,x0,y0,z0,c0); ++ return *this; ++ } ++ ++ //! Save image as a sub-image into an existing .cimg file \overloading. ++ const CImg& save_cimg(std::FILE *const file, ++ const unsigned int n0, ++ const unsigned int x0, const unsigned int y0, ++ const unsigned int z0, const unsigned int c0) const { ++ CImgList(*this,true).save_cimg(file,n0,x0,y0,z0,c0); ++ return *this; ++ } ++ ++ //! Save blank image as a .cimg file. ++ /** ++ \param filename Filename, as a C-string. ++ \param dx Width of the image. ++ \param dy Height of the image. ++ \param dz Depth of the image. ++ \param dc Number of channels of the image. ++ \note ++ - All pixel values of the saved image are set to \c 0. ++ - Use this method to save large images without having to instanciate and allocate them. ++ **/ ++ 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 dc=1) { ++ return CImgList::save_empty_cimg(filename,1,dx,dy,dz,dc); ++ } ++ ++ //! Save blank image as a .cimg file \overloading. ++ /** ++ Same as save_empty_cimg(const char *,unsigned int,unsigned int,unsigned int,unsigned int) ++ with a file stream argument instead of a filename string. ++ **/ ++ static void save_empty_cimg(std::FILE *const file, ++ const unsigned int dx, const unsigned int dy=1, ++ const unsigned int dz=1, const unsigned int dc=1) { ++ return CImgList::save_empty_cimg(file,1,dx,dy,dz,dc); ++ } ++ ++ //! Save image as an INRIMAGE-4 file. ++ /** ++ \param filename Filename, as a C-string. ++ \param voxel_size Pointer to 3 values specifying the voxel sizes along the X,Y and Z dimensions. ++ **/ ++ const CImg& save_inr(const char *const filename, const float *const voxel_size=0) const { ++ return _save_inr(0,filename,voxel_size); ++ } ++ ++ //! Save image as an INRIMAGE-4 file \overloading. ++ const CImg& save_inr(std::FILE *const file, const float *const voxel_size=0) const { ++ return _save_inr(file,0,voxel_size); ++ } ++ ++ const CImg& _save_inr(std::FILE *const file, const char *const filename, const float *const voxel_size) const { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_inr(): Specified filename is (null).", ++ cimg_instance); ++ if (is_empty()) { cimg::fempty(file,filename); return *this; } ++ ++ 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_instance ++ "save_inr(): Unsupported pixel type '%s' for file '%s'", ++ cimg_instance, ++ pixel_type(),filename?filename:"(FILE*)"); ++ ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); ++ CImg header(257); ++ int err = cimg_snprintf(header,header._width,"#INRIMAGE-4#{\nXDIM=%u\nYDIM=%u\nZDIM=%u\nVDIM=%u\n", ++ _width,_height,_depth,_spectrum); ++ if (voxel_size) err+=cimg_sprintf(header._data + err,"VX=%g\nVY=%g\nVZ=%g\n", ++ voxel_size[0],voxel_size[1],voxel_size[2]); ++ err+=cimg_sprintf(header._data + err,"TYPE=%s\nCPU=%s\n",inrtype,cimg::endianness()?"sun":"decm"); ++ std::memset(header._data + err,'\n',252 - err); ++ std::memcpy(header._data + 252,"##}\n",4); ++ cimg::fwrite(header._data,256,nfile); ++ cimg_forXYZ(*this,x,y,z) cimg_forC(*this,c) cimg::fwrite(&((*this)(x,y,z,c)),1,nfile); ++ if (!file) cimg::fclose(nfile); ++ return *this; ++ } ++ ++ //! Save image as an OpenEXR file. ++ /** ++ \param filename Filename, as a C-string. ++ \note The OpenEXR file format is described here. ++ **/ ++ const CImg& save_exr(const char *const filename) const { ++ if (!filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_exr(): Specified filename is (null).", ++ cimg_instance); ++ if (is_empty()) { cimg::fempty(0,filename); return *this; } ++ if (_depth>1) ++ cimg::warn(_cimg_instance ++ "save_exr(): Instance is volumetric, only the first slice will be saved in file '%s'.", ++ cimg_instance, ++ filename); ++ ++#ifndef cimg_use_openexr ++ return save_other(filename); ++#else ++ Imf::Rgba *const ptrd0 = new Imf::Rgba[(size_t)_width*_height], *ptrd = ptrd0, rgba; ++ switch (_spectrum) { ++ case 1 : { // Grayscale image. ++ for (const T *ptr_r = data(), *const ptr_e = ptr_r + (ulongT)_width*_height; ptr_rPandore file specifications ++ for more information). ++ **/ ++ const CImg& save_pandore(const char *const filename, const unsigned int colorspace=0) const { ++ return _save_pandore(0,filename,colorspace); ++ } ++ ++ //! Save image as a Pandore-5 file \overloading. ++ /** ++ Same as save_pandore(const char *,unsigned int) const ++ with a file stream argument instead of a filename string. ++ **/ ++ const CImg& save_pandore(std::FILE *const file, const unsigned int colorspace=0) const { ++ return _save_pandore(file,0,colorspace); ++ } ++ ++ 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] = _spectrum; 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] = _spectrum; dims[1] = _width; nbdims = 2; ++ } ++ if (id==26 || id==27 || id==29) { ++ dims[0] = _spectrum; dims[1] = _height; dims[2] = _width; nbdims=3; ++ } ++ if (id==30 || id==31 || id==33) { ++ dims[0] = _spectrum; dims[1] = _depth; dims[2] = _height; dims[3] = _width; nbdims = 4; ++ } ++ return nbdims; ++ } ++ ++ const CImg& _save_pandore(std::FILE *const file, const char *const filename, ++ const unsigned int colorspace) const { ++ ++#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==_spectrum):true) && !std::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(unsigned long)==4) { CImg ndims(5); \ ++ for (int d = 0; d<5; ++d) ndims[d] = (unsigned long)dims[d]; cimg::fwrite(ndims._data,nbdims,nfile); } \ ++ else if (sizeof(unsigned int)==4) { CImg ndims(5); \ ++ for (int d = 0; d<5; ++d) ndims[d] = (unsigned int)dims[d]; cimg::fwrite(ndims._data,nbdims,nfile); } \ ++ else if (sizeof(unsigned short)==4) { CImg ndims(5); \ ++ for (int d = 0; d<5; ++d) ndims[d] = (unsigned short)dims[d]; cimg::fwrite(ndims._data,nbdims,nfile); } \ ++ else throw CImgIOException(_cimg_instance \ ++ "save_pandore(): Unsupported datatype for file '%s'.",\ ++ cimg_instance, \ ++ filename?filename:"(FILE*)"); \ ++ if (id==2 || id==5 || id==8 || id==16 || id==19 || id==22 || id==26 || id==30) { \ ++ __cimg_save_pandore_case(unsigned char); \ ++ } else if (id==3 || id==6 || id==9 || id==17 || id==20 || id==23 || id==27 || id==31) { \ ++ if (sizeof(unsigned long)==4) { __cimg_save_pandore_case(unsigned long); } \ ++ else if (sizeof(unsigned int)==4) { __cimg_save_pandore_case(unsigned int); } \ ++ else if (sizeof(unsigned short)==4) { __cimg_save_pandore_case(unsigned short); } \ ++ else throw CImgIOException(_cimg_instance \ ++ "save_pandore(): Unsupported datatype for file '%s'.",\ ++ cimg_instance, \ ++ filename?filename:"(FILE*)"); \ ++ } 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_instance \ ++ "save_pandore(): Unsupported datatype for file '%s'.",\ ++ cimg_instance, \ ++ filename?filename:"(FILE*)"); \ ++ } \ ++ saved = true; \ ++ } ++ ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_pandore(): Specified filename is (null).", ++ cimg_instance); ++ if (is_empty()) { cimg::fempty(file,filename); return *this; } ++ ++ 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] = { 0 }; ++ 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,"unsigned short",3); ++ _cimg_save_pandore_case(1,1,1,"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 int64",3); ++ _cimg_save_pandore_case(1,1,1,"int64",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,"unsigned short",6); ++ _cimg_save_pandore_case(0,1,1,"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 int64",6); ++ _cimg_save_pandore_case(0,1,1,"int64",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,"unsigned short",9); ++ _cimg_save_pandore_case(0,0,1,"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 int64",9); ++ _cimg_save_pandore_case(0,0,1,"int64",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,"unsigned short",17); ++ _cimg_save_pandore_case(0,1,3,"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 int64",17); ++ _cimg_save_pandore_case(0,1,3,"int64",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,"unsigned short",20); ++ _cimg_save_pandore_case(0,0,3,"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 int64",20); ++ _cimg_save_pandore_case(0,0,3,"int64",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,"unsigned short",23); ++ _cimg_save_pandore_case(1,1,0,"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 int64",23); ++ _cimg_save_pandore_case(1,1,0,"int64",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,"unsigned short",27); ++ _cimg_save_pandore_case(0,1,0,"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 int64",27); ++ _cimg_save_pandore_case(0,1,0,"int64",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,"unsigned short",31); ++ _cimg_save_pandore_case(0,0,0,"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 int64",31); ++ _cimg_save_pandore_case(0,0,0,"int64",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 image as a raw data file. ++ /** ++ \param filename Filename, as a C-string. ++ \param is_multiplexed Tells if the image channels are stored in a multiplexed way (\c true) or not (\c false). ++ \note The .raw format does not store the image dimensions in the output file, ++ so you have to keep track of them somewhere to be able to read the file correctly afterwards. ++ **/ ++ const CImg& save_raw(const char *const filename, const bool is_multiplexed=false) const { ++ return _save_raw(0,filename,is_multiplexed); ++ } ++ ++ //! Save image as a raw data file \overloading. ++ /** ++ Same as save_raw(const char *,bool) const ++ with a file stream argument instead of a filename string. ++ **/ ++ const CImg& save_raw(std::FILE *const file, const bool is_multiplexed=false) const { ++ return _save_raw(file,0,is_multiplexed); ++ } ++ ++ const CImg& _save_raw(std::FILE *const file, const char *const filename, const bool is_multiplexed) const { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_raw(): Specified filename is (null).", ++ cimg_instance); ++ if (is_empty()) { cimg::fempty(file,filename); return *this; } ++ ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); ++ if (!is_multiplexed) cimg::fwrite(_data,size(),nfile); ++ else { ++ CImg buf(_spectrum); ++ cimg_forXYZ(*this,x,y,z) { ++ cimg_forC(*this,c) buf[c] = (*this)(x,y,z,c); ++ cimg::fwrite(buf._data,_spectrum,nfile); ++ } ++ } ++ if (!file) cimg::fclose(nfile); ++ return *this; ++ } ++ ++ //! Save image as a .yuv video file. ++ /** ++ \param filename Filename, as a C-string. ++ \param chroma_subsampling Type of chroma subsampling. Can be { 420 | 422 | 444 }. ++ \param is_rgb Tells if pixel values of the instance image are RGB-coded (\c true) or YUV-coded (\c false). ++ \note Each slice of the instance image is considered to be a single frame of the output video file. ++ **/ ++ const CImg& save_yuv(const char *const filename, ++ const unsigned int chroma_subsampling=444, ++ const bool is_rgb=true) const { ++ get_split('z').save_yuv(filename,chroma_subsampling,is_rgb); ++ return *this; ++ } ++ ++ //! Save image as a .yuv video file \overloading. ++ /** ++ Same as save_yuv(const char*,const unsigned int,const bool) const ++ with a file stream argument instead of a filename string. ++ **/ ++ const CImg& save_yuv(std::FILE *const file, ++ const unsigned int chroma_subsampling=444, ++ const bool is_rgb=true) const { ++ get_split('z').save_yuv(file,chroma_subsampling,is_rgb); ++ return *this; ++ } ++ ++ //! Save 3d object as an Object File Format (.off) file. ++ /** ++ \param filename Filename, as a C-string. ++ \param primitives List of 3d object primitives. ++ \param colors List of 3d object colors. ++ \note ++ - Instance image contains the vertices data of the 3d object. ++ - Textured, transparent or sphere-shaped primitives cannot be managed by the .off file format. ++ Such primitives will be lost or simplified during file saving. ++ - The .off file format is described here. ++ **/ ++ template ++ const CImg& save_off(const CImgList& primitives, const CImgList& colors, ++ const char *const filename) const { ++ return _save_off(primitives,colors,0,filename); ++ } ++ ++ //! Save 3d object as an Object File Format (.off) file \overloading. ++ /** ++ Same as save_off(const CImgList&,const CImgList&,const char*) const ++ with a file stream argument instead of a filename string. ++ **/ ++ template ++ const CImg& save_off(const CImgList& primitives, const CImgList& colors, ++ std::FILE *const file) const { ++ return _save_off(primitives,colors,file,0); ++ } ++ ++ template ++ const CImg& _save_off(const CImgList& primitives, const CImgList& colors, ++ std::FILE *const file, const char *const filename) const { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_off(): Specified filename is (null).", ++ cimg_instance); ++ if (is_empty()) ++ throw CImgInstanceException(_cimg_instance ++ "save_off(): Empty instance, for file '%s'.", ++ cimg_instance, ++ filename?filename:"(FILE*)"); ++ ++ CImgList opacities; ++ CImg error_message(1024); ++ if (!is_object3d(primitives,colors,opacities,true,error_message)) ++ throw CImgInstanceException(_cimg_instance ++ "save_off(): Invalid specified 3d object, for file '%s' (%s).", ++ cimg_instance, ++ filename?filename:"(FILE*)",error_message.data()); ++ ++ const CImg default_color(1,3,1,1,200); ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"w"); ++ unsigned int supported_primitives = 0; ++ cimglist_for(primitives,l) if (primitives[l].size()!=5) ++supported_primitives; ++ std::fprintf(nfile,"OFF\n%u %u %u\n",_width,supported_primitives,3*primitives._width); ++ cimg_forX(*this,i) std::fprintf(nfile,"%f %f %f\n", ++ (float)((*this)(i,0)),(float)((*this)(i,1)),(float)((*this)(i,2))); ++ cimglist_for(primitives,l) { ++ const CImg& color = l1?color[1]:r)/255.0f, b = (csiz>2?color[2]:g)/255.0f; ++ switch (psiz) { ++ case 1 : std::fprintf(nfile,"1 %u %f %f %f\n", ++ (unsigned int)primitives(l,0),r,g,b); break; ++ case 2 : 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 : 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 : 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; ++ case 5 : 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 6 : { ++ const unsigned int xt = (unsigned int)primitives(l,2), yt = (unsigned int)primitives(l,3); ++ const float ++ rt = color.atXY(xt,yt,0)/255.0f, ++ gt = (csiz>1?color.atXY(xt,yt,1):r)/255.0f, ++ bt = (csiz>2?color.atXY(xt,yt,2):g)/255.0f; ++ std::fprintf(nfile,"2 %u %u %f %f %f\n", ++ (unsigned int)primitives(l,0),(unsigned int)primitives(l,1),rt,gt,bt); ++ } break; ++ case 9 : { ++ const unsigned int xt = (unsigned int)primitives(l,3), yt = (unsigned int)primitives(l,4); ++ const float ++ rt = color.atXY(xt,yt,0)/255.0f, ++ gt = (csiz>1?color.atXY(xt,yt,1):r)/255.0f, ++ bt = (csiz>2?color.atXY(xt,yt,2):g)/255.0f; ++ 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),rt,gt,bt); ++ } break; ++ case 12 : { ++ const unsigned int xt = (unsigned int)primitives(l,4), yt = (unsigned int)primitives(l,5); ++ const float ++ rt = color.atXY(xt,yt,0)/255.0f, ++ gt = (csiz>1?color.atXY(xt,yt,1):r)/255.0f, ++ bt = (csiz>2?color.atXY(xt,yt,2):g)/255.0f; ++ 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),rt,gt,bt); ++ } break; ++ } ++ } ++ if (!file) cimg::fclose(nfile); ++ return *this; ++ } ++ ++ //! Save volumetric image as a video, using the OpenCV library. ++ /** ++ \param filename Filename to write data to. ++ \param fps Number of frames per second. ++ \param codec Type of compression (See http://www.fourcc.org/codecs.php to see available codecs). ++ \param keep_open Tells if the video writer associated to the specified filename ++ must be kept open or not (to allow frames to be added in the same file afterwards). ++ **/ ++ const CImg& save_video(const char *const filename, const unsigned int fps=25, ++ const char *codec=0, const bool keep_open=false) const { ++ if (is_empty()) { CImgList().save_video(filename,fps,codec,keep_open); return *this; } ++ CImgList list; ++ get_split('z').move_to(list); ++ list.save_video(filename,fps,codec,keep_open); ++ return *this; ++ } ++ ++ //! Save volumetric image as a video, using ffmpeg external binary. ++ /** ++ \param filename Filename, as a C-string. ++ \param fps Video framerate. ++ \param codec Video codec, as a C-string. ++ \param bitrate Video bitrate. ++ \note ++ - Each slice of the instance image is considered to be a single frame of the output video file. ++ - This method uses \c ffmpeg, an external executable binary provided by ++ FFmpeg. ++ It must be installed for the method to succeed. ++ **/ ++ const CImg& save_ffmpeg_external(const char *const filename, const unsigned int fps=25, ++ const char *const codec=0, const unsigned int bitrate=2048) const { ++ if (!filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_ffmpeg_external(): Specified filename is (null).", ++ cimg_instance); ++ if (is_empty()) { cimg::fempty(0,filename); return *this; } ++ ++ CImgList list; ++ get_split('z').move_to(list); ++ list.save_ffmpeg_external(filename,fps,codec,bitrate); ++ return *this; ++ } ++ ++ //! Save image using gzip external binary. ++ /** ++ \param filename Filename, as a C-string. ++ \note This method uses \c gzip, an external executable binary provided by ++ gzip. ++ It must be installed for the method to succeed. ++ **/ ++ const CImg& save_gzip_external(const char *const filename) const { ++ if (!filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_gzip_external(): Specified filename is (null).", ++ cimg_instance); ++ if (is_empty()) { cimg::fempty(0,filename); return *this; } ++ ++ CImg command(1024), filename_tmp(256), body(256); ++ const char ++ *ext = cimg::split_filename(filename,body), ++ *ext2 = cimg::split_filename(body,0); ++ std::FILE *file; ++ do { ++ if (!cimg::strcasecmp(ext,"gz")) { ++ if (*ext2) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", ++ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2); ++ else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg", ++ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); ++ } else { ++ if (*ext) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", ++ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext); ++ else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg", ++ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); ++ } ++ if ((file=std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); ++ } while (file); ++ save(filename_tmp); ++ cimg_snprintf(command,command._width,"%s -c \"%s\" > \"%s\"", ++ cimg::gzip_path(), ++ CImg::string(filename_tmp)._system_strescape().data(), ++ CImg::string(filename)._system_strescape().data()); ++ cimg::system(command); ++ file = std_fopen(filename,"rb"); ++ if (!file) ++ throw CImgIOException(_cimg_instance ++ "save_gzip_external(): Failed to save file '%s' with external command 'gzip'.", ++ cimg_instance, ++ filename); ++ ++ else cimg::fclose(file); ++ std::remove(filename_tmp); ++ return *this; ++ } ++ ++ //! Save image using GraphicsMagick's external binary. ++ /** ++ \param filename Filename, as a C-string. ++ \param quality Image quality (expressed in percent), when the file format supports it. ++ \note This method uses \c gm, an external executable binary provided by ++ GraphicsMagick. ++ It must be installed for the method to succeed. ++ **/ ++ const CImg& save_graphicsmagick_external(const char *const filename, const unsigned int quality=100) const { ++ if (!filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_graphicsmagick_external(): Specified filename is (null).", ++ cimg_instance); ++ if (is_empty()) { cimg::fempty(0,filename); return *this; } ++ if (_depth>1) ++ cimg::warn(_cimg_instance ++ "save_other(): File '%s', saving a volumetric image with an external call to " ++ "GraphicsMagick only writes the first image slice.", ++ cimg_instance,filename); ++ ++#ifdef cimg_use_png ++#define _cimg_sge_ext1 "png" ++#define _cimg_sge_ext2 "png" ++#else ++#define _cimg_sge_ext1 "pgm" ++#define _cimg_sge_ext2 "ppm" ++#endif ++ CImg command(1024), filename_tmp(256); ++ std::FILE *file; ++ do { ++ cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", ++ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(), ++ _spectrum==1?_cimg_sge_ext1:_cimg_sge_ext2); ++ if ((file=std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); ++ } while (file); ++#ifdef cimg_use_png ++ save_png(filename_tmp); ++#else ++ save_pnm(filename_tmp); ++#endif ++ cimg_snprintf(command,command._width,"%s convert -quality %u \"%s\" \"%s\"", ++ cimg::graphicsmagick_path(),quality, ++ CImg::string(filename_tmp)._system_strescape().data(), ++ CImg::string(filename)._system_strescape().data()); ++ cimg::system(command); ++ file = std_fopen(filename,"rb"); ++ if (!file) ++ throw CImgIOException(_cimg_instance ++ "save_graphicsmagick_external(): Failed to save file '%s' with external command 'gm'.", ++ cimg_instance, ++ filename); ++ ++ if (file) cimg::fclose(file); ++ std::remove(filename_tmp); ++ return *this; ++ } ++ ++ //! Save image using ImageMagick's external binary. ++ /** ++ \param filename Filename, as a C-string. ++ \param quality Image quality (expressed in percent), when the file format supports it. ++ \note This method uses \c convert, an external executable binary provided by ++ ImageMagick. ++ It must be installed for the method to succeed. ++ **/ ++ const CImg& save_imagemagick_external(const char *const filename, const unsigned int quality=100) const { ++ if (!filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_imagemagick_external(): Specified filename is (null).", ++ cimg_instance); ++ if (is_empty()) { cimg::fempty(0,filename); return *this; } ++ if (_depth>1) ++ cimg::warn(_cimg_instance ++ "save_other(): File '%s', saving a volumetric image with an external call to " ++ "ImageMagick only writes the first image slice.", ++ cimg_instance,filename); ++#ifdef cimg_use_png ++#define _cimg_sie_ext1 "png" ++#define _cimg_sie_ext2 "png" ++#else ++#define _cimg_sie_ext1 "pgm" ++#define _cimg_sie_ext2 "ppm" ++#endif ++ CImg command(1024), filename_tmp(256); ++ std::FILE *file; ++ do { ++ cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s",cimg::temporary_path(), ++ cimg_file_separator,cimg::filenamerand(),_spectrum==1?_cimg_sie_ext1:_cimg_sie_ext2); ++ if ((file=std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); ++ } while (file); ++#ifdef cimg_use_png ++ save_png(filename_tmp); ++#else ++ save_pnm(filename_tmp); ++#endif ++ cimg_snprintf(command,command._width,"%s -quality %u \"%s\" \"%s\"", ++ cimg::imagemagick_path(),quality, ++ CImg::string(filename_tmp)._system_strescape().data(), ++ CImg::string(filename)._system_strescape().data()); ++ cimg::system(command); ++ file = std_fopen(filename,"rb"); ++ if (!file) ++ throw CImgIOException(_cimg_instance ++ "save_imagemagick_external(): Failed to save file '%s' with " ++ "external command 'magick/convert'.", ++ cimg_instance, ++ filename); ++ ++ if (file) cimg::fclose(file); ++ std::remove(filename_tmp); ++ return *this; ++ } ++ ++ //! Save image as a Dicom file. ++ /** ++ \param filename Filename, as a C-string. ++ \note This method uses \c medcon, an external executable binary provided by ++ (X)Medcon. ++ It must be installed for the method to succeed. ++ **/ ++ const CImg& save_medcon_external(const char *const filename) const { ++ if (!filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_medcon_external(): Specified filename is (null).", ++ cimg_instance); ++ if (is_empty()) { cimg::fempty(0,filename); return *this; } ++ ++ CImg command(1024), filename_tmp(256), body(256); ++ std::FILE *file; ++ do { ++ cimg_snprintf(filename_tmp,filename_tmp._width,"%s.hdr",cimg::filenamerand()); ++ if ((file=std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); ++ } while (file); ++ save_analyze(filename_tmp); ++ cimg_snprintf(command,command._width,"%s -w -c dicom -o \"%s\" -f \"%s\"", ++ cimg::medcon_path(), ++ CImg::string(filename)._system_strescape().data(), ++ CImg::string(filename_tmp)._system_strescape().data()); ++ cimg::system(command); ++ std::remove(filename_tmp); ++ cimg::split_filename(filename_tmp,body); ++ cimg_snprintf(filename_tmp,filename_tmp._width,"%s.img",body._data); ++ std::remove(filename_tmp); ++ ++ file = std_fopen(filename,"rb"); ++ if (!file) { ++ cimg_snprintf(command,command._width,"m000-%s",filename); ++ file = std_fopen(command,"rb"); ++ if (!file) { ++ cimg::fclose(cimg::fopen(filename,"r")); ++ throw CImgIOException(_cimg_instance ++ "save_medcon_external(): Failed to save file '%s' with external command 'medcon'.", ++ cimg_instance, ++ filename); ++ } ++ } ++ cimg::fclose(file); ++ std::rename(command,filename); ++ return *this; ++ } ++ ++ // Save image for non natively supported formats. ++ /** ++ \param filename Filename, as a C-string. ++ \param quality Image quality (expressed in percent), when the file format supports it. ++ \note ++ - The filename extension tells about the desired file format. ++ - This method tries to save the instance image as a file, using external tools from ++ ImageMagick or ++ GraphicsMagick. ++ At least one of these tool must be installed for the method to succeed. ++ - It is recommended to use the generic method save(const char*, int) const instead, ++ as it can handle some file formats natively. ++ **/ ++ const CImg& save_other(const char *const filename, const unsigned int quality=100) const { ++ if (!filename) ++ throw CImgArgumentException(_cimg_instance ++ "save_other(): Specified filename is (null).", ++ cimg_instance); ++ if (is_empty()) { cimg::fempty(0,filename); return *this; } ++ if (_depth>1) ++ cimg::warn(_cimg_instance ++ "save_other(): File '%s', saving a volumetric image with an external call to " ++ "ImageMagick or GraphicsMagick only writes the first image slice.", ++ cimg_instance,filename); ++ ++ const unsigned int omode = 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(omode); ++ if (!is_saved) ++ throw CImgIOException(_cimg_instance ++ "save_other(): Failed to save file '%s'. Format is not natively supported, " ++ "and no external commands succeeded.", ++ cimg_instance, ++ filename); ++ return *this; ++ } ++ ++ //! Serialize a CImg instance into a raw CImg buffer. ++ /** ++ \param is_compressed tells if zlib compression must be used for serialization ++ (this requires 'cimg_use_zlib' been enabled). ++ **/ ++ CImg get_serialize(const bool is_compressed=false) const { ++ return CImgList(*this,true).get_serialize(is_compressed); ++ } ++ ++ // [internal] Return a 40x38 color logo of a 'danger' item. ++ static CImg _logo40x38() { ++ CImg res(40,38,1,3); ++ const unsigned char *ptrs = cimg::logo40x38; ++ T *ptr1 = res.data(0,0,0,0), *ptr2 = res.data(0,0,0,1), *ptr3 = res.data(0,0,0,2); ++ for (ulongT off = 0; off<(ulongT)res._width*res._height;) { ++ const unsigned char n = *(ptrs++), r = *(ptrs++), g = *(ptrs++), b = *(ptrs++); ++ for (unsigned int l = 0; l structure ++ # ++ # ++ # ++ #------------------------------------------ ++ */ ++ //! Represent a list of images CImg. ++ template ++ struct CImgList { ++ unsigned int _width, _allocated_width; ++ CImg *_data; ++ ++ //! Simple iterator type, to loop through each image of a list. ++ /** ++ \note ++ - The \c CImgList::iterator type is defined as a CImg*. ++ - You may use it like this: ++ \code ++ CImgList<> list; // Assuming this image list is not empty. ++ for (CImgList<>::iterator it = list.begin(); it* iterator; ++ ++ //! Simple const iterator type, to loop through each image of a \c const list instance. ++ /** ++ \note ++ - The \c CImgList::const_iterator type is defined to be a const CImg*. ++ - Similar to CImgList::iterator, but for constant list instances. ++ **/ ++ typedef const CImg* const_iterator; ++ ++ //! Pixel value type. ++ /** ++ Refer to the pixels value type of the images in the list. ++ \note ++ - The \c CImgList::value_type type of a \c CImgList is defined to be a \c T. ++ It is then similar to CImg::value_type. ++ - \c CImgList::value_type is actually not used in %CImg methods. It has been mainly defined for ++ compatibility with STL naming conventions. ++ **/ ++ typedef T value_type; ++ ++ // Define common types related to template type T. ++ 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 uint64T; ++ typedef typename cimg::last::type int64T; ++ 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 / Instance Management ++ //@{ ++ //-------------------------------------------------------- ++ ++ //! Destructor. ++ /** ++ Destroy current list instance. ++ \note ++ - Any allocated buffer is deallocated. ++ - Destroying an empty list does nothing actually. ++ **/ ++ ~CImgList() { ++ delete[] _data; ++ } ++ ++ //! Default constructor. ++ /** ++ Construct a new empty list instance. ++ \note ++ - An empty list has no pixel data and its dimension width() is set to \c 0, as well as its ++ image buffer pointer data(). ++ - An empty list may be reassigned afterwards, with the family of the assign() methods. ++ In all cases, the type of pixels stays \c T. ++ **/ ++ CImgList(): ++ _width(0),_allocated_width(0),_data(0) {} ++ ++ //! Construct list containing empty images. ++ /** ++ \param n Number of empty images. ++ \note Useful when you know by advance the number of images you want to manage, as ++ it will allocate the right amount of memory for the list, without needs for reallocation ++ (that may occur when starting from an empty list and inserting several images in it). ++ **/ ++ explicit CImgList(const unsigned int n):_width(n) { ++ if (n) _data = new CImg[_allocated_width = std::max(16U,(unsigned int)cimg::nearest_pow2(n))]; ++ else { _allocated_width = 0; _data = 0; } ++ } ++ ++ //! Construct list containing images of specified size. ++ /** ++ \param n Number of images. ++ \param width Width of images. ++ \param height Height of images. ++ \param depth Depth of images. ++ \param spectrum Number of channels of images. ++ \note Pixel values are not initialized and may probably contain garbage. ++ **/ ++ CImgList(const unsigned int n, const unsigned int width, const unsigned int height=1, ++ const unsigned int depth=1, const unsigned int spectrum=1): ++ _width(0),_allocated_width(0),_data(0) { ++ assign(n); ++ cimglist_apply(*this,assign)(width,height,depth,spectrum); ++ } ++ ++ //! Construct list containing images of specified size, and initialize pixel values. ++ /** ++ \param n Number of images. ++ \param width Width of images. ++ \param height Height of images. ++ \param depth Depth of images. ++ \param spectrum Number of channels of images. ++ \param val Initialization value for images pixels. ++ **/ ++ CImgList(const unsigned int n, const unsigned int width, const unsigned int height, ++ const unsigned int depth, const unsigned int spectrum, const T& val): ++ _width(0),_allocated_width(0),_data(0) { ++ assign(n); ++ cimglist_apply(*this,assign)(width,height,depth,spectrum,val); ++ } ++ ++ //! Construct list containing images of specified size, and initialize pixel values from a sequence of integers. ++ /** ++ \param n Number of images. ++ \param width Width of images. ++ \param height Height of images. ++ \param depth Depth of images. ++ \param spectrum Number of channels of images. ++ \param val0 First value of the initializing integers sequence. ++ \param val1 Second value of the initializing integers sequence. ++ \warning You must specify at least width*height*depth*spectrum values in your argument list, ++ or you will probably segfault. ++ **/ ++ CImgList(const unsigned int n, const unsigned int width, const unsigned int height, ++ const unsigned int depth, const unsigned int spectrum, const int val0, const int val1, ...): ++ _width(0),_allocated_width(0),_data(0) { ++#define _CImgList_stdarg(t) { \ ++ assign(n,width,height,depth,spectrum); \ ++ const ulongT siz = (ulongT)width*height*depth*spectrum, nsiz = siz*n; \ ++ T *ptrd = _data->_data; \ ++ va_list ap; \ ++ va_start(ap,val1); \ ++ for (ulongT l = 0, s = 0, i = 0; iwidth*height*depth*spectrum values in your argument list, ++ or you will probably segfault. ++ **/ ++ CImgList(const unsigned int n, const unsigned int width, const unsigned int height, ++ const unsigned int depth, const unsigned int spectrum, const double val0, const double val1, ...): ++ _width(0),_allocated_width(0),_data(0) { ++ _CImgList_stdarg(double); ++ } ++ ++ //! Construct list containing copies of an input image. ++ /** ++ \param n Number of images. ++ \param img Input image to copy in the constructed list. ++ \param is_shared Tells if the elements of the list are shared or non-shared copies of \c img. ++ **/ ++ template ++ CImgList(const unsigned int n, const CImg& img, const bool is_shared=false): ++ _width(0),_allocated_width(0),_data(0) { ++ assign(n); ++ cimglist_apply(*this,assign)(img,is_shared); ++ } ++ ++ //! Construct list from one image. ++ /** ++ \param img Input image to copy in the constructed list. ++ \param is_shared Tells if the element of the list is a shared or non-shared copy of \c img. ++ **/ ++ template ++ explicit CImgList(const CImg& img, const bool is_shared=false): ++ _width(0),_allocated_width(0),_data(0) { ++ assign(1); ++ _data[0].assign(img,is_shared); ++ } ++ ++ //! Construct list from two images. ++ /** ++ \param img1 First input image to copy in the constructed list. ++ \param img2 Second input image to copy in the constructed list. ++ \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. ++ **/ ++ template ++ CImgList(const CImg& img1, const CImg& img2, const bool is_shared=false): ++ _width(0),_allocated_width(0),_data(0) { ++ assign(2); ++ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); ++ } ++ ++ //! Construct list from three images. ++ /** ++ \param img1 First input image to copy in the constructed list. ++ \param img2 Second input image to copy in the constructed list. ++ \param img3 Third input image to copy in the constructed list. ++ \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. ++ **/ ++ template ++ CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const bool is_shared=false): ++ _width(0),_allocated_width(0),_data(0) { ++ assign(3); ++ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); ++ } ++ ++ //! Construct list from four images. ++ /** ++ \param img1 First input image to copy in the constructed list. ++ \param img2 Second input image to copy in the constructed list. ++ \param img3 Third input image to copy in the constructed list. ++ \param img4 Fourth input image to copy in the constructed list. ++ \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. ++ **/ ++ template ++ CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, ++ const bool is_shared=false): ++ _width(0),_allocated_width(0),_data(0) { ++ assign(4); ++ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); ++ _data[3].assign(img4,is_shared); ++ } ++ ++ //! Construct list from five images. ++ /** ++ \param img1 First input image to copy in the constructed list. ++ \param img2 Second input image to copy in the constructed list. ++ \param img3 Third input image to copy in the constructed list. ++ \param img4 Fourth input image to copy in the constructed list. ++ \param img5 Fifth input image to copy in the constructed list. ++ \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. ++ **/ ++ template ++ CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, ++ const CImg& img5, const bool is_shared=false): ++ _width(0),_allocated_width(0),_data(0) { ++ assign(5); ++ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); ++ _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); ++ } ++ ++ //! Construct list from six images. ++ /** ++ \param img1 First input image to copy in the constructed list. ++ \param img2 Second input image to copy in the constructed list. ++ \param img3 Third input image to copy in the constructed list. ++ \param img4 Fourth input image to copy in the constructed list. ++ \param img5 Fifth input image to copy in the constructed list. ++ \param img6 Sixth input image to copy in the constructed list. ++ \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. ++ **/ ++ template ++ CImgList(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, ++ const CImg& img5, const CImg& img6, const bool is_shared=false): ++ _width(0),_allocated_width(0),_data(0) { ++ assign(6); ++ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); ++ _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); ++ } ++ ++ //! Construct list from seven images. ++ /** ++ \param img1 First input image to copy in the constructed list. ++ \param img2 Second input image to copy in the constructed list. ++ \param img3 Third input image to copy in the constructed list. ++ \param img4 Fourth input image to copy in the constructed list. ++ \param img5 Fifth input image to copy in the constructed list. ++ \param img6 Sixth input image to copy in the constructed list. ++ \param img7 Seventh input image to copy in the constructed list. ++ \param is_shared Tells if the elements of the list are shared or non-shared copies of input 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 bool is_shared=false): ++ _width(0),_allocated_width(0),_data(0) { ++ assign(7); ++ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); ++ _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); ++ _data[6].assign(img7,is_shared); ++ } ++ ++ //! Construct list from eight images. ++ /** ++ \param img1 First input image to copy in the constructed list. ++ \param img2 Second input image to copy in the constructed list. ++ \param img3 Third input image to copy in the constructed list. ++ \param img4 Fourth input image to copy in the constructed list. ++ \param img5 Fifth input image to copy in the constructed list. ++ \param img6 Sixth input image to copy in the constructed list. ++ \param img7 Seventh input image to copy in the constructed list. ++ \param img8 Eighth input image to copy in the constructed list. ++ \param is_shared Tells if the elements of the list are shared or non-shared copies of input 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, ++ const bool is_shared=false): ++ _width(0),_allocated_width(0),_data(0) { ++ assign(8); ++ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); ++ _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); ++ _data[6].assign(img7,is_shared); _data[7].assign(img8,is_shared); ++ } ++ ++ //! Construct list copy. ++ /** ++ \param list Input list to copy. ++ \note The shared state of each element of the constructed list is kept the same as in \c list. ++ **/ ++ template ++ CImgList(const CImgList& list):_width(0),_allocated_width(0),_data(0) { ++ assign(list._width); ++ cimglist_for(*this,l) _data[l].assign(list[l],false); ++ } ++ ++ //! Construct list copy \specialization. ++ CImgList(const CImgList& list):_width(0),_allocated_width(0),_data(0) { ++ assign(list._width); ++ cimglist_for(*this,l) _data[l].assign(list[l],list[l]._is_shared); ++ } ++ ++ //! Construct list copy, and force the shared state of the list elements. ++ /** ++ \param list Input list to copy. ++ \param is_shared Tells if the elements of the list are shared or non-shared copies of input images. ++ **/ ++ template ++ CImgList(const CImgList& list, const bool is_shared):_width(0),_allocated_width(0),_data(0) { ++ assign(list._width); ++ cimglist_for(*this,l) _data[l].assign(list[l],is_shared); ++ } ++ ++ //! Construct list by reading the content of a file. ++ /** ++ \param filename Filename, as a C-string. ++ **/ ++ explicit CImgList(const char *const filename):_width(0),_allocated_width(0),_data(0) { ++ assign(filename); ++ } ++ ++ //! Construct list from the content of a display window. ++ /** ++ \param disp Display window to get content from. ++ \note Constructed list contains a single image only. ++ **/ ++ explicit CImgList(const CImgDisplay& disp):_width(0),_allocated_width(0),_data(0) { ++ assign(disp); ++ } ++ ++ //! Return a list with elements being shared copies of images in the list instance. ++ /** ++ \note list2 = list1.get_shared() is equivalent to list2.assign(list1,true). ++ **/ ++ CImgList get_shared() { ++ CImgList res(_width); ++ cimglist_for(*this,l) res[l].assign(_data[l],true); ++ return res; ++ } ++ ++ //! Return a list with elements being shared copies of images in the list instance \const. ++ const CImgList get_shared() const { ++ CImgList res(_width); ++ cimglist_for(*this,l) res[l].assign(_data[l],true); ++ return res; ++ } ++ ++ //! Destructor \inplace. ++ /** ++ \see CImgList(). ++ **/ ++ CImgList& assign() { ++ delete[] _data; ++ _width = _allocated_width = 0; ++ _data = 0; ++ return *this; ++ } ++ ++ //! Destructor \inplace. ++ /** ++ Equivalent to assign(). ++ \note Only here for compatibility with STL naming conventions. ++ **/ ++ CImgList& clear() { ++ return assign(); ++ } ++ ++ //! Construct list containing empty images \inplace. ++ /** ++ \see CImgList(unsigned int). ++ **/ ++ CImgList& assign(const unsigned int n) { ++ if (!n) return assign(); ++ if (_allocated_width(n<<2)) { ++ delete[] _data; ++ _data = new CImg[_allocated_width = std::max(16U,(unsigned int)cimg::nearest_pow2(n))]; ++ } ++ _width = n; ++ return *this; ++ } ++ ++ //! Construct list containing images of specified size \inplace. ++ /** ++ \see CImgList(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int). ++ **/ ++ CImgList& assign(const unsigned int n, const unsigned int width, const unsigned int height=1, ++ const unsigned int depth=1, const unsigned int spectrum=1) { ++ assign(n); ++ cimglist_apply(*this,assign)(width,height,depth,spectrum); ++ return *this; ++ } ++ ++ //! Construct list containing images of specified size, and initialize pixel values \inplace. ++ /** ++ \see CImgList(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, const T). ++ **/ ++ CImgList& assign(const unsigned int n, const unsigned int width, const unsigned int height, ++ const unsigned int depth, const unsigned int spectrum, const T& val) { ++ assign(n); ++ cimglist_apply(*this,assign)(width,height,depth,spectrum,val); ++ return *this; ++ } ++ ++ //! Construct list with images of specified size, and initialize pixel values from a sequence of integers \inplace. ++ /** ++ \see CImgList(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, const int, const int, ...). ++ **/ ++ CImgList& assign(const unsigned int n, const unsigned int width, const unsigned int height, ++ const unsigned int depth, const unsigned int spectrum, const int val0, const int val1, ...) { ++ _CImgList_stdarg(int); ++ return *this; ++ } ++ ++ //! Construct list with images of specified size, and initialize pixel values from a sequence of doubles \inplace. ++ /** ++ \see CImgList(unsigned int,unsigned int,unsigned int,unsigned int,unsigned int,const double,const double,...). ++ **/ ++ CImgList& assign(const unsigned int n, const unsigned int width, const unsigned int height, ++ const unsigned int depth, const unsigned int spectrum, ++ const double val0, const double val1, ...) { ++ _CImgList_stdarg(double); ++ return *this; ++ } ++ ++ //! Construct list containing copies of an input image \inplace. ++ /** ++ \see CImgList(unsigned int, const CImg&, bool). ++ **/ ++ template ++ CImgList& assign(const unsigned int n, const CImg& img, const bool is_shared=false) { ++ assign(n); ++ cimglist_apply(*this,assign)(img,is_shared); ++ return *this; ++ } ++ ++ //! Construct list from one image \inplace. ++ /** ++ \see CImgList(const CImg&, bool). ++ **/ ++ template ++ CImgList& assign(const CImg& img, const bool is_shared=false) { ++ assign(1); ++ _data[0].assign(img,is_shared); ++ return *this; ++ } ++ ++ //! Construct list from two images \inplace. ++ /** ++ \see CImgList(const CImg&, const CImg&, bool). ++ **/ ++ template ++ CImgList& assign(const CImg& img1, const CImg& img2, const bool is_shared=false) { ++ assign(2); ++ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); ++ return *this; ++ } ++ ++ //! Construct list from three images \inplace. ++ /** ++ \see CImgList(const CImg&, const CImg&, const CImg&, bool). ++ **/ ++ template ++ CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const bool is_shared=false) { ++ assign(3); ++ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); ++ return *this; ++ } ++ ++ //! Construct list from four images \inplace. ++ /** ++ \see CImgList(const CImg&, const CImg&, const CImg&, const CImg&, bool). ++ **/ ++ template ++ CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, ++ const bool is_shared=false) { ++ assign(4); ++ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); ++ _data[3].assign(img4,is_shared); ++ return *this; ++ } ++ ++ //! Construct list from five images \inplace. ++ /** ++ \see CImgList(const CImg&, const CImg&, const CImg&, const CImg&, const CImg&, bool). ++ **/ ++ template ++ CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, ++ const CImg& img5, const bool is_shared=false) { ++ assign(5); ++ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); ++ _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); ++ return *this; ++ } ++ ++ //! Construct list from six images \inplace. ++ /** ++ \see CImgList(const CImg&,const CImg&,const CImg&,const CImg&,const CImg&,const CImg&, bool). ++ **/ ++ template ++ CImgList& assign(const CImg& img1, const CImg& img2, const CImg& img3, const CImg& img4, ++ const CImg& img5, const CImg& img6, const bool is_shared=false) { ++ assign(6); ++ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); ++ _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); ++ return *this; ++ } ++ ++ //! Construct list from seven images \inplace. ++ /** ++ \see CImgList(const CImg&,const CImg&,const CImg&,const CImg&,const CImg&,const CImg&, ++ const CImg&, bool). ++ **/ ++ 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 is_shared=false) { ++ assign(7); ++ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); ++ _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); ++ _data[6].assign(img7,is_shared); ++ return *this; ++ } ++ ++ //! Construct list from eight images \inplace. ++ /** ++ \see CImgList(const CImg&,const CImg&,const CImg&,const CImg&,const CImg&,const CImg&, ++ const CImg&, const CImg&, bool). ++ **/ ++ 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 is_shared=false) { ++ assign(8); ++ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared); ++ _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared); ++ _data[6].assign(img7,is_shared); _data[7].assign(img8,is_shared); ++ return *this; ++ } ++ ++ //! Construct list as a copy of an existing list and force the shared state of the list elements \inplace. ++ /** ++ \see CImgList(const CImgList&, bool is_shared). ++ **/ ++ template ++ CImgList& assign(const CImgList& list, const bool is_shared=false) { ++ cimg::unused(is_shared); ++ assign(list._width); ++ cimglist_for(*this,l) _data[l].assign(list[l],false); ++ return *this; ++ } ++ ++ //! Construct list as a copy of an existing list and force shared state of elements \inplace \specialization. ++ CImgList& assign(const CImgList& list, const bool is_shared=false) { ++ if (this==&list) return *this; ++ CImgList res(list._width); ++ cimglist_for(res,l) res[l].assign(list[l],is_shared); ++ return res.move_to(*this); ++ } ++ ++ //! Construct list by reading the content of a file \inplace. ++ /** ++ \see CImgList(const char *const). ++ **/ ++ CImgList& assign(const char *const filename) { ++ return load(filename); ++ } ++ ++ //! Construct list from the content of a display window \inplace. ++ /** ++ \see CImgList(const CImgDisplay&). ++ **/ ++ CImgList& assign(const CImgDisplay &disp) { ++ return assign(CImg(disp)); ++ } ++ ++ //! Transfer the content of the list instance to another list. ++ /** ++ \param list Destination list. ++ \note When returning, the current list instance is empty and the initial content of \c list is destroyed. ++ **/ ++ template ++ CImgList& move_to(CImgList& list) { ++ list.assign(_width); ++ bool is_one_shared_element = false; ++ cimglist_for(*this,l) is_one_shared_element|=_data[l]._is_shared; ++ if (is_one_shared_element) cimglist_for(*this,l) list[l].assign(_data[l]); ++ else cimglist_for(*this,l) _data[l].move_to(list[l]); ++ assign(); ++ return list; ++ } ++ ++ //! Transfer the content of the list instance at a specified position in another list. ++ /** ++ \param list Destination list. ++ \param pos Index of the insertion in the list. ++ \note When returning, the list instance is empty and the initial content of \c list is preserved ++ (only images indexes may be modified). ++ **/ ++ template ++ CImgList& move_to(CImgList& list, const unsigned int pos) { ++ if (is_empty()) return list; ++ const unsigned int npos = pos>list._width?list._width:pos; ++ list.insert(_width,npos); ++ bool is_one_shared_element = false; ++ cimglist_for(*this,l) is_one_shared_element|=_data[l]._is_shared; ++ if (is_one_shared_element) cimglist_for(*this,l) list[npos + l].assign(_data[l]); ++ else cimglist_for(*this,l) _data[l].move_to(list[npos + l]); ++ assign(); ++ return list; ++ } ++ ++ //! Swap all fields between two list instances. ++ /** ++ \param list List to swap fields with. ++ \note Can be used to exchange the content of two lists in a fast way. ++ **/ ++ CImgList& swap(CImgList& list) { ++ cimg::swap(_width,list._width,_allocated_width,list._allocated_width); ++ cimg::swap(_data,list._data); ++ return list; ++ } ++ ++ //! Return a reference to an empty list. ++ /** ++ \note Can be used to define default values in a function taking a CImgList as an argument. ++ \code ++ void f(const CImgList& list=CImgList::empty()); ++ \endcode ++ **/ ++ static CImgList& empty() { ++ static CImgList _empty; ++ return _empty.assign(); ++ } ++ ++ //! Return a reference to an empty list \const. ++ static const CImgList& const_empty() { ++ static const CImgList _empty; ++ return _empty; ++ } ++ ++ //@} ++ //------------------------------------------ ++ // ++ //! \name Overloaded Operators ++ //@{ ++ //------------------------------------------ ++ ++ //! Return a reference to one image element of the list. ++ /** ++ \param pos Indice of the image element. ++ **/ ++ CImg& operator()(const unsigned int pos) { ++#if cimg_verbosity>=3 ++ if (pos>=_width) { ++ cimg::warn(_cimglist_instance ++ "operator(): Invalid image request, at position [%u].", ++ cimglist_instance, ++ pos); ++ return *_data; ++ } ++#endif ++ return _data[pos]; ++ } ++ ++ //! Return a reference to one image of the list. ++ /** ++ \param pos Indice of the image element. ++ **/ ++ const CImg& operator()(const unsigned int pos) const { ++ return const_cast*>(this)->operator()(pos); ++ } ++ ++ //! Return a reference to one pixel value of one image of the list. ++ /** ++ \param pos Indice of the image element. ++ \param x X-coordinate of the pixel value. ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ \param c C-coordinate of the pixel value. ++ \note list(n,x,y,z,c) is equivalent to list[n](x,y,z,c). ++ **/ ++ T& operator()(const unsigned int pos, const unsigned int x, const unsigned int y=0, ++ const unsigned int z=0, const unsigned int c=0) { ++ return (*this)[pos](x,y,z,c); ++ } ++ ++ //! Return a reference to one pixel value of one image of the list \const. ++ const T& operator()(const unsigned int pos, const unsigned int x, const unsigned int y=0, ++ const unsigned int z=0, const unsigned int c=0) const { ++ return (*this)[pos](x,y,z,c); ++ } ++ ++ //! Return pointer to the first image of the list. ++ /** ++ \note Images in a list are stored as a buffer of \c CImg. ++ **/ ++ operator CImg*() { ++ return _data; ++ } ++ ++ //! Return pointer to the first image of the list \const. ++ operator const CImg*() const { ++ return _data; ++ } ++ ++ //! Construct list from one image \inplace. ++ /** ++ \param img Input image to copy in the constructed list. ++ \note list = img; is equivalent to list.assign(img);. ++ **/ ++ template ++ CImgList& operator=(const CImg& img) { ++ return assign(img); ++ } ++ ++ //! Construct list from another list. ++ /** ++ \param list Input list to copy. ++ \note list1 = list2 is equivalent to list1.assign(list2);. ++ **/ ++ template ++ CImgList& operator=(const CImgList& list) { ++ return assign(list); ++ } ++ ++ //! Construct list from another list \specialization. ++ CImgList& operator=(const CImgList& list) { ++ return assign(list); ++ } ++ ++ //! Construct list by reading the content of a file \inplace. ++ /** ++ \see CImgList(const char *const). ++ **/ ++ CImgList& operator=(const char *const filename) { ++ return assign(filename); ++ } ++ ++ //! Construct list from the content of a display window \inplace. ++ /** ++ \see CImgList(const CImgDisplay&). ++ **/ ++ CImgList& operator=(const CImgDisplay& disp) { ++ return assign(disp); ++ } ++ ++ //! Return a non-shared copy of a list. ++ /** ++ \note +list is equivalent to CImgList(list,false). ++ It forces the copy to have non-shared elements. ++ **/ ++ CImgList operator+() const { ++ return CImgList(*this,false); ++ } ++ ++ //! Return a copy of the list instance, where image \c img has been inserted at the end. ++ /** ++ \param img Image inserted at the end of the instance copy. ++ \note Define a convenient way to create temporary lists of images, as in the following code: ++ \code ++ (img1,img2,img3,img4).display("My four images"); ++ \endcode ++ **/ ++ template ++ CImgList& operator,(const CImg& img) { ++ return insert(img); ++ } ++ ++ //! Return a copy of the list instance, where image \c img has been inserted at the end \const. ++ template ++ CImgList operator,(const CImg& img) const { ++ return (+*this).insert(img); ++ } ++ ++ //! Return a copy of the list instance, where all elements of input list \c list have been inserted at the end. ++ /** ++ \param list List inserted at the end of the instance copy. ++ **/ ++ template ++ CImgList& operator,(const CImgList& list) { ++ return insert(list); ++ } ++ ++ //! Return a copy of the list instance, where all elements of input \c list have been inserted at the end \const. ++ template ++ CImgList& operator,(const CImgList& list) const { ++ return (+*this).insert(list); ++ } ++ ++ //! Return image corresponding to the appending of all images of the instance list along specified axis. ++ /** ++ \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. ++ \note list>'x' is equivalent to list.get_append('x'). ++ **/ ++ CImg operator>(const char axis) const { ++ return get_append(axis,0); ++ } ++ ++ //! Return list corresponding to the splitting of all images of the instance list along specified axis. ++ /** ++ \param axis Axis used for image splitting. ++ \note list<'x' is equivalent to list.get_split('x'). ++ **/ ++ CImgList operator<(const char axis) const { ++ return get_split(axis); ++ } ++ ++ //@} ++ //------------------------------------- ++ // ++ //! \name Instance Characteristics ++ //@{ ++ //------------------------------------- ++ ++ //! Return the type of image pixel values as a C string. ++ /** ++ Return a \c char* string containing the usual type name of the image pixel values ++ (i.e. a stringified version of the template parameter \c T). ++ \note ++ - The returned string may contain spaces (as in \c "unsigned char"). ++ - If the pixel type \c T does not correspond to a registered type, the string "unknown" is returned. ++ **/ ++ static const char* pixel_type() { ++ return cimg::type::string(); ++ } ++ ++ //! Return the size of the list, i.e. the number of images contained in it. ++ /** ++ \note Similar to size() but returns result as a (signed) integer. ++ **/ ++ int width() const { ++ return (int)_width; ++ } ++ ++ //! Return the size of the list, i.e. the number of images contained in it. ++ /** ++ \note Similar to width() but returns result as an unsigned integer. ++ **/ ++ unsigned int size() const { ++ return _width; ++ } ++ ++ //! Return pointer to the first image of the list. ++ /** ++ \note Images in a list are stored as a buffer of \c CImg. ++ **/ ++ CImg *data() { ++ return _data; ++ } ++ ++ //! Return pointer to the first image of the list \const. ++ const CImg *data() const { ++ return _data; ++ } ++ ++ //! Return pointer to the pos-th image of the list. ++ /** ++ \param pos Indice of the image element to access. ++ \note list.data(n); is equivalent to list.data + n;. ++ **/ ++#if cimg_verbosity>=3 ++ CImg *data(const unsigned int pos) { ++ if (pos>=size()) ++ cimg::warn(_cimglist_instance ++ "data(): Invalid pointer request, at position [%u].", ++ cimglist_instance, ++ pos); ++ return _data + pos; ++ } ++ ++ const CImg *data(const unsigned int l) const { ++ return const_cast*>(this)->data(l); ++ } ++#else ++ CImg *data(const unsigned int l) { ++ return _data + l; ++ } ++ ++ //! Return pointer to the pos-th image of the list \const. ++ const CImg *data(const unsigned int l) const { ++ return _data + l; ++ } ++#endif ++ ++ //! Return iterator to the first image of the list. ++ /** ++ **/ ++ iterator begin() { ++ return _data; ++ } ++ ++ //! Return iterator to the first image of the list \const. ++ const_iterator begin() const { ++ return _data; ++ } ++ ++ //! Return iterator to one position after the last image of the list. ++ /** ++ **/ ++ iterator end() { ++ return _data + _width; ++ } ++ ++ //! Return iterator to one position after the last image of the list \const. ++ const_iterator end() const { ++ return _data + _width; ++ } ++ ++ //! Return reference to the first image of the list. ++ /** ++ **/ ++ CImg& front() { ++ return *_data; ++ } ++ ++ //! Return reference to the first image of the list \const. ++ const CImg& front() const { ++ return *_data; ++ } ++ ++ //! Return a reference to the last image of the list. ++ /** ++ **/ ++ const CImg& back() const { ++ return *(_data + _width - 1); ++ } ++ ++ //! Return a reference to the last image of the list \const. ++ CImg& back() { ++ return *(_data + _width - 1); ++ } ++ ++ //! Return pos-th image of the list. ++ /** ++ \param pos Indice of the image element to access. ++ **/ ++ CImg& at(const int pos) { ++ if (is_empty()) ++ throw CImgInstanceException(_cimglist_instance ++ "at(): Empty instance.", ++ cimglist_instance); ++ ++ return _data[cimg::cut(pos,0,width() - 1)]; ++ } ++ ++ //! Access to pixel value with Dirichlet boundary conditions. ++ /** ++ \param pos Indice of the image element to access. ++ \param x X-coordinate of the pixel value. ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ \param c C-coordinate of the pixel value. ++ \param out_value Default value returned if \c offset is outside image bounds. ++ \note list.atNXYZC(p,x,y,z,c); is equivalent to list[p].atXYZC(x,y,z,c);. ++ **/ ++ T& atNXYZC(const int pos, const int x, const int y, const int z, const int c, const T& out_value) { ++ return (pos<0 || pos>=(int)_width)?(cimg::temporary(out_value)=out_value):_data[pos].atXYZC(x,y,z,c,out_value); ++ } ++ ++ //! Access to pixel value with Dirichlet boundary conditions \const. ++ T atNXYZC(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const { ++ return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atXYZC(x,y,z,c,out_value); ++ } ++ ++ //! Access to pixel value with Neumann boundary conditions. ++ /** ++ \param pos Indice of the image element to access. ++ \param x X-coordinate of the pixel value. ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ \param c C-coordinate of the pixel value. ++ \note list.atNXYZC(p,x,y,z,c); is equivalent to list[p].atXYZC(x,y,z,c);. ++ **/ ++ T& atNXYZC(const int pos, const int x, const int y, const int z, const int c) { ++ if (is_empty()) ++ throw CImgInstanceException(_cimglist_instance ++ "atNXYZC(): Empty instance.", ++ cimglist_instance); ++ ++ return _atNXYZC(pos,x,y,z,c); ++ } ++ ++ //! Access to pixel value with Neumann boundary conditions \const. ++ T atNXYZC(const int pos, const int x, const int y, const int z, const int c) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimglist_instance ++ "atNXYZC(): Empty instance.", ++ cimglist_instance); ++ ++ return _atNXYZC(pos,x,y,z,c); ++ } ++ ++ T& _atNXYZC(const int pos, const int x, const int y, const int z, const int c) { ++ return _data[cimg::cut(pos,0,width() - 1)].atXYZC(x,y,z,c); ++ } ++ ++ T _atNXYZC(const int pos, const int x, const int y, const int z, const int c) const { ++ return _data[cimg::cut(pos,0,width() - 1)].atXYZC(x,y,z,c); ++ } ++ ++ //! Access pixel value with Dirichlet boundary conditions for the 3 coordinates (\c pos, \c x,\c y,\c z). ++ /** ++ \param pos Indice of the image element to access. ++ \param x X-coordinate of the pixel value. ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ \param c C-coordinate of the pixel value. ++ \param out_value Default value returned if \c offset is outside image bounds. ++ \note list.atNXYZ(p,x,y,z,c); is equivalent to list[p].atXYZ(x,y,z,c);. ++ **/ ++ T& atNXYZ(const int pos, const int x, const int y, const int z, const int c, const T& out_value) { ++ return (pos<0 || pos>=(int)_width)?(cimg::temporary(out_value)=out_value):_data[pos].atXYZ(x,y,z,c,out_value); ++ } ++ ++ //! Access pixel value with Dirichlet boundary conditions for the 3 coordinates (\c pos, \c x,\c y,\c z) \const. ++ T atNXYZ(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const { ++ return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atXYZ(x,y,z,c,out_value); ++ } ++ ++ //! Access to pixel value with Neumann boundary conditions for the 4 coordinates (\c pos, \c x,\c y,\c z). ++ /** ++ \param pos Indice of the image element to access. ++ \param x X-coordinate of the pixel value. ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ \param c C-coordinate of the pixel value. ++ \note list.atNXYZ(p,x,y,z,c); is equivalent to list[p].atXYZ(x,y,z,c);. ++ **/ ++ T& atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) { ++ if (is_empty()) ++ throw CImgInstanceException(_cimglist_instance ++ "atNXYZ(): Empty instance.", ++ cimglist_instance); ++ ++ return _atNXYZ(pos,x,y,z,c); ++ } ++ ++ //! Access to pixel value with Neumann boundary conditions for the 4 coordinates (\c pos, \c x,\c y,\c z) \const. ++ T atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimglist_instance ++ "atNXYZ(): Empty instance.", ++ cimglist_instance); ++ ++ return _atNXYZ(pos,x,y,z,c); ++ } ++ ++ T& _atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) { ++ return _data[cimg::cut(pos,0,width() - 1)].atXYZ(x,y,z,c); ++ } ++ ++ T _atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) const { ++ return _data[cimg::cut(pos,0,width() - 1)].atXYZ(x,y,z,c); ++ } ++ ++ //! Access to pixel value with Dirichlet boundary conditions for the 3 coordinates (\c pos, \c x,\c y). ++ /** ++ \param pos Indice of the image element to access. ++ \param x X-coordinate of the pixel value. ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ \param c C-coordinate of the pixel value. ++ \param out_value Default value returned if \c offset is outside image bounds. ++ \note list.atNXYZ(p,x,y,z,c); is equivalent to list[p].atXYZ(x,y,z,c);. ++ **/ ++ T& atNXY(const int pos, const int x, const int y, const int z, const int c, const T& out_value) { ++ return (pos<0 || pos>=(int)_width)?(cimg::temporary(out_value)=out_value):_data[pos].atXY(x,y,z,c,out_value); ++ } ++ ++ //! Access to pixel value with Dirichlet boundary conditions for the 3 coordinates (\c pos, \c x,\c y) \const. ++ T atNXY(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const { ++ return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atXY(x,y,z,c,out_value); ++ } ++ ++ //! Access to pixel value with Neumann boundary conditions for the 3 coordinates (\c pos, \c x,\c y). ++ /** ++ \param pos Indice of the image element to access. ++ \param x X-coordinate of the pixel value. ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ \param c C-coordinate of the pixel value. ++ \note list.atNXYZ(p,x,y,z,c); is equivalent to list[p].atXYZ(x,y,z,c);. ++ **/ ++ T& atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) { ++ if (is_empty()) ++ throw CImgInstanceException(_cimglist_instance ++ "atNXY(): Empty instance.", ++ cimglist_instance); ++ ++ return _atNXY(pos,x,y,z,c); ++ } ++ ++ //! Access to pixel value with Neumann boundary conditions for the 3 coordinates (\c pos, \c x,\c y) \const. ++ T atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimglist_instance ++ "atNXY(): Empty instance.", ++ cimglist_instance); ++ ++ return _atNXY(pos,x,y,z,c); ++ } ++ ++ T& _atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) { ++ return _data[cimg::cut(pos,0,width() - 1)].atXY(x,y,z,c); ++ } ++ ++ T _atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) const { ++ return _data[cimg::cut(pos,0,width() - 1)].atXY(x,y,z,c); ++ } ++ ++ //! Access to pixel value with Dirichlet boundary conditions for the 2 coordinates (\c pos,\c x). ++ /** ++ \param pos Indice of the image element to access. ++ \param x X-coordinate of the pixel value. ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ \param c C-coordinate of the pixel value. ++ \param out_value Default value returned if \c offset is outside image bounds. ++ \note list.atNXYZ(p,x,y,z,c); is equivalent to list[p].atXYZ(x,y,z,c);. ++ **/ ++ T& atNX(const int pos, const int x, const int y, const int z, const int c, const T& out_value) { ++ return (pos<0 || pos>=(int)_width)?(cimg::temporary(out_value)=out_value):_data[pos].atX(x,y,z,c,out_value); ++ } ++ ++ //! Access to pixel value with Dirichlet boundary conditions for the 2 coordinates (\c pos,\c x) \const. ++ T atNX(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const { ++ return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atX(x,y,z,c,out_value); ++ } ++ ++ //! Access to pixel value with Neumann boundary conditions for the 2 coordinates (\c pos, \c x). ++ /** ++ \param pos Indice of the image element to access. ++ \param x X-coordinate of the pixel value. ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ \param c C-coordinate of the pixel value. ++ \note list.atNXYZ(p,x,y,z,c); is equivalent to list[p].atXYZ(x,y,z,c);. ++ **/ ++ T& atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) { ++ if (is_empty()) ++ throw CImgInstanceException(_cimglist_instance ++ "atNX(): Empty instance.", ++ cimglist_instance); ++ ++ return _atNX(pos,x,y,z,c); ++ } ++ ++ //! Access to pixel value with Neumann boundary conditions for the 2 coordinates (\c pos, \c x) \const. ++ T atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimglist_instance ++ "atNX(): Empty instance.", ++ cimglist_instance); ++ ++ return _atNX(pos,x,y,z,c); ++ } ++ ++ T& _atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) { ++ return _data[cimg::cut(pos,0,width() - 1)].atX(x,y,z,c); ++ } ++ ++ T _atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) const { ++ return _data[cimg::cut(pos,0,width() - 1)].atX(x,y,z,c); ++ } ++ ++ //! Access to pixel value with Dirichlet boundary conditions for the coordinate (\c pos). ++ /** ++ \param pos Indice of the image element to access. ++ \param x X-coordinate of the pixel value. ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ \param c C-coordinate of the pixel value. ++ \param out_value Default value returned if \c offset is outside image bounds. ++ \note list.atNXYZ(p,x,y,z,c); is equivalent to list[p].atXYZ(x,y,z,c);. ++ **/ ++ T& atN(const int pos, const int x, const int y, const int z, const int c, const T& out_value) { ++ return (pos<0 || pos>=(int)_width)?(cimg::temporary(out_value)=out_value):(*this)(pos,x,y,z,c); ++ } ++ ++ //! Access to pixel value with Dirichlet boundary conditions for the coordinate (\c pos) \const. ++ T atN(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const { ++ return (pos<0 || pos>=(int)_width)?out_value:(*this)(pos,x,y,z,c); ++ } ++ ++ //! Return pixel value with Neumann boundary conditions for the coordinate (\c pos). ++ /** ++ \param pos Indice of the image element to access. ++ \param x X-coordinate of the pixel value. ++ \param y Y-coordinate of the pixel value. ++ \param z Z-coordinate of the pixel value. ++ \param c C-coordinate of the pixel value. ++ \note list.atNXYZ(p,x,y,z,c); is equivalent to list[p].atXYZ(x,y,z,c);. ++ **/ ++ T& atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) { ++ if (is_empty()) ++ throw CImgInstanceException(_cimglist_instance ++ "atN(): Empty instance.", ++ cimglist_instance); ++ return _atN(pos,x,y,z,c); ++ } ++ ++ //! Return pixel value with Neumann boundary conditions for the coordinate (\c pos) \const. ++ T atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimglist_instance ++ "atN(): Empty instance.", ++ cimglist_instance); ++ return _atN(pos,x,y,z,c); ++ } ++ ++ T& _atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) { ++ return _data[cimg::cut(pos,0,width() - 1)](x,y,z,c); ++ } ++ ++ T _atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) const { ++ return _data[cimg::cut(pos,0,width() - 1)](x,y,z,c); ++ } ++ ++ //@} ++ //------------------------------------- ++ // ++ //! \name Instance Checking ++ //@{ ++ //------------------------------------- ++ ++ //! Return \c true if list is empty. ++ /** ++ **/ ++ bool is_empty() const { ++ return (!_data || !_width); ++ } ++ ++ //! Test if number of image elements is equal to specified value. ++ /** ++ \param size_n Number of image elements to test. ++ **/ ++ bool is_sameN(const unsigned int size_n) const { ++ return _width==size_n; ++ } ++ ++ //! Test if number of image elements is equal between two images lists. ++ /** ++ \param list Input list to compare with. ++ **/ ++ template ++ bool is_sameN(const CImgList& list) const { ++ return is_sameN(list._width); ++ } ++ ++ // Define useful functions to check list dimensions. ++ // (cannot be documented because 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<_width && res; ++l) res = _data[l].is_same##axis(val); return res; \ ++ } \ ++ bool is_sameN##axis(const unsigned int n, const unsigned int val) const { \ ++ return is_sameN(n) && is_same##axis(val); \ ++ } \ ++ ++#define _cimglist_def_is_same2(axis1,axis2) \ ++ bool is_same##axis1##axis2(const unsigned int val1, const unsigned int val2) const { \ ++ bool res = true; \ ++ for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_same##axis1##axis2(val1,val2); return res; \ ++ } \ ++ bool is_sameN##axis1##axis2(const unsigned int n, const unsigned int val1, const unsigned int val2) const { \ ++ return is_sameN(n) && is_same##axis1##axis2(val1,val2); \ ++ } \ ++ ++#define _cimglist_def_is_same3(axis1,axis2,axis3) \ ++ bool is_same##axis1##axis2##axis3(const unsigned int val1, const unsigned int val2, \ ++ const unsigned int val3) const { \ ++ bool res = true; \ ++ for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_same##axis1##axis2##axis3(val1,val2,val3); \ ++ return res; \ ++ } \ ++ bool is_sameN##axis1##axis2##axis3(const unsigned int n, const unsigned int val1, \ ++ const unsigned int val2, const unsigned int val3) const { \ ++ return is_sameN(n) && is_same##axis1##axis2##axis3(val1,val2,val3); \ ++ } \ ++ ++#define _cimglist_def_is_same(axis) \ ++ template bool is_same##axis(const CImg& img) const { \ ++ bool res = true; for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_same##axis(img); return res; \ ++ } \ ++ template bool is_same##axis(const CImgList& list) const { \ ++ const unsigned int lmin = std::min(_width,list._width); \ ++ 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(XC) ++ _cimglist_def_is_same(YZ) ++ _cimglist_def_is_same(YC) ++ _cimglist_def_is_same(XYZ) ++ _cimglist_def_is_same(XYC) ++ _cimglist_def_is_same(YZC) ++ _cimglist_def_is_same(XYZC) ++ _cimglist_def_is_same1(X) ++ _cimglist_def_is_same1(Y) ++ _cimglist_def_is_same1(Z) ++ _cimglist_def_is_same1(C) ++ _cimglist_def_is_same2(X,Y) ++ _cimglist_def_is_same2(X,Z) ++ _cimglist_def_is_same2(X,C) ++ _cimglist_def_is_same2(Y,Z) ++ _cimglist_def_is_same2(Y,C) ++ _cimglist_def_is_same2(Z,C) ++ _cimglist_def_is_same3(X,Y,Z) ++ _cimglist_def_is_same3(X,Y,C) ++ _cimglist_def_is_same3(X,Z,C) ++ _cimglist_def_is_same3(Y,Z,C) ++ ++ //! Test if dimensions of each image of the list match specified arguments. ++ /** ++ \param dx Checked image width. ++ \param dy Checked image height. ++ \param dz Checked image depth. ++ \param dc Checked image spectrum. ++ **/ ++ bool is_sameXYZC(const unsigned int dx, const unsigned int dy, ++ const unsigned int dz, const unsigned int dc) const { ++ bool res = true; ++ for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_sameXYZC(dx,dy,dz,dc); ++ return res; ++ } ++ ++ //! Test if list dimensions match specified arguments. ++ /** ++ \param n Number of images in the list. ++ \param dx Checked image width. ++ \param dy Checked image height. ++ \param dz Checked image depth. ++ \param dc Checked image spectrum. ++ **/ ++ bool is_sameNXYZC(const unsigned int n, ++ const unsigned int dx, const unsigned int dy, ++ const unsigned int dz, const unsigned int dc) const { ++ return is_sameN(n) && is_sameXYZC(dx,dy,dz,dc); ++ } ++ ++ //! Test if list contains one particular pixel location. ++ /** ++ \param n Index of the image whom checked pixel value belong to. ++ \param x X-coordinate of the checked pixel value. ++ \param y Y-coordinate of the checked pixel value. ++ \param z Z-coordinate of the checked pixel value. ++ \param c C-coordinate of the checked pixel value. ++ **/ ++ bool containsNXYZC(const int n, const int x=0, const int y=0, const int z=0, const int c=0) const { ++ if (is_empty()) return false; ++ return n>=0 && n<(int)_width && x>=0 && x<_data[n].width() && y>=0 && y<_data[n].height() && ++ z>=0 && z<_data[n].depth() && c>=0 && c<_data[n].spectrum(); ++ } ++ ++ //! Test if list contains image with specified indice. ++ /** ++ \param n Index of the checked image. ++ **/ ++ bool containsN(const int n) const { ++ if (is_empty()) return false; ++ return n>=0 && n<(int)_width; ++ } ++ ++ //! Test if one image of the list contains the specified referenced value. ++ /** ++ \param pixel Reference to pixel value to test. ++ \param[out] n Index of image containing the pixel value, if test succeeds. ++ \param[out] x X-coordinate of the pixel value, if test succeeds. ++ \param[out] y Y-coordinate of the pixel value, if test succeeds. ++ \param[out] z Z-coordinate of the pixel value, if test succeeds. ++ \param[out] c C-coordinate of the pixel value, if test succeeds. ++ \note If true, set coordinates (n,x,y,z,c). ++ **/ ++ template ++ bool contains(const T& pixel, t& n, t& x, t&y, t& z, t& c) const { ++ if (is_empty()) return false; ++ cimglist_for(*this,l) if (_data[l].contains(pixel,x,y,z,c)) { n = (t)l; return true; } ++ return false; ++ } ++ ++ //! Test if one of the image list contains the specified referenced value. ++ /** ++ \param pixel Reference to pixel value to test. ++ \param[out] n Index of image containing the pixel value, if test succeeds. ++ \param[out] x X-coordinate of the pixel value, if test succeeds. ++ \param[out] y Y-coordinate of the pixel value, if test succeeds. ++ \param[out] z Z-coordinate of the pixel value, if test succeeds. ++ \note If true, set coordinates (n,x,y,z). ++ **/ ++ template ++ bool contains(const T& pixel, t& n, t& x, t&y, t& z) const { ++ t c; ++ return contains(pixel,n,x,y,z,c); ++ } ++ ++ //! Test if one of the image list contains the specified referenced value. ++ /** ++ \param pixel Reference to pixel value to test. ++ \param[out] n Index of image containing the pixel value, if test succeeds. ++ \param[out] x X-coordinate of the pixel value, if test succeeds. ++ \param[out] y Y-coordinate of the pixel value, if test succeeds. ++ \note If true, set coordinates (n,x,y). ++ **/ ++ template ++ bool contains(const T& pixel, t& n, t& x, t&y) const { ++ t z, c; ++ return contains(pixel,n,x,y,z,c); ++ } ++ ++ //! Test if one of the image list contains the specified referenced value. ++ /** ++ \param pixel Reference to pixel value to test. ++ \param[out] n Index of image containing the pixel value, if test succeeds. ++ \param[out] x X-coordinate of the pixel value, if test succeeds. ++ \note If true, set coordinates (n,x). ++ **/ ++ template ++ bool contains(const T& pixel, t& n, t& x) const { ++ t y, z, c; ++ return contains(pixel,n,x,y,z,c); ++ } ++ ++ //! Test if one of the image list contains the specified referenced value. ++ /** ++ \param pixel Reference to pixel value to test. ++ \param[out] n Index of image containing the pixel value, if test succeeds. ++ \note If true, set coordinates (n). ++ **/ ++ template ++ bool contains(const T& pixel, t& n) const { ++ t x, y, z, c; ++ return contains(pixel,n,x,y,z,c); ++ } ++ ++ //! Test if one of the image list contains the specified referenced value. ++ /** ++ \param pixel Reference to pixel value to test. ++ **/ ++ bool contains(const T& pixel) const { ++ unsigned int n, x, y, z, c; ++ return contains(pixel,n,x,y,z,c); ++ } ++ ++ //! Test if the list contains the image 'img'. ++ /** ++ \param img Reference to image to test. ++ \param[out] n Index of image in the list, if test succeeds. ++ \note 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; ++ } ++ ++ //! Test if the list contains the image img. ++ /** ++ \param img Reference to image to test. ++ **/ ++ bool contains(const CImg& img) const { ++ unsigned int n; ++ return contains(img,n); ++ } ++ ++ //@} ++ //------------------------------------- ++ // ++ //! \name Mathematical Functions ++ //@{ ++ //------------------------------------- ++ ++ //! Return a reference to the minimum pixel value of the instance list. ++ /** ++ **/ ++ T& min() { ++ if (is_empty()) ++ throw CImgInstanceException(_cimglist_instance ++ "min(): Empty instance.", ++ cimglist_instance); ++ T *ptr_min = _data->_data; ++ T min_value = *ptr_min; ++ cimglist_for(*this,l) { ++ const CImg& img = _data[l]; ++ cimg_for(img,ptrs,T) if (*ptrs_data; ++ T min_value = *ptr_min; ++ cimglist_for(*this,l) { ++ const CImg& img = _data[l]; ++ cimg_for(img,ptrs,T) if (*ptrs_data; ++ T max_value = *ptr_max; ++ cimglist_for(*this,l) { ++ const CImg& img = _data[l]; ++ cimg_for(img,ptrs,T) if (*ptrs>max_value) max_value = *(ptr_max=ptrs); ++ } ++ return *ptr_max; ++ } ++ ++ //! Return a reference to the maximum pixel value of the instance list \const. ++ const T& max() const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimglist_instance ++ "max(): Empty instance.", ++ cimglist_instance); ++ const T *ptr_max = _data->_data; ++ T max_value = *ptr_max; ++ cimglist_for(*this,l) { ++ const CImg& img = _data[l]; ++ cimg_for(img,ptrs,T) if (*ptrs>max_value) max_value = *(ptr_max=ptrs); ++ } ++ return *ptr_max; ++ } ++ ++ //! Return a reference to the minimum pixel value of the instance list and return the maximum vvalue as well. ++ /** ++ \param[out] max_val Value of the maximum value found. ++ **/ ++ template ++ T& min_max(t& max_val) { ++ if (is_empty()) ++ throw CImgInstanceException(_cimglist_instance ++ "min_max(): Empty instance.", ++ cimglist_instance); ++ T *ptr_min = _data->_data; ++ T min_value = *ptr_min, max_value = min_value; ++ cimglist_for(*this,l) { ++ const CImg& img = _data[l]; ++ cimg_for(img,ptrs,T) { ++ const T val = *ptrs; ++ if (valmax_value) max_value = val; ++ } ++ } ++ max_val = (t)max_value; ++ return *ptr_min; ++ } ++ ++ //! Return a reference to the minimum pixel value of the instance list and return the maximum vvalue as well \const. ++ /** ++ \param[out] max_val Value of the maximum value found. ++ **/ ++ template ++ const T& min_max(t& max_val) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimglist_instance ++ "min_max(): Empty instance.", ++ cimglist_instance); ++ const T *ptr_min = _data->_data; ++ T min_value = *ptr_min, max_value = min_value; ++ cimglist_for(*this,l) { ++ const CImg& img = _data[l]; ++ cimg_for(img,ptrs,T) { ++ const T val = *ptrs; ++ if (valmax_value) max_value = val; ++ } ++ } ++ max_val = (t)max_value; ++ return *ptr_min; ++ } ++ ++ //! Return a reference to the minimum pixel value of the instance list and return the minimum value as well. ++ /** ++ \param[out] min_val Value of the minimum value found. ++ **/ ++ template ++ T& max_min(t& min_val) { ++ if (is_empty()) ++ throw CImgInstanceException(_cimglist_instance ++ "max_min(): Empty instance.", ++ cimglist_instance); ++ T *ptr_max = _data->_data; ++ T min_value = *ptr_max, max_value = min_value; ++ cimglist_for(*this,l) { ++ const CImg& img = _data[l]; ++ cimg_for(img,ptrs,T) { ++ const T val = *ptrs; ++ if (val>max_value) { max_value = val; ptr_max = ptrs; } ++ if (val ++ const T& max_min(t& min_val) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimglist_instance ++ "max_min(): Empty instance.", ++ cimglist_instance); ++ const T *ptr_max = _data->_data; ++ T min_value = *ptr_max, max_value = min_value; ++ cimglist_for(*this,l) { ++ const CImg& img = _data[l]; ++ cimg_for(img,ptrs,T) { ++ const T val = *ptrs; ++ if (val>max_value) { max_value = val; ptr_max = ptrs; } ++ if (val ++ CImgList& insert(const CImg& img, const unsigned int pos=~0U, const bool is_shared=false) { ++ const unsigned int npos = pos==~0U?_width:pos; ++ if (npos>_width) ++ throw CImgArgumentException(_cimglist_instance ++ "insert(): Invalid insertion request of specified image (%u,%u,%u,%u,%p) " ++ "at position %u.", ++ cimglist_instance, ++ img._width,img._height,img._depth,img._spectrum,img._data,npos); ++ if (is_shared) ++ throw CImgArgumentException(_cimglist_instance ++ "insert(): Invalid insertion request of specified shared image " ++ "CImg<%s>(%u,%u,%u,%u,%p) at position %u (pixel types are different).", ++ cimglist_instance, ++ img.pixel_type(),img._width,img._height,img._depth,img._spectrum,img._data,npos); ++ ++ CImg *const new_data = (++_width>_allocated_width)?new CImg[_allocated_width?(_allocated_width<<=1): ++ (_allocated_width=16)]:0; ++ if (!_data) { // Insert new element into empty list. ++ _data = new_data; ++ *_data = img; ++ } else { ++ if (new_data) { // Insert with re-allocation. ++ if (npos) std::memcpy(new_data,_data,sizeof(CImg)*npos); ++ if (npos!=_width - 1) std::memcpy(new_data + npos + 1,_data + npos,sizeof(CImg)*(_width - 1 - npos)); ++ std::memset(_data,0,sizeof(CImg)*(_width - 1)); ++ delete[] _data; ++ _data = new_data; ++ } else if (npos!=_width - 1) // Insert without re-allocation. ++ std::memmove(_data + npos + 1,_data + npos,sizeof(CImg)*(_width - 1 - npos)); ++ _data[npos]._width = _data[npos]._height = _data[npos]._depth = _data[npos]._spectrum = 0; ++ _data[npos]._data = 0; ++ _data[npos] = img; ++ } ++ return *this; ++ } ++ ++ //! Insert a copy of the image \c img into the current image list, at position \c pos \specialization. ++ CImgList& insert(const CImg& img, const unsigned int pos=~0U, const bool is_shared=false) { ++ const unsigned int npos = pos==~0U?_width:pos; ++ if (npos>_width) ++ throw CImgArgumentException(_cimglist_instance ++ "insert(): Invalid insertion request of specified image (%u,%u,%u,%u,%p) " ++ "at position %u.", ++ cimglist_instance, ++ img._width,img._height,img._depth,img._spectrum,img._data,npos); ++ CImg *const new_data = (++_width>_allocated_width)?new CImg[_allocated_width?(_allocated_width<<=1): ++ (_allocated_width=16)]:0; ++ if (!_data) { // Insert new element into empty list. ++ _data = new_data; ++ if (is_shared && img) { ++ _data->_width = img._width; ++ _data->_height = img._height; ++ _data->_depth = img._depth; ++ _data->_spectrum = img._spectrum; ++ _data->_is_shared = true; ++ _data->_data = img._data; ++ } else *_data = img; ++ } ++ else { ++ if (new_data) { // Insert with re-allocation. ++ if (npos) std::memcpy(new_data,_data,sizeof(CImg)*npos); ++ if (npos!=_width - 1) std::memcpy(new_data + npos + 1,_data + npos,sizeof(CImg)*(_width - 1 - npos)); ++ if (is_shared && img) { ++ new_data[npos]._width = img._width; ++ new_data[npos]._height = img._height; ++ new_data[npos]._depth = img._depth; ++ new_data[npos]._spectrum = img._spectrum; ++ 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]._spectrum = 0; ++ new_data[npos]._data = 0; ++ new_data[npos] = img; ++ } ++ std::memset(_data,0,sizeof(CImg)*(_width - 1)); ++ delete[] _data; ++ _data = new_data; ++ } else { // Insert without re-allocation. ++ if (npos!=_width - 1) std::memmove(_data + npos + 1,_data + npos,sizeof(CImg)*(_width - 1 - npos)); ++ if (is_shared && img) { ++ _data[npos]._width = img._width; ++ _data[npos]._height = img._height; ++ _data[npos]._depth = img._depth; ++ _data[npos]._spectrum = img._spectrum; ++ _data[npos]._is_shared = true; ++ _data[npos]._data = img._data; ++ } else { ++ _data[npos]._width = _data[npos]._height = _data[npos]._depth = _data[npos]._spectrum = 0; ++ _data[npos]._data = 0; ++ _data[npos] = img; ++ } ++ } ++ } ++ return *this; ++ } ++ ++ //! Insert a copy of the image \c img into the current image list, at position \c pos \newinstance. ++ template ++ CImgList get_insert(const CImg& img, const unsigned int pos=~0U, const bool is_shared=false) const { ++ return (+*this).insert(img,pos,is_shared); ++ } ++ ++ //! Insert n empty images img into the current image list, at position \p pos. ++ /** ++ \param n Number of empty images to insert. ++ \param pos Index of the insertion. ++ **/ ++ CImgList& insert(const unsigned int n, const unsigned int pos=~0U) { ++ CImg empty; ++ if (!n) return *this; ++ const unsigned int npos = pos==~0U?_width: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 \c n copies of the image \c img into the current image list, at position \c pos. ++ /** ++ \param n Number of image copies to insert. ++ \param img Image to insert by copy. ++ \param pos Index of the insertion. ++ \param is_shared Tells if inserted images are shared copies of \c img or not. ++ **/ ++ template ++ CImgList& insert(const unsigned int n, const CImg& img, const unsigned int pos=~0U, ++ const bool is_shared=false) { ++ if (!n) return *this; ++ const unsigned int npos = pos==~0U?_width:pos; ++ insert(img,npos,is_shared); ++ for (unsigned int i = 1; i ++ CImgList get_insert(const unsigned int n, const CImg& img, const unsigned int pos=~0U, ++ const bool is_shared=false) const { ++ return (+*this).insert(n,img,pos,is_shared); ++ } ++ ++ //! Insert a copy of the image list \c list into the current image list, starting from position \c pos. ++ /** ++ \param list Image list to insert. ++ \param pos Index of the insertion. ++ \param is_shared Tells if inserted images are shared copies of images of \c list or not. ++ **/ ++ template ++ CImgList& insert(const CImgList& list, const unsigned int pos=~0U, const bool is_shared=false) { ++ const unsigned int npos = pos==~0U?_width:pos; ++ if ((void*)this!=(void*)&list) cimglist_for(list,l) insert(list[l],npos + l,is_shared); ++ else insert(CImgList(list),npos,is_shared); ++ return *this; ++ } ++ ++ //! Insert a copy of the image list \c list into the current image list, starting from position \c pos \newinstance. ++ template ++ CImgList get_insert(const CImgList& list, const unsigned int pos=~0U, const bool is_shared=false) const { ++ return (+*this).insert(list,pos,is_shared); ++ } ++ ++ //! Insert n copies of the list \c list at position \c pos of the current list. ++ /** ++ \param n Number of list copies to insert. ++ \param list Image list to insert. ++ \param pos Index of the insertion. ++ \param is_shared Tells if inserted images are shared copies of images of \c list or not. ++ **/ ++ template ++ CImgList& insert(const unsigned int n, const CImgList& list, const unsigned int pos=~0U, ++ const bool is_shared=false) { ++ if (!n) return *this; ++ const unsigned int npos = pos==~0U?_width:pos; ++ for (unsigned int i = 0; i ++ CImgList get_insert(const unsigned int n, const CImgList& list, const unsigned int pos=~0U, ++ const bool is_shared=false) const { ++ return (+*this).insert(n,list,pos,is_shared); ++ } ++ ++ //! Remove all images between from indexes. ++ /** ++ \param pos1 Starting index of the removal. ++ \param pos2 Ending index of the removal. ++ **/ ++ CImgList& remove(const unsigned int pos1, const unsigned int pos2) { ++ const unsigned int ++ npos1 = pos1=_width) ++ throw CImgArgumentException(_cimglist_instance ++ "remove(): Invalid remove request at positions %u->%u.", ++ cimglist_instance, ++ npos1,tpos2); ++ else { ++ if (tpos2>=_width) ++ throw CImgArgumentException(_cimglist_instance ++ "remove(): Invalid remove request at positions %u->%u.", ++ cimglist_instance, ++ npos1,tpos2); ++ ++ for (unsigned int k = npos1; k<=npos2; ++k) _data[k].assign(); ++ const unsigned int nb = 1 + npos2 - npos1; ++ if (!(_width-=nb)) return assign(); ++ if (_width>(_allocated_width>>2) || _allocated_width<=16) { // Removing items without reallocation. ++ if (npos1!=_width) std::memmove(_data + npos1,_data + npos2 + 1,sizeof(CImg)*(_width - npos1)); ++ std::memset(_data + _width,0,sizeof(CImg)*nb); ++ } else { // Removing items with reallocation. ++ _allocated_width>>=2; ++ while (_allocated_width>16 && _width<(_allocated_width>>1)) _allocated_width>>=1; ++ CImg *const new_data = new CImg[_allocated_width]; ++ if (npos1) std::memcpy(new_data,_data,sizeof(CImg)*npos1); ++ if (npos1!=_width) std::memcpy(new_data + npos1,_data + npos2 + 1,sizeof(CImg)*(_width - npos1)); ++ if (_width!=_allocated_width) std::memset(new_data + _width,0,sizeof(CImg)*(_allocated_width - _width)); ++ std::memset(_data,0,sizeof(CImg)*(_width + nb)); ++ delete[] _data; ++ _data = new_data; ++ } ++ } ++ return *this; ++ } ++ ++ //! Remove all images between from indexes \newinstance. ++ CImgList get_remove(const unsigned int pos1, const unsigned int pos2) const { ++ return (+*this).remove(pos1,pos2); ++ } ++ ++ //! Remove image at index \c pos from the image list. ++ /** ++ \param pos Index of the image to remove. ++ **/ ++ CImgList& remove(const unsigned int pos) { ++ return remove(pos,pos); ++ } ++ ++ //! Remove image at index \c pos from the image list \newinstance. ++ CImgList get_remove(const unsigned int pos) const { ++ return (+*this).remove(pos); ++ } ++ ++ //! Remove last image. ++ /** ++ **/ ++ CImgList& remove() { ++ return remove(_width - 1); ++ } ++ ++ //! Remove last image \newinstance. ++ CImgList get_remove() const { ++ return (+*this).remove(); ++ } ++ ++ //! Reverse list order. ++ CImgList& reverse() { ++ for (unsigned int l = 0; l<_width/2; ++l) (*this)[l].swap((*this)[_width - 1 - l]); ++ return *this; ++ } ++ ++ //! Reverse list order \newinstance. ++ CImgList get_reverse() const { ++ return (+*this).reverse(); ++ } ++ ++ //! Return a sublist. ++ /** ++ \param pos0 Starting index of the sublist. ++ \param pos1 Ending index of the sublist. ++ **/ ++ CImgList& images(const unsigned int pos0, const unsigned int pos1) { ++ return get_images(pos0,pos1).move_to(*this); ++ } ++ ++ //! Return a sublist \newinstance. ++ CImgList get_images(const unsigned int pos0, const unsigned int pos1) const { ++ if (pos0>pos1 || pos1>=_width) ++ throw CImgArgumentException(_cimglist_instance ++ "images(): Specified sub-list indices (%u->%u) are out of bounds.", ++ cimglist_instance, ++ pos0,pos1); ++ CImgList res(pos1 - pos0 + 1); ++ cimglist_for(res,l) res[l].assign(_data[pos0 + l]); ++ return res; ++ } ++ ++ //! Return a shared sublist. ++ /** ++ \param pos0 Starting index of the sublist. ++ \param pos1 Ending index of the sublist. ++ **/ ++ CImgList get_shared_images(const unsigned int pos0, const unsigned int pos1) { ++ if (pos0>pos1 || pos1>=_width) ++ throw CImgArgumentException(_cimglist_instance ++ "get_shared_images(): Specified sub-list indices (%u->%u) are out of bounds.", ++ cimglist_instance, ++ pos0,pos1); ++ CImgList res(pos1 - pos0 + 1); ++ cimglist_for(res,l) res[l].assign(_data[pos0 + l],_data[pos0 + l]?true:false); ++ return res; ++ } ++ ++ //! Return a shared sublist \newinstance. ++ const CImgList get_shared_images(const unsigned int pos0, const unsigned int pos1) const { ++ if (pos0>pos1 || pos1>=_width) ++ throw CImgArgumentException(_cimglist_instance ++ "get_shared_images(): Specified sub-list indices (%u->%u) are out of bounds.", ++ cimglist_instance, ++ pos0,pos1); ++ CImgList res(pos1 - pos0 + 1); ++ cimglist_for(res,l) res[l].assign(_data[pos0 + l],_data[pos0 + l]?true:false); ++ return res; ++ } ++ ++ //! Return a single image which is the appending of all images of the current CImgList instance. ++ /** ++ \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. ++ \param align Appending alignment. ++ **/ ++ CImg get_append(const char axis, const float align=0) const { ++ if (is_empty()) return CImg(); ++ if (_width==1) return +((*this)[0]); ++ unsigned int dx = 0, dy = 0, dz = 0, dc = 0, pos = 0; ++ CImg res; ++ switch (cimg::lowercase(axis)) { ++ case 'x' : { // Along the X-axis. ++ cimglist_for(*this,l) { ++ const CImg& img = (*this)[l]; ++ if (img) { ++ dx+=img._width; ++ dy = std::max(dy,img._height); ++ dz = std::max(dz,img._depth); ++ dc = std::max(dc,img._spectrum); ++ } ++ } ++ res.assign(dx,dy,dz,dc,(T)0); ++ if (res) cimglist_for(*this,l) { ++ const CImg& img = (*this)[l]; ++ if (img) res.draw_image(pos, ++ (int)(align*(dy - img._height)), ++ (int)(align*(dz - img._depth)), ++ (int)(align*(dc - img._spectrum)), ++ img); ++ pos+=img._width; ++ } ++ } break; ++ case 'y' : { // Along the Y-axis. ++ cimglist_for(*this,l) { ++ const CImg& img = (*this)[l]; ++ if (img) { ++ dx = std::max(dx,img._width); ++ dy+=img._height; ++ dz = std::max(dz,img._depth); ++ dc = std::max(dc,img._spectrum); ++ } ++ } ++ res.assign(dx,dy,dz,dc,(T)0); ++ if (res) cimglist_for(*this,l) { ++ const CImg& img = (*this)[l]; ++ if (img) res.draw_image((int)(align*(dx - img._width)), ++ pos, ++ (int)(align*(dz - img._depth)), ++ (int)(align*(dc - img._spectrum)), ++ img); ++ pos+=img._height; ++ } ++ } break; ++ case 'z' : { // Along the Z-axis. ++ cimglist_for(*this,l) { ++ const CImg& img = (*this)[l]; ++ if (img) { ++ dx = std::max(dx,img._width); ++ dy = std::max(dy,img._height); ++ dz+=img._depth; ++ dc = std::max(dc,img._spectrum); ++ } ++ } ++ res.assign(dx,dy,dz,dc,(T)0); ++ if (res) cimglist_for(*this,l) { ++ const CImg& img = (*this)[l]; ++ if (img) res.draw_image((int)(align*(dx - img._width)), ++ (int)(align*(dy - img._height)), ++ pos, ++ (int)(align*(dc - img._spectrum)), ++ img); ++ pos+=img._depth; ++ } ++ } break; ++ default : { // Along the C-axis. ++ cimglist_for(*this,l) { ++ const CImg& img = (*this)[l]; ++ if (img) { ++ dx = std::max(dx,img._width); ++ dy = std::max(dy,img._height); ++ dz = std::max(dz,img._depth); ++ dc+=img._spectrum; ++ } ++ } ++ res.assign(dx,dy,dz,dc,(T)0); ++ if (res) cimglist_for(*this,l) { ++ const CImg& img = (*this)[l]; ++ if (img) res.draw_image((int)(align*(dx - img._width)), ++ (int)(align*(dy - img._height)), ++ (int)(align*(dz - img._depth)), ++ pos, ++ img); ++ pos+=img._spectrum; ++ } ++ } ++ } ++ return res; ++ } ++ ++ //! Return a list where each image has been split along the specified axis. ++ /** ++ \param axis Axis to split images along. ++ \param nb Number of spliting parts for each image. ++ **/ ++ CImgList& split(const char axis, const int nb=-1) { ++ return get_split(axis,nb).move_to(*this); ++ } ++ ++ //! Return a list where each image has been split along the specified axis \newinstance. ++ CImgList get_split(const char axis, const int nb=-1) const { ++ CImgList res; ++ cimglist_for(*this,l) _data[l].get_split(axis,nb).move_to(res,~0U); ++ return res; ++ } ++ ++ //! Insert image at the end of the list. ++ /** ++ \param img Image to insert. ++ **/ ++ template ++ CImgList& push_back(const CImg& img) { ++ return insert(img); ++ } ++ ++ //! Insert image at the front of the list. ++ /** ++ \param img Image to insert. ++ **/ ++ template ++ CImgList& push_front(const CImg& img) { ++ return insert(img,0); ++ } ++ ++ //! Insert list at the end of the current list. ++ /** ++ \param list List to insert. ++ **/ ++ template ++ CImgList& push_back(const CImgList& list) { ++ return insert(list); ++ } ++ ++ //! Insert list at the front of the current list. ++ /** ++ \param list List to insert. ++ **/ ++ template ++ CImgList& push_front(const CImgList& list) { ++ return insert(list,0); ++ } ++ ++ //! Remove last image. ++ /** ++ **/ ++ CImgList& pop_back() { ++ return remove(_width - 1); ++ } ++ ++ //! Remove first image. ++ /** ++ **/ ++ CImgList& pop_front() { ++ return remove(0); ++ } ++ ++ //! Remove image pointed by iterator. ++ /** ++ \param iter Iterator pointing to the image to remove. ++ **/ ++ CImgList& erase(const iterator iter) { ++ return remove(iter - _data); ++ } ++ ++ //@} ++ //---------------------------------- ++ // ++ //! \name Data Input ++ //@{ ++ //---------------------------------- ++ ++ //! Display a simple interactive interface to select images or sublists. ++ /** ++ \param disp Window instance to display selection and user interface. ++ \param feature_type Can be \c false to select a single image, or \c true to select a sublist. ++ \param axis Axis along whom images are appended for visualization. ++ \param align Alignment setting when images have not all the same size. ++ \param exit_on_anykey Exit function when any key is pressed. ++ \return A one-column vector containing the selected image indexes. ++ **/ ++ CImg get_select(CImgDisplay &disp, const bool feature_type=true, ++ const char axis='x', const float align=0, ++ const bool exit_on_anykey=false) const { ++ return _select(disp,0,feature_type,axis,align,exit_on_anykey,0,false,false,false); ++ } ++ ++ //! Display a simple interactive interface to select images or sublists. ++ /** ++ \param title Title of a new window used to display selection and user interface. ++ \param feature_type Can be \c false to select a single image, or \c true to select a sublist. ++ \param axis Axis along whom images are appended for visualization. ++ \param align Alignment setting when images have not all the same size. ++ \param exit_on_anykey Exit function when any key is pressed. ++ \return A one-column vector containing the selected image indexes. ++ **/ ++ CImg get_select(const char *const title, const bool feature_type=true, ++ const char axis='x', const float align=0, ++ const bool exit_on_anykey=false) const { ++ CImgDisplay disp; ++ return _select(disp,title,feature_type,axis,align,exit_on_anykey,0,false,false,false); ++ } ++ ++ CImg _select(CImgDisplay &disp, const char *const title, const bool feature_type, ++ const char axis, const float align, const bool exit_on_anykey, ++ const unsigned int orig, const bool resize_disp, ++ const bool exit_on_rightbutton, const bool exit_on_wheel) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimglist_instance ++ "select(): Empty instance.", ++ cimglist_instance); ++ ++ // Create image correspondence table and get list dimensions for visualization. ++ CImgList _indices; ++ unsigned int max_width = 0, max_height = 0, sum_width = 0, sum_height = 0; ++ cimglist_for(*this,l) { ++ const CImg& img = _data[l]; ++ const unsigned int ++ w = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,false), ++ h = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,true); ++ if (w>max_width) max_width = w; ++ if (h>max_height) max_height = h; ++ sum_width+=w; sum_height+=h; ++ if (axis=='x') CImg(w,1,1,1,(unsigned int)l).move_to(_indices); ++ else CImg(h,1,1,1,(unsigned int)l).move_to(_indices); ++ } ++ const CImg indices0 = _indices>'x'; ++ ++ // Create display window. ++ if (!disp) { ++ if (axis=='x') disp.assign(cimg_fitscreen(sum_width,max_height,1),title?title:0,1); ++ else disp.assign(cimg_fitscreen(max_width,sum_height,1),title?title:0,1); ++ if (!title) disp.set_title("CImgList<%s> (%u)",pixel_type(),_width); ++ } else if (title) disp.set_title("%s",title); ++ if (resize_disp) { ++ if (axis=='x') disp.resize(cimg_fitscreen(sum_width,max_height,1),false); ++ else disp.resize(cimg_fitscreen(max_width,sum_height,1),false); ++ } ++ ++ const unsigned int old_normalization = disp.normalization(); ++ bool old_is_resized = disp.is_resized(); ++ disp._normalization = 0; ++ disp.show().set_key(0); ++ static const unsigned char foreground_color[] = { 255,255,255 }, background_color[] = { 0,0,0 }; ++ ++ // Enter event loop. ++ CImg visu0, visu; ++ CImg indices; ++ CImg positions(_width,4,1,1,-1); ++ int oindice0 = -1, oindice1 = -1, indice0 = -1, indice1 = -1; ++ bool is_clicked = false, is_selected = false, text_down = false, update_display = true; ++ unsigned int key = 0; ++ ++ while (!is_selected && !disp.is_closed() && !key) { ++ ++ // Create background image. ++ if (!visu0) { ++ visu0.assign(disp._width,disp._height,1,3,0); visu.assign(); ++ (indices0.get_resize(axis=='x'?visu0._width:visu0._height,1)).move_to(indices); ++ unsigned int ind = 0; ++ const CImg onexone(1,1,1,1,(T)0); ++ if (axis=='x') ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=4)) ++ cimglist_for(*this,ind) { ++ unsigned int x0 = 0; ++ while (x0 &src = _data[ind]?_data[ind]:onexone; ++ CImg res; ++ src.__get_select(disp,old_normalization,(src._width - 1)/2,(src._height - 1)/2,(src._depth - 1)/2). ++ move_to(res); ++ const unsigned int h = CImgDisplay::_fitscreen(res._width,res._height,1,128,-85,true); ++ res.resize(x1 - x0,std::max(32U,h*disp._height/max_height),1,res._spectrum==1?3:-100); ++ positions(ind,0) = positions(ind,2) = (int)x0; ++ positions(ind,1) = positions(ind,3) = (int)(align*(visu0.height() - res.height())); ++ positions(ind,2)+=res._width; ++ positions(ind,3)+=res._height - 1; ++ visu0.draw_image(positions(ind,0),positions(ind,1),res); ++ } ++ else ++ cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=4)) ++ cimglist_for(*this,ind) { ++ unsigned int y0 = 0; ++ while (y0 &src = _data[ind]?_data[ind]:onexone; ++ CImg res; ++ src.__get_select(disp,old_normalization,(src._width - 1)/2,(src._height - 1)/2,(src._depth - 1)/2). ++ move_to(res); ++ const unsigned int w = CImgDisplay::_fitscreen(res._width,res._height,1,128,-85,false); ++ res.resize(std::max(32U,w*disp._width/max_width),y1 - y0,1,res._spectrum==1?3:-100); ++ positions(ind,0) = positions(ind,2) = (int)(align*(visu0.width() - res.width())); ++ positions(ind,1) = positions(ind,3) = (int)y0; ++ positions(ind,2)+=res._width - 1; ++ positions(ind,3)+=res._height; ++ visu0.draw_image(positions(ind,0),positions(ind,1),res); ++ } ++ if (axis=='x') --positions(ind,2); else --positions(ind,3); ++ update_display = true; ++ } ++ ++ if (!visu || oindice0!=indice0 || oindice1!=indice1) { ++ if (indice0>=0 && indice1>=0) { ++ visu.assign(visu0,false); ++ const int indm = std::min(indice0,indice1), indM = std::max(indice0,indice1); ++ for (int ind = indm; ind<=indM; ++ind) if (positions(ind,0)>=0) { ++ visu.draw_rectangle(positions(ind,0),positions(ind,1),positions(ind,2),positions(ind,3), ++ background_color,0.2f); ++ if ((axis=='x' && positions(ind,2) - positions(ind,0)>=8) || ++ (axis!='x' && positions(ind,3) - positions(ind,1)>=8)) ++ visu.draw_rectangle(positions(ind,0),positions(ind,1),positions(ind,2),positions(ind,3), ++ foreground_color,0.9f,0xAAAAAAAA); ++ } ++ const int yt = (int)text_down?visu.height() - 13:0; ++ if (is_clicked) visu.draw_text(0,yt," Images #%u - #%u, Size = %u", ++ foreground_color,background_color,0.7f,13, ++ orig + indm,orig + indM,indM - indm + 1); ++ else visu.draw_text(0,yt," Image #%u (%u,%u,%u,%u)",foreground_color,background_color,0.7f,13, ++ orig + indice0, ++ _data[indice0]._width, ++ _data[indice0]._height, ++ _data[indice0]._depth, ++ _data[indice0]._spectrum); ++ update_display = true; ++ } else visu.assign(); ++ } ++ if (!visu) { visu.assign(visu0,true); update_display = true; } ++ if (update_display) { visu.display(disp); update_display = false; } ++ disp.wait(); ++ ++ // Manage user events. ++ const int xm = disp.mouse_x(), ym = disp.mouse_y(); ++ int indice = -1; ++ ++ if (xm>=0) { ++ indice = (int)indices(axis=='x'?xm:ym); ++ if (disp.button()&1) { ++ if (!is_clicked) { is_clicked = true; oindice0 = indice0; indice0 = indice; } ++ oindice1 = indice1; indice1 = indice; ++ if (!feature_type) is_selected = true; ++ } else { ++ if (!is_clicked) { oindice0 = oindice1 = indice0; indice0 = indice1 = indice; } ++ else is_selected = true; ++ } ++ } else { ++ if (is_clicked) { ++ if (!(disp.button()&1)) { is_clicked = is_selected = false; indice0 = indice1 = -1; } ++ else indice1 = -1; ++ } else indice0 = indice1 = -1; ++ } ++ ++ if (disp.button()&4) { is_clicked = is_selected = false; indice0 = indice1 = -1; } ++ if (disp.button()&2 && exit_on_rightbutton) { is_selected = true; indice1 = indice0 = -1; } ++ if (disp.wheel() && exit_on_wheel) is_selected = true; ++ ++ CImg filename(32); ++ switch (key = disp.key()) { ++#if cimg_OS!=2 ++ case cimg::keyCTRLRIGHT : ++#endif ++ case 0 : case cimg::keyCTRLLEFT : key = 0; break; ++ case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ disp.set_fullscreen(false). ++ 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.set_key(key,false); key = 0; visu0.assign(); ++ } break; ++ case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ disp.set_fullscreen(false). ++ resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true; ++ disp.set_key(key,false); key = 0; visu0.assign(); ++ } break; ++ case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ disp.set_fullscreen(false). ++ resize(cimg_fitscreen(axis=='x'?sum_width:max_width,axis=='x'?max_height:sum_height,1),false). ++ _is_resized = true; ++ disp.set_key(key,false); key = 0; visu0.assign(); ++ } break; ++ case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ disp.resize(disp.screen_width(),disp.screen_height(),false).toggle_fullscreen()._is_resized = true; ++ disp.set_key(key,false); key = 0; visu0.assign(); ++ } break; ++ case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ static unsigned int snap_number = 0; ++ std::FILE *file; ++ do { ++ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++); ++ if ((file=std_fopen(filename,"r"))!=0) cimg::fclose(file); ++ } while (file); ++ if (visu0) { ++ (+visu0).draw_text(0,0," Saving snapshot... ", ++ foreground_color,background_color,0.7f,13).display(disp); ++ visu0.save(filename); ++ (+visu0).draw_text(0,0," Snapshot '%s' saved. ", ++ foreground_color,background_color,0.7f,13,filename._data).display(disp); ++ } ++ disp.set_key(key,false).wait(); key = 0; ++ } break; ++ case cimg::keyO : ++ if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { ++ static unsigned int snap_number = 0; ++ std::FILE *file; ++ do { ++#ifdef cimg_use_zlib ++ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); ++#else ++ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); ++#endif ++ if ((file=std_fopen(filename,"r"))!=0) cimg::fclose(file); ++ } while (file); ++ (+visu0).draw_text(0,0," Saving instance... ", ++ foreground_color,background_color,0.7f,13).display(disp); ++ save(filename); ++ (+visu0).draw_text(0,0," Instance '%s' saved. ", ++ foreground_color,background_color,0.7f,13,filename._data).display(disp); ++ disp.set_key(key,false).wait(); key = 0; ++ } break; ++ } ++ if (disp.is_resized()) { disp.resize(false); visu0.assign(); } ++ if (ym>=0 && ym<13) { if (!text_down) { visu.assign(); text_down = true; }} ++ else if (ym>=visu.height() - 13) { if (text_down) { visu.assign(); text_down = false; }} ++ if (!exit_on_anykey && key && key!=cimg::keyESC && ++ (key!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) { ++ key = 0; ++ } ++ } ++ CImg res(1,2,1,1,-1); ++ if (is_selected) { ++ if (feature_type) res.fill(std::min(indice0,indice1),std::max(indice0,indice1)); ++ else res.fill(indice0); ++ } ++ if (!(disp.button()&2)) disp.set_button(); ++ disp._normalization = old_normalization; ++ disp._is_resized = old_is_resized; ++ disp.set_key(key); ++ return res; ++ } ++ ++ //! Load a list from a file. ++ /** ++ \param filename Filename to read data from. ++ **/ ++ CImgList& load(const char *const filename) { ++ if (!filename) ++ throw CImgArgumentException(_cimglist_instance ++ "load(): Specified filename is (null).", ++ cimglist_instance); ++ ++ if (!cimg::strncasecmp(filename,"http://",7) || !cimg::strncasecmp(filename,"https://",8)) { ++ CImg filename_local(256); ++ load(cimg::load_network(filename,filename_local)); ++ std::remove(filename_local); ++ return *this; ++ } ++ ++ const bool is_stdin = *filename=='-' && (!filename[1] || filename[1]=='.'); ++ const char *const ext = cimg::split_filename(filename); ++ const unsigned int omode = cimg::exception_mode(); ++ cimg::exception_mode(0); ++ bool is_loaded = true; ++ 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); ++ else if (!cimg::strcasecmp(ext,"gif")) load_gif_external(filename); ++ else if (!cimg::strcasecmp(ext,"cimg") || ++ !cimg::strcasecmp(ext,"cimgz") || ++ !*ext) load_cimg(filename); ++ else if (!cimg::strcasecmp(ext,"rec") || ++ !cimg::strcasecmp(ext,"par")) load_parrec(filename); ++ else 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,"mp4") || ++ !cimg::strcasecmp(ext,"mkv") || ++ !cimg::strcasecmp(ext,"mpe") || ++ !cimg::strcasecmp(ext,"movie") || ++ !cimg::strcasecmp(ext,"ogm") || ++ !cimg::strcasecmp(ext,"ogg") || ++ !cimg::strcasecmp(ext,"ogv") || ++ !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_video(filename); ++ else if (!cimg::strcasecmp(ext,"gz")) load_gzip_external(filename); ++ else is_loaded = false; ++ } catch (CImgIOException&) { is_loaded = false; } ++ ++ // If nothing loaded, try to guess file format from magic number in file. ++ if (!is_loaded && !is_stdin) { ++ std::FILE *const file = std_fopen(filename,"rb"); ++ if (!file) { ++ cimg::exception_mode(omode); ++ throw CImgIOException(_cimglist_instance ++ "load(): Failed to open file '%s'.", ++ cimglist_instance, ++ filename); ++ } ++ ++ const char *const f_type = cimg::ftype(file,filename); ++ std::fclose(file); ++ is_loaded = true; ++ try { ++ if (!cimg::strcasecmp(f_type,"gif")) load_gif_external(filename); ++ else if (!cimg::strcasecmp(f_type,"tif")) load_tiff(filename); ++ else is_loaded = false; ++ } catch (CImgIOException&) { is_loaded = false; } ++ } ++ ++ // If nothing loaded, try to load file as a single image. ++ if (!is_loaded) { ++ assign(1); ++ try { ++ _data->load(filename); ++ } catch (CImgIOException&) { ++ cimg::exception_mode(omode); ++ throw CImgIOException(_cimglist_instance ++ "load(): Failed to recognize format of file '%s'.", ++ cimglist_instance, ++ filename); ++ } ++ } ++ cimg::exception_mode(omode); ++ return *this; ++ } ++ ++ //! Load a list from a file \newinstance. ++ static CImgList get_load(const char *const filename) { ++ return CImgList().load(filename); ++ } ++ ++ //! Load a list from a .cimg file. ++ /** ++ \param filename Filename to read data from. ++ **/ ++ CImgList& load_cimg(const char *const filename) { ++ return _load_cimg(0,filename); ++ } ++ ++ //! Load a list from a .cimg file \newinstance. ++ static CImgList get_load_cimg(const char *const filename) { ++ return CImgList().load_cimg(filename); ++ } ++ ++ //! Load a list from a .cimg file. ++ /** ++ \param file File to read data from. ++ **/ ++ CImgList& load_cimg(std::FILE *const file) { ++ return _load_cimg(file,0); ++ } ++ ++ //! Load a list from a .cimg file \newinstance. ++ static CImgList get_load_cimg(std::FILE *const file) { ++ return CImgList().load_cimg(file); ++ } ++ ++ CImgList& _load_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,C); \ ++ uLongf destlen = (ulongT)raw.size()*sizeof(Tss); \ ++ uncompress((Bytef*)raw._data,&destlen,cbuf,csiz); \ ++ delete[] cbuf; \ ++ if (endian!=cimg::endianness()) cimg::invert_endianness(raw._data,raw.size()); \ ++ raw.move_to(img); \ ++} ++#else ++#define _cimgz_load_cimg_case(Tss) \ ++ throw CImgIOException(_cimglist_instance \ ++ "load_cimg(): Unable to load compressed data from file '%s' unless zlib is enabled.", \ ++ cimglist_instance, \ ++ 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 && j<255) tmp[j++] = (char)i; tmp[j] = 0; \ ++ W = H = D = C = 0; csiz = 0; \ ++ if ((err = cimg_sscanf(tmp,"%u %u %u %u #%lu",&W,&H,&D,&C,&csiz))<4) \ ++ throw CImgIOException(_cimglist_instance \ ++ "load_cimg(): Invalid specified size (%u,%u,%u,%u) of image %u in file '%s'.", \ ++ cimglist_instance, \ ++ W,H,D,C,l,filename?filename:("(FILE*)")); \ ++ if (W*H*D*C>0) { \ ++ CImg raw; \ ++ CImg &img = _data[l]; \ ++ if (err==5) _cimgz_load_cimg_case(Tss) \ ++ else { \ ++ img.assign(W,H,D,C); \ ++ T *ptrd = img._data; \ ++ for (ulongT to_read = img.size(); to_read; ) { \ ++ raw.assign((unsigned int)std::min(to_read,cimg_iobuffer)); \ ++ cimg::fread(raw._data,raw._width,nfile); \ ++ if (endian!=cimg::endianness()) cimg::invert_endianness(raw._data,raw.size()); \ ++ const Tss *ptrs = raw._data; \ ++ for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); \ ++ to_read-=raw._width; \ ++ } \ ++ } \ ++ } \ ++ } \ ++ loaded = true; \ ++ } ++ ++ if (!filename && !file) ++ throw CImgArgumentException(_cimglist_instance ++ "load_cimg(): Specified filename is (null).", ++ cimglist_instance); ++ ++ const ulongT cimg_iobuffer = (ulongT)24*1024*1024; ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); ++ bool loaded = false, endian = cimg::endianness(); ++ CImg tmp(256), str_pixeltype(256), str_endian(256); ++ *tmp = *str_pixeltype = *str_endian = 0; ++ unsigned int j, N = 0, W, H, D, C; ++ unsigned long csiz; ++ int i, err; ++ do { ++ j = 0; while ((i=std::fgetc(nfile))!='\n' && i>=0 && j<255) tmp[j++] = (char)i; tmp[j] = 0; ++ } while (*tmp=='#' && i>=0); ++ err = cimg_sscanf(tmp,"%u%*c%255[A-Za-z64_]%*c%255[sA-Za-z_ ]", ++ &N,str_pixeltype._data,str_endian._data); ++ if (err<2) { ++ if (!file) cimg::fclose(nfile); ++ throw CImgIOException(_cimglist_instance ++ "load_cimg(): CImg header not found in file '%s'.", ++ cimglist_instance, ++ 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",unsigned char); ++ _cimg_load_cimg_case("uchar",unsigned char); ++ _cimg_load_cimg_case("char",char); ++ _cimg_load_cimg_case("unsigned_short",unsigned short); ++ _cimg_load_cimg_case("ushort",unsigned short); ++ _cimg_load_cimg_case("short",short); ++ _cimg_load_cimg_case("unsigned_int",unsigned int); ++ _cimg_load_cimg_case("uint",unsigned int); ++ _cimg_load_cimg_case("int",int); ++ _cimg_load_cimg_case("unsigned_long",ulongT); ++ _cimg_load_cimg_case("ulong",ulongT); ++ _cimg_load_cimg_case("long",longT); ++ _cimg_load_cimg_case("unsigned_int64",uint64T); ++ _cimg_load_cimg_case("uint64",uint64T); ++ _cimg_load_cimg_case("int64",int64T); ++ _cimg_load_cimg_case("float",float); ++ _cimg_load_cimg_case("double",double); ++ ++ if (!loaded) { ++ if (!file) cimg::fclose(nfile); ++ throw CImgIOException(_cimglist_instance ++ "load_cimg(): Unsupported pixel type '%s' for file '%s'.", ++ cimglist_instance, ++ str_pixeltype._data,filename?filename:"(FILE*)"); ++ } ++ if (!file) cimg::fclose(nfile); ++ return *this; ++ } ++ ++ //! Load a sublist list from a (non compressed) .cimg file. ++ /** ++ \param filename Filename to read data from. ++ \param n0 Starting index of images to read (~0U for max). ++ \param n1 Ending index of images to read (~0U for max). ++ \param x0 Starting X-coordinates of image regions to read. ++ \param y0 Starting Y-coordinates of image regions to read. ++ \param z0 Starting Z-coordinates of image regions to read. ++ \param c0 Starting C-coordinates of image regions to read. ++ \param x1 Ending X-coordinates of image regions to read (~0U for max). ++ \param y1 Ending Y-coordinates of image regions to read (~0U for max). ++ \param z1 Ending Z-coordinates of image regions to read (~0U for max). ++ \param c1 Ending C-coordinates of image regions to read (~0U for max). ++ **/ ++ 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 c0, ++ const unsigned int x1, const unsigned int y1, ++ const unsigned int z1, const unsigned int c1) { ++ return _load_cimg(0,filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); ++ } ++ ++ //! Load a sublist list from a (non compressed) .cimg file \newinstance. ++ 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 c0, ++ const unsigned int x1, const unsigned int y1, ++ const unsigned int z1, const unsigned int c1) { ++ return CImgList().load_cimg(filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); ++ } ++ ++ //! Load a sub-image list from a (non compressed) .cimg file \overloading. ++ CImgList& load_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 c0, ++ const unsigned int x1, const unsigned int y1, ++ const unsigned int z1, const unsigned int c1) { ++ return _load_cimg(file,0,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); ++ } ++ ++ //! Load a sub-image list from a (non compressed) .cimg file \newinstance. ++ static CImgList get_load_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 c0, ++ const unsigned int x1, const unsigned int y1, ++ const unsigned int z1, const unsigned int c1) { ++ return CImgList().load_cimg(file,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1); ++ } ++ ++ CImgList& _load_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 c0, ++ const unsigned int x1, const unsigned int y1, ++ const unsigned int z1, const unsigned int c1) { ++#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=std::fgetc(nfile))!='\n' && i>=0) tmp[j++] = (char)i; tmp[j] = 0; \ ++ W = H = D = C = 0; \ ++ if (cimg_sscanf(tmp,"%u %u %u %u",&W,&H,&D,&C)!=4) \ ++ throw CImgIOException(_cimglist_instance \ ++ "load_cimg(): Invalid specified size (%u,%u,%u,%u) of image %u in file '%s'", \ ++ cimglist_instance, \ ++ W,H,D,C,l,filename?filename:"(FILE*)"); \ ++ if (W*H*D*C>0) { \ ++ if (l=W || ny0>=H || nz0>=D || nc0>=C) cimg::fseek(nfile,W*H*D*C*sizeof(Tss),SEEK_CUR); \ ++ else { \ ++ const unsigned int \ ++ _nx1 = nx1==~0U?W - 1:nx1, \ ++ _ny1 = ny1==~0U?H - 1:ny1, \ ++ _nz1 = nz1==~0U?D - 1:nz1, \ ++ _nc1 = nc1==~0U?C - 1:nc1; \ ++ if (_nx1>=W || _ny1>=H || _nz1>=D || _nc1>=C) \ ++ throw CImgArgumentException(_cimglist_instance \ ++ "load_cimg(): Invalid specified coordinates " \ ++ "[%u](%u,%u,%u,%u) -> [%u](%u,%u,%u,%u) " \ ++ "because image [%u] in file '%s' has size (%u,%u,%u,%u).", \ ++ cimglist_instance, \ ++ n0,x0,y0,z0,c0,n1,x1,y1,z1,c1,l,filename?filename:"(FILE*)",W,H,D,C); \ ++ CImg raw(1 + _nx1 - nx0); \ ++ CImg &img = _data[l - nn0]; \ ++ img.assign(1 + _nx1 - nx0,1 + _ny1 - ny0,1 + _nz1 - nz0,1 + _nc1 - nc0); \ ++ T *ptrd = img._data; \ ++ ulongT skipvb = nc0*W*H*D*sizeof(Tss); \ ++ if (skipvb) cimg::fseek(nfile,skipvb,SEEK_CUR); \ ++ for (unsigned int c = 1 + _nc1 - nc0; c; --c) { \ ++ const ulongT skipzb = nz0*W*H*sizeof(Tss); \ ++ if (skipzb) cimg::fseek(nfile,skipzb,SEEK_CUR); \ ++ for (unsigned int z = 1 + _nz1 - nz0; z; --z) { \ ++ const ulongT skipyb = ny0*W*sizeof(Tss); \ ++ if (skipyb) cimg::fseek(nfile,skipyb,SEEK_CUR); \ ++ for (unsigned int y = 1 + _ny1 - ny0; y; --y) { \ ++ const ulongT skipxb = nx0*sizeof(Tss); \ ++ if (skipxb) cimg::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 ulongT skipxe = (W - 1 - _nx1)*sizeof(Tss); \ ++ if (skipxe) cimg::fseek(nfile,skipxe,SEEK_CUR); \ ++ } \ ++ const ulongT skipye = (H - 1 - _ny1)*W*sizeof(Tss); \ ++ if (skipye) cimg::fseek(nfile,skipye,SEEK_CUR); \ ++ } \ ++ const ulongT skipze = (D - 1 - _nz1)*W*H*sizeof(Tss); \ ++ if (skipze) cimg::fseek(nfile,skipze,SEEK_CUR); \ ++ } \ ++ const ulongT skipve = (C - 1 - _nc1)*W*H*D*sizeof(Tss); \ ++ if (skipve) cimg::fseek(nfile,skipve,SEEK_CUR); \ ++ } \ ++ } \ ++ } \ ++ loaded = true; \ ++ } ++ ++ if (!filename && !file) ++ throw CImgArgumentException(_cimglist_instance ++ "load_cimg(): Specified filename is (null).", ++ cimglist_instance); ++ unsigned int ++ nn0 = std::min(n0,n1), nn1 = std::max(n0,n1), ++ nx0 = std::min(x0,x1), nx1 = std::max(x0,x1), ++ ny0 = std::min(y0,y1), ny1 = std::max(y0,y1), ++ nz0 = std::min(z0,z1), nz1 = std::max(z0,z1), ++ nc0 = std::min(c0,c1), nc1 = std::max(c0,c1); ++ ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); ++ bool loaded = false, endian = cimg::endianness(); ++ CImg tmp(256), str_pixeltype(256), str_endian(256); ++ *tmp = *str_pixeltype = *str_endian = 0; ++ unsigned int j, N, W, H, D, C; ++ int i, err; ++ j = 0; while ((i=std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = 0; ++ err = cimg_sscanf(tmp,"%u%*c%255[A-Za-z64_]%*c%255[sA-Za-z_ ]", ++ &N,str_pixeltype._data,str_endian._data); ++ if (err<2) { ++ if (!file) cimg::fclose(nfile); ++ throw CImgIOException(_cimglist_instance ++ "load_cimg(): CImg header not found in file '%s'.", ++ cimglist_instance, ++ filename?filename:"(FILE*)"); ++ } ++ if (!cimg::strncasecmp("little",str_endian,6)) endian = false; ++ else if (!cimg::strncasecmp("big",str_endian,3)) endian = true; ++ nn1 = n1==~0U?N - 1:n1; ++ if (nn1>=N) ++ throw CImgArgumentException(_cimglist_instance ++ "load_cimg(): Invalid specified coordinates [%u](%u,%u,%u,%u) -> [%u](%u,%u,%u,%u) " ++ "because file '%s' contains only %u images.", ++ cimglist_instance, ++ n0,x0,y0,z0,c0,n1,x1,y1,z1,c1,filename?filename:"(FILE*)",N); ++ assign(1 + nn1 - n0); ++ _cimg_load_cimg_case2("bool",bool); ++ _cimg_load_cimg_case2("unsigned_char",unsigned char); ++ _cimg_load_cimg_case2("uchar",unsigned char); ++ _cimg_load_cimg_case2("char",char); ++ _cimg_load_cimg_case2("unsigned_short",unsigned short); ++ _cimg_load_cimg_case2("ushort",unsigned short); ++ _cimg_load_cimg_case2("short",short); ++ _cimg_load_cimg_case2("unsigned_int",unsigned int); ++ _cimg_load_cimg_case2("uint",unsigned int); ++ _cimg_load_cimg_case2("int",int); ++ _cimg_load_cimg_case2("unsigned_long",ulongT); ++ _cimg_load_cimg_case2("ulong",ulongT); ++ _cimg_load_cimg_case2("long",longT); ++ _cimg_load_cimg_case2("unsigned_int64",uint64T); ++ _cimg_load_cimg_case2("uint64",uint64T); ++ _cimg_load_cimg_case2("int64",int64T); ++ _cimg_load_cimg_case2("float",float); ++ _cimg_load_cimg_case2("double",double); ++ if (!loaded) { ++ if (!file) cimg::fclose(nfile); ++ throw CImgIOException(_cimglist_instance ++ "load_cimg(): Unsupported pixel type '%s' for file '%s'.", ++ cimglist_instance, ++ str_pixeltype._data,filename?filename:"(FILE*)"); ++ } ++ if (!file) cimg::fclose(nfile); ++ return *this; ++ } ++ ++ //! Load a list from a PAR/REC (Philips) file. ++ /** ++ \param filename Filename to read data from. ++ **/ ++ CImgList& load_parrec(const char *const filename) { ++ if (!filename) ++ throw CImgArgumentException(_cimglist_instance ++ "load_parrec(): Specified filename is (null).", ++ cimglist_instance); ++ ++ CImg body(1024), filenamepar(1024), filenamerec(1024); ++ *body = *filenamepar = *filenamerec = 0; ++ const char *const ext = cimg::split_filename(filename,body); ++ if (!std::strcmp(ext,"par")) { ++ std::strncpy(filenamepar,filename,filenamepar._width - 1); ++ cimg_snprintf(filenamerec,filenamerec._width,"%s.rec",body._data); ++ } ++ if (!std::strcmp(ext,"PAR")) { ++ std::strncpy(filenamepar,filename,filenamepar._width - 1); ++ cimg_snprintf(filenamerec,filenamerec._width,"%s.REC",body._data); ++ } ++ if (!std::strcmp(ext,"rec")) { ++ std::strncpy(filenamerec,filename,filenamerec._width - 1); ++ cimg_snprintf(filenamepar,filenamepar._width,"%s.par",body._data); ++ } ++ if (!std::strcmp(ext,"REC")) { ++ std::strncpy(filenamerec,filename,filenamerec._width - 1); ++ cimg_snprintf(filenamepar,filenamepar._width,"%s.PAR",body._data); ++ } ++ std::FILE *file = cimg::fopen(filenamepar,"r"); ++ ++ // Parse header file ++ CImgList st_slices; ++ CImgList st_global; ++ CImg line(256); *line = 0; ++ int err; ++ do { err = std::fscanf(file,"%255[^\n]%*c",line._data); } while (err!=EOF && (*line=='#' || *line=='.')); ++ do { ++ unsigned int sn,size_x,size_y,pixsize; ++ float rs,ri,ss; ++ err = std::fscanf(file,"%u%*u%*u%*u%*u%*u%*u%u%*u%u%u%g%g%g%*[^\n]",&sn,&pixsize,&size_x,&size_y,&ri,&rs,&ss); ++ if (err==7) { ++ CImg::vector((float)sn,(float)pixsize,(float)size_x,(float)size_y,ri,rs,ss,0).move_to(st_slices); ++ unsigned int i; for (i = 0; i::vector(size_x,size_y,sn).move_to(st_global); ++ else { ++ CImg &vec = st_global[i]; ++ if (size_x>vec[0]) vec[0] = size_x; ++ if (size_y>vec[1]) vec[1] = size_y; ++ vec[2] = sn; ++ } ++ st_slices[st_slices._width - 1][7] = (float)i; ++ } ++ } while (err==7); ++ ++ // Read data ++ std::FILE *file2 = cimg::fopen(filenamerec,"rb"); ++ cimglist_for(st_global,l) { ++ const CImg& vec = st_global[l]; ++ CImg(vec[0],vec[1],vec[2]).move_to(*this); ++ } ++ ++ 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], ++ size_x = (unsigned int)vec[2], ++ size_y = (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(size_x,size_y); ++ cimg::fread(buf._data,size_x*size_y,file2); ++ if (cimg::endianness()) cimg::invert_endianness(buf._data,size_x*size_y); ++ 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(size_x,size_y); ++ cimg::fread(buf._data,size_x*size_y,file2); ++ if (cimg::endianness()) cimg::invert_endianness(buf._data,size_x*size_y); ++ 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(size_x,size_y); ++ cimg::fread(buf._data,size_x*size_y,file2); ++ if (cimg::endianness()) cimg::invert_endianness(buf._data,size_x*size_y); ++ 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(_cimglist_instance ++ "load_parrec(): Unsupported %d-bits pixel type for file '%s'.", ++ cimglist_instance, ++ pixsize,filename); ++ } ++ } ++ cimg::fclose(file); ++ cimg::fclose(file2); ++ if (!_width) ++ throw CImgIOException(_cimglist_instance ++ "load_parrec(): Failed to recognize valid PAR-REC data in file '%s'.", ++ cimglist_instance, ++ filename); ++ return *this; ++ } ++ ++ //! Load a list from a PAR/REC (Philips) file \newinstance. ++ static CImgList get_load_parrec(const char *const filename) { ++ return CImgList().load_parrec(filename); ++ } ++ ++ //! Load a list from a YUV image sequence file. ++ /** ++ \param filename Filename to read data from. ++ \param size_x Width of the images. ++ \param size_y Height of the images. ++ \param chroma_subsampling Type of chroma subsampling. Can be { 420 | 422 | 444 }. ++ \param first_frame Index of first image frame to read. ++ \param last_frame Index of last image frame to read. ++ \param step_frame Step applied between each frame. ++ \param yuv2rgb Apply YUV to RGB transformation during reading. ++ **/ ++ CImgList& load_yuv(const char *const filename, ++ const unsigned int size_x, const unsigned int size_y, ++ const unsigned int chroma_subsampling=444, ++ 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,size_x,size_y,chroma_subsampling, ++ first_frame,last_frame,step_frame,yuv2rgb); ++ } ++ ++ //! Load a list from a YUV image sequence file \newinstance. ++ static CImgList get_load_yuv(const char *const filename, ++ const unsigned int size_x, const unsigned int size_y=1, ++ const unsigned int chroma_subsampling=444, ++ 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,size_x,size_y,chroma_subsampling, ++ first_frame,last_frame,step_frame,yuv2rgb); ++ } ++ ++ //! Load a list from an image sequence YUV file \overloading. ++ CImgList& load_yuv(std::FILE *const file, ++ const unsigned int size_x, const unsigned int size_y, ++ const unsigned int chroma_subsampling=444, ++ 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,size_x,size_y,chroma_subsampling, ++ first_frame,last_frame,step_frame,yuv2rgb); ++ } ++ ++ //! Load a list from an image sequence YUV file \newinstance. ++ static CImgList get_load_yuv(std::FILE *const file, ++ const unsigned int size_x, const unsigned int size_y=1, ++ const unsigned int chroma_subsampling=444, ++ 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,size_x,size_y,chroma_subsampling, ++ first_frame,last_frame,step_frame,yuv2rgb); ++ } ++ ++ CImgList& _load_yuv(std::FILE *const file, const char *const filename, ++ const unsigned int size_x, const unsigned int size_y, ++ const unsigned int chroma_subsampling, ++ const unsigned int first_frame, const unsigned int last_frame, ++ const unsigned int step_frame, const bool yuv2rgb) { ++ if (!filename && !file) ++ throw CImgArgumentException(_cimglist_instance ++ "load_yuv(): Specified filename is (null).", ++ cimglist_instance); ++ if (chroma_subsampling!=420 && chroma_subsampling!=422 && chroma_subsampling!=444) ++ throw CImgArgumentException(_cimglist_instance ++ "load_yuv(): Specified chroma subsampling '%u' is invalid, for file '%s'.", ++ cimglist_instance, ++ chroma_subsampling,filename?filename:"(FILE*)"); ++ const unsigned int ++ cfx = chroma_subsampling==420 || chroma_subsampling==422?2:1, ++ cfy = chroma_subsampling==420?2:1, ++ nfirst_frame = first_frame YUV(size_x,size_y,1,3), UV(size_x/cfx,size_y/cfy,1,2); ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); ++ bool stop_flag = false; ++ int err; ++ if (nfirst_frame) { ++ err = cimg::fseek(nfile,(uint64T)nfirst_frame*(YUV._width*YUV._height + 2*UV._width*UV._height),SEEK_CUR); ++ if (err) { ++ if (!file) cimg::fclose(nfile); ++ throw CImgIOException(_cimglist_instance ++ "load_yuv(): File '%s' doesn't contain frame number %u.", ++ cimglist_instance, ++ filename?filename:"(FILE*)",nfirst_frame); ++ } ++ } ++ unsigned int frame; ++ for (frame = nfirst_frame; !stop_flag && frame<=nlast_frame; frame+=nstep_frame) { ++ YUV.get_shared_channel(0).fill(0); ++ // *TRY* to read the luminance part, do not replace by cimg::fread! ++ err = (int)std::fread((void*)(YUV._data),1,(size_t)YUV._width*YUV._height,nfile); ++ if (err!=(int)(YUV._width*YUV._height)) { ++ stop_flag = true; ++ if (err>0) ++ cimg::warn(_cimglist_instance ++ "load_yuv(): File '%s' contains incomplete data or given image dimensions " ++ "(%u,%u) are incorrect.", ++ cimglist_instance, ++ filename?filename:"(FILE*)",size_x,size_y); ++ } else { ++ UV.fill(0); ++ // *TRY* to read the luminance part, do not replace by cimg::fread! ++ err = (int)std::fread((void*)(UV._data),1,(size_t)UV.size(),nfile); ++ if (err!=(int)(UV.size())) { ++ stop_flag = true; ++ if (err>0) ++ cimg::warn(_cimglist_instance ++ "load_yuv(): File '%s' contains incomplete data or given image dimensions " ++ "(%u,%u) are incorrect.", ++ cimglist_instance, ++ filename?filename:"(FILE*)",size_x,size_y); ++ } else { ++ const ucharT *ptrs1 = UV._data, *ptrs2 = UV.data(0,0,0,1); ++ ucharT *ptrd1 = YUV.data(0,0,0,1), *ptrd2 = YUV.data(0,0,0,2); ++ const unsigned int wd = YUV._width; ++ switch (chroma_subsampling) { ++ case 420 : ++ cimg_forY(UV,y) { ++ cimg_forX(UV,x) { ++ const ucharT U = *(ptrs1++), V = *(ptrs2++); ++ ptrd1[wd] = U; *(ptrd1)++ = U; ++ ptrd1[wd] = U; *(ptrd1)++ = U; ++ ptrd2[wd] = V; *(ptrd2)++ = V; ++ ptrd2[wd] = V; *(ptrd2)++ = V; ++ } ++ ptrd1+=wd; ptrd2+=wd; ++ } ++ break; ++ case 422 : ++ cimg_forXY(UV,x,y) { ++ const ucharT U = *(ptrs1++), V = *(ptrs2++); ++ *(ptrd1++) = U; *(ptrd1++) = U; ++ *(ptrd2++) = V; *(ptrd2++) = V; ++ } ++ break; ++ default : ++ YUV.draw_image(0,0,0,1,UV); ++ } ++ if (yuv2rgb) YUV.YCbCrtoRGB(); ++ insert(YUV); ++ if (nstep_frame>1) cimg::fseek(nfile,(uint64T)(nstep_frame - 1)*(size_x*size_y + size_x*size_y/2),SEEK_CUR); ++ } ++ } ++ } ++ if (stop_flag && nlast_frame!=~0U && frame!=nlast_frame) ++ cimg::warn(_cimglist_instance ++ "load_yuv(): Frame %d not reached since only %u frames were found in file '%s'.", ++ cimglist_instance, ++ nlast_frame,frame - 1,filename?filename:"(FILE*)"); ++ ++ if (!file) cimg::fclose(nfile); ++ return *this; ++ } ++ ++ //! Load an image from a video file, using OpenCV library. ++ /** ++ \param filename Filename, as a C-string. ++ \param first_frame Index of the first frame to read. ++ \param last_frame Index of the last frame to read. ++ \param step_frame Step value for frame reading. ++ \note If step_frame==0, the current video stream is forced to be released (without any frames read). ++ **/ ++ CImgList& load_video(const char *const filename, ++ const unsigned int first_frame=0, const unsigned int last_frame=~0U, ++ const unsigned int step_frame=1) { ++#ifndef cimg_use_opencv ++ if (first_frame || last_frame!=~0U || step_frame>1) ++ throw CImgArgumentException(_cimglist_instance ++ "load_video() : File '%s', arguments 'first_frame', 'last_frame' " ++ "and 'step_frame' can be only set when using OpenCV " ++ "(-Dcimg_use_opencv must be enabled).", ++ cimglist_instance,filename); ++ return load_ffmpeg_external(filename); ++#else ++ static CvCapture *captures[32] = { 0 }; ++ static CImgList filenames(32); ++ static CImg positions(32,1,1,1,0); ++ static int last_used_index = -1; ++ ++ // Detect if a video capture already exists for the specified filename. ++ cimg::mutex(9); ++ int index = -1; ++ if (filename) { ++ if (last_used_index>=0 && !std::strcmp(filename,filenames[last_used_index])) { ++ index = last_used_index; ++ } else cimglist_for(filenames,l) if (filenames[l] && !std::strcmp(filename,filenames[l])) { ++ index = l; break; ++ } ++ } else index = last_used_index; ++ cimg::mutex(9,0); ++ ++ // Release stream if needed. ++ if (!step_frame || (index>=0 && positions[index]>first_frame)) { ++ if (index>=0) { ++ cimg::mutex(9); ++ cvReleaseCapture(&captures[index]); ++ captures[index] = 0; filenames[index].assign(); positions[index] = 0; ++ if (last_used_index==index) last_used_index = -1; ++ index = -1; ++ cimg::mutex(9,0); ++ } else ++ if (filename) ++ cimg::warn(_cimglist_instance ++ "load_video() : File '%s', no opened video stream associated with filename found.", ++ cimglist_instance,filename); ++ else ++ cimg::warn(_cimglist_instance ++ "load_video() : No opened video stream found.", ++ cimglist_instance,filename); ++ if (!step_frame) return *this; ++ } ++ ++ // Find empty slot for capturing video stream. ++ if (index<0) { ++ if (!filename) ++ throw CImgArgumentException(_cimglist_instance ++ "load_video(): No already open video reader found. You must specify a " ++ "non-(null) filename argument for the first call.", ++ cimglist_instance); ++ else { cimg::mutex(9); cimglist_for(filenames,l) if (!filenames[l]) { index = l; break; } cimg::mutex(9,0); } ++ if (index<0) ++ throw CImgIOException(_cimglist_instance ++ "load_video(): File '%s', no video reader slots available. " ++ "You have to release some of your previously opened videos.", ++ cimglist_instance,filename); ++ cimg::mutex(9); ++ captures[index] = cvCaptureFromFile(filename); ++ CImg::string(filename).move_to(filenames[index]); ++ positions[index] = 0; ++ cimg::mutex(9,0); ++ if (!captures[index]) { ++ filenames[index].assign(); ++ std::fclose(cimg::fopen(filename,"rb")); // Check file availability. ++ throw CImgIOException(_cimglist_instance ++ "load_video(): File '%s', unable to detect format of video file.", ++ cimglist_instance,filename); ++ } ++ } ++ ++ cimg::mutex(9); ++ const unsigned int nb_frames = (unsigned int)std::max(0.,cvGetCaptureProperty(captures[index], ++ CV_CAP_PROP_FRAME_COUNT)); ++ cimg::mutex(9,0); ++ assign(); ++ ++ // Skip frames if necessary. ++ bool go_on = true; ++ unsigned int &pos = positions[index]; ++ while (pos frame(src->width,src->height,1,3); ++ const int step = (int)(src->widthStep - 3*src->width); ++ const unsigned char* ptrs = (unsigned char*)src->imageData; ++ T *ptr_r = frame.data(0,0,0,0), *ptr_g = frame.data(0,0,0,1), *ptr_b = frame.data(0,0,0,2); ++ if (step>0) cimg_forY(frame,y) { ++ cimg_forX(frame,x) { *(ptr_b++) = (T)*(ptrs++); *(ptr_g++) = (T)*(ptrs++); *(ptr_r++) = (T)*(ptrs++); } ++ ptrs+=step; ++ } else for (ulongT siz = (ulongT)src->width*src->height; siz; --siz) { ++ *(ptr_b++) = (T)*(ptrs++); *(ptr_g++) = (T)*(ptrs++); *(ptr_r++) = (T)*(ptrs++); ++ } ++ frame.move_to(*this); ++ ++pos; ++ ++ bool skip_failed = false; ++ for (unsigned int i = 1; i=nb_frames)) { // Close video stream when necessary. ++ cimg::mutex(9); ++ cvReleaseCapture(&captures[index]); ++ captures[index] = 0; ++ filenames[index].assign(); ++ positions[index] = 0; ++ index = -1; ++ cimg::mutex(9,0); ++ } ++ ++ cimg::mutex(9); ++ last_used_index = index; ++ cimg::mutex(9,0); ++ ++ if (is_empty()) ++ throw CImgIOException(_cimglist_instance ++ "load_video(): File '%s', unable to locate frame %u.", ++ cimglist_instance,filename,first_frame); ++ return *this; ++#endif ++ } ++ ++ //! Load an image from a video file, using OpenCV library \newinstance. ++ static CImgList get_load_video(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_video(filename,first_frame,last_frame,step_frame); ++ } ++ ++ //! Load an image from a video file using the external tool 'ffmpeg'. ++ /** ++ \param filename Filename to read data from. ++ **/ ++ CImgList& load_ffmpeg_external(const char *const filename) { ++ if (!filename) ++ throw CImgArgumentException(_cimglist_instance ++ "load_ffmpeg_external(): Specified filename is (null).", ++ cimglist_instance); ++ std::fclose(cimg::fopen(filename,"rb")); // Check if file exists. ++ CImg command(1024), filename_tmp(256), filename_tmp2(256); ++ std::FILE *file = 0; ++ do { ++ cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", ++ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); ++ cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_000001.ppm",filename_tmp._data); ++ if ((file=std_fopen(filename_tmp2,"rb"))!=0) cimg::fclose(file); ++ } while (file); ++ cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_%%6d.ppm",filename_tmp._data); ++ cimg_snprintf(command,command._width,"%s -i \"%s\" \"%s\"", ++ cimg::ffmpeg_path(), ++ CImg::string(filename)._system_strescape().data(), ++ CImg::string(filename_tmp2)._system_strescape().data()); ++ cimg::system(command,0); ++ const unsigned int omode = cimg::exception_mode(); ++ cimg::exception_mode(0); ++ assign(); ++ unsigned int i = 1; ++ for (bool stop_flag = false; !stop_flag; ++i) { ++ cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_%.6u.ppm",filename_tmp._data,i); ++ CImg img; ++ try { img.load_pnm(filename_tmp2); } ++ catch (CImgException&) { stop_flag = true; } ++ if (img) { img.move_to(*this); std::remove(filename_tmp2); } ++ } ++ cimg::exception_mode(omode); ++ if (is_empty()) ++ throw CImgIOException(_cimglist_instance ++ "load_ffmpeg_external(): Failed to open file '%s' with external command 'ffmpeg'.", ++ cimglist_instance, ++ filename); ++ return *this; ++ } ++ ++ //! Load an image from a video file using the external tool 'ffmpeg' \newinstance. ++ static CImgList get_load_ffmpeg_external(const char *const filename) { ++ return CImgList().load_ffmpeg_external(filename); ++ } ++ ++ //! Load gif file, using ImageMagick or GraphicsMagick's external tools. ++ /** ++ \param filename Filename to read data from. ++ **/ ++ CImgList& load_gif_external(const char *const filename) { ++ if (!filename) ++ throw CImgArgumentException(_cimglist_instance ++ "load_gif_external(): Specified filename is (null).", ++ cimglist_instance); ++ std::fclose(cimg::fopen(filename,"rb")); // Check if file exists. ++ if (!_load_gif_external(filename,false)) ++ if (!_load_gif_external(filename,true)) ++ try { assign(CImg().load_other(filename)); } catch (CImgException&) { assign(); } ++ if (is_empty()) ++ throw CImgIOException(_cimglist_instance ++ "load_gif_external(): Failed to open file '%s'.", ++ cimglist_instance,filename); ++ return *this; ++ } ++ ++ CImgList& _load_gif_external(const char *const filename, const bool use_graphicsmagick=false) { ++ CImg command(1024), filename_tmp(256), filename_tmp2(256); ++ std::FILE *file = 0; ++ do { ++ cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", ++ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); ++ if (use_graphicsmagick) cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s.png.0",filename_tmp._data); ++ else cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s-0.png",filename_tmp._data); ++ if ((file=std_fopen(filename_tmp2,"rb"))!=0) cimg::fclose(file); ++ } while (file); ++ if (use_graphicsmagick) cimg_snprintf(command,command._width,"%s convert \"%s\" \"%s.png\"", ++ cimg::graphicsmagick_path(), ++ CImg::string(filename)._system_strescape().data(), ++ CImg::string(filename_tmp)._system_strescape().data()); ++ else cimg_snprintf(command,command._width,"%s \"%s\" \"%s.png\"", ++ cimg::imagemagick_path(), ++ CImg::string(filename)._system_strescape().data(), ++ CImg::string(filename_tmp)._system_strescape().data()); ++ cimg::system(command,0); ++ const unsigned int omode = cimg::exception_mode(); ++ cimg::exception_mode(0); ++ assign(); ++ ++ // Try to read a single frame gif. ++ cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s.png",filename_tmp._data); ++ CImg img; ++ try { img.load_png(filename_tmp2); } ++ catch (CImgException&) { } ++ if (img) { img.move_to(*this); std::remove(filename_tmp2); } ++ else { // Try to read animated gif. ++ unsigned int i = 0; ++ for (bool stop_flag = false; !stop_flag; ++i) { ++ if (use_graphicsmagick) cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s.png.%u",filename_tmp._data,i); ++ else cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s-%u.png",filename_tmp._data,i); ++ CImg img; ++ try { img.load_png(filename_tmp2); } ++ catch (CImgException&) { stop_flag = true; } ++ if (img) { img.move_to(*this); std::remove(filename_tmp2); } ++ } ++ } ++ cimg::exception_mode(omode); ++ return *this; ++ } ++ ++ //! Load gif file, using ImageMagick or GraphicsMagick's external tools \newinstance. ++ static CImgList get_load_gif_external(const char *const filename) { ++ return CImgList().load_gif_external(filename); ++ } ++ ++ //! Load a gzipped list, using external tool 'gunzip'. ++ /** ++ \param filename Filename to read data from. ++ **/ ++ CImgList& load_gzip_external(const char *const filename) { ++ if (!filename) ++ throw CImgIOException(_cimglist_instance ++ "load_gzip_external(): Specified filename is (null).", ++ cimglist_instance); ++ std::fclose(cimg::fopen(filename,"rb")); // Check if file exists. ++ CImg command(1024), filename_tmp(256), body(256); ++ const char ++ *ext = cimg::split_filename(filename,body), ++ *ext2 = cimg::split_filename(body,0); ++ std::FILE *file = 0; ++ do { ++ if (!cimg::strcasecmp(ext,"gz")) { ++ if (*ext2) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", ++ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2); ++ else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", ++ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); ++ } else { ++ if (*ext) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", ++ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext); ++ else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", ++ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); ++ } ++ if ((file=std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); ++ } while (file); ++ cimg_snprintf(command,command._width,"%s -c \"%s\" > \"%s\"", ++ cimg::gunzip_path(), ++ CImg::string(filename)._system_strescape().data(), ++ CImg::string(filename_tmp)._system_strescape().data()); ++ cimg::system(command); ++ if (!(file = std_fopen(filename_tmp,"rb"))) { ++ cimg::fclose(cimg::fopen(filename,"r")); ++ throw CImgIOException(_cimglist_instance ++ "load_gzip_external(): Failed to open file '%s'.", ++ cimglist_instance, ++ filename); ++ ++ } else cimg::fclose(file); ++ load(filename_tmp); ++ std::remove(filename_tmp); ++ return *this; ++ } ++ ++ //! Load a gzipped list, using external tool 'gunzip' \newinstance. ++ static CImgList get_load_gzip_external(const char *const filename) { ++ return CImgList().load_gzip_external(filename); ++ } ++ ++ //! Load a 3d object from a .OFF file. ++ /** ++ \param filename Filename to read data from. ++ \param[out] primitives At return, contains the list of 3d object primitives. ++ \param[out] colors At return, contains the list of 3d object colors. ++ \return List of 3d object vertices. ++ **/ ++ template ++ CImgList& load_off(const char *const filename, ++ CImgList& primitives, CImgList& colors) { ++ return get_load_off(filename,primitives,colors).move_to(*this); ++ } ++ ++ //! Load a 3d object from a .OFF file \newinstance. ++ template ++ static CImgList get_load_off(const char *const filename, ++ CImgList& primitives, CImgList& colors) { ++ return CImg().load_off(filename,primitives,colors)<'x'; ++ } ++ ++ //! Load images from a TIFF file. ++ /** ++ \param filename Filename to read data from. ++ \param first_frame Index of first image frame to read. ++ \param last_frame Index of last image frame to read. ++ \param step_frame Step applied between each frame. ++ \param[out] voxel_size Voxel size, as stored in the filename. ++ \param[out] description Description, as stored in the filename. ++ **/ ++ 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, ++ float *const voxel_size=0, ++ CImg *const description=0) { ++ const unsigned int ++ nfirst_frame = first_frame::get_load_tiff(filename)); ++#else ++#if cimg_verbosity<3 ++ TIFFSetWarningHandler(0); ++ TIFFSetErrorHandler(0); ++#endif ++ 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_instance ++ "load_tiff(): Invalid specified frame range is [%u,%u] (step %u) since " ++ "file '%s' contains %u image(s).", ++ cimglist_instance, ++ nfirst_frame,nlast_frame,nstep_frame,filename,nb_images); ++ ++ 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); ++ cimglist_for(*this,l) _data[l]._load_tiff(tif,nfirst_frame + l*nstep_frame,voxel_size,description); ++ TIFFClose(tif); ++ } else throw CImgIOException(_cimglist_instance ++ "load_tiff(): Failed to open file '%s'.", ++ cimglist_instance, ++ filename); ++ return *this; ++#endif ++ } ++ ++ //! Load a multi-page TIFF file \newinstance. ++ 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, ++ float *const voxel_size=0, ++ CImg *const description=0) { ++ return CImgList().load_tiff(filename,first_frame,last_frame,step_frame,voxel_size,description); ++ } ++ ++ //@} ++ //---------------------------------- ++ // ++ //! \name Data Output ++ //@{ ++ //---------------------------------- ++ ++ //! Print information about the list on the standard output. ++ /** ++ \param title Label set to the information displayed. ++ \param display_stats Tells if image statistics must be computed and displayed. ++ **/ ++ const CImgList& print(const char *const title=0, const bool display_stats=true) const { ++ unsigned int msiz = 0; ++ cimglist_for(*this,l) msiz+=_data[l].size(); ++ msiz*=sizeof(T); ++ const unsigned int mdisp = msiz<8*1024?0U:msiz<8*1024*1024?1U:2U; ++ CImg _title(64); ++ if (!title) cimg_snprintf(_title,_title._width,"CImgList<%s>",pixel_type()); ++ std::fprintf(cimg::output(),"%s%s%s%s: %sthis%s = %p, %ssize%s = %u/%u [%u %s], %sdata%s = (CImg<%s>*)%p", ++ cimg::t_magenta,cimg::t_bold,title?title:_title._data,cimg::t_normal, ++ cimg::t_bold,cimg::t_normal,(void*)this, ++ cimg::t_bold,cimg::t_normal,_width,_allocated_width, ++ mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20)), ++ mdisp==0?"b":(mdisp==1?"Kio":"Mio"), ++ cimg::t_bold,cimg::t_normal,pixel_type(),(void*)begin()); ++ if (_data) std::fprintf(cimg::output(),"..%p.\n",(void*)((char*)end() - 1)); ++ else std::fprintf(cimg::output(),".\n"); ++ ++ char tmp[16] = { 0 }; ++ cimglist_for(*this,ll) { ++ cimg_snprintf(tmp,sizeof(tmp),"[%d]",ll); ++ std::fprintf(cimg::output()," "); ++ _data[ll].print(tmp,display_stats); ++ if (ll==3 && width()>8) { ll = width() - 5; std::fprintf(cimg::output()," ...\n"); } ++ } ++ std::fflush(cimg::output()); ++ return *this; ++ } ++ ++ //! Display the current CImgList instance in an existing CImgDisplay window (by reference). ++ /** ++ \param disp Reference to an existing CImgDisplay instance, where the current image list will be displayed. ++ \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. ++ \param align Appending alignmenet. ++ \note This function displays the list images of the current CImgList instance into an existing ++ CImgDisplay window. ++ Images of the list are appended in a single temporarly image for visualization purposes. ++ The function returns immediately. ++ **/ ++ const CImgList& display(CImgDisplay &disp, const char axis='x', const float align=0) const { ++ disp.display(*this,axis,align); ++ return *this; ++ } ++ ++ //! Display the current CImgList instance in a new display window. ++ /** ++ \param disp Display window. ++ \param display_info Tells if image information are displayed on the standard output. ++ \param axis Alignment axis for images viewing. ++ \param align Apending alignment. ++ \param[in,out] XYZ Contains the XYZ coordinates at start / exit of the function. ++ \param exit_on_anykey Exit function when any key is pressed. ++ \note 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 appended 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. ++ **/ ++ const CImgList& display(CImgDisplay &disp, const bool display_info, ++ const char axis='x', const float align=0, ++ unsigned int *const XYZ=0, const bool exit_on_anykey=false) const { ++ bool is_exit = false; ++ return _display(disp,0,0,display_info,axis,align,XYZ,exit_on_anykey,0,true,is_exit); ++ } ++ ++ //! Display the current CImgList instance in a new display window. ++ /** ++ \param title Title of the opening display window. ++ \param display_info Tells if list information must be written on standard output. ++ \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. ++ \param align Appending alignment. ++ \param[in,out] XYZ Contains the XYZ coordinates at start / exit of the function. ++ \param exit_on_anykey Exit function when any key is pressed. ++ **/ ++ const CImgList& display(const char *const title=0, const bool display_info=true, ++ const char axis='x', const float align=0, ++ unsigned int *const XYZ=0, const bool exit_on_anykey=false) const { ++ CImgDisplay disp; ++ bool is_exit = false; ++ return _display(disp,title,0,display_info,axis,align,XYZ,exit_on_anykey,0,true,is_exit); ++ } ++ ++ const CImgList& _display(CImgDisplay &disp, const char *const title, const CImgList *const titles, ++ const bool display_info, const char axis, const float align, unsigned int *const XYZ, ++ const bool exit_on_anykey, const unsigned int orig, const bool is_first_call, ++ bool &is_exit) const { ++ if (is_empty()) ++ throw CImgInstanceException(_cimglist_instance ++ "display(): Empty instance.", ++ cimglist_instance); ++ if (!disp) { ++ if (axis=='x') { ++ unsigned int sum_width = 0, max_height = 0; ++ cimglist_for(*this,l) { ++ const CImg &img = _data[l]; ++ const unsigned int ++ w = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,false), ++ h = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,true); ++ sum_width+=w; ++ if (h>max_height) max_height = h; ++ } ++ disp.assign(cimg_fitscreen(sum_width,max_height,1),title?title:titles?titles->__display()._data:0,1); ++ } else { ++ unsigned int max_width = 0, sum_height = 0; ++ cimglist_for(*this,l) { ++ const CImg &img = _data[l]; ++ const unsigned int ++ w = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,false), ++ h = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,true); ++ if (w>max_width) max_width = w; ++ sum_height+=h; ++ } ++ disp.assign(cimg_fitscreen(max_width,sum_height,1),title?title:titles?titles->__display()._data:0,1); ++ } ++ if (!title && !titles) disp.set_title("CImgList<%s> (%u)",pixel_type(),_width); ++ } else if (title) disp.set_title("%s",title); ++ else if (titles) disp.set_title("%s",titles->__display()._data); ++ const CImg dtitle = CImg::string(disp.title()); ++ if (display_info) print(disp.title()); ++ disp.show().flush(); ++ ++ if (_width==1) { ++ const unsigned int dw = disp._width, dh = disp._height; ++ if (!is_first_call) ++ disp.resize(cimg_fitscreen(_data[0]._width,_data[0]._height,_data[0]._depth),false); ++ disp.set_title("%s (%ux%ux%ux%u)", ++ dtitle.data(),_data[0]._width,_data[0]._height,_data[0]._depth,_data[0]._spectrum); ++ _data[0]._display(disp,0,false,XYZ,exit_on_anykey,!is_first_call); ++ if (disp.key()) is_exit = true; ++ disp.resize(cimg_fitscreen(dw,dh,1),false).set_title("%s",dtitle.data()); ++ } else { ++ bool disp_resize = !is_first_call; ++ while (!disp.is_closed() && !is_exit) { ++ const CImg s = _select(disp,0,true,axis,align,exit_on_anykey,orig,disp_resize,!is_first_call,true); ++ disp_resize = true; ++ if (s[0]<0 && !disp.wheel()) { // No selections done. ++ if (disp.button()&2) { disp.flush(); break; } ++ is_exit = true; ++ } else if (disp.wheel()) { // Zoom in/out. ++ const int wheel = disp.wheel(); ++ disp.set_wheel(); ++ if (!is_first_call && wheel<0) break; ++ if (wheel>0 && _width>=4) { ++ const unsigned int ++ delta = std::max(1U,(unsigned int)cimg::round(0.3*_width)), ++ ind0 = (unsigned int)std::max(0,s[0] - (int)delta), ++ ind1 = (unsigned int)std::min(width() - 1,s[0] + (int)delta); ++ if ((ind0!=0 || ind1!=_width - 1) && ind1 - ind0>=3) { ++ const CImgList sublist = get_shared_images(ind0,ind1); ++ CImgList t_sublist; ++ if (titles) t_sublist = titles->get_shared_images(ind0,ind1); ++ sublist._display(disp,0,titles?&t_sublist:0,false,axis,align,XYZ,exit_on_anykey, ++ orig + ind0,false,is_exit); ++ } ++ } ++ } else if (s[0]!=0 || s[1]!=width() - 1) { ++ const CImgList sublist = get_shared_images(s[0],s[1]); ++ CImgList t_sublist; ++ if (titles) t_sublist = titles->get_shared_images(s[0],s[1]); ++ sublist._display(disp,0,titles?&t_sublist:0,false,axis,align,XYZ,exit_on_anykey, ++ orig + s[0],false,is_exit); ++ } ++ disp.set_title("%s",dtitle.data()); ++ } ++ } ++ return *this; ++ } ++ ++ // [internal] Return string to describe display title. ++ CImg __display() const { ++ CImg res, str; ++ cimglist_for(*this,l) { ++ CImg::string(_data[l]).move_to(str); ++ if (l!=width() - 1) { ++ str.resize(str._width + 1,1,1,1,0); ++ str[str._width - 2] = ','; ++ str[str._width - 1] = ' '; ++ } ++ res.append(str,'x'); ++ } ++ if (!res) return CImg(1,1,1,1,0).move_to(res); ++ cimg::strellipsize(res,128,false); ++ if (_width>1) { ++ const unsigned int l = (unsigned int)std::strlen(res); ++ if (res._width<=l + 16) res.resize(l + 16,1,1,1,0); ++ cimg_snprintf(res._data + l,16," (#%u)",_width); ++ } ++ return res; ++ } ++ ++ //! Save list into a file. ++ /** ++ \param filename Filename to write data to. ++ \param number When positive, represents an index added to the filename. Otherwise, no number is added. ++ \param digits Number of digits used for adding the number to the filename. ++ **/ ++ const CImgList& save(const char *const filename, const int number=-1, const unsigned int digits=6) const { ++ if (!filename) ++ throw CImgArgumentException(_cimglist_instance ++ "save(): Specified filename is (null).", ++ cimglist_instance); ++ // Do not test for empty instances, since .cimg format is able to manage empty instances. ++ const bool is_stdout = *filename=='-' && (!filename[1] || filename[1]=='.'); ++ const char *const ext = cimg::split_filename(filename); ++ CImg nfilename(1024); ++ const char *const fn = is_stdout?filename:number>=0?cimg::number_filename(filename,number,digits,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 ++ if (!cimg::strcasecmp(ext,"cimgz")) return save_cimg(fn,true); ++ else if (!cimg::strcasecmp(ext,"cimg") || !*ext) return save_cimg(fn,false); ++ else if (!cimg::strcasecmp(ext,"yuv")) return save_yuv(fn,444,true); ++ else 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,"mp4") || ++ !cimg::strcasecmp(ext,"mkv") || ++ !cimg::strcasecmp(ext,"mpe") || ++ !cimg::strcasecmp(ext,"movie") || ++ !cimg::strcasecmp(ext,"ogm") || ++ !cimg::strcasecmp(ext,"ogg") || ++ !cimg::strcasecmp(ext,"ogv") || ++ !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_video(fn); ++#ifdef cimg_use_tiff ++ else if (!cimg::strcasecmp(ext,"tif") || ++ !cimg::strcasecmp(ext,"tiff")) return save_tiff(fn); ++#endif ++ else if (!cimg::strcasecmp(ext,"gz")) return save_gzip_external(fn); ++ else { ++ if (_width==1) _data[0].save(fn,-1); ++ else cimglist_for(*this,l) { _data[l].save(fn,is_stdout?-1:l); if (is_stdout) std::fputc(EOF,cimg::_stdout()); } ++ } ++ return *this; ++ } ++ ++ //! Tell if an image list can be saved as one single file. ++ /** ++ \param filename Filename, as a C-string. ++ \return \c true if the file format supports multiple images, \c false otherwise. ++ **/ ++ static bool is_saveable(const char *const filename) { ++ const char *const ext = cimg::split_filename(filename); ++ if (!cimg::strcasecmp(ext,"cimgz") || ++#ifdef cimg_use_tiff ++ !cimg::strcasecmp(ext,"tif") || ++ !cimg::strcasecmp(ext,"tiff") || ++#endif ++ !cimg::strcasecmp(ext,"yuv") || ++ !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,"mp4") || ++ !cimg::strcasecmp(ext,"mkv") || ++ !cimg::strcasecmp(ext,"mpe") || ++ !cimg::strcasecmp(ext,"movie") || ++ !cimg::strcasecmp(ext,"ogm") || ++ !cimg::strcasecmp(ext,"ogg") || ++ !cimg::strcasecmp(ext,"ogv") || ++ !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 true; ++ return false; ++ } ++ ++ //! Save image sequence as a GIF animated file. ++ /** ++ \param filename Filename to write data to. ++ \param fps Number of desired frames per second. ++ \param nb_loops Number of loops (\c 0 for infinite looping). ++ **/ ++ const CImgList& save_gif_external(const char *const filename, const float fps=25, ++ const unsigned int nb_loops=0) { ++ CImg command(1024), filename_tmp(256), filename_tmp2(256); ++ CImgList filenames; ++ std::FILE *file = 0; ++ ++#ifdef cimg_use_png ++#define _cimg_save_gif_ext "png" ++#else ++#define _cimg_save_gif_ext "ppm" ++#endif ++ ++ do { ++ cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", ++ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); ++ cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_000001." _cimg_save_gif_ext,filename_tmp._data); ++ if ((file=std_fopen(filename_tmp2,"rb"))!=0) cimg::fclose(file); ++ } while (file); ++ cimglist_for(*this,l) { ++ cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_%.6u." _cimg_save_gif_ext,filename_tmp._data,l + 1); ++ CImg::string(filename_tmp2).move_to(filenames); ++ if (_data[l]._depth>1 || _data[l]._spectrum!=3) _data[l].get_resize(-100,-100,1,3).save(filename_tmp2); ++ else _data[l].save(filename_tmp2); ++ } ++ cimg_snprintf(command,command._width,"%s -delay %u -loop %u", ++ cimg::imagemagick_path(),(unsigned int)std::max(0.0f,cimg::round(100/fps)),nb_loops); ++ CImg::string(command).move_to(filenames,0); ++ cimg_snprintf(command,command._width,"\"%s\"", ++ CImg::string(filename)._system_strescape().data()); ++ CImg::string(command).move_to(filenames); ++ CImg _command = filenames>'x'; ++ cimg_for(_command,p,char) if (!*p) *p = ' '; ++ _command.back() = 0; ++ ++ cimg::system(_command); ++ file = std_fopen(filename,"rb"); ++ if (!file) ++ throw CImgIOException(_cimglist_instance ++ "save_gif_external(): Failed to save file '%s' with external command 'magick/convert'.", ++ cimglist_instance, ++ filename); ++ else cimg::fclose(file); ++ cimglist_for_in(*this,1,filenames._width - 1,l) std::remove(filenames[l]); ++ return *this; ++ } ++ ++ //! Save list as a YUV image sequence file. ++ /** ++ \param filename Filename to write data to. ++ \param chroma_subsampling Type of chroma subsampling. Can be { 420 | 422 | 444 }. ++ \param is_rgb Tells if the RGB to YUV conversion must be done for saving. ++ **/ ++ const CImgList& save_yuv(const char *const filename=0, ++ const unsigned int chroma_subsampling=444, ++ const bool is_rgb=true) const { ++ return _save_yuv(0,filename,chroma_subsampling,is_rgb); ++ } ++ ++ //! Save image sequence into a YUV file. ++ /** ++ \param file File to write data to. ++ \param chroma_subsampling Type of chroma subsampling. Can be { 420 | 422 | 444 }. ++ \param is_rgb Tells if the RGB to YUV conversion must be done for saving. ++ **/ ++ const CImgList& save_yuv(std::FILE *const file, ++ const unsigned int chroma_subsampling=444, ++ const bool is_rgb=true) const { ++ return _save_yuv(file,0,chroma_subsampling,is_rgb); ++ } ++ ++ const CImgList& _save_yuv(std::FILE *const file, const char *const filename, ++ const unsigned int chroma_subsampling, ++ const bool is_rgb) const { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimglist_instance ++ "save_yuv(): Specified filename is (null).", ++ cimglist_instance); ++ if (chroma_subsampling!=420 && chroma_subsampling!=422 && chroma_subsampling!=444) ++ throw CImgArgumentException(_cimglist_instance ++ "save_yuv(): Specified chroma subsampling %u is invalid, for file '%s'.", ++ cimglist_instance, ++ chroma_subsampling,filename?filename:"(FILE*)"); ++ if (is_empty()) { cimg::fempty(file,filename); return *this; } ++ const unsigned int ++ cfx = chroma_subsampling==420 || chroma_subsampling==422?2:1, ++ cfy = chroma_subsampling==420?2:1, ++ w0 = (*this)[0]._width, h0 = (*this)[0]._height, ++ width0 = w0 + (w0%cfx), height0 = h0 + (h0%cfy); ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); ++ cimglist_for(*this,l) { ++ const CImg &frame = (*this)[l]; ++ CImg YUV; ++ if (sizeof(T)==1 && !is_rgb && ++ frame._width==width0 && frame._height==height0 && frame._depth==1 && frame._spectrum==3) ++ YUV.assign((unsigned char*)frame._data,width0,height0,1,3,true); ++ else { ++ YUV = frame; ++ if (YUV._width!=width0 || YUV._height!=height0 || YUV._depth!=1) YUV.resize(width0,height0,1,-100,0); ++ if (YUV._spectrum!=3) YUV.resize(-100,-100,1,3,YUV._spectrum==1?1:0); ++ if (is_rgb) YUV.RGBtoYCbCr(); ++ } ++ if (chroma_subsampling==444) ++ cimg::fwrite(YUV._data,(size_t)YUV._width*YUV._height*3,nfile); ++ else { ++ cimg::fwrite(YUV._data,(size_t)YUV._width*YUV._height,nfile); ++ CImg UV = YUV.get_channels(1,2); ++ UV.resize(UV._width/cfx,UV._height/cfy,1,2,2); ++ cimg::fwrite(UV._data,(size_t)UV._width*UV._height*2,nfile); ++ } ++ } ++ if (!file) cimg::fclose(nfile); ++ return *this; ++ } ++ ++ //! Save list into a .cimg file. ++ /** ++ \param filename Filename to write data to. ++ \param is_compressed Tells if data compression must be enabled. ++ **/ ++ const CImgList& save_cimg(const char *const filename, const bool is_compressed=false) const { ++ return _save_cimg(0,filename,is_compressed); ++ } ++ ++ const CImgList& _save_cimg(std::FILE *const file, const char *const filename, const bool is_compressed) const { ++ if (!file && !filename) ++ throw CImgArgumentException(_cimglist_instance ++ "save_cimg(): Specified filename is (null).", ++ cimglist_instance); ++#ifndef cimg_use_zlib ++ if (is_compressed) ++ cimg::warn(_cimglist_instance ++ "save_cimg(): Unable to save compressed data in file '%s' unless zlib is enabled, " ++ "saving them uncompressed.", ++ cimglist_instance, ++ filename?filename:"(FILE*)"); ++#endif ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); ++ const char *const ptype = pixel_type(), *const etype = cimg::endianness()?"big":"little"; ++ if (std::strstr(ptype,"unsigned")==ptype) std::fprintf(nfile,"%u unsigned_%s %s_endian\n",_width,ptype + 9,etype); ++ else std::fprintf(nfile,"%u %s %s_endian\n",_width,ptype,etype); ++ cimglist_for(*this,l) { ++ const CImg& img = _data[l]; ++ std::fprintf(nfile,"%u %u %u %u",img._width,img._height,img._depth,img._spectrum); ++ 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 failed_to_compress = true; ++ if (is_compressed) { ++#ifdef cimg_use_zlib ++ const ulongT siz = sizeof(T)*ref.size(); ++ uLongf csiz = siz + siz/100 + 16; ++ Bytef *const cbuf = new Bytef[csiz]; ++ if (compress(cbuf,&csiz,(Bytef*)ref._data,siz)) ++ cimg::warn(_cimglist_instance ++ "save_cimg(): Failed to save compressed data for file '%s', saving them uncompressed.", ++ cimglist_instance, ++ filename?filename:"(FILE*)"); ++ else { ++ std::fprintf(nfile," #%lu\n",csiz); ++ cimg::fwrite(cbuf,csiz,nfile); ++ delete[] cbuf; ++ failed_to_compress = false; ++ } ++#endif ++ } ++ if (failed_to_compress) { // Write in a non-compressed way. ++ std::fputc('\n',nfile); ++ cimg::fwrite(ref._data,ref.size(),nfile); ++ } ++ } else std::fputc('\n',nfile); ++ } ++ if (!file) cimg::fclose(nfile); ++ return *this; ++ } ++ ++ //! Save list into a .cimg file. ++ /** ++ \param file File to write data to. ++ \param is_compressed Tells if data compression must be enabled. ++ **/ ++ const CImgList& save_cimg(std::FILE *file, const bool is_compressed=false) const { ++ return _save_cimg(file,0,is_compressed); ++ } ++ ++ const CImgList& _save_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 c0) const { ++#define _cimg_save_cimg_case(Ts,Tss) \ ++ if (!saved && !cimg::strcasecmp(Ts,str_pixeltype)) { \ ++ for (unsigned int l = 0; l0) { \ ++ if (l=W || y0>=H || z0>=D || c0>=D) cimg::fseek(nfile,W*H*D*C*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, \ ++ c1 = c0 + img._spectrum - 1, \ ++ nx1 = x1>=W?W - 1:x1, \ ++ ny1 = y1>=H?H - 1:y1, \ ++ nz1 = z1>=D?D - 1:z1, \ ++ nc1 = c1>=C?C - 1:c1; \ ++ CImg raw(1 + nx1 - x0); \ ++ const unsigned int skipvb = c0*W*H*D*sizeof(Tss); \ ++ if (skipvb) cimg::fseek(nfile,skipvb,SEEK_CUR); \ ++ for (unsigned int v = 1 + nc1 - c0; v; --v) { \ ++ const unsigned int skipzb = z0*W*H*sizeof(Tss); \ ++ if (skipzb) cimg::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::fseek(nfile,skipyb,SEEK_CUR); \ ++ for (unsigned int y = 1 + ny1 - y0; y; --y) { \ ++ const unsigned int skipxb = x0*sizeof(Tss); \ ++ if (skipxb) cimg::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::fseek(nfile,skipxe,SEEK_CUR); \ ++ } \ ++ const unsigned int skipye = (H - 1 - ny1)*W*sizeof(Tss); \ ++ if (skipye) cimg::fseek(nfile,skipye,SEEK_CUR); \ ++ } \ ++ const unsigned int skipze = (D - 1 - nz1)*W*H*sizeof(Tss); \ ++ if (skipze) cimg::fseek(nfile,skipze,SEEK_CUR); \ ++ } \ ++ const unsigned int skipve = (C - 1 - nc1)*W*H*D*sizeof(Tss); \ ++ if (skipve) cimg::fseek(nfile,skipve,SEEK_CUR); \ ++ } \ ++ } \ ++ } \ ++ saved = true; \ ++ } ++ ++ if (!file && !filename) ++ throw CImgArgumentException(_cimglist_instance ++ "save_cimg(): Specified filename is (null).", ++ cimglist_instance); ++ if (is_empty()) ++ throw CImgInstanceException(_cimglist_instance ++ "save_cimg(): Empty instance, for file '%s'.", ++ cimglist_instance, ++ filename?filename:"(FILE*)"); ++ ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"rb+"); ++ bool saved = false, endian = cimg::endianness(); ++ CImg tmp(256), str_pixeltype(256), str_endian(256); ++ *tmp = *str_pixeltype = *str_endian = 0; ++ unsigned int j, N, W, H, D, C; ++ int i, err; ++ j = 0; while ((i=std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = 0; ++ err = cimg_sscanf(tmp,"%u%*c%255[A-Za-z64_]%*c%255[sA-Za-z_ ]",&N,str_pixeltype._data,str_endian._data); ++ if (err<2) { ++ if (!file) cimg::fclose(nfile); ++ throw CImgIOException(_cimglist_instance ++ "save_cimg(): CImg header not found in file '%s'.", ++ cimglist_instance, ++ 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 = std::min(N,n0 + _width); ++ _cimg_save_cimg_case("bool",bool); ++ _cimg_save_cimg_case("unsigned_char",unsigned char); ++ _cimg_save_cimg_case("uchar",unsigned char); ++ _cimg_save_cimg_case("char",char); ++ _cimg_save_cimg_case("unsigned_short",unsigned short); ++ _cimg_save_cimg_case("ushort",unsigned short); ++ _cimg_save_cimg_case("short",short); ++ _cimg_save_cimg_case("unsigned_int",unsigned int); ++ _cimg_save_cimg_case("uint",unsigned int); ++ _cimg_save_cimg_case("int",int); ++ _cimg_save_cimg_case("unsigned_int64",uint64T); ++ _cimg_save_cimg_case("uint64",uint64T); ++ _cimg_save_cimg_case("int64",int64T); ++ _cimg_save_cimg_case("float",float); ++ _cimg_save_cimg_case("double",double); ++ if (!saved) { ++ if (!file) cimg::fclose(nfile); ++ throw CImgIOException(_cimglist_instance ++ "save_cimg(): Unsupported data type '%s' for file '%s'.", ++ cimglist_instance, ++ filename?filename:"(FILE*)",str_pixeltype._data); ++ } ++ if (!file) cimg::fclose(nfile); ++ return *this; ++ } ++ ++ //! Insert the image instance into into an existing .cimg file, at specified coordinates. ++ /** ++ \param filename Filename to write data to. ++ \param n0 Starting index of images to write. ++ \param x0 Starting X-coordinates of image regions to write. ++ \param y0 Starting Y-coordinates of image regions to write. ++ \param z0 Starting Z-coordinates of image regions to write. ++ \param c0 Starting C-coordinates of image regions to write. ++ **/ ++ 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 c0) const { ++ return _save_cimg(0,filename,n0,x0,y0,z0,c0); ++ } ++ ++ //! Insert the image instance into into an existing .cimg file, at specified coordinates. ++ /** ++ \param file File to write data to. ++ \param n0 Starting index of images to write. ++ \param x0 Starting X-coordinates of image regions to write. ++ \param y0 Starting Y-coordinates of image regions to write. ++ \param z0 Starting Z-coordinates of image regions to write. ++ \param c0 Starting C-coordinates of image regions to write. ++ **/ ++ const CImgList& save_cimg(std::FILE *const file, ++ const unsigned int n0, ++ const unsigned int x0, const unsigned int y0, ++ const unsigned int z0, const unsigned int c0) const { ++ return _save_cimg(file,0,n0,x0,y0,z0,c0); ++ } ++ ++ static void _save_empty_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 dc) { ++ std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); ++ const ulongT siz = (ulongT)dx*dy*dz*dc*sizeof(T); ++ std::fprintf(nfile,"%u %s\n",nb,pixel_type()); ++ for (unsigned int i=nb; i; --i) { ++ std::fprintf(nfile,"%u %u %u %u\n",dx,dy,dz,dc); ++ for (ulongT off = siz; off; --off) std::fputc(0,nfile); ++ } ++ if (!file) cimg::fclose(nfile); ++ } ++ ++ //! Save empty (non-compressed) .cimg file with specified dimensions. ++ /** ++ \param filename Filename to write data to. ++ \param nb Number of images to write. ++ \param dx Width of images in the written file. ++ \param dy Height of images in the written file. ++ \param dz Depth of images in the written file. ++ \param dc Spectrum of images in the written file. ++ **/ ++ 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 dc=1) { ++ return _save_empty_cimg(0,filename,nb,dx,dy,dz,dc); ++ } ++ ++ //! Save empty .cimg file with specified dimensions. ++ /** ++ \param file File to write data to. ++ \param nb Number of images to write. ++ \param dx Width of images in the written file. ++ \param dy Height of images in the written file. ++ \param dz Depth of images in the written file. ++ \param dc Spectrum of images in the written file. ++ **/ ++ static void save_empty_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 dc=1) { ++ return _save_empty_cimg(file,0,nb,dx,dy,dz,dc); ++ } ++ ++ //! Save list as a TIFF file. ++ /** ++ \param filename Filename to write data to. ++ \param compression_type Compression mode used to write data. ++ \param voxel_size Voxel size, to be stored in the filename. ++ \param description Description, to be stored in the filename. ++ \param use_bigtiff Allow to save big tiff files (>4Gb). ++ **/ ++ const CImgList& save_tiff(const char *const filename, const unsigned int compression_type=0, ++ const float *const voxel_size=0, const char *const description=0, ++ const bool use_bigtiff=true) const { ++ if (!filename) ++ throw CImgArgumentException(_cimglist_instance ++ "save_tiff(): Specified filename is (null).", ++ cimglist_instance); ++ if (is_empty()) { cimg::fempty(0,filename); return *this; } ++ ++#ifndef cimg_use_tiff ++ if (_width==1) _data[0].save_tiff(filename,compression_type,voxel_size,description,use_bigtiff); ++ else cimglist_for(*this,l) { ++ CImg nfilename(1024); ++ cimg::number_filename(filename,l,6,nfilename); ++ _data[l].save_tiff(nfilename,compression_type,voxel_size,description,use_bigtiff); ++ } ++#else ++ ulongT siz = 0; ++ cimglist_for(*this,l) siz+=_data[l].size(); ++ const bool _use_bigtiff = use_bigtiff && sizeof(siz)>=8 && siz*sizeof(T)>=1UL<<31; // No bigtiff for small images. ++ TIFF *tif = TIFFOpen(filename,_use_bigtiff?"w8":"w4"); ++ if (tif) { ++ for (unsigned int dir = 0, l = 0; l<_width; ++l) { ++ const CImg& img = (*this)[l]; ++ cimg_forZ(img,z) img._save_tiff(tif,dir++,z,compression_type,voxel_size,description); ++ } ++ TIFFClose(tif); ++ } else ++ throw CImgIOException(_cimglist_instance ++ "save_tiff(): Failed to open stream for file '%s'.", ++ cimglist_instance, ++ filename); ++#endif ++ return *this; ++ } ++ ++ //! Save list as a gzipped file, using external tool 'gzip'. ++ /** ++ \param filename Filename to write data to. ++ **/ ++ const CImgList& save_gzip_external(const char *const filename) const { ++ if (!filename) ++ throw CImgIOException(_cimglist_instance ++ "save_gzip_external(): Specified filename is (null).", ++ cimglist_instance); ++ CImg command(1024), filename_tmp(256), body(256); ++ const char ++ *ext = cimg::split_filename(filename,body), ++ *ext2 = cimg::split_filename(body,0); ++ std::FILE *file; ++ do { ++ if (!cimg::strcasecmp(ext,"gz")) { ++ if (*ext2) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", ++ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2); ++ else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg", ++ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); ++ } else { ++ if (*ext) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", ++ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext); ++ else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg", ++ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); ++ } ++ if ((file=std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); ++ } while (file); ++ ++ if (is_saveable(body)) { ++ save(filename_tmp); ++ cimg_snprintf(command,command._width,"%s -c \"%s\" > \"%s\"", ++ cimg::gzip_path(), ++ CImg::string(filename_tmp)._system_strescape().data(), ++ CImg::string(filename)._system_strescape().data()); ++ cimg::system(command); ++ file = std_fopen(filename,"rb"); ++ if (!file) ++ throw CImgIOException(_cimglist_instance ++ "save_gzip_external(): Failed to save file '%s' with external command 'gzip'.", ++ cimglist_instance, ++ filename); ++ else cimg::fclose(file); ++ std::remove(filename_tmp); ++ } else { ++ CImg nfilename(1024); ++ cimglist_for(*this,l) { ++ cimg::number_filename(body,l,6,nfilename); ++ if (*ext) cimg_sprintf(nfilename._data + std::strlen(nfilename),".%s",ext); ++ _data[l].save_gzip_external(nfilename); ++ } ++ } ++ return *this; ++ } ++ ++ //! Save image sequence, using the OpenCV library. ++ /** ++ \param filename Filename to write data to. ++ \param fps Number of frames per second. ++ \param codec Type of compression (See http://www.fourcc.org/codecs.php to see available codecs). ++ \param keep_open Tells if the video writer associated to the specified filename ++ must be kept open or not (to allow frames to be added in the same file afterwards). ++ **/ ++ const CImgList& save_video(const char *const filename, const unsigned int fps=25, ++ const char *codec=0, const bool keep_open=false) const { ++#ifndef cimg_use_opencv ++ cimg::unused(codec,keep_open); ++ return save_ffmpeg_external(filename,fps); ++#else ++ static CvVideoWriter *writers[32] = { 0 }; ++ static CImgList filenames(32); ++ static CImg sizes(32,2,1,1,0); ++ static int last_used_index = -1; ++ ++ // Detect if a video writer already exists for the specified filename. ++ cimg::mutex(9); ++ int index = -1; ++ if (filename) { ++ if (last_used_index>=0 && !std::strcmp(filename,filenames[last_used_index])) { ++ index = last_used_index; ++ } else cimglist_for(filenames,l) if (filenames[l] && !std::strcmp(filename,filenames[l])) { ++ index = l; break; ++ } ++ } else index = last_used_index; ++ cimg::mutex(9,0); ++ ++ // Find empty slot for capturing video stream. ++ if (index<0) { ++ if (!filename) ++ throw CImgArgumentException(_cimglist_instance ++ "save_video(): No already open video writer found. You must specify a " ++ "non-(null) filename argument for the first call.", ++ cimglist_instance); ++ else { cimg::mutex(9); cimglist_for(filenames,l) if (!filenames[l]) { index = l; break; } cimg::mutex(9,0); } ++ if (index<0) ++ throw CImgIOException(_cimglist_instance ++ "save_video(): File '%s', no video writer slots available. " ++ "You have to release some of your previously opened videos.", ++ cimglist_instance,filename); ++ if (is_empty()) ++ throw CImgInstanceException(_cimglist_instance ++ "save_video(): Instance list is empty.", ++ cimglist_instance); ++ const unsigned int W = _data?_data[0]._width:0, H = _data?_data[0]._height:0; ++ if (!W || !H) ++ throw CImgInstanceException(_cimglist_instance ++ "save_video(): Frame [0] is an empty image.", ++ cimglist_instance); ++ ++#define _cimg_docase(x) ((x)>='a'&&(x)<='z'?(x) + 'A' - 'a':(x)) ++ ++ const char ++ *const _codec = codec && *codec?codec:cimg_OS==2?"mpeg":"mp4v", ++ codec0 = _cimg_docase(_codec[0]), ++ codec1 = _codec[0]?_cimg_docase(_codec[1]):0, ++ codec2 = _codec[1]?_cimg_docase(_codec[2]):0, ++ codec3 = _codec[2]?_cimg_docase(_codec[3]):0; ++ cimg::mutex(9); ++ writers[index] = cvCreateVideoWriter(filename,CV_FOURCC(codec0,codec1,codec2,codec3), ++ fps,cvSize(W,H)); ++ CImg::string(filename).move_to(filenames[index]); ++ sizes(index,0) = W; sizes(index,1) = H; ++ cimg::mutex(9,0); ++ if (!writers[index]) ++ throw CImgIOException(_cimglist_instance ++ "save_video(): File '%s', unable to initialize video writer with codec '%c%c%c%c'.", ++ cimglist_instance,filename, ++ codec0,codec1,codec2,codec3); ++ } ++ ++ if (!is_empty()) { ++ const unsigned int W = sizes(index,0), H = sizes(index,1); ++ cimg::mutex(9); ++ IplImage *ipl = cvCreateImage(cvSize(W,H),8,3); ++ cimglist_for(*this,l) { ++ CImg &src = _data[l]; ++ if (src.is_empty()) ++ cimg::warn(_cimglist_instance ++ "save_video(): Skip empty frame %d for file '%s'.", ++ cimglist_instance,l,filename); ++ if (src._depth>1 || src._spectrum>3) ++ cimg::warn(_cimglist_instance ++ "save_video(): Frame %u has incompatible dimension (%u,%u,%u,%u). " ++ "Some image data may be ignored when writing frame into video file '%s'.", ++ cimglist_instance,l,src._width,src._height,src._depth,src._spectrum,filename); ++ if (src._width==W && src._height==H && src._spectrum==3) { ++ const T *ptr_r = src.data(0,0,0,0), *ptr_g = src.data(0,0,0,1), *ptr_b = src.data(0,0,0,2); ++ char *ptrd = ipl->imageData; ++ cimg_forXY(src,x,y) { ++ *(ptrd++) = (char)*(ptr_b++); *(ptrd++) = (char)*(ptr_g++); *(ptrd++) = (char)*(ptr_r++); ++ } ++ } else { ++ CImg _src(src,false); ++ _src.channels(0,std::min(_src._spectrum - 1,2U)).resize(W,H); ++ _src.resize(W,H,1,3,_src._spectrum==1); ++ const unsigned char *ptr_r = _src.data(0,0,0,0), *ptr_g = _src.data(0,0,0,1), *ptr_b = _src.data(0,0,0,2); ++ char *ptrd = ipl->imageData; ++ cimg_forXY(_src,x,y) { ++ *(ptrd++) = (char)*(ptr_b++); *(ptrd++) = (char)*(ptr_g++); *(ptrd++) = (char)*(ptr_r++); ++ } ++ } ++ cvWriteFrame(writers[index],ipl); ++ } ++ cvReleaseImage(&ipl); ++ cimg::mutex(9,0); ++ } ++ ++ cimg::mutex(9); ++ if (!keep_open) { ++ cvReleaseVideoWriter(&writers[index]); ++ writers[index] = 0; ++ filenames[index].assign(); ++ sizes(index,0) = sizes(index,1) = 0; ++ last_used_index = -1; ++ } else last_used_index = index; ++ cimg::mutex(9,0); ++ ++ return *this; ++#endif ++ } ++ ++ //! Save image sequence, using the external tool 'ffmpeg'. ++ /** ++ \param filename Filename to write data to. ++ \param fps Number of frames per second. ++ \param codec Type of compression. ++ \param bitrate Output bitrate ++ **/ ++ const CImgList& save_ffmpeg_external(const char *const filename, const unsigned int fps=25, ++ const char *const codec=0, const unsigned int bitrate=2048) const { ++ if (!filename) ++ throw CImgArgumentException(_cimglist_instance ++ "save_ffmpeg_external(): Specified filename is (null).", ++ cimglist_instance); ++ if (is_empty()) { cimg::fempty(0,filename); return *this; } ++ ++ const char ++ *const ext = cimg::split_filename(filename), ++ *const _codec = codec?codec:!cimg::strcasecmp(ext,"flv")?"flv":"mpeg2video"; ++ ++ CImg command(1024), filename_tmp(256), filename_tmp2(256); ++ CImgList filenames; ++ std::FILE *file = 0; ++ cimglist_for(*this,l) if (!_data[l].is_sameXYZ(_data[0])) ++ throw CImgInstanceException(_cimglist_instance ++ "save_ffmpeg_external(): Invalid instance dimensions for file '%s'.", ++ cimglist_instance, ++ filename); ++ do { ++ cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s", ++ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand()); ++ cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_000001.ppm",filename_tmp._data); ++ if ((file=std_fopen(filename_tmp2,"rb"))!=0) cimg::fclose(file); ++ } while (file); ++ cimglist_for(*this,l) { ++ cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_%.6u.ppm",filename_tmp._data,l + 1); ++ CImg::string(filename_tmp2).move_to(filenames); ++ if (_data[l]._depth>1 || _data[l]._spectrum!=3) _data[l].get_resize(-100,-100,1,3).save_pnm(filename_tmp2); ++ else _data[l].save_pnm(filename_tmp2); ++ } ++ cimg_snprintf(command,command._width,"%s -i \"%s_%%6d.ppm\" -vcodec %s -b %uk -r %u -y \"%s\"", ++ cimg::ffmpeg_path(), ++ CImg::string(filename_tmp)._system_strescape().data(), ++ _codec,bitrate,fps, ++ CImg::string(filename)._system_strescape().data()); ++ cimg::system(command); ++ file = std_fopen(filename,"rb"); ++ if (!file) ++ throw CImgIOException(_cimglist_instance ++ "save_ffmpeg_external(): Failed to save file '%s' with external command 'ffmpeg'.", ++ cimglist_instance, ++ filename); ++ else cimg::fclose(file); ++ cimglist_for(*this,l) std::remove(filenames[l]); ++ return *this; ++ } ++ ++ //! Serialize a CImgList instance into a raw CImg buffer. ++ /** ++ \param is_compressed tells if zlib compression must be used for serialization ++ (this requires 'cimg_use_zlib' been enabled). ++ **/ ++ CImg get_serialize(const bool is_compressed=false) const { ++#ifndef cimg_use_zlib ++ if (is_compressed) ++ cimg::warn(_cimglist_instance ++ "get_serialize(): Unable to compress data unless zlib is enabled, " ++ "storing them uncompressed.", ++ cimglist_instance); ++#endif ++ CImgList stream; ++ CImg tmpstr(128); ++ const char *const ptype = pixel_type(), *const etype = cimg::endianness()?"big":"little"; ++ if (std::strstr(ptype,"unsigned")==ptype) ++ cimg_snprintf(tmpstr,tmpstr._width,"%u unsigned_%s %s_endian\n",_width,ptype + 9,etype); ++ else ++ cimg_snprintf(tmpstr,tmpstr._width,"%u %s %s_endian\n",_width,ptype,etype); ++ CImg::string(tmpstr,false).move_to(stream); ++ cimglist_for(*this,l) { ++ const CImg& img = _data[l]; ++ cimg_snprintf(tmpstr,tmpstr._width,"%u %u %u %u",img._width,img._height,img._depth,img._spectrum); ++ CImg::string(tmpstr,false).move_to(stream); ++ 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 failed_to_compress = true; ++ if (is_compressed) { ++#ifdef cimg_use_zlib ++ const ulongT siz = sizeof(T)*ref.size(); ++ uLongf csiz = (ulongT)compressBound(siz); ++ Bytef *const cbuf = new Bytef[csiz]; ++ if (compress(cbuf,&csiz,(Bytef*)ref._data,siz)) ++ cimg::warn(_cimglist_instance ++ "get_serialize(): Failed to save compressed data, saving them uncompressed.", ++ cimglist_instance); ++ else { ++ cimg_snprintf(tmpstr,tmpstr._width," #%lu\n",csiz); ++ CImg::string(tmpstr,false).move_to(stream); ++ CImg(cbuf,csiz).move_to(stream); ++ delete[] cbuf; ++ failed_to_compress = false; ++ } ++#endif ++ } ++ if (failed_to_compress) { // Write in a non-compressed way. ++ CImg::string("\n",false).move_to(stream); ++ stream.insert(1); ++ stream.back().assign((unsigned char*)ref._data,ref.size()*sizeof(T),1,1,1,true); ++ } ++ } else CImg::string("\n",false).move_to(stream); ++ } ++ cimglist_apply(stream,unroll)('y'); ++ return stream>'y'; ++ } ++ ++ //! Unserialize a CImg serialized buffer into a CImgList list. ++ template ++ static CImgList get_unserialize(const CImg& buffer) { ++#ifdef cimg_use_zlib ++#define _cimgz_unserialize_case(Tss) { \ ++ Bytef *cbuf = 0; \ ++ if (sizeof(t)!=1 || cimg::type::string()==cimg::type::string()) { \ ++ cbuf = new Bytef[csiz]; Bytef *_cbuf = cbuf; \ ++ for (ulongT i = 0; i::get_unserialize(): Unable to unserialize compressed data " \ ++ "unless zlib is enabled.", \ ++ pixel_type()); ++#endif ++ ++#define _cimg_unserialize_case(Ts,Tss) \ ++ if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \ ++ for (unsigned int l = 0; l::unserialize(): Invalid specified size (%u,%u,%u,%u) for " \ ++ "image #%u in serialized buffer.", \ ++ pixel_type(),W,H,D,C,l); \ ++ if (W*H*D*C>0) { \ ++ CImg raw; \ ++ CImg &img = res._data[l]; \ ++ if (err==5) _cimgz_unserialize_case(Tss) \ ++ else if (sizeof(Tss)==sizeof(t) && cimg::type::is_float()==cimg::type::is_float()) { \ ++ raw.assign((Tss*)stream,W,H,D,C,true); \ ++ stream+=raw.size(); \ ++ } else { \ ++ raw.assign(W,H,D,C); \ ++ CImg _raw((unsigned char*)raw._data,W*sizeof(Tss),H,D,C,true); \ ++ cimg_for(_raw,p,unsigned char) *p = (unsigned char)*(stream++); \ ++ } \ ++ if (endian!=cimg::endianness()) cimg::invert_endianness(raw._data,raw.size()); \ ++ raw.move_to(img); \ ++ } \ ++ } \ ++ loaded = true; \ ++ } ++ ++ if (buffer.is_empty()) ++ throw CImgArgumentException("CImgList<%s>::get_unserialize(): Specified serialized buffer is (null).", ++ pixel_type()); ++ CImgList res; ++ const t *stream = buffer._data, *const estream = buffer._data + buffer.size(); ++ bool loaded = false, endian = cimg::endianness(), is_bytef = false; ++ CImg tmp(256), str_pixeltype(256), str_endian(256); ++ *tmp = *str_pixeltype = *str_endian = 0; ++ unsigned int j, N = 0, W, H, D, C; ++ uint64T csiz; ++ int i, err; ++ cimg::unused(is_bytef); ++ do { ++ j = 0; while ((i=(int)*stream)!='\n' && stream::get_unserialize(): CImg header not found in serialized buffer.", ++ pixel_type()); ++ if (!cimg::strncasecmp("little",str_endian,6)) endian = false; ++ else if (!cimg::strncasecmp("big",str_endian,3)) endian = true; ++ res.assign(N); ++ _cimg_unserialize_case("bool",bool); ++ _cimg_unserialize_case("unsigned_char",unsigned char); ++ _cimg_unserialize_case("uchar",unsigned char); ++ _cimg_unserialize_case("char",char); ++ _cimg_unserialize_case("unsigned_short",unsigned short); ++ _cimg_unserialize_case("ushort",unsigned short); ++ _cimg_unserialize_case("short",short); ++ _cimg_unserialize_case("unsigned_int",unsigned int); ++ _cimg_unserialize_case("uint",unsigned int); ++ _cimg_unserialize_case("int",int); ++ _cimg_unserialize_case("unsigned_int64",uint64T); ++ _cimg_unserialize_case("uint64",uint64T); ++ _cimg_unserialize_case("int64",int64T); ++ _cimg_unserialize_case("float",float); ++ _cimg_unserialize_case("double",double); ++ if (!loaded) ++ throw CImgArgumentException("CImgList<%s>::get_unserialize(): Unsupported pixel type '%s' defined " ++ "in serialized buffer.", ++ pixel_type(),str_pixeltype._data); ++ return res; ++ } ++ ++ //@} ++ //---------------------------------- ++ // ++ //! \name Others ++ //@{ ++ //---------------------------------- ++ ++ //! Crop font along the X-axis. ++ /** ++ **/ ++ CImgList& crop_font() { ++ return get_crop_font().move_to(*this); ++ } ++ ++ //! Crop font along the X-axis \newinstance. ++ /** ++ **/ ++ 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) CImg(letter._width,letter._height,1,letter._spectrum,0).move_to(res); ++ else letter.get_crop(xmin,0,xmax,letter._height - 1).move_to(res); ++ } ++ res[' '].resize(res['f']._width,-100,-100,-100,0); ++ if (' ' + 256& font(const unsigned int font_height, const bool is_variable_width=true) { ++ if (!font_height) return CImgList::const_empty(); ++ cimg::mutex(11); ++ ++ // Decompress nearest base font data if needed. ++ static const char *data_fonts[] = { cimg::data_font12x13, cimg::data_font20x23, cimg::data_font47x53, 0 }; ++ static const unsigned int data_widths[] = { 12,20,47,90 }, data_heights[] = { 13,23,53,103 }, ++ data_Ms[] = { 86,79,57,47 }; ++ const unsigned int data_ind = font_height<=13U?0U:font_height<=23U?1U:font_height<=53U?2U:3U; ++ static CImg base_fonts[4]; ++ CImg &base_font = base_fonts[data_ind]; ++ if (!base_font) { ++ const unsigned int w = data_widths[data_ind], h = data_heights[data_ind], M = data_Ms[data_ind]; ++ base_font.assign(256*w,h); ++ const char *data_font = data_fonts[data_ind]; ++ unsigned char *ptrd = base_font; ++ const unsigned char *const ptrde = base_font.end(); ++ ++ // Special case needed for 90x103 to avoid MS compiler limit with big strings. ++ CImg data90x103; ++ if (!data_font) { ++ ((CImg(cimg::_data_font90x103[0], ++ (unsigned int)std::strlen(cimg::_data_font90x103[0]),1,1,1,true), ++ CImg(cimg::_data_font90x103[1], ++ (unsigned int)std::strlen(cimg::_data_font90x103[1]) + 1,1,1,1,true))>'x'). ++ move_to(data90x103); ++ data_font = data90x103.data(); ++ } ++ ++ // Uncompress font data (decode RLE). ++ for (const char *ptrs = data_font; *ptrs; ++ptrs) { ++ const int c = (int)(*ptrs - M - 32), v = c>=0?255:0, n = c>=0?c:-c; ++ if (ptrd + n<=ptrde) { std::memset(ptrd,v,n); ptrd+=n; } ++ else { std::memset(ptrd,v,ptrde - ptrd); break; } ++ } ++ } ++ ++ // Find optimal font cache location to return. ++ static CImgList fonts[16]; ++ static bool is_variable_widths[16] = { 0 }; ++ unsigned int ind = ~0U; ++ for (int i = 0; i<16; ++i) ++ if (!fonts[i] || (is_variable_widths[i]==is_variable_width && font_height==fonts[i][0]._height)) { ++ ind = (unsigned int)i; break; // Found empty slot or cached font. ++ } ++ if (ind==~0U) { // No empty slots nor existing font in cache. ++ fonts->assign(); ++ std::memmove(fonts,fonts + 1,15*sizeof(CImgList)); ++ std::memmove(is_variable_widths,is_variable_widths + 1,15*sizeof(bool)); ++ std::memset(fonts + (ind=15),0,sizeof(CImgList)); // Free a slot in cache for new font. ++ } ++ CImgList &font = fonts[ind]; ++ ++ // Render requested font. ++ if (!font) { ++ const unsigned int padding_x = font_height<33U?1U:font_height<53U?2U:font_height<103U?3U:4U; ++ is_variable_widths[ind] = is_variable_width; ++ font = base_font.get_split('x',256); ++ if (font_height!=font[0]._height) ++ cimglist_for(font,l) ++ font[l].resize(std::max(1U,font[l]._width*font_height/font[l]._height),font_height,-100,-100, ++ font[0]._height>font_height?2:5); ++ if (is_variable_width) font.crop_font(); ++ cimglist_for(font,l) font[l].resize(font[l]._width + padding_x,-100,1,1,0,0,0.5); ++ font.insert(256,0); ++ cimglist_for_in(font,0,255,l) font[l].assign(font[l + 256]._width,font[l + 256]._height,1,3,1); ++ } ++ cimg::mutex(11,0); ++ return font; ++ } ++ ++ //! Compute a 1d Fast Fourier Transform, along specified axis. ++ /** ++ \param axis Axis along which the Fourier transform is computed. ++ \param invert Tells if the direct (\c false) or inverse transform (\c true) is computed. ++ **/ ++ CImgList& FFT(const char axis, const bool invert=false) { ++ if (is_empty()) return *this; ++ if (_width==1) insert(1); ++ if (_width>2) ++ cimg::warn(_cimglist_instance ++ "FFT(): Instance has more than 2 images", ++ cimglist_instance); ++ ++ CImg::FFT(_data[0],_data[1],axis,invert); ++ return *this; ++ } ++ ++ //! Compute a 1-D Fast Fourier Transform, along specified axis \newinstance. ++ CImgList get_FFT(const char axis, const bool invert=false) const { ++ return CImgList(*this,false).FFT(axis,invert); ++ } ++ ++ //! Compute a n-d Fast Fourier Transform. ++ /** ++ \param invert Tells if the direct (\c false) or inverse transform (\c true) is computed. ++ **/ ++ CImgList& FFT(const bool invert=false) { ++ if (is_empty()) return *this; ++ if (_width==1) insert(1); ++ if (_width>2) ++ cimg::warn(_cimglist_instance ++ "FFT(): Instance has more than 2 images", ++ cimglist_instance); ++ ++ CImg::FFT(_data[0],_data[1],invert); ++ return *this; ++ } ++ ++ //! Compute a n-d Fast Fourier Transform \newinstance. ++ CImgList get_FFT(const bool invert=false) const { ++ return CImgList(*this,false).FFT(invert); ++ } ++ ++ //! Reverse primitives orientations of a 3d object. ++ /** ++ **/ ++ CImgList& reverse_object3d() { ++ cimglist_for(*this,l) { ++ CImg& p = _data[l]; ++ switch (p.size()) { ++ case 2 : case 3: cimg::swap(p[0],p[1]); break; ++ case 6 : cimg::swap(p[0],p[1],p[2],p[4],p[3],p[5]); break; ++ case 9 : cimg::swap(p[0],p[1],p[3],p[5],p[4],p[6]); break; ++ case 4 : cimg::swap(p[0],p[1],p[2],p[3]); break; ++ case 12 : cimg::swap(p[0],p[1],p[2],p[3],p[4],p[6],p[5],p[7],p[8],p[10],p[9],p[11]); break; ++ } ++ } ++ return *this; ++ } ++ ++ //! Reverse primitives orientations of a 3d object \newinstance. ++ CImgList get_reverse_object3d() const { ++ return (+*this).reverse_object3d(); ++ } ++ ++ //@} ++ }; // struct CImgList { ... ++ ++ /* ++ #--------------------------------------------- ++ # ++ # Completion of previously declared functions ++ # ++ #---------------------------------------------- ++ */ ++ ++namespace cimg { ++ ++ // Functions to return standard streams 'stdin', 'stdout' and 'stderr'. ++ // (throw a CImgIOException when macro 'cimg_use_r' is defined). ++ inline FILE* _stdin(const bool throw_exception) { ++#ifndef cimg_use_r ++ cimg::unused(throw_exception); ++ return stdin; ++#else ++ if (throw_exception) { ++ cimg::exception_mode(0); ++ throw CImgIOException("cimg::stdin(): Reference to 'stdin' stream not allowed in R mode " ++ "('cimg_use_r' is defined)."); ++ } ++ return 0; ++#endif ++ } ++ ++ inline FILE* _stdout(const bool throw_exception) { ++#ifndef cimg_use_r ++ cimg::unused(throw_exception); ++ return stdout; ++#else ++ if (throw_exception) { ++ cimg::exception_mode(0); ++ throw CImgIOException("cimg::stdout(): Reference to 'stdout' stream not allowed in R mode " ++ "('cimg_use_r' is defined)."); ++ } ++ return 0; ++#endif ++ } ++ ++ inline FILE* _stderr(const bool throw_exception) { ++#ifndef cimg_use_r ++ cimg::unused(throw_exception); ++ return stderr; ++#else ++ if (throw_exception) { ++ cimg::exception_mode(0); ++ throw CImgIOException("cimg::stderr(): Reference to 'stderr' stream not allowed in R mode " ++ "('cimg_use_r' is defined)."); ++ } ++ return 0; ++#endif ++ } ++ ++ // Open a file (with wide character support on Windows). ++ inline std::FILE *win_fopen(const char *const path, const char *const mode) { ++#if cimg_OS==2 ++ // Convert 'path' to a wide-character string. ++ int err = MultiByteToWideChar(CP_UTF8,0,path,-1,0,0); ++ if (!err) return std_fopen(path,mode); ++ CImg wpath(err); ++ err = MultiByteToWideChar(CP_UTF8,0,path,-1,wpath,err); ++ if (!err) return std_fopen(path,mode); ++ ++ // Convert 'mode' to a wide-character string. ++ err = MultiByteToWideChar(CP_UTF8,0,mode,-1,0,0); ++ if (!err) return std_fopen(path,mode); ++ CImg wmode(err); ++ err = MultiByteToWideChar(CP_UTF8,0,mode,-1,wmode,err); ++ if (!err) return std_fopen(path,mode); ++ return _wfopen(wpath,wmode); ++#else ++ return std_fopen(path,mode); ++#endif ++ } ++ ++ //! Get/set path to store temporary files. ++ /** ++ \param user_path Specified path, or \c 0 to get the path currently used. ++ \param reinit_path Force path to be recalculated (may take some time). ++ \return Path where temporary files can be saved. ++ **/ ++ inline const char* temporary_path(const char *const user_path, const bool reinit_path) { ++#define _cimg_test_temporary_path(p) \ ++ if (!path_found) { \ ++ cimg_snprintf(s_path,s_path.width(),"%s",p); \ ++ cimg_snprintf(tmp,tmp._width,"%s%c%s",s_path.data(),cimg_file_separator,filename_tmp._data); \ ++ if ((file=std_fopen(tmp,"wb"))!=0) { cimg::fclose(file); std::remove(tmp); path_found = true; } \ ++ } ++ static CImg s_path; ++ cimg::mutex(7); ++ if (reinit_path) s_path.assign(); ++ if (user_path) { ++ if (!s_path) s_path.assign(1024); ++ std::strncpy(s_path,user_path,1023); ++ } else if (!s_path) { ++ s_path.assign(1024); ++ bool path_found = false; ++ CImg tmp(1024), filename_tmp(256); ++ std::FILE *file = 0; ++ cimg_snprintf(filename_tmp,filename_tmp._width,"%s.tmp",cimg::filenamerand()); ++ char *tmpPath = std::getenv("TMP"); ++ if (!tmpPath) { tmpPath = std::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) { ++ *s_path = 0; ++ std::strncpy(tmp,filename_tmp,tmp._width - 1); ++ if ((file=std_fopen(tmp,"wb"))!=0) { cimg::fclose(file); std::remove(tmp); path_found = true; } ++ } ++ if (!path_found) { ++ cimg::mutex(7,0); ++ throw CImgIOException("cimg::temporary_path(): Failed to locate path for writing temporary files.\n"); ++ } ++ } ++ cimg::mutex(7,0); ++ return s_path; ++ } ++ ++ //! Get/set path to the Program Files/ directory (Windows only). ++ /** ++ \param user_path Specified path, or \c 0 to get the path currently used. ++ \param reinit_path Force path to be recalculated (may take some time). ++ \return Path containing the program files. ++ **/ ++#if cimg_OS==2 ++ inline const char* programfiles_path(const char *const user_path, const bool reinit_path) { ++ static CImg s_path; ++ cimg::mutex(7); ++ if (reinit_path) s_path.assign(); ++ if (user_path) { ++ if (!s_path) s_path.assign(1024); ++ std::strncpy(s_path,user_path,1023); ++ } else if (!s_path) { ++ s_path.assign(MAX_PATH); ++ *s_path = 0; ++ // Note: in the following line, 0x26 = CSIDL_PROGRAM_FILES (not defined on every compiler). ++#if !defined(__INTEL_COMPILER) ++ if (!SHGetSpecialFolderPathA(0,s_path,0x0026,false)) { ++ const char *const pfPath = std::getenv("PROGRAMFILES"); ++ if (pfPath) std::strncpy(s_path,pfPath,MAX_PATH - 1); ++ else std::strcpy(s_path,"C:\\PROGRA~1"); ++ } ++#else ++ std::strcpy(s_path,"C:\\PROGRA~1"); ++#endif ++ } ++ cimg::mutex(7,0); ++ return s_path; ++ } ++#endif ++ ++ //! Get/set path to the ImageMagick's \c convert binary. ++ /** ++ \param user_path Specified path, or \c 0 to get the path currently used. ++ \param reinit_path Force path to be recalculated (may take some time). ++ \return Path containing the \c convert binary. ++ **/ ++ inline const char* imagemagick_path(const char *const user_path, const bool reinit_path) { ++ static CImg s_path; ++ cimg::mutex(7); ++ if (reinit_path) s_path.assign(); ++ if (user_path) { ++ if (!s_path) s_path.assign(1024); ++ std::strncpy(s_path,user_path,1023); ++ } else if (!s_path) { ++ s_path.assign(1024); ++ bool path_found = false; ++ std::FILE *file = 0; ++#if cimg_OS==2 ++ const char *const pf_path = programfiles_path(); ++ for (int l = 0; l<2 && !path_found; ++l) { ++ const char *const s_exe = l?"convert":"magick"; ++ cimg_snprintf(s_path,s_path._width,".\\%s.exe",s_exe); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ for (int k = 32; k>=10 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"%s\\IMAGEM~1.%.2d-\\%s.exe",pf_path,k,s_exe); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 9; k>=0 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"%s\\IMAGEM~1.%d-Q\\%s.exe",pf_path,k,s_exe); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 32; k>=0 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"%s\\IMAGEM~1.%d\\%s.exe",pf_path,k,s_exe); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 32; k>=10 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"%s\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\%s.exe",pf_path,k,s_exe); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 9; k>=0 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"%s\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\%s.exe",pf_path,k,s_exe); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 32; k>=0 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"%s\\IMAGEM~1.%d\\VISUA~1\\BIN\\%s.exe",pf_path,k,s_exe); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 32; k>=10 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"C:\\IMAGEM~1.%.2d-\\%s.exe",k,s_exe); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 9; k>=0 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"C:\\IMAGEM~1.%d-Q\\%s.exe",k,s_exe); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 32; k>=0 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"C:\\IMAGEM~1.%d\\%s.exe",k,s_exe); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 32; k>=10 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"C:\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\%s.exe",k,s_exe); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 9; k>=0 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"C:\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\%s.exe",k,s_exe); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 32; k>=0 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"C:\\IMAGEM~1.%d\\VISUA~1\\BIN\\%s.exe",k,s_exe); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 32; k>=10 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"D:\\IMAGEM~1.%.2d-\\%s.exe",k,s_exe); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 9; k>=0 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"D:\\IMAGEM~1.%d-Q\\%s.exe",k,s_exe); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 32; k>=0 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"D:\\IMAGEM~1.%d\\%s.exe",k,s_exe); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 32; k>=10 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"D:\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\%s.exe",k,s_exe); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 9; k>=0 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"D:\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\%s.exe",k,s_exe); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 32; k>=0 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"D:\\IMAGEM~1.%d\\VISUA~1\\BIN\\%s.exe",k,s_exe); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ if (!path_found) cimg_snprintf(s_path,s_path._width,"%s.exe",s_exe); ++ } ++#else ++ std::strcpy(s_path,"./magick"); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ if (!path_found) { ++ std::strcpy(s_path,"./convert"); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ if (!path_found) std::strcpy(s_path,"convert"); ++#endif ++ winformat_string(s_path); ++ } ++ cimg::mutex(7,0); ++ return s_path; ++ } ++ ++ //! Get/set path to the GraphicsMagick's \c gm binary. ++ /** ++ \param user_path Specified path, or \c 0 to get the path currently used. ++ \param reinit_path Force path to be recalculated (may take some time). ++ \return Path containing the \c gm binary. ++ **/ ++ inline const char* graphicsmagick_path(const char *const user_path, const bool reinit_path) { ++ static CImg s_path; ++ cimg::mutex(7); ++ if (reinit_path) s_path.assign(); ++ if (user_path) { ++ if (!s_path) s_path.assign(1024); ++ std::strncpy(s_path,user_path,1023); ++ } else if (!s_path) { ++ s_path.assign(1024); ++ bool path_found = false; ++ std::FILE *file = 0; ++#if cimg_OS==2 ++ const char *const pf_path = programfiles_path(); ++ if (!path_found) { ++ std::strcpy(s_path,".\\gm.exe"); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 32; k>=10 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"%s\\GRAPHI~1.%.2d-\\gm.exe",pf_path,k); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 9; k>=0 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"%s\\GRAPHI~1.%d-Q\\gm.exe",pf_path,k); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 32; k>=0 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"%s\\GRAPHI~1.%d\\gm.exe",pf_path,k); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 32; k>=10 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"%s\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",pf_path,k); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 9; k>=0 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"%s\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",pf_path,k); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 32; k>=0 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"%s\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",pf_path,k); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 32; k>=10 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"C:\\GRAPHI~1.%.2d-\\gm.exe",k); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 9; k>=0 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"C:\\GRAPHI~1.%d-Q\\gm.exe",k); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 32; k>=0 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"C:\\GRAPHI~1.%d\\gm.exe",k); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 32; k>=10 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"C:\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",k); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 9; k>=0 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"C:\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",k); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 32; k>=0 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"C:\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",k); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 32; k>=10 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"D:\\GRAPHI~1.%.2d-\\gm.exe",k); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 9; k>=0 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"D:\\GRAPHI~1.%d-Q\\gm.exe",k); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 32; k>=0 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"D:\\GRAPHI~1.%d\\gm.exe",k); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 32; k>=10 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"D:\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",k); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 9; k>=0 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"D:\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",k); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ for (int k = 32; k>=0 && !path_found; --k) { ++ cimg_snprintf(s_path,s_path._width,"D:\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",k); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ if (!path_found) std::strcpy(s_path,"gm.exe"); ++#else ++ if (!path_found) { ++ std::strcpy(s_path,"./gm"); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ if (!path_found) std::strcpy(s_path,"gm"); ++#endif ++ winformat_string(s_path); ++ } ++ cimg::mutex(7,0); ++ return s_path; ++ } ++ ++ //! Get/set path to the XMedcon's \c medcon binary. ++ /** ++ \param user_path Specified path, or \c 0 to get the path currently used. ++ \param reinit_path Force path to be recalculated (may take some time). ++ \return Path containing the \c medcon binary. ++ **/ ++ inline const char* medcon_path(const char *const user_path, const bool reinit_path) { ++ static CImg s_path; ++ cimg::mutex(7); ++ if (reinit_path) s_path.assign(); ++ if (user_path) { ++ if (!s_path) s_path.assign(1024); ++ std::strncpy(s_path,user_path,1023); ++ } else if (!s_path) { ++ s_path.assign(1024); ++ bool path_found = false; ++ std::FILE *file = 0; ++#if cimg_OS==2 ++ const char *const pf_path = programfiles_path(); ++ if (!path_found) { ++ std::strcpy(s_path,".\\medcon.exe"); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ if (!path_found) { ++ cimg_snprintf(s_path,s_path._width,"%s\\XMedCon\\bin\\medcon.bat",pf_path); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ if (!path_found) { ++ cimg_snprintf(s_path,s_path._width,"%s\\XMedCon\\bin\\medcon.exe",pf_path); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ if (!path_found) { ++ std::strcpy(s_path,"C:\\XMedCon\\bin\\medcon.exe"); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ if (!path_found) std::strcpy(s_path,"medcon.exe"); ++#else ++ if (!path_found) { ++ std::strcpy(s_path,"./medcon"); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ if (!path_found) std::strcpy(s_path,"medcon"); ++#endif ++ winformat_string(s_path); ++ } ++ cimg::mutex(7,0); ++ return s_path; ++ } ++ ++ //! Get/set path to the FFMPEG's \c ffmpeg binary. ++ /** ++ \param user_path Specified path, or \c 0 to get the path currently used. ++ \param reinit_path Force path to be recalculated (may take some time). ++ \return Path containing the \c ffmpeg binary. ++ **/ ++ inline const char *ffmpeg_path(const char *const user_path, const bool reinit_path) { ++ static CImg s_path; ++ cimg::mutex(7); ++ if (reinit_path) s_path.assign(); ++ if (user_path) { ++ if (!s_path) s_path.assign(1024); ++ std::strncpy(s_path,user_path,1023); ++ } else if (!s_path) { ++ s_path.assign(1024); ++ bool path_found = false; ++ std::FILE *file = 0; ++#if cimg_OS==2 ++ if (!path_found) { ++ std::strcpy(s_path,".\\ffmpeg.exe"); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ if (!path_found) std::strcpy(s_path,"ffmpeg.exe"); ++#else ++ if (!path_found) { ++ std::strcpy(s_path,"./ffmpeg"); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ if (!path_found) std::strcpy(s_path,"ffmpeg"); ++#endif ++ winformat_string(s_path); ++ } ++ cimg::mutex(7,0); ++ return s_path; ++ } ++ ++ //! Get/set path to the \c gzip binary. ++ /** ++ \param user_path Specified path, or \c 0 to get the path currently used. ++ \param reinit_path Force path to be recalculated (may take some time). ++ \return Path containing the \c gzip binary. ++ **/ ++ inline const char *gzip_path(const char *const user_path, const bool reinit_path) { ++ static CImg s_path; ++ cimg::mutex(7); ++ if (reinit_path) s_path.assign(); ++ if (user_path) { ++ if (!s_path) s_path.assign(1024); ++ std::strncpy(s_path,user_path,1023); ++ } else if (!s_path) { ++ s_path.assign(1024); ++ bool path_found = false; ++ std::FILE *file = 0; ++#if cimg_OS==2 ++ if (!path_found) { ++ std::strcpy(s_path,".\\gzip.exe"); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ if (!path_found) std::strcpy(s_path,"gzip.exe"); ++#else ++ if (!path_found) { ++ std::strcpy(s_path,"./gzip"); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ if (!path_found) std::strcpy(s_path,"gzip"); ++#endif ++ winformat_string(s_path); ++ } ++ cimg::mutex(7,0); ++ return s_path; ++ } ++ ++ //! Get/set path to the \c gunzip binary. ++ /** ++ \param user_path Specified path, or \c 0 to get the path currently used. ++ \param reinit_path Force path to be recalculated (may take some time). ++ \return Path containing the \c gunzip binary. ++ **/ ++ inline const char *gunzip_path(const char *const user_path, const bool reinit_path) { ++ static CImg s_path; ++ cimg::mutex(7); ++ if (reinit_path) s_path.assign(); ++ if (user_path) { ++ if (!s_path) s_path.assign(1024); ++ std::strncpy(s_path,user_path,1023); ++ } else if (!s_path) { ++ s_path.assign(1024); ++ bool path_found = false; ++ std::FILE *file = 0; ++#if cimg_OS==2 ++ if (!path_found) { ++ std::strcpy(s_path,".\\gunzip.exe"); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ if (!path_found) std::strcpy(s_path,"gunzip.exe"); ++#else ++ if (!path_found) { ++ std::strcpy(s_path,"./gunzip"); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ if (!path_found) std::strcpy(s_path,"gunzip"); ++#endif ++ winformat_string(s_path); ++ } ++ cimg::mutex(7,0); ++ return s_path; ++ } ++ ++ //! Get/set path to the \c dcraw binary. ++ /** ++ \param user_path Specified path, or \c 0 to get the path currently used. ++ \param reinit_path Force path to be recalculated (may take some time). ++ \return Path containing the \c dcraw binary. ++ **/ ++ inline const char *dcraw_path(const char *const user_path, const bool reinit_path) { ++ static CImg s_path; ++ cimg::mutex(7); ++ if (reinit_path) s_path.assign(); ++ if (user_path) { ++ if (!s_path) s_path.assign(1024); ++ std::strncpy(s_path,user_path,1023); ++ } else if (!s_path) { ++ s_path.assign(1024); ++ bool path_found = false; ++ std::FILE *file = 0; ++#if cimg_OS==2 ++ if (!path_found) { ++ std::strcpy(s_path,".\\dcraw.exe"); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ if (!path_found) std::strcpy(s_path,"dcraw.exe"); ++#else ++ if (!path_found) { ++ std::strcpy(s_path,"./dcraw"); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ if (!path_found) std::strcpy(s_path,"dcraw"); ++#endif ++ winformat_string(s_path); ++ } ++ cimg::mutex(7,0); ++ return s_path; ++ } ++ ++ //! Get/set path to the \c wget binary. ++ /** ++ \param user_path Specified path, or \c 0 to get the path currently used. ++ \param reinit_path Force path to be recalculated (may take some time). ++ \return Path containing the \c wget binary. ++ **/ ++ inline const char *wget_path(const char *const user_path, const bool reinit_path) { ++ static CImg s_path; ++ cimg::mutex(7); ++ if (reinit_path) s_path.assign(); ++ if (user_path) { ++ if (!s_path) s_path.assign(1024); ++ std::strncpy(s_path,user_path,1023); ++ } else if (!s_path) { ++ s_path.assign(1024); ++ bool path_found = false; ++ std::FILE *file = 0; ++#if cimg_OS==2 ++ if (!path_found) { ++ std::strcpy(s_path,".\\wget.exe"); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ if (!path_found) std::strcpy(s_path,"wget.exe"); ++#else ++ if (!path_found) { ++ std::strcpy(s_path,"./wget"); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ if (!path_found) std::strcpy(s_path,"wget"); ++#endif ++ winformat_string(s_path); ++ } ++ cimg::mutex(7,0); ++ return s_path; ++ } ++ ++ //! Get/set path to the \c curl binary. ++ /** ++ \param user_path Specified path, or \c 0 to get the path currently used. ++ \param reinit_path Force path to be recalculated (may take some time). ++ \return Path containing the \c curl binary. ++ **/ ++ inline const char *curl_path(const char *const user_path, const bool reinit_path) { ++ static CImg s_path; ++ cimg::mutex(7); ++ if (reinit_path) s_path.assign(); ++ if (user_path) { ++ if (!s_path) s_path.assign(1024); ++ std::strncpy(s_path,user_path,1023); ++ } else if (!s_path) { ++ s_path.assign(1024); ++ bool path_found = false; ++ std::FILE *file = 0; ++#if cimg_OS==2 ++ if (!path_found) { ++ std::strcpy(s_path,".\\curl.exe"); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ if (!path_found) std::strcpy(s_path,"curl.exe"); ++#else ++ if (!path_found) { ++ std::strcpy(s_path,"./curl"); ++ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } ++ } ++ if (!path_found) std::strcpy(s_path,"curl"); ++#endif ++ winformat_string(s_path); ++ } ++ cimg::mutex(7,0); ++ return s_path; ++ } ++ ++ // [internal] Sorting function, used by cimg::files(). ++ inline int _sort_files(const void* a, const void* b) { ++ const CImg &sa = *(CImg*)a, &sb = *(CImg*)b; ++ return std::strcmp(sa._data,sb._data); ++ } ++ ++ //! Return list of files/directories in specified directory. ++ /** ++ \param path Path to the directory. Set to 0 for current directory. ++ \param is_pattern Tell if specified path has a matching pattern in it. ++ \param mode Output type, can be primary { 0=files only | 1=folders only | 2=files + folders }. ++ \param include_path Tell if \c path must be included in resulting filenames. ++ \return A list of filenames. ++ **/ ++ inline CImgList files(const char *const path, const bool is_pattern=false, ++ const unsigned int mode=2, const bool include_path=false) { ++ if (!path || !*path) return files("*",true,mode,include_path); ++ CImgList res; ++ ++ // If path is a valid folder name, ignore argument 'is_pattern'. ++ const bool _is_pattern = is_pattern && !cimg::is_directory(path); ++ bool is_root = false, is_current = false; ++ cimg::unused(is_root,is_current); ++ ++ // Clean format of input path. ++ CImg pattern, _path = CImg::string(path); ++#if cimg_OS==2 ++ for (char *ps = _path; *ps; ++ps) if (*ps=='\\') *ps='/'; ++#endif ++ char *pd = _path; ++ for (char *ps = pd; *ps; ++ps) { if (*ps!='/' || *ps!=*(ps+1)) *(pd++) = *ps; } ++ *pd = 0; ++ unsigned int lp = (unsigned int)std::strlen(_path); ++ if (!_is_pattern && lp && _path[lp - 1]=='/') { ++ _path[lp - 1] = 0; --lp; ++#if cimg_OS!=2 ++ is_root = !*_path; ++#endif ++ } ++ ++ // Separate folder path and matching pattern. ++ if (_is_pattern) { ++ const unsigned int bpos = (unsigned int)(cimg::basename(_path,'/') - _path.data()); ++ CImg::string(_path).move_to(pattern); ++ if (bpos) { ++ _path[bpos - 1] = 0; // End 'path' at last slash. ++#if cimg_OS!=2 ++ is_root = !*_path; ++#endif ++ } else { // No path to folder specified, assuming current folder. ++ is_current = true; *_path = 0; ++ } ++ lp = (unsigned int)std::strlen(_path); ++ } ++ ++ // Windows version. ++#if cimg_OS==2 ++ if (!_is_pattern) { ++ pattern.assign(lp + 3); ++ std::memcpy(pattern,_path,lp); ++ pattern[lp] = '/'; pattern[lp + 1] = '*'; pattern[lp + 2] = 0; ++ } ++ WIN32_FIND_DATAA file_data; ++ const HANDLE dir = FindFirstFileA(pattern.data(),&file_data); ++ if (dir==INVALID_HANDLE_VALUE) return CImgList::const_empty(); ++ do { ++ const char *const filename = file_data.cFileName; ++ if (*filename!='.' || (filename[1] && (filename[1]!='.' || filename[2]))) { ++ const unsigned int lf = (unsigned int)std::strlen(filename); ++ const bool is_directory = (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)!=0; ++ if ((!mode && !is_directory) || (mode==1 && is_directory) || mode>=2) { ++ if (include_path) { ++ CImg full_filename((lp?lp+1:0) + lf + 1); ++ if (lp) { std::memcpy(full_filename,_path,lp); full_filename[lp] = '/'; } ++ std::memcpy(full_filename._data + (lp?lp + 1:0),filename,lf + 1); ++ full_filename.move_to(res); ++ } else CImg(filename,lf + 1).move_to(res); ++ } ++ } ++ } while (FindNextFileA(dir,&file_data)); ++ FindClose(dir); ++ ++ // Unix version (posix). ++#elif cimg_OS == 1 ++ DIR *const dir = opendir(is_root?"/":is_current?".":_path.data()); ++ if (!dir) return CImgList::const_empty(); ++ struct dirent *ent; ++ while ((ent=readdir(dir))!=0) { ++ const char *const filename = ent->d_name; ++ if (*filename!='.' || (filename[1] && (filename[1]!='.' || filename[2]))) { ++ const unsigned int lf = (unsigned int)std::strlen(filename); ++ CImg full_filename(lp + lf + 2); ++ ++ if (!is_current) { ++ full_filename.assign(lp + lf + 2); ++ if (lp) std::memcpy(full_filename,_path,lp); ++ full_filename[lp] = '/'; ++ std::memcpy(full_filename._data + lp + 1,filename,lf + 1); ++ } else full_filename.assign(filename,lf + 1); ++ ++ struct stat st; ++ if (stat(full_filename,&st)==-1) continue; ++ const bool is_directory = (st.st_mode & S_IFDIR)!=0; ++ if ((!mode && !is_directory) || (mode==1 && is_directory) || mode==2) { ++ if (include_path) { ++ if (!_is_pattern || (_is_pattern && !fnmatch(pattern,full_filename,0))) ++ full_filename.move_to(res); ++ } else { ++ if (!_is_pattern || (_is_pattern && !fnmatch(pattern,full_filename,0))) ++ CImg(filename,lf + 1).move_to(res); ++ } ++ } ++ } ++ } ++ closedir(dir); ++#endif ++ ++ // Sort resulting list by lexicographic order. ++ if (res._width>=2) std::qsort(res._data,res._width,sizeof(CImg),_sort_files); ++ ++ return res; ++ } ++ ++ //! Try to guess format from an image file. ++ /** ++ \param file Input file (can be \c 0 if \c filename is set). ++ \param filename Filename, as a C-string (can be \c 0 if \c file is set). ++ \return C-string containing the guessed file format, or \c 0 if nothing has been guessed. ++ **/ ++ inline const char *ftype(std::FILE *const file, const char *const filename) { ++ if (!file && !filename) ++ throw CImgArgumentException("cimg::ftype(): Specified filename is (null)."); ++ static const char ++ *const _pnm = "pnm", ++ *const _pfm = "pfm", ++ *const _bmp = "bmp", ++ *const _gif = "gif", ++ *const _jpg = "jpg", ++ *const _off = "off", ++ *const _pan = "pan", ++ *const _png = "png", ++ *const _tif = "tif", ++ *const _inr = "inr", ++ *const _dcm = "dcm"; ++ const char *f_type = 0; ++ CImg header; ++ const unsigned int omode = cimg::exception_mode(); ++ cimg::exception_mode(0); ++ try { ++ header._load_raw(file,filename,512,1,1,1,false,false,0); ++ const unsigned char *const uheader = (unsigned char*)header._data; ++ if (!std::strncmp(header,"OFF\n",4)) f_type = _off; // OFF. ++ else if (!std::strncmp(header,"#INRIMAGE",9)) f_type = _inr; // INRIMAGE. ++ else if (!std::strncmp(header,"PANDORE",7)) f_type = _pan; // PANDORE. ++ else if (!std::strncmp(header.data() + 128,"DICM",4)) f_type = _dcm; // DICOM. ++ else if (uheader[0]==0xFF && uheader[1]==0xD8 && uheader[2]==0xFF) f_type = _jpg; // JPEG. ++ else if (header[0]=='B' && header[1]=='M') f_type = _bmp; // BMP. ++ else if (header[0]=='G' && header[1]=='I' && header[2]=='F' && header[3]=='8' && header[5]=='a' && // GIF. ++ (header[4]=='7' || header[4]=='9')) f_type = _gif; ++ else if (uheader[0]==0x89 && uheader[1]==0x50 && uheader[2]==0x4E && uheader[3]==0x47 && // PNG. ++ uheader[4]==0x0D && uheader[5]==0x0A && uheader[6]==0x1A && uheader[7]==0x0A) f_type = _png; ++ else if ((uheader[0]==0x49 && uheader[1]==0x49) || (uheader[0]==0x4D && uheader[1]==0x4D)) f_type = _tif; // TIFF. ++ else { // PNM or PFM. ++ CImgList _header = header.get_split(CImg::vector('\n'),0,false); ++ cimglist_for(_header,l) { ++ if (_header(l,0)=='#') continue; ++ if (_header[l]._height==2 && _header(l,0)=='P') { ++ const char c = _header(l,1); ++ if (c=='f' || c=='F') { f_type = _pfm; break; } ++ if (c>='1' && c<='9') { f_type = _pnm; break; } ++ } ++ f_type = 0; break; ++ } ++ } ++ } catch (CImgIOException&) { } ++ cimg::exception_mode(omode); ++ return f_type; ++ } ++ ++ //! Load file from network as a local temporary file. ++ /** ++ \param url URL of the filename, as a C-string. ++ \param[out] filename_local C-string containing the path to a local copy of \c filename. ++ \param timeout Maximum time (in seconds) authorized for downloading the file from the URL. ++ \param try_fallback When using libcurl, tells using system calls as fallbacks in case of libcurl failure. ++ \param referer Referer used, as a C-string. ++ \return Value of \c filename_local. ++ \note Use the \c libcurl library, or the external binaries \c wget or \c curl to perform the download. ++ **/ ++ inline char *load_network(const char *const url, char *const filename_local, ++ const unsigned int timeout, const bool try_fallback, ++ const char *const referer) { ++ if (!url) ++ throw CImgArgumentException("cimg::load_network(): Specified URL is (null)."); ++ if (!filename_local) ++ throw CImgArgumentException("cimg::load_network(): Specified destination string is (null)."); ++ ++ const char *const __ext = cimg::split_filename(url), *const _ext = (*__ext && __ext>url)?__ext - 1:__ext; ++ CImg ext = CImg::string(_ext); ++ std::FILE *file = 0; ++ *filename_local = 0; ++ if (ext._width>16 || !cimg::strncasecmp(ext,"cgi",3)) *ext = 0; ++ else cimg::strwindows_reserved(ext); ++ do { ++ cimg_snprintf(filename_local,256,"%s%c%s%s", ++ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext._data); ++ if ((file=std_fopen(filename_local,"rb"))!=0) cimg::fclose(file); ++ } while (file); ++ ++#ifdef cimg_use_curl ++ const unsigned int omode = cimg::exception_mode(); ++ cimg::exception_mode(0); ++ try { ++ CURL *curl = 0; ++ CURLcode res; ++ curl = curl_easy_init(); ++ if (curl) { ++ file = cimg::fopen(filename_local,"wb"); ++ curl_easy_setopt(curl,CURLOPT_URL,url); ++ curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,0); ++ curl_easy_setopt(curl,CURLOPT_WRITEDATA,file); ++ curl_easy_setopt(curl,CURLOPT_SSL_VERIFYPEER,0L); ++ curl_easy_setopt(curl,CURLOPT_SSL_VERIFYHOST,0L); ++ curl_easy_setopt(curl,CURLOPT_FOLLOWLOCATION,1L); ++ if (timeout) curl_easy_setopt(curl,CURLOPT_TIMEOUT,(long)timeout); ++ if (std::strchr(url,'?')) curl_easy_setopt(curl,CURLOPT_HTTPGET,1L); ++ if (referer) curl_easy_setopt(curl,CURLOPT_REFERER,referer); ++ res = curl_easy_perform(curl); ++ curl_easy_cleanup(curl); ++ cimg::fseek(file,0,SEEK_END); // Check if file size is 0. ++ const cimg_ulong siz = cimg::ftell(file); ++ cimg::fclose(file); ++ if (siz>0 && res==CURLE_OK) { ++ cimg::exception_mode(omode); ++ return filename_local; ++ } else std::remove(filename_local); ++ } ++ } catch (...) { } ++ cimg::exception_mode(omode); ++ if (!try_fallback) throw CImgIOException("cimg::load_network(): Failed to load file '%s' with libcurl.",url); ++#endif ++ ++ CImg command((unsigned int)std::strlen(url) + 64); ++ cimg::unused(try_fallback); ++ ++ // Try with 'curl' first. ++ if (timeout) { ++ if (referer) ++ cimg_snprintf(command,command._width,"%s -e %s -m %u -f --silent --compressed -o \"%s\" \"%s\"", ++ cimg::curl_path(),referer,timeout,filename_local,url); ++ else ++ cimg_snprintf(command,command._width,"%s -m %u -f --silent --compressed -o \"%s\" \"%s\"", ++ cimg::curl_path(),timeout,filename_local,url); ++ } else { ++ if (referer) ++ cimg_snprintf(command,command._width,"%s -e %s -f --silent --compressed -o \"%s\" \"%s\"", ++ cimg::curl_path(),referer,filename_local,url); ++ else ++ cimg_snprintf(command,command._width,"%s -f --silent --compressed -o \"%s\" \"%s\"", ++ cimg::curl_path(),filename_local,url); ++ } ++ cimg::system(command); ++ ++ if (!(file = std_fopen(filename_local,"rb"))) { ++ ++ // Try with 'wget' otherwise. ++ if (timeout) { ++ if (referer) ++ cimg_snprintf(command,command._width,"%s --referer=%s -T %u -q -r -l 0 --no-cache -O \"%s\" \"%s\"", ++ cimg::wget_path(),referer,timeout,filename_local,url); ++ else ++ cimg_snprintf(command,command._width,"%s -T %u -q -r -l 0 --no-cache -O \"%s\" \"%s\"", ++ cimg::wget_path(),timeout,filename_local,url); ++ } else { ++ if (referer) ++ cimg_snprintf(command,command._width,"%s --referer=%s -q -r -l 0 --no-cache -O \"%s\" \"%s\"", ++ cimg::wget_path(),referer,filename_local,url); ++ else ++ cimg_snprintf(command,command._width,"%s -q -r -l 0 --no-cache -O \"%s\" \"%s\"", ++ cimg::wget_path(),filename_local,url); ++ } ++ cimg::system(command); ++ ++ if (!(file = std_fopen(filename_local,"rb"))) ++ throw CImgIOException("cimg::load_network(): Failed to load file '%s' with external commands " ++ "'wget' or 'curl'.",url); ++ cimg::fclose(file); ++ ++ // Try gunzip it. ++ cimg_snprintf(command,command._width,"%s.gz",filename_local); ++ std::rename(filename_local,command); ++ cimg_snprintf(command,command._width,"%s --quiet \"%s.gz\"", ++ gunzip_path(),filename_local); ++ cimg::system(command); ++ file = std_fopen(filename_local,"rb"); ++ if (!file) { ++ cimg_snprintf(command,command._width,"%s.gz",filename_local); ++ std::rename(command,filename_local); ++ file = std_fopen(filename_local,"rb"); ++ } ++ } ++ cimg::fseek(file,0,SEEK_END); // Check if file size is 0. ++ if (std::ftell(file)<=0) ++ throw CImgIOException("cimg::load_network(): Failed to load URL '%s' with external commands " ++ "'wget' or 'curl'.",url); ++ cimg::fclose(file); ++ return filename_local; ++ } ++ ++ // Implement a tic/toc mechanism to display elapsed time of algorithms. ++ inline cimg_ulong tictoc(const bool is_tic) { ++ cimg::mutex(2); ++ static CImg times(64); ++ static unsigned int pos = 0; ++ const cimg_ulong t1 = cimg::time(); ++ if (is_tic) { ++ // Tic ++ times[pos++] = t1; ++ if (pos>=times._width) ++ throw CImgArgumentException("cimg::tic(): Too much calls to 'cimg::tic()' without calls to 'cimg::toc()'."); ++ cimg::mutex(2,0); ++ return t1; ++ } ++ ++ // Toc ++ if (!pos) ++ throw CImgArgumentException("cimg::toc(): No previous call to 'cimg::tic()' has been made."); ++ const cimg_ulong ++ t0 = times[--pos], ++ dt = t1>=t0?(t1 - t0):cimg::type::max(); ++ const unsigned int ++ edays = (unsigned int)(dt/86400000.0), ++ ehours = (unsigned int)((dt - edays*86400000.0)/3600000.0), ++ emin = (unsigned int)((dt - edays*86400000.0 - ehours*3600000.0)/60000.0), ++ esec = (unsigned int)((dt - edays*86400000.0 - ehours*3600000.0 - emin*60000.0)/1000.0), ++ ems = (unsigned int)(dt - edays*86400000.0 - ehours*3600000.0 - emin*60000.0 - esec*1000.0); ++ if (!edays && !ehours && !emin && !esec) ++ std::fprintf(cimg::output(),"%s[CImg]%*sElapsed time: %u ms%s\n", ++ cimg::t_red,1 + 2*pos,"",ems,cimg::t_normal); ++ else { ++ if (!edays && !ehours && !emin) ++ std::fprintf(cimg::output(),"%s[CImg]%*sElapsed time: %u sec %u ms%s\n", ++ cimg::t_red,1 + 2*pos,"",esec,ems,cimg::t_normal); ++ else { ++ if (!edays && !ehours) ++ std::fprintf(cimg::output(),"%s[CImg]%*sElapsed time: %u min %u sec %u ms%s\n", ++ cimg::t_red,1 + 2*pos,"",emin,esec,ems,cimg::t_normal); ++ else{ ++ if (!edays) ++ std::fprintf(cimg::output(),"%s[CImg]%*sElapsed time: %u hours %u min %u sec %u ms%s\n", ++ cimg::t_red,1 + 2*pos,"",ehours,emin,esec,ems,cimg::t_normal); ++ else{ ++ std::fprintf(cimg::output(),"%s[CImg]%*sElapsed time: %u days %u hours %u min %u sec %u ms%s\n", ++ cimg::t_red,1 + 2*pos,"",edays,ehours,emin,esec,ems,cimg::t_normal); ++ } ++ } ++ } ++ } ++ cimg::mutex(2,0); ++ return dt; ++ } ++ ++ // Return a temporary string describing the size of a memory buffer. ++ inline const char *strbuffersize(const cimg_ulong size) { ++ static CImg res(256); ++ cimg::mutex(5); ++ if (size<1024LU) cimg_snprintf(res,res._width,"%lu byte%s",(unsigned long)size,size>1?"s":""); ++ else if (size<1024*1024LU) { const float nsize = size/1024.0f; cimg_snprintf(res,res._width,"%.1f Kio",nsize); } ++ else if (size<1024*1024*1024LU) { ++ const float nsize = size/(1024*1024.0f); cimg_snprintf(res,res._width,"%.1f Mio",nsize); ++ } else { const float nsize = size/(1024*1024*1024.0f); cimg_snprintf(res,res._width,"%.1f Gio",nsize); } ++ cimg::mutex(5,0); ++ return res; ++ } ++ ++ //! Display a simple dialog box, and wait for the user's response. ++ /** ++ \param title Title of the dialog window. ++ \param msg Main message displayed inside the dialog window. ++ \param button1_label Label of the 1st button. ++ \param button2_label Label of the 2nd button (\c 0 to hide button). ++ \param button3_label Label of the 3rd button (\c 0 to hide button). ++ \param button4_label Label of the 4th button (\c 0 to hide button). ++ \param button5_label Label of the 5th button (\c 0 to hide button). ++ \param button6_label Label of the 6th button (\c 0 to hide button). ++ \param logo Image logo displayed at the left of the main message. ++ \param is_centered Tells if the dialog window must be centered on the screen. ++ \return Indice of clicked button (from \c 0 to \c 5), or \c -1 if the dialog window has been closed by the user. ++ \note ++ - Up to 6 buttons can be defined in the dialog window. ++ - The function returns when a user clicked one of the button or closed the dialog window. ++ - If a button text is set to 0, the corresponding button (and the followings) will not appear in the dialog box. ++ At least one button must be specified. ++ **/ ++ template ++ inline int dialog(const char *const title, const char *const msg, ++ const char *const button1_label, const char *const button2_label, ++ const char *const button3_label, const char *const button4_label, ++ const char *const button5_label, const char *const button6_label, ++ const CImg& logo, const bool is_centered=false) { ++#if cimg_display==0 ++ cimg::unused(title,msg,button1_label,button2_label,button3_label,button4_label,button5_label,button6_label, ++ logo._data,is_centered); ++ throw CImgIOException("cimg::dialog(): No display available."); ++#else ++ static 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_label) { CImg().draw_text(0,0,button1_label,black,gray,1,13).move_to(buttons); ++ if (button2_label) { CImg().draw_text(0,0,button2_label,black,gray,1,13).move_to(buttons); ++ if (button3_label) { CImg().draw_text(0,0,button3_label,black,gray,1,13).move_to(buttons); ++ if (button4_label) { CImg().draw_text(0,0,button4_label,black,gray,1,13).move_to(buttons); ++ if (button5_label) { CImg().draw_text(0,0,button5_label,black,gray,1,13).move_to(buttons); ++ if (button6_label) { CImg().draw_text(0,0,button6_label,black,gray,1,13).move_to(buttons); ++ }}}}}} ++ if (!buttons._width) ++ throw CImgArgumentException("cimg::dialog(): No buttons have been defined."); ++ cimglist_for(buttons,l) buttons[l].resize(-100,-100,1,3); ++ ++ unsigned int bw = 0, bh = 0; ++ cimglist_for(buttons,l) { bw = std::max(bw,buttons[l]._width); bh = std::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) { ++ CImg(cbutton). ++ draw_image(1 + (bw -buttons[ll].width())/2,1 + (bh - buttons[ll].height())/2,buttons[ll]). ++ move_to(cbuttons); ++ CImg(sbutton). ++ draw_image((bw - buttons[ll].width())/2,(bh - buttons[ll].height())/2,buttons[ll]). ++ move_to(sbuttons); ++ CImg(button). ++ draw_image((bw - buttons[ll].width())/2,(bh - buttons[ll].height())/2,buttons[ll]). ++ move_to(buttons[ll]); ++ } ++ ++ CImg canvas; ++ if (msg) ++ ((CImg().draw_text(0,0,"%s",gray,0,1,13,msg)*=-1)+=200).resize(-100,-100,1,3).move_to(canvas); ++ ++ const unsigned int ++ bwall = (buttons._width - 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] = { 0 }; ++ 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,is_centered?true:false); ++ if (is_centered) disp.move((CImgDisplay::screen_width() - disp.width())/2, ++ (CImgDisplay::screen_height() - disp.height())/2); ++ bool stop_flag = false, refresh = false; ++ int oselected = -1, oclicked = -1, selected = -1, clicked = -1; ++ while (!disp.is_closed() && !stop_flag) { ++ 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,false); ++ ++ 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) stop_flag = true; ++ ++ if (disp.key()) { ++ oselected = selected; ++ switch (disp.key()) { ++ case cimg::keyESC : selected = -1; stop_flag = true; break; ++ case cimg::keyENTER : if (selected<0) selected = 0; stop_flag = true; break; ++ case cimg::keyTAB : ++ case cimg::keyARROWRIGHT : ++ case cimg::keyARROWDOWN : selected = (selected + 1)%buttons.width(); break; ++ case cimg::keyARROWLEFT : ++ case cimg::keyARROWUP : selected = (selected + buttons.width() - 1)%buttons.width(); break; ++ } ++ disp.set_key(); ++ if (selected!=oselected) refresh = true; ++ } ++ } ++ if (!disp) selected = -1; ++ return selected; ++#endif ++ } ++ ++ //! Display a simple dialog box, and wait for the user's response \specialization. ++ inline int dialog(const char *const title, const char *const msg, ++ const char *const button1_label, const char *const button2_label, const char *const button3_label, ++ const char *const button4_label, const char *const button5_label, const char *const button6_label, ++ const bool is_centered) { ++ return dialog(title,msg,button1_label,button2_label,button3_label,button4_label,button5_label,button6_label, ++ CImg::_logo40x38(),is_centered); ++ } ++ ++ //! Evaluate math expression. ++ /** ++ \param expression C-string describing the formula to evaluate. ++ \param x Value of the pre-defined variable \c x. ++ \param y Value of the pre-defined variable \c y. ++ \param z Value of the pre-defined variable \c z. ++ \param c Value of the pre-defined variable \c c. ++ \return Result of the formula evaluation. ++ \note Set \c expression to \c 0 to keep evaluating the last specified \c expression. ++ \par Example ++ \code ++ const double ++ res1 = cimg::eval("cos(x)^2 + sin(y)^2",2,2), // will return '1'. ++ res2 = cimg::eval(0,1,1); // will return '1' too. ++ \endcode ++ **/ ++ inline double eval(const char *const expression, const double x, const double y, const double z, const double c) { ++ static const CImg empty; ++ return empty.eval(expression,x,y,z,c); ++ } ++ ++ template ++ inline CImg::type> eval(const char *const expression, const CImg& xyzc) { ++ static const CImg empty; ++ return empty.eval(expression,xyzc); ++ } ++ ++ // End of cimg:: namespace ++} ++ ++ // End of cimg_library:: namespace ++} ++ ++//! Short alias name. ++namespace cil = cimg_library_suffixed; ++ ++#ifdef _cimg_redefine_False ++#define False 0 ++#endif ++#ifdef _cimg_redefine_True ++#define True 1 ++#endif ++#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 ++#ifdef _cimg_redefine_PI ++#define PI 3.141592653589793238462643383 ++#endif ++#ifdef _MSC_VER ++#pragma warning(pop) ++#endif ++ ++#endif ++// Local Variables: ++// mode: c++ ++// End: Property changes on: head/graphics/openfx-misc/files/patch-CImg_CImg.h ___________________________________________________________________ Added: fbsd:nokeywords ## -0,0 +1 ## +yes \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/graphics/openfx-misc/files/patch-CImg_Inpaint_inpaint.h =================================================================== --- head/graphics/openfx-misc/files/patch-CImg_Inpaint_inpaint.h (nonexistent) +++ head/graphics/openfx-misc/files/patch-CImg_Inpaint_inpaint.h (revision 468804) @@ -0,0 +1,503 @@ +--- CImg/Inpaint/inpaint.h.orig 2018-04-30 23:16:26 UTC ++++ CImg/Inpaint/inpaint.h +@@ -0,0 +1,500 @@ ++/* ++ # ++ # File : inpaint.h ++ # ( C++ header file - CImg plug-in ) ++ # ++ # Copyright : David Tschumperlé ++ # ++ # License : CeCILL v2.0 ++ # ( http://www.cecill.info/licences/Licence_CeCILL_V2-en.html ) ++ # ++ # Description : ++ # ++ # This plug-in implements the patch-based inpainting algorithm for 2d images, as ++ # described in the two following publications : ++ # ++ # "A Smarter Examplar-based Inpainting Algorithm using Local and Global Heuristics ++ # for more Geometric Coherence." ++ # (M. Daisy, P. Buyssens, D. Tschumperlé, O. Lezoray). ++ # IEEE International Conference on Image Processing (ICIP'14), Paris/France, Oct. 2014 ++ # ++ # and ++ # ++ # "A Fast Spatial Patch Blending Algorithm for Artefact Reduction in Pattern-based ++ # Image Inpainting." ++ # (M. Daisy, D. Tschumperlé, O. Lezoray). ++ # SIGGRAPH Asia 2013 Technical Briefs, Hong-Kong, November 2013. ++ # ++ # 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_inpaint ++#define cimg_plugin_inpaint ++ ++template ++CImg& inpaint_patch(const CImg& mask, const unsigned int patch_size=11, ++ const unsigned int lookup_size=22, const float lookup_factor=1, ++ const int lookup_increment=1, ++ const unsigned int blend_size=0, const float blend_threshold=0.5f, ++ const float blend_decay=0.02, const unsigned int blend_scales=10, ++ const bool is_blend_outer=false) { ++ if (depth()>1) ++ throw CImgInstanceException(_cimg_instance ++ "inpaint_patch(): Instance image is volumetric (should be 2d).", ++ cimg_instance); ++ if (!is_sameXYZ(mask)) ++ throw CImgArgumentException(_cimg_instance ++ "inpaint_patch() : Sizes of instance image and specified mask " ++ "(%u,%u,%u,%u) do not match.", ++ cimg_instance, ++ mask._width,mask._height,mask._depth,mask._spectrum); ++ if (!patch_size) ++ throw CImgArgumentException(_cimg_instance ++ "inpaint_patch() : Specified patch size is 0, must be strictly " ++ "positive.", ++ cimg_instance); ++ if (!lookup_size) ++ throw CImgArgumentException(_cimg_instance ++ "inpaint_patch() : Specified lookup size is 0, must be strictly " ++ "positive.", ++ cimg_instance); ++ if (lookup_factor<0) ++ throw CImgArgumentException(_cimg_instance ++ "inpaint_patch() : Specified lookup factor %g is negative, must be " ++ "positive.", ++ cimg_instance, ++ lookup_factor); ++ if (!lookup_increment) ++ throw CImgArgumentException(_cimg_instance ++ "inpaint_patch() : Specified lookup increment is 0, must be " ++ "strictly positive.", ++ cimg_instance); ++ if (blend_decay<0) ++ throw CImgArgumentException(_cimg_instance ++ "inpaint_patch() : Specified blend decay %g is negative, must be " ++ "positive.", ++ cimg_instance, ++ blend_decay); ++ ++ // Find (dilated by 2) bounding box for the inpainting mask. ++ unsigned int xm0 = _width, ym0 = _height, xm1 = 0, ym1 = 0; ++ bool is_mask_found = false; ++ cimg_forXY(mask,x,y) if (mask(x,y)) { ++ is_mask_found = true; ++ if (x<(int)xm0) xm0 = (unsigned int)x; ++ if (x>(int)xm1) xm1 = (unsigned int)x; ++ if (y<(int)ym0) ym0 = (unsigned int)y; ++ if (y>(int)ym1) ym1 = (unsigned int)y; ++ } ++ if (!is_mask_found) return *this; ++ xm0 = xm0>2?xm0 - 2:0; ++ ym0 = ym0>2?ym0 - 2:0; ++ xm1 = xm1<_width - 3?xm1 + 2:_width - 1; ++ ym1 = ym1<_height - 3?ym1 + 2:_height - 1; ++ int ox = xm0, oy = ym0; ++ unsigned int dx = xm1 - xm0 + 1U, dy = ym1 - ym0 + 1U; ++ ++ // Construct normalized version of the mask. ++ CImg nmask(dx,dy); ++ unsigned char *ptrM = nmask.data(); ++ cimg_for_inXY(mask,xm0,ym0,xm1,ym1,x,y) *(ptrM++) = mask(x,y)?0:1; ++ xm0 = ym0 = 0; xm1 = dx - 1; ym1 = dy - 1; ++ ++ // Start patch filling algorithm. ++ const int p2 = (int)patch_size/2, p1 = (int)patch_size - p2 - 1; ++ const unsigned int patch_size2 = patch_size*patch_size; ++ unsigned int _lookup_size = lookup_size, nb_lookups = 0, nb_fails = 0, nb_saved_patches = 0; ++ bool is_strict_search = true; ++ const float one = 1; ++ ++ CImg confidences(nmask), priorities(dx,dy,1,2,-1), pC; ++ CImg saved_patches(4,256), is_visited(width(),height(),1,1,0); ++ CImg pM, pN; // Pre-declare patch variables (avoid iterative memory alloc/dealloc). ++ CImg pP, pbest; ++ CImg weights(patch_size,patch_size,1,1,0); ++ weights.draw_gaussian((float)p1,(float)p1,patch_size/15.0f,&one)/=patch_size2; ++ unsigned int target_index = 0; ++ ++ while (true) { ++ ++ // Extract mask border points and compute priorities to find target point. ++ unsigned int nb_border_points = 0; ++ float target_confidence = -1, target_priority = -1; ++ int target_x = -1, target_y = -1; ++ CImg_5x5(M,unsigned char); ++ ++ cimg_for_in5x5(nmask,xm0,ym0,xm1,ym1,x,y,0,0,M,unsigned char) ++ if (!Mcc && (Mcp || Mcn || Mpc || Mnc)) { // Found mask border point. ++ ++ float confidence_term = -1, data_term = -1; ++ if (priorities(x,y)>=0) { // If priority has already been computed. ++ confidence_term = priorities(x,y,0); ++ data_term = priorities(x,y,1); ++ } else { // If priority must be computed/updated. ++ ++ // Compute smoothed normal vector. ++ const float ++ // N = smoothed 3x3 neighborhood of M. ++ Npc = (4.0f*Mpc + 2.0f*Mbc + 2.0f*Mcc + 2.0f*Mpp + 2.0f*Mpn + Mbp + Mbn + Mcp + Mcn)/16, ++ Nnc = (4.0f*Mnc + 2.0f*Mac + 2.0f*Mcc + 2.0f*Mnp + 2.0f*Mnn + Map + Man + Mcp + Mcn)/16, ++ Ncp = (4.0f*Mcp + 2.0f*Mcb + 2.0f*Mcc + 2.0f*Mpp + 2.0f*Mnp + Mpb + Mnb + Mpc + Mnc)/16, ++ Ncn = (4.0f*Mcn + 2.0f*Mca + 2.0f*Mcc + 2.0f*Mpn + 2.0f*Mnn + Mpa + Mna + Mpc + Mnc)/16, ++ _nx = 0.5f*(Nnc - Npc), ++ _ny = 0.5f*(Ncn - Ncp), ++ nn = std::sqrt(1e-8f + _nx*_nx + _ny*_ny), ++ nx = _nx/nn, ++ ny = _ny/nn; ++ ++ // Compute confidence term. ++ nmask._inpaint_patch_crop(x - p1,y - p1,x + p2,y + p2,1).move_to(pM); ++ confidences._inpaint_patch_crop(x - p1,y - p1,x + p2,y + p2,1).move_to(pC); ++ confidence_term = 0; ++ const unsigned char *ptrM = pM.data(); ++ cimg_for(pC,ptrC,float) confidence_term+=*ptrC**(ptrM++); ++ confidence_term/=patch_size2; ++ priorities(x,y,0) = confidence_term; ++ ++ // Compute data term. ++ _inpaint_patch_crop(ox + x - p1,oy + y - p1,ox + x + p2,oy + y + p2,2).move_to(pP); ++ float mean_ix2 = 0, mean_ixiy = 0, mean_iy2 = 0; ++ ++ CImg_3x3(I,T); ++ CImg_3x3(_M, unsigned char); ++ cimg_forC(pP,c) cimg_for3x3(pP,p,q,0,c,I,T) { ++ // Compute weight-mean of structure tensor inside patch. ++ cimg_get3x3(pM,p,q,0,0,_M,unsigned char); ++ const float ++ ixf = (float)(_Mnc*_Mcc*(Inc - Icc)), ++ iyf = (float)(_Mcn*_Mcc*(Icn - Icc)), ++ ixb = (float)(_Mcc*_Mpc*(Icc - Ipc)), ++ iyb = (float)(_Mcc*_Mcp*(Icc - Icp)), ++ ix = cimg::abs(ixf)>cimg::abs(ixb)?ixf:ixb, ++ iy = cimg::abs(iyf)>cimg::abs(iyb)?iyf:iyb, ++ w = weights(p,q); ++ mean_ix2 += w*ix*ix; ++ mean_ixiy += w*ix*iy; ++ mean_iy2 += w*iy*iy; ++ } ++ const float // Compute tensor-directed data term. ++ ux = mean_ix2*(-ny) + mean_ixiy*nx, ++ uy = mean_ixiy*(-ny) + mean_iy2*nx; ++ data_term = std::sqrt(ux*ux + uy*uy); ++ priorities(x,y,1) = data_term; ++ } ++ const float priority = confidence_term*data_term; ++ if (priority>target_priority) { ++ target_priority = priority; target_confidence = confidence_term; ++ target_x = ox + x; target_y = oy + y; ++ } ++ ++nb_border_points; ++ } ++ if (!nb_border_points) break; // No more mask border points to inpaint! ++ ++ // Locate already reconstructed neighbors (if any), to get good origins for patch lookup. ++ CImg lookup_candidates(2,256); ++ unsigned int nb_lookup_candidates = 0, *ptr_lookup_candidates = lookup_candidates.data(); ++ const unsigned int *ptr_saved_patches = saved_patches.data(); ++ const int ++ x0 = target_x - (int)patch_size, y0 = target_y - (int)patch_size, ++ x1 = target_x + (int)patch_size, y1 = target_y + (int)patch_size; ++ for (unsigned int k = 0; k=x0 && (int)dest_y>=y0 && (int)dest_x<=x1 && (int)dest_y<=y1) { ++ const int off_x = target_x - dest_x, off_y = target_y - dest_y; ++ *(ptr_lookup_candidates++) = src_x + off_x; ++ *(ptr_lookup_candidates++) = src_y + off_y; ++ if (++nb_lookup_candidates>=lookup_candidates._height) ++ lookup_candidates.resize(2,-200,1,1,0); ++ } ++ } ++ // Add also target point as a center for the patch lookup. ++ *(ptr_lookup_candidates++) = target_x; ++ *(ptr_lookup_candidates++) = target_y; ++ ++nb_lookup_candidates; ++ ++ // Divide size of lookup regions if several lookup sources have been detected. ++ unsigned int final_lookup_size = _lookup_size; ++ if (nb_lookup_candidates>1) { ++ const unsigned int ++ _final_lookup_size = (unsigned int)cimg::round(_lookup_size*lookup_factor/ ++ std::sqrt((float)nb_lookup_candidates),1,1); ++ final_lookup_size = _final_lookup_size + 1 - (_final_lookup_size%2); ++ } ++ const int l2 = (int)final_lookup_size/2, l1 = (int)final_lookup_size - l2 - 1; ++ ++#ifdef gmic_debug ++ CImg visu(*this,false); ++ for (unsigned int C = 0; C::vector(0,255,0).data(),0.2f); ++ } ++ visu.draw_rectangle(target_x - p1,target_y - p1,target_x + p2,target_y + p2, ++ CImg::vector(255,0,0).data(),0.5f); ++ static int foo = 0; ++ if (!(foo%1)) { ++ // visu.save("video.ppm",foo); ++ static CImgDisplay disp_debug; ++ disp_debug.display(visu).set_title("DEBUG"); ++ } ++ ++foo; ++#endif // #ifdef gmic_debug ++ ++ // Find best patch candidate to fill target point. ++ _inpaint_patch_crop(target_x - p1,target_y - p1,target_x + p2,target_y + p2,0).move_to(pP); ++ nmask._inpaint_patch_crop(target_x - ox - p1,target_y - oy - p1,target_x - ox + p2,target_y - oy + p2,0). ++ move_to(pM); ++ ++target_index; ++ const unsigned int ++ _lookup_increment = (unsigned int)(lookup_increment>0?lookup_increment: ++ nb_lookup_candidates>1?1:-lookup_increment); ++ float best_ssd = cimg::type::max(); ++ int best_x = -1, best_y = -1; ++ for (unsigned int C = 0; C=best_ssd) break; ++ _pC-=pC._spectrum*patch_size2; ++ _pP-=pC._spectrum*patch_size2; ++ } ++ ++_pC; ++_pP; ++ } ++ if (ssd=4) { // If too much consecutive fails : ++ nb_fails = 0; ++ _lookup_size+=_lookup_size/2; // Try to expand the lookup size. ++ if (++nb_lookups>=3) { ++ if (is_strict_search) { // If still fails, switch to non-strict search mode. ++ is_strict_search = false; ++ _lookup_size = lookup_size; ++ nb_lookups = 0; ++ } ++ else return *this; // Pathological case, probably a weird mask. ++ } ++ } ++ } else { // Best patch found -> reconstruct missing part on the target patch. ++ _lookup_size = lookup_size; ++ nb_lookups = nb_fails = 0; ++ _inpaint_patch_crop(best_x - p1,best_y - p1,best_x + p2,best_y + p2,0).move_to(pbest); ++ nmask._inpaint_patch_crop(target_x - ox - p1,target_y - oy - p1,target_x - ox + p2,target_y - oy + p2,1). ++ move_to(pM); ++ cimg_for(pM,ptr,unsigned char) *ptr=1 - *ptr; ++ draw_image(target_x - p1,target_y - p1,pbest,pM,1,1); ++ confidences.draw_image(target_x - ox - p1,target_y - oy - p1,pC.fill(target_confidence),pM,1,1); ++ nmask.draw_rectangle(target_x - ox - p1,target_y - oy - p1,0,0,target_x - ox + p2,target_y - oy + p2,0,0,1); ++ priorities.draw_rectangle(target_x - ox - (int)patch_size, ++ target_y - oy - (int)patch_size,0,0, ++ target_x - ox + 3*p2/2, ++ target_y - oy + 3*p2/2,0,0,-1); ++ // Remember patch positions. ++ unsigned int *ptr_saved_patches = saved_patches.data(0,nb_saved_patches); ++ *(ptr_saved_patches++) = best_x; ++ *(ptr_saved_patches++) = best_y; ++ *(ptr_saved_patches++) = target_x; ++ *ptr_saved_patches = target_y; ++ if (++nb_saved_patches>=saved_patches._height) saved_patches.resize(4,-200,1,1,0); ++ } ++ } ++ nmask.assign(); // Free some unused memory resources. ++ priorities.assign(); ++ confidences.assign(); ++ is_visited.assign(); ++ ++ // Blend inpainting result (if requested), using multi-scale blending algorithm. ++ if (blend_size && blend_scales) { ++ const float _blend_threshold = std::max(0.0f,std::min(1.0f,blend_threshold)); ++ saved_patches._height = nb_saved_patches; ++ ++ // Re-crop image and mask if outer blending is activated. ++ if (is_blend_outer) { ++ const int ++ b2 = (int)blend_size/2, b1 = (int)blend_size - b2 - 1, ++ xb0 = std::max(0,ox - b1), ++ yb0 = std::max(0,oy - b1), ++ xb1 = std::min(_width - 1,xb0 + dx + b1 + b2), ++ yb1 = std::min(_height - 1,yb0 + dy + b1 + b2); ++ ox = xb0; oy = yb0; dx = xb1 - xb0 + 1U, dy = yb1 - yb0 + 1U; ++ } ++ ++ // Generate map of source offsets. ++ CImg offsets(dx,dy,1,2); ++ unsigned int *ptr = saved_patches.end(); ++ cimg_forY(saved_patches,i) { ++ const unsigned int yd = *(--ptr), xd = *(--ptr), ys = *(--ptr), xs = *(--ptr); ++ for (int l = -p1; l<=p2; ++l) ++ for (int k = -p1; k<=p2; ++k) { ++ const int xdk = xd + k, ydl = yd + l; ++ if (xdk>=0 && xdk<=width() - 1 && ydl>=0 && ydl<=height() - 1 && mask(xd + k,yd + l)) { ++ offsets(xd - ox + k,yd - oy + l,0) = xs + k; ++ offsets(xd - ox + k,yd - oy + l,1) = ys + l; ++ } ++ } ++ } ++ unsigned int *ptrx = offsets.data(0,0,0,0), *ptry = offsets.data(0,0,0,1); ++ cimg_forXY(offsets,x,y) { ++ if (!mask(x + ox,y + oy)) { *ptrx = x + ox; *ptry = y + oy; } ++ ++ptrx; ++ptry; ++ } ++ ++ // Generate map of local blending amplitudes. ++ CImg blend_map(dx,dy,1,1,0); ++ CImg_3x3(I,float); ++ cimg_for3XY(offsets,x,y) if (mask(x + ox,y + oy)) { ++ const float ++ iox = std::max((float)offsets(_n1x,y,0) - offsets(x,y,0), ++ (float)offsets(x,y,0) - offsets(_p1x,y,0)), ++ ioy = std::max((float)offsets(x,_n1y,1) - offsets(x,y,1), ++ (float)offsets(x,y,1) - offsets(x,_p1y,1)), ++ ion = std::sqrt(iox*iox + ioy*ioy); ++ float iin = 0; ++ cimg_forC(*this,c) { ++ cimg_get3x3(*this,x,y,0,c,I,float); ++ const float ++ iix = (float)std::max(Inc - Icc,Icc - Ipc), ++ iiy = (float)std::max(Icn - Icc,Icc - Icp); ++ iin+=std::log(1 + iix*iix + iiy*iiy); ++ } ++ iin/=_spectrum; ++ blend_map(x,y) = ion*iin; ++ } ++ blend_map.threshold(blend_map.max()*_blend_threshold).distance(1); ++ cimg_forXY(blend_map,x,y) blend_map(x,y) = 1/(1 + blend_decay*blend_map(x,y)); ++ blend_map.quantize(blend_scales + 1,false); ++ float bm, bM = blend_map.max_min(bm); ++ if (bm==bM) blend_map.fill((float)blend_scales); ++ ++ // Generate blending scales. ++ CImg result = _inpaint_patch_crop(ox,oy,ox + dx - 1,oy + dy - 1,0); ++ for (unsigned int blend_iter = 1; blend_iter<=blend_scales; ++blend_iter) { ++ const unsigned int ++ _blend_width = blend_iter*blend_size/blend_scales, ++ blend_width = _blend_width?_blend_width + 1 - (_blend_width%2):0; ++ if (!blend_width) continue; ++ const int b2 = (int)blend_width/2, b1 = (int)blend_width - b2 - 1; ++ CImg ++ blended = _inpaint_patch_crop(ox,oy,ox + dx - 1,oy + dy - 1,0), ++ cumul(dx,dy,1,1); ++ weights.assign(blend_width,blend_width,1,1,0). ++ draw_gaussian((float)b1,(float)b1,blend_width/4.0f,&one); ++ cimg_forXY(cumul,x,y) cumul(x,y) = mask(x + ox,y + oy)?0.0f:1.0f; ++ blended.mul(cumul); ++ ++ cimg_forY(saved_patches,l) { ++ const unsigned int *ptr = saved_patches.data(0,l); ++ const int ++ xs = (int)*(ptr++), ++ ys = (int)*(ptr++), ++ xd = (int)*(ptr++), ++ yd = (int)*(ptr++); ++ if (xs - b1<0 || ys - b1<0 || xs + b2>=width() || ys + b2>=height()) { // Blend with partial patch. ++ const int ++ xs0 = std::max(0,xs - b1), ++ ys0 = std::max(0,ys - b1), ++ xs1 = std::min(width() - 1,xs + b2), ++ ys1 = std::min(height() - 1,ys + b2); ++ _inpaint_patch_crop(xs0,ys0,xs1,ys1,0).move_to(pP); ++ weights._inpaint_patch_crop(xs0 - xs + b1,ys0 - ys + b1,xs1 - xs + b1,ys1 - ys + b1,0).move_to(pC); ++ blended.draw_image(xd + xs0 - xs - ox,yd + ys0 - ys - oy,pP,pC,-1); ++ cumul.draw_image(xd + xs0 - xs - ox,yd + ys0 - ys - oy,pC,-1); ++ } else { // Blend with full-size patch. ++ _inpaint_patch_crop(xs - b1,ys - b1,xs + b2,ys + b2,0).move_to(pP); ++ blended.draw_image(xd - b1 - ox,yd - b1 - oy,pP,weights,-1); ++ cumul.draw_image(xd - b1 - ox,yd - b1 - oy,weights,-1); ++ } ++ } ++ ++ if (is_blend_outer) { ++ cimg_forXY(blended,x,y) if (blend_map(x,y)==blend_iter) { ++ const float cum = cumul(x,y); ++ if (cum>0) cimg_forC(*this,c) result(x,y,c) = (T)(blended(x,y,c)/cum); ++ } ++ } else { cimg_forXY(blended,x,y) if (mask(x + ox,y + oy) && blend_map(x,y)==blend_iter) { ++ const float cum = cumul(x,y); ++ if (cum>0) cimg_forC(*this,c) result(x,y,c) = (T)(blended(x,y,c)/cum); ++ } ++ } ++ } ++ if (is_blend_outer) draw_image(ox,oy,result); ++ else cimg_forXY(result,x,y) if (mask(x + ox,y + oy)) ++ cimg_forC(*this,c) (*this)(x + ox,y + oy,c) = (T)result(x,y,c); ++ } ++ return *this; ++} ++ ++// Special crop function that supports more boundary conditions : ++// 0=dirichlet (with value 0), 1=dirichlet (with value 1) and 2=neumann. ++CImg _inpaint_patch_crop(const int x0, const int y0, const int x1, const int y1, ++ const unsigned int boundary=0) const { ++ const int ++ nx0 = x0 res(1U + nx1 - nx0,1U + ny1 - ny0,1,_spectrum); ++ if (nx0<0 || nx1>=width() || ny0<0 || ny1>=height()) { ++ if (boundary>=2) cimg_forXYZC(res,x,y,z,c) res(x,y,z,c) = _atXY(nx0 + x,ny0 + y,z,c); ++ else res.fill((T)boundary).draw_image(-nx0,-ny0,*this); ++ } else res.draw_image(-nx0,-ny0,*this); ++ return res; ++} ++ ++template ++CImg get_inpaint_patch(const CImg& mask, const unsigned int patch_size=11, ++ const unsigned int lookup_size=22, const float lookup_factor=1, ++ const int lookup_increment=1, ++ const unsigned int blend_size=0, const float blend_threshold=0.5, ++ const float blend_decay=0.02f, const unsigned int blend_scales=10, ++ const bool is_blend_outer=false) const { ++ return (+*this).inpaint_patch(mask,patch_size,lookup_size,lookup_factor,lookup_increment, ++ blend_size,blend_threshold,blend_decay,blend_scales,is_blend_outer); ++} ++ ++#endif /* cimg_plugin_inpaint */ Property changes on: head/graphics/openfx-misc/files/patch-CImg_Inpaint_inpaint.h ___________________________________________________________________ Added: fbsd:nokeywords ## -0,0 +1 ## +yes \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/graphics/openfx-misc/files/patch-CImg_Makefile =================================================================== --- head/graphics/openfx-misc/files/patch-CImg_Makefile (nonexistent) +++ head/graphics/openfx-misc/files/patch-CImg_Makefile (revision 468804) @@ -0,0 +1,22 @@ +--- CImg/Makefile.orig 2018-04-05 10:43:14 UTC ++++ CImg/Makefile +@@ -179,16 +179,16 @@ endif + CIMGVERSION=88fab6de7bfc141a1f577e3cf1b17b9fb1e4f438 + + CImg.h: Inpaint/inpaint.h +- curl -L -s -S -o $@ https://raw.githubusercontent.com/dtschump/CImg/$(CIMGVERSION)/CImg.h ++# curl -L -s -S -o $@ https://raw.githubusercontent.com/dtschump/CImg/$(CIMGVERSION)/CImg.h + # patch -p0 -d. < CImg-2.0.0-anisotropic.patch + # patch -p0 -d. < CImg-1.7.1-omp.patch + + Inpaint/inpaint.h: +- curl -L -s -S -o $@ https://raw.githubusercontent.com/dtschump/CImg/$(CIMGVERSION)/plugins/inpaint.h ++# curl -L -s -S -o $@ https://raw.githubusercontent.com/dtschump/CImg/$(CIMGVERSION)/plugins/inpaint.h + patch -p0 -d. < Inpaint/inpaint.h.patch + + nlmeans.h: +- curl -L -s -S -o $@ https://raw.githubusercontent.com/dtschump/CImg/$(CIMGVERSION)/plugins/nlmeans.h ++# curl -L -s -S -o $@ https://raw.githubusercontent.com/dtschump/CImg/$(CIMGVERSION)/plugins/nlmeans.h + + #git archive --remote=git://git.code.sf.net/p/gmic/source $(CIMGVERSION):src CImg.h | tar xf - + Property changes on: head/graphics/openfx-misc/files/patch-CImg_Makefile ___________________________________________________________________ Added: fbsd:nokeywords ## -0,0 +1 ## +yes \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/graphics/openfx-misc/files/patch-CImg_nlmeans.h =================================================================== --- head/graphics/openfx-misc/files/patch-CImg_nlmeans.h (nonexistent) +++ head/graphics/openfx-misc/files/patch-CImg_nlmeans.h (revision 468804) @@ -0,0 +1,245 @@ +--- CImg/nlmeans.h.orig 2018-04-30 23:16:26 UTC ++++ CImg/nlmeans.h +@@ -0,0 +1,242 @@ ++/* ++ # ++ # File : nlmeans.h ++ # ( C++ header file - CImg plug-in ) ++ # ++ # Description : CImg plugin that implements the non-local mean filter. ++ # This file is a part of the CImg Library project. ++ # ( http://cimg.eu ) ++ # ++ # [1] Buades, A.; Coll, B.; Morel, J.-M.: A non-local algorithm for image denoising ++ # IEEE Computer Society Conference on Computer Vision and Pattern Recognition, 2005. CVPR 2005. ++ # Volume 2, 20-25 June 2005 Page(s):60 - 65 vol. 2 ++ # ++ # [2] Buades, A. Coll, B. and Morel, J.: A review of image denoising algorithms, with a new one. ++ # Multiscale Modeling and Simulation: A SIAM Interdisciplinary Journal 4 (2004) 490-530 ++ # ++ # [3] Gasser, T. Sroka,L. Jennen Steinmetz,C. Residual variance and residual pattern nonlinear regression. ++ # Biometrika 73 (1986) 625-659 ++ # ++ # Copyright : Jerome Boulanger ++ # ( http://www.irisa.fr/vista/Equipe/People/Jerome.Boulanger.html ) ++ # ++ # 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_nlmeans ++#define cimg_plugin_nlmeans ++ ++//! NL-Means denoising algorithm. ++/** ++ This is the in-place version of get_nlmean(). ++**/ ++CImg& nlmeans(int patch_size=1, double lambda=-1, double alpha=3, double sigma=-1, int sampling=1){ ++ if (!is_empty()){ ++ if (sigma<0) sigma = std::sqrt(variance_noise()); // noise variance estimation ++ const double np = (2*patch_size + 1)*(2*patch_size + 1)*spectrum()/(double)sampling; ++ if (lambda<0) {// Bandwidth estimation ++ if (np<100) ++ lambda = ((((((1.1785e-12*np - 5.1827e-10)*np + 9.5946e-08)*np - ++ 9.7798e-06)*np + 6.0756e-04)*np - 0.0248)*np + 1.9203)*np + 7.9599; ++ else ++ lambda = (-7.2611e-04*np + 1.3213)*np + 15.2726; ++ } ++#if cimg_debug>=1 ++ std::fprintf(stderr,"Size of the patch : %dx%d \n", ++ 2*patch_size + 1,2*patch_size + 1); ++ std::fprintf(stderr,"Size of window where similar patch are looked for : %dx%d \n", ++ (int)(alpha*(2*patch_size + 1)),(int)(alpha*(2*patch_size + 1))); ++ std::fprintf(stderr,"Bandwidth of the kernel : %fx%f^2 \n", ++ lambda,sigma); ++ std::fprintf(stderr,"Noise standard deviation estimated to : %f \n", ++ sigma); ++#endif ++ ++ CImg dest(width(),height(),depth(),spectrum(),0); ++ double *uhat = new double[spectrum()]; ++ const double h2 = -.5/(lambda*sigma*sigma); // [Kervrann] notations ++ if (depth()!=1){ // 3D case ++ const CImg<> P = (*this).get_blur(1); // inspired from Mahmoudi&Sapiro SPletter dec 05 ++ const int n_simu = 64; ++ CImg<> tmp(n_simu,n_simu,n_simu); ++ const double sig = std::sqrt(tmp.fill(0.f).noise(sigma).blur(1).pow(2.).sum()/(n_simu*n_simu*n_simu)); ++ const int ++ patch_size_z = 0, ++ pxi = (int)(alpha*patch_size), ++ pyi = (int)(alpha*patch_size), ++ pzi = 2; //Define the size of the neighborhood in z ++ for (int zi = 0; zi=1 ++ std::fprintf(stderr,"\rProcessing : %3d %%",(int)((float)zi/(float)depth()*100.));fflush(stdout); ++#endif ++ for (int yi = 0; yi=0 && zj_=0 && zi_=0 && yj_=0 && yi_=0 && xj_=0 && xi_wmax?w:wmax; ++ cimg_forC(*this,v) uhat[v]+=w*(*this)(xj,yj,zj,v); ++ sw+=w; ++ } ++ } ++ // add the central pixel ++ cimg_forC(*this,v) uhat[v]+=wmax*(*this)(xi,yi,zi,v); ++ sw+=wmax; ++ if (sw) cimg_forC(*this,v) dest(xi,yi,zi,v) = (T)(uhat[v]/=sw); ++ else cimg_forC(*this,v) dest(xi,yi,zi,v) = (*this)(xi,yi,zi,v); ++ } ++ } ++ } ++ else { // 2D case ++ const CImg<> P = (*this).get_blur(1); // inspired from Mahmoudi&Sapiro SPletter dec 05 ++ const int n_simu = 512; ++ CImg<> tmp(n_simu,n_simu); ++ const double sig = std::sqrt(tmp.fill(0.f).noise(sigma).blur(1).pow(2.).sum()/(n_simu*n_simu)); ++ const int ++ pxi = (int)(alpha*patch_size), ++ pyi = (int)(alpha*patch_size); //Define the size of the neighborhood ++ for (int yi = 0; yi=1 ++ std::fprintf(stderr,"\rProcessing : %3d %%",(int)((float)yi/(float)height()*100.));fflush(stdout); ++#endif ++ for (int xi = 0; xi=0 && yj_=0 && yi_=0 && xj_=0 && xi_wmax?w:wmax; // Store the maximum of the weights ++ sw+=w; // Compute the sum of the weights ++ } ++ } ++ // add the central pixel with the maximum weight ++ cimg_forC(*this,v) uhat[v]+=wmax*(*this)(xi,yi,v); ++ sw+=wmax; ++ ++ // Compute the estimate for the current pixel ++ if (sw) cimg_forC(*this,v) dest(xi,yi,v) = (T)(uhat[v]/=sw); ++ else cimg_forC(*this,v) dest(xi,yi,v) = (*this)(xi,yi,v); ++ } ++ } // main loop ++ } // 2d ++ delete [] uhat; ++ dest.move_to(*this); ++#if cimg_debug>=1 ++ std::fprintf(stderr,"\n"); // make a new line ++#endif ++ } // is empty ++ return *this; ++} ++ ++//! Get the result of the NL-Means denoising algorithm. ++/** ++ \param patch_size = radius of the patch (1=3x3 by default) ++ \param lambda = bandwidth ( -1 by default : automatic selection) ++ \param alpha = size of the region where similar patch are searched (3 x patch_size = 9x9 by default) ++ \param sigma = noise standard deviation (-1 = estimation) ++ \param sampling = sampling of the patch (1 = uses all point, 2 = uses one point on 4, etc) ++ If the image has three dimensions then the patch is only in 2D and the neighborhood extent in time is only 5. ++ If the image has several channel (color images), the distance between the two patch is computed using ++ all the channels. ++ The greater the patch is the best is the result. ++ Lambda parameter is function of the size of the patch size. The automatic Lambda parameter is taken ++ in the Chi2 table at a significiance level of 0.01. This diffear from the original paper [1]. ++ The weighted average becomes then: ++ \f$$ \hat{f}(x,y) = \sum_{x',y'} \frac{1}{Z} exp(\frac{P(x,y)-P(x',y')}{2 \lambda \sigma^2}) f(x',y') $$\f ++ where \f$ P(x,y) $\f denotes the patch in (x,y) location. ++ ++ An a priori is also used to increase the speed of the algorithm in the spirit of Sapiro et al. SPletter dec 05 ++ ++ This very basic version of the Non-Local Means algorithm provides an output image which contains ++ some residual noise with a relatively small variance (\f$\sigma<5$\f). ++ ++ [1] A non-local algorithm for image denoising ++ Buades, A.; Coll, B.; Morel, J.-M.; ++ Computer Vision and Pattern Recognition, 2005. CVPR 2005. IEEE Computer Society Conference on ++ Volume 2, 20-25 June 2005 Page(s):60 - 65 vol. 2 ++**/ ++CImg get_nlmeans( int patch_size=1, double lambda=-1, double alpha=3 ,double sigma=-1, int sampling=1) const { ++ return CImg(*this).nlmeans(patch_size,lambda,alpha,sigma,sampling); ++} ++ ++#endif /* cimg_plugin_nlmeans */ Property changes on: head/graphics/openfx-misc/files/patch-CImg_nlmeans.h ___________________________________________________________________ Added: fbsd:nokeywords ## -0,0 +1 ## +yes \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/graphics/openfx-misc/files/patch-Makefile.master =================================================================== --- head/graphics/openfx-misc/files/patch-Makefile.master (nonexistent) +++ head/graphics/openfx-misc/files/patch-Makefile.master (revision 468804) @@ -0,0 +1,9 @@ +--- Makefile.master.orig 2018-04-05 10:43:14 UTC ++++ Makefile.master +@@ -8,5 +8,5 @@ include $(PATHTOROOT)/Plugins/Makefile.master + + CXXFLAGS += -DOFX_EXTENSIONS_VEGAS -DOFX_EXTENSIONS_NUKE -DOFX_EXTENSIONS_NATRON -DOFX_EXTENSIONS_TUTTLE -DOFX_SUPPORTS_OPENGLRENDER + +-CXXFLAGS += -I$(TOP_SRCDIR)/Misc -I$(OFXSEXTPATH) ++CXXFLAGS += -I$(TOP_SRCDIR)/Misc -I$(OFXSEXTPATH) -I/usr/local/include + VPATH += $(TOP_SRCDIR)/Misc $(OFXSEXTPATH) Property changes on: head/graphics/openfx-misc/files/patch-Makefile.master ___________________________________________________________________ Added: fbsd:nokeywords ## -0,0 +1 ## +yes \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/graphics/openfx-misc/pkg-descr =================================================================== --- head/graphics/openfx-misc/pkg-descr (nonexistent) +++ head/graphics/openfx-misc/pkg-descr (revision 468804) @@ -0,0 +1,5 @@ +Miscellaneous OFX / OpenFX / Open Effects plugins. +These plugins were primarily developped for Natron, but may be used with other +OpenFX hosts. + +WWW: https://github.com/devernay/openfx-misc Property changes on: head/graphics/openfx-misc/pkg-descr ___________________________________________________________________ Added: fbsd:nokeywords ## -0,0 +1 ## +yes \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/graphics/openfx-misc/pkg-plist =================================================================== --- head/graphics/openfx-misc/pkg-plist (nonexistent) +++ head/graphics/openfx-misc/pkg-plist (revision 468804) @@ -0,0 +1,342 @@ +OFX/Plugins/CImg.ofx.bundle/Contents/Info.plist +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/eu.cimg.EdgeDetect.png +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/eu.cimg.EdgeDetect.svg +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/eu.cimg.EdgeExtend.png +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/eu.cimg.EdgeExtend.svg +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/eu.cimg.Inpaint.png +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/eu.cimg.Inpaint.svg +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/fr.inria.EdgeBlur.png +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/fr.inria.EdgeBlur.svg +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgBilateral.png +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgBilateral.svg +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgBloom.png +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgBloom.svg +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgBlur.png +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgBlur.svg +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgChromaBlur.png +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgChromaBlur.svg +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgDenoise.png +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgDenoise.svg +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgDilate.png +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgDilate.svg +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgEqualize.png +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgEqualize.svg +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgErode.png +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgErode.svg +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgErodeSmooth.png +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgErodeSmooth.svg +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgExpression.png +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgExpression.svg +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgGuided.png +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgGuided.svg +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgHistEQ.png +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgHistEQ.svg +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgLaplacian.png +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgLaplacian.svg +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgNoise.png +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgNoise.svg +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgPlasma.png +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgPlasma.svg +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgRollingGuidance.png +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgRollingGuidance.svg +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgSharpen.png +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgSharpen.svg +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgSharpenInvDiff.png +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgSharpenInvDiff.svg +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgSharpenShock.png +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgSmooth.png +OFX/Plugins/CImg.ofx.bundle/Contents/Resources/net.sf.cimg.CImgSmooth.svg +OFX/Plugins/CImg.ofx.bundle/Contents/FreeBSD-x86-64/CImg.ofx +OFX/Plugins/Misc.ofx.bundle/Contents/Info.plist +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/PIKColor.gizmo +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/PIKColor.py +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/fr.inria.PIKColor.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/fr.inria.PIKColor.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.AddPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.AdjustRoDPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.AdjustRoDPlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.CheckerBoardPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.CheckerBoardPlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.ChromaKeyerPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.ChromaKeyerPlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Clamp.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Clamp.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.ClipTestPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.ClipTestPlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.ColorBars.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.ColorBars.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.ColorCorrectPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.ColorCorrectPlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.ColorLookupPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.ColorLookupPlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.ColorMatrixPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.ColorMatrixPlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.ColorWheel.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.ColorWheel.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.ConstantPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.ConstantPlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.CornerPinMaskedPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.CornerPinMaskedPlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.CornerPinPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.CornerPinPlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.CropPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.CropPlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Deinterlace.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Deinterlace.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Despill.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Despill.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.DifferencePlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.DifferencePlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.DirBlur.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.DirBlur.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.DissolvePlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.DissolvePlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.FrameBlend.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.FrameBlend.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.FrameHold.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.FrameHold.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.FrameRange.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.FrameRange.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.GammaPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.GodRays.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.GodRays.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.GradePlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.GradePlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.HSVToRGB.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.HSVToRGB.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.HSVToolPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.HSVToolPlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.HueCorrect.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.HueCorrect.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.HueKeyer.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.HueKeyer.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.IDistort.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.IDistort.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.ImageStatistics.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.ImageStatistics.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Invert.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Invert.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.KeyerPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.KeyerPlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.LabToRGB709.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.LensDistortion.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.LensDistortion.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.MergeDifference.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.MergeIn.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.MergeMax.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.MergeMin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.MergeMultiply.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.MergeOut.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.MergePlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.MergePlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.MergePlus.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.MergeScreen.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Mirror.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Mirror.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.MultiplyPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.NoOpPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.NoOpPlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Noise.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Noise.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.PIK.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.PIK.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Position.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Position.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Premult.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Premult.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.RGB709ToLab.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.RGB709ToXYZ.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.RGBToHSV.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.RGBToHSV.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Radial.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Radial.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Ramp.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Ramp.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Rectangle.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Rectangle.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Reformat.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Reformat.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Retime.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Retime.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.RotoPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.RotoPlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.STMap.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.STMap.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.SaturationPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.SaturationPlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.ShufflePlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.ShufflePlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.SlitScan.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.SlitScan.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.TrackerPM.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.TrackerPM.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.TransformMaskedPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.TransformMaskedPlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.TransformPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.TransformPlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Unpremult.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.Unpremult.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.VectorToColorPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.VectorToColorPlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.XYZToRGB709.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.anaglyphPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.anaglyphPlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.joinViewsPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.joinViewsPlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.mixViewsPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.mixViewsPlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.oneViewPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.oneViewPlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.sideBySidePlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.sideBySidePlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.switchPlugin.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.switchPlugin.svg +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.timeOffset.png +OFX/Plugins/Misc.ofx.bundle/Contents/Resources/net.sf.openfx.timeOffset.svg +OFX/Plugins/Misc.ofx.bundle/Contents/FreeBSD-x86-64/Misc.ofx +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Info.plist +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/net.sf.openfx.Shadertoy.png +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/net.sf.openfx.Shadertoy.svg +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/Makefile +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/CRT-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/DawnBringer 4bit-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/DoF_bokeh_2.4-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/DoF_bokeh_2.4.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/Shadertoy.txt +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/anaglyphic-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/ball.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/barrelblurchroma.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/barrelblurchroma-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/basicfractal.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/bilateralfilter.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/bilateralfilter-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/bleepyblocks.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/bloompaint-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/bloompostproc.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/bloompostproc-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/blurpoissondisc.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/blurpoissondisc-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/bokehdisc-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/bubbles.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/c64.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/cellular.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/chromaticaberration.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/circularblur.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/circularblur-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/cloud-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/cloudy sky-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/cmykhalftone.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/cmykhalftone-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/colorcircles.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/color grid-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/cubify-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/deformflower.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/disks-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/dotdotdot.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/dotdotdot-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/fastblur.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/fastblur-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/filmgrain.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/filmgrain-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/fireball.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/fireball-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/fisheye-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/flaring.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/flash-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/fractaltiling.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/gaussianblur.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/gaussianblur-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/glitch01.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/glitch01-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/glitch02.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/glitch02-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/glitchA.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/glitchA-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/glitchB.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/glitchB-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/glowingthing.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/hdrbloom.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/imagecelshade.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/imagecelshade-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/infinitefall.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/inputtime.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/interationsshiny.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/iterationsshiny.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/iterationscoral.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/iterationsguts.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/iterationsinv.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/iterationstrig.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/iterationsworms.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/juliabulb.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/juliasm.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/juliatrap.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/kaleidoscope-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/lensflare.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/mandelbrotdist.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/mandelbrottrap.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/medianfilter.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/medianfilter-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/mipmapblur.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/mipmapblur-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/moneyfilter.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/moneyfilter-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/montecarloblur.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/montecarloblur-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/mpegartifacts.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/noise.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/noiseanimelectric.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/noiseanimlava.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/noiseanimwatery.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/noiseblur.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/noise distortion-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/notebook.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/oldvideo.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/oldvideo-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/plasma2.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/plasmatriangle.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/postprocessing.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/postprocessing-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/quadmirror-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/radialblur.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/radialblur-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/raining.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/sea-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/sharpen-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/silexarst.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/simplefire.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/sinebands.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/skyatnight.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/spiral.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/startunnel.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/star nest-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/stellar.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/stripes-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/test.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/tvsnow.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/vangogh.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/venus.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/vignette-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/voronoi distance-natron.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/warp.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/warpproc1.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/warpproc2.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/warpproc3.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/warpproc4.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/warptex.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/watercaustic.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources/presets/default/worleynoisewaters.frag.glsl +OFX/Plugins/Shadertoy.ofx.bundle/Contents/FreeBSD-x86-64/Shadertoy.ofx +@dir OFX/Plugins/CImg.ofx.bundle/Contents/Resources +@dir OFX/Plugins/CImg.ofx.bundle/Contents/FreeBSD-x86-64 +@dir OFX/Plugins/CImg.ofx.bundle/Contents +@dir OFX/Plugins/CImg.ofx.bundle +@dir OFX/Plugins/Misc.ofx.bundle/Contents/Resources +@dir OFX/Plugins/Misc.ofx.bundle/Contents/FreeBSD-x86-64 +@dir OFX/Plugins/Misc.ofx.bundle/Contents +@dir OFX/Plugins/Misc.ofx.bundle +@dir OFX/Plugins/Shadertoy.ofx.bundle/Contents/Resources +@dir OFX/Plugins/Shadertoy.ofx.bundle/Contents/FreeBSD-x86-64 +@dir OFX/Plugins/Shadertoy.ofx.bundle/Contents +@dir OFX/Plugins/Shadertoy.ofx.bundle +@dir OFX/Plugins +@dir OFX Property changes on: head/graphics/openfx-misc/pkg-plist ___________________________________________________________________ Added: fbsd:nokeywords ## -0,0 +1 ## +yes \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property