@@ -11,29 +11,29 @@ import '../dom.dart';
11
11
import '../html_image_codec.dart' ;
12
12
import '../safe_browser_api.dart' ;
13
13
import '../util.dart' ;
14
+ import 'canvas.dart' ;
14
15
import 'canvaskit_api.dart' ;
15
16
import 'image_wasm_codecs.dart' ;
16
17
import 'image_web_codecs.dart' ;
18
+ import 'painting.dart' ;
19
+ import 'picture.dart' ;
20
+ import 'picture_recorder.dart' ;
17
21
import 'skia_object_cache.dart' ;
18
22
19
23
/// Instantiates a [ui.Codec] backed by an `SkAnimatedImage` from Skia.
20
- // TODO(yjbanov): Implement targetWidth and targetHeight support.
21
- // https://github.com/flutter/flutter/issues/34075
22
24
FutureOr <ui.Codec > skiaInstantiateImageCodec (Uint8List list,
23
25
[int ? targetWidth, int ? targetHeight]) {
24
- if (browserSupportsImageDecoder) {
26
+ // If we have either a target width or target height, use canvaskit to decode.
27
+ if (browserSupportsImageDecoder && (targetWidth == null && targetHeight == null )) {
25
28
return CkBrowserImageDecoder .create (
26
29
data: list,
27
30
debugSource: 'encoded image bytes' ,
28
- targetWidth: targetWidth,
29
- targetHeight: targetHeight,
30
31
);
31
32
} else {
32
- return CkAnimatedImage .decodeFromBytes (list, 'encoded image bytes' );
33
+ return CkAnimatedImage .decodeFromBytes (list, 'encoded image bytes' , targetWidth : targetWidth, targetHeight : targetHeight );
33
34
}
34
35
}
35
36
36
- // TODO(yjbanov): add support for targetWidth/targetHeight (https://github.com/flutter/flutter/issues/34075)
37
37
void skiaDecodeImageFromPixels (
38
38
Uint8List pixels,
39
39
int width,
@@ -45,6 +45,13 @@ void skiaDecodeImageFromPixels(
45
45
int ? targetHeight,
46
46
bool allowUpscaling = true ,
47
47
}) {
48
+ if (targetWidth != null ) {
49
+ assert (allowUpscaling || targetWidth <= width);
50
+ }
51
+ if (targetHeight != null ) {
52
+ assert (allowUpscaling || targetHeight <= height);
53
+ }
54
+
48
55
// Run in a timer to avoid janking the current frame by moving the decoding
49
56
// work outside the frame event.
50
57
Timer .run (() {
@@ -65,10 +72,88 @@ void skiaDecodeImageFromPixels(
65
72
return ;
66
73
}
67
74
75
+ if (targetWidth != null || targetHeight != null ) {
76
+ if (! validUpscale (allowUpscaling, targetWidth, targetHeight, width, height)) {
77
+ domWindow.console.warn ('Cannot apply targetWidth/targetHeight when allowUpscaling is false.' );
78
+ } else {
79
+ return callback (scaleImage (skImage, targetWidth, targetHeight));
80
+ }
81
+ }
68
82
return callback (CkImage (skImage));
69
83
});
70
84
}
71
85
86
+ // An invalid upscale happens when allowUpscaling is false AND either the given
87
+ // targetWidth is larger than the originalWidth OR the targetHeight is larger than originalHeight.
88
+ bool validUpscale (bool allowUpscaling, int ? targetWidth, int ? targetHeight, int originalWidth, int originalHeight) {
89
+ if (allowUpscaling) {
90
+ return true ;
91
+ }
92
+ final bool targetWidthFits;
93
+ final bool targetHeightFits;
94
+ if (targetWidth != null ) {
95
+ targetWidthFits = targetWidth <= originalWidth;
96
+ } else {
97
+ targetWidthFits = true ;
98
+ }
99
+
100
+ if (targetHeight != null ) {
101
+ targetHeightFits = targetHeight <= originalHeight;
102
+ } else {
103
+ targetHeightFits = true ;
104
+ }
105
+ return targetWidthFits && targetHeightFits;
106
+ }
107
+
108
+ /// Creates a scaled [CkImage] from an [SkImage] by drawing the [SkImage] to a canvas.
109
+ ///
110
+ /// This function will only be called if either a targetWidth or targetHeight is not null
111
+ ///
112
+ /// If only one of targetWidth or targetHeight are specified, the other
113
+ /// dimension will be scaled according to the aspect ratio of the supplied
114
+ /// dimension.
115
+ ///
116
+ /// If either targetWidth or targetHeight is less than or equal to zero, it
117
+ /// will be treated as if it is null.
118
+ CkImage scaleImage (SkImage image, int ? targetWidth, int ? targetHeight) {
119
+ assert (targetWidth != null || targetHeight != null );
120
+ if (targetWidth != null && targetWidth <= 0 ) {
121
+ targetWidth = null ;
122
+ }
123
+ if (targetHeight != null && targetHeight <= 0 ) {
124
+ targetHeight = null ;
125
+ }
126
+ if (targetWidth == null && targetHeight != null ) {
127
+ targetWidth = (targetHeight * (image.width () / image.height ())).round ();
128
+ targetHeight = targetHeight;
129
+ } else if (targetHeight == null && targetWidth != null ) {
130
+ targetWidth = targetWidth;
131
+ targetHeight = targetWidth ~ / (image.width () / image.height ());
132
+ }
133
+
134
+ assert (targetWidth != null );
135
+ assert (targetHeight != null );
136
+
137
+ final CkPictureRecorder recorder = CkPictureRecorder ();
138
+ final CkCanvas canvas = recorder.beginRecording (ui.Rect .largest);
139
+
140
+ canvas.drawImageRect (
141
+ CkImage (image),
142
+ ui.Rect .fromLTWH (0 , 0 , image.width (), image.height ()),
143
+ ui.Rect .fromLTWH (0 , 0 , targetWidth! .toDouble (), targetHeight! .toDouble ()),
144
+ CkPaint ()
145
+ );
146
+
147
+ final CkPicture picture = recorder.endRecording ();
148
+ final ui.Image finalImage = picture.toImageSync (
149
+ targetWidth,
150
+ targetHeight
151
+ );
152
+
153
+ final CkImage ckImage = finalImage as CkImage ;
154
+ return ckImage;
155
+ }
156
+
72
157
/// Thrown when the web engine fails to decode an image, either due to a
73
158
/// network issue, corrupted image contents, or missing codec.
74
159
class ImageCodecException implements Exception {
0 commit comments