Skip to content

Commit c5ab57a

Browse files
authored
[flutter_svg] feat: Expose the renderStrategy property in SvgPicture (#9373)
This PR exposes the `renderStrategy` property from the `vector_graphics` package to address the performance issue discussed in [#166184](flutter/flutter#166184). By default, SVG widgets are rendered using the picture strategy, which records a list of drawing commands and replays them each frame. While this offers greater flexibility (e.g., for scaling), it can result in significant raster time when many SVGs are present in the widget tree. As demonstrated in [#166184](flutter/flutter#166184), using the default strategy can lead to frame drops due to high raster time: <img width="820" alt="image" src="https://github.com/user-attachments/assets/dfc58d50-c8b2-477e-9a75-fe0eb742f9c7" /> Switching to the raster strategy renders the SVGs into images ahead of time, reducing the raster workload: <img width="894" alt="image" src="https://github.com/user-attachments/assets/2fd8ddbf-3693-4797-be99-8b0919ef1703" /> Exposing this property allows developers to choose between flexibility and performance based on their app’s needs. Fixes flutter/flutter#166184 ## Pre-Review Checklist
1 parent 97fe921 commit c5ab57a

File tree

5 files changed

+135
-6
lines changed

5 files changed

+135
-6
lines changed

third_party/packages/flutter_svg/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
## NEXT
1+
## 2.2.0
22

3+
* Exposes `renderingStrategy` in `SvgPicture` constructors.
34
* Updates minimum supported SDK version to Flutter 3.27/Dart 3.6.
45

56
## 2.1.0

third_party/packages/flutter_svg/README.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,15 @@ import 'dart:ui' as ui;
130130
```
131131

132132
The `SvgPicture` helps to automate this logic, and it provides some convenience
133-
wrappers for getting assets from multiple sources. Unlike the `vector_graphics`
134-
package, this package _does not render the data to an `Image` at any point_.
135-
This carries a performance penalty for some common use cases, but also allows
136-
for more flexibility around scaling.
133+
wrappers for getting assets from multiple sources.
134+
135+
This package now supports a render strategy setting, allowing certain
136+
applications to achieve better performance when needed. By default, the
137+
rendering uses the original `picture` mode, which retains full flexibility in
138+
scaling. Alternatively, when using the `raster` strategy, the SVG data is
139+
rendered into an `Image`, which is then drawn using drawImage. This approach may
140+
sacrifice some flexibility—especially around resolution scaling—but can
141+
significantly improve rendering performance in specific use cases.
137142

138143
## Precompiling and Optimizing SVGs
139144

third_party/packages/flutter_svg/lib/svg.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ class SvgPicture extends StatelessWidget {
100100
'The SVG theme must be set on the bytesLoader.')
101101
SvgTheme? theme,
102102
@Deprecated('This no longer does anything.') bool cacheColorFilter = false,
103+
this.renderingStrategy = RenderingStrategy.picture,
103104
});
104105

105106
/// Instantiates a widget that renders an SVG picture from an [AssetBundle].
@@ -201,6 +202,7 @@ class SvgPicture extends StatelessWidget {
201202
@Deprecated('Use colorFilter instead.')
202203
ui.BlendMode colorBlendMode = ui.BlendMode.srcIn,
203204
@Deprecated('This no longer does anything.') bool cacheColorFilter = false,
205+
this.renderingStrategy = RenderingStrategy.picture,
204206
}) : bytesLoader = SvgAssetLoader(
205207
assetName,
206208
packageName: package,
@@ -265,6 +267,7 @@ class SvgPicture extends StatelessWidget {
265267
SvgTheme? theme,
266268
ColorMapper? colorMapper,
267269
http.Client? httpClient,
270+
this.renderingStrategy = RenderingStrategy.picture,
268271
}) : bytesLoader = SvgNetworkLoader(
269272
url,
270273
headers: headers,
@@ -325,6 +328,7 @@ class SvgPicture extends StatelessWidget {
325328
SvgTheme? theme,
326329
ColorMapper? colorMapper,
327330
@Deprecated('This no longer does anything.') bool cacheColorFilter = false,
331+
this.renderingStrategy = RenderingStrategy.picture,
328332
}) : bytesLoader = SvgFileLoader(
329333
file,
330334
theme: theme,
@@ -380,6 +384,7 @@ class SvgPicture extends StatelessWidget {
380384
SvgTheme? theme,
381385
ColorMapper? colorMapper,
382386
@Deprecated('This no longer does anything.') bool cacheColorFilter = false,
387+
this.renderingStrategy = RenderingStrategy.picture,
383388
}) : bytesLoader = SvgBytesLoader(
384389
bytes,
385390
theme: theme,
@@ -435,6 +440,7 @@ class SvgPicture extends StatelessWidget {
435440
SvgTheme? theme,
436441
ColorMapper? colorMapper,
437442
@Deprecated('This no longer does anything.') bool cacheColorFilter = false,
443+
this.renderingStrategy = RenderingStrategy.picture,
438444
}) : bytesLoader = SvgStringLoader(
439445
string,
440446
theme: theme,
@@ -526,6 +532,14 @@ class SvgPicture extends StatelessWidget {
526532
/// The color filter, if any, to apply to this widget.
527533
final ColorFilter? colorFilter;
528534

535+
/// Widget rendering strategy used to balance flexibility and performance.
536+
///
537+
/// See the enum [RenderingStrategy] for details of all possible options and their common
538+
/// use cases.
539+
///
540+
/// Defaults to [RenderingStrategy.picture].
541+
final RenderingStrategy renderingStrategy;
542+
529543
@override
530544
Widget build(BuildContext context) {
531545
return createCompatVectorGraphic(
@@ -540,6 +554,7 @@ class SvgPicture extends StatelessWidget {
540554
errorBuilder: errorBuilder,
541555
colorFilter: colorFilter,
542556
placeholderBuilder: placeholderBuilder,
557+
strategy: renderingStrategy,
543558
clipViewbox: !allowDrawingOutsideViewBox,
544559
matchTextDirection: matchTextDirection,
545560
);

third_party/packages/flutter_svg/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: flutter_svg
22
description: An SVG rendering and widget library for Flutter, which allows painting and displaying Scalable Vector Graphics 1.1 files.
33
repository: https://github.com/flutter/packages/tree/main/third_party/packages/flutter_svg
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_svg%22
5-
version: 2.1.0
5+
version: 2.2.0
66

77
environment:
88
sdk: ^3.6.0

third_party/packages/flutter_svg/test/widget_svg_test.dart

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:flutter_svg/flutter_svg.dart';
77

88
import 'package:flutter_test/flutter_test.dart';
99
import 'package:http/http.dart' as http;
10+
import 'package:vector_graphics/vector_graphics_compat.dart';
1011

1112
class _TolerantComparator extends LocalFileComparator {
1213
_TolerantComparator(super.testFile);
@@ -139,6 +140,28 @@ void main() {
139140
await _checkWidgetAndGolden(key, 'flutter_logo.string.png');
140141
});
141142

143+
testWidgets('SvgPicture.string with renderingStrategy',
144+
(WidgetTester tester) async {
145+
final GlobalKey key = GlobalKey();
146+
await tester.pumpWidget(
147+
MediaQuery(
148+
data: mediaQueryData,
149+
child: RepaintBoundary(
150+
key: key,
151+
child: SvgPicture.string(
152+
svgStr,
153+
width: 100.0,
154+
height: 100.0,
155+
renderingStrategy: RenderingStrategy.raster,
156+
),
157+
),
158+
),
159+
);
160+
161+
await tester.pumpAndSettle();
162+
await _checkWidgetAndGolden(key, 'flutter_logo.string.png');
163+
});
164+
142165
testWidgets('SvgPicture.string with colorMapper',
143166
(WidgetTester tester) async {
144167
final GlobalKey key = GlobalKey();
@@ -295,6 +318,25 @@ void main() {
295318
await _checkWidgetAndGolden(key, 'flutter_logo.memory.png');
296319
});
297320

321+
testWidgets('SvgPicture.memory with strategy', (WidgetTester tester) async {
322+
final GlobalKey key = GlobalKey();
323+
await tester.pumpWidget(
324+
MediaQuery(
325+
data: mediaQueryData,
326+
child: RepaintBoundary(
327+
key: key,
328+
child: SvgPicture.memory(
329+
svgBytes,
330+
renderingStrategy: RenderingStrategy.raster,
331+
),
332+
),
333+
),
334+
);
335+
await tester.pumpAndSettle();
336+
337+
await _checkWidgetAndGolden(key, 'flutter_logo.memory.png');
338+
});
339+
298340
testWidgets('SvgPicture.memory with colorMapper',
299341
(WidgetTester tester) async {
300342
final GlobalKey key = GlobalKey();
@@ -334,6 +376,26 @@ void main() {
334376
await _checkWidgetAndGolden(key, 'flutter_logo.asset.png');
335377
});
336378

379+
testWidgets('SvgPicture.asset with strategy', (WidgetTester tester) async {
380+
final FakeAssetBundle fakeAsset = FakeAssetBundle();
381+
final GlobalKey key = GlobalKey();
382+
await tester.pumpWidget(
383+
MediaQuery(
384+
data: mediaQueryData,
385+
child: RepaintBoundary(
386+
key: key,
387+
child: SvgPicture.asset(
388+
'test.svg',
389+
bundle: fakeAsset,
390+
renderingStrategy: RenderingStrategy.raster,
391+
),
392+
),
393+
),
394+
);
395+
await tester.pumpAndSettle();
396+
await _checkWidgetAndGolden(key, 'flutter_logo.asset.png');
397+
});
398+
337399
testWidgets('SvgPicture.asset with colorMapper', (WidgetTester tester) async {
338400
final FakeAssetBundle fakeAsset = FakeAssetBundle();
339401
final GlobalKey key = GlobalKey();
@@ -380,6 +442,33 @@ void main() {
380442
await _checkWidgetAndGolden(key, 'flutter_logo.asset.png');
381443
});
382444

445+
testWidgets('SvgPicture.asset DefaultAssetBundle with strategy',
446+
(WidgetTester tester) async {
447+
final FakeAssetBundle fakeAsset = FakeAssetBundle();
448+
final GlobalKey key = GlobalKey();
449+
await tester.pumpWidget(
450+
Directionality(
451+
textDirection: TextDirection.ltr,
452+
child: MediaQuery(
453+
data: mediaQueryData,
454+
child: DefaultAssetBundle(
455+
bundle: fakeAsset,
456+
child: RepaintBoundary(
457+
key: key,
458+
child: SvgPicture.asset(
459+
'test.svg',
460+
semanticsLabel: 'Test SVG',
461+
renderingStrategy: RenderingStrategy.raster,
462+
),
463+
),
464+
),
465+
),
466+
),
467+
);
468+
await tester.pumpAndSettle();
469+
await _checkWidgetAndGolden(key, 'flutter_logo.asset.png');
470+
});
471+
383472
testWidgets('SvgPicture.asset DefaultAssetBundle with colorMapper',
384473
(WidgetTester tester) async {
385474
final FakeAssetBundle fakeAsset = FakeAssetBundle();
@@ -425,6 +514,25 @@ void main() {
425514
await _checkWidgetAndGolden(key, 'flutter_logo.network.png');
426515
});
427516

517+
testWidgets('SvgPicture.network with strategy', (WidgetTester tester) async {
518+
final GlobalKey key = GlobalKey();
519+
await tester.pumpWidget(
520+
MediaQuery(
521+
data: mediaQueryData,
522+
child: RepaintBoundary(
523+
key: key,
524+
child: SvgPicture.network(
525+
'test.svg',
526+
httpClient: FakeHttpClient(),
527+
renderingStrategy: RenderingStrategy.raster,
528+
),
529+
),
530+
),
531+
);
532+
await tester.pumpAndSettle();
533+
await _checkWidgetAndGolden(key, 'flutter_logo.network.png');
534+
});
535+
428536
testWidgets('SvgPicture.network with colorMapper',
429537
(WidgetTester tester) async {
430538
final GlobalKey key = GlobalKey();

0 commit comments

Comments
 (0)