\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<char> 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<char> 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<char> pattern, _path = CImg<char>::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<char>::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<char>::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<char> 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<char>(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<char>::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<char> 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<char>(filename,lf + 1).move_to(res);
            }
          }
        }
      }
      closedir(dir);
#endif

      // Sort resulting list by lexicographic order.