widget_snap 1.2.0 copy "widget_snap: ^1.2.0" to clipboard
widget_snap: ^1.2.0 copied to clipboard

Export any widget to PNG — rendered offscreen, even content larger than the screen. Pure Flutter render pipeline, zero dependencies, all platforms.

widget_snap #

pub package pub points likes CI style: very good analysis license: MIT

widget_snap — turn any widget into a PNG, rendered offscreen

Export any Flutter widget to PNG. The widget is rendered offscreen — it never has to be mounted or visible, so content taller or wider than the screen works too.

Zero dependencies. It drives Flutter's own render pipeline (BuildOwner + PipelineOwner + RenderView) — no screenshot or other third-party capture package, no platform code. Only flutter itself, which is why it runs on Android, iOS, web, macOS, Windows, and Linux.

Features #

  • Capture any widget — mounted or not, larger than the screen or not.
  • Size-driven layout: pin the width or the height (or both); the other axis grows to fit the content.
  • Inherits your app's theme, MediaQuery, and text direction via context.
  • Fails loud on build errors instead of silently exporting a blank image.
  • Two-layer API: pure bytes (core) or a temp-file path (convenience).
  • No permissions required. Bytes stay in memory; toPngFile writes only to the app-private temp dir. Saving to the gallery or sharing is your app's step (and its permission prompt), not this package's.

Larger than the screen? One call. #

A two-week activity report, far taller than a phone screen, captured in a single toPngBytes call

This report never fits a phone screen — it was captured in a single toPngBytes call. In fact, every image in this README was exported by widget_snap itself: see tool/readme_images.dart.

Platform support #

Android iOS Web macOS Windows Linux
✅*

* On the web, use toPngBytestoPngFile throws UnsupportedError because browsers have no writable filesystem. See Web.

Install #

flutter pub add widget_snap

Usage #

The API is a pair of extension methods on Widget:

import 'package:widget_snap/widget_snap.dart';

// Core: capture to bytes — you own the IO.
final bytes = await myWidget.toPngBytes(
  context,        // carries inherited theme/media/direction into the offscreen tree
  width: 1080,    // optional; default = current view width. Height grows to fit.
  // height: 720, // or pin the height instead, and let the width grow (wide content)
);
// upload, preview in-memory (Image.memory), custom storage ...

// Or the convenience wrapper when the next step needs a path:
final path = await myWidget.toPngFile(
  context,
  filename: 'export.png',   // written under the system temp dir
);
await SharePlus.instance.share(ShareParams(files: [XFile(path)]));
// or Gal.putImage(path), ...

Prefer a named entry point? The WidgetSnap facade mirrors both — type WidgetSnap. and autocomplete shows the whole API:

final bytes = await WidgetSnap.pngBytes(myWidget, context);
final path  = await WidgetSnap.pngFile(myWidget, context, filename: 'export.png');

Parameters #

Param Required Description
context Source of inherited theme, MediaQuery, and text direction.
width Target width in logical pixels. Default = current view width (when height is also unset); the unpinned axis grows to fit the content.
height Target height in logical pixels. Pin this for naturally-wide content (timelines, charts) and let the width grow.
filename file variant only Bare file name, no path separators (written under the system temp dir).
pixelRatio Raster scale, default 2.5. Clamped (≥ 1.0) so no output axis exceeds ~4096px.
delay Wait before capture so async images (network/asset) resolve; default zero. Prefer precacheImage (see Notes).
backgroundColor Fill behind the content, default opaque white. Pass Colors.transparent for a PNG with an alpha channel, or any color to tint the canvas.

toPngBytes returns the PNG Uint8List; toPngFile returns the written file's path.

Web #

toPngBytes works as-is. There is no filesystem to write to, so instead of toPngFile, hand the bytes to whatever download/share mechanism your app uses — e.g. share_plus:

final bytes = await myWidget.toPngBytes(context);
await SharePlus.instance.share(ShareParams(
  files: [XFile.fromData(bytes, mimeType: 'image/png', name: 'export.png')],
));

Design boundary #

This package knows nothing about your app — no models, no i18n, no state management, no share/download logic. It captures a widget to PNG and hands the result back. Your app owns what happens next (share sheet, save to gallery, upload, …).

Notes & limits #

  • No MaterialApp needed. The offscreen tree wraps your widget in a white Material + Directionality + MediaQuery, so Ink, InkWell, and Text render as in-app.
  • pixelRatio is clamped to the GPU texture cap. The raster scale is reduced (never below 1.0) so no output axis exceeds ~4096px — the texture cap on low-end devices. The pinned axis is clamped up front, the growing axis after layout once the content's size is known. Content larger than 4096 logical pixels still rides over the cap; if you hit clipping or OOM there, capture a smaller width/height.
  • A fresh tree is captured, not your live one. The widget is rebuilt offscreen, so runtime state of a live counterpart — a checked checkbox, typed text, a scroll offset — does not carry over. Build the export copy from your app's data.
  • No Overlay in the offscreen tree. Widgets that require an Overlay / Navigator ancestor (Tooltip, dropdowns, anything that pops routes) throw during the offscreen build. The export fails loudly with that error instead of producing a blank image. Strip such widgets from the export copy of your content.
  • Layout errors fail loud too. Content that fails layout — e.g. a ListView growing along the unpinned axis ("unbounded height") — rethrows the original framework error instead of exporting garbage. Give scrollables a bounded main axis: pin that axis, or set shrinkWrap: true.
  • Don't reuse live GlobalKeys. The offscreen tree mounts on the framework's BuildOwner (so Flutter-internal GlobalKeys — Ink, form fields — resolve correctly) and is unmounted right after capture. One consequence: don't pass content holding a GlobalKey that is simultaneously mounted in the live app tree (duplicate-key error in debug) — build a fresh copy of the widget for export instead.
  • Very large captures throw. When rasterizing or encoding the capture fails, toPngBytes throws a StateError naming the pixel size and the fix (capture smaller, or lower pixelRatio) — never a silent blank image. Measured ceiling on the web (CanvasKit): total area of roughly 180 million pixels (13312×13312 works, 14336×14336 does not); a skinny 400×131072 strip is fine. Native platforms handle far more. Probe your own setup with test/stress_probe.dart.
  • Async images: precacheImage first, or pass delay. NetworkImage / asset decodes paint blank on the first frame. The deterministic fix is await precacheImage(provider, context) for each image before capturing — the offscreen tree then reads them straight from the image cache. delay remains as a time-based fallback.
  • Files are temporary. toPngFile writes under the system temp dir, which the OS may clean at any time (iOS routinely does). Move the file if you need it to persist. filename must be a non-empty bare name — an empty name or path separators throw an ArgumentError.
  • Flutter-version sensitive. Uses the internal render-pipeline API (ViewConfiguration.logicalConstraints, RenderView(view:)). Verified on Flutter 3.35 and 3.41 (tests run on the VM and in Chrome). If a Flutter upgrade breaks capture, this is the first place to check.

Roadmap #

  • JPEG output (quality knob) for photo-heavy content.
  • Tiled capture for documents that exceed the GPU texture cap along the growing axis, or the web renderer's ~180M-pixel memory ceiling.
  • PDF export (pagination, headers/footers, bookmarks) as a separate layer.
0
likes
160
points
13
downloads
screenshot

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Export any widget to PNG — rendered offscreen, even content larger than the screen. Pure Flutter render pipeline, zero dependencies, all platforms.

Repository (GitHub)
View/report issues

Topics

#screenshot #widget-to-image #png #export #capture

License

MIT (license)

Dependencies

flutter

More

Packages that depend on widget_snap