From f7ce00687aa9f45cecb43210d6233716398f5eb5 Mon Sep 17 00:00:00 2001 From: Majid Valipour Date: Fri, 6 Oct 2017 00:24:40 -0400 Subject: [PATCH 01/11] Add basic image encoding with limited format --- lib/ui/BUILD.gn | 2 + lib/ui/dart_ui.cc | 2 + lib/ui/painting.dart | 20 +++++ lib/ui/painting/image_encoding.cc | 136 ++++++++++++++++++++++++++++++ lib/ui/painting/image_encoding.h | 19 +++++ 5 files changed, 179 insertions(+) create mode 100644 lib/ui/painting/image_encoding.cc create mode 100644 lib/ui/painting/image_encoding.h diff --git a/lib/ui/BUILD.gn b/lib/ui/BUILD.gn index 2862a9b68cf96..8b3be72d5973b 100644 --- a/lib/ui/BUILD.gn +++ b/lib/ui/BUILD.gn @@ -24,6 +24,8 @@ source_set("ui") { "painting/gradient.h", "painting/image.cc", "painting/image.h", + "painting/image_encoding.cc", + "painting/image_encoding.h", "painting/image_filter.cc", "painting/image_filter.h", "painting/image_shader.cc", diff --git a/lib/ui/dart_ui.cc b/lib/ui/dart_ui.cc index 59d083a191778..5cc2b07976d46 100644 --- a/lib/ui/dart_ui.cc +++ b/lib/ui/dart_ui.cc @@ -12,6 +12,7 @@ #include "flutter/lib/ui/painting/frame_info.h" #include "flutter/lib/ui/painting/gradient.h" #include "flutter/lib/ui/painting/image.h" +#include "flutter/lib/ui/painting/image_encoding.h" #include "flutter/lib/ui/painting/image_filter.h" #include "flutter/lib/ui/painting/image_shader.h" #include "flutter/lib/ui/painting/path.h" @@ -56,6 +57,7 @@ void DartUI::InitForGlobal() { Codec::RegisterNatives(g_natives); DartRuntimeHooks::RegisterNatives(g_natives); FrameInfo::RegisterNatives(g_natives); + ImageEncoding::RegisterNatives(g_natives); ImageFilter::RegisterNatives(g_natives); ImageShader::RegisterNatives(g_natives); Paragraph::RegisterNatives(g_natives); diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 4b041a27df3eb..f961a99ac4a6b 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -1316,6 +1316,26 @@ Future _decodeImageFromListAsync(Uint8List list, ImageDecoderCallback call callback(frameInfo.image); } +/// The encoding formats supported by the [encodeImage]. +enum EncodedImageFormat { + // Be conservative with the formats we expose. We can increase the list in future but reducing + // it is more difficult. + JPEG, + PNG, + WEBP, +} + +/// Callback signature for [encodeImage]. +typedef void ImageEncoderCallback(Uint8List result); + +/// Convert an [Image] object into a byte array/ +/// +/// [format] is encoding format to be used. +/// +/// [quality] is a value in [0, 100] where 0 corresponds to the lowest quality. +void encodeImage(Image image, EncodedImageFormat format, int quality, ImageEncoderCallback callback) + native "encodeImage"; + /// Determines the winding rule that decides how the interior of a [Path] is /// calculated. /// diff --git a/lib/ui/painting/image_encoding.cc b/lib/ui/painting/image_encoding.cc new file mode 100644 index 0000000000000..27622d3c68c5b --- /dev/null +++ b/lib/ui/painting/image_encoding.cc @@ -0,0 +1,136 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/lib/ui/painting/image_encoding.h" + +#include "flutter/common/threads.h" +#include "flutter/lib/ui/painting/image.h" +#include "flutter/lib/ui/painting/resource_context.h" +#include "lib/fxl/build_config.h" +#include "lib/fxl/functional/make_copyable.h" +#include "lib/tonic/dart_persistent_value.h" +#include "lib/tonic/dart_state.h" +#include "lib/tonic/logging/dart_invoke.h" +#include "lib/tonic/typed_data/uint8_list.h" +#include "third_party/skia/include/core/SkEncodedImageFormat.h" +#include "third_party/skia/include/core/SkImage.h" + +using tonic::DartInvoke; +using tonic::DartPersistentValue; +using tonic::ToDart; + +namespace blink { +namespace { + +void InvokeDataCallback(std::unique_ptr callback, + sk_sp buffer) { + tonic::DartState* dart_state = callback->dart_state().get(); + if (!dart_state) { + return; + } + tonic::DartState::Scope scope(dart_state); + if (!buffer) { + DartInvoke(callback->value(), {Dart_Null()}); + } else { + Dart_Handle dart_data = tonic::DartConverter::ToDart( + buffer->bytes(), buffer->size()); + DartInvoke(callback->value(), {dart_data}); + } +} + +sk_sp EncodeImage(sk_sp image, + SkEncodedImageFormat format, + int quality) { + if (image == nullptr) { + return nullptr; + } + return image->encodeToData(format, quality); +} + +void EncodeImageAndInvokeDataCallback( + std::unique_ptr callback, + sk_sp image, + SkEncodedImageFormat format, + int quality) { + sk_sp encoded = EncodeImage(std::move(image), format, quality); + + Threads::UI()->PostTask( + fxl::MakeCopyable([callback = std::move(callback), encoded]() mutable { + InvokeDataCallback(std::move(callback), std::move(encoded)); + })); +} + +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; + } +} + +void EncodeImage(Dart_NativeArguments args) { + Dart_Handle exception = nullptr; + + CanvasImage* canvas_image = + tonic::DartConverter::FromArguments(args, 0, exception); + + if (exception) { + Dart_ThrowException(exception); + return; + } + if (!canvas_image) + Dart_ThrowException(ToDart("encodeImage called with non-genuine Image.")); + + int format = tonic::DartConverter::FromArguments(args, 1, exception); + if (exception) { + Dart_ThrowException(exception); + return; + } + SkEncodedImageFormat image_format = ToSkEncodedImageFormat(format); + + int quality = tonic::DartConverter::FromArguments(args, 2, exception); + if (exception) { + Dart_ThrowException(exception); + return; + } + if (quality > 100) + quality = 100; + if (quality < 0) + quality = 0; + + Dart_Handle callback_handle = Dart_GetNativeArgument(args, 3); + if (!Dart_IsClosure(callback_handle)) { + Dart_ThrowException(ToDart("Callback must be a function")); + return; + } + + auto callback = std::make_unique( + tonic::DartState::Current(), callback_handle); + sk_sp image = canvas_image->image(); + + Threads::IO()->PostTask(fxl::MakeCopyable( + [callback = std::move(callback), image, image_format, quality]() mutable { + EncodeImageAndInvokeDataCallback(std::move(callback), std::move(image), + image_format, quality); + })); +} + +} // namespace + +void ImageEncoding::RegisterNatives(tonic::DartLibraryNatives* natives) { + natives->Register({ + {"encodeImage", EncodeImage, 4, true}, + }); +} + +} // namespace blink diff --git a/lib/ui/painting/image_encoding.h b/lib/ui/painting/image_encoding.h new file mode 100644 index 0000000000000..b716458131099 --- /dev/null +++ b/lib/ui/painting/image_encoding.h @@ -0,0 +1,19 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_LIB_UI_PAINTING_IMAGE_ENCODING_H_ +#define FLUTTER_LIB_UI_PAINTING_IMAGE_ENCODING_H_ + +#include "lib/tonic/dart_library_natives.h" + +namespace blink { + +class ImageEncoding { + public: + static void RegisterNatives(tonic::DartLibraryNatives* natives); +}; + +} // namespace blink + +#endif // FLUTTER_LIB_UI_PAINTING_IMAGE_ENCODING_H_ From 5472da98e5be69e54db78317f375cfca88cabf0e Mon Sep 17 00:00:00 2001 From: Majid Valipour Date: Thu, 8 Mar 2018 22:20:47 -0500 Subject: [PATCH 02/11] prefer single quote --- lib/ui/painting.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index f961a99ac4a6b..57661dbc60688 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -1334,7 +1334,7 @@ typedef void ImageEncoderCallback(Uint8List result); /// /// [quality] is a value in [0, 100] where 0 corresponds to the lowest quality. void encodeImage(Image image, EncodedImageFormat format, int quality, ImageEncoderCallback callback) - native "encodeImage"; + native 'encodeImage'; /// Determines the winding rule that decides how the interior of a [Path] is /// calculated. From a4e3b63c211addac3bf2e8d6fed92b92f65165e5 Mon Sep 17 00:00:00 2001 From: Majid Valipour Date: Sun, 1 Apr 2018 12:57:11 -0400 Subject: [PATCH 03/11] Address review feedback including: - Add tests - Move to make encode a method of Image - futurize - Avoid using Dart_ThrowException instead use return value - Improve formatting and comments --- lib/ui/dart_ui.cc | 1 - lib/ui/painting.dart | 47 +++++++++++++---------- lib/ui/painting/image.cc | 8 ++++ lib/ui/painting/image.h | 2 + lib/ui/painting/image_encoding.cc | 43 +++++---------------- lib/ui/painting/image_encoding.h | 10 +++-- testing/dart/encoding_test.dart | 61 ++++++++++++++++++++++++++++++ testing/resources/square-80.jpg | Bin 0 -> 928 bytes testing/resources/square-80.png | Bin 0 -> 125 bytes testing/resources/square-80.webp | Bin 0 -> 666 bytes testing/run_tests.sh | 8 ++-- 11 files changed, 118 insertions(+), 62 deletions(-) create mode 100644 testing/dart/encoding_test.dart create mode 100644 testing/resources/square-80.jpg create mode 100644 testing/resources/square-80.png create mode 100644 testing/resources/square-80.webp diff --git a/lib/ui/dart_ui.cc b/lib/ui/dart_ui.cc index 5cc2b07976d46..25b5bdf44a95d 100644 --- a/lib/ui/dart_ui.cc +++ b/lib/ui/dart_ui.cc @@ -57,7 +57,6 @@ void DartUI::InitForGlobal() { Codec::RegisterNatives(g_natives); DartRuntimeHooks::RegisterNatives(g_natives); FrameInfo::RegisterNatives(g_natives); - ImageEncoding::RegisterNatives(g_natives); ImageFilter::RegisterNatives(g_natives); ImageShader::RegisterNatives(g_natives); Paragraph::RegisterNatives(g_natives); diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 57661dbc60688..95b021c32576d 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -1196,6 +1196,16 @@ class Paint { } } +/// The encoding formats supported by the [Image.toByteData]. +// These enum values must be kept in sync with the logic in ToSkEncodedImageFormat. +enum EncodeFormat { + // Be conservative with the formats we expose. It is easy to add new formats in future but + // difficult to remove. + JPEG, + PNG, + WEBP, +} + /// Opaque handle to raw decoded image data (pixels). /// /// To obtain an [Image] object, use [instantiateImageCodec]. @@ -1215,6 +1225,23 @@ class Image extends NativeFieldWrapperClass2 { /// The number of image pixels along the image's vertical axis. int get height native 'Image_height'; + /// Converts the [Image] object into a byte array. + /// + /// The [format] is encoding format to be used. + /// + /// The [quality] is a value in the range 0 to 100 where 0 corresponds to the lowest quality. + /// + /// 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 toByteData({EncodeFormat format: EncodeFormat.JPEG, int quality: 80}) { + return _futurize( + (_Callback callback) => _toByteData(format.index, quality, callback) + ); + } + + /// Returns an error message on failure, null on success. + String _toByteData(int format, int quality, _Callback callback) native 'Image_toByteData'; + /// Release the resources used by this object. The object is no longer usable /// after this method is called. void dispose() native 'Image_dispose'; @@ -1316,26 +1343,6 @@ Future _decodeImageFromListAsync(Uint8List list, ImageDecoderCallback call callback(frameInfo.image); } -/// The encoding formats supported by the [encodeImage]. -enum EncodedImageFormat { - // Be conservative with the formats we expose. We can increase the list in future but reducing - // it is more difficult. - JPEG, - PNG, - WEBP, -} - -/// Callback signature for [encodeImage]. -typedef void ImageEncoderCallback(Uint8List result); - -/// Convert an [Image] object into a byte array/ -/// -/// [format] is encoding format to be used. -/// -/// [quality] is a value in [0, 100] where 0 corresponds to the lowest quality. -void encodeImage(Image image, EncodedImageFormat format, int quality, ImageEncoderCallback callback) - native 'encodeImage'; - /// Determines the winding rule that decides how the interior of a [Path] is /// calculated. /// diff --git a/lib/ui/painting/image.cc b/lib/ui/painting/image.cc index 0b0127cd0c6c2..db8a227bb44c5 100644 --- a/lib/ui/painting/image.cc +++ b/lib/ui/painting/image.cc @@ -5,6 +5,7 @@ #include "flutter/lib/ui/painting/image.h" #include "flutter/common/threads.h" +#include "flutter/lib/ui/painting/image_encoding.h" #include "flutter/lib/ui/painting/utils.h" #include "lib/tonic/converter/dart_converter.h" #include "lib/tonic/dart_args.h" @@ -20,6 +21,7 @@ IMPLEMENT_WRAPPERTYPEINFO(ui, Image); #define FOR_EACH_BINDING(V) \ V(Image, width) \ V(Image, height) \ + V(Image, toByteData) \ V(Image, dispose) FOR_EACH_BINDING(DART_NATIVE_CALLBACK) @@ -36,6 +38,12 @@ CanvasImage::~CanvasImage() { SkiaUnrefOnIOThread(&image_); } +Dart_Handle CanvasImage::toByteData(int format, + int quality, + Dart_Handle callback_handle) { + return EncodeImage(this, format, quality, callback_handle); +} + void CanvasImage::dispose() { ClearDartWrapper(); } diff --git a/lib/ui/painting/image.h b/lib/ui/painting/image.h index 8ad11660721f0..69d63cca51b84 100644 --- a/lib/ui/painting/image.h +++ b/lib/ui/painting/image.h @@ -27,6 +27,8 @@ class CanvasImage final : public fxl::RefCountedThreadSafe, int width() { return image_->width(); } int height() { return image_->height(); } + Dart_Handle toByteData(int format, int quality, Dart_Handle callback); + void dispose(); const sk_sp& image() const { return image_; } diff --git a/lib/ui/painting/image_encoding.cc b/lib/ui/painting/image_encoding.cc index 27622d3c68c5b..f356e955f6745 100644 --- a/lib/ui/painting/image_encoding.cc +++ b/lib/ui/painting/image_encoding.cc @@ -78,42 +78,25 @@ SkEncodedImageFormat ToSkEncodedImageFormat(int format) { } } -void EncodeImage(Dart_NativeArguments args) { - Dart_Handle exception = nullptr; - - CanvasImage* canvas_image = - tonic::DartConverter::FromArguments(args, 0, exception); +} // namespace - if (exception) { - Dart_ThrowException(exception); - return; - } +Dart_Handle EncodeImage(CanvasImage* canvas_image, + int format, + int quality, + Dart_Handle callback_handle) { if (!canvas_image) - Dart_ThrowException(ToDart("encodeImage called with non-genuine Image.")); + return ToDart("encode called with non-genuine Image."); + + if (!Dart_IsClosure(callback_handle)) + return ToDart("Callback must be a function."); - int format = tonic::DartConverter::FromArguments(args, 1, exception); - if (exception) { - Dart_ThrowException(exception); - return; - } SkEncodedImageFormat image_format = ToSkEncodedImageFormat(format); - int quality = tonic::DartConverter::FromArguments(args, 2, exception); - if (exception) { - Dart_ThrowException(exception); - return; - } if (quality > 100) quality = 100; if (quality < 0) quality = 0; - Dart_Handle callback_handle = Dart_GetNativeArgument(args, 3); - if (!Dart_IsClosure(callback_handle)) { - Dart_ThrowException(ToDart("Callback must be a function")); - return; - } - auto callback = std::make_unique( tonic::DartState::Current(), callback_handle); sk_sp image = canvas_image->image(); @@ -123,14 +106,8 @@ void EncodeImage(Dart_NativeArguments args) { EncodeImageAndInvokeDataCallback(std::move(callback), std::move(image), image_format, quality); })); -} - -} // namespace -void ImageEncoding::RegisterNatives(tonic::DartLibraryNatives* natives) { - natives->Register({ - {"encodeImage", EncodeImage, 4, true}, - }); + return Dart_Null(); } } // namespace blink diff --git a/lib/ui/painting/image_encoding.h b/lib/ui/painting/image_encoding.h index b716458131099..a121d597ccefd 100644 --- a/lib/ui/painting/image_encoding.h +++ b/lib/ui/painting/image_encoding.h @@ -9,10 +9,12 @@ namespace blink { -class ImageEncoding { - public: - static void RegisterNatives(tonic::DartLibraryNatives* natives); -}; +class CanvasImage; + +Dart_Handle EncodeImage(CanvasImage* canvas_image, + int format, + int quality, + Dart_Handle callback_handle); } // namespace blink diff --git a/testing/dart/encoding_test.dart b/testing/dart/encoding_test.dart new file mode 100644 index 0000000000000..9848358154bf5 --- /dev/null +++ b/testing/dart/encoding_test.dart @@ -0,0 +1,61 @@ +// Copyright 2018 The Chromium 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:ui'; +import 'dart:typed_data'; +import 'dart:io'; + +import 'package:test/test.dart'; +import 'package:path/path.dart' as path; + +void main() { + + Image createSquareTestImage() { + PictureRecorder recorder = new PictureRecorder(); + Canvas canvas = new Canvas(recorder, new Rect.fromLTWH(0.0, 0.0, 10.0, 10.0)); + + var black = new Paint() + ..strokeWidth = 1.0 + ..color = const Color.fromRGBO(0, 0, 0, 1.0); + var green = new Paint() + ..strokeWidth = 1.0 + ..color = const Color.fromRGBO(0, 255, 0, 1.0); + + canvas.drawRect(new Rect.fromLTWH(0.0, 0.0, 10.0, 10.0), black); + canvas.drawRect(new Rect.fromLTWH(2.0, 2.0, 6.0, 6.0), green); + return recorder.endRecording().toImage(10, 10); + } + + Uint8List readFile(fileName) { + final file = new File(path.join('flutter', 'testing', 'resources', fileName)); + return file.readAsBytesSync(); + } + + final Image testImage = createSquareTestImage(); + + test('Encode with default arguments', () async { + Uint8List data = await testImage.toByteData(); + Uint8List expected = readFile('square-80.jpg'); + expect(data, expected); + }); + + test('Encode JPEG', () async { + Uint8List data = await testImage.toByteData(format: EncodeFormat.JPEG, quality:80); + Uint8List expected = readFile('square-80.jpg'); + expect(data, expected); + }); + + test('Encode PNG', () async { + Uint8List data = await testImage.toByteData(format: EncodeFormat.PNG, quality: 80); + Uint8List expected = readFile('square-80.png'); + expect(data, expected); + }); + + test('Encode WEBP', () async { + Uint8List data = await testImage.toByteData(format: EncodeFormat.WEBP, quality: 80); + Uint8List expected = readFile('square-80.webp'); + expect(data, expected); + }); +} \ No newline at end of file diff --git a/testing/resources/square-80.jpg b/testing/resources/square-80.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1140c33bd39039758e39b057c1da6ea0746bee4e GIT binary patch literal 928 zcmex=dDF&IcpE-rwvvCu$szg+{F&cJY47mGY0A>d!iC?v=ksAd6>t&xtzPC{armlTu$)tmsSPcA3|vF`xcHn};aAiF>S zGB z4}|FqISi=`3JjhMc?`)6dO(&Dg8_pfgW3Pv49*N}EUav-ENpD7Z0zi899#liT%4R- zB7FQj0^%YP65=9aVp6iI@>0^uGGby1Itt2a>Y7@blJdF+x*7(m8k!m)Lm1iF*||8l zgt@qcHKfF(G)M;j4=@OFFmN$&F*7PLFbOg;3o`yc!XO4ryNoc;LufX3ptu4s9ka49 zBTF$dF#|mzD9kRTXb4mxilLE(6(}q($iT$N%!;Iuk(r4_P?15&Ffh^S|1Aa{prK5H z%z_N|46To=#qP}9^mz4?#oI%7Z{GEN$DD^pRraU~)+k97F!1VX3mo&yF%mLo`184O f^8GnkrJ}-_x2_p3OGf4F%}28J29*~C-V}>VJUX<4B-HR z8jh3>AYa|n#W6%;YH~_K!jJO~ED1_(g3Lg`J*V|_jGK_J3>TNAzopr06J_NxBvhE literal 0 HcmV?d00001 diff --git a/testing/resources/square-80.webp b/testing/resources/square-80.webp new file mode 100644 index 0000000000000000000000000000000000000000..fe3924cad729ba651481c25c3b80c15db695a26c GIT binary patch literal 666 zcmWIYbaR`;#J~{l>J$(bU=hK^z`&pY#GGK{>FgXJ0hDE6V3Gin0t^hfc_l?b?oJ93 zkx>dDF&IcpE-rwvvCxbR4BxIX07(Xh%eq+P2?=qgq!uRw6@38Wk|Lnx3=E7ZK(9f_TU#4ax>C;_TD0aBk_Py}M%0kUm!b4o#WfdI(G zJgIr1!9Y3!h-Dax8G;zx8JvJ@sscoK<}YSou=vKnz&sHlmNbQdVS6hBgTx(#n94#1 z27aK}wkrjRMTua?umDM>v@`~WPs Date: Sun, 1 Apr 2018 14:59:14 -0400 Subject: [PATCH 04/11] Revert changes to run_tests --- testing/run_tests.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testing/run_tests.sh b/testing/run_tests.sh index 70b02e3537eb8..e84ce1c5509f1 100755 --- a/testing/run_tests.sh +++ b/testing/run_tests.sh @@ -2,11 +2,11 @@ set -ex -#out/host_debug_unopt/fxl_unittests -#out/host_debug_unopt/synchronization_unittests -#out/host_debug_unopt/wtf_unittests +out/host_debug_unopt/fxl_unittests +out/host_debug_unopt/synchronization_unittests +out/host_debug_unopt/wtf_unittests -#flutter/travis/analyze.sh +flutter/travis/analyze.sh pushd flutter/testing/dart pub get From 447d8d511cf81a5e01278b498aa178b8d6a1d488 Mon Sep 17 00:00:00 2001 From: Majid Valipour Date: Sun, 1 Apr 2018 15:18:49 -0400 Subject: [PATCH 05/11] Update quality documentation --- lib/ui/painting.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 95b021c32576d..be6e12dcac481 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -1229,7 +1229,9 @@ class Image extends NativeFieldWrapperClass2 { /// /// The [format] is encoding format to be used. /// - /// The [quality] is a value in the range 0 to 100 where 0 corresponds to the lowest quality. + /// The [quality] is a value in the range 0 to 100. [quality] is a format specific metric trading + /// off size and encoding error. When used, 100 encodes with highest quality (i.e., least error) + /// and 0 encodes with lowest quality (i.e., smallest size). /// /// Returns a future which complete with the binary image data (e.g a PNG or JPEG binary data) or /// an error if encoding fails. From 2ecab34190273f3c545fbd4b1be70afef6bf8e05 Mon Sep 17 00:00:00 2001 From: Majid Valipour Date: Sun, 1 Apr 2018 15:39:09 -0400 Subject: [PATCH 06/11] Update license file to make Travis happy --- travis/licenses_golden/licenses_flutter | 2 ++ 1 file changed, 2 insertions(+) diff --git a/travis/licenses_golden/licenses_flutter b/travis/licenses_golden/licenses_flutter index 7de50c115fd76..8749f6880dbf4 100644 --- a/travis/licenses_golden/licenses_flutter +++ b/travis/licenses_golden/licenses_flutter @@ -1642,6 +1642,8 @@ ORIGIN: ../../../flutter/assets/asset_provider.h + ../../../LICENSE TYPE: LicenseType.bsd FILE: ../../../flutter/assets/asset_provider.h FILE: ../../../flutter/flutter_kernel_transformers/lib/track_widget_constructor_locations.dart +FILE: ../../../flutter/lib/ui/painting/image_encoding.cc +FILE: ../../../flutter/lib/ui/painting/image_encoding.h FILE: ../../../flutter/shell/platform/android/apk_asset_provider.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.mm From 7202b1624acbd9e4476ba7a5dcd73c0bd8aadd9c Mon Sep 17 00:00:00 2001 From: Majid Valipour Date: Sun, 1 Apr 2018 16:03:14 -0400 Subject: [PATCH 07/11] minor cleanup --- lib/ui/dart_ui.cc | 1 - lib/ui/painting/image.cc | 4 ++-- lib/ui/painting/image.h | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/ui/dart_ui.cc b/lib/ui/dart_ui.cc index 25b5bdf44a95d..59d083a191778 100644 --- a/lib/ui/dart_ui.cc +++ b/lib/ui/dart_ui.cc @@ -12,7 +12,6 @@ #include "flutter/lib/ui/painting/frame_info.h" #include "flutter/lib/ui/painting/gradient.h" #include "flutter/lib/ui/painting/image.h" -#include "flutter/lib/ui/painting/image_encoding.h" #include "flutter/lib/ui/painting/image_filter.h" #include "flutter/lib/ui/painting/image_shader.h" #include "flutter/lib/ui/painting/path.h" diff --git a/lib/ui/painting/image.cc b/lib/ui/painting/image.cc index db8a227bb44c5..42e733241c990 100644 --- a/lib/ui/painting/image.cc +++ b/lib/ui/painting/image.cc @@ -40,8 +40,8 @@ CanvasImage::~CanvasImage() { Dart_Handle CanvasImage::toByteData(int format, int quality, - Dart_Handle callback_handle) { - return EncodeImage(this, format, quality, callback_handle); + Dart_Handle callback) { + return EncodeImage(this, format, quality, callback); } void CanvasImage::dispose() { diff --git a/lib/ui/painting/image.h b/lib/ui/painting/image.h index 69d63cca51b84..a7ed4298506f5 100644 --- a/lib/ui/painting/image.h +++ b/lib/ui/painting/image.h @@ -28,7 +28,6 @@ class CanvasImage final : public fxl::RefCountedThreadSafe, int width() { return image_->width(); } int height() { return image_->height(); } Dart_Handle toByteData(int format, int quality, Dart_Handle callback); - void dispose(); const sk_sp& image() const { return image_; } From e8a88202fc3d4cc687450bb7fb4afc792616747d Mon Sep 17 00:00:00 2001 From: Majid Valipour Date: Wed, 4 Apr 2018 22:38:19 -0400 Subject: [PATCH 08/11] Make EncodingFormat a class --- lib/ui/painting.dart | 88 +++++++++++++++--- testing/dart/encoding_test.dart | 52 +++++------ .../resources/{square-80.png => square.png} | Bin 3 files changed, 100 insertions(+), 40 deletions(-) rename testing/resources/{square-80.png => square.png} (100%) diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index be6e12dcac481..56b49617c50b4 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -1196,14 +1196,78 @@ class Paint { } } -/// The encoding formats supported by the [Image.toByteData]. -// These enum values must be kept in sync with the logic in ToSkEncodedImageFormat. -enum EncodeFormat { - // Be conservative with the formats we expose. It is easy to add new formats in future but - // difficult to remove. - JPEG, - PNG, - WEBP, +/// An encoding format to use with the [Image.toByteData]. +class EncodingFormat { + 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; + + /// 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: + /// + /// * , the Wikipedia page on PNG. + /// * , 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: + /// + /// * , 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: + /// + /// * , the Wikipedia page on WebP. + const EncodingFormat.webp({int quality = 80}) + : _format = _webpFormat, + _quality = quality; } /// Opaque handle to raw decoded image data (pixels). @@ -1229,15 +1293,11 @@ class Image extends NativeFieldWrapperClass2 { /// /// The [format] is encoding format to be used. /// - /// The [quality] is a value in the range 0 to 100. [quality] is a format specific metric trading - /// off size and encoding error. When used, 100 encodes with highest quality (i.e., least error) - /// and 0 encodes with lowest quality (i.e., smallest size). - /// /// 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 toByteData({EncodeFormat format: EncodeFormat.JPEG, int quality: 80}) { + Future toByteData({EncodingFormat format: const EncodingFormat.jpeg()}) { return _futurize( - (_Callback callback) => _toByteData(format.index, quality, callback) + (_Callback callback) => _toByteData(format._format, format._quality, callback) ); } diff --git a/testing/dart/encoding_test.dart b/testing/dart/encoding_test.dart index 9848358154bf5..23c849aa64db3 100644 --- a/testing/dart/encoding_test.dart +++ b/testing/dart/encoding_test.dart @@ -12,27 +12,6 @@ import 'package:path/path.dart' as path; void main() { - Image createSquareTestImage() { - PictureRecorder recorder = new PictureRecorder(); - Canvas canvas = new Canvas(recorder, new Rect.fromLTWH(0.0, 0.0, 10.0, 10.0)); - - var black = new Paint() - ..strokeWidth = 1.0 - ..color = const Color.fromRGBO(0, 0, 0, 1.0); - var green = new Paint() - ..strokeWidth = 1.0 - ..color = const Color.fromRGBO(0, 255, 0, 1.0); - - canvas.drawRect(new Rect.fromLTWH(0.0, 0.0, 10.0, 10.0), black); - canvas.drawRect(new Rect.fromLTWH(2.0, 2.0, 6.0, 6.0), green); - return recorder.endRecording().toImage(10, 10); - } - - Uint8List readFile(fileName) { - final file = new File(path.join('flutter', 'testing', 'resources', fileName)); - return file.readAsBytesSync(); - } - final Image testImage = createSquareTestImage(); test('Encode with default arguments', () async { @@ -42,20 +21,41 @@ void main() { }); test('Encode JPEG', () async { - Uint8List data = await testImage.toByteData(format: EncodeFormat.JPEG, quality:80); + Uint8List data = await testImage.toByteData(format: new EncodingFormat.jpeg(quality:80)); Uint8List expected = readFile('square-80.jpg'); expect(data, expected); }); test('Encode PNG', () async { - Uint8List data = await testImage.toByteData(format: EncodeFormat.PNG, quality: 80); - Uint8List expected = readFile('square-80.png'); + Uint8List data = await testImage.toByteData(format: new EncodingFormat.png()); + Uint8List expected = readFile('square.png'); expect(data, expected); }); test('Encode WEBP', () async { - Uint8List data = await testImage.toByteData(format: EncodeFormat.WEBP, quality: 80); + Uint8List data = await testImage.toByteData(format: new EncodingFormat.webp(quality: 80)); Uint8List expected = readFile('square-80.webp'); expect(data, expected); }); -} \ No newline at end of file +} + +Image createSquareTestImage() { + PictureRecorder recorder = new PictureRecorder(); + Canvas canvas = new Canvas(recorder, new Rect.fromLTWH(0.0, 0.0, 10.0, 10.0)); + + Paint black = new Paint() + ..strokeWidth = 1.0 + ..color = const Color.fromRGBO(0, 0, 0, 1.0); + Paint green = new Paint() + ..strokeWidth = 1.0 + ..color = const Color.fromRGBO(0, 255, 0, 1.0); + + canvas.drawRect(new Rect.fromLTWH(0.0, 0.0, 10.0, 10.0), black); + canvas.drawRect(new Rect.fromLTWH(2.0, 2.0, 6.0, 6.0), green); + return recorder.endRecording().toImage(10, 10); +} + +Uint8List readFile(fileName) { + final file = new File(path.join('flutter', 'testing', 'resources', fileName)); + return file.readAsBytesSync(); +} diff --git a/testing/resources/square-80.png b/testing/resources/square.png similarity index 100% rename from testing/resources/square-80.png rename to testing/resources/square.png From 813e9eca87ab258c793e2a908c3d1a0f18ba08b5 Mon Sep 17 00:00:00 2001 From: Majid Valipour Date: Wed, 4 Apr 2018 22:57:22 -0400 Subject: [PATCH 09/11] fix formatting of encoding_test.dart --- testing/dart/encoding_test.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/testing/dart/encoding_test.dart b/testing/dart/encoding_test.dart index 23c849aa64db3..853cebf05a79a 100644 --- a/testing/dart/encoding_test.dart +++ b/testing/dart/encoding_test.dart @@ -11,7 +11,6 @@ import 'package:test/test.dart'; import 'package:path/path.dart' as path; void main() { - final Image testImage = createSquareTestImage(); test('Encode with default arguments', () async { @@ -21,19 +20,22 @@ void main() { }); test('Encode JPEG', () async { - Uint8List data = await testImage.toByteData(format: new EncodingFormat.jpeg(quality:80)); + Uint8List data = await testImage.toByteData( + format: new EncodingFormat.jpeg(quality: 80)); Uint8List expected = readFile('square-80.jpg'); expect(data, expected); }); test('Encode PNG', () async { - Uint8List data = await testImage.toByteData(format: new EncodingFormat.png()); + Uint8List data = + await testImage.toByteData(format: new EncodingFormat.png()); Uint8List expected = readFile('square.png'); expect(data, expected); }); test('Encode WEBP', () async { - Uint8List data = await testImage.toByteData(format: new EncodingFormat.webp(quality: 80)); + Uint8List data = await testImage.toByteData( + format: new EncodingFormat.webp(quality: 80)); Uint8List expected = readFile('square-80.webp'); expect(data, expected); }); From cd5f265e89ac9aac3e7adb500742bfdcc15c460c Mon Sep 17 00:00:00 2001 From: Majid Valipour Date: Thu, 5 Apr 2018 08:31:44 -0400 Subject: [PATCH 10/11] Switch to returning ByteData --- lib/ui/painting.dart | 11 +++++++---- testing/dart/encoding_test.dart | 26 +++++++++++++------------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 56b49617c50b4..7de1e8719c318 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -1295,10 +1295,13 @@ class Image extends NativeFieldWrapperClass2 { /// /// 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 toByteData({EncodingFormat format: const EncodingFormat.jpeg()}) { - return _futurize( - (_Callback callback) => _toByteData(format._format, format._quality, callback) - ); + Future toByteData({EncodingFormat format: const EncodingFormat.jpeg()}) { + return _futurize((_Callback callback) { + _toByteData(format._format, format._quality, (Uint8List encoded) { + callback(new ByteData.view(encoded.buffer)); + }); + }); + } /// Returns an error message on failure, null on success. diff --git a/testing/dart/encoding_test.dart b/testing/dart/encoding_test.dart index 853cebf05a79a..5fbbb4eb63909 100644 --- a/testing/dart/encoding_test.dart +++ b/testing/dart/encoding_test.dart @@ -14,30 +14,30 @@ void main() { final Image testImage = createSquareTestImage(); test('Encode with default arguments', () async { - Uint8List data = await testImage.toByteData(); - Uint8List expected = readFile('square-80.jpg'); - expect(data, expected); + ByteData data = await testImage.toByteData(); + List expected = readFile('square-80.jpg'); + expect(new Uint8List.view(data.buffer), expected); }); test('Encode JPEG', () async { - Uint8List data = await testImage.toByteData( + ByteData data = await testImage.toByteData( format: new EncodingFormat.jpeg(quality: 80)); - Uint8List expected = readFile('square-80.jpg'); - expect(data, expected); + List expected = readFile('square-80.jpg'); + expect(new Uint8List.view(data.buffer), expected); }); test('Encode PNG', () async { - Uint8List data = + ByteData data = await testImage.toByteData(format: new EncodingFormat.png()); - Uint8List expected = readFile('square.png'); - expect(data, expected); + List expected = readFile('square.png'); + expect(new Uint8List.view(data.buffer), expected); }); test('Encode WEBP', () async { - Uint8List data = await testImage.toByteData( + ByteData data = await testImage.toByteData( format: new EncodingFormat.webp(quality: 80)); - Uint8List expected = readFile('square-80.webp'); - expect(data, expected); + List expected = readFile('square-80.webp'); + expect(new Uint8List.view(data.buffer), expected); }); } @@ -57,7 +57,7 @@ Image createSquareTestImage() { return recorder.endRecording().toImage(10, 10); } -Uint8List readFile(fileName) { +List readFile(fileName) { final file = new File(path.join('flutter', 'testing', 'resources', fileName)); return file.readAsBytesSync(); } From c89adb9841ffdc367356d616332fccf2408998fe Mon Sep 17 00:00:00 2001 From: Majid Valipour Date: Thu, 5 Apr 2018 20:06:24 -0400 Subject: [PATCH 11/11] Address feedback --- lib/ui/painting.dart | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 7de1e8719c318..4a874200f7eeb 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -1198,16 +1198,6 @@ class Paint { /// An encoding format to use with the [Image.toByteData]. class EncodingFormat { - 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; - /// PNG format. /// /// A loss-less compression format for images. This format is well suited for @@ -1268,6 +1258,16 @@ class EncodingFormat { 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). @@ -1297,11 +1297,10 @@ class Image extends NativeFieldWrapperClass2 { /// an error if encoding fails. Future toByteData({EncodingFormat format: const EncodingFormat.jpeg()}) { return _futurize((_Callback callback) { - _toByteData(format._format, format._quality, (Uint8List encoded) { - callback(new ByteData.view(encoded.buffer)); + return _toByteData(format._format, format._quality, (Uint8List encoded) { + callback(encoded.buffer.asByteData()); }); }); - } /// Returns an error message on failure, null on success.