diff --git a/lib/ui/BUILD.gn b/lib/ui/BUILD.gn index 7d581bb90..429c8f391 100644 --- a/lib/ui/BUILD.gn +++ b/lib/ui/BUILD.gn @@ -16,6 +16,8 @@ source_set("ui") { "dart_ui.h", "painting/canvas.cc", "painting/canvas.h", + "painting/codec.cc", + "painting/codec.h", "painting/gradient.cc", "painting/gradient.h", "painting/image.cc", diff --git a/lib/ui/dart_ui.cc b/lib/ui/dart_ui.cc index b3c842599..d28fdae61 100644 --- a/lib/ui/dart_ui.cc +++ b/lib/ui/dart_ui.cc @@ -8,6 +8,7 @@ #include "flutter/lib/ui/compositing/scene_builder.h" #include "flutter/lib/ui/dart_runtime_hooks.h" #include "flutter/lib/ui/painting/canvas.h" +#include "flutter/lib/ui/painting/codec.h" #include "flutter/lib/ui/painting/gradient.h" #include "flutter/lib/ui/painting/image.h" #include "flutter/lib/ui/painting/image_decoding.h" @@ -53,6 +54,7 @@ void DartUI::InitForGlobal() { CanvasGradient::RegisterNatives(g_natives); CanvasImage::RegisterNatives(g_natives); CanvasPath::RegisterNatives(g_natives); + Codec::RegisterNatives(g_natives); DartRuntimeHooks::RegisterNatives(g_natives); ImageDecoding::RegisterNatives(g_natives); ImageFilter::RegisterNatives(g_natives); diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 4387a3208..967846b67 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -868,6 +868,45 @@ abstract class Image extends NativeFieldWrapperClass2 { /// Callback signature for [decodeImageFromList]. typedef void ImageDecoderCallback(Image result); +/// Information for a single animation frame. +/// +/// Obtain a FrameInfo with [Codec.getNextFrame]. +abstract class FrameInfo extends NativeFieldWrapperClass2 { + // The duration this frame should be shown. + int get durationMillis native "FrameInfo_durationMillis"; + + // The Image object for this frame. + Image get image native "FrameInfo_image"; +} + +/// A handle to an image codec. +abstract class Codec extends NativeFieldWrapperClass2 { + /// Number of frames in this image. + int get frameCount native "Codec_frameCount"; + + /// Number of times to repeat the animation. + /// + /// * 0 when the animation should be played once. + /// * -1 for infinity repetitions. + int get repetitionCount native "Codec_repetitionCount"; + + /// Returns the next animation frame. + /// + /// Wraps back to the first frame after returning the last frame. + FrameInfo getNextFrame() native "Codec_getNextFrame"; + + /// Release the resources used by this object. The object is no longer usable + /// after this method is called. + void dispose() native "Codec_dispose"; +} + +/// Callback signature for [imageCodecFromList]. +typedef void CodecCallback(Codec result); + +/// Instatiates a [Codec] object for an image binary data. +void instantiateImageCodec(Uint8List list, CodecCallback callback) + native "instantiateImageCodec"; + /// Convert an image file from a byte array into an [Image] object. void decodeImageFromList(Uint8List list, ImageDecoderCallback callback) native "decodeImageFromList"; diff --git a/lib/ui/painting/codec.cc b/lib/ui/painting/codec.cc new file mode 100644 index 000000000..b06dafd66 --- /dev/null +++ b/lib/ui/painting/codec.cc @@ -0,0 +1,136 @@ +// Copyright 2017 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/codec.h" + +#include "flutter/common/threads.h" +#include "flutter/glue/trace_event.h" +#include "lib/fxl/functional/make_copyable.h" +#include "lib/tonic/dart_binding_macros.h" +#include "lib/tonic/dart_library_natives.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/codec/SkCodec.h" + +using tonic::DartInvoke; +using tonic::DartPersistentValue; +using tonic::ToDart; + +namespace blink { + +IMPLEMENT_WRAPPERTYPEINFO(ui, Codec); + +#define FOR_EACH_BINDING(V) \ + V(Codec, frameCount) \ + V(Codec, repetitionCount) \ + V(Codec, dispose) + +FOR_EACH_BINDING(DART_NATIVE_CALLBACK) + +void Codec::dispose() { + ClearDartWrapper(); +} + +MultiFrameCodec::MultiFrameCodec(std::unique_ptr codec) + : codec_(std::move(codec)) { + frameCount_ = codec_->getFrameCount(); + repetitionCount_ = codec_->getRepetitionCount(); +} + +namespace { + +static constexpr const char* kInitCodecTraceTag = "InitCodec"; + +std::unique_ptr InitCodec(sk_sp buffer, size_t trace_id) { + TRACE_FLOW_STEP("flutter", kInitCodecTraceTag, trace_id); + TRACE_EVENT0("blink", "InitCodec"); + + if (buffer == nullptr || buffer->isEmpty()) { + return nullptr; + } + + return SkCodec::MakeFromData(buffer); +} + +void InvokeCodecCallback(std::unique_ptr codec, + int frameCount, + int repetitionCount, + std::unique_ptr callback, + size_t trace_id) { + tonic::DartState* dart_state = callback->dart_state().get(); + if (!dart_state) { + TRACE_FLOW_END("flutter", kInitCodecTraceTag, trace_id); + return; + } + tonic::DartState::Scope scope(dart_state); + if (!codec) { + DartInvoke(callback->value(), {Dart_Null()}); + } else { + fxl::RefPtr resultCodec = MultiFrameCodec::Create(std::move(codec)); + DartInvoke(callback->value(), {ToDart(resultCodec)}); + } + TRACE_FLOW_END("flutter", kInitCodecTraceTag, trace_id); +} + +void InitCodecAndInvokeCodecCallback( + std::unique_ptr callback, + sk_sp buffer, + size_t trace_id) { + std::unique_ptr codec = InitCodec(std::move(buffer), trace_id); + int frameCount = codec->getFrameCount(); + int repetitionCount = codec->getRepetitionCount(); + Threads::UI()->PostTask(fxl::MakeCopyable([ + callback = std::move(callback), codec = std::move(codec), trace_id, + frameCount, repetitionCount + ]() mutable { + InvokeCodecCallback(std::move(codec), frameCount, repetitionCount, + std::move(callback), trace_id); + })); +} + +void InstantiateImageCodec(Dart_NativeArguments args) { + static size_t trace_counter = 1; + const size_t trace_id = trace_counter++; + TRACE_FLOW_BEGIN("flutter", kInitCodecTraceTag, trace_id); + + Dart_Handle exception = nullptr; + + tonic::Uint8List list = + tonic::DartConverter::FromArguments(args, 0, exception); + if (exception) { + TRACE_FLOW_END("flutter", kInitCodecTraceTag, trace_id); + Dart_ThrowException(exception); + return; + } + + Dart_Handle callback_handle = Dart_GetNativeArgument(args, 1); + if (!Dart_IsClosure(callback_handle)) { + TRACE_FLOW_END("flutter", kInitCodecTraceTag, trace_id); + Dart_ThrowException(ToDart("Callback must be a function")); + return; + } + + auto buffer = SkData::MakeWithCopy(list.data(), list.num_elements()); + + Threads::IO()->PostTask(fxl::MakeCopyable([ + callback = std::make_unique( + tonic::DartState::Current(), callback_handle), + buffer = std::move(buffer), trace_id + ]() mutable { + InitCodecAndInvokeCodecCallback(std::move(callback), std::move(buffer), + trace_id); + })); +} + +} // namespace + +void Codec::RegisterNatives(tonic::DartLibraryNatives* natives) { + natives->Register({ + {"instantiateImageCodec", InstantiateImageCodec, 2, true}, + }); + natives->Register({FOR_EACH_BINDING(DART_REGISTER_NATIVE)}); +} + +} // namespace blink diff --git a/lib/ui/painting/codec.h b/lib/ui/painting/codec.h new file mode 100644 index 000000000..750a43976 --- /dev/null +++ b/lib/ui/painting/codec.h @@ -0,0 +1,54 @@ +// Copyright 2017 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_CODEC_H_ +#define FLUTTER_LIB_UI_PAINTING_CODEC_H_ + +#include "lib/tonic/dart_wrappable.h" +#include "third_party/skia/include/codec/SkCodec.h" + +namespace tonic { +class DartLibraryNatives; +} // namespace tonic + +namespace blink { + +// A handle to an SkCodec object. +// +// Doesn't mirror SkCodec's API but provides a simple sequential access API. +class Codec : public fxl::RefCountedThreadSafe, + public tonic::DartWrappable { + DEFINE_WRAPPERTYPEINFO(); + + public: + virtual int frameCount() = 0; + virtual int repetitionCount() = 0; + void dispose(); + + static void RegisterNatives(tonic::DartLibraryNatives* natives); +}; + +class MultiFrameCodec : public Codec { + FRIEND_MAKE_REF_COUNTED(MultiFrameCodec); + + public: + static fxl::RefPtr Create(std::unique_ptr codec) { + return fxl::MakeRefCounted(std::move(codec)); + } + + int frameCount() { return frameCount_; } + int repetitionCount() { return repetitionCount_; } + + static void RegisterNatives(tonic::DartLibraryNatives* natives); + + private: + MultiFrameCodec(std::unique_ptr codec); + + const std::unique_ptr codec_; + int frameCount_; + int repetitionCount_; +}; +} // namespace blink + +#endif // FLUTTER_LIB_UI_PAINTING_CODEC_H_ diff --git a/testing/dart/codec_test.dart b/testing/dart/codec_test.dart new file mode 100644 index 000000000..2fe95adb7 --- /dev/null +++ b/testing/dart/codec_test.dart @@ -0,0 +1,43 @@ +// Copyright 2017 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:io'; +import 'dart:ui' as ui; +import 'dart:typed_data'; + +import 'package:test/test.dart'; +import 'package:path/path.dart' as path; + +void main() { + + test('Animation metadata', () async { + Uint8List data = await _getSkiaResource('alphabetAnim.gif').readAsBytes(); + Completer completer = new Completer(); + ui.instantiateImageCodec(data, completer.complete); + ui.Codec codec = await completer.future; + expect(codec.frameCount, 13); + expect(codec.repetitionCount, 0); + codec.dispose(); + + data = await _getSkiaResource('test640x479.gif').readAsBytes(); + completer = new Completer(); + ui.instantiateImageCodec(data, completer.complete); + codec = await completer.future; + expect(codec.frameCount, 4); + expect(codec.repetitionCount, -1); + }); +} + +/// Returns a File handle to a file in the skia/resources directory. +File _getSkiaResource(String fileName) { + // As Platform.script is not working for flutter_tester + // (https://github.com/flutter/flutter/issues/12847), this is currently + // assuming the curent working directory is engine/src. + // This is fragile and should be changed once the Platform.script issue is + // resolved. + String assetPath = + path.join('third_party', 'skia', 'resources', fileName); + return new File(assetPath); +} diff --git a/travis/licenses_golden/licenses_flutter b/travis/licenses_golden/licenses_flutter index 0c9a0c0d8..6b8bf9a71 100644 --- a/travis/licenses_golden/licenses_flutter +++ b/travis/licenses_golden/licenses_flutter @@ -1137,6 +1137,8 @@ FILE: ../../../flutter/fml/trace_event.cc FILE: ../../../flutter/fml/trace_event.h FILE: ../../../flutter/lib/ui/compositing/scene_host.cc FILE: ../../../flutter/lib/ui/compositing/scene_host.h +FILE: ../../../flutter/lib/ui/painting/codec.cc +FILE: ../../../flutter/lib/ui/painting/codec.h FILE: ../../../flutter/lib/ui/painting/utils.cc FILE: ../../../flutter/lib/ui/painting/vertices.cc FILE: ../../../flutter/lib/ui/painting/vertices.h