| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <SkCanvas.h> |
| |
| #include "CanvasState.h" |
| #include "utils/MathUtils.h" |
| |
| namespace android { |
| namespace uirenderer { |
| |
| |
| CanvasState::CanvasState(CanvasStateClient& renderer) |
| : mDirtyClip(false) |
| , mWidth(-1) |
| , mHeight(-1) |
| , mSaveCount(1) |
| , mFirstSnapshot(new Snapshot) |
| , mCanvas(renderer) |
| , mSnapshot(mFirstSnapshot) { |
| |
| } |
| |
| CanvasState::~CanvasState() { |
| |
| } |
| |
| void CanvasState::initializeSaveStack(float clipLeft, float clipTop, |
| float clipRight, float clipBottom, const Vector3& lightCenter) { |
| mSnapshot = new Snapshot(mFirstSnapshot, |
| SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); |
| mSnapshot->setClip(clipLeft, clipTop, clipRight, clipBottom); |
| mSnapshot->fbo = mCanvas.onGetTargetFbo(); |
| mSnapshot->setRelativeLightCenter(lightCenter); |
| mSaveCount = 1; |
| } |
| |
| void CanvasState::setViewport(int width, int height) { |
| mWidth = width; |
| mHeight = height; |
| mFirstSnapshot->initializeViewport(width, height); |
| mCanvas.onViewportInitialized(); |
| |
| // create a temporary 1st snapshot, so old snapshots are released, |
| // and viewport can be queried safely. |
| // TODO: remove, combine viewport + save stack initialization |
| mSnapshot = new Snapshot(mFirstSnapshot, |
| SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); |
| mSaveCount = 1; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Save (layer) |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Guaranteed to save without side-effects |
| * |
| * This approach, here and in restoreSnapshot(), allows subclasses to directly manipulate the save |
| * stack, and ensures restoreToCount() doesn't call back into subclass overrides. |
| */ |
| int CanvasState::saveSnapshot(int flags) { |
| mSnapshot = new Snapshot(mSnapshot, flags); |
| return mSaveCount++; |
| } |
| |
| int CanvasState::save(int flags) { |
| return saveSnapshot(flags); |
| } |
| |
| /** |
| * Guaranteed to restore without side-effects. |
| */ |
| void CanvasState::restoreSnapshot() { |
| sp<Snapshot> toRemove = mSnapshot; |
| sp<Snapshot> toRestore = mSnapshot->previous; |
| |
| mSaveCount--; |
| mSnapshot = toRestore; |
| |
| // subclass handles restore implementation |
| mCanvas.onSnapshotRestored(*toRemove, *toRestore); |
| } |
| |
| void CanvasState::restore() { |
| if (mSaveCount > 1) { |
| restoreSnapshot(); |
| } |
| } |
| |
| void CanvasState::restoreToCount(int saveCount) { |
| if (saveCount < 1) saveCount = 1; |
| |
| while (mSaveCount > saveCount) { |
| restoreSnapshot(); |
| } |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Matrix |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| void CanvasState::getMatrix(SkMatrix* matrix) const { |
| mSnapshot->transform->copyTo(*matrix); |
| } |
| |
| void CanvasState::translate(float dx, float dy, float dz) { |
| mSnapshot->transform->translate(dx, dy, dz); |
| } |
| |
| void CanvasState::rotate(float degrees) { |
| mSnapshot->transform->rotate(degrees, 0.0f, 0.0f, 1.0f); |
| } |
| |
| void CanvasState::scale(float sx, float sy) { |
| mSnapshot->transform->scale(sx, sy, 1.0f); |
| } |
| |
| void CanvasState::skew(float sx, float sy) { |
| mSnapshot->transform->skew(sx, sy); |
| } |
| |
| void CanvasState::setMatrix(const SkMatrix& matrix) { |
| mSnapshot->transform->load(matrix); |
| } |
| |
| void CanvasState::setMatrix(const Matrix4& matrix) { |
| mSnapshot->transform->load(matrix); |
| } |
| |
| void CanvasState::concatMatrix(const SkMatrix& matrix) { |
| mat4 transform(matrix); |
| mSnapshot->transform->multiply(transform); |
| } |
| |
| void CanvasState::concatMatrix(const Matrix4& matrix) { |
| mSnapshot->transform->multiply(matrix); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Clip |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| bool CanvasState::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) { |
| if (CC_LIKELY(currentTransform()->rectToRect())) { |
| mDirtyClip |= mSnapshot->clip(left, top, right, bottom, op); |
| return !mSnapshot->clipRect->isEmpty(); |
| } |
| |
| SkPath path; |
| path.addRect(left, top, right, bottom); |
| |
| return CanvasState::clipPath(&path, op); |
| } |
| |
| bool CanvasState::clipPath(const SkPath* path, SkRegion::Op op) { |
| SkMatrix transform; |
| currentTransform()->copyTo(transform); |
| |
| SkPath transformed; |
| path->transform(transform, &transformed); |
| |
| SkRegion clip; |
| if (!mSnapshot->previous->clipRegion->isEmpty()) { |
| clip.setRegion(*mSnapshot->previous->clipRegion); |
| } else { |
| if (mSnapshot->previous == firstSnapshot()) { |
| clip.setRect(0, 0, getWidth(), getHeight()); |
| } else { |
| Rect* bounds = mSnapshot->previous->clipRect; |
| clip.setRect(bounds->left, bounds->top, bounds->right, bounds->bottom); |
| } |
| } |
| |
| SkRegion region; |
| region.setPath(transformed, clip); |
| |
| // region is the transformed input path, masked by the previous clip |
| mDirtyClip |= mSnapshot->clipRegionTransformed(region, op); |
| return !mSnapshot->clipRect->isEmpty(); |
| } |
| |
| bool CanvasState::clipRegion(const SkRegion* region, SkRegion::Op op) { |
| mDirtyClip |= mSnapshot->clipRegionTransformed(*region, op); |
| return !mSnapshot->clipRect->isEmpty(); |
| } |
| |
| void CanvasState::setClippingOutline(LinearAllocator& allocator, const Outline* outline) { |
| Rect bounds; |
| float radius; |
| if (!outline->getAsRoundRect(&bounds, &radius)) return; // only RR supported |
| |
| bool outlineIsRounded = MathUtils::isPositive(radius); |
| if (!outlineIsRounded || currentTransform()->isSimple()) { |
| // TODO: consider storing this rect separately, so that this can't be replaced with clip ops |
| clipRect(bounds.left, bounds.top, bounds.right, bounds.bottom, SkRegion::kIntersect_Op); |
| } |
| if (outlineIsRounded) { |
| setClippingRoundRect(allocator, bounds, radius, false); |
| } |
| } |
| |
| void CanvasState::setClippingRoundRect(LinearAllocator& allocator, |
| const Rect& rect, float radius, bool highPriority) { |
| mSnapshot->setClippingRoundRect(allocator, rect, radius, highPriority); |
| } |
| |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Quick Rejection |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Calculates whether content drawn within the passed bounds would be outside of, or intersect with |
| * the clipRect. Does not modify the scissor. |
| * |
| * @param clipRequired if not null, will be set to true if element intersects clip |
| * (and wasn't rejected) |
| * |
| * @param snapOut if set, the geometry will be treated as having an AA ramp. |
| * See Rect::snapGeometryToPixelBoundaries() |
| */ |
| bool CanvasState::calculateQuickRejectForScissor(float left, float top, |
| float right, float bottom, |
| bool* clipRequired, bool* roundRectClipRequired, |
| bool snapOut) const { |
| if (mSnapshot->isIgnored() || bottom <= top || right <= left) { |
| return true; |
| } |
| |
| Rect r(left, top, right, bottom); |
| currentTransform()->mapRect(r); |
| r.snapGeometryToPixelBoundaries(snapOut); |
| |
| Rect clipRect(*currentClipRect()); |
| clipRect.snapToPixelBoundaries(); |
| |
| if (!clipRect.intersects(r)) return true; |
| |
| // clip is required if geometry intersects clip rect |
| if (clipRequired) { |
| *clipRequired = !clipRect.contains(r); |
| } |
| |
| // round rect clip is required if RR clip exists, and geometry intersects its corners |
| if (roundRectClipRequired) { |
| *roundRectClipRequired = mSnapshot->roundRectClipState != NULL |
| && mSnapshot->roundRectClipState->areaRequiresRoundRectClip(r); |
| } |
| return false; |
| } |
| |
| bool CanvasState::quickRejectConservative(float left, float top, |
| float right, float bottom) const { |
| if (mSnapshot->isIgnored() || bottom <= top || right <= left) { |
| return true; |
| } |
| |
| Rect r(left, top, right, bottom); |
| currentTransform()->mapRect(r); |
| r.roundOut(); // rounded out to be conservative |
| |
| Rect clipRect(*currentClipRect()); |
| clipRect.snapToPixelBoundaries(); |
| |
| if (!clipRect.intersects(r)) return true; |
| |
| return false; |
| } |
| |
| } // namespace uirenderer |
| } // namespace android |