Skip to content

Commit

Permalink
Support different encodings in Image.toByteData() (flutter#5060)
Browse files Browse the repository at this point in the history
  • Loading branch information
tvolkert authored Apr 21, 2018
1 parent 2320608 commit cb3376c
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 86 deletions.
53 changes: 43 additions & 10 deletions lib/ui/painting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1196,6 +1196,38 @@ class Paint {
}
}

/// The format in which image bytes should be returned when using
/// [Image.toByteData].
enum ImageByteFormat {
/// Raw RGBA format.
///
/// Unencoded bytes, in RGBA row-primary form, 8 bits per channel.
rawRgba,

/// Raw unmodified format.
///
/// Unencoded bytes, in the image's existing format. For example, a grayscale
/// image may use a single 8-bit channel for each pixel.
rawUnmodified,

/// PNG format.
///
/// A loss-less compression format for images. This format is well suited for
/// images with hard edges, such as screenshots or sprites, and images with
/// text. Transparency is supported. The PNG format supports images up to
/// 2,147,483,647 pixels in either dimension, though in practice available
/// memory provides a more immediate limitation on maximum image size.
///
/// PNG images normally use the `.png` file extension and the `image/png` MIME
/// type.
///
/// See also:
///
/// * <https://en.wikipedia.org/wiki/Portable_Network_Graphics>, the Wikipedia page on PNG.
/// * <https://tools.ietf.org/rfc/rfc2083.txt>, the PNG standard.
png,
}

