| /* |
| * Copyright 2012, The Android Open Source Project |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #define LOG_TAG "PlatformGraphicsContextRecording" |
| #define LOG_NDEBUG 1 |
| |
| #include "config.h" |
| #include "PlatformGraphicsContextRecording.h" |
| |
| #include "AndroidLog.h" |
| #include "FloatRect.h" |
| #include "FloatQuad.h" |
| #include "Font.h" |
| #include "GraphicsContext.h" |
| #include "GraphicsOperation.h" |
| #include "PlatformGraphicsContextSkia.h" |
| #include "RTree.h" |
| #include "SkDevice.h" |
| |
| #include "wtf/NonCopyingSort.h" |
| #include "wtf/HashSet.h" |
| #include "wtf/StringHasher.h" |
| |
| #include <utils/LinearAllocator.h> |
| |
| #define NEW_OP(X) new (heap()) GraphicsOperation::X |
| |
| #define USE_CLIPPING_PAINTER true |
| |
| // Operations smaller than this area aren't considered opaque, and thus don't |
| // clip operations below. Chosen empirically. |
| #define MIN_TRACKED_OPAQUE_AREA 750 |
| |
| // Cap on ClippingPainter's recursive depth. Chosen empirically. |
| #define MAX_CLIPPING_RECURSION_COUNT 400 |
| |
| namespace WebCore { |
| |
| static FloatRect approximateTextBounds(size_t numGlyphs, |
| const SkPoint pos[], const SkPaint& paint) |
| { |
| if (!numGlyphs || !pos) { |
| return FloatRect(); |
| } |
| |
| // get glyph position bounds |
| SkScalar minX = pos[0].x(); |
| SkScalar maxX = minX; |
| SkScalar minY = pos[0].y(); |
| SkScalar maxY = minY; |
| for (size_t i = 1; i < numGlyphs; ++i) { |
| SkScalar x = pos[i].x(); |
| SkScalar y = pos[i].y(); |
| minX = std::min(minX, x); |
| maxX = std::max(maxX, x); |
| minY = std::min(minY, y); |
| maxY = std::max(maxY, y); |
| } |
| |
| // build final rect |
| SkPaint::FontMetrics metrics; |
| SkScalar bufY = paint.getFontMetrics(&metrics); |
| SkScalar bufX = bufY * 2; |
| SkScalar adjY = metrics.fAscent / 2; |
| minY += adjY; |
| maxY += adjY; |
| SkRect rect; |
| rect.set(minX - bufX, minY - bufY, maxX + bufX, maxY + bufY); |
| return rect; |
| } |
| |
| class StateHash { |
| public: |
| static unsigned hash(PlatformGraphicsContext::State* const& state) |
| { |
| return StringHasher::hashMemory(state, sizeof(PlatformGraphicsContext::State)); |
| } |
| |
| static bool equal(PlatformGraphicsContext::State* const& a, |
| PlatformGraphicsContext::State* const& b) |
| { |
| return a && b && !memcmp(a, b, sizeof(PlatformGraphicsContext::State)); |
| } |
| |
| static const bool safeToCompareToEmptyOrDeleted = false; |
| }; |
| |
| class SkPaintHash { |
| public: |
| static unsigned hash(const SkPaint* const& paint) |
| { |
| return StringHasher::hashMemory(paint, sizeof(SkPaint)); |
| } |
| |
| static bool equal(const SkPaint* const& a, |
| const SkPaint* const& b) |
| { |
| return a && b && (*a == *b); |
| } |
| |
| static const bool safeToCompareToEmptyOrDeleted = false; |
| }; |
| |
| typedef HashSet<PlatformGraphicsContext::State*, StateHash> StateHashSet; |
| typedef HashSet<const SkPaint*, SkPaintHash> SkPaintHashSet; |
| |
| class CanvasState { |
| public: |
| CanvasState(CanvasState* parent) |
| : m_parent(parent) |
| , m_isTransparencyLayer(false) |
| {} |
| |
| CanvasState(CanvasState* parent, float opacity) |
| : m_parent(parent) |
| , m_isTransparencyLayer(true) |
| , m_opacity(opacity) |
| {} |
| |
| ~CanvasState() { |
| ALOGV("Delete %p", this); |
| for (size_t i = 0; i < m_operations.size(); i++) |
| m_operations[i]->~RecordingData(); |
| m_operations.clear(); |
| } |
| |
| bool isParentOf(CanvasState* other) { |
| while (other->m_parent) { |
| if (other->m_parent == this) |
| return true; |
| other = other->m_parent; |
| } |
| return false; |
| } |
| |
| void playback(PlatformGraphicsContext* context, size_t fromId, size_t toId) const { |
| ALOGV("playback %p from %d->%d", this, fromId, toId); |
| for (size_t i = 0; i < m_operations.size(); i++) { |
| RecordingData *data = m_operations[i]; |
| if (data->m_orderBy < fromId) |
| continue; |
| if (data->m_orderBy > toId) |
| break; |
| ALOGV("Applying operation[%d] %p->%s()", i, data->m_operation, |
| data->m_operation->name()); |
| data->m_operation->apply(context); |
| } |
| } |
| |
| CanvasState* parent() { return m_parent; } |
| |
| void enterState(PlatformGraphicsContext* context) { |
| ALOGV("enterState %p", this); |
| if (m_isTransparencyLayer) |
| context->beginTransparencyLayer(m_opacity); |
| else |
| context->save(); |
| } |
| |
| void exitState(PlatformGraphicsContext* context) { |
| ALOGV("exitState %p", this); |
| if (m_isTransparencyLayer) |
| context->endTransparencyLayer(); |
| else |
| context->restore(); |
| } |
| |
| void adoptAndAppend(RecordingData* data) { |
| m_operations.append(data); |
| } |
| |
| bool isTransparencyLayer() { |
| return m_isTransparencyLayer; |
| } |
| |
| void* operator new(size_t size, android::LinearAllocator* la) { |
| return la->alloc(size); |
| } |
| |
| private: |
| CanvasState *m_parent; |
| bool m_isTransparencyLayer; |
| float m_opacity; |
| Vector<RecordingData*> m_operations; |
| }; |
| |
| class RecordingImpl { |
| private: |
| // Careful, ordering matters here. Ordering is first constructed == last destroyed, |
| // so we have to make sure our Heap is the first thing listed so that it is |
| // the last thing destroyed. |
| android::LinearAllocator m_heap; |
| public: |
| RecordingImpl() |
| : m_tree(&m_heap) |
| , m_nodeCount(0) |
| { |
| } |
| |
| ~RecordingImpl() { |
| clearStates(); |
| clearCanvasStates(); |
| clearSkPaints(); |
| } |
| |
| PlatformGraphicsContext::State* getState(PlatformGraphicsContext::State* inState) { |
| StateHashSet::iterator it = m_states.find(inState); |
| if (it != m_states.end()) |
| return (*it); |
| void* buf = heap()->alloc(sizeof(PlatformGraphicsContext::State)); |
| PlatformGraphicsContext::State* state = new (buf) PlatformGraphicsContext::State(*inState); |
| m_states.add(state); |
| return state; |
| } |
| |
| const SkPaint* getSkPaint(const SkPaint& inPaint) { |
| SkPaintHashSet::iterator it = m_paints.find(&inPaint); |
| if (it != m_paints.end()) |
| return (*it); |
| void* buf = heap()->alloc(sizeof(SkPaint)); |
| SkPaint* paint = new (buf) SkPaint(inPaint); |
| m_paints.add(paint); |
| return paint; |
| } |
| |
| void addCanvasState(CanvasState* state) { |
| m_canvasStates.append(state); |
| } |
| |
| void removeCanvasState(const CanvasState* state) { |
| if (m_canvasStates.last() == state) |
| m_canvasStates.removeLast(); |
| else { |
| size_t indx = m_canvasStates.find(state); |
| m_canvasStates.remove(indx); |
| } |
| } |
| |
| void applyState(PlatformGraphicsContext* context, |
| CanvasState* fromState, size_t fromId, |
| CanvasState* toState, size_t toId) { |
| ALOGV("applyState(%p->%p, %d-%d)", fromState, toState, fromId, toId); |
| if (fromState != toState && fromState) { |
| if (fromState->isParentOf(toState)) { |
| // Going down the tree, playback any parent operations then save |
| // before playing back our current operations |
| applyState(context, fromState, fromId, toState->parent(), toId); |
| toState->enterState(context); |
| } else if (toState->isParentOf(fromState)) { |
| // Going up the tree, pop some states |
| while (fromState != toState) { |
| fromState->exitState(context); |
| fromState = fromState->parent(); |
| } |
| } else { |
| // Siblings in the tree |
| fromState->exitState(context); |
| applyState(context, fromState->parent(), fromId, toState, toId); |
| return; |
| } |
| } else if (!fromState) { |
| if (toState->parent()) |
| applyState(context, fromState, fromId, toState->parent(), toId); |
| toState->enterState(context); |
| } |
| toState->playback(context, fromId, toId); |
| } |
| |
| android::LinearAllocator* heap() { return &m_heap; } |
| |
| RTree::RTree m_tree; |
| int m_nodeCount; |
| |
| void dumpMemoryStats() { |
| static const char* PREFIX = " "; |
| ALOGD("Heap:"); |
| m_heap.dumpMemoryStats(PREFIX); |
| } |
| |
| private: |
| |
| void clearStates() { |
| StateHashSet::iterator end = m_states.end(); |
| for (StateHashSet::iterator it = m_states.begin(); it != end; ++it) |
| (*it)->~State(); |
| m_states.clear(); |
| } |
| |
| void clearSkPaints() { |
| SkPaintHashSet::iterator end = m_paints.end(); |
| for (SkPaintHashSet::iterator it = m_paints.begin(); it != end; ++it) |
| (*it)->~SkPaint(); |
| m_paints.clear(); |
| } |
| |
| void clearCanvasStates() { |
| for (size_t i = 0; i < m_canvasStates.size(); i++) |
| m_canvasStates[i]->~CanvasState(); |
| m_canvasStates.clear(); |
| } |
| |
| StateHashSet m_states; |
| SkPaintHashSet m_paints; |
| Vector<CanvasState*> m_canvasStates; |
| }; |
| |
| Recording::~Recording() |
| { |
| delete m_recording; |
| } |
| |
| static bool CompareRecordingDataOrder(const RecordingData* a, const RecordingData* b) |
| { |
| return a->m_orderBy < b->m_orderBy; |
| } |
| |
| static IntRect enclosedIntRect(const FloatRect& rect) |
| { |
| float left = ceilf(rect.x()); |
| float top = ceilf(rect.y()); |
| float width = floorf(rect.maxX()) - left; |
| float height = floorf(rect.maxY()) - top; |
| |
| return IntRect(clampToInteger(left), clampToInteger(top), |
| clampToInteger(width), clampToInteger(height)); |
| } |
| |
| #if USE_CLIPPING_PAINTER |
| class ClippingPainter { |
| public: |
| ClippingPainter(RecordingImpl* recording, |
| PlatformGraphicsContextSkia& context, |
| const SkMatrix& initialMatrix, |
| Vector<RecordingData*> &nodes) |
| : m_recording(recording) |
| , m_context(context) |
| , m_initialMatrix(initialMatrix) |
| , m_nodes(nodes) |
| , m_lastOperationId(0) |
| , m_currState(0) |
| {} |
| |
| void draw(const SkIRect& bounds) { |
| drawWithClipRecursive(static_cast<int>(m_nodes.size()) - 1, bounds, 0); |
| |
| while (m_currState) { |
| m_currState->exitState(&m_context); |
| m_currState = m_currState->parent(); |
| } |
| } |
| |
| private: |
| void drawOperation(RecordingData* node, const SkRegion* uncovered) |
| { |
| GraphicsOperation::Operation* op = node->m_operation; |
| m_recording->applyState(&m_context, m_currState, |
| m_lastOperationId, op->m_canvasState, node->m_orderBy); |
| m_currState = op->m_canvasState; |
| m_lastOperationId = node->m_orderBy; |
| |
| // if other opaque operations will cover the current one, clip that area out |
| // (and restore the clip immediately after drawing) |
| if (uncovered) { |
| m_context.save(); |
| m_context.canvas()->clipRegion(*uncovered, SkRegion::kIntersect_Op); |
| } |
| op->apply(&(m_context)); |
| if (uncovered) |
| m_context.restore(); |
| } |
| |
| void drawWithClipRecursive(int index, const SkIRect& bounds, const SkRegion* uncovered) |
| { |
| if (index < 0) |
| return; |
| RecordingData* recordingData = m_nodes[index]; |
| GraphicsOperation::Operation* op = recordingData->m_operation; |
| if (index != 0) { |
| const IntRect* opaqueRect = op->opaqueRect(); |
| if (!opaqueRect || opaqueRect->isEmpty()) { |
| drawWithClipRecursive(index - 1, bounds, uncovered); |
| } else { |
| SkRegion newUncovered; |
| if (uncovered) |
| newUncovered = *uncovered; |
| else |
| newUncovered = SkRegion(bounds); |
| |
| SkRect mappedRect = *opaqueRect; |
| m_initialMatrix.mapRect(&mappedRect); |
| newUncovered.op(enclosedIntRect(mappedRect), SkRegion::kDifference_Op); |
| if (!newUncovered.isEmpty()) |
| drawWithClipRecursive(index - 1, bounds, &newUncovered); |
| } |
| } |
| |
| if (!uncovered || !uncovered->isEmpty()) |
| drawOperation(recordingData, uncovered); |
| } |
| |
| RecordingImpl* m_recording; |
| PlatformGraphicsContextSkia& m_context; |
| const SkMatrix& m_initialMatrix; |
| const Vector<RecordingData*>& m_nodes; |
| size_t m_lastOperationId; |
| CanvasState* m_currState; |
| }; |
| #endif // USE_CLIPPING_PAINTER |
| |
| void Recording::draw(SkCanvas* canvas) |
| { |
| if (!m_recording) { |
| ALOGW("No recording!"); |
| return; |
| } |
| SkRect clip; |
| if (!canvas->getClipBounds(&clip)) { |
| ALOGW("Empty clip!"); |
| return; |
| } |
| Vector<RecordingData*> nodes; |
| |
| WebCore::IntRect iclip = enclosingIntRect(clip); |
| m_recording->m_tree.search(iclip, nodes); |
| |
| size_t count = nodes.size(); |
| ALOGV("Drawing %d nodes out of %d", count, m_recording->m_nodeCount); |
| if (count) { |
| int saveCount = canvas->getSaveCount(); |
| nonCopyingSort(nodes.begin(), nodes.end(), CompareRecordingDataOrder); |
| PlatformGraphicsContextSkia context(canvas); |
| #if USE_CLIPPING_PAINTER |
| if (canvas->getDevice() && canvas->getDevice()->config() != SkBitmap::kNo_Config |
| && count < MAX_CLIPPING_RECURSION_COUNT) { |
| ClippingPainter painter(recording(), context, canvas->getTotalMatrix(), nodes); |
| painter.draw(canvas->getTotalClip().getBounds()); |
| } else |
| #endif |
| { |
| CanvasState* currState = 0; |
| size_t lastOperationId = 0; |
| for (size_t i = 0; i < count; i++) { |
| GraphicsOperation::Operation* op = nodes[i]->m_operation; |
| m_recording->applyState(&context, currState, lastOperationId, |
| op->m_canvasState, nodes[i]->m_orderBy); |
| currState = op->m_canvasState; |
| lastOperationId = nodes[i]->m_orderBy; |
| ALOGV("apply: %p->%s()", op, op->name()); |
| op->apply(&context); |
| } |
| while (currState) { |
| currState->exitState(&context); |
| currState = currState->parent(); |
| } |
| } |
| if (saveCount != canvas->getSaveCount()) { |
| ALOGW("Save/restore mismatch! %d vs. %d", saveCount, canvas->getSaveCount()); |
| } |
| } |
| } |
| |
| void Recording::setRecording(RecordingImpl* impl) |
| { |
| if (m_recording == impl) |
| return; |
| if (m_recording) |
| delete m_recording; |
| m_recording = impl; |
| } |
| |
| //************************************** |
| // PlatformGraphicsContextRecording |
| //************************************** |
| |
| PlatformGraphicsContextRecording::PlatformGraphicsContextRecording(Recording* recording) |
| : PlatformGraphicsContext() |
| , mPicture(0) |
| , mRecording(recording) |
| , mOperationState(0) |
| , m_maxZoomScale(1) |
| , m_isEmpty(true) |
| , m_canvasProxy(this) |
| { |
| ALOGV("RECORDING: begin"); |
| if (mRecording) |
| mRecording->setRecording(new RecordingImpl()); |
| mMatrixStack.append(SkMatrix::I()); |
| mCurrentMatrix = &(mMatrixStack.last()); |
| pushStateOperation(new (heap()) CanvasState(0)); |
| } |
| |
| PlatformGraphicsContextRecording::~PlatformGraphicsContextRecording() |
| { |
| ALOGV("RECORDING: end"); |
| IF_ALOGV() |
| mRecording->recording()->dumpMemoryStats(); |
| } |
| |
| bool PlatformGraphicsContextRecording::isPaintingDisabled() |
| { |
| return !mRecording; |
| } |
| |
| SkCanvas* PlatformGraphicsContextRecording::recordingCanvas() |
| { |
| m_maxZoomScale = 1e6f; |
| return &m_canvasProxy; |
| } |
| |
| //************************************** |
| // State management |
| //************************************** |
| |
| void PlatformGraphicsContextRecording::beginTransparencyLayer(float opacity) |
| { |
| CanvasState* parent = mRecordingStateStack.last().mCanvasState; |
| pushStateOperation(new (heap()) CanvasState(parent, opacity)); |
| mRecordingStateStack.last().disableOpaqueTracking(); |
| } |
| |
| void PlatformGraphicsContextRecording::endTransparencyLayer() |
| { |
| popStateOperation(); |
| } |
| |
| void PlatformGraphicsContextRecording::save() |
| { |
| PlatformGraphicsContext::save(); |
| CanvasState* parent = mRecordingStateStack.last().mCanvasState; |
| pushStateOperation(new (heap()) CanvasState(parent)); |
| pushMatrix(); |
| } |
| |
| void PlatformGraphicsContextRecording::restore() |
| { |
| PlatformGraphicsContext::restore(); |
| popMatrix(); |
| popStateOperation(); |
| } |
| |
| //************************************** |
| // State setters |
| //************************************** |
| |
| void PlatformGraphicsContextRecording::setAlpha(float alpha) |
| { |
| PlatformGraphicsContext::setAlpha(alpha); |
| mOperationState = 0; |
| } |
| |
| void PlatformGraphicsContextRecording::setCompositeOperation(CompositeOperator op) |
| { |
| PlatformGraphicsContext::setCompositeOperation(op); |
| mOperationState = 0; |
| } |
| |
| bool PlatformGraphicsContextRecording::setFillColor(const Color& c) |
| { |
| if (PlatformGraphicsContext::setFillColor(c)) { |
| mOperationState = 0; |
| return true; |
| } |
| return false; |
| } |
| |
| bool PlatformGraphicsContextRecording::setFillShader(SkShader* fillShader) |
| { |
| if (PlatformGraphicsContext::setFillShader(fillShader)) { |
| mOperationState = 0; |
| return true; |
| } |
| return false; |
| } |
| |
| void PlatformGraphicsContextRecording::setLineCap(LineCap cap) |
| { |
| PlatformGraphicsContext::setLineCap(cap); |
| mOperationState = 0; |
| } |
| |
| void PlatformGraphicsContextRecording::setLineDash(const DashArray& dashes, float dashOffset) |
| { |
| PlatformGraphicsContext::setLineDash(dashes, dashOffset); |
| mOperationState = 0; |
| } |
| |
| void PlatformGraphicsContextRecording::setLineJoin(LineJoin join) |
| { |
| PlatformGraphicsContext::setLineJoin(join); |
| mOperationState = 0; |
| } |
| |
| void PlatformGraphicsContextRecording::setMiterLimit(float limit) |
| { |
| PlatformGraphicsContext::setMiterLimit(limit); |
| mOperationState = 0; |
| } |
| |
| void PlatformGraphicsContextRecording::setShadow(int radius, int dx, int dy, SkColor c) |
| { |
| PlatformGraphicsContext::setShadow(radius, dx, dy, c); |
| mOperationState = 0; |
| } |
| |
| void PlatformGraphicsContextRecording::setShouldAntialias(bool useAA) |
| { |
| m_state->useAA = useAA; |
| PlatformGraphicsContext::setShouldAntialias(useAA); |
| mOperationState = 0; |
| } |
| |
| bool PlatformGraphicsContextRecording::setStrokeColor(const Color& c) |
| { |
| if (PlatformGraphicsContext::setStrokeColor(c)) { |
| mOperationState = 0; |
| return true; |
| } |
| return false; |
| } |
| |
| bool PlatformGraphicsContextRecording::setStrokeShader(SkShader* strokeShader) |
| { |
| if (PlatformGraphicsContext::setStrokeShader(strokeShader)) { |
| mOperationState = 0; |
| return true; |
| } |
| return false; |
| } |
| |
| void PlatformGraphicsContextRecording::setStrokeStyle(StrokeStyle style) |
| { |
| PlatformGraphicsContext::setStrokeStyle(style); |
| mOperationState = 0; |
| } |
| |
| void PlatformGraphicsContextRecording::setStrokeThickness(float f) |
| { |
| PlatformGraphicsContext::setStrokeThickness(f); |
| mOperationState = 0; |
| } |
| |
| //************************************** |
| // Matrix operations |
| //************************************** |
| |
| void PlatformGraphicsContextRecording::concatCTM(const AffineTransform& affine) |
| { |
| mCurrentMatrix->preConcat(affine); |
| appendStateOperation(NEW_OP(ConcatCTM)(affine)); |
| } |
| |
| void PlatformGraphicsContextRecording::rotate(float angleInRadians) |
| { |
| float value = angleInRadians * (180.0f / 3.14159265f); |
| mCurrentMatrix->preRotate(SkFloatToScalar(value)); |
| appendStateOperation(NEW_OP(Rotate)(angleInRadians)); |
| } |
| |
| void PlatformGraphicsContextRecording::scale(const FloatSize& size) |
| { |
| mCurrentMatrix->preScale(SkFloatToScalar(size.width()), SkFloatToScalar(size.height())); |
| appendStateOperation(NEW_OP(Scale)(size)); |
| } |
| |
| void PlatformGraphicsContextRecording::translate(float x, float y) |
| { |
| mCurrentMatrix->preTranslate(SkFloatToScalar(x), SkFloatToScalar(y)); |
| appendStateOperation(NEW_OP(Translate)(x, y)); |
| } |
| |
| const SkMatrix& PlatformGraphicsContextRecording::getTotalMatrix() |
| { |
| return *mCurrentMatrix; |
| } |
| |
| //************************************** |
| // Clipping |
| //************************************** |
| |
| void PlatformGraphicsContextRecording::addInnerRoundedRectClip(const IntRect& rect, |
| int thickness) |
| { |
| mRecordingStateStack.last().disableOpaqueTracking(); |
| appendStateOperation(NEW_OP(InnerRoundedRectClip)(rect, thickness)); |
| } |
| |
| void PlatformGraphicsContextRecording::canvasClip(const Path& path) |
| { |
| mRecordingStateStack.last().disableOpaqueTracking(); |
| clip(path); |
| } |
| |
| bool PlatformGraphicsContextRecording::clip(const FloatRect& rect) |
| { |
| clipState(rect); |
| appendStateOperation(NEW_OP(Clip)(rect)); |
| return true; |
| } |
| |
| bool PlatformGraphicsContextRecording::clip(const Path& path) |
| { |
| mRecordingStateStack.last().disableOpaqueTracking(); |
| clipState(path.boundingRect()); |
| appendStateOperation(NEW_OP(ClipPath)(path)); |
| return true; |
| } |
| |
| bool PlatformGraphicsContextRecording::clipConvexPolygon(size_t numPoints, |
| const FloatPoint*, bool antialias) |
| { |
| // TODO |
| return true; |
| } |
| |
| bool PlatformGraphicsContextRecording::clipOut(const IntRect& r) |
| { |
| mRecordingStateStack.last().disableOpaqueTracking(); |
| appendStateOperation(NEW_OP(ClipOut)(r)); |
| return true; |
| } |
| |
| bool PlatformGraphicsContextRecording::clipOut(const Path& path) |
| { |
| mRecordingStateStack.last().disableOpaqueTracking(); |
| appendStateOperation(NEW_OP(ClipPath)(path, true)); |
| return true; |
| } |
| |
| bool PlatformGraphicsContextRecording::clipPath(const Path& pathToClip, WindRule clipRule) |
| { |
| mRecordingStateStack.last().disableOpaqueTracking(); |
| clipState(pathToClip.boundingRect()); |
| GraphicsOperation::ClipPath* operation = NEW_OP(ClipPath)(pathToClip); |
| operation->setWindRule(clipRule); |
| appendStateOperation(operation); |
| return true; |
| } |
| |
| void PlatformGraphicsContextRecording::clearRect(const FloatRect& rect) |
| { |
| appendDrawingOperation(NEW_OP(ClearRect)(rect), rect); |
| } |
| |
| //************************************** |
| // Drawing |
| //************************************** |
| |
| void PlatformGraphicsContextRecording::drawBitmapPattern( |
| const SkBitmap& bitmap, const SkMatrix& matrix, |
| CompositeOperator compositeOp, const FloatRect& destRect) |
| { |
| appendDrawingOperation( |
| NEW_OP(DrawBitmapPattern)(bitmap, matrix, compositeOp, destRect), |
| destRect); |
| } |
| |
| void PlatformGraphicsContextRecording::drawBitmapRect(const SkBitmap& bitmap, |
| const SkIRect* srcPtr, const SkRect& dst, |
| CompositeOperator op) |
| { |
| float widthScale = dst.width() == 0 ? 1 : bitmap.width() / dst.width(); |
| float heightScale = dst.height() == 0 ? 1 : bitmap.height() / dst.height(); |
| m_maxZoomScale = std::max(m_maxZoomScale, std::max(widthScale, heightScale)); |
| // null src implies full bitmap as source rect |
| SkIRect src = srcPtr ? *srcPtr : SkIRect::MakeWH(bitmap.width(), bitmap.height()); |
| appendDrawingOperation(NEW_OP(DrawBitmapRect)(bitmap, src, dst, op), dst); |
| } |
| |
| void PlatformGraphicsContextRecording::drawConvexPolygon(size_t numPoints, |
| const FloatPoint* points, |
| bool shouldAntialias) |
| { |
| if (numPoints < 1) return; |
| if (numPoints != 4) { |
| // TODO: Build a path and call draw on that (webkit currently never calls this) |
| ALOGW("drawConvexPolygon with numPoints != 4 is not supported!"); |
| return; |
| } |
| FloatRect bounds; |
| bounds.fitToPoints(points[0], points[1], points[2], points[3]); |
| appendDrawingOperation(NEW_OP(DrawConvexPolygonQuad)(points, shouldAntialias), bounds); |
| } |
| |
| void PlatformGraphicsContextRecording::drawEllipse(const IntRect& rect) |
| { |
| appendDrawingOperation(NEW_OP(DrawEllipse)(rect), rect); |
| } |
| |
| void PlatformGraphicsContextRecording::drawFocusRing(const Vector<IntRect>& rects, |
| int width, int offset, |
| const Color& color) |
| { |
| if (!rects.size()) |
| return; |
| IntRect bounds = rects[0]; |
| for (size_t i = 1; i < rects.size(); i++) |
| bounds.unite(rects[i]); |
| appendDrawingOperation(NEW_OP(DrawFocusRing)(rects, width, offset, color), bounds); |
| } |
| |
| void PlatformGraphicsContextRecording::drawHighlightForText( |
| const Font& font, const TextRun& run, const FloatPoint& point, int h, |
| const Color& backgroundColor, ColorSpace colorSpace, int from, |
| int to, bool isActive) |
| { |
| IntRect rect = (IntRect)font.selectionRectForText(run, point, h, from, to); |
| if (isActive) |
| fillRect(rect, backgroundColor); |
| else { |
| int x = rect.x(), y = rect.y(), w = rect.width(), h = rect.height(); |
| const int t = 3, t2 = t * 2; |
| |
| fillRect(IntRect(x, y, w, t), backgroundColor); |
| fillRect(IntRect(x, y+h-t, w, t), backgroundColor); |
| fillRect(IntRect(x, y+t, t, h-t2), backgroundColor); |
| fillRect(IntRect(x+w-t, y+t, t, h-t2), backgroundColor); |
| } |
| } |
| |
| void PlatformGraphicsContextRecording::drawLine(const IntPoint& point1, |
| const IntPoint& point2) |
| { |
| FloatRect bounds = FloatQuad(point1, point1, point2, point2).boundingBox(); |
| float width = m_state->strokeThickness; |
| if (!width) width = 1; |
| bounds.inflate(width); |
| appendDrawingOperation(NEW_OP(DrawLine)(point1, point2), bounds); |
| } |
| |
| void PlatformGraphicsContextRecording::drawLineForText(const FloatPoint& pt, float width) |
| { |
| FloatRect bounds(pt.x(), pt.y(), width, m_state->strokeThickness); |
| appendDrawingOperation(NEW_OP(DrawLineForText)(pt, width), bounds); |
| } |
| |
| void PlatformGraphicsContextRecording::drawLineForTextChecking(const FloatPoint& pt, |
| float width, GraphicsContext::TextCheckingLineStyle lineStyle) |
| { |
| FloatRect bounds(pt.x(), pt.y(), width, m_state->strokeThickness); |
| appendDrawingOperation(NEW_OP(DrawLineForTextChecking)(pt, width, lineStyle), bounds); |
| } |
| |
| void PlatformGraphicsContextRecording::drawRect(const IntRect& rect) |
| { |
| appendDrawingOperation(NEW_OP(DrawRect)(rect), rect); |
| } |
| |
| void PlatformGraphicsContextRecording::fillPath(const Path& pathToFill, WindRule fillRule) |
| { |
| appendDrawingOperation(NEW_OP(FillPath)(pathToFill, fillRule), pathToFill.boundingRect()); |
| } |
| |
| void PlatformGraphicsContextRecording::fillRect(const FloatRect& rect) |
| { |
| appendDrawingOperation(NEW_OP(FillRect)(rect), rect); |
| } |
| |
| void PlatformGraphicsContextRecording::fillRect(const FloatRect& rect, |
| const Color& color) |
| { |
| GraphicsOperation::FillRect* operation = NEW_OP(FillRect)(rect); |
| operation->setColor(color); |
| appendDrawingOperation(operation, rect); |
| } |
| |
| void PlatformGraphicsContextRecording::fillRoundedRect( |
| const IntRect& rect, const IntSize& topLeft, const IntSize& topRight, |
| const IntSize& bottomLeft, const IntSize& bottomRight, |
| const Color& color) |
| { |
| appendDrawingOperation(NEW_OP(FillRoundedRect)(rect, topLeft, |
| topRight, bottomLeft, bottomRight, color), rect); |
| } |
| |
| void PlatformGraphicsContextRecording::strokeArc(const IntRect& r, int startAngle, |
| int angleSpan) |
| { |
| appendDrawingOperation(NEW_OP(StrokeArc)(r, startAngle, angleSpan), r); |
| } |
| |
| void PlatformGraphicsContextRecording::strokePath(const Path& pathToStroke) |
| { |
| appendDrawingOperation(NEW_OP(StrokePath)(pathToStroke), pathToStroke.boundingRect()); |
| } |
| |
| void PlatformGraphicsContextRecording::strokeRect(const FloatRect& rect, float lineWidth) |
| { |
| FloatRect bounds = rect; |
| bounds.inflate(lineWidth); |
| appendDrawingOperation(NEW_OP(StrokeRect)(rect, lineWidth), bounds); |
| } |
| |
| void PlatformGraphicsContextRecording::drawPosText(const void* inText, size_t byteLength, |
| const SkPoint inPos[], const SkPaint& inPaint) |
| { |
| if (inPaint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding) { |
| ALOGE("Unsupported text encoding! %d", inPaint.getTextEncoding()); |
| return; |
| } |
| FloatRect bounds = approximateTextBounds(byteLength / sizeof(uint16_t), inPos, inPaint); |
| bounds.move(m_textOffset); // compensate font rendering-side translates |
| |
| const SkPaint* paint = mRecording->recording()->getSkPaint(inPaint); |
| size_t posSize = sizeof(SkPoint) * paint->countText(inText, byteLength); |
| void* text = heap()->alloc(byteLength); |
| SkPoint* pos = (SkPoint*) heap()->alloc(posSize); |
| memcpy(text, inText, byteLength); |
| memcpy(pos, inPos, posSize); |
| appendDrawingOperation(NEW_OP(DrawPosText)(text, byteLength, pos, paint), bounds); |
| } |
| |
| void PlatformGraphicsContextRecording::drawMediaButton(const IntRect& rect, RenderSkinMediaButton::MediaButton buttonType, |
| bool translucent, bool drawBackground, |
| const IntRect& thumb) |
| { |
| appendDrawingOperation(NEW_OP(DrawMediaButton)(rect, buttonType, |
| translucent, drawBackground, thumb), rect); |
| } |
| |
| void PlatformGraphicsContextRecording::clipState(const FloatRect& clip) |
| { |
| if (mRecordingStateStack.size()) { |
| SkRect mapBounds; |
| mCurrentMatrix->mapRect(&mapBounds, clip); |
| mRecordingStateStack.last().clip(mapBounds); |
| } |
| } |
| |
| void PlatformGraphicsContextRecording::pushStateOperation(CanvasState* canvasState) |
| { |
| ALOGV("RECORDING: pushStateOperation: %p(isLayer=%d)", canvasState, canvasState->isTransparencyLayer()); |
| |
| RecordingState* parent = mRecordingStateStack.isEmpty() ? 0 : &(mRecordingStateStack.last()); |
| mRecordingStateStack.append(RecordingState(canvasState, parent)); |
| mRecording->recording()->addCanvasState(canvasState); |
| } |
| |
| void PlatformGraphicsContextRecording::popStateOperation() |
| { |
| RecordingState state = mRecordingStateStack.last(); |
| mRecordingStateStack.removeLast(); |
| mOperationState = 0; |
| if (!state.mHasDrawing) { |
| ALOGV("RECORDING: popStateOperation is deleting %p(isLayer=%d)", |
| state.mCanvasState, state.mCanvasState->isTransparencyLayer()); |
| mRecording->recording()->removeCanvasState(state.mCanvasState); |
| state.mCanvasState->~CanvasState(); |
| heap()->rewindIfLastAlloc(state.mCanvasState, sizeof(CanvasState)); |
| } else { |
| ALOGV("RECORDING: popStateOperation: %p(isLayer=%d)", |
| state.mCanvasState, state.mCanvasState->isTransparencyLayer()); |
| // Make sure we propagate drawing upwards so we don't delete our parent |
| mRecordingStateStack.last().mHasDrawing = true; |
| } |
| } |
| |
| void PlatformGraphicsContextRecording::pushMatrix() |
| { |
| mMatrixStack.append(mMatrixStack.last()); |
| mCurrentMatrix = &(mMatrixStack.last()); |
| } |
| |
| void PlatformGraphicsContextRecording::popMatrix() |
| { |
| mMatrixStack.removeLast(); |
| mCurrentMatrix = &(mMatrixStack.last()); |
| } |
| |
| IntRect PlatformGraphicsContextRecording::calculateFinalBounds(FloatRect bounds) |
| { |
| if (bounds.isEmpty() && mRecordingStateStack.last().mHasClip) { |
| ALOGV("Empty bounds, but has clip so using that"); |
| return enclosingIntRect(mRecordingStateStack.last().mBounds); |
| } |
| if (m_gc->hasShadow()) { |
| const ShadowRec& shadow = m_state->shadow; |
| if (shadow.blur > 0) |
| bounds.inflate(ceilf(shadow.blur)); |
| bounds.setWidth(bounds.width() + abs(shadow.dx)); |
| bounds.setHeight(bounds.height() + abs(shadow.dy)); |
| if (shadow.dx < 0) |
| bounds.move(shadow.dx, 0); |
| if (shadow.dy < 0) |
| bounds.move(0, shadow.dy); |
| // Add a bit extra to deal with rounding and blurring |
| bounds.inflate(4); |
| } |
| if (m_state->strokeStyle != NoStroke) |
| bounds.inflate(std::min(1.0f, m_state->strokeThickness)); |
| SkRect translated; |
| mCurrentMatrix->mapRect(&translated, bounds); |
| FloatRect ftrect = translated; |
| if (mRecordingStateStack.last().mHasClip |
| && !translated.intersect(mRecordingStateStack.last().mBounds)) { |
| ALOGV("Operation bounds=" FLOAT_RECT_FORMAT " clipped out by clip=" FLOAT_RECT_FORMAT, |
| FLOAT_RECT_ARGS(ftrect), FLOAT_RECT_ARGS(mRecordingStateStack.last().mBounds)); |
| return IntRect(); |
| } |
| return enclosingIntRect(translated); |
| } |
| |
| IntRect PlatformGraphicsContextRecording::calculateCoveredBounds(FloatRect bounds) |
| { |
| if (mRecordingStateStack.last().mOpaqueTrackingDisabled |
| || m_state->alpha != 1.0f |
| || (m_state->fillShader != 0 && !m_state->fillShader->isOpaque()) |
| || (m_state->mode != SkXfermode::kSrc_Mode && m_state->mode != SkXfermode::kSrcOver_Mode) |
| || !mCurrentMatrix->rectStaysRect()) { |
| return IntRect(); |
| } |
| |
| SkRect translated; |
| mCurrentMatrix->mapRect(&translated, bounds); |
| FloatRect ftrect = translated; |
| if (mRecordingStateStack.last().mHasClip |
| && !translated.intersect(mRecordingStateStack.last().mBounds)) { |
| ALOGV("Operation opaque area=" FLOAT_RECT_FORMAT " clipped out by clip=" FLOAT_RECT_FORMAT, |
| FLOAT_RECT_ARGS(ftrect), FLOAT_RECT_ARGS(mRecordingStateStack.last().mBounds)); |
| return IntRect(); |
| } |
| return enclosedIntRect(translated); |
| } |
| |
| void PlatformGraphicsContextRecording::appendDrawingOperation( |
| GraphicsOperation::Operation* operation, const FloatRect& untranslatedBounds) |
| { |
| m_isEmpty = false; |
| RecordingState& state = mRecordingStateStack.last(); |
| state.mHasDrawing = true; |
| if (!mOperationState) |
| mOperationState = mRecording->recording()->getState(m_state); |
| operation->m_state = mOperationState; |
| operation->m_canvasState = state.mCanvasState; |
| |
| WebCore::IntRect ibounds = calculateFinalBounds(untranslatedBounds); |
| if (ibounds.isEmpty()) { |
| ALOGV("RECORDING: Operation %s() was clipped out", operation->name()); |
| operation->~Operation(); |
| return; |
| } |
| #if USE_CLIPPING_PAINTER |
| if (operation->isOpaque() |
| && !untranslatedBounds.isEmpty() |
| && (untranslatedBounds.width() * untranslatedBounds.height() > MIN_TRACKED_OPAQUE_AREA)) { |
| // if the operation maps to an opaque rect, record the area it will cover |
| operation->setOpaqueRect(calculateCoveredBounds(untranslatedBounds)); |
| } |
| #endif |
| ALOGV("RECORDING: appendOperation %p->%s() bounds " INT_RECT_FORMAT, operation, operation->name(), |
| INT_RECT_ARGS(ibounds)); |
| RecordingData* data = new (heap()) RecordingData(operation, mRecording->recording()->m_nodeCount++); |
| mRecording->recording()->m_tree.insert(ibounds, data); |
| } |
| |
| void PlatformGraphicsContextRecording::appendStateOperation(GraphicsOperation::Operation* operation) |
| { |
| ALOGV("RECORDING: appendOperation %p->%s()", operation, operation->name()); |
| RecordingData* data = new (heap()) RecordingData(operation, mRecording->recording()->m_nodeCount++); |
| mRecordingStateStack.last().mCanvasState->adoptAndAppend(data); |
| } |
| |
| android::LinearAllocator* PlatformGraphicsContextRecording::heap() |
| { |
| return mRecording->recording()->heap(); |
| } |
| |
| } // WebCore |