Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(197)

Unified Diff: src/utils/SkCanvasUtils.cpp

Issue 23545017: Create a semi-stable API for capturing the state of an SkCanvas and reconstructing that state acros… (Closed) Base URL: https://skia.googlecode.com/svn/trunk
Patch Set: Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« include/utils/SkCanvasUtils.h ('K') | « include/utils/SkCanvasUtils.h ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: src/utils/SkCanvasUtils.cpp
diff --git a/src/utils/SkCanvasUtils.cpp b/src/utils/SkCanvasUtils.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ba015d56684853bfac04a0c972bff40c72c8270c
--- /dev/null
+++ b/src/utils/SkCanvasUtils.cpp
@@ -0,0 +1,296 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkCanvasUtils.h"
+#include "SkCanvas.h"
+#include "SkDevice.h"
+#include "SkNWayCanvas.h"
+#include "SkSurface.h"
+#include "SkTScopedPtr.h"
mtklein 2013/08/28 18:20:46 Ben will scream. I think the Ben-friendly version
djsollen 2013/08/29 15:09:19 Done.
+#include "SkWriter32.h"
+
+#define CANVAS_STATE_VERSION 1
+
+enum RasterConfigs {
+ kRGB_565_RasterConfig = 4,
scroggo 2013/08/28 17:22:01 Maybe a comment explaining why these start with 4?
djsollen 2013/08/29 15:09:19 Done.
+ kARGB_4444_RasterConfig = 5,
+ kARGB_8888_RasterConfig = 6
+};
+typedef int32_t RasterConfig;
mtklein 2013/08/28 18:20:46 Can you plaster a few warnings around to remind th
djsollen 2013/08/29 15:09:19 Done.
+
+enum CanvasBackends {
+ kUnknown_CanvasBackend = 0,
+ kRaster_CanvasBackend = 1,
+ kGPU_CanvasBackend = 2,
+ kPDF_CanvasBackend = 3
+};
+typedef int32_t CanvasBackend;
+
+struct SkMCState {
+ // The Matrix and Clip are relative to |pixels|, not the source canvas.
+ float matrix[9]; // The matrix currently in effect on the canvas.
+
+ // this only works for non-antialiased clips
+ int32_t clipRectCount; // Number of rects in |clip_rects|.
+ int32_t* clipRects; // Clip area: 4 ints per rect in {x,y,w,h} format.
mtklein 2013/08/28 18:20:46 Can't hurt to have a struct for this, right? Then
djsollen 2013/08/29 15:09:19 Done.
+
mtklein 2013/08/28 18:20:46 stray newline
djsollen 2013/08/29 15:09:19 Done.
+};
+
+// NOTE: If you add more members, bump CanvasState::version.
+struct SkCanvasLayerState {
+ CanvasBackend type; // |pixel| format: a value from AwPixelConfig.
mtklein 2013/08/28 18:20:46 Comment is confusing?
djsollen 2013/08/29 15:09:19 Done.
+ int32_t x, y;
+ int32_t width; // In pixels.
+ int32_t height; // In pixels.
+
+ SkMCState mcState;
+
+ union {
+ struct {
+ RasterConfig config; // |pixel| format: a value from RasterConfigs.
+ int32_t rowBytes; // Number of bytes from start of one line to next.
scroggo 2013/08/28 17:22:01 nit: alignment of //'s Also, should rowBytes be s
djsollen 2013/08/29 15:09:19 No size_t is not guaranteed to always be 32 bits.
+ void* pixels; // The pixels, all (height * row_bytes) of them.
+ } raster;
+ struct {
+ int32_t textureID;
+ } gpu;
+ } content;
mtklein 2013/08/28 18:20:46 You may find it more readable to remove the name "
djsollen 2013/08/29 15:09:19 Done.
+};
+
+class SkCanvasState {
scroggo 2013/08/28 17:22:01 Should this inherit from SkNoncopyable and hide th
djsollen 2013/08/29 15:09:19 This needs to stay as close to a simple struct as
+public:
+ SkCanvasState(const SkISize& size) {
+ version = CANVAS_STATE_VERSION;
+ width = size.width();
+ height = size.height();
+ layerCount = 0;
+ layers = NULL;
+ }
+
+ ~SkCanvasState() {
+ // loop through the layers and free the data allocated to the clipRects
+ for (int i = 0; i < layerCount; ++i) {
+ sk_free(layers[i].mcState.clipRects);
+ }
+
+ sk_free(mcState.clipRects);
scroggo 2013/08/28 17:22:01 Should we set this to NULL in the constructor?
mtklein 2013/08/28 18:20:46 More than that, what about initializing the struct
+ sk_free(layers);
+ }
+
+ int32_t version; // The CanvasState version this struct was built with.
scroggo 2013/08/28 17:22:01 Is there a reason these don't follow our conventio
mtklein 2013/08/28 18:20:46 Given that they're public members, I personally li
mtklein 2013/08/28 18:20:46 Add a reminder that we can never reorder around th
+ int32_t width;
+ int32_t height;
+
+ SkMCState mcState;
+
+ int32_t layerCount;
+ SkCanvasLayerState* layers;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class ClipValidator : public SkCanvas::ClipVisitor {
+ public:
scroggo 2013/08/28 17:22:01 nit: spacing. We typically have no spaces before "
djsollen 2013/08/29 15:09:19 Done.
+ ClipValidator() : failed_(false) {}
+ bool failed() { return failed_; }
+
+ // ClipVisitor
+ virtual void clipRect(const SkRect& rect, SkRegion::Op op, bool antialias) {
scroggo 2013/08/28 17:22:01 SK_OVERRIDE?
djsollen 2013/08/29 15:09:19 Done.
+ failed_ |= antialias;
+ }
+
+ virtual void clipPath(const SkPath&, SkRegion::Op, bool antialias) {
scroggo 2013/08/28 17:22:01 SK_OVERRIDE?
djsollen 2013/08/29 15:09:19 Done.
+ failed_ |= antialias;
+ }
+
+ private:
scroggo 2013/08/28 17:22:01 Same spacing nit.
djsollen 2013/08/29 15:09:19 Done.
+ bool failed_;
+};
+
+static void setup_MC_state(SkMCState* state, const SkMatrix& matrix, const SkRegion& clip) {
+ // initialize the struct
+ state->clipRectCount = 0;
+
+ // capture the matrix
+ for (int i = 0; i < 9; i++) {
+ state->matrix[i] = matrix.get(i);
+ }
+
+ // capture the clip
+ const int clipBufferSize = 3 * sizeof(SkIRect);
+ char clipBuffer[clipBufferSize];
+ SkWriter32 clipWriter(sizeof(SkIRect), clipBuffer, clipBufferSize);
+
+ if (!clip.isEmpty()) {
+ // only returns the b/w clip so aa clips fail
+ SkRegion::Iterator clip_iterator(clip);
+ for (; !clip_iterator.done(); clip_iterator.next()) {
+ clipWriter.writeIRect(clip_iterator.rect());
+ state->clipRectCount++;
+ }
+ }
+
+ // allocate memory for the clip then and copy them to the struct
+ state->clipRects = (int32_t*) sk_malloc_throw(clipWriter.size());
+ clipWriter.flatten(state->clipRects);
+}
+
+
+
+SkCanvasState* SkCanvasUtils::CaptureCanvasState(SkCanvas* canvas) {
scroggo 2013/08/28 17:22:01 Should we check for a NULL canvas?
djsollen 2013/08/29 15:09:19 Done.
+ // Check the clip can be decomposed into simple rectangles.
scroggo 2013/08/28 17:22:01 Comment seems inaccurate; the validator checks for
djsollen 2013/08/29 15:09:19 non-antialiased arbitrary paths are OK as the get
+ ClipValidator validator;
+ canvas->replayClips(&validator);
+ if (validator.failed()) {
+ SkDEBUGF(("CaptureCanvasState does not currently support canvases with antialiased clips"));
+ return NULL;
+ }
+
+ const SkISize canvasSize = canvas->getDeviceSize();
+ SkTScopedPtr<SkCanvasState> canvasState(SkNEW_ARGS(SkCanvasState, (canvasSize)));
scroggo 2013/08/28 17:22:01 Aren't we moving away from SkTScopedPtr?
djsollen 2013/08/29 15:09:19 Done.
+
+ // decompose the total matrix and clip
+ setup_MC_state(&canvasState->mcState, canvas->getTotalMatrix(), canvas->getTotalClip());
+
+ // decompose the layers
+ const int layerBufferSize = 3 * sizeof(SkCanvasLayerState);
mtklein 2013/08/28 18:20:46 Remind us where 3 comes from? Just a guess about
djsollen 2013/08/29 15:09:19 Done.
+ char layerBuffer[layerBufferSize];
+ SkWriter32 layerWriter(sizeof(SkCanvasLayerState), layerBuffer, layerBufferSize);
+
+ for (SkCanvas::LayerIter layer(canvas, false); !layer.done(); layer.next()) {
mtklein 2013/08/28 18:20:46 can you add an inline "false /*meaning*/" comment
djsollen 2013/08/29 15:09:19 Done.
+ // if the layer is clipped out then we can skip it
+ if (layer.clip().isEmpty()) {
+ continue;
+ }
+
+ // we currently only work for a bitmap backed devices
scroggo 2013/08/28 17:22:01 nit: The word 'a' is not needed here
djsollen 2013/08/29 15:09:19 Done.
+ const SkBitmap& bitmap = layer.device()->accessBitmap(true);
mtklein 2013/08/28 18:20:46 Same for true?
djsollen 2013/08/29 15:09:19 Done.
+ if (bitmap.empty() || bitmap.isNull() || !bitmap.lockPixelsAreWritable()) {
+ return NULL;
+ }
+
+ SkCanvasLayerState* layerState = (SkCanvasLayerState*) layerWriter.reserve(sizeof(SkCanvasLayerState));
scroggo 2013/08/28 17:22:01 100 chars.
+ layerState->type = kRaster_CanvasBackend;
+ layerState->x = layer.x();
+ layerState->y = layer.y();
+ layerState->width = bitmap.width();
+ layerState->height = bitmap.height();
+
+ layerState->content.raster.config =
scroggo 2013/08/28 17:22:01 nit: I find a switch statement much more readable
+ bitmap.config() == SkBitmap::kARGB_8888_Config ? kARGB_8888_RasterConfig :
+ bitmap.config() == SkBitmap::kARGB_4444_Config ? kARGB_4444_RasterConfig :
mtklein 2013/08/28 18:20:46 Can't we just screw 4444 now?
+ bitmap.config() == SkBitmap::kRGB_565_Config ? kRGB_565_RasterConfig : -1;
mtklein 2013/08/28 18:20:46 return NULL on the failure case?
djsollen 2013/08/29 15:09:19 Done.
+ layerState->content.raster.rowBytes = bitmap.rowBytes();
+ layerState->content.raster.pixels = bitmap.getPixels();
+
+ if (layerState->content.raster.config < 0) {
mtklein 2013/08/28 18:20:46 Oh. :) Maybe move this up with the rest of raste
+ return NULL;
+ }
+
+ setup_MC_state(&layerState->mcState, layer.matrix(), layer.clip());
+ canvasState->layerCount++;
+ }
+
+ // allocate memory for the layers and then and copy them to the struct
+ canvasState->layers = (SkCanvasLayerState*) sk_malloc_throw(layerWriter.size());
mtklein 2013/08/28 18:20:46 Slight paranoia... add an assert that layerWriter.
djsollen 2013/08/29 15:09:19 Done.
+ layerWriter.flatten(canvasState->layers);
+
+ // for now, just ignore any client supplied DrawFilter.
+ if (canvas->getDrawFilter()) {
+ SkDEBUGF(("CaptureCanvasState will ignore the canvas' draw filter."));
+ }
+
+ return canvasState.release();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static void setup_canvas_from_MC_state(const SkMCState& state, SkCanvas* canvas) {
+ // reconstruct the matrix
+ SkMatrix matrix;
+ for (int i = 0; i < 9; i++) {
+ matrix.set(i, state.matrix[i]);
+ }
+
+ // reconstruct the clip
+ SkRegion clip;
+ for (int i = 0; i < state.clipRectCount; ++i) {
+ const int32_t rect_offset = i * 4;
+ clip.op(SkIRect::MakeLTRB(state.clipRects[rect_offset + 0],
+ state.clipRects[rect_offset + 1],
+ state.clipRects[rect_offset + 2],
+ state.clipRects[rect_offset + 3]),
+ SkRegion::kUnion_Op);
mtklein 2013/08/28 18:20:46 dedent SkRegion::kUnion_Op to line up with SkIRect
djsollen 2013/08/29 15:09:19 Done.
+ }
+
+ canvas->setMatrix(matrix);
+ canvas->setClipRegion(clip);
+}
+
+static SkCanvas* create_canvas_from_canvas_layer(const SkCanvasLayerState& layerState) {
+ SkASSERT(kRaster_CanvasBackend == layerState.type);
+
+ SkBitmap bitmap;
+ SkBitmap::Config config =
+ layerState.content.raster.config == kARGB_8888_RasterConfig ? SkBitmap::kARGB_8888_Config :
+ layerState.content.raster.config == kRGB_565_RasterConfig ? SkBitmap::kRGB_565_Config :
+ SkBitmap::kNo_Config;
mtklein 2013/08/28 18:20:46 Why the asymmetry for 4444? We let a 4444 canvas
djsollen 2013/08/29 15:09:19 Done.
+
+ if (config == SkBitmap::kNo_Config) {
+ return NULL;
+ }
+
+ bitmap.setConfig(config, layerState.width, layerState.height,
+ layerState.content.raster.rowBytes);
+ bitmap.setPixels(layerState.content.raster.pixels);
+
+ SkASSERT(!bitmap.empty());
+ SkASSERT(!bitmap.isNull());
+
+ // create a device & canvas
+ SkAutoTUnref<SkDevice> device(SkNEW_ARGS(SkDevice, (bitmap)));
+ SkAutoTUnref<SkCanvas> canvas(SkNEW_ARGS(SkCanvas, (device.get())));
mtklein 2013/08/28 18:20:46 Given that you don't otherwise use the device, and
scroggo 2013/08/28 20:42:02 The SkCanvas constructor will ref the device, so w
+
+ // setup the matrix and clip
+ setup_canvas_from_MC_state(layerState.mcState, canvas.get());
+
+ return canvas.detach();
+}
+
+SkCanvas* SkCanvasUtils::CreateFromCanvasState(SkCanvasState* state) {
scroggo 2013/08/28 17:22:01 Should we check for a NULL state?
+
+ // check that the versions match
+ if (CANVAS_STATE_VERSION != state->version) {
+ SkDebugf("CreateFromCanvasState version does not match the one use to create the input");
+ return NULL;
+ }
+
+ if (state->layerCount < 1)
scroggo 2013/08/28 17:22:01 Braces.
+ return NULL;
scroggo 2013/08/28 17:22:01 4 space tabs
+
+ SkAutoTUnref<SkNWayCanvas> canvas(SkNEW_ARGS(SkNWayCanvas, (state->width, state->height)));
+
+ // setup the matrix and clip on the n-way canvas
+ setup_canvas_from_MC_state(state->mcState, canvas);
+
+ // Iterate over the layers and add them to the n-way canvas
+ for (int i = 0; i < state->layerCount; ++i) {
+ SkAutoTUnref<SkCanvas> canvasLayer(create_canvas_from_canvas_layer(state->layers[i]));
+ if (!canvasLayer.get()) {
+ return NULL;
+ }
+ canvas->addCanvas(canvasLayer.get());
mtklein 2013/08/28 18:20:46 Would it be effectively the same to call canvasLay
scroggo 2013/08/28 20:42:02 No, addCanvas refs it, so we want canvasLayer to g
djsollen 2013/08/29 15:09:19 the detach would run first which would push the re
+ }
+
+ return canvas.detach();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void SkCanvasUtils::ReleaseCanvasState(SkCanvasState* state) {
+ SkDELETE(state);
+}
« include/utils/SkCanvasUtils.h ('K') | « include/utils/SkCanvasUtils.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698