Mobile Development 12 min read

PowerImage: Boosting Flutter Image Loading with FFI & Texture Integration

The article introduces PowerImage, a new Flutter image library that combines external texture and FFI to enable direct ui.Image loading, pre‑caching, simulator support, and custom channels, and compares its performance against traditional Texture solutions with detailed memory and scrolling analyses.

Alibaba Terminal Technology
Alibaba Terminal Technology
Alibaba Terminal Technology
PowerImage: Boosting Flutter Image Loading with FFI & Texture Integration

Background

Last year the Xianyu image library performed well at large scale but faced issues such as cache misuse when mixing native images, inability to display images on simulators, and the need for an external image channel for album scenarios.

PowerImage Overview

We combined external texture with an FFI solution to create a more native‑like design, naming the new library “PowerImage”.

Core capabilities

Support loading ui.Image directly.

Provide image pre‑caching similar to precacheImage.

Introduce texture cache that integrates with native image cache, avoiding memory issues.

Enable image display on simulators (flutter‑1.23.0‑18.1.pre).

Custom image‑type channel for scenarios like album.

Improved exception capture.

Support animated images.

Flutter native image pipeline

In the native Flutter approach, an Image widget obtains an ImageStream from an ImageProvider, monitors its state, and finally rebuilds a RawImage using RenderImage. The core data resides in ImageInfo as a ui.Image.

New solution: FFI + external texture

We pass native memory address, length, width, height, etc., to Flutter to generate a ui.Image. On iOS the native side extracts parameters:

_rowBytes = CGImageGetBytesPerRow(cgImage);
CGDataProviderRef dataProvider = CGImageGetDataProvider(cgImage);
CFDataRef rawDataRef = CGDataProviderCopyData(dataProvider);
_handle = (long)CFDataGetBytePtr(rawDataRef);
NSData *data = CFBridgingRelease(rawDataRef);
self.data = data;
_length = data.length;

On the Dart side we create an ImageInfo from the received map:

@override
FutureOr<ImageInfo> createImageInfo(Map map) {
  Completer<ImageInfo> completer = Completer<ImageInfo>();
  int handle = map['handle'];
  int length = map['length'];
  int width = map['width'];
  int height = map['height'];
  int rowBytes = map['rowBytes'];
  ui.PixelFormat pixelFormat =
      ui.PixelFormat.values[map['flutterPixelFormat'] ?? 0];
  Pointer<Uint8> pointer = Pointer<Uint8>.fromAddress(handle);
  Uint8List pixels = pointer.asTypedList(length);
  ui.decodeImageFromPixels(pixels, width, height, pixelFormat,
      (ui.Image image) {
    ImageInfo imageInfo = ImageInfo(image: image);
    completer.complete(imageInfo);
    // release native memory
    PowerImageLoader.instance.releaseImageRequest(options);
  }, rowBytes: rowBytes);
  return completer.future;
}

Because decodeImageFromPixels copies data, memory‑peak can increase; we propose two optimizations: let Flutter decode the data before passing it, or work with the Flutter team to reduce the copy.

Texture vs FFI comparison

We implemented a lightweight TextureImage class to act as a shell for cache size calculation:

import 'dart:typed_data';
import 'dart:ui' as ui show Image;
import 'dart:ui';

class TextureImage implements ui.Image {
  int _width;
  int _height;
  int textureId;
  TextureImage(this.textureId, int width, int height)
      : _width = width,
        _height = height;

  @override
  void dispose() {}

  @override
  int get height => _height;

  @override
  Future<ByteData> toByteData({ImageByteFormat format = ImageByteFormat.rawRgba}) {
    throw UnimplementedError();
  }

  @override
  int get width => _width;
}

Performance tests on iPhone 11 Pro (300 network images in a ListView) showed:

Texture: memory fluctuation ~395 MB, smoother.

FFI: memory fluctuation ~480 MB, with spikes.

Memory analysis indicated that Texture keeps only a placeholder on the Flutter side, avoiding the extra 100 MB native cache, while FFI incurs additional copies.

Overall architecture

We abstracted PowerImageProvider to produce ImageInfo for both external (FFI) and texture sources, delegating loading and releasing to PowerImageLoader. ImageExt is a custom widget exposing imageBuilder for texture, and ImageCacheExt extends ImageCache to provide release callbacks for Flutter <2.2.0.

Extensibility

The library supports custom image types (e.g., album) and custom rendering paths, allowing developers to pass arbitrary parameters to native loaders or perform decoding on the Flutter side to reduce memory copies.

Performance results

Scrolling tests on Android OnePlus 8T showed that the UI thread performs best with the Texture approach, while the Raster thread favors PowerImage over IFImage. Code size on the Dart side decreased significantly due to shared native logic.

Future work

Animated‑image support is in progress: the current pre‑release returns a OneFrameImageStreamCompleter; we plan to replace it with MultiFrameImageStreamCompleter. We also aim to open‑source PowerImage with documentation, integration guides, performance reports, and automated tests.

References

ISSUE: https://github.com/flutter/flutter/issues/86402

PR: https://github.com/flutter/flutter/pull/86555

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

FlutterperformanceFFIImage LoadingPowerImage
Alibaba Terminal Technology
Written by

Alibaba Terminal Technology

Official public account of Alibaba Terminal

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.