Skip to content

Add new option parse_animation to parse metadata for animated images #676

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ fluttergen -c example/pubspec.yaml
## Configuration file

[FlutterGen] generates dart files based on the key **`flutter`** and **`flutter_gen`** of [`pubspec.yaml`](https://dart.dev/tools/pub/pubspec).
Default configuration can be found [here](https://github.com/FlutterGen/flutter_gen/tree/main/packages/core/lib/settings/config_default.dart).
Default configuration can be found [here](https://github.com/FlutterGen/flutter_gen/tree/main/packages/core/lib/settings/config_default.dart).

```yaml
# pubspec.yaml
Expand Down Expand Up @@ -173,7 +173,6 @@ flutter:

You can also configure generate options in the `build.yaml`, it will be read before the `pubspec.yaml` if it exists.


```yaml
# build.yaml
# ...
Expand Down Expand Up @@ -276,7 +275,9 @@ Widget build(BuildContext context) {
);
}
```

or

```dart
// Explicit usage for `Image`/`SvgPicture`/`Lottie`.
Widget build(BuildContext context) {
Expand Down Expand Up @@ -340,6 +341,28 @@ Widget build(BuildContext context) {
}
```

When we need to obtain more animation details, we can use the `parse_animation` option. This will automatically parse all animation information for GIFs and WebP files, including frames, duration, etc. As this option significantly increases generation time, it's disabled by default and needs to be manually enabled.

```yaml
flutter_gen:
parse_animation: true # <- Add this line (default: false)
# This option implies parse_metadata: true
```

For GIF and WebP animation, several new nullable field is added to the
generated class. For example:

```dart
AssetGenImage get animated =>
const AssetGenImage(
'assets/images/animated.webp',
size: Size(209.0, 49.0),
isAnimation: true,
duration: Duration(milliseconds: 1000),
frames: 15,
);
```

#### Usage Example

[FlutterGen] generates [Image](https://api.flutter.dev/flutter/widgets/Image-class.html) class if the asset is Flutter supported image format.
Expand Down Expand Up @@ -575,7 +598,9 @@ Plugin issues that are not specific to [FlutterGen] can be filed in the [Flutter
### Known Issues

#### Bad State: No Element when using build_runner

If you get an error message like this:

```
[SEVERE] flutter_gen_runner:flutter_gen_runner on $package$:

Expand Down Expand Up @@ -614,7 +639,7 @@ output-localization-file: app_localizations.dart
synthetic-package: false <--- ⚠️Add this line⚠️
```

If you get
If you get

## Contributing

Expand Down
5 changes: 3 additions & 2 deletions packages/command/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: flutter_gen
description: The Flutter code generator for your assets, fonts, colors, … — Get rid of all String-based APIs.
version: 5.10.0
version: 5.11.0
homepage: https://github.com/FlutterGen/flutter_gen
repository: https://github.com/FlutterGen/flutter_gen
documentation: https://github.com/FlutterGen/flutter_gen
Expand All @@ -13,7 +13,8 @@ executables:
fluttergen: flutter_gen_command

dependencies:
flutter_gen_core: 5.10.0
flutter_gen_core: 5.11.0

args: ^2.0.0

dev_dependencies:
Expand Down
1 change: 1 addition & 0 deletions packages/core/lib/generators/assets_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Future<String> generateAssets(
ImageIntegration(
config.packageParameterLiteral,
parseMetadata: config.flutterGen.parseMetadata,
parseAnimation: config.flutterGen.parseAnimation,
),
if (config.flutterGen.integrations.flutterSvg)
SvgIntegration(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:io';

import 'package:flutter_gen_core/generators/integrations/integration.dart';
import 'package:image/image.dart' as img;
import 'package:image_size_getter/file_input.dart';
import 'package:image_size_getter/image_size_getter.dart';

Expand All @@ -11,9 +12,12 @@ import 'package:image_size_getter/image_size_getter.dart';
class ImageIntegration extends Integration {
ImageIntegration(
String packageName, {
required this.parseAnimation,
super.parseMetadata,
}) : super(packageName);

final bool parseAnimation;

String get packageParameter => isPackage ? ' = package' : '';

String get keyName =>
Expand All @@ -32,6 +36,9 @@ class ImageIntegration extends Integration {
this._assetName, {
this.size,
this.flavors = const {},
this.isAnimation = false,
this.duration = Duration.zero,
this.frames = 1,
});

final String _assetName;
Expand All @@ -40,6 +47,9 @@ ${isPackage ? "\n static const String package = '$packageName';" : ''}

final Size? size;
final Set<String> flavors;
final bool isAnimation;
final Duration duration;
final int frames;

Image image({
Key? key,
Expand Down Expand Up @@ -116,12 +126,20 @@ ${isPackage ? "\n static const String package = '$packageName';" : ''}

@override
String classInstantiate(AssetType asset) {
final info = parseMetadata ? _getMetadata(asset) : null;
final info = parseMetadata || parseAnimation ? _getMetadata(asset) : null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the user puts parse_metadata: false and parse_animation: true at the same time? We used to imply that parse_metadata is false, but it seems a bit overwhelming, and we could throw an exception here rather than keep implying these behaviors.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parse_animation: true implies parse_metadata: false. Parsing animation requires image meta data. We can choose to drop meta data in this case. However, as the meta data has been parsed, I feel it's ok to write the parsed meta data in generated files.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we go further and let parse_metadata: true include generating animations without using this extra option?

final buffer = StringBuffer(className);
buffer.write('(');
buffer.write('\'${asset.posixStylePath}\'');
if (info != null) {
buffer.write(', size: Size(${info.width}, ${info.height})');
buffer.write(', size: const Size(${info.width}, ${info.height})');

if (info.animation case final animation?) {
buffer.write(', isAnimation: ${animation.frames > 1}');
buffer.write(
', duration: Duration(milliseconds: ${animation.duration.inMilliseconds})',
);
buffer.write(', frames: ${animation.frames}');
}
}
if (asset.flavors.isNotEmpty) {
buffer.write(', flavors: {');
Expand Down Expand Up @@ -161,11 +179,47 @@ ${isPackage ? "\n static const String package = '$packageName';" : ''}
FileInput(File(asset.fullPath)),
);
final size = result.size;
return ImageMetadata(size.width.toDouble(), size.height.toDouble());
final animation = parseAnimation ? _parseAnimation(asset) : null;

return ImageMetadata(
width: size.width.toDouble(),
height: size.height.toDouble(),
animation: animation,
);
} catch (e) {
stderr
.writeln('[WARNING] Failed to parse \'${asset.path}\' metadata: $e');
}
return null;
}

ImageAnimation? _parseAnimation(AssetType asset) {
final decoder = switch (asset.mime) {
'image/gif' => img.GifDecoder(),
'image/webp' => img.WebPDecoder(),
_ => null,
};

if (decoder == null) {
return null;
}

final file = File(asset.fullPath);
final bytes = file.readAsBytesSync();
final image = decoder.decode(bytes);

if (image == null) {
return null;
}

return ImageAnimation(
frames: image.frames.length,
duration: Duration(
milliseconds: image.frames.fold(
0,
(duration, frame) => duration + frame.frameDuration,
),
),
);
}
}
18 changes: 17 additions & 1 deletion packages/core/lib/generators/integrations/integration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,24 @@ const String deprecationMessagePackage =
/// Currently only contains the width and height, but could contain more in
/// future.
class ImageMetadata {
const ImageMetadata(this.width, this.height);
const ImageMetadata({
required this.width,
required this.height,
this.animation,
});

final double width;
final double height;
final ImageAnimation? animation;
}

/// Metadata about the parsed animation file when [parseAnimation] is true.
class ImageAnimation {
const ImageAnimation({
required this.frames,
required this.duration,
});

final int frames;
final Duration duration;
}
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,10 @@ ${isPackage ? "\n static const String package = '$packageName';" : ''}
// but it's also the same way it will be eventually rendered by Flutter.
final svg = File(asset.fullPath).readAsStringSync();
final vec = parseWithoutOptimizers(svg);
return ImageMetadata(vec.width, vec.height);
return ImageMetadata(
width: vec.width,
height: vec.height,
);
} catch (e) {
stderr.writeln(
'[WARNING] Failed to parse SVG \'${asset.path}\' metadata: $e',
Expand Down
2 changes: 2 additions & 0 deletions packages/core/lib/settings/config_default.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ flutter_gen:
line_length: 80
# Optional
parse_metadata: false
# Optional
parse_animation: false

# Optional
integrations:
Expand Down
4 changes: 4 additions & 0 deletions packages/core/lib/settings/pubspec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ class FlutterGen {
required this.output,
required this.lineLength,
required this.parseMetadata,
required this.parseAnimation,
required this.assets,
required this.fonts,
required this.integrations,
Expand All @@ -120,6 +121,9 @@ class FlutterGen {
@JsonKey(name: 'parse_metadata', required: true)
final bool parseMetadata;

@JsonKey(name: 'parse_animation', required: true)
final bool parseAnimation;

@JsonKey(name: 'assets', required: true)
final FlutterGenAssets assets;

Expand Down
5 changes: 4 additions & 1 deletion packages/core/lib/settings/pubspec.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/core/lib/version.gen.dart
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/// DO NOT MODIFY BY HAND, Generated by version_gen
String packageVersion = '5.10.0';
String packageVersion = '5.11.0';
5 changes: 3 additions & 2 deletions packages/core/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: flutter_gen_core
description: The Flutter code generator for your assets, fonts, colors, … — Get rid of all String-based APIs.
version: 5.10.0
version: 5.11.0
homepage: https://github.com/FlutterGen/flutter_gen
repository: https://github.com/FlutterGen/flutter_gen
documentation: https://github.com/FlutterGen/flutter_gen
Expand All @@ -16,7 +16,7 @@ dependencies:
meta: ^1.7.0
path: ^1.8.0
yaml: ^3.0.0
mime: '>=1.0.0 <3.0.0'
mime: ">=1.0.0 <3.0.0"
xml: ^6.0.0
dartx: ^1.0.0
color: ^3.0.0
Expand All @@ -30,6 +30,7 @@ dependencies:
pub_semver: ^2.0.0
vector_graphics_compiler: ^1.1.9
image_size_getter: ^2.4.0
image: ^4.5.4

dev_dependencies:
lints: any # Ignoring the version to allow editing across SDK versions.
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading