Yalantis/uCrop · error · CImgIOException

save_gif_external(): Failed to save file

Error message

save_gif_external(): Failed to save file '%s' with external command 'magick/convert'.

What it means

save_gif_external() assembles an ImageMagick 'magick'/'convert' command line from per-frame temp files and runs it via cimg::system(). A nonzero exit status from that external process throws this CImgIOException — the GIF was not written because ImageMagick itself failed.

Solutions

  1. Install ImageMagick or set the correct binary via CImg_IMAGEMAGICK_PATH env / cimg::imagemagick_path().
  2. Reduce the number of frames or write to a shorter temp directory to shrink the command line.
  3. Verify the exact ImageMagick invocation manually with the same arguments to see the real error.

Example fix

// before
list.save_gif_external("anim.gif"); // fails if ImageMagick absent
// after
if (std::system("command -v convert >/dev/null")!=0)
  throw std::runtime_error("ImageMagick not installed");
list.save_gif_external("anim.gif");
Defensive patterns

Strategy: fallback

Validate before calling

if (std::system("command -v magick >/dev/null 2>&1 && command -v convert >/dev/null 2>&1") != 0) {
  fprintf(stderr, "ImageMagick unavailable\n");
  return false;
}

Try / catch

try { list.save_gif_external(out); }
catch (CImgIOException &e) { /* save frames individually or use a native GIF encoder */ }

Prevention

When it happens

Trigger: cimg::system() returns !=0 when executing the imagemagick command: 'magick'/'convert' binary not installed, bad imagemagick_path(), command-line length limits exceeded, or invalid frame files.

Common situations: CI containers without ImageMagick; IM6 'convert' vs IM7 'magick' naming differences; very long frame lists exceeding shell/command length limits (common on Android execve limits).

Related errors


AI-assisted analysis of Yalantis/uCrop@f788b534b4 (2026-09-08). Data as JSON: /api/errors/7c7c5b088829f500. Report an issue: GitHub.

Appendix: source

Thrown at ucrop/src/main/jni/CImg.h:68054

        else if (frame._spectrum==1) frame.assign(frame.get_resize(-100,-100,1,3),false);
        else if (frame._spectrum==2)
          frame.assign(frame.get_resize(-100,-100,1,4).draw_image(0,0,0,2,frame.get_shared_channel(0)),false);
        frame.save(filename_tmp2);
      }
      cimg_snprintf(command,command._width,"\"%s\" -delay %u -loop %u -dispose previous",
                    cimg::imagemagick_path(),
                    (unsigned int)std::max(0.f,cimg::round(100/fps)),
                    nb_loops);
      CImg<ucharT>::string(command).move_to(filenames,0);
      cimg_snprintf(command,command._width,"\"%s\"",
                    CImg<charT>::string(filename)._system_strescape().data());
      CImg<ucharT>::string(command).move_to(filenames);
      CImg<charT> _command = filenames>'x';
      cimg_for(_command,p,char) if (!*p) *p = ' ';
      _command.back() = 0;

      if (cimg::system(_command,cimg::imagemagick_path())!=0)
        throw CImgIOException(_cimglist_instance
                              "save_gif_external(): Failed to save file '%s' with external command 'magick/convert'.",
                              cimglist_instance,
                              filename);
      if (!cimg::path_exists(filename))
        throw CImgIOException(_cimglist_instance
                              "save_gif_external(): Failed to save file '%s' with external command 'magick/convert'.",
                              cimglist_instance,
                              filename);
      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 <tt>{ 420 | 422 | 444 }</tt>.
      \param is_rgb Tells if the RGB to YUV conversion must be done for saving.
    **/

View on GitHub (pinned to f788b534b4)