mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Implement Scene::toImage for creating a raster image representation of a scene. (#5021)
This commit is contained in:
parent
572c5844aa
commit
8973c733a5
@ -6,6 +6,7 @@
|
||||
|
||||
#include "flutter/flow/layers/layer.h"
|
||||
#include "flutter/glue/trace_event.h"
|
||||
#include "third_party/skia/include/core/SkPictureRecorder.h"
|
||||
|
||||
namespace flow {
|
||||
|
||||
@ -60,6 +61,7 @@ void LayerTree::UpdateScene(SceneUpdateContext& context,
|
||||
#endif
|
||||
|
||||
void LayerTree::Paint(CompositorContext::ScopedFrame& frame) const {
|
||||
TRACE_EVENT0("flutter", "LayerTree::Paint");
|
||||
Layer::PaintContext context = {
|
||||
*frame.canvas(), //
|
||||
frame.context().frame_time(), //
|
||||
@ -67,10 +69,50 @@ void LayerTree::Paint(CompositorContext::ScopedFrame& frame) const {
|
||||
frame.context().texture_registry(), //
|
||||
checkerboard_offscreen_layers_ //
|
||||
};
|
||||
TRACE_EVENT0("flutter", "LayerTree::Paint");
|
||||
|
||||
if (root_layer_->needs_painting())
|
||||
root_layer_->Paint(context);
|
||||
}
|
||||
|
||||
sk_sp<SkPicture> LayerTree::Flatten(const SkRect& bounds) {
|
||||
TRACE_EVENT0("flutter", "LayerTree::Flatten");
|
||||
|
||||
SkPictureRecorder recorder;
|
||||
auto canvas = recorder.beginRecording(bounds);
|
||||
|
||||
if (!canvas) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Layer::PrerollContext preroll_context{
|
||||
nullptr, // raster_cache (don't consult the cache)
|
||||
nullptr, // gr_context (used for the raster cache)
|
||||
nullptr, // SkColorSpace* dst_color_space
|
||||
SkRect::MakeEmpty(), // SkRect child_paint_bounds
|
||||
};
|
||||
|
||||
const Stopwatch unused_stopwatch;
|
||||
TextureRegistry unused_texture_registry;
|
||||
|
||||
Layer::PaintContext paint_context = {
|
||||
*canvas, // canvas
|
||||
unused_stopwatch, // frame time (dont care)
|
||||
unused_stopwatch, // engine time (dont care)
|
||||
unused_texture_registry, // texture registry (not supported)
|
||||
false // checkerboard offscreen layers
|
||||
};
|
||||
|
||||
// Even if we don't have a root layer, we still need to create an empty
|
||||
// picture.
|
||||
if (root_layer_) {
|
||||
root_layer_->Preroll(&preroll_context, SkMatrix::I());
|
||||
// The needs painting flag may be set after the preroll. So check it after.
|
||||
if (root_layer_->needs_painting()) {
|
||||
root_layer_->Paint(paint_context);
|
||||
}
|
||||
}
|
||||
|
||||
return recorder.finishRecordingAsPicture();
|
||||
}
|
||||
|
||||
} // namespace flow
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
#include "flutter/flow/layers/layer.h"
|
||||
#include "lib/fxl/macros.h"
|
||||
#include "lib/fxl/time/time_delta.h"
|
||||
#include "third_party/skia/include/core/SkPicture.h"
|
||||
#include "third_party/skia/include/core/SkSize.h"
|
||||
|
||||
namespace flow {
|
||||
@ -33,6 +34,8 @@ class LayerTree {
|
||||
|
||||
void Paint(CompositorContext::ScopedFrame& frame) const;
|
||||
|
||||
sk_sp<SkPicture> Flatten(const SkRect& bounds);
|
||||
|
||||
Layer* root_layer() const { return root_layer_.get(); }
|
||||
|
||||
void set_root_layer(std::unique_ptr<Layer> root_layer) {
|
||||
|
||||
@ -17,6 +17,19 @@ class Scene extends NativeFieldWrapperClass2 {
|
||||
/// To create a Scene object, use a [SceneBuilder].
|
||||
Scene._();
|
||||
|
||||
|
||||
/// Creates a raster image representation of the current state of the scene.
|
||||
/// This is a slow operation that is performed on a background thread.
|
||||
Future<Image> toImage(int width, int height) {
|
||||
if (width <= 0 || height <= 0)
|
||||
throw new Exception('Invalid image dimensions.');
|
||||
return _futurize(
|
||||
(_Callback<Image> callback) => _toImage(width, height, callback)
|
||||
);
|
||||
}
|
||||
|
||||
String _toImage(int width, int height, _Callback<Image> callback) native 'Scene_toImage';
|
||||
|
||||
/// Releases the resources used by this scene.
|
||||
///
|
||||
/// After calling this function, the scene is cannot be used further.
|
||||
|
||||
@ -4,18 +4,26 @@
|
||||
|
||||
#include "flutter/lib/ui/compositing/scene.h"
|
||||
|
||||
#include "flutter/fml/trace_event.h"
|
||||
#include "flutter/lib/ui/painting/image.h"
|
||||
#include "lib/fxl/functional/make_copyable.h"
|
||||
#include "lib/tonic/converter/dart_converter.h"
|
||||
#include "lib/tonic/dart_args.h"
|
||||
#include "lib/tonic/dart_binding_macros.h"
|
||||
#include "lib/tonic/dart_library_natives.h"
|
||||
#include "third_party/skia/include/core/SkCanvas.h"
|
||||
#include "third_party/skia/include/core/SkPictureRecorder.h"
|
||||
#include "lib/tonic/dart_persistent_value.h"
|
||||
#include "lib/tonic/dart_wrappable.h"
|
||||
#include "lib/tonic/logging/dart_invoke.h"
|
||||
#include "third_party/skia/include/core/SkImageInfo.h"
|
||||
#include "third_party/skia/include/core/SkSurface.h"
|
||||
|
||||
namespace blink {
|
||||
|
||||
IMPLEMENT_WRAPPERTYPEINFO(ui, Scene);
|
||||
|
||||
#define FOR_EACH_BINDING(V) V(Scene, dispose)
|
||||
#define FOR_EACH_BINDING(V) \
|
||||
V(Scene, toImage) \
|
||||
V(Scene, dispose)
|
||||
|
||||
DART_BIND_ALL(Scene, FOR_EACH_BINDING)
|
||||
|
||||
@ -46,6 +54,128 @@ void Scene::dispose() {
|
||||
ClearDartWrapper();
|
||||
}
|
||||
|
||||
static sk_sp<SkImage> CreateSceneSnapshot(GrContext* context,
|
||||
sk_sp<SkPicture> picture,
|
||||
const SkSize& size) {
|
||||
TRACE_EVENT0("flutter", "CreateSceneSnapshot");
|
||||
auto image_info =
|
||||
SkImageInfo::MakeN32Premul(SkISize::Make(size.width(), size.height()));
|
||||
|
||||
sk_sp<SkSurface> surface;
|
||||
|
||||
if (context) {
|
||||
surface = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, image_info);
|
||||
}
|
||||
|
||||
if (!surface) {
|
||||
surface = SkSurface::MakeRaster(image_info);
|
||||
}
|
||||
|
||||
if (!surface) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto canvas = surface->getCanvas();
|
||||
|
||||
if (!canvas) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (picture) {
|
||||
canvas->drawPicture(picture.get());
|
||||
}
|
||||
|
||||
auto snapshot = surface->makeImageSnapshot();
|
||||
|
||||
if (!snapshot) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return snapshot->makeRasterImage();
|
||||
}
|
||||
|
||||
Dart_Handle Scene::toImage(uint32_t width,
|
||||
uint32_t height,
|
||||
Dart_Handle raw_image_callback) {
|
||||
TRACE_EVENT0("flutter", "Scene::toImage");
|
||||
if (Dart_IsNull(raw_image_callback) || !Dart_IsClosure(raw_image_callback)) {
|
||||
return tonic::ToDart("Image callback was invalid");
|
||||
}
|
||||
|
||||
if (!m_layerTree) {
|
||||
return tonic::ToDart("Scene did not contain a layer tree.");
|
||||
}
|
||||
|
||||
if (width == 0 || height == 0) {
|
||||
return tonic::ToDart("Image dimensions for scene were invalid.");
|
||||
}
|
||||
|
||||
auto dart_state = UIDartState::Current();
|
||||
|
||||
auto image_callback = std::make_unique<tonic::DartPersistentValue>(
|
||||
dart_state, raw_image_callback);
|
||||
|
||||
// We can't create an image on this task runner because we don't have a
|
||||
// graphics context. Even if we did, it would be slow anyway. Also, this
|
||||
// thread owns the sole reference to the layer tree. So we flatten the layer
|
||||
// tree into a picture and use that as the thread transport mechanism.
|
||||
|
||||
auto bounds_size = SkSize::Make(width, height);
|
||||
auto picture = m_layerTree->Flatten(SkRect::MakeSize(bounds_size));
|
||||
if (!picture) {
|
||||
// Already in Dart scope.
|
||||
return tonic::ToDart("Could not flatten scene into a layer tree.");
|
||||
}
|
||||
|
||||
auto resource_context = dart_state->GetResourceContext();
|
||||
auto ui_task_runner = dart_state->GetTaskRunners().GetUITaskRunner();
|
||||
auto unref_queue = dart_state->GetSkiaUnrefQueue();
|
||||
|
||||
// The picture has been prepared on the UI thread.
|
||||
dart_state->GetTaskRunners().GetIOTaskRunner()->PostTask(
|
||||
fxl::MakeCopyable([picture = std::move(picture), //
|
||||
bounds_size, //
|
||||
resource_context = std::move(resource_context), //
|
||||
ui_task_runner = std::move(ui_task_runner), //
|
||||
image_callback = std::move(image_callback), //
|
||||
unref_queue = std::move(unref_queue) //
|
||||
]() mutable {
|
||||
// Snapshot the picture on the IO thread that contains an optional
|
||||
// GrContext.
|
||||
auto image = CreateSceneSnapshot(resource_context.get(),
|
||||
std::move(picture), bounds_size);
|
||||
|
||||
// Send the image back to the UI thread for submission back to the
|
||||
// framework.
|
||||
ui_task_runner->PostTask(
|
||||
fxl::MakeCopyable([image = std::move(image), //
|
||||
image_callback = std::move(image_callback), //
|
||||
unref_queue = std::move(unref_queue) //
|
||||
]() mutable {
|
||||
auto dart_state = image_callback->dart_state().get();
|
||||
if (!dart_state) {
|
||||
// The root isolate could have died in the meantime.
|
||||
return;
|
||||
}
|
||||
tonic::DartState::Scope scope(dart_state);
|
||||
|
||||
if (!image) {
|
||||
tonic::DartInvoke(image_callback->Get(), {Dart_Null()});
|
||||
return;
|
||||
}
|
||||
|
||||
auto dart_image = CanvasImage::Create();
|
||||
dart_image->set_image({std::move(image), std::move(unref_queue)});
|
||||
auto raw_dart_image = tonic::ToDart(std::move(dart_image));
|
||||
|
||||
// All done!
|
||||
tonic::DartInvoke(image_callback->Get(), {raw_dart_image});
|
||||
}));
|
||||
}));
|
||||
|
||||
return Dart_Null();
|
||||
}
|
||||
|
||||
std::unique_ptr<flow::LayerTree> Scene::takeLayerTree() {
|
||||
return std::move(m_layerTree);
|
||||
}
|
||||
|
||||
@ -32,6 +32,10 @@ class Scene : public fxl::RefCountedThreadSafe<Scene>,
|
||||
|
||||
std::unique_ptr<flow::LayerTree> takeLayerTree();
|
||||
|
||||
Dart_Handle toImage(uint32_t width,
|
||||
uint32_t height,
|
||||
Dart_Handle image_callback);
|
||||
|
||||
void dispose();
|
||||
|
||||
static void RegisterNatives(tonic::DartLibraryNatives* natives);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user