Skip to content

Commit

Permalink
Return raw (unencoded) bytes in Image.toByteData() (flutter#5008)
Browse files Browse the repository at this point in the history
Building image encoding into the engine bloated the
binary size. This change will return raw bytes, and
callers who use this functionality can take on the
dependency on image encoding in their apps (via a
Dart package or a platform plugin).

Fixes flutter/flutter#16537
  • Loading branch information
tvolkert authored Apr 16, 2018
1 parent d812a61 commit 4eaf2c2
Show file tree
Hide file tree
Showing 11 changed files with 126 additions and 168 deletions.
88 changes: 7 additions & 81 deletions lib/ui/painting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1196,80 +1196,6 @@ class Paint {
}
}

/// An encoding format to use with the [Image.toByteData].
class EncodingFormat {
/// 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.
const EncodingFormat.png()
: _format = _pngFormat,
_quality = 0;

/// JPEG format.
///
/// This format, strictly speaking called JFIF, is a lossy compression
/// graphics format that can handle images up to 65,535 pixels in either
/// dimension. The [quality] metric is a value in the range 0 to 100 that
/// controls the compression ratio. Values in the range of about 50 to 90 are
/// somewhat reasonable; values above 95 increase the file size with little
/// noticeable improvement to the quality, values below 50 drop the quality
/// substantially.
///
/// This format is well suited for photographs. It is very poorly suited for
/// images with hard edges or text. It does not support transparency.
///
/// JPEG images normally use the `.jpeg` file extension and the `image/jpeg`
/// MIME type.
///
/// See also:
///
/// * <https://en.wikipedia.org/wiki/JPEG>, the Wikipedia page on JPEG.
const EncodingFormat.jpeg({int quality = 80})
: _format = _jpegFormat,
_quality = quality;

/// WebP format.
///
/// The WebP format supports both lossy and lossless compression; however, the
/// [Image.toByteData] method always uses lossy compression when [webp] is
/// specified. The [quality] metric is a value in the range 0 to 100 that
/// controls the compression ratio; higher values result in better quality but
/// larger file sizes, and vice versa. WebP images are limited to 16,383
/// pixels in each direction (width and height).
///
/// WebP images normally use the `.webp` file extension and the `image/webp`
/// MIME type.
///
/// See also:
///
/// * <https://en.wikipedia.org/wiki/WebP>, the Wikipedia page on WebP.
const EncodingFormat.webp({int quality = 80})
: _format = _webpFormat,
_quality = quality;

final int _format;
final int _quality;

// Be conservative with the formats we expose. It is easy to add new formats
// in future but difficult to remove.
// These values must be kept in sync with the logic in ToSkEncodedImageFormat.
static const int _jpegFormat = 0;
static const int _pngFormat = 1;
static const int _webpFormat = 2;
}

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

/// Converts the [Image] object into a byte array.
///
/// The [format] is encoding format to be used.
/// The image bytes will be RGBA form, 8 bits per channel, row-primary.
///
/// Returns a future which complete with the binary image data (e.g a PNG or JPEG binary data) or
/// an error if encoding fails.
Future<ByteData> toByteData({EncodingFormat format: const EncodingFormat.jpeg()}) {
/// Returns a future that completes with the binary image data or an error
/// if encoding fails.
Future<ByteData> toByteData() {
return _futurize((_Callback<ByteData> callback) {
return _toByteData(format._format, format._quality, (Uint8List encoded) {
callback(encoded.buffer.asByteData());
return _toByteData((Uint8List encoded) {
callback(encoded?.buffer?.asByteData());
});
});
}

/// Returns an error message on failure, null on success.
String _toByteData(int format, int quality, _Callback<Uint8List> callback) native 'Image_toByteData';
String _toByteData(_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
6 changes: 2 additions & 4 deletions lib/ui/painting/image.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,8 @@ CanvasImage::CanvasImage() = default;

CanvasImage::~CanvasImage() = default;

Dart_Handle CanvasImage::toByteData(int format,
int quality,
Dart_Handle callback) {
return EncodeImage(this, format, quality, callback);
Dart_Handle CanvasImage::toByteData(Dart_Handle callback) {
return GetImageBytes(this, 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(int format, int quality, Dart_Handle callback);
Dart_Handle toByteData(Dart_Handle callback);

void dispose();

Expand Down
91 changes: 49 additions & 42 deletions lib/ui/painting/image_encoding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <utility>

#include "flutter/common/task_runners.h"
#include "flutter/glue/trace_event.h"
#include "flutter/lib/ui/painting/image.h"
#include "flutter/lib/ui/ui_dart_state.h"
#include "lib/fxl/build_config.h"
Expand All @@ -17,6 +18,7 @@
#include "lib/tonic/typed_data/uint8_list.h"
#include "third_party/skia/include/core/SkEncodedImageFormat.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkSurface.h"

using tonic::DartInvoke;
using tonic::DartPersistentValue;
Expand All @@ -41,77 +43,82 @@ void InvokeDataCallback(std::unique_ptr<DartPersistentValue> callback,
}
}

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

if (image == nullptr) {
return nullptr;
}
return image->encodeToData(format, quality);

// Copy the GPU image snapshot into CPU memory.
auto cpu_snapshot = image->makeRasterImage();
if (!cpu_snapshot) {
FXL_LOG(ERROR) << "Pixel copy failed.";
return nullptr;
}

SkPixmap pixmap;
if (!cpu_snapshot->peekPixels(&pixmap)) {
FXL_LOG(ERROR) << "Pixel address is not available.";
return nullptr;
}

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

// Convert the pixel data to N32 to adhere to our API contract.
const auto image_info = SkImageInfo::MakeN32Premul(image->width(),
image->height());
auto surface = SkSurface::MakeRaster(image_info);
surface->writePixels(pixmap, 0, 0);
if (!surface->peekPixels(&pixmap)) {
FXL_LOG(ERROR) << "Pixel address is not available.";
return nullptr;
}
ASSERT(pixmap.colorType() == kRGBA_8888_SkColorType);

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

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

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

SkEncodedImageFormat ToSkEncodedImageFormat(int format) {
// Map the formats exposed in flutter to formats supported in Skia.
// See:
// https://github.com/google/skia/blob/master/include/core/SkEncodedImageFormat.h
switch (format) {
case 0:
return SkEncodedImageFormat::kJPEG;
case 1:
return SkEncodedImageFormat::kPNG;
case 2:
return SkEncodedImageFormat::kWEBP;
default:
/* NOTREACHED */
return SkEncodedImageFormat::kWEBP;
}
}

} // namespace

Dart_Handle EncodeImage(CanvasImage* canvas_image,
int format,
int quality,
Dart_Handle callback_handle) {
Dart_Handle GetImageBytes(CanvasImage* canvas_image,
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.");

SkEncodedImageFormat image_format = ToSkEncodedImageFormat(format);

if (quality > 100)
quality = 100;
if (quality < 0)
quality = 0;

auto callback = std::make_unique<DartPersistentValue>(
tonic::DartState::Current(), callback_handle);
sk_sp<SkImage> image = canvas_image->image();

const auto& task_runners = UIDartState::Current()->GetTaskRunners();

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

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

class CanvasImage;

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

} // namespace blink

Expand Down
Loading

0 comments on commit 4eaf2c2

Please sign in to comment.