Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 3ed9f12

Browse files
authored
Reland: "Added wide-gamut color support for ui.Image.toByteData and ui.Image.colorSpace" (#40312)
Reland: "Added wide-gamut color support for `ui.Image.toByteData` and `ui.Image.colorSpace`"
1 parent 9fc3246 commit 3ed9f12

File tree

13 files changed

+163
-8
lines changed

13 files changed

+163
-8
lines changed

lib/ui/dart_ui.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ typedef CanvasPath Path;
190190
V(Image, width, 1) \
191191
V(Image, height, 1) \
192192
V(Image, toByteData, 3) \
193+
V(Image, colorSpace, 1) \
193194
V(ImageDescriptor, bytesPerPixel, 1) \
194195
V(ImageDescriptor, dispose, 1) \
195196
V(ImageDescriptor, height, 1) \

lib/ui/painting.dart

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1568,6 +1568,31 @@ class Paint {
15681568
}
15691569
}
15701570

1571+
/// The color space describes the colors that are available to an [Image].
1572+
///
1573+
/// This value can help decide which [ImageByteFormat] to use with
1574+
/// [Image.toByteData]. Images that are in the [extendedSRGB] color space
1575+
/// should use something like [ImageByteFormat.rawExtendedRgba128] so that
1576+
/// colors outside of the sRGB gamut aren't lost.
1577+
///
1578+
/// This is also the result of [Image.colorSpace].
1579+
///
1580+
/// See also: https://en.wikipedia.org/wiki/Color_space
1581+
enum ColorSpace {
1582+
/// The sRGB color space.
1583+
///
1584+
/// You may know this as the standard color space for the web or the color
1585+
/// space of non-wide-gamut Flutter apps.
1586+
///
1587+
/// See also: https://en.wikipedia.org/wiki/SRGB
1588+
sRGB,
1589+
/// A color space that is backwards compatible with sRGB but can represent
1590+
/// colors outside of that gamut with values outside of [0..1]. In order to
1591+
/// see the extended values an [ImageByteFormat] like
1592+
/// [ImageByteFormat.rawExtendedRgba128] must be used.
1593+
extendedSRGB,
1594+
}
1595+
15711596
/// The format in which image bytes should be returned when using
15721597
/// [Image.toByteData].
15731598
// We do not expect to add more encoding formats to the ImageByteFormat enum,
@@ -1591,6 +1616,31 @@ enum ImageByteFormat {
15911616
/// image may use a single 8-bit channel for each pixel.
15921617
rawUnmodified,
15931618

1619+
/// Raw extended range RGBA format.
1620+
///
1621+
/// Unencoded bytes, in RGBA row-primary form with straight alpha, 32 bit
1622+
/// float (IEEE 754 binary32) per channel.
1623+
///
1624+
/// Example usage:
1625+
///
1626+
/// ```dart
1627+
/// import 'dart:ui' as ui;
1628+
/// import 'dart:typed_data';
1629+
///
1630+
/// Future<Map<String, double>> getFirstPixel(ui.Image image) async {
1631+
/// final ByteData data =
1632+
/// (await image.toByteData(format: ui.ImageByteFormat.rawExtendedRgba128))!;
1633+
/// final Float32List floats = Float32List.view(data.buffer);
1634+
/// return <String, double>{
1635+
/// 'r': floats[0],
1636+
/// 'g': floats[1],
1637+
/// 'b': floats[2],
1638+
/// 'a': floats[3],
1639+
/// };
1640+
/// }
1641+
/// ```
1642+
rawExtendedRgba128,
1643+
15941644
/// PNG format.
15951645
///
15961646
/// A loss-less compression format for images. This format is well suited for
@@ -1726,6 +1776,10 @@ class Image {
17261776
/// The [format] argument specifies the format in which the bytes will be
17271777
/// returned.
17281778
///
1779+
/// Using [ImageByteFormat.rawRgba] on an image in the color space
1780+
/// [ColorSpace.extendedSRGB] will result in the gamut being squished to fit
1781+
/// into the sRGB gamut, resulting in the loss of wide-gamut colors.
1782+
///
17291783
/// Returns a future that completes with the binary image data or an error
17301784
/// if encoding fails.
17311785
// We do not expect to add more encoding formats to the ImageByteFormat enum,
@@ -1737,6 +1791,29 @@ class Image {
17371791
return _image.toByteData(format: format);
17381792
}
17391793

1794+
/// The color space that is used by the [Image]'s colors.
1795+
///
1796+
/// This value is a consequence of how the [Image] has been created. For
1797+
/// example, loading a PNG that is in the Display P3 color space will result
1798+
/// in a [ColorSpace.extendedSRGB] image.
1799+
///
1800+
/// On rendering backends that don't support wide gamut colors (anything but
1801+
/// iOS impeller), wide gamut images will still report [ColorSpace.sRGB] if
1802+
/// rendering wide gamut colors isn't supported.
1803+
// Note: The docstring will become outdated as new platforms support wide
1804+
// gamut color, please keep it up to date.
1805+
ColorSpace get colorSpace {
1806+
final int colorSpaceValue = _image.colorSpace;
1807+
switch (colorSpaceValue) {
1808+
case 0:
1809+
return ColorSpace.sRGB;
1810+
case 1:
1811+
return ColorSpace.extendedSRGB;
1812+
default:
1813+
throw UnsupportedError('Unrecognized color space: $colorSpaceValue');
1814+
}
1815+
}
1816+
17401817
/// If asserts are enabled, returns the [StackTrace]s of each open handle from
17411818
/// [clone], in creation order.
17421819
///
@@ -1903,6 +1980,9 @@ class _Image extends NativeFieldWrapperClass1 {
19031980

19041981
final Set<Image> _handles = <Image>{};
19051982

1983+
@Native<Int32 Function(Pointer<Void>)>(symbol: 'Image::colorSpace')
1984+
external int get colorSpace;
1985+
19061986
@override
19071987
String toString() => '[$width\u00D7$height]';
19081988
}

lib/ui/painting/image.cc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
#include <algorithm>
88
#include <limits>
99

10+
#if IMPELLER_SUPPORTS_RENDERING
11+
#include "flutter/lib/ui/painting/image_encoding_impeller.h"
12+
#endif
1013
#include "flutter/lib/ui/painting/image_encoding.h"
1114
#include "third_party/tonic/converter/dart_converter.h"
1215
#include "third_party/tonic/dart_args.h"
@@ -35,4 +38,16 @@ void CanvasImage::dispose() {
3538
ClearDartWrapper();
3639
}
3740

41+
int CanvasImage::colorSpace() {
42+
if (image_->skia_image()) {
43+
return ColorSpace::kSRGB;
44+
} else if (image_->impeller_texture()) {
45+
#if IMPELLER_SUPPORTS_RENDERING
46+
return ImageEncodingImpeller::GetColorSpace(image_->impeller_texture());
47+
#endif // IMPELLER_SUPPORTS_RENDERING
48+
}
49+
50+
return -1;
51+
}
52+
3853
} // namespace flutter

lib/ui/painting/image.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414

1515
namespace flutter {
1616

17+
// Must be kept in sync with painting.dart.
18+
enum ColorSpace {
19+
kSRGB,
20+
kExtendedSRGB,
21+
};
22+
1723
class CanvasImage final : public RefCountedDartWrappable<CanvasImage> {
1824
DEFINE_WRAPPERTYPEINFO();
1925
FML_FRIEND_MAKE_REF_COUNTED(CanvasImage);
@@ -37,6 +43,8 @@ class CanvasImage final : public RefCountedDartWrappable<CanvasImage> {
3743

3844
void set_image(sk_sp<DlImage> image) { image_ = image; }
3945

46+
int colorSpace();
47+
4048
private:
4149
CanvasImage();
4250

lib/ui/painting/image_encoding.cc

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ enum ImageByteFormat {
3838
kRawRGBA,
3939
kRawStraightRGBA,
4040
kRawUnmodified,
41+
kRawExtendedRgba128,
4142
kPNG,
4243
};
4344

@@ -123,19 +124,21 @@ sk_sp<SkData> EncodeImage(const sk_sp<SkImage>& raster_image,
123124
return nullptr;
124125
};
125126
return png_image;
126-
} break;
127-
case kRawRGBA: {
127+
}
128+
case kRawRGBA:
128129
return CopyImageByteData(raster_image, kRGBA_8888_SkColorType,
129130
kPremul_SkAlphaType);
130-
} break;
131-
case kRawStraightRGBA: {
131+
132+
case kRawStraightRGBA:
132133
return CopyImageByteData(raster_image, kRGBA_8888_SkColorType,
133134
kUnpremul_SkAlphaType);
134-
} break;
135-
case kRawUnmodified: {
135+
136+
case kRawUnmodified:
136137
return CopyImageByteData(raster_image, raster_image->colorType(),
137138
raster_image->alphaType());
138-
} break;
139+
case kRawExtendedRgba128:
140+
return CopyImageByteData(raster_image, kRGBA_F32_SkColorType,
141+
kUnpremul_SkAlphaType);
139142
}
140143

141144
FML_LOG(ERROR) << "Unknown error encoding image.";

lib/ui/painting/image_encoding_impeller.cc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,4 +163,16 @@ void ImageEncodingImpeller::ConvertImageToRaster(
163163
});
164164
}
165165

