render 0.0.4 copy "render: ^0.0.4" to clipboard
render: ^0.0.4 copied to clipboard

A package to render any native static and moving flutter widgets to exportable formats

pub.dev Sponsoring likes popularity pub points GitHub issues GitHub license GitHub stars GitHub stars


Render #

A flutter widget to render and convert widgets into a wide range of exportable file formats. Main features include:

  • Render static widgets to export formats (eg. png, jpeg, ...)
  • Render moving widgets to export formats (eg. gif, mp4, ...)
  • Record rendering moving widgets to export formats
  • Rendering widgets that are not in your widget tree (not displayed/build)

All features support (if supported by format): Transparency & sound


Table of Contents

🚀 Getting started #

Installing #

Depend on it

With Flutter:

flutter pub add render

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  render: ^x.x.x

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:render/render.dart';

Quick start #

Render provides you with a wide range of methods to capture widgets. All widgets to be captures must be wrapped in the Render widget, with a provided controller to initiate rendering.

import 'package:render/render.dart';

final controller = RenderController();

Render(
    controller: controller,
    child: Container(),
),

final result = await controller.captureMotion(Duration(seconds: 4));
await controller.captureImage(format: ImageFormat.png, settings:  ImageSettings(pixelRatio: 3),);

Tip: full interactive example for usage in ./example folder.

Usage #

In the following the 3 different operations for rendering is mentioned. Multiple simultaneous operations on one Render instance are allowed, but only one RenderController per Render instance is expected.

Image rendering

There are 4 methods you can call to capture an image:

  • captureImage() to render the child of Render widget, which is in your widget tree.
  • captureImageFromWidget(Widget widget) to render a invisible provided widget.
  • captureImageWithStream(Widget widget) to render the child of Render widget, which is in your widget tree with a notification stream.
  • captureImageFromWidgetWithStream(Widget widget) to render a invisible provided widget with a notification stream.
final imageResult = await renderController.captureImage(
     format: ImageFormat.png,
     settings: const ImageSettings(pixelRatio: 3),
);

Image.file(imageResult.output); // show result as image

Look up Handle Streams to get to know how to render images with a notification streams.

Motion rendering

There are 4 methods you can call to capture motion of a widget:

  • captureMotion() to render the child of Render widget, which is in your widget tree.
  • captureMotionFromWidget(Widget widget) to render a invisible provided widget.
  • captureMotionWithStream(Widget widget) to render the child of Render widget, which is in your widget tree with a notification stream.
  • captureMotionFromWidgetWithStream(Widget widget) to render a invisible provided widget with a notification stream.
final result = await renderController.captureMotionWithStream(
     functionController.duration,
     settings: const MotionSettings(pixelRatio: 4),
     format: Format.gif,
);

final controller = VideoPlayerController.file(result.output);
await controller.initialize();
await controller.play();

VideoPlayer(snapshot.data!); // show result as video

Audio:

Currently there is no way to record the internal audio of a flutter app or specific widgets, therefore the only feasible way for now is to pass audio as a file. To do this you can pass multiple audio files (from eg. video, url, music, etc) to the target format:

controller.captureMotion(
    ...
    format: MovFormat(audio: [
        RenderAudio.url(url),
        RenderAudio.file(file),
    ]),
);

Depending on the rendering settings, motion rendering can take quite long, so it is highly recommended to use methods with stream return, to notify the user about the progress of rendering. Look up Handle Streams to get to know how to render motion with a notification streams.

Recording motion

There are 2 methods you can call to record motion of a widget. Both functions returns a MotionRecorder to stop()and access the stream of the activity.

  • recordMotion() to record the child of Render widget, which is in your widget tree.
  • recordMotionFromWidget(Widget widget) to record a invisible provided widget.
final recorder = renderController.recordMotion(
       functionController.duration,
       settings: const MotionSettings(pixelRatio: 5),
       format: const GifFormat(),
);

await Future.delayed(Duration(seconds: 5));

final result = await recorder.stop(); // result can then be displayed (see Motion rendering)

Depending on the rendering settings, motion rendering can take quite long, so it is highly recommended to use methods with stream return, to notify the user about the progress of rendering. Look up Handle Streams to get to know how to listen to the recording stream, to notify the user about the process of rendering/capturing.

Out of context

...fromWidget() method's replace the need for the [Render] widget as a parent widget. Simply pass the widget that needs to be rendered in the function.

