Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
06d0cd51
Unverified
Commit
06d0cd51
authored
Jun 26, 2020
by
Dan Field
Committed by
GitHub
Jun 26, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Allow detection of images using more memory than necessary (#59877)
parent
fd7a72ee
Changes
15
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
615 additions
and
11 deletions
+615
-11
image_painting_event_test.dart
dev/tracing_tests/test/image_painting_event_test.dart
+124
-0
_network_image_io.dart
packages/flutter/lib/src/painting/_network_image_io.dart
+1
-0
_network_image_web.dart
packages/flutter/lib/src/painting/_network_image_web.dart
+1
-0
debug.dart
packages/flutter/lib/src/painting/debug.dart
+98
-1
decoration_image.dart
packages/flutter/lib/src/painting/decoration_image.dart
+61
-0
image_provider.dart
packages/flutter/lib/src/painting/image_provider.dart
+8
-1
image_stream.dart
packages/flutter/lib/src/painting/image_stream.dart
+20
-6
image.dart
packages/flutter/lib/src/rendering/image.dart
+5
-0
basic.dart
packages/flutter/lib/src/widgets/basic.dart
+6
-0
image.dart
packages/flutter/lib/src/widgets/image.dart
+1
-0
image_provider_network_image_test.dart
...tter/test/painting/image_provider_network_image_test.dart
+61
-0
image_provider_test.dart
packages/flutter/test/painting/image_provider_test.dart
+70
-0
mocks_for_image_cache.dart
packages/flutter/test/painting/mocks_for_image_cache.dart
+4
-1
paint_image_test.dart
packages/flutter/test/painting/paint_image_test.dart
+114
-2
image_test.dart
packages/flutter/test/widgets/image_test.dart
+41
-0
No files found.
dev/tracing_tests/test/image_painting_event_test.dart
0 → 100644
View file @
06d0cd51
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:async'
;
import
'dart:convert'
show
jsonEncode
;
import
'dart:developer'
as
developer
;
import
'dart:typed_data'
;
import
'dart:ui'
as
ui
;
import
'package:flutter/painting.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:vm_service/vm_service.dart'
;
import
'package:vm_service/vm_service_io.dart'
;
void
main
(
)
{
VmService
vmService
;
LiveTestWidgetsFlutterBinding
binding
;
setUpAll
(()
async
{
final
developer
.
ServiceProtocolInfo
info
=
await
developer
.
Service
.
getInfo
();
if
(
info
.
serverUri
==
null
)
{
fail
(
'This test _must_ be run with --enable-vmservice.'
);
}
vmService
=
await
vmServiceConnectUri
(
'ws://localhost:
${info.serverUri.port}${info.serverUri.path}
ws'
);
await
vmService
.
streamListen
(
EventStreams
.
kExtension
);
// Initialize bindings
binding
=
LiveTestWidgetsFlutterBinding
();
binding
.
framePolicy
=
LiveTestWidgetsFlutterBindingFramePolicy
.
fullyLive
;
binding
.
attachRootWidget
(
const
SizedBox
.
expand
());
expect
(
binding
.
framesEnabled
,
true
);
// Pump two frames to make sure we clear out any inter-frame comparisons.
await
binding
.
endOfFrame
;
await
binding
.
endOfFrame
;
});
test
(
'Image painting events - deduplicates across frames'
,
()
async
{
final
Completer
<
Event
>
completer
=
Completer
<
Event
>();
vmService
.
onExtensionEvent
.
first
.
then
(
completer
.
complete
);
const
TestImage
image
=
TestImage
(
width:
300
,
height:
300
);
final
TestCanvas
canvas
=
TestCanvas
();
paintImage
(
canvas:
canvas
,
rect:
const
Rect
.
fromLTWH
(
50.0
,
75.0
,
200.0
,
100.0
),
image:
image
,
debugImageLabel:
'test.png'
,
);
// Make sure that we don't report an identical image size info if we
// redraw in the next frame.
await
binding
.
endOfFrame
;
paintImage
(
canvas:
canvas
,
rect:
const
Rect
.
fromLTWH
(
50.0
,
75.0
,
200.0
,
100.0
),
image:
image
,
debugImageLabel:
'test.png'
,
);
await
binding
.
endOfFrame
;
final
Event
event
=
await
completer
.
future
;
expect
(
event
.
extensionKind
,
'Flutter.ImageSizesForFrame'
);
expect
(
jsonEncode
(
event
.
extensionData
.
data
),
'{"test.png":{"source":"test.png","displaySize":{"width":200.0,"height":100.0},"imageSize":{"width":300.0,"height":300.0},"displaySizeInBytes":106666,"decodedSizeInBytes":480000}}'
,
);
},
skip:
isBrowser
);
// uses dart:isolate and io
test
(
'Image painting events - deduplicates across frames'
,
()
async
{
final
Completer
<
Event
>
completer
=
Completer
<
Event
>();
vmService
.
onExtensionEvent
.
first
.
then
(
completer
.
complete
);
const
TestImage
image
=
TestImage
(
width:
300
,
height:
300
);
final
TestCanvas
canvas
=
TestCanvas
();
paintImage
(
canvas:
canvas
,
rect:
const
Rect
.
fromLTWH
(
50.0
,
75.0
,
200.0
,
100.0
),
image:
image
,
debugImageLabel:
'test.png'
,
);
paintImage
(
canvas:
canvas
,
rect:
const
Rect
.
fromLTWH
(
50.0
,
75.0
,
300.0
,
300.0
),
image:
image
,
debugImageLabel:
'test.png'
,
);
await
binding
.
endOfFrame
;
final
Event
event
=
await
completer
.
future
;
expect
(
event
.
extensionKind
,
'Flutter.ImageSizesForFrame'
);
expect
(
jsonEncode
(
event
.
extensionData
.
data
),
'{"test.png":{"source":"test.png","displaySize":{"width":300.0,"height":300.0},"imageSize":{"width":300.0,"height":300.0},"displaySizeInBytes":480000,"decodedSizeInBytes":480000}}'
,
);
},
skip:
isBrowser
);
// uses dart:isolate and io
}
class
TestImage
implements
ui
.
Image
{
const
TestImage
({
this
.
height
=
0
,
this
.
width
=
0
});
@override
final
int
height
;
@override
final
int
width
;
@override
void
dispose
()
{}
@override
Future
<
ByteData
>
toByteData
(
{
ui
.
ImageByteFormat
format
=
ui
.
ImageByteFormat
.
rawRgba
})
{
throw
UnimplementedError
();
}
}
class
TestCanvas
implements
Canvas
{
@override
void
noSuchMethod
(
Invocation
invocation
)
{}
}
packages/flutter/lib/src/painting/_network_image_io.dart
View file @
06d0cd51
...
@@ -51,6 +51,7 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm
...
@@ -51,6 +51,7 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm
codec:
_loadAsync
(
key
as
NetworkImage
,
chunkEvents
,
decode
),
codec:
_loadAsync
(
key
as
NetworkImage
,
chunkEvents
,
decode
),
chunkEvents:
chunkEvents
.
stream
,
chunkEvents:
chunkEvents
.
stream
,
scale:
key
.
scale
,
scale:
key
.
scale
,
debugLabel:
key
.
url
,
informationCollector:
()
{
informationCollector:
()
{
return
<
DiagnosticsNode
>[
return
<
DiagnosticsNode
>[
DiagnosticsProperty
<
image_provider
.
ImageProvider
>(
'Image provider'
,
this
),
DiagnosticsProperty
<
image_provider
.
ImageProvider
>(
'Image provider'
,
this
),
...
...
packages/flutter/lib/src/painting/_network_image_web.dart
View file @
06d0cd51
...
@@ -54,6 +54,7 @@ class NetworkImage
...
@@ -54,6 +54,7 @@ class NetworkImage
chunkEvents:
chunkEvents
.
stream
,
chunkEvents:
chunkEvents
.
stream
,
codec:
_loadAsync
(
key
as
NetworkImage
,
decode
,
chunkEvents
),
codec:
_loadAsync
(
key
as
NetworkImage
,
decode
,
chunkEvents
),
scale:
key
.
scale
,
scale:
key
.
scale
,
debugLabel:
key
.
url
,
informationCollector:
_imageStreamInformationCollector
(
key
));
informationCollector:
_imageStreamInformationCollector
(
key
));
}
}
...
...
packages/flutter/lib/src/painting/debug.dart
View file @
06d0cd51
...
@@ -5,6 +5,7 @@
...
@@ -5,6 +5,7 @@
// @dart = 2.8
// @dart = 2.8
import
'dart:io'
;
import
'dart:io'
;
import
'dart:ui'
show
Size
,
hashValues
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/foundation.dart'
;
...
@@ -31,6 +32,101 @@ typedef HttpClientProvider = HttpClient Function();
...
@@ -31,6 +32,101 @@ typedef HttpClientProvider = HttpClient Function();
/// This value is ignored in non-debug builds.
/// This value is ignored in non-debug builds.
HttpClientProvider
debugNetworkImageHttpClientProvider
;
HttpClientProvider
debugNetworkImageHttpClientProvider
;
typedef
PaintImageCallback
=
void
Function
(
ImageSizeInfo
);
/// Tracks the bytes used by a [ui.Image] compared to the bytes needed to paint
/// that image without scaling it.
@immutable
class
ImageSizeInfo
{
/// Creates an object to track the backing size of a [ui.Image] compared to
/// its display size on a [Canvas].
///
/// This class is used by the framework when it paints an image to a canvas
/// to report to `dart:developer`'s [postEvent], as well as to the
/// [debugOnPaintImage] callback if it is set.
const
ImageSizeInfo
({
this
.
source
,
this
.
displaySize
,
this
.
imageSize
});
/// A unique identifier for this image, for example its asset path or network
/// URL.
final
String
source
;
/// The size of the area the image will be rendered in.
final
Size
displaySize
;
/// The size the image has been decoded to.
final
Size
imageSize
;
/// The number of bytes needed to render the image without scaling it.
int
get
displaySizeInBytes
=>
_sizeToBytes
(
displaySize
);
/// The number of bytes used by the image in memory.
int
get
decodedSizeInBytes
=>
_sizeToBytes
(
imageSize
);
int
_sizeToBytes
(
Size
size
)
{
// Assume 4 bytes per pixel and that mipmapping will be used, which adds
// 4/3.
return
(
size
.
width
*
size
.
height
*
4
*
(
4
/
3
)).
toInt
();
}
/// Returns a JSON encodable representation of this object.
Map
<
String
,
Object
>
toJson
()
{
return
<
String
,
Object
>{
'source'
:
source
,
'displaySize'
:
<
String
,
double
>{
'width'
:
displaySize
.
width
,
'height'
:
displaySize
.
height
,
},
'imageSize'
:
<
String
,
double
>{
'width'
:
imageSize
.
width
,
'height'
:
imageSize
.
height
,
},
'displaySizeInBytes'
:
displaySizeInBytes
,
'decodedSizeInBytes'
:
decodedSizeInBytes
,
};
}
@override
bool
operator
==(
Object
other
)
{
if
(
other
.
runtimeType
!=
runtimeType
)
{
return
false
;
}
return
other
is
ImageSizeInfo
&&
other
.
source
==
source
&&
other
.
imageSize
==
imageSize
&&
other
.
displaySize
==
displaySize
;
}
@override
int
get
hashCode
=>
hashValues
(
source
,
displaySize
,
imageSize
);
@override
String
toString
()
=>
'ImageSizeInfo(
$source
, imageSize:
$imageSize
, displaySize:
$displaySize
)'
;
}
/// If not null, called when the framework is about to paint an [Image] to a
/// [Canvas] with an [ImageSizeInfo] that contains the decoded size of the
/// image as well as its output size.
///
/// A test can use this callback to detect if images under test are being
/// rendered with the appropriate cache dimensions.
///
/// For example, if a 100x100 image is decoded it takes roughly 53kb in memory
/// (including mipmapping overhead). If it is only ever displayed at 50x50, it
/// would take only 13kb if the cacheHeight/cacheWidth parameters had been
/// specified at that size. This problem becomes more serious for larger
/// images, such as a high resolution image from a 12MP camera, which would be
/// 64mb when decoded.
///
/// When using this callback, developers should consider whether the image will
/// be panned or scaled up in the application, how many images are being
/// displayed, and whether the application will run on multiple devices with
/// different resolutions and memory capacities. For example, it should be fine
/// to have an image that animates from thumbnail size to full screen be at
/// a higher resolution while animating, but it would be problematic to have
/// a grid or list of such thumbnails all be at the full resolution at the same
/// time.
PaintImageCallback
debugOnPaintImage
;
/// Returns true if none of the painting library debug variables have been changed.
/// Returns true if none of the painting library debug variables have been changed.
///
///
/// This function is used by the test framework to ensure that debug variables
/// This function is used by the test framework to ensure that debug variables
...
@@ -45,7 +141,8 @@ HttpClientProvider debugNetworkImageHttpClientProvider;
...
@@ -45,7 +141,8 @@ HttpClientProvider debugNetworkImageHttpClientProvider;
bool
debugAssertAllPaintingVarsUnset
(
String
reason
,
{
bool
debugDisableShadowsOverride
=
false
})
{
bool
debugAssertAllPaintingVarsUnset
(
String
reason
,
{
bool
debugDisableShadowsOverride
=
false
})
{
assert
(()
{
assert
(()
{
if
(
debugDisableShadows
!=
debugDisableShadowsOverride
||
if
(
debugDisableShadows
!=
debugDisableShadowsOverride
||
debugNetworkImageHttpClientProvider
!=
null
)
{
debugNetworkImageHttpClientProvider
!=
null
||
debugOnPaintImage
!=
null
)
{
throw
FlutterError
(
reason
);
throw
FlutterError
(
reason
);
}
}
return
true
;
return
true
;
...
...
packages/flutter/lib/src/painting/decoration_image.dart
View file @
06d0cd51
...
@@ -4,14 +4,17 @@
...
@@ -4,14 +4,17 @@
// @dart = 2.8
// @dart = 2.8
import
'dart:developer'
as
developer
;
import
'dart:ui'
as
ui
show
Image
;
import
'dart:ui'
as
ui
show
Image
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/scheduler.dart'
;
import
'alignment.dart'
;
import
'alignment.dart'
;
import
'basic_types.dart'
;
import
'basic_types.dart'
;
import
'borders.dart'
;
import
'borders.dart'
;
import
'box_fit.dart'
;
import
'box_fit.dart'
;
import
'debug.dart'
;
import
'image_provider.dart'
;
import
'image_provider.dart'
;
import
'image_stream.dart'
;
import
'image_stream.dart'
;
...
@@ -275,6 +278,7 @@ class DecorationImagePainter {
...
@@ -275,6 +278,7 @@ class DecorationImagePainter {
canvas:
canvas
,
canvas:
canvas
,
rect:
rect
,
rect:
rect
,
image:
_image
.
image
,
image:
_image
.
image
,
debugImageLabel:
_image
.
debugLabel
,
scale:
_details
.
scale
*
_image
.
scale
,
scale:
_details
.
scale
*
_image
.
scale
,
colorFilter:
_details
.
colorFilter
,
colorFilter:
_details
.
colorFilter
,
fit:
_details
.
fit
,
fit:
_details
.
fit
,
...
@@ -317,6 +321,25 @@ class DecorationImagePainter {
...
@@ -317,6 +321,25 @@ class DecorationImagePainter {
}
}
}
}
/// Used by [paintImage] to report image sizes drawn at the end of the frame.
Map
<
String
,
ImageSizeInfo
>
_pendingImageSizeInfo
=
<
String
,
ImageSizeInfo
>{};
/// [ImageSizeInfo]s that were reported on the last frame.
///
/// Used to prevent duplicative reports from frame to frame.
Set
<
ImageSizeInfo
>
_lastFrameImageSizeInfo
=
<
ImageSizeInfo
>{};
/// Flushes inter-frame tracking of image size information from [paintImage].
///
/// Has no effect if asserts are disabled.
@visibleForTesting
void
debugFlushLastFrameImageSizeInfo
(
)
{
assert
(()
{
_lastFrameImageSizeInfo
=
<
ImageSizeInfo
>{};
return
true
;
}());
}
/// Paints an image into the given rectangle on the canvas.
/// Paints an image into the given rectangle on the canvas.
///
///
/// The arguments have the following meanings:
/// The arguments have the following meanings:
...
@@ -389,6 +412,7 @@ void paintImage({
...
@@ -389,6 +412,7 @@ void paintImage({
@required
Canvas
canvas
,
@required
Canvas
canvas
,
@required
Rect
rect
,
@required
Rect
rect
,
@required
ui
.
Image
image
,
@required
ui
.
Image
image
,
String
debugImageLabel
,
double
scale
=
1.0
,
double
scale
=
1.0
,
ColorFilter
colorFilter
,
ColorFilter
colorFilter
,
BoxFit
fit
,
BoxFit
fit
,
...
@@ -431,6 +455,43 @@ void paintImage({
...
@@ -431,6 +455,43 @@ void paintImage({
// as we apply a nine-patch stretch.
// as we apply a nine-patch stretch.
assert
(
sourceSize
==
inputSize
,
'centerSlice was used with a BoxFit that does not guarantee that the image is fully visible.'
);
assert
(
sourceSize
==
inputSize
,
'centerSlice was used with a BoxFit that does not guarantee that the image is fully visible.'
);
}
}
// Output size is fully calculated.
if
(!
kReleaseMode
)
{
final
ImageSizeInfo
sizeInfo
=
ImageSizeInfo
(
// Some ImageProvider implementations may not have given this.
source
:
debugImageLabel
??
'<Unknown Image(
${image.width}
×
${image.height}
)>'
,
imageSize:
Size
(
image
.
width
.
toDouble
(),
image
.
height
.
toDouble
()),
displaySize:
outputSize
,
);
// Avoid emitting events that are the same as those emitted in the last frame.
if
(!
_lastFrameImageSizeInfo
.
contains
(
sizeInfo
))
{
final
ImageSizeInfo
existingSizeInfo
=
_pendingImageSizeInfo
[
sizeInfo
.
source
];
if
(
existingSizeInfo
==
null
||
existingSizeInfo
.
displaySizeInBytes
<
sizeInfo
.
displaySizeInBytes
)
{
_pendingImageSizeInfo
[
sizeInfo
.
source
]
=
sizeInfo
;
}
// _pendingImageSizeInfo.add(sizeInfo);
if
(
debugOnPaintImage
!=
null
)
{
debugOnPaintImage
(
sizeInfo
);
}
SchedulerBinding
.
instance
.
addPostFrameCallback
((
Duration
timeStamp
)
{
_lastFrameImageSizeInfo
=
_pendingImageSizeInfo
.
values
.
toSet
();
if
(
_pendingImageSizeInfo
.
isEmpty
)
{
return
;
}
developer
.
postEvent
(
'Flutter.ImageSizesForFrame'
,
<
Object
,
Object
>{
for
(
ImageSizeInfo
imageSizeInfo
in
_pendingImageSizeInfo
.
values
)
imageSizeInfo
.
source
:
imageSizeInfo
.
toJson
()
},
);
_pendingImageSizeInfo
=
<
String
,
ImageSizeInfo
>{};
});
}
}
if
(
repeat
!=
ImageRepeat
.
noRepeat
&&
destinationSize
==
outputSize
)
{
if
(
repeat
!=
ImageRepeat
.
noRepeat
&&
destinationSize
==
outputSize
)
{
// There's no need to repeat the image because we're exactly filling the
// There's no need to repeat the image because we're exactly filling the
// output rect with the image.
// output rect with the image.
...
...
packages/flutter/lib/src/painting/image_provider.dart
View file @
06d0cd51
...
@@ -651,6 +651,7 @@ abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKe
...
@@ -651,6 +651,7 @@ abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKe
return
MultiFrameImageStreamCompleter
(
return
MultiFrameImageStreamCompleter
(
codec:
_loadAsync
(
key
,
decode
),
codec:
_loadAsync
(
key
,
decode
),
scale:
key
.
scale
,
scale:
key
.
scale
,
debugLabel:
key
.
name
,
informationCollector:
collector
informationCollector:
collector
);
);
}
}
...
@@ -764,7 +765,11 @@ class ResizeImage extends ImageProvider<_SizeAwareCacheKey> {
...
@@ -764,7 +765,11 @@ class ResizeImage extends ImageProvider<_SizeAwareCacheKey> {
);
);
return
decode
(
bytes
,
cacheWidth:
width
,
cacheHeight:
height
,
allowUpscaling:
this
.
allowUpscaling
);
return
decode
(
bytes
,
cacheWidth:
width
,
cacheHeight:
height
,
allowUpscaling:
this
.
allowUpscaling
);
};
};
return
imageProvider
.
load
(
key
.
providerCacheKey
,
decodeResize
);
final
ImageStreamCompleter
completer
=
imageProvider
.
load
(
key
.
providerCacheKey
,
decodeResize
);
if
(!
kReleaseMode
)
{
completer
.
debugLabel
=
'
${completer.debugLabel}
- Resized(
${key.width}
×
${key.height}
)'
;
}
return
completer
;
}
}
@override
@override
...
@@ -860,6 +865,7 @@ class FileImage extends ImageProvider<FileImage> {
...
@@ -860,6 +865,7 @@ class FileImage extends ImageProvider<FileImage> {
return
MultiFrameImageStreamCompleter
(
return
MultiFrameImageStreamCompleter
(
codec:
_loadAsync
(
key
,
decode
),
codec:
_loadAsync
(
key
,
decode
),
scale:
key
.
scale
,
scale:
key
.
scale
,
debugLabel:
key
.
file
.
path
,
informationCollector:
()
sync
*
{
informationCollector:
()
sync
*
{
yield
ErrorDescription
(
'Path:
${file?.path}
'
);
yield
ErrorDescription
(
'Path:
${file?.path}
'
);
},
},
...
@@ -933,6 +939,7 @@ class MemoryImage extends ImageProvider<MemoryImage> {
...
@@ -933,6 +939,7 @@ class MemoryImage extends ImageProvider<MemoryImage> {
return
MultiFrameImageStreamCompleter
(
return
MultiFrameImageStreamCompleter
(
codec:
_loadAsync
(
key
,
decode
),
codec:
_loadAsync
(
key
,
decode
),
scale:
key
.
scale
,
scale:
key
.
scale
,
debugLabel:
'MemoryImage(
${describeIdentity(key.bytes)}
)'
,
);
);
}
}
...
...
packages/flutter/lib/src/painting/image_stream.dart
View file @
06d0cd51
...
@@ -20,7 +20,9 @@ class ImageInfo {
...
@@ -20,7 +20,9 @@ class ImageInfo {
/// Creates an [ImageInfo] object for the given [image] and [scale].
/// Creates an [ImageInfo] object for the given [image] and [scale].
///
///
/// Both the image and the scale must not be null.
/// Both the image and the scale must not be null.
const
ImageInfo
({
@required
this
.
image
,
this
.
scale
=
1.0
})
///
/// The tag may be used to identify the source of this image.
const
ImageInfo
({
@required
this
.
image
,
this
.
scale
=
1.0
,
this
.
debugLabel
})
:
assert
(
image
!=
null
),
:
assert
(
image
!=
null
),
assert
(
scale
!=
null
);
assert
(
scale
!=
null
);
...
@@ -42,11 +44,14 @@ class ImageInfo {
...
@@ -42,11 +44,14 @@ class ImageInfo {
/// (e.g. in the arguments given to [Canvas.drawImage]).
/// (e.g. in the arguments given to [Canvas.drawImage]).
final
double
scale
;
final
double
scale
;
/// A string used for debugging purpopses to identify the source of this image.
final
String
debugLabel
;
@override
@override
String
toString
()
=>
'
$image
@
${debugFormatDouble(scale)}
x'
;
String
toString
()
=>
'
$
{debugLabel != null ? '$debugLabel ' : ''}$
image
@
${debugFormatDouble(scale)}
x'
;
@override
@override
int
get
hashCode
=>
hashValues
(
image
,
scale
);
int
get
hashCode
=>
hashValues
(
image
,
scale
,
debugLabel
);
@override
@override
bool
operator
==(
Object
other
)
{
bool
operator
==(
Object
other
)
{
...
@@ -54,7 +59,8 @@ class ImageInfo {
...
@@ -54,7 +59,8 @@ class ImageInfo {
return
false
;
return
false
;
return
other
is
ImageInfo
return
other
is
ImageInfo
&&
other
.
image
==
image
&&
other
.
image
==
image
&&
other
.
scale
==
scale
;
&&
other
.
scale
==
scale
&&
other
.
debugLabel
==
debugLabel
;
}
}
}
}
...
@@ -331,6 +337,9 @@ abstract class ImageStreamCompleter with Diagnosticable {
...
@@ -331,6 +337,9 @@ abstract class ImageStreamCompleter with Diagnosticable {
ImageInfo
_currentImage
;
ImageInfo
_currentImage
;
FlutterErrorDetails
_currentError
;
FlutterErrorDetails
_currentError
;
/// A string identifying the source of the underlying image.
String
debugLabel
;
/// Whether any listeners are currently registered.
/// Whether any listeners are currently registered.
///
///
/// Clients should not depend on this value for their behavior, because having
/// Clients should not depend on this value for their behavior, because having
...
@@ -623,6 +632,9 @@ class MultiFrameImageStreamCompleter extends ImageStreamCompleter {
...
@@ -623,6 +632,9 @@ class MultiFrameImageStreamCompleter extends ImageStreamCompleter {
/// The `scale` parameter is the linear scale factor for drawing this frames
/// The `scale` parameter is the linear scale factor for drawing this frames
/// of this image at their intended size.
/// of this image at their intended size.
///
///
/// The `tag` parameter is passed on to created [ImageInfo] objects to
/// help identify the source of the image.
///
/// The `chunkEvents` parameter is an optional stream of notifications about
/// The `chunkEvents` parameter is an optional stream of notifications about
/// the loading progress of the image. If this stream is provided, the events
/// the loading progress of the image. If this stream is provided, the events
/// produced by the stream will be delivered to registered [ImageChunkListener]s
/// produced by the stream will be delivered to registered [ImageChunkListener]s
...
@@ -630,11 +642,13 @@ class MultiFrameImageStreamCompleter extends ImageStreamCompleter {
...
@@ -630,11 +642,13 @@ class MultiFrameImageStreamCompleter extends ImageStreamCompleter {
MultiFrameImageStreamCompleter
({
MultiFrameImageStreamCompleter
({
@required
Future
<
ui
.
Codec
>
codec
,
@required
Future
<
ui
.
Codec
>
codec
,
@required
double
scale
,
@required
double
scale
,
String
debugLabel
,
Stream
<
ImageChunkEvent
>
chunkEvents
,
Stream
<
ImageChunkEvent
>
chunkEvents
,
InformationCollector
informationCollector
,
InformationCollector
informationCollector
,
})
:
assert
(
codec
!=
null
),
})
:
assert
(
codec
!=
null
),
_informationCollector
=
informationCollector
,
_informationCollector
=
informationCollector
,
_scale
=
scale
{
_scale
=
scale
{
this
.
debugLabel
=
debugLabel
;
codec
.
then
<
void
>(
_handleCodecReady
,
onError:
(
dynamic
error
,
StackTrace
stack
)
{
codec
.
then
<
void
>(
_handleCodecReady
,
onError:
(
dynamic
error
,
StackTrace
stack
)
{
reportError
(
reportError
(
context:
ErrorDescription
(
'resolving an image codec'
),
context:
ErrorDescription
(
'resolving an image codec'
),
...
@@ -688,7 +702,7 @@ class MultiFrameImageStreamCompleter extends ImageStreamCompleter {
...
@@ -688,7 +702,7 @@ class MultiFrameImageStreamCompleter extends ImageStreamCompleter {
if
(!
hasListeners
)
if
(!
hasListeners
)
return
;
return
;
if
(
_isFirstFrame
()
||
_hasFrameDurationPassed
(
timestamp
))
{
if
(
_isFirstFrame
()
||
_hasFrameDurationPassed
(
timestamp
))
{
_emitFrame
(
ImageInfo
(
image:
_nextFrame
.
image
,
scale:
_scale
));
_emitFrame
(
ImageInfo
(
image:
_nextFrame
.
image
,
scale:
_scale
,
debugLabel:
debugLabel
));
_shownTimestamp
=
timestamp
;
_shownTimestamp
=
timestamp
;
_frameDuration
=
_nextFrame
.
duration
;
_frameDuration
=
_nextFrame
.
duration
;
_nextFrame
=
null
;
_nextFrame
=
null
;
...
@@ -729,7 +743,7 @@ class MultiFrameImageStreamCompleter extends ImageStreamCompleter {
...
@@ -729,7 +743,7 @@ class MultiFrameImageStreamCompleter extends ImageStreamCompleter {
if
(
_codec
.
frameCount
==
1
)
{
if
(
_codec
.
frameCount
==
1
)
{
// This is not an animated image, just return it and don't schedule more
// This is not an animated image, just return it and don't schedule more
// frames.
// frames.
_emitFrame
(
ImageInfo
(
image:
_nextFrame
.
image
,
scale:
_scale
));
_emitFrame
(
ImageInfo
(
image:
_nextFrame
.
image
,
scale:
_scale
,
debugLabel:
debugLabel
));
return
;
return
;
}
}
_scheduleAppFrame
();
_scheduleAppFrame
();
...
...
packages/flutter/lib/src/rendering/image.dart
View file @
06d0cd51
...
@@ -28,6 +28,7 @@ class RenderImage extends RenderBox {
...
@@ -28,6 +28,7 @@ class RenderImage extends RenderBox {
/// [alignment] will need resolving or if [matchTextDirection] is true.
/// [alignment] will need resolving or if [matchTextDirection] is true.
RenderImage
({
RenderImage
({
ui
.
Image
image
,
ui
.
Image
image
,
this
.
debugImageLabel
,
double
width
,
double
width
,
double
height
,
double
height
,
double
scale
=
1.0
,
double
scale
=
1.0
,
...
@@ -94,6 +95,9 @@ class RenderImage extends RenderBox {
...
@@ -94,6 +95,9 @@ class RenderImage extends RenderBox {
markNeedsLayout
();
markNeedsLayout
();
}
}
/// A string used to identify the source of the image.
String
debugImageLabel
;
/// If non-null, requires the image to have this width.
/// If non-null, requires the image to have this width.
///
///
/// If null, the image will pick a size that best preserves its intrinsic
/// If null, the image will pick a size that best preserves its intrinsic
...
@@ -377,6 +381,7 @@ class RenderImage extends RenderBox {
...
@@ -377,6 +381,7 @@ class RenderImage extends RenderBox {
canvas:
context
.
canvas
,
canvas:
context
.
canvas
,
rect:
offset
&
size
,
rect:
offset
&
size
,
image:
_image
,
image:
_image
,
debugImageLabel:
debugImageLabel
,
scale:
_scale
,
scale:
_scale
,
colorFilter:
_colorFilter
,
colorFilter:
_colorFilter
,
fit:
_fit
,
fit:
_fit
,
...
...
packages/flutter/lib/src/widgets/basic.dart
View file @
06d0cd51
...
@@ -5301,6 +5301,7 @@ class RawImage extends LeafRenderObjectWidget {
...
@@ -5301,6 +5301,7 @@ class RawImage extends LeafRenderObjectWidget {
const
RawImage
({
const
RawImage
({
Key
key
,
Key
key
,
this
.
image
,
this
.
image
,
this
.
debugImageLabel
,
this
.
width
,
this
.
width
,
this
.
height
,
this
.
height
,
this
.
scale
=
1.0
,
this
.
scale
=
1.0
,
...
@@ -5324,6 +5325,9 @@ class RawImage extends LeafRenderObjectWidget {
...
@@ -5324,6 +5325,9 @@ class RawImage extends LeafRenderObjectWidget {
/// The image to display.
/// The image to display.
final
ui
.
Image
image
;
final
ui
.
Image
image
;
/// A string identifying the source of the image.
final
String
debugImageLabel
;
/// If non-null, require the image to have this width.
/// If non-null, require the image to have this width.
///
///
/// If null, the image will pick a size that best preserves its intrinsic
/// If null, the image will pick a size that best preserves its intrinsic
...
@@ -5443,6 +5447,7 @@ class RawImage extends LeafRenderObjectWidget {
...
@@ -5443,6 +5447,7 @@ class RawImage extends LeafRenderObjectWidget {
assert
((!
matchTextDirection
&&
alignment
is
Alignment
)
||
debugCheckHasDirectionality
(
context
));
assert
((!
matchTextDirection
&&
alignment
is
Alignment
)
||
debugCheckHasDirectionality
(
context
));
return
RenderImage
(
return
RenderImage
(
image:
image
,
image:
image
,
debugImageLabel:
debugImageLabel
,
width:
width
,
width:
width
,
height:
height
,
height:
height
,
scale:
scale
,
scale:
scale
,
...
@@ -5464,6 +5469,7 @@ class RawImage extends LeafRenderObjectWidget {
...
@@ -5464,6 +5469,7 @@ class RawImage extends LeafRenderObjectWidget {
void
updateRenderObject
(
BuildContext
context
,
RenderImage
renderObject
)
{
void
updateRenderObject
(
BuildContext
context
,
RenderImage
renderObject
)
{
renderObject
renderObject
..
image
=
image
..
image
=
image
..
debugImageLabel
=
debugImageLabel
..
width
=
width
..
width
=
width
..
height
=
height
..
height
=
height
..
scale
=
scale
..
scale
=
scale
...
...
packages/flutter/lib/src/widgets/image.dart
View file @
06d0cd51
...
@@ -1205,6 +1205,7 @@ class _ImageState extends State<Image> with WidgetsBindingObserver {
...
@@ -1205,6 +1205,7 @@ class _ImageState extends State<Image> with WidgetsBindingObserver {
Widget
result
=
RawImage
(
Widget
result
=
RawImage
(
image:
_imageInfo
?.
image
,
image:
_imageInfo
?.
image
,
debugImageLabel:
_imageInfo
?.
debugLabel
,
width:
widget
.
width
,
width:
widget
.
width
,
height:
widget
.
height
,
height:
widget
.
height
,
scale:
_imageInfo
?.
scale
??
1.0
,
scale:
_imageInfo
?.
scale
??
1.0
,
...
...
packages/flutter/test/painting/image_provider_network_image_test.dart
View file @
06d0cd51
...
@@ -8,6 +8,7 @@ import 'dart:async';
...
@@ -8,6 +8,7 @@ import 'dart:async';
import
'dart:io'
;
import
'dart:io'
;
import
'dart:math'
as
math
;
import
'dart:math'
as
math
;
import
'dart:typed_data'
;
import
'dart:typed_data'
;
import
'dart:ui'
show
Codec
,
FrameInfo
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/painting.dart'
;
import
'package:flutter/painting.dart'
;
...
@@ -219,8 +220,68 @@ void main() {
...
@@ -219,8 +220,68 @@ void main() {
debugNetworkImageHttpClientProvider
=
null
;
debugNetworkImageHttpClientProvider
=
null
;
},
skip:
isBrowser
);
// Browser does not resolve images this way.
},
skip:
isBrowser
);
// Browser does not resolve images this way.
Future
<
Codec
>
_decoder
(
Uint8List
bytes
,
{
int
cacheWidth
,
int
cacheHeight
,
bool
allowUpscaling
})
async
{
return
FakeCodec
();
}
test
(
'Network image sets tag'
,
()
async
{
const
String
url
=
'http://test.png'
;
const
int
chunkSize
=
8
;
final
List
<
Uint8List
>
chunks
=
<
Uint8List
>[
for
(
int
offset
=
0
;
offset
<
kTransparentImage
.
length
;
offset
+=
chunkSize
)
Uint8List
.
fromList
(
kTransparentImage
.
skip
(
offset
).
take
(
chunkSize
).
toList
()),
];
final
_MockHttpClientRequest
request
=
_MockHttpClientRequest
();
final
_MockHttpClientResponse
response
=
_MockHttpClientResponse
();
when
(
httpClient
.
getUrl
(
any
)).
thenAnswer
((
_
)
=>
Future
<
HttpClientRequest
>.
value
(
request
));
when
(
request
.
close
()).
thenAnswer
((
_
)
=>
Future
<
HttpClientResponse
>.
value
(
response
));
when
(
response
.
statusCode
).
thenReturn
(
HttpStatus
.
ok
);
when
(
response
.
contentLength
).
thenReturn
(
kTransparentImage
.
length
);
when
(
response
.
listen
(
any
,
onDone:
anyNamed
(
'onDone'
),
onError:
anyNamed
(
'onError'
),
cancelOnError:
anyNamed
(
'cancelOnError'
),
)).
thenAnswer
((
Invocation
invocation
)
{
final
void
Function
(
List
<
int
>)
onData
=
invocation
.
positionalArguments
[
0
]
as
void
Function
(
List
<
int
>);
final
void
Function
(
Object
)
onError
=
invocation
.
namedArguments
[
#onError
]
as
void
Function
(
Object
);
final
VoidCallback
onDone
=
invocation
.
namedArguments
[
#onDone
]
as
VoidCallback
;
final
bool
cancelOnError
=
invocation
.
namedArguments
[
#cancelOnError
]
as
bool
;
return
Stream
<
Uint8List
>.
fromIterable
(
chunks
).
listen
(
onData
,
onDone:
onDone
,
onError:
onError
,
cancelOnError:
cancelOnError
,
);
});
const
NetworkImage
provider
=
NetworkImage
(
url
);
final
MultiFrameImageStreamCompleter
completer
=
provider
.
load
(
provider
,
_decoder
)
as
MultiFrameImageStreamCompleter
;
expect
(
completer
.
debugLabel
,
url
);
});
}
}
class
_MockHttpClient
extends
Mock
implements
HttpClient
{}
class
_MockHttpClient
extends
Mock
implements
HttpClient
{}
class
_MockHttpClientRequest
extends
Mock
implements
HttpClientRequest
{}
class
_MockHttpClientRequest
extends
Mock
implements
HttpClientRequest
{}
class
_MockHttpClientResponse
extends
Mock
implements
HttpClientResponse
{}
class
_MockHttpClientResponse
extends
Mock
implements
HttpClientResponse
{}
class
FakeCodec
implements
Codec
{
@override
void
dispose
()
{}
@override
int
get
frameCount
=>
throw
UnimplementedError
();
@override
Future
<
FrameInfo
>
getNextFrame
()
{
throw
UnimplementedError
();
}
@override
int
get
repetitionCount
=>
throw
UnimplementedError
();
}
packages/flutter/test/painting/image_provider_test.dart
View file @
06d0cd51
...
@@ -6,13 +6,17 @@
...
@@ -6,13 +6,17 @@
import
'dart:async'
;
import
'dart:async'
;
import
'dart:io'
;
import
'dart:io'
;
import
'dart:typed_data'
;
import
'dart:ui'
;
import
'package:file/memory.dart'
;
import
'package:file/memory.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/painting.dart'
;
import
'package:flutter/painting.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'../rendering/rendering_tester.dart'
;
import
'../rendering/rendering_tester.dart'
;
import
'image_data.dart'
;
import
'mocks_for_image_cache.dart'
;
import
'mocks_for_image_cache.dart'
;
void
main
(
)
{
void
main
(
)
{
...
@@ -136,4 +140,70 @@ void main() {
...
@@ -136,4 +140,70 @@ void main() {
expect
(
await
error
.
future
,
isStateError
);
expect
(
await
error
.
future
,
isStateError
);
});
});
Future
<
Codec
>
_decoder
(
Uint8List
bytes
,
{
int
cacheWidth
,
int
cacheHeight
,
bool
allowUpscaling
})
async
{
return
FakeCodec
();
}
test
(
'File image sets tag'
,
()
async
{
final
MemoryFileSystem
fs
=
MemoryFileSystem
();
final
File
file
=
fs
.
file
(
'/blue.png'
)..
createSync
(
recursive:
true
)..
writeAsBytesSync
(
kBlueRectPng
);
final
FileImage
provider
=
FileImage
(
file
);
final
MultiFrameImageStreamCompleter
completer
=
provider
.
load
(
provider
,
_decoder
)
as
MultiFrameImageStreamCompleter
;
expect
(
completer
.
debugLabel
,
file
.
path
);
});
test
(
'Memory image sets tag'
,
()
async
{
final
Uint8List
bytes
=
Uint8List
.
fromList
(
kBlueRectPng
);
final
MemoryImage
provider
=
MemoryImage
(
bytes
);
final
MultiFrameImageStreamCompleter
completer
=
provider
.
load
(
provider
,
_decoder
)
as
MultiFrameImageStreamCompleter
;
expect
(
completer
.
debugLabel
,
'MemoryImage(
${describeIdentity(bytes)}
)'
);
});
test
(
'Asset image sets tag'
,
()
async
{
const
String
asset
=
'images/blue.png'
;
final
ExactAssetImage
provider
=
ExactAssetImage
(
asset
,
bundle:
_TestAssetBundle
());
final
AssetBundleImageKey
key
=
await
provider
.
obtainKey
(
ImageConfiguration
.
empty
);
final
MultiFrameImageStreamCompleter
completer
=
provider
.
load
(
key
,
_decoder
)
as
MultiFrameImageStreamCompleter
;
expect
(
completer
.
debugLabel
,
asset
);
});
test
(
'Resize image sets tag'
,
()
async
{
final
Uint8List
bytes
=
Uint8List
.
fromList
(
kBlueRectPng
);
final
ResizeImage
provider
=
ResizeImage
(
MemoryImage
(
bytes
),
width:
40
,
height:
40
);
final
MultiFrameImageStreamCompleter
completer
=
provider
.
load
(
await
provider
.
obtainKey
(
ImageConfiguration
.
empty
),
_decoder
,
)
as
MultiFrameImageStreamCompleter
;
expect
(
completer
.
debugLabel
,
'MemoryImage(
${describeIdentity(bytes)}
) - Resized(40×40)'
);
});
}
class
FakeCodec
implements
Codec
{
@override
void
dispose
()
{}
@override
int
get
frameCount
=>
throw
UnimplementedError
();
@override
Future
<
FrameInfo
>
getNextFrame
()
{
throw
UnimplementedError
();
}
@override
int
get
repetitionCount
=>
throw
UnimplementedError
();
}
class
_TestAssetBundle
extends
CachingAssetBundle
{
@override
Future
<
ByteData
>
load
(
String
key
)
async
{
return
Uint8List
.
fromList
(
kBlueRectPng
).
buffer
.
asByteData
();
}
}
}
packages/flutter/test/painting/mocks_for_image_cache.dart
View file @
06d0cd51
...
@@ -13,7 +13,7 @@ import 'package:flutter/foundation.dart';
...
@@ -13,7 +13,7 @@ import 'package:flutter/foundation.dart';
import
'package:flutter/painting.dart'
;
import
'package:flutter/painting.dart'
;
class
TestImageInfo
implements
ImageInfo
{
class
TestImageInfo
implements
ImageInfo
{
const
TestImageInfo
(
this
.
value
,
{
this
.
image
,
this
.
scale
=
1.0
});
const
TestImageInfo
(
this
.
value
,
{
this
.
image
,
this
.
scale
=
1.0
,
this
.
debugLabel
});
@override
@override
final
ui
.
Image
image
;
final
ui
.
Image
image
;
...
@@ -21,6 +21,9 @@ class TestImageInfo implements ImageInfo {
...
@@ -21,6 +21,9 @@ class TestImageInfo implements ImageInfo {
@override
@override
final
double
scale
;
final
double
scale
;
@override
final
String
debugLabel
;
final
int
value
;
final
int
value
;
@override
@override
...
...
packages/flutter/test/painting/paint_image_test.dart
View file @
06d0cd51
...
@@ -8,10 +8,9 @@ import 'dart:async';
...
@@ -8,10 +8,9 @@ import 'dart:async';
import
'dart:typed_data'
;
import
'dart:typed_data'
;
import
'dart:ui'
as
ui
;
import
'dart:ui'
as
ui
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/painting.dart'
;
import
'package:flutter/painting.dart'
;
import
'../flutter_test_alternative.dart'
;
class
TestImage
implements
ui
.
Image
{
class
TestImage
implements
ui
.
Image
{
TestImage
({
this
.
width
,
this
.
height
});
TestImage
({
this
.
width
,
this
.
height
});
...
@@ -40,6 +39,10 @@ class TestCanvas implements Canvas {
...
@@ -40,6 +39,10 @@ class TestCanvas implements Canvas {
}
}
void
main
(
)
{
void
main
(
)
{
setUp
(()
{
debugFlushLastFrameImageSizeInfo
();
});
test
(
'Cover and align'
,
()
{
test
(
'Cover and align'
,
()
{
final
TestImage
image
=
TestImage
(
width:
300
,
height:
300
);
final
TestImage
image
=
TestImage
(
width:
300
,
height:
300
);
final
TestCanvas
canvas
=
TestCanvas
();
final
TestCanvas
canvas
=
TestCanvas
();
...
@@ -61,5 +64,114 @@ void main() {
...
@@ -61,5 +64,114 @@ void main() {
expect
(
command
.
positionalArguments
[
2
],
equals
(
const
Rect
.
fromLTWH
(
50.0
,
75.0
,
200.0
,
100.0
)));
expect
(
command
.
positionalArguments
[
2
],
equals
(
const
Rect
.
fromLTWH
(
50.0
,
75.0
,
200.0
,
100.0
)));
});
});
testWidgets
(
'Reports Image painting'
,
(
WidgetTester
tester
)
async
{
ImageSizeInfo
imageSizeInfo
;
int
count
=
0
;
debugOnPaintImage
=
(
ImageSizeInfo
info
)
{
count
+=
1
;
imageSizeInfo
=
info
;
};
final
TestImage
image
=
TestImage
(
width:
300
,
height:
300
);
final
TestCanvas
canvas
=
TestCanvas
();
paintImage
(
canvas:
canvas
,
rect:
const
Rect
.
fromLTWH
(
50.0
,
75.0
,
200.0
,
100.0
),
image:
image
,
debugImageLabel:
'test.png'
,
);
expect
(
count
,
1
);
expect
(
imageSizeInfo
,
isNotNull
);
expect
(
imageSizeInfo
.
source
,
'test.png'
);
expect
(
imageSizeInfo
.
imageSize
,
const
Size
(
300
,
300
));
expect
(
imageSizeInfo
.
displaySize
,
const
Size
(
200
,
100
));
// Make sure that we don't report an identical image size info if we
// redraw in the next frame.
tester
.
binding
.
scheduleForcedFrame
();
await
tester
.
pump
();
paintImage
(
canvas:
canvas
,
rect:
const
Rect
.
fromLTWH
(
50.0
,
75.0
,
200.0
,
100.0
),
image:
image
,
debugImageLabel:
'test.png'
,
);
expect
(
count
,
1
);
debugOnPaintImage
=
null
;
});
testWidgets
(
'Reports Image painting - change per frame'
,
(
WidgetTester
tester
)
async
{
ImageSizeInfo
imageSizeInfo
;
int
count
=
0
;
debugOnPaintImage
=
(
ImageSizeInfo
info
)
{
count
+=
1
;
imageSizeInfo
=
info
;
};
final
TestImage
image
=
TestImage
(
width:
300
,
height:
300
);
final
TestCanvas
canvas
=
TestCanvas
();
paintImage
(
canvas:
canvas
,
rect:
const
Rect
.
fromLTWH
(
50.0
,
75.0
,
200.0
,
100.0
),
image:
image
,
debugImageLabel:
'test.png'
,
);
expect
(
count
,
1
);
expect
(
imageSizeInfo
,
isNotNull
);
expect
(
imageSizeInfo
.
source
,
'test.png'
);
expect
(
imageSizeInfo
.
imageSize
,
const
Size
(
300
,
300
));
expect
(
imageSizeInfo
.
displaySize
,
const
Size
(
200
,
100
));
// Make sure that we don't report an identical image size info if we
// redraw in the next frame.
tester
.
binding
.
scheduleForcedFrame
();
await
tester
.
pump
();
paintImage
(
canvas:
canvas
,
rect:
const
Rect
.
fromLTWH
(
50.0
,
75.0
,
200.0
,
150.0
),
image:
image
,
debugImageLabel:
'test.png'
,
);
expect
(
count
,
2
);
expect
(
imageSizeInfo
,
isNotNull
);
expect
(
imageSizeInfo
.
source
,
'test.png'
);
expect
(
imageSizeInfo
.
imageSize
,
const
Size
(
300
,
300
));
expect
(
imageSizeInfo
.
displaySize
,
const
Size
(
200
,
150
));
debugOnPaintImage
=
null
;
});
testWidgets
(
'Reports Image painting - no debug label'
,
(
WidgetTester
tester
)
async
{
ImageSizeInfo
imageSizeInfo
;
int
count
=
0
;
debugOnPaintImage
=
(
ImageSizeInfo
info
)
{
count
+=
1
;
imageSizeInfo
=
info
;
};
final
TestImage
image
=
TestImage
(
width:
300
,
height:
200
);
final
TestCanvas
canvas
=
TestCanvas
();
paintImage
(
canvas:
canvas
,
rect:
const
Rect
.
fromLTWH
(
50.0
,
75.0
,
200.0
,
100.0
),
image:
image
,
);
expect
(
count
,
1
);
expect
(
imageSizeInfo
,
isNotNull
);
expect
(
imageSizeInfo
.
source
,
'<Unknown Image(300×200)>'
);
expect
(
imageSizeInfo
.
imageSize
,
const
Size
(
300
,
200
));
expect
(
imageSizeInfo
.
displaySize
,
const
Size
(
200
,
100
));
debugOnPaintImage
=
null
;
});
// See also the DecorationImage tests in: decoration_test.dart
// See also the DecorationImage tests in: decoration_test.dart
}
}
packages/flutter/test/widgets/image_test.dart
View file @
06d0cd51
...
@@ -1731,6 +1731,47 @@ void main() {
...
@@ -1731,6 +1731,47 @@ void main() {
// See https://github.com/flutter/flutter/issues/54292.
// See https://github.com/flutter/flutter/issues/54292.
skip:
kIsWeb
,
skip:
kIsWeb
,
);
);
testWidgets
(
'Reports image size when painted'
,
(
WidgetTester
tester
)
async
{
ImageSizeInfo
imageSizeInfo
;
int
count
=
0
;
debugOnPaintImage
=
(
ImageSizeInfo
info
)
{
count
+=
1
;
imageSizeInfo
=
info
;
};
final
ui
.
Image
image
=
await
tester
.
runAsync
(()
=>
createTestImage
(
kBlueRectPng
));
final
TestImageStreamCompleter
streamCompleter
=
TestImageStreamCompleter
(
ImageInfo
(
image:
image
,
scale:
1.0
,
debugLabel:
'test.png'
,
),
);
final
TestImageProvider
imageProvider
=
TestImageProvider
(
streamCompleter:
streamCompleter
);
await
tester
.
pumpWidget
(
Center
(
child:
SizedBox
(
height:
50
,
width:
50
,
child:
Image
(
image:
imageProvider
),
),
),
);
expect
(
count
,
1
);
expect
(
imageSizeInfo
,
const
ImageSizeInfo
(
source
:
'test.png'
,
imageSize:
Size
(
100
,
100
),
displaySize:
Size
(
50
,
50
),
),
);
debugOnPaintImage
=
null
;
});
}
}
class
ImagePainter
extends
CustomPainter
{
class
ImagePainter
extends
CustomPainter
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment