flutterimageextractframeanimated-gif

In Flutter, how to extract frame by frame from an animated gif?


I'm trying to extract frames from an animated gif using Flutter. By researching the Flutter API, I thought the following code should work but it only gives me the first frame although it gives me the correct frames count.

static Future<List<ui.Image>> loadAnimatedGif(String assetPath) async {
    List<ui.Image> gifImage = [];
    ByteData data = await rootBundle.load(assetPath);
    var codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
    int frmCnt = codec.frameCount;
    print("frmcnt is $frmCnt"); //it gives correct frames count
    for (int i = 0; i < frmCnt; i++) {
      var frame = await codec.getNextFrame();
      gifImage.add(frame.Image);
     
    }
   
    return gifImage;
  }

Solution

  • I made a simple app out of this and it works using the same code but added a converter in it.

    What I notice at first because nothing

    1. Using the ui.Image loads the frames but it delays for about 10secs before showing all of them using a breakpoint to debug it.
    2. Converting ui.Image to Uint8List takes time as much as the frames but the format shows all frames as soon the extraction is done.

    Here is the dart pad to quickly run and test it.

    Here is the code if you will like to scan through it, I am loading the gif from Network.

    class GifFramesDisplay extends StatefulWidget {
      const GifFramesDisplay({super.key});
    
      @override
      State<GifFramesDisplay> createState() => _GifFramesDisplayState();
    }
    
    class _GifFramesDisplayState extends State<GifFramesDisplay> {
      List<Uint8List> _frames = [];
    
      @override
      void initState() {
        super.initState();
    
        loadGif();
      }
    
      Future<void> loadGif() async {
        // Load the gif image from the network url and store the bytes in a ByteData object
        final url = Uri.parse(
            'https://raw.githubusercontent.com/Aman-Malhotra/animate_icons/main/demo/animate_icons.gif');
        final ByteData data = await NetworkAssetBundle(url).load(url.path);
    
        // Using the _extractGifFrames function to extract the frames
        _extractGifFrames(data).then((List<Uint8List> frames) {
          // Set the state to update the UI
          setState(() {
            _frames = frames;
          });
        });
      }
    
      // Function to extract gif image frames
      Future<List<Uint8List>> _extractGifFrames(ByteData data) async {
        // Create a list to store the frames
        final List<Uint8List> frames = <Uint8List>[];
    
        // Create a codec to decode the gif
        final ui.Codec codec =
            await ui.instantiateImageCodec(data.buffer.asUint8List());
    
        // Count the number of frames in the gif
        final int frameCount = codec.frameCount;
        print('Total frameCount: $frameCount');
    
        // Loop through the frames and add them to the list
        for (int i = 0; i < frameCount; i++) {
          // Get the next frame
          final ui.FrameInfo fi = await codec.getNextFrame();
    
          // Add the frame to the list
          final frame = await loadImage(fi.image);
    
          // Add the frame to the list if it is not null
          if (frame != null) {
            frames.add(frame);
          }
        }
    
        return frames;
      }
    
      Future<Uint8List?> loadImage(ui.Image image) async {
        final ByteData? byteData =
            await image.toByteData(format: ui.ImageByteFormat.png);
        return byteData?.buffer.asUint8List();
      }
    
      @override
      Widget build(BuildContext context) {
        return Center(
          child: _frames.isEmpty
              ? const CircularProgressIndicator()
              : ListView.builder(
                  itemCount: _frames.length,
                  itemBuilder: (BuildContext context, int index) {
                    return Image.memory(_frames[index]);
                  },
                ),
        );
      }
    }
    

    Click to watch a preview of the loaded gif frames of the code above