blob: 1bd718d792a568c58cbda61f94a48cb6d660e68d [file] [log] [blame]
// Copyright 2015 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 "platform/graphics/paint/PaintArtifactToSkCanvas.h"
#include "platform/graphics/paint/ClipPaintPropertyNode.h"
#include "platform/graphics/paint/DrawingDisplayItem.h"
#include "platform/graphics/paint/EffectPaintPropertyNode.h"
#include "platform/graphics/paint/PaintArtifact.h"
#include "platform/graphics/paint/PaintChunk.h"
#include "platform/graphics/paint/TransformPaintPropertyNode.h"
#include "platform/graphics/skia/SkiaUtils.h"
#include "platform/transforms/AffineTransform.h"
#include "platform/transforms/TransformationMatrix.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkPicture.h"
#include "third_party/skia/include/core/SkPictureRecorder.h"
namespace blink {
namespace {
void paintDisplayItemToSkCanvas(const DisplayItem& displayItem, SkCanvas* canvas)
{
DisplayItem::Type type = displayItem.getType();
if (DisplayItem::isDrawingType(type)) {
canvas->drawPicture(static_cast<const DrawingDisplayItem&>(displayItem).picture());
return;
}
}
} // namespace
// Compute the list of nodes from 'a' to commonAncestor(a, b) and 'b' to
// commonAncestor(a, b), exclusive.
//
// For the following tree:
// _root_
// | |
// a d
// | |
// b e
// |
// c
// The common ancestor of 'c' and 'e' is 'root'. The paths would be:
// path from c to commonAncestor(c,e) = [b,a]
// path from e to commonAncestor(c,e) = [d]
static void computePathsToCommonAncestor(const EffectPaintPropertyNode* a, const EffectPaintPropertyNode* b, Vector<const EffectPaintPropertyNode*>& aToCommonAncestor, Vector<const EffectPaintPropertyNode*>& bToCommonAncestor)
{
// Calculate the path from a -> root.
const EffectPaintPropertyNode* current = a;
while (current) {
aToCommonAncestor.append(current);
current = current->parent();
}
// Calculate the path from b -> root.
current = b;
while (current) {
bToCommonAncestor.append(current);
current = current->parent();
}
// Pop nodes from root -> a and root -> b while the last node is equal so we
// are left with just the common ancestor path, not including the common
// ancestor itself.
while (aToCommonAncestor.size() && bToCommonAncestor.size() && aToCommonAncestor.last() == bToCommonAncestor.last()) {
aToCommonAncestor.removeLast();
bToCommonAncestor.removeLast();
}
}
static void applyEffectNodesToCanvas(const EffectPaintPropertyNode* previousEffect, const EffectPaintPropertyNode* currentEffect, SkCanvas* canvas)
{
if (previousEffect == currentEffect)
return;
Vector<const EffectPaintPropertyNode*> effectsToUnapply;
Vector<const EffectPaintPropertyNode*> effectsToApply;
computePathsToCommonAncestor(previousEffect, currentEffect, effectsToUnapply, effectsToApply);
size_t popEffectCount = effectsToUnapply.size();
for (size_t popEffect = 0; popEffect < popEffectCount; popEffect++)
canvas->restore();
for (Vector<const EffectPaintPropertyNode*>::reverse_iterator it = effectsToApply.rbegin(); it != effectsToApply.rend(); ++it) {
const EffectPaintPropertyNode* node = *it;
ASSERT(node);
SkPaint layerPaint;
layerPaint.setAlpha(static_cast<unsigned char>(node->opacity() * 255));
canvas->saveLayer(nullptr, &layerPaint);
}
}
static TransformationMatrix totalTransform(const TransformPaintPropertyNode* currentSpace)
{
TransformationMatrix matrix;
for (; currentSpace; currentSpace = currentSpace->parent()) {
TransformationMatrix localMatrix = currentSpace->matrix();
localMatrix.applyTransformOrigin(currentSpace->origin());
matrix = localMatrix * matrix;
}
return matrix;
}
void paintArtifactToSkCanvas(const PaintArtifact& artifact, SkCanvas* canvas)
{
SkAutoCanvasRestore restore(canvas, true);
const DisplayItemList& displayItems = artifact.getDisplayItemList();
const EffectPaintPropertyNode* previousEffect = nullptr;
for (const PaintChunk& chunk : artifact.paintChunks()) {
// Setup the canvas clip state first because it clobbers matrix state.
for (const ClipPaintPropertyNode* currentClipNode = chunk.properties.clip.get();
currentClipNode; currentClipNode = currentClipNode->parent()) {
canvas->setMatrix(TransformationMatrix::toSkMatrix44(totalTransform(currentClipNode->localTransformSpace())));
canvas->clipRRect(currentClipNode->clipRect());
}
// Set the canvas state to match the paint properties.
TransformationMatrix combinedMatrix = totalTransform(chunk.properties.transform.get());
canvas->setMatrix(TransformationMatrix::toSkMatrix44(combinedMatrix));
// Push and pop layers on the SkCanvas as necessary to implement the
// current effect.
// TODO(pdr): This will need to be revisited for non-opacity effects
// such as filters which require interleaving with transforms.
const EffectPaintPropertyNode* chunkEffect = chunk.properties.effect.get();
applyEffectNodesToCanvas(previousEffect, chunkEffect, canvas);
previousEffect = chunkEffect;
// Draw the display items in the paint chunk.
for (const auto& displayItem : displayItems.itemsInPaintChunk(chunk))
paintDisplayItemToSkCanvas(displayItem, canvas);
}
}
sk_sp<const SkPicture> paintArtifactToSkPicture(const PaintArtifact& artifact, const SkRect& bounds)
{
SkPictureRecorder recorder;
SkCanvas* canvas = recorder.beginRecording(bounds);
paintArtifactToSkCanvas(artifact, canvas);
return recorder.finishRecordingAsPicture();
}
} // namespace blink