Index: src/utils/SkCanvasStateUtils.cpp |
diff --git a/src/utils/SkCanvasStateUtils.cpp b/src/utils/SkCanvasStateUtils.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..8abab6b5ae19805c9703b7c9e8833e1c76b88d1a |
--- /dev/null |
+++ b/src/utils/SkCanvasStateUtils.cpp |
@@ -0,0 +1,339 @@ |
+/* |
+ * 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 "SkCanvasStateUtils.h" |
+#include "SkCanvas.h" |
+#include "SkDevice.h" |
+#include "SkNWayCanvas.h" |
+#include "SkWriter32.h" |
+ |
+#define CANVAS_STATE_VERSION 1 |
+/* |
+ * WARNING: The structs below are part of a stable ABI and as such we explicitly |
+ * use unambigious primitives (e.g. int32_t instead of an enum). |
+ * |
+ * ANY CHANGES TO THE STRUCTS BELOW THAT IMPACT THE ABI SHOULD RESULT IN AN |
+ * UPDATE OF THE CANVAS_STATE_VERSION. SUCH CHANGES SHOULD ONLY BE MADE IF |
+ * ABSOLUTELY NECESSARY! |
+ */ |
+enum RasterConfigs { |
+ kUnknown_RasterConfig = 0, |
+ kRGB_565_RasterConfig = 1, |
+ kARGB_8888_RasterConfig = 2 |
+}; |
+typedef int32_t RasterConfig; |
+ |
+enum CanvasBackends { |
+ kUnknown_CanvasBackend = 0, |
+ kRaster_CanvasBackend = 1, |
+ kGPU_CanvasBackend = 2, |
+ kPDF_CanvasBackend = 3 |
+}; |
+typedef int32_t CanvasBackend; |
+ |
+struct ClipRect { |
+ int32_t left, top, right, bottom; |
+}; |
+ |
+struct SkMCState { |
+ float matrix[9]; |
+ // NOTE: this only works for non-antialiased clips |
+ int32_t clipRectCount; |
+ ClipRect* clipRects; |
+}; |
+ |
+// NOTE: If you add more members, bump CanvasState::version. |
+struct SkCanvasLayerState { |
+ CanvasBackend type; |
+ int32_t x, y; |
+ int32_t width; |
+ int32_t height; |
+ |
+ SkMCState mcState; |
+ |
+ union { |
+ struct { |
+ RasterConfig config; // pixel format: a value from RasterConfigs. |
+ uint32_t rowBytes; // Number of bytes from start of one line to next. |
+ void* pixels; // The pixels, all (height * rowBytes) of them. |
+ } raster; |
+ struct { |
+ int32_t textureID; |
+ } gpu; |
+ }; |
+}; |
+ |
+class SkCanvasState { |
+public: |
+ SkCanvasState(SkCanvas* canvas) { |
+ SkASSERT(canvas); |
+ version = CANVAS_STATE_VERSION; |
+ width = canvas->getDeviceSize().width(); |
+ height = canvas->getDeviceSize().height(); |
+ layerCount = 0; |
+ layers = NULL; |
+ originalCanvas = SkRef(canvas); |
+ |
+ mcState.clipRectCount = 0; |
+ mcState.clipRects = 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); |
+ sk_free(layers); |
+ |
+ // it is now safe to free the canvas since there should be no remaining |
+ // references to the content that is referenced by this canvas (e.g. pixels) |
+ originalCanvas->unref(); |
+ } |
+ |
+ /** |
+ * The version this struct was built with. This field must always appear |
+ * first in the struct so that when the versions don't match (and the |
+ * remaining contents and size are potentially different) we can still |
+ * compare the version numbers. |
+ */ |
+ int32_t version; |
+ |
+ int32_t width; |
+ int32_t height; |
+ |
+ SkMCState mcState; |
+ |
+ int32_t layerCount; |
+ SkCanvasLayerState* layers; |
+ |
+private: |
+ SkCanvas* originalCanvas; |
+}; |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+ |
+class ClipValidator : public SkCanvas::ClipVisitor { |
+public: |
+ ClipValidator() : fFailed(false) {} |
+ bool failed() { return fFailed; } |
+ |
+ // ClipVisitor |
+ virtual void clipRect(const SkRect& rect, SkRegion::Op op, bool antialias) SK_OVERRIDE { |
+ fFailed |= antialias; |
+ } |
+ |
+ virtual void clipPath(const SkPath&, SkRegion::Op, bool antialias) SK_OVERRIDE { |
+ fFailed |= antialias; |
+ } |
+ |
+private: |
+ bool fFailed; |
+}; |
+ |
+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 |
+ * |
+ * storage is allocated on the stack for the first 4 rects. This value was |
+ * chosen somewhat arbitrarily, but does allow us to represent simple clips |
+ * and some more common complex clips (e.g. a clipRect with a sub-rect |
+ * clipped out of its interior) without needing to malloc any additional memory. |
+ */ |
+ const int clipBufferSize = 4 * sizeof(ClipRect); |
+ char clipBuffer[clipBufferSize]; |
+ SkWriter32 clipWriter(sizeof(ClipRect), 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()) { |
+ // this assumes the SkIRect is stored in l,t,r,b ordering which |
+ // matches the ordering of our ClipRect struct |
+ clipWriter.writeIRect(clip_iterator.rect()); |
+ state->clipRectCount++; |
+ } |
+ } |
+ |
+ // allocate memory for the clip then and copy them to the struct |
+ state->clipRects = (ClipRect*) sk_malloc_throw(clipWriter.size()); |
+ clipWriter.flatten(state->clipRects); |
+} |
+ |
+ |
+ |
+SkCanvasState* SkCanvasStateUtils::CaptureCanvasState(SkCanvas* canvas) { |
+ SkASSERT(canvas); |
+ |
+ // Check the clip can be decomposed into rectangles (i.e. no soft clips). |
+ ClipValidator validator; |
+ canvas->replayClips(&validator); |
+ if (validator.failed()) { |
+ SkDEBUGF(("CaptureCanvasState does not support canvases with antialiased clips.\n")); |
+ return NULL; |
+ } |
+ |
+ SkAutoTDelete<SkCanvasState> canvasState(SkNEW_ARGS(SkCanvasState, (canvas))); |
+ |
+ // decompose the total matrix and clip |
+ setup_MC_state(&canvasState->mcState, canvas->getTotalMatrix(), canvas->getTotalClip()); |
+ |
+ /* |
+ * decompose the layers |
+ * |
+ * storage is allocated on the stack for the first 3 layers. It is common in |
+ * some view systems (e.g. Android) that a few non-clipped layers are present |
+ * and we will not need to malloc any additional memory in those cases. |
+ */ |
+ const int layerBufferSize = 3 * sizeof(SkCanvasLayerState); |
+ char layerBuffer[layerBufferSize]; |
+ SkWriter32 layerWriter(sizeof(SkCanvasLayerState), layerBuffer, layerBufferSize); |
+ int layerCount = 0; |
+ for (SkCanvas::LayerIter layer(canvas, true/*skipEmptyClips*/); !layer.done(); layer.next()) { |
+ |
+ // we currently only work for bitmap backed devices |
+ const SkBitmap& bitmap = layer.device()->accessBitmap(true/*changePixels*/); |
+ if (bitmap.empty() || bitmap.isNull() || !bitmap.lockPixelsAreWritable()) { |
+ return NULL; |
+ } |
+ |
+ SkCanvasLayerState* layerState = |
+ (SkCanvasLayerState*) layerWriter.reserve(sizeof(SkCanvasLayerState)); |
+ layerState->type = kRaster_CanvasBackend; |
+ layerState->x = layer.x(); |
+ layerState->y = layer.y(); |
+ layerState->width = bitmap.width(); |
+ layerState->height = bitmap.height(); |
+ |
+ switch (bitmap.config()) { |
+ case SkBitmap::kARGB_8888_Config: |
+ layerState->raster.config = kARGB_8888_RasterConfig; |
+ break; |
+ case SkBitmap::kRGB_565_Config: |
+ layerState->raster.config = kRGB_565_RasterConfig; |
+ break; |
+ default: |
+ return NULL; |
+ } |
+ layerState->raster.rowBytes = bitmap.rowBytes(); |
+ layerState->raster.pixels = bitmap.getPixels(); |
+ |
+ setup_MC_state(&layerState->mcState, layer.matrix(), layer.clip()); |
+ layerCount++; |
+ } |
+ |
+ // allocate memory for the layers and then and copy them to the struct |
+ SkASSERT(layerWriter.size() == layerCount * sizeof(SkCanvasLayerState)); |
+ canvasState->layerCount = layerCount; |
+ canvasState->layers = (SkCanvasLayerState*) sk_malloc_throw(layerWriter.size()); |
+ layerWriter.flatten(canvasState->layers); |
+ |
+ // for now, just ignore any client supplied DrawFilter. |
+ if (canvas->getDrawFilter()) { |
+ SkDEBUGF(("CaptureCanvasState will ignore the canvases draw filter.\n")); |
+ } |
+ |
+ return canvasState.detach(); |
+} |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+ |
+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) { |
+ clip.op(SkIRect::MakeLTRB(state.clipRects[i].left, |
+ state.clipRects[i].top, |
+ state.clipRects[i].right, |
+ state.clipRects[i].bottom), |
+ SkRegion::kUnion_Op); |
+ } |
+ |
+ 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.raster.config == kARGB_8888_RasterConfig ? SkBitmap::kARGB_8888_Config : |
+ layerState.raster.config == kRGB_565_RasterConfig ? SkBitmap::kRGB_565_Config : |
+ SkBitmap::kNo_Config; |
+ |
+ if (config == SkBitmap::kNo_Config) { |
+ return NULL; |
+ } |
+ |
+ bitmap.setConfig(config, layerState.width, layerState.height, |
+ layerState.raster.rowBytes); |
+ bitmap.setPixels(layerState.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()))); |
+ |
+ // setup the matrix and clip |
+ setup_canvas_from_MC_state(layerState.mcState, canvas.get()); |
+ |
+ return canvas.detach(); |
+} |
+ |
+SkCanvas* SkCanvasStateUtils::CreateFromCanvasState(const SkCanvasState* state) { |
+ SkASSERT(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) { |
+ return NULL; |
+ } |
+ |
+ 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()); |
+ } |
+ |
+ return canvas.detach(); |
+} |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+ |
+void SkCanvasStateUtils::ReleaseCanvasState(SkCanvasState* state) { |
+ SkDELETE(state); |
+} |