166+
int ImageEncodingImpeller::GetColorSpace(
167+
const std::shared_ptr<impeller::Texture>& texture) {
168+
const impeller::TextureDescriptor& desc = texture->GetTextureDescriptor();
169+
switch (desc.format) {
170+
case impeller::PixelFormat::kB10G10R10XR: // intentional_fallthrough
171+
case impeller::PixelFormat::kR16G16B16A16Float:
172+
return ColorSpace::kExtendedSRGB;
173+
default:
174+
return ColorSpace::kSRGB;
175+
}
176+
}
177+
166178
} // namespace flutter

lib/ui/painting/image_encoding_impeller.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ namespace flutter {
1717

1818
class ImageEncodingImpeller {
1919
public:
20+
static int GetColorSpace(const std::shared_ptr<impeller::Texture>& texture);
21+
2022
/// Converts a DlImage to a SkImage.
2123
/// This should be called from the thread that corresponds to
2224
/// `dl_image->owning_context()` when gpu access is guaranteed.

lib/web_ui/lib/painting.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,8 @@ abstract class Image {
351351

352352
List<StackTrace>? debugGetOpenHandleStackTraces() => null;
353353

354+
ColorSpace get colorSpace => ColorSpace.sRGB;
355+
354356
@override
355357
String toString() => '[$width\u00D7$height]';
356358
}
@@ -431,6 +433,11 @@ class ImageFilter {
431433
engine.renderer.composeImageFilters(outer: outer, inner: inner);
432434
}
433435

436+
enum ColorSpace {
437+
sRGB,
438+
extendedSRGB,
439+
}
440+
434441
enum ImageByteFormat {
435442
rawRgba,
436443
rawStraightRgba,

lib/web_ui/lib/src/engine/canvaskit/image.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,9 @@ class CkImage implements ui.Image, StackTraceDebugger {
378378
}
379379
}
380380

381+
@override
382+
ui.ColorSpace get colorSpace => ui.ColorSpace.sRGB;
383+
381384
Future<ByteData> _readPixelsFromSkImage(ui.ImageByteFormat format) {
382385
final SkAlphaType alphaType = format == ui.ImageByteFormat.rawStraightRgba ? canvasKit.AlphaType.Unpremul : canvasKit.AlphaType.Premul;
383386
final ByteData? data = _encodeImage(

lib/web_ui/lib/src/engine/html_image_codec.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,9 @@ class HtmlImage implements ui.Image {
204204
}
205205
}
206206

207+
@override
208+
ui.ColorSpace get colorSpace => ui.ColorSpace.sRGB;
209+
207210
DomHTMLImageElement cloneImageElement() {
208211
if (!_didClone) {
209212
_didClone = true;

0 commit comments

Comments
 (0)