Skip to content

Commit

Permalink
Initial implementation of ui.Codec (a wrapper for SkCodec) (flutter#4318
Browse files Browse the repository at this point in the history
)

This is the first step to support animated GIFs: flutter/flutter#204

TBD in following CLs:
 * Implement Codec.getNextFrame.
 * Add Framework side support to run animations.
  • Loading branch information
amirh authored Nov 3, 2017
1 parent 60b6780 commit 941ed76
Show file tree
Hide file tree
Showing 7 changed files with 278 additions and 0 deletions.
2 changes: 2 additions & 0 deletions lib/ui/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions lib/ui/dart_ui.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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);
Expand Down
39 changes: 39 additions & 0 deletions lib/ui/painting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
136 changes: 136 additions & 0 deletions lib/ui/painting/codec.cc
Original file line number Diff line number Diff line change
@@ -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<SkCodec> codec)
: codec_(std::move(codec)) {
frameCount_ = codec_->getFrameCount();
repetitionCount_ = codec_->getRepetitionCount();
}

namespace {

static constexpr const char* kInitCodecTraceTag = "InitCodec";

std::unique_ptr<SkCodec> InitCodec(sk_sp<SkData> 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<SkCodec> codec,
int frameCount,
int repetitionCount,
std::unique_ptr<DartPersistentValue> 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<Codec> resultCodec = MultiFrameCodec::Create(std::move(codec));
DartInvoke(callback->value(), {ToDart(resultCodec)});
}
TRACE_FLOW_END("flutter", kInitCodecTraceTag, trace_id);
}

void InitCodecAndInvokeCodecCallback(
std::unique_ptr<DartPersistentValue> callback,
sk_sp<SkData> buffer,
size_t trace_id) {
std::unique_ptr<SkCodec> 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<tonic::Uint8List>::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<DartPersistentValue>(
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
54 changes: 54 additions & 0 deletions lib/ui/painting/codec.h
Original file line number Diff line number Diff line change
@@ -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<Codec>,
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<MultiFrameCodec> Create(std::unique_ptr<SkCodec> codec) {
return fxl::MakeRefCounted<MultiFrameCodec>(std::move(codec));
}

int frameCount() { return frameCount_; }
int repetitionCount() { return repetitionCount_; }

static void RegisterNatives(tonic::DartLibraryNatives* natives);

private:
MultiFrameCodec(std::unique_ptr<SkCodec> codec);

const std::unique_ptr<SkCodec> codec_;
int frameCount_;
int repetitionCount_;
};
} // namespace blink

#endif // FLUTTER_LIB_UI_PAINTING_CODEC_H_
43 changes: 43 additions & 0 deletions testing/dart/codec_test.dart
Original file line number Diff line number Diff line change
@@ -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<ui.Codec> completer = new Completer<ui.Codec>();
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.Codec>();
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);
}
2 changes: 2 additions & 0 deletions travis/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 941ed76

Please sign in to comment.