/// Opaque handle to raw decoded image data (pixels).
///
/// To obtain an [Image] object, use [instantiateImageCodec].
Expand All @@ -1217,20 +1249,21 @@ class Image extends NativeFieldWrapperClass2 {

/// Converts the [Image] object into a byte array.
///
/// The image bytes will be RGBA form, 8 bits per channel, row-primary.
/// The [format] argument specifies the format in which the bytes will be
/// returned.
///
/// Returns a future that completes with the binary image data or an error
/// if encoding fails.
Future<ByteData> toByteData() {
Future<ByteData> toByteData({ImageByteFormat format: ImageByteFormat.rawRgba}) {
return _futurize((_Callback<ByteData> callback) {
return _toByteData((Uint8List encoded) {
return _toByteData(format.index, (Uint8List encoded) {
callback(encoded?.buffer?.asByteData());
});
});
}

/// Returns an error message on failure, null on success.
String _toByteData(_Callback<Uint8List> callback) native 'Image_toByteData';
String _toByteData(int format, _Callback<Uint8List> callback) native 'Image_toByteData';

/// Release the resources used by this object. The object is no longer usable
/// after this method is called.
Expand Down Expand Up @@ -1530,8 +1563,8 @@ class Path extends NativeFieldWrapperClass2 {

/// Adds a new subpath that consists of a curve that forms the
/// ellipse that fills the given rectangle.
///
/// To add a circle, pass an appropriate rectangle as `oval`. [Rect.fromCircle]
///
/// To add a circle, pass an appropriate rectangle as `oval`. [Rect.fromCircle]
/// can be used to easily describe the circle's center [Offset] and radius.
void addOval(Rect oval) {
assert(_rectIsValid(oval));
Expand Down Expand Up @@ -1938,8 +1971,8 @@ class Gradient extends Shader {
/// If `center`, `radius`, `colors`, or `tileMode` are null, or if `colors` or
/// `colorStops` contain null values, this constructor will throw a
/// [NoSuchMethodError].
///
/// If `matrix4` is provided, the gradient fill will be transformed by the
///
/// If `matrix4` is provided, the gradient fill will be transformed by the
/// specified 4x4 matrix relative to the local coordinate system. `matrix4` must
/// be a column-major matrix packed into a list of 16 values.
Gradient.radial(
Expand Down Expand Up @@ -2917,11 +2950,11 @@ typedef String _Callbacker<T>(_Callback<T> callback);
///
/// ```dart
/// typedef void IntCallback(int result);
///
///
/// String _doSomethingAndCallback(IntCallback callback) {
/// new Timer(new Duration(seconds: 1), () { callback(1); });
/// }
///
///
/// Future<int> doSomething() {
/// return _futurize(_doSomethingAndCallback);
/// }
Expand Down
4 changes: 2 additions & 2 deletions lib/ui/painting/image.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ CanvasImage::CanvasImage() = default;

CanvasImage::~CanvasImage() = default;

Dart_Handle CanvasImage::toByteData(Dart_Handle callback) {
return GetImageBytes(this, callback);
Dart_Handle CanvasImage::toByteData(int format, Dart_Handle callback) {
return EncodeImage(this, format, callback);
}

void CanvasImage::dispose() {
Expand Down
2 changes: 1 addition & 1 deletion lib/ui/painting/image.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class CanvasImage final : public fxl::RefCountedThreadSafe<CanvasImage>,

int height() { return image_.get()->height(); }

Dart_Handle toByteData(Dart_Handle callback);
Dart_Handle toByteData(int format, Dart_Handle callback);

void dispose();

Expand Down
52 changes: 36 additions & 16 deletions lib/ui/painting/image_encoding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ using tonic::ToDart;
namespace blink {
namespace {

// This must be kept in sync with the enum in painting.dart
enum ImageByteFormat {
kRawRGBA,
kRawUnmodified,
kPNG,
};

void InvokeDataCallback(std::unique_ptr<DartPersistentValue> callback,
sk_sp<SkData> buffer) {
tonic::DartState* dart_state = callback->dart_state().get();
Expand All @@ -47,13 +54,17 @@ void InvokeDataCallback(std::unique_ptr<DartPersistentValue> callback,
}
}

sk_sp<SkData> GetImageBytesAsRGBA(sk_sp<SkImage> image) {
sk_sp<SkData> EncodeImage(sk_sp<SkImage> image, ImageByteFormat format) {
TRACE_EVENT0("flutter", __FUNCTION__);

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

if (format == kPNG) {
return image->encodeToData(SkEncodedImageFormat::kPNG, 0);
}

// Copy the GPU image snapshot into CPU memory.
auto cpu_snapshot = image->makeRasterImage();
if (!cpu_snapshot) {
Expand All @@ -67,6 +78,11 @@ sk_sp<SkData> GetImageBytesAsRGBA(sk_sp<SkImage> image) {
return nullptr;
}

if (format == kRawUnmodified) {
return SkData::MakeWithCopy(pixmap.addr(), pixmap.computeByteSize());
}

ASSERT(format == kRawRGBA);
if (pixmap.colorType() != kRGBA_8888_SkColorType) {
TRACE_EVENT0("flutter", "ConvertToRGBA");

Expand All @@ -80,36 +96,38 @@ sk_sp<SkData> GetImageBytesAsRGBA(sk_sp<SkImage> image) {
return nullptr;
}

const size_t pixmap_size = pixmap.computeByteSize();
return SkData::MakeWithCopy(pixmap.addr32(), pixmap_size);
return SkData::MakeWithCopy(pixmap.addr32(), pixmap.computeByteSize());
} else {
const size_t pixmap_size = pixmap.computeByteSize();
return SkData::MakeWithCopy(pixmap.addr32(), pixmap_size);
return SkData::MakeWithCopy(pixmap.addr32(), pixmap.computeByteSize());
}
}

void GetImageBytesAndInvokeDataCallback(
void EncodeImageAndInvokeDataCallback(
std::unique_ptr<DartPersistentValue> callback,
sk_sp<SkImage> image,
fxl::RefPtr<fxl::TaskRunner> ui_task_runner) {
sk_sp<SkData> buffer = GetImageBytesAsRGBA(std::move(image));
fxl::RefPtr<fxl::TaskRunner> ui_task_runner,
ImageByteFormat format) {
sk_sp<SkData> encoded = EncodeImage(std::move(image), format);

ui_task_runner->PostTask(
fxl::MakeCopyable([callback = std::move(callback), buffer]() mutable {
InvokeDataCallback(std::move(callback), std::move(buffer));
fxl::MakeCopyable([callback = std::move(callback), encoded]() mutable {
InvokeDataCallback(std::move(callback), std::move(encoded));
}));
}

} // namespace

Dart_Handle GetImageBytes(CanvasImage* canvas_image,
Dart_Handle callback_handle) {
Dart_Handle EncodeImage(CanvasImage* canvas_image,
int format,
Dart_Handle callback_handle) {
if (!canvas_image)
return ToDart("encode called with non-genuine Image.");

if (!Dart_IsClosure(callback_handle))
return ToDart("Callback must be a function.");

ImageByteFormat image_format = static_cast<ImageByteFormat>(format);

auto callback = std::make_unique<DartPersistentValue>(
tonic::DartState::Current(), callback_handle);
sk_sp<SkImage> image = canvas_image->image();
Expand All @@ -118,10 +136,12 @@ Dart_Handle GetImageBytes(CanvasImage* canvas_image,

task_runners.GetIOTaskRunner()->PostTask(fxl::MakeCopyable(
[callback = std::move(callback), image,
ui_task_runner = task_runners.GetUITaskRunner()]() mutable {
GetImageBytesAndInvokeDataCallback(std::move(callback),
std::move(image),
std::move(ui_task_runner));
ui_task_runner = task_runners.GetUITaskRunner(),
image_format]() mutable {
EncodeImageAndInvokeDataCallback(std::move(callback),
std::move(image),
std::move(ui_task_runner),
image_format);
}));

return Dart_Null();
Expand Down
5 changes: 3 additions & 2 deletions lib/ui/painting/image_encoding.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ namespace blink {

class CanvasImage;

Dart_Handle GetImageBytes(CanvasImage* canvas_image,
Dart_Handle callback_handle);
Dart_Handle EncodeImage(CanvasImage* canvas_image,
int format,
Dart_Handle callback_handle);

} // namespace blink

Expand Down
Loading

0 comments on commit cb3376c

Please sign in to comment.