Note that rendering out of context will still build and render each frame of the widget. It will not reduce processing time in any way.

final imageResult = await renderController.captureImageFromWidget(
    Container(), // The widget to be rendered
    format: ImageFormat.png,
    settings: const ImageSettings(pixelRatio: 3),
);

Image.file(imageResult.output); // show result as image

Known Confusions:

  • Sound during capturing: When capturing motions with sound out of context, it will still play the sound in the application. To conquer this, please remove the playing sound of your video widget and pass the sound to the audio input of the format.

Handling stream & information flow

Using information stream is highly recommended for rendering motion, due to longer loading phases. The following example shows how to handle streams of a rendering process:

final stream = renderController.captureMotionWithStream( // start capturing with stream
    functionController.duration,
    settings: const MotionSettings(pixelRatio: 10),
    format: const GifFormat(),
);

stream.listen((event) { // listens to stream until it closes by itself (when result is present)
    // other events might be result, log or error
    if (event.isActivity){
        final activity = event as RenderActivity;
        // Here could be a setState() call to update your process status
        print("Process: ${activity.progressPercentage}");
    }
});

// result can then be displayed (see Motion rendering)
final result = await stream.firstWhere((element) => element.isResult);

🔩 Compatibility #

Limitations and Performance #

Render supports transparency across with all compatible file types (video & image). Note, that the default flutter Video_player does not support displaying transparent videos and will only show a black or white background.

The maximum frame rate of rendering is limited to the maximum frame rate of the current flutter application. Very high quality rendering (>60fps, >10xlogical pixels) might reduce application frame rate and consequently the fluency of rendering, resulting in frame jumps in the output file.

Using settings to keep up performance

You can take advantage of simultaneousCaptureHandlers, pixelRatio and frameRate in the RenderSettings class.

Handlers process and write frames from the RAM to a local directory. Each frame size is determined by the size of pixelRatio, and the frameRateis settings how many handler operations are needed per second. Having multiple handlers at the same time heavily influences the performance of the application during rendering.

The more handlers are running simultaneously the worse gets the framerate and might result in a " laggy" behavior. Less simultaneously handlers result in longer loading phases, as simultaneous handling can not be done during capturing anymore.

Note, that if there a lot of unhandled frames it might still result in laggy behavior, as the application's RAM gets filled with UI images, instead of many handler operations.

To get a good sweet spot you can follow the following introduction for your specific situation:

  • Low pixelRatio - high frameRate - many handlers
  • high pixelRatio - low frameRate - many handlers
  • high pixelRatio - high frameRate - few handlers

Supported Platforms #

Android iOS Web macOS Windows Linux
Support SDK 16+ 9.0+ Any 10.11+ Windows 10+ any
Motion ✔️ ✔️ ❌️️ ✔️ Untested Untested
Image ✔️ ✔️ ❌️️️ ✔️ Untested Untested

There currently no support for web, as file writing is an issue. Windows version might require a simple rewrite of processing structure, but i do not have access a device to debug.

Exportable Formats #

Below are the currently supported and planned formats, that are also mostly supported by the default flutter Video_player and Image visualizer. Note that the default video player does not support transparency.

Motion Formats .mp4 .mov .gif .webp .apng .mpeg .mkv .hls .dash .raw .qtff
Support ✔️ ✔️ ✔️ [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ]
Image Formats .jpg .png .bmp .tiff .tga .pvr .ico .psd .exr
Support ✔️ ✔️ ✔️ ✔️ [ ] [ ] [ ] [ ] [ ]

Custom Formats

In case you want to export your rendering as a custom format, that is currently not supported by this plugin, but support by FFmpeg conversion, you can follow the instructions below to extend the format class:

class YourFormat extends MotionFormat { // you can either extend MotionFormat or ImageFormat
  final dynamic formatSpecificSetting;

  /// Your format description
  YourFormat({
    required this.formatSpecificSetting,
    required super.handling,
    required super.interpolation,
    required super.scale,
  }) : super(
          processShare: 0.3,
          audio: null, // if your format does not have audio support
        );

  @override
  MotionFormat copyWith({RenderScale? scale, Interpolation? interpolation}) {
    throw UnimplementedError();
  }

  @override
  String get extension => "YourExtension";

