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); |
+} |