Skip to content

Instantly share code, notes, and snippets.

@mockturtl
Forked from slightfoot/capture_widget.dart
Created May 29, 2025 14:12
Show Gist options
  • Save mockturtl/2e2c1a67632470eb8f83981f7369e56f to your computer and use it in GitHub Desktop.
Save mockturtl/2e2c1a67632470eb8f83981f7369e56f to your computer and use it in GitHub Desktop.

Revisions

  1. mockturtl revised this gist May 29, 2025. 1 changed file with 87 additions and 129 deletions.
    216 changes: 87 additions & 129 deletions capture_widget.dart
    Original file line number Diff line number Diff line change
    @@ -6,159 +6,117 @@ import 'package:flutter/material.dart';
    import 'package:flutter/rendering.dart';

    void main() {
    runApp(
    MaterialApp(
    theme: ThemeData(
    primarySwatch: Colors.indigo,
    accentColor: Colors.pinkAccent,
    ),
    home: ExampleScreen(),
    ),
    );
    runApp(MaterialApp(
    theme: ThemeData(primarySwatch: Colors.indigo), home: ExampleScreen()));
    }

    class ExampleScreen extends StatefulWidget {
    @override
    _ExampleScreenState createState() => new _ExampleScreenState();
    }

    class _ExampleScreenState extends State<ExampleScreen> {
    final _captureKey = GlobalKey<CaptureWidgetState>();
    Future<CaptureResult> _image;

    void _onCapturePressed() {
    setState(() {
    _image = _captureKey.currentState.captureImage();
    });
    }
    class CaptureResult {
    final Uint8List data;
    final int width;
    final int height;

    @override
    Widget build(BuildContext context) {
    return CaptureWidget(
    key: _captureKey,
    capture: Material(
    child: Padding(
    padding: const EdgeInsets.all(8.0),
    child: Column(
    mainAxisSize: MainAxisSize.min,
    children: <Widget>[
    Text(
    'These widgets are not visible on the screen yet can still be captured by a RepaintBoundary.',
    ),
    SizedBox(height: 12.0),
    Container(
    width: 25.0,
    height: 25.0,
    color: Colors.red,
    ),
    ],
    ),
    ),
    ),
    child: Scaffold(
    appBar: AppBar(
    title: Text('Widget To Image Demo'),
    ),
    body: FutureBuilder<CaptureResult>(
    future: _image,
    builder: (BuildContext context, AsyncSnapshot<CaptureResult> snapshot) {
    return SingleChildScrollView(
    child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
    Center(
    child: RaisedButton(
    child: Text('Capture Image'),
    onPressed: _onCapturePressed,
    ),
    ),
    if (snapshot.connectionState == ConnectionState.waiting)
    Center(
    child: CircularProgressIndicator(),
    )
    else if (snapshot.hasData) ...[
    Text(
    '${snapshot.data.width} x ${snapshot.data.height}',
    textAlign: TextAlign.center,
    ),
    Container(
    margin: const EdgeInsets.all(12.0),
    decoration: BoxDecoration(
    border: Border.all(color: Colors.grey.shade300, width: 2.0),
    ),
    child: Image.memory(
    snapshot.data.data,
    scale: MediaQuery.of(context).devicePixelRatio,
    ),
    ),
    ],
    ],
    ),
    );
    },
    ),
    ),
    );
    }
    const CaptureResult(this.data, this.width, this.height);
    }

    class CaptureWidget extends StatefulWidget {
    final Widget child;
    final Widget capture;

    const CaptureWidget({
    Key key,
    this.capture,
    this.child,
    }) : super(key: key);
    super.key,
    required this.capture,
    required this.child,
    });

    @override
    CaptureWidgetState createState() => CaptureWidgetState();
    State<CaptureWidget> createState() => CaptureWidgetState();
    }

    class CaptureWidgetState extends State<CaptureWidget> {
    final _boundaryKey = GlobalKey();

    Future<CaptureResult> captureImage() async {
    final pixelRatio = MediaQuery.of(context).devicePixelRatio;
    final boundary = _boundaryKey.currentContext.findRenderObject() as RenderRepaintBoundary;
    final image = await boundary.toImage(pixelRatio: pixelRatio);
    final data = await image.toByteData(format: ui.ImageByteFormat.png);
    return CaptureResult(data.buffer.asUint8List(), image.width, image.height);
    }

    @override
    Widget build(BuildContext context) {
    return LayoutBuilder(
    builder: (BuildContext context, BoxConstraints constraints) {
    Widget build(BuildContext context) =>
    LayoutBuilder(builder: (context, constraints) {
    final height = constraints.maxHeight * 2;
    return Stack(
    fit: StackFit.passthrough,
    children: <Widget>[
    widget.child,
    Positioned(
    left: 0.0,
    right: 0.0,
    return Stack(fit: StackFit.passthrough, children: <Widget>[
    widget.child,
    Positioned(
    left: 0,
    right: 0,
    top: height,
    height: height,
    child: Center(
    child: RepaintBoundary(
    key: _boundaryKey,
    child: widget.capture,
    ),
    ),
    ),
    ],
    );
    },
    );
    child: RepaintBoundary(
    key: _boundaryKey, child: widget.capture))),
    ]);
    });

    Future<CaptureResult> captureImage() async {
    final pixelRatio = MediaQuery.of(context).devicePixelRatio;
    final boundary = _boundaryKey.currentContext?.findRenderObject()
    as RenderRepaintBoundary?;
    final image = await boundary?.toImage(pixelRatio: pixelRatio);
    final data = await image?.toByteData(format: ui.ImageByteFormat.png);
    return CaptureResult(
    data!.buffer.asUint8List(), image?.width ?? 0, image?.height ?? 0);
    }
    }

    class CaptureResult {
    final Uint8List data;
    final int width;
    final int height;
    class ExampleScreen extends StatefulWidget {
    @override
    State<ExampleScreen> createState() => _ExampleScreenState();
    }

    const CaptureResult(this.data, this.width, this.height);
    class _ExampleScreenState extends State<ExampleScreen> {
    final _captureKey = GlobalKey<CaptureWidgetState>();
    Future<CaptureResult>? _image;

    @override
    Widget build(BuildContext context) => CaptureWidget(
    key: _captureKey,
    capture: Material(
    child: Padding(
    padding: const EdgeInsets.all(8),
    child: Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
    const Text(
    'These widgets are not visible on the screen yet can still be captured by a RepaintBoundary.',
    ),
    const SizedBox(height: 12),
    Container(width: 25, height: 25, color: Colors.red),
    ]))),
    child: Scaffold(
    appBar: AppBar(title: const Text('Widget To Image Demo')),
    body: FutureBuilder<CaptureResult>(
    future: _image,
    builder: (context, snapshot) => SingleChildScrollView(
    child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
    Center(
    child: ElevatedButton(
    onPressed: _onCapturePressed,
    child: const Text('Capture Image'))),
    if (snapshot.connectionState == ConnectionState.waiting)
    const Center(child: CircularProgressIndicator())
    else if (snapshot.hasData) ...[
    Text(
    '${snapshot.data!.width} x ${snapshot.data!.height}',
    textAlign: TextAlign.center),
    for (var i = 0; i < 50; i++)
    Container(
    margin: const EdgeInsets.all(12),
    decoration: BoxDecoration(
    border: Border.all(
    color: Colors.grey.shade300, width: 2)),
    child: Image.memory(snapshot.data!.data,
    scale: MediaQuery.of(context)
    .devicePixelRatio)),
    ],
    ])))));

    void _onCapturePressed() {
    _image = _captureKey.currentState?.captureImage();
    setState(() {});
    }
    }
  2. @slightfoot slightfoot revised this gist Aug 12, 2019. 1 changed file with 15 additions and 17 deletions.
    32 changes: 15 additions & 17 deletions capture_widget.dart
    Original file line number Diff line number Diff line change
    @@ -132,25 +132,23 @@ class CaptureWidgetState extends State<CaptureWidget> {
    return LayoutBuilder(
    builder: (BuildContext context, BoxConstraints constraints) {
    final height = constraints.maxHeight * 2;
    return OverflowBox(
    alignment: Alignment.topLeft,
    minHeight: height,
    maxHeight: height,
    child: Column(
    children: <Widget>[
    Expanded(
    child: widget.child,
    ),
    Expanded(
    child: Center(
    child: RepaintBoundary(
    key: _boundaryKey,
    child: widget.capture,
    ),
    return Stack(
    fit: StackFit.passthrough,
    children: <Widget>[
    widget.child,
    Positioned(
    left: 0.0,
    right: 0.0,
    top: height,
    height: height,
    child: Center(
    child: RepaintBoundary(
    key: _boundaryKey,
    child: widget.capture,
    ),
    ),
    ],
    ),
    ),
    ],
    );
    },
    );
  3. @slightfoot slightfoot created this gist Aug 12, 2019.
    166 changes: 166 additions & 0 deletions capture_widget.dart
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,166 @@
    import 'dart:async';
    import 'dart:typed_data';
    import 'dart:ui' as ui;

    import 'package:flutter/material.dart';
    import 'package:flutter/rendering.dart';

    void main() {
    runApp(
    MaterialApp(
    theme: ThemeData(
    primarySwatch: Colors.indigo,
    accentColor: Colors.pinkAccent,
    ),
    home: ExampleScreen(),
    ),
    );
    }

    class ExampleScreen extends StatefulWidget {
    @override
    _ExampleScreenState createState() => new _ExampleScreenState();
    }

    class _ExampleScreenState extends State<ExampleScreen> {
    final _captureKey = GlobalKey<CaptureWidgetState>();
    Future<CaptureResult> _image;

    void _onCapturePressed() {
    setState(() {
    _image = _captureKey.currentState.captureImage();
    });
    }

    @override
    Widget build(BuildContext context) {
    return CaptureWidget(
    key: _captureKey,
    capture: Material(
    child: Padding(
    padding: const EdgeInsets.all(8.0),
    child: Column(
    mainAxisSize: MainAxisSize.min,
    children: <Widget>[
    Text(
    'These widgets are not visible on the screen yet can still be captured by a RepaintBoundary.',
    ),
    SizedBox(height: 12.0),
    Container(
    width: 25.0,
    height: 25.0,
    color: Colors.red,
    ),
    ],
    ),
    ),
    ),
    child: Scaffold(
    appBar: AppBar(
    title: Text('Widget To Image Demo'),
    ),
    body: FutureBuilder<CaptureResult>(
    future: _image,
    builder: (BuildContext context, AsyncSnapshot<CaptureResult> snapshot) {
    return SingleChildScrollView(
    child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
    Center(
    child: RaisedButton(
    child: Text('Capture Image'),
    onPressed: _onCapturePressed,
    ),
    ),
    if (snapshot.connectionState == ConnectionState.waiting)
    Center(
    child: CircularProgressIndicator(),
    )
    else if (snapshot.hasData) ...[
    Text(
    '${snapshot.data.width} x ${snapshot.data.height}',
    textAlign: TextAlign.center,
    ),
    Container(
    margin: const EdgeInsets.all(12.0),
    decoration: BoxDecoration(
    border: Border.all(color: Colors.grey.shade300, width: 2.0),
    ),
    child: Image.memory(
    snapshot.data.data,
    scale: MediaQuery.of(context).devicePixelRatio,
    ),
    ),
    ],
    ],
    ),
    );
    },
    ),
    ),
    );
    }
    }

    class CaptureWidget extends StatefulWidget {
    final Widget child;
    final Widget capture;

    const CaptureWidget({
    Key key,
    this.capture,
    this.child,
    }) : super(key: key);

    @override
    CaptureWidgetState createState() => CaptureWidgetState();
    }

    class CaptureWidgetState extends State<CaptureWidget> {
    final _boundaryKey = GlobalKey();

    Future<CaptureResult> captureImage() async {
    final pixelRatio = MediaQuery.of(context).devicePixelRatio;
    final boundary = _boundaryKey.currentContext.findRenderObject() as RenderRepaintBoundary;
    final image = await boundary.toImage(pixelRatio: pixelRatio);
    final data = await image.toByteData(format: ui.ImageByteFormat.png);
    return CaptureResult(data.buffer.asUint8List(), image.width, image.height);
    }

    @override
    Widget build(BuildContext context) {
    return LayoutBuilder(
    builder: (BuildContext context, BoxConstraints constraints) {
    final height = constraints.maxHeight * 2;
    return OverflowBox(
    alignment: Alignment.topLeft,
    minHeight: height,
    maxHeight: height,
    child: Column(
    children: <Widget>[
    Expanded(
    child: widget.child,
    ),
    Expanded(
    child: Center(
    child: RepaintBoundary(
    key: _boundaryKey,
    child: widget.capture,
    ),
    ),
    ),
    ],
    ),
    );
    },
    );
    }
    }

    class CaptureResult {
    final Uint8List data;
    final int width;
    final int height;

    const CaptureResult(this.data, this.width, this.height);
    }