Skip to content
Snippets Groups Projects
CImg.h 2.76 MiB
Newer Older
  • Learn to ignore specific revisions
  • 57001 57002 57003 57004 57005 57006 57007 57008 57009 57010 57011 57012 57013 57014 57015 57016 57017 57018 57019 57020 57021 57022 57023 57024 57025 57026 57027 57028 57029 57030 57031 57032 57033 57034 57035 57036 57037 57038 57039 57040 57041 57042 57043 57044 57045 57046 57047 57048 57049 57050 57051 57052 57053 57054 57055 57056 57057 57058 57059 57060 57061 57062 57063 57064 57065 57066 57067 57068 57069 57070 57071 57072 57073 57074 57075 57076 57077 57078 57079 57080 57081 57082 57083 57084 57085 57086 57087 57088 57089 57090 57091 57092 57093 57094 57095 57096 57097 57098 57099 57100 57101 57102 57103 57104 57105 57106 57107 57108 57109 57110 57111 57112 57113 57114 57115 57116 57117 57118 57119 57120 57121 57122 57123 57124 57125 57126 57127 57128 57129 57130 57131 57132 57133 57134 57135 57136 57137 57138 57139 57140 57141 57142 57143 57144 57145 57146 57147 57148 57149 57150 57151 57152 57153 57154 57155 57156 57157 57158 57159 57160 57161 57162 57163 57164 57165 57166 57167 57168 57169 57170 57171 57172 57173 57174 57175 57176 57177 57178 57179 57180 57181 57182 57183 57184 57185 57186 57187 57188 57189 57190 57191 57192 57193 57194 57195 57196 57197 57198 57199 57200 57201 57202 57203 57204 57205 57206 57207 57208 57209 57210 57211 57212 57213 57214 57215 57216 57217 57218 57219 57220 57221 57222 57223 57224 57225 57226 57227 57228 57229 57230 57231 57232 57233 57234 57235 57236 57237 57238 57239 57240 57241 57242 57243 57244 57245 57246 57247 57248 57249 57250 57251 57252 57253 57254 57255 57256 57257 57258 57259 57260 57261 57262 57263 57264 57265 57266 57267 57268 57269 57270 57271 57272 57273 57274 57275 57276 57277 57278 57279 57280 57281 57282 57283 57284 57285 57286 57287 57288 57289 57290 57291 57292 57293 57294 57295 57296 57297 57298 57299 57300 57301 57302 57303 57304 57305 57306 57307 57308 57309 57310 57311 57312 57313 57314 57315 57316 57317 57318 57319 57320 57321 57322 57323 57324 57325 57326 57327 57328 57329 57330 57331 57332 57333 57334 57335 57336 57337 57338 57339 57340 57341 57342 57343 57344 57345 57346 57347 57348 57349 57350 57351 57352 57353 57354 57355 57356 57357 57358 57359 57360 57361 57362 57363 57364 57365 57366 57367 57368 57369 57370 57371 57372 57373 57374 57375 57376 57377 57378 57379 57380 57381 57382 57383 57384 57385 57386 57387 57388 57389 57390 57391 57392 57393 57394 57395 57396 57397 57398 57399 57400 57401 57402 57403 57404 57405 57406 57407 57408 57409 57410 57411 57412 57413 57414 57415 57416 57417 57418 57419 57420 57421 57422 57423 57424 57425 57426 57427 57428 57429 57430 57431 57432 57433 57434 57435 57436 57437 57438 57439 57440 57441 57442 57443 57444 57445 57446 57447 57448 57449 57450 57451 57452 57453 57454 57455 57456 57457 57458 57459 57460 57461 57462 57463 57464 57465 57466 57467 57468 57469 57470 57471 57472 57473 57474 57475 57476 57477 57478 57479 57480 57481 57482 57483 57484 57485 57486 57487 57488 57489 57490 57491 57492 57493 57494 57495 57496 57497 57498 57499 57500 57501 57502 57503 57504 57505 57506 57507 57508 57509 57510 57511 57512 57513 57514 57515 57516 57517 57518 57519 57520 57521 57522 57523 57524 57525 57526 57527 57528 57529 57530 57531 57532 57533 57534 57535 57536 57537 57538 57539 57540 57541 57542 57543 57544 57545 57546 57547 57548 57549 57550 57551 57552 57553 57554 57555 57556 57557 57558 57559 57560 57561 57562 57563 57564 57565 57566 57567 57568 57569 57570 57571 57572 57573 57574 57575 57576 57577 57578 57579 57580 57581 57582 57583 57584 57585 57586 57587 57588 57589 57590 57591 57592 57593 57594 57595 57596 57597 57598 57599 57600 57601 57602 57603 57604 57605 57606 57607 57608 57609 57610 57611 57612 57613 57614 57615 57616 57617 57618 57619 57620 57621 57622 57623 57624 57625 57626 57627 57628 57629 57630 57631 57632 57633 57634 57635 57636 57637 57638 57639 57640 57641 57642 57643 57644 57645 57646 57647 57648 57649 57650 57651 57652 57653 57654 57655 57656 57657 57658 57659 57660 57661 57662 57663 57664 57665 57666 57667 57668 57669 57670 57671 57672 57673 57674 57675 57676 57677 57678 57679 57680 57681 57682 57683 57684 57685 57686 57687 57688 57689 57690 57691 57692 57693 57694 57695 57696 57697 57698 57699 57700 57701 57702 57703 57704 57705 57706 57707 57708 57709 57710 57711 57712 57713 57714 57715 57716 57717 57718 57719 57720 57721 57722 57723 57724 57725 57726 57727 57728 57729 57730 57731 57732 57733 57734 57735 57736 57737 57738 57739 57740 57741 57742 57743 57744 57745 57746 57747 57748 57749 57750 57751 57752 57753 57754 57755 57756 57757 57758 57759 57760 57761 57762 57763 57764 57765 57766 57767 57768 57769 57770 57771 57772 57773 57774 57775 57776 57777 57778 57779 57780 57781 57782 57783 57784 57785 57786 57787 57788 57789 57790 57791 57792 57793 57794 57795 57796 57797 57798 57799 57800 57801 57802 57803 57804 57805 57806 57807 57808 57809 57810 57811 57812 57813 57814 57815 57816 57817 57818 57819 57820 57821 57822 57823 57824 57825 57826 57827 57828 57829 57830 57831 57832 57833 57834 57835 57836 57837 57838 57839 57840 57841 57842 57843 57844 57845 57846 57847 57848 57849 57850 57851 57852 57853 57854 57855 57856 57857 57858 57859 57860 57861 57862 57863 57864 57865 57866 57867 57868 57869 57870 57871 57872 57873 57874 57875 57876 57877 57878 57879 57880 57881 57882 57883 57884 57885 57886 57887 57888 57889 57890 57891 57892 57893 57894 57895 57896 57897 57898 57899 57900 57901 57902 57903 57904 57905 57906 57907 57908 57909 57910 57911 57912 57913 57914 57915 57916 57917 57918 57919 57920 57921 57922 57923 57924 57925 57926 57927 57928 57929 57930 57931 57932 57933 57934 57935 57936 57937 57938 57939 57940 57941 57942 57943 57944 57945 57946 57947 57948 57949 57950 57951 57952 57953 57954 57955 57956 57957 57958 57959 57960 57961 57962 57963 57964 57965 57966 57967 57968 57969 57970 57971 57972 57973 57974 57975 57976 57977 57978 57979 57980 57981 57982 57983 57984 57985 57986 57987 57988 57989 57990 57991 57992 57993 57994 57995 57996 57997 57998 57999 58000
              imn = (unsigned int)vec[7];
            const float ri = vec[4], rs = vec[5], ss = vec[6];
            switch (pixsize) {
            case 8 : {
              CImg<ucharT> 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<T>& img = (*this)[imn];
              cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss));
            } break;
            case 16 : {
              CImg<ushortT> 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<T>& img = (*this)[imn];
              cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss));
            } break;
            case 32 : {
              CImg<uintT> 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<T>& 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<T> get_load_parrec(const char *const filename) {
          return CImgList<T>().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 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<T>& load_yuv(const char *const filename,
                              const unsigned int size_x, const unsigned int size_y,
                              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,first_frame,last_frame,step_frame,yuv2rgb);
        }
    
        //! Load a list from a YUV image sequence file \newinstance.
        static CImgList<T> get_load_yuv(const char *const filename,
                                        const unsigned int size_x, const unsigned int size_y=1,
                                        const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                                        const unsigned int step_frame=1, const bool yuv2rgb=true) {
          return CImgList<T>().load_yuv(filename,size_x,size_y,first_frame,last_frame,step_frame,yuv2rgb);
        }
    
        //! Load a list from an image sequence YUV file \overloading.
        CImgList<T>& load_yuv(std::FILE *const file,
                              const unsigned int size_x, const unsigned int size_y,
                              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,first_frame,last_frame,step_frame,yuv2rgb);
        }
    
        //! Load a list from an image sequence YUV file \newinstance.
        static CImgList<T> get_load_yuv(std::FILE *const file,
                                        const unsigned int size_x, const unsigned int size_y=1,
                                        const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                                        const unsigned int step_frame=1, const bool yuv2rgb=true) {
          return CImgList<T>().load_yuv(file,size_x,size_y,first_frame,last_frame,step_frame,yuv2rgb);
        }
    
        CImgList<T>& _load_yuv(std::FILE *const file, const char *const filename,
                               const unsigned int size_x, const unsigned int size_y,
                               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 (size_x%2 || size_y%2)
            throw CImgArgumentException(_cimglist_instance
                                        "load_yuv(): Invalid odd XY dimensions %ux%u in file '%s'.",
                                        cimglist_instance,
                                        size_x,size_y,filename?filename:"(FILE*)");
          if (!size_x || !size_y)
            throw CImgArgumentException(_cimglist_instance
                                        "load_yuv(): Invalid sequence size (%u,%u) in file '%s'.",
                                        cimglist_instance,
                                        size_x,size_y,filename?filename:"(FILE*)");
    
          const unsigned int
            nfirst_frame = first_frame<last_frame?first_frame:last_frame,
            nlast_frame = first_frame<last_frame?last_frame:first_frame,
            nstep_frame = step_frame?step_frame:1;
    
          CImg<ucharT> tmp(size_x,size_y,1,3), UV(size_x/2,size_y/2,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,nfirst_frame*(size_x*size_y + size_x*size_y/2),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) {
            tmp.fill(0);
            // *TRY* to read the luminance part, do not replace by cimg::fread!
            err = (int)std::fread((void*)(tmp._data),1,(ulongT)tmp._width*tmp._height,nfile);
            if (err!=(int)(tmp._width*tmp._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 {
                cimg_forXY(UV,x,y) {
                  const int x2 = x*2, y2 = y*2;
                  tmp(x2,y2,1) = tmp(x2 + 1,y2,1) = tmp(x2,y2 + 1,1) = tmp(x2 + 1,y2 + 1,1) = UV(x,y,0);
                  tmp(x2,y2,2) = tmp(x2 + 1,y2,2) = tmp(x2,y2 + 1,2) = tmp(x2 + 1,y2 + 1,2) = UV(x,y,1);
                }
                if (yuv2rgb) tmp.YCbCrtoRGB();
                insert(tmp);
                if (nstep_frame>1) cimg::fseek(nfile,(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<T>& 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<charT> filenames(32);
          static CImg<uintT> 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<charT>::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<first_frame) {
            cimg::mutex(9);
            if (!cvGrabFrame(captures[index])) { cimg::mutex(9,0); go_on = false; break; }
            cimg::mutex(9,0);
            ++pos;
          }
    
          // Read and convert frames.
          const IplImage *src = 0;
          if (go_on) {
            const unsigned int _last_frame = std::min(nb_frames?nb_frames - 1:~0U,last_frame);
            while (pos<=_last_frame) {
              cimg::mutex(9);
              src = cvQueryFrame(captures[index]);
              if (src) {
                CImg<T> 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<step_frame && pos<=_last_frame; ++i, ++pos)
                  if (!cvGrabFrame(captures[index])) { skip_failed = true; break; }
                if (skip_failed) src = 0;
              }
              cimg::mutex(9,0);
              if (!src) break;
            }
          }
    
          if (!src || (nb_frames && pos>=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<T> 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<T>().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<T>& 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<charT> 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);
    #if cimg_OS!=2
          cimg_snprintf(command,command._width,"%s -i \"%s\" \"%s\" >/dev/null 2>&1",
                        cimg::ffmpeg_path(),
                        CImg<charT>::string(filename)._system_strescape().data(),
                        CImg<charT>::string(filename_tmp2)._system_strescape().data());
    #else
          cimg_snprintf(command,command._width,"\"%s -i \"%s\" \"%s\"\" >NUL 2>&1",
                        cimg::ffmpeg_path(),
                        CImg<charT>::string(filename)._system_strescape().data(),
                        CImg<charT>::string(filename_tmp2)._system_strescape().data());
    #endif
          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<T> 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<T> get_load_ffmpeg_external(const char *const filename) {
          return CImgList<T>().load_ffmpeg_external(filename);
        }
    
        //! Load gif file, using ImageMagick or GraphicsMagick's external tools.
        /**
          \param filename Filename to read data from.
          \param use_graphicsmagick Tells if GraphicsMagick's tool 'gm' is used instead of ImageMagick's tool 'convert'.
        **/
        CImgList<T>& 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<T>().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<T>& _load_gif_external(const char *const filename, const bool use_graphicsmagick=false) {
          CImg<charT> 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 cimg_OS!=2
          if (use_graphicsmagick) cimg_snprintf(command,command._width,"%s convert \"%s\" \"%s.png\" >/dev/null 2>&1",
                                                cimg::graphicsmagick_path(),
                                                CImg<charT>::string(filename)._system_strescape().data(),
                                                CImg<charT>::string(filename_tmp)._system_strescape().data());
          else cimg_snprintf(command,command._width,"%s \"%s\" \"%s.png\" >/dev/null 2>&1",
                             cimg::imagemagick_path(),
                             CImg<charT>::string(filename)._system_strescape().data(),
                             CImg<charT>::string(filename_tmp)._system_strescape().data());
    #else
          if (use_graphicsmagick) cimg_snprintf(command,command._width,"\"%s convert \"%s\" \"%s.png\"\" >NUL 2>&1",
                                                cimg::graphicsmagick_path(),
                                                CImg<charT>::string(filename)._system_strescape().data(),
                                                CImg<charT>::string(filename_tmp)._system_strescape().data());
          else cimg_snprintf(command,command._width,"\"%s \"%s\" \"%s.png\"\" >NUL 2>&1",
                             cimg::imagemagick_path(),
                             CImg<charT>::string(filename)._system_strescape().data(),
                             CImg<charT>::string(filename_tmp)._system_strescape().data());
    #endif
          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<T> 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<T> 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<T> get_load_gif_external(const char *const filename) {
          return CImgList<T>().load_gif_external(filename);
        }
    
        //! Load a gzipped list, using external tool 'gunzip'.
        /**
          \param filename Filename to read data from.
        **/
        CImgList<T>& 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<charT> 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<charT>::string(filename)._system_strescape().data(),
                        CImg<charT>::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<T> get_load_gzip_external(const char *const filename) {
          return CImgList<T>().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<typename tf, typename tc>
        CImgList<T>& load_off(const char *const filename,
                              CImgList<tf>& primitives, CImgList<tc>& colors) {
          return get_load_off(filename,primitives,colors).move_to(*this);
        }
    
        //! Load a 3d object from a .OFF file \newinstance.
        template<typename tf, typename tc>
          static CImgList<T> get_load_off(const char *const filename,
                                          CImgList<tf>& primitives, CImgList<tc>& colors) {
          return CImg<T>().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.
        **/
        CImgList<T>& 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<charT> *const description=0) {
          const unsigned int
            nfirst_frame = first_frame<last_frame?first_frame:last_frame,
            nstep_frame = step_frame?step_frame:1;
          unsigned int nlast_frame = first_frame<last_frame?last_frame:first_frame;
    #ifndef cimg_use_tiff
          cimg::unused(voxel_size,description);
          if (nfirst_frame || nlast_frame!=~0U || nstep_frame!=1)
            throw CImgArgumentException(_cimglist_instance
                                        "load_tiff(): Unable to load sub-images from file '%s' unless libtiff is enabled.",
                                        cimglist_instance,
                                        filename);
    
          return assign(CImg<T>::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<T> 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<charT> *const description=0) {
          return CImgList<T>().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<T>& 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<charT> _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 <tt>{ 'x' | 'y' | 'z' | 'c' }</tt>.
           \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<T>& 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.
           \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<T>& 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 <tt>{ 'x' | 'y' | 'z' | 'c' }</tt>.
          \param align Appending alignment.
        **/
        const CImgList<T>& 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<T>& _display(CImgDisplay &disp, const char *const title, const CImgList<charT> *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<T> &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<T> &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<char> dtitle = CImg<char>::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<intT> 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<T> sublist = get_shared_images(ind0,ind1);
                    CImgList<charT> 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<T> sublist = get_shared_images(s[0],s[1]);
                CImgList<charT> 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<charT> __display() const {
          CImg<charT> res, str;
          cimglist_for(*this,l) {
            CImg<charT>::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<charT>(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<T>& 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<charT> 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,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<T>& save_gif_external(const char *const filename, const float fps=25,
                                             const unsigned int nb_loops=0) {
          CImg<charT> command(1024), filename_tmp(256), filename_tmp2(256);
          CImgList<charT> 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<charT>::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);
          }
    
    #if cimg_OS!=2
          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<ucharT>::string(command).move_to(filenames,0);
          cimg_snprintf(command,command._width,"\"%s\" >/dev/null 2>&1",
                        CImg<charT>::string(filename)._system_strescape().data());
          CImg<ucharT>::string(command).move_to(filenames);
    #else
          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<ucharT>::string(command).move_to(filenames,0);
          cimg_snprintf(command,command._width,"\"%s\"\" >NUL 2>&1",
                        CImg<charT>::string(filename)._system_strescape().data());
          CImg<ucharT>::string(command).move_to(filenames);
    #endif
          CImg<charT> _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 'convert'.",
                                  cimglist_instance,
                                  filename);
          else cimg::fclose(file);
          cimglist_for_in(*this,1,filenames._width - 1,l) std::remove(filenames[l]);
          return *this;
        }
    
        const CImgList<T>& _save_yuv(std::FILE *const file, const char *const filename, const bool is_rgb) const {
          if (!file && !filename)
            throw CImgArgumentException(_cimglist_instance
                                        "save_yuv(): Specified filename is (null).",
                                        cimglist_instance);
          if (is_empty()) { cimg::fempty(file,filename); return *this; }
          if ((*this)[0].width()%2 || (*this)[0].height()%2)
            throw CImgInstanceException(_cimglist_instance
                                        "save_yuv(): Invalid odd instance dimensions (%u,%u) for file '%s'.",
                                        cimglist_instance,
                                        (*this)[0].width(),(*this)[0].height(),
                                        filename?filename:"(FILE*)");