  @override
  FFmpegRenderOperation processor(
      {required String inputPath,
      required String outputPath,
      required double frameRate}) {
    //return an FFmpegRenderOperation that takes care of all settings 
    return super.processor(
      inputPath: inputPath,
      outputPath: outputPath,
      frameRate: frameRate,
    );
  }
}

After creating your format you can simply use it as format in Render methods:

controller.captureImage(format: YourFormat());

If you think your format can be of use for other, please submit it as a new issue or pull request and we can merge it for public use.

📁 Class documentation #

In the following example you can see the documentation for main connections between classes and usages every parameter is also documented in the code itself. Note that there might be parameters which are not shown in the following class diagram.

%% DIAGRAM CAN ONLY BE SHOWN IN DESKTOP GITHUB (https://github.com/polarby/render#%EF%B8%8F-class-documentation)

classDiagram
    YourWidget <|-- Render
    Render <|-- RenderController
    YourWidget <|-- RenderController : .fromWidget()
    RenderController <|-- MotionSettings
    RenderController <|-- ImageSettings
    note for RenderController "Additinonal Parameters:\n-RenderFormat format\n-LogLevel logLevel\nMotion:\n-Duration duration"


    
    class Render{
        -Widget child
        -RenderController controller
    }
    class RenderController{
        -captureImage(ImageSettings settings)
        -captureImageWithStream(ImageSettings settings)
        -captureImageFromWidget(Widget widget, ImageSettings settings)
        -captureImageFromWidgetWithStream(Widget widget, ImageSettings settings)
        -captureMotion(MotionSettings settings)
        -captureMotionWithStream(MotionSettings settings)
        -captureMotionFromWidget(Widget widget, MotionSettings settings)
        -captureMotionFromWidgetWithStream(Widget widget, MotionSettings settings)
        -recordMotion(MotionSettings settings)
        -recordMotionFromWidget(Widget widget, MotionSettings settings)
    }
    class MotionSettings{
        -pixelRatio
        -processTimeout
        -frameRate
        -simultaneousCaptureHandlers
    }
    class ImageSettings{
        -pixelRatio
        -processTimeout
    }

🔧 Under the hood #

%% DIAGRAM CAN ONLY BE SHOWN IN DESKTOP GITHUB (https://github.com/polarby/render#%EF%B8%8F-render-under-the-hood)

stateDiagram
    [*] --> Capturing
    Capturing --> Handling 
    Handling --> Capturing 
    Handling --> Processing
    Processing --> [*]

Render contains native flutter methods to capture frames and a light FFmpeg wrapper for format conversion.

It relies on RepaintBoundary to capture flutter widgets frame by frame. Each frame is needs to be build-out (not necessary in a visible widget tree) to be able to get captured.

During capturing, handlers are asynchronously initiated to do conversion from a captured raw image and then write the file of eachframe in png format to a temporary directory.

In the processing step, each frame is read from the temporary directory, to then be processed by Ffmpeg (a tool for video, audio and image processing), which then process each frame to the wanted output type. Scaling of frames, sound adaption is also handled here.

📑 Licence & Patents #

Render alone is licenced under MIT Licence, because Open-Source rocks, and everything else just sucks for everyone!

Disclaimer: I am not a lawyer. If you are concerned enough, seek professional legal advice.

Unfortunately this package also takes advantage of FFmpeg plugin, which is believed to have patented algorithms. It is not clearly explained in their documentation, but it is believed that FFmpeg and x264 (both used in this package) include algorithms which are subject to software patents. For more info check the FFmpeg-Kit patent disclaimer.

Also note that, even if someone else does patent it, they may not assert their right to prevent you using the invention. They would only do so if your use of the invention materially impacts their sales or otherwise made them more money than taking legal action against you. Source

As mentioned above, seek professional advice if it concerns you.

Please refer to Pub.dev to see the used library's and possibly different sub-licences.

🚨 Known Issues #

  • Platform views cannot be rendered by flutter itself (Platform views examples: Google Maps, Camera, etc.). There is an active issue to find an alternative for Render plugin.

📢 Additional information & Contribution #

Contributions are very welcome and can be merged within hours if testing is successful. Please note that this is an open source project and is not maintained by a company, but only volunteers.

85
likes
0
points
140
downloads

Publisher

unverified uploader

Weekly Downloads

A package to render any native static and moving flutter widgets to exportable formats

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

ffmpeg_kit_flutter_https_gpl, flutter, path_provider, provider, rich_console, uuid

More

Packages that depend on render