|  | /* | 
|  | * Copyright (C) 2015 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 "ClipArea.h" | 
|  |  | 
|  | #include "utils/LinearAllocator.h" | 
|  |  | 
|  | #include <SkPath.h> | 
|  | #include <limits> | 
|  | #include <type_traits> | 
|  |  | 
|  | namespace android { | 
|  | namespace uirenderer { | 
|  |  | 
|  | static void handlePoint(Rect& transformedBounds, const Matrix4& transform, float x, float y) { | 
|  | Vertex v = {x, y}; | 
|  | transform.mapPoint(v.x, v.y); | 
|  | transformedBounds.expandToCover(v.x, v.y); | 
|  | } | 
|  |  | 
|  | Rect transformAndCalculateBounds(const Rect& r, const Matrix4& transform) { | 
|  | const float kMinFloat = std::numeric_limits<float>::lowest(); | 
|  | const float kMaxFloat = std::numeric_limits<float>::max(); | 
|  | Rect transformedBounds = { kMaxFloat, kMaxFloat, kMinFloat, kMinFloat }; | 
|  | handlePoint(transformedBounds, transform, r.left, r.top); | 
|  | handlePoint(transformedBounds, transform, r.right, r.top); | 
|  | handlePoint(transformedBounds, transform, r.left, r.bottom); | 
|  | handlePoint(transformedBounds, transform, r.right, r.bottom); | 
|  | return transformedBounds; | 
|  | } | 
|  |  | 
|  | void ClipBase::dump() const { | 
|  | ALOGD("mode %d" RECT_STRING, mode, RECT_ARGS(rect)); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * TransformedRectangle | 
|  | */ | 
|  |  | 
|  | TransformedRectangle::TransformedRectangle() { | 
|  | } | 
|  |  | 
|  | TransformedRectangle::TransformedRectangle(const Rect& bounds, | 
|  | const Matrix4& transform) | 
|  | : mBounds(bounds) | 
|  | , mTransform(transform) { | 
|  | } | 
|  |  | 
|  | bool TransformedRectangle::canSimplyIntersectWith( | 
|  | const TransformedRectangle& other) const { | 
|  |  | 
|  | return mTransform == other.mTransform; | 
|  | } | 
|  |  | 
|  | void TransformedRectangle::intersectWith(const TransformedRectangle& other) { | 
|  | mBounds.doIntersect(other.mBounds); | 
|  | } | 
|  |  | 
|  | bool TransformedRectangle::isEmpty() const { | 
|  | return mBounds.isEmpty(); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * RectangleList | 
|  | */ | 
|  |  | 
|  | RectangleList::RectangleList() | 
|  | : mTransformedRectanglesCount(0) { | 
|  | } | 
|  |  | 
|  | bool RectangleList::isEmpty() const { | 
|  | if (mTransformedRectanglesCount < 1) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | for (int i = 0; i < mTransformedRectanglesCount; i++) { | 
|  | if (mTransformedRectangles[i].isEmpty()) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | int RectangleList::getTransformedRectanglesCount() const { | 
|  | return mTransformedRectanglesCount; | 
|  | } | 
|  |  | 
|  | const TransformedRectangle& RectangleList::getTransformedRectangle(int i) const { | 
|  | return mTransformedRectangles[i]; | 
|  | } | 
|  |  | 
|  | void RectangleList::setEmpty() { | 
|  | mTransformedRectanglesCount = 0; | 
|  | } | 
|  |  | 
|  | void RectangleList::set(const Rect& bounds, const Matrix4& transform) { | 
|  | mTransformedRectanglesCount = 1; | 
|  | mTransformedRectangles[0] = TransformedRectangle(bounds, transform); | 
|  | } | 
|  |  | 
|  | bool RectangleList::intersectWith(const Rect& bounds, | 
|  | const Matrix4& transform) { | 
|  | TransformedRectangle newRectangle(bounds, transform); | 
|  |  | 
|  | // Try to find a rectangle with a compatible transformation | 
|  | int index = 0; | 
|  | for (; index < mTransformedRectanglesCount; index++) { | 
|  | TransformedRectangle& tr(mTransformedRectangles[index]); | 
|  | if (tr.canSimplyIntersectWith(newRectangle)) { | 
|  | tr.intersectWith(newRectangle); | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Add it to the list if there is room | 
|  | if (index < kMaxTransformedRectangles) { | 
|  | mTransformedRectangles[index] = newRectangle; | 
|  | mTransformedRectanglesCount += 1; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // This rectangle list is full | 
|  | return false; | 
|  | } | 
|  |  | 
|  | Rect RectangleList::calculateBounds() const { | 
|  | Rect bounds; | 
|  | for (int index = 0; index < mTransformedRectanglesCount; index++) { | 
|  | const TransformedRectangle& tr(mTransformedRectangles[index]); | 
|  | if (index == 0) { | 
|  | bounds = tr.transformedBounds(); | 
|  | } else { | 
|  | bounds.doIntersect(tr.transformedBounds()); | 
|  | } | 
|  | } | 
|  | return bounds; | 
|  | } | 
|  |  | 
|  | static SkPath pathFromTransformedRectangle(const Rect& bounds, | 
|  | const Matrix4& transform) { | 
|  | SkPath rectPath; | 
|  | SkPath rectPathTransformed; | 
|  | rectPath.addRect(bounds.left, bounds.top, bounds.right, bounds.bottom); | 
|  | SkMatrix skTransform; | 
|  | transform.copyTo(skTransform); | 
|  | rectPath.transform(skTransform, &rectPathTransformed); | 
|  | return rectPathTransformed; | 
|  | } | 
|  |  | 
|  | SkRegion RectangleList::convertToRegion(const SkRegion& clip) const { | 
|  | SkRegion rectangleListAsRegion; | 
|  | for (int index = 0; index < mTransformedRectanglesCount; index++) { | 
|  | const TransformedRectangle& tr(mTransformedRectangles[index]); | 
|  | SkPath rectPathTransformed = pathFromTransformedRectangle( | 
|  | tr.getBounds(), tr.getTransform()); | 
|  | if (index == 0) { | 
|  | rectangleListAsRegion.setPath(rectPathTransformed, clip); | 
|  | } else { | 
|  | SkRegion rectRegion; | 
|  | rectRegion.setPath(rectPathTransformed, clip); | 
|  | rectangleListAsRegion.op(rectRegion, SkRegion::kIntersect_Op); | 
|  | } | 
|  | } | 
|  | return rectangleListAsRegion; | 
|  | } | 
|  |  | 
|  | void RectangleList::transform(const Matrix4& transform) { | 
|  | for (int index = 0; index < mTransformedRectanglesCount; index++) { | 
|  | mTransformedRectangles[index].transform(transform); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * ClipArea | 
|  | */ | 
|  |  | 
|  | ClipArea::ClipArea() | 
|  | : mMode(ClipMode::Rectangle) { | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Interface | 
|  | */ | 
|  |  | 
|  | void ClipArea::setViewportDimensions(int width, int height) { | 
|  | mPostViewportClipObserved = false; | 
|  | mViewportBounds.set(0, 0, width, height); | 
|  | mClipRect = mViewportBounds; | 
|  | } | 
|  |  | 
|  | void ClipArea::setEmpty() { | 
|  | onClipUpdated(); | 
|  | mMode = ClipMode::Rectangle; | 
|  | mClipRect.setEmpty(); | 
|  | mClipRegion.setEmpty(); | 
|  | mRectangleList.setEmpty(); | 
|  | } | 
|  |  | 
|  | void ClipArea::setClip(float left, float top, float right, float bottom) { | 
|  | onClipUpdated(); | 
|  | mMode = ClipMode::Rectangle; | 
|  | mClipRect.set(left, top, right, bottom); | 
|  | mClipRegion.setEmpty(); | 
|  | } | 
|  |  | 
|  | void ClipArea::clipRectWithTransform(const Rect& r, const mat4* transform, | 
|  | SkRegion::Op op) { | 
|  | if (op == SkRegion::kReplace_Op) mReplaceOpObserved = true; | 
|  | if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op; | 
|  | onClipUpdated(); | 
|  | switch (mMode) { | 
|  | case ClipMode::Rectangle: | 
|  | rectangleModeClipRectWithTransform(r, transform, op); | 
|  | break; | 
|  | case ClipMode::RectangleList: | 
|  | rectangleListModeClipRectWithTransform(r, transform, op); | 
|  | break; | 
|  | case ClipMode::Region: | 
|  | regionModeClipRectWithTransform(r, transform, op); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | void ClipArea::clipRegion(const SkRegion& region, SkRegion::Op op) { | 
|  | if (op == SkRegion::kReplace_Op) mReplaceOpObserved = true; | 
|  | if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op; | 
|  | onClipUpdated(); | 
|  | enterRegionMode(); | 
|  | mClipRegion.op(region, op); | 
|  | onClipRegionUpdated(); | 
|  | } | 
|  |  | 
|  | void ClipArea::clipPathWithTransform(const SkPath& path, const mat4* transform, | 
|  | SkRegion::Op op) { | 
|  | if (op == SkRegion::kReplace_Op) mReplaceOpObserved = true; | 
|  | if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op; | 
|  | onClipUpdated(); | 
|  | SkMatrix skTransform; | 
|  | transform->copyTo(skTransform); | 
|  | SkPath transformed; | 
|  | path.transform(skTransform, &transformed); | 
|  | SkRegion region; | 
|  | regionFromPath(transformed, region); | 
|  | enterRegionMode(); | 
|  | mClipRegion.op(region, op); | 
|  | onClipRegionUpdated(); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Rectangle mode | 
|  | */ | 
|  |  | 
|  | void ClipArea::enterRectangleMode() { | 
|  | // Entering rectangle mode discards any | 
|  | // existing clipping information from the other modes. | 
|  | // The only way this occurs is by a clip setting operation. | 
|  | mMode = ClipMode::Rectangle; | 
|  | } | 
|  |  | 
|  | void ClipArea::rectangleModeClipRectWithTransform(const Rect& r, | 
|  | const mat4* transform, SkRegion::Op op) { | 
|  |  | 
|  | if (op == SkRegion::kReplace_Op && transform->rectToRect()) { | 
|  | mClipRect = r; | 
|  | transform->mapRect(mClipRect); | 
|  | return; | 
|  | } else if (op != SkRegion::kIntersect_Op) { | 
|  | enterRegionMode(); | 
|  | regionModeClipRectWithTransform(r, transform, op); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (transform->rectToRect()) { | 
|  | Rect transformed(r); | 
|  | transform->mapRect(transformed); | 
|  | mClipRect.doIntersect(transformed); | 
|  | return; | 
|  | } | 
|  |  | 
|  | enterRectangleListMode(); | 
|  | rectangleListModeClipRectWithTransform(r, transform, op); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * RectangleList mode implementation | 
|  | */ | 
|  |  | 
|  | void ClipArea::enterRectangleListMode() { | 
|  | // Is is only legal to enter rectangle list mode from | 
|  | // rectangle mode, since rectangle list mode cannot represent | 
|  | // all clip areas that can be represented by a region. | 
|  | ALOG_ASSERT(mMode == ClipMode::Rectangle); | 
|  | mMode = ClipMode::RectangleList; | 
|  | mRectangleList.set(mClipRect, Matrix4::identity()); | 
|  | } | 
|  |  | 
|  | void ClipArea::rectangleListModeClipRectWithTransform(const Rect& r, | 
|  | const mat4* transform, SkRegion::Op op) { | 
|  | if (op != SkRegion::kIntersect_Op | 
|  | || !mRectangleList.intersectWith(r, *transform)) { | 
|  | enterRegionMode(); | 
|  | regionModeClipRectWithTransform(r, transform, op); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Region mode implementation | 
|  | */ | 
|  |  | 
|  | void ClipArea::enterRegionMode() { | 
|  | ClipMode oldMode = mMode; | 
|  | mMode = ClipMode::Region; | 
|  | if (oldMode != ClipMode::Region) { | 
|  | if (oldMode == ClipMode::Rectangle) { | 
|  | mClipRegion.setRect(mClipRect.toSkIRect()); | 
|  | } else { | 
|  | mClipRegion = mRectangleList.convertToRegion(createViewportRegion()); | 
|  | onClipRegionUpdated(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void ClipArea::regionModeClipRectWithTransform(const Rect& r, | 
|  | const mat4* transform, SkRegion::Op op) { | 
|  | SkPath transformedRect = pathFromTransformedRectangle(r, *transform); | 
|  | SkRegion transformedRectRegion; | 
|  | regionFromPath(transformedRect, transformedRectRegion); | 
|  | mClipRegion.op(transformedRectRegion, op); | 
|  | onClipRegionUpdated(); | 
|  | } | 
|  |  | 
|  | void ClipArea::onClipRegionUpdated() { | 
|  | if (!mClipRegion.isEmpty()) { | 
|  | mClipRect.set(mClipRegion.getBounds()); | 
|  |  | 
|  | if (mClipRegion.isRect()) { | 
|  | mClipRegion.setEmpty(); | 
|  | enterRectangleMode(); | 
|  | } | 
|  | } else { | 
|  | mClipRect.setEmpty(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Clip serialization | 
|  | */ | 
|  |  | 
|  | const ClipBase* ClipArea::serializeClip(LinearAllocator& allocator) { | 
|  | if (!mPostViewportClipObserved) { | 
|  | // Only initial clip-to-viewport observed, so no serialization of clip necessary | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | static_assert(std::is_trivially_destructible<Rect>::value, | 
|  | "expect Rect to be trivially destructible"); | 
|  | static_assert(std::is_trivially_destructible<RectangleList>::value, | 
|  | "expect RectangleList to be trivially destructible"); | 
|  |  | 
|  | if (mLastSerialization == nullptr) { | 
|  | ClipBase* serialization = nullptr; | 
|  | switch (mMode) { | 
|  | case ClipMode::Rectangle: | 
|  | serialization = allocator.create<ClipRect>(mClipRect); | 
|  | break; | 
|  | case ClipMode::RectangleList: | 
|  | serialization = allocator.create<ClipRectList>(mRectangleList); | 
|  | serialization->rect = mRectangleList.calculateBounds(); | 
|  | break; | 
|  | case ClipMode::Region: | 
|  | serialization = allocator.create<ClipRegion>(mClipRegion); | 
|  | serialization->rect.set(mClipRegion.getBounds()); | 
|  | break; | 
|  | } | 
|  | serialization->intersectWithRoot = mReplaceOpObserved; | 
|  | // TODO: this is only done for draw time, should eventually avoid for record time | 
|  | serialization->rect.snapToPixelBoundaries(); | 
|  | mLastSerialization = serialization; | 
|  | } | 
|  | return mLastSerialization; | 
|  | } | 
|  |  | 
|  | inline static const RectangleList& getRectList(const ClipBase* scb) { | 
|  | return reinterpret_cast<const ClipRectList*>(scb)->rectList; | 
|  | } | 
|  |  | 
|  | inline static const SkRegion& getRegion(const ClipBase* scb) { | 
|  | return reinterpret_cast<const ClipRegion*>(scb)->region; | 
|  | } | 
|  |  | 
|  | // Conservative check for too many rectangles to fit in rectangle list. | 
|  | // For simplicity, doesn't account for rect merging | 
|  | static bool cannotFitInRectangleList(const ClipArea& clipArea, const ClipBase* scb) { | 
|  | int currentRectCount = clipArea.isRectangleList() | 
|  | ? clipArea.getRectangleList().getTransformedRectanglesCount() | 
|  | : 1; | 
|  | int recordedRectCount = (scb->mode == ClipMode::RectangleList) | 
|  | ? getRectList(scb).getTransformedRectanglesCount() | 
|  | : 1; | 
|  | return currentRectCount + recordedRectCount > RectangleList::kMaxTransformedRectangles; | 
|  | } | 
|  |  | 
|  | static const ClipRect sEmptyClipRect(Rect(0, 0)); | 
|  |  | 
|  | const ClipBase* ClipArea::serializeIntersectedClip(LinearAllocator& allocator, | 
|  | const ClipBase* recordedClip, const Matrix4& recordedClipTransform) { | 
|  |  | 
|  | // if no recordedClip passed, just serialize current state | 
|  | if (!recordedClip) return serializeClip(allocator); | 
|  |  | 
|  | // if either is empty, clip is empty | 
|  | if (CC_UNLIKELY(recordedClip->rect.isEmpty())|| mClipRect.isEmpty()) return &sEmptyClipRect; | 
|  |  | 
|  | if (!mLastResolutionResult | 
|  | || recordedClip != mLastResolutionClip | 
|  | || recordedClipTransform != mLastResolutionTransform) { | 
|  | mLastResolutionClip = recordedClip; | 
|  | mLastResolutionTransform = recordedClipTransform; | 
|  |  | 
|  | if (CC_LIKELY(mMode == ClipMode::Rectangle | 
|  | && recordedClip->mode == ClipMode::Rectangle | 
|  | && recordedClipTransform.rectToRect())) { | 
|  | // common case - result is a single rectangle | 
|  | auto rectClip = allocator.create<ClipRect>(recordedClip->rect); | 
|  | recordedClipTransform.mapRect(rectClip->rect); | 
|  | rectClip->rect.doIntersect(mClipRect); | 
|  | rectClip->rect.snapToPixelBoundaries(); | 
|  | mLastResolutionResult = rectClip; | 
|  | } else if (CC_UNLIKELY(mMode == ClipMode::Region | 
|  | || recordedClip->mode == ClipMode::Region | 
|  | || cannotFitInRectangleList(*this, recordedClip))) { | 
|  | // region case | 
|  | SkRegion other; | 
|  | switch (recordedClip->mode) { | 
|  | case ClipMode::Rectangle: | 
|  | if (CC_LIKELY(recordedClipTransform.rectToRect())) { | 
|  | // simple transform, skip creating SkPath | 
|  | Rect resultClip(recordedClip->rect); | 
|  | recordedClipTransform.mapRect(resultClip); | 
|  | other.setRect(resultClip.toSkIRect()); | 
|  | } else { | 
|  | SkPath transformedRect = pathFromTransformedRectangle(recordedClip->rect, | 
|  | recordedClipTransform); | 
|  | other.setPath(transformedRect, createViewportRegion()); | 
|  | } | 
|  | break; | 
|  | case ClipMode::RectangleList: { | 
|  | RectangleList transformedList(getRectList(recordedClip)); | 
|  | transformedList.transform(recordedClipTransform); | 
|  | other = transformedList.convertToRegion(createViewportRegion()); | 
|  | break; | 
|  | } | 
|  | case ClipMode::Region: | 
|  | other = getRegion(recordedClip); | 
|  | applyTransformToRegion(recordedClipTransform, &other); | 
|  | } | 
|  |  | 
|  | ClipRegion* regionClip = allocator.create<ClipRegion>(); | 
|  | switch (mMode) { | 
|  | case ClipMode::Rectangle: | 
|  | regionClip->region.op(mClipRect.toSkIRect(), other, SkRegion::kIntersect_Op); | 
|  | break; | 
|  | case ClipMode::RectangleList: | 
|  | regionClip->region.op(mRectangleList.convertToRegion(createViewportRegion()), | 
|  | other, SkRegion::kIntersect_Op); | 
|  | break; | 
|  | case ClipMode::Region: | 
|  | regionClip->region.op(mClipRegion, other, SkRegion::kIntersect_Op); | 
|  | break; | 
|  | } | 
|  | // Don't need to snap, since region's in int bounds | 
|  | regionClip->rect.set(regionClip->region.getBounds()); | 
|  | mLastResolutionResult = regionClip; | 
|  | } else { | 
|  | auto rectListClip = allocator.create<ClipRectList>(mRectangleList); | 
|  | auto&& rectList = rectListClip->rectList; | 
|  | if (mMode == ClipMode::Rectangle) { | 
|  | rectList.set(mClipRect, Matrix4::identity()); | 
|  | } | 
|  |  | 
|  | if (recordedClip->mode == ClipMode::Rectangle) { | 
|  | rectList.intersectWith(recordedClip->rect, recordedClipTransform); | 
|  | } else { | 
|  | const RectangleList& other = getRectList(recordedClip); | 
|  | for (int i = 0; i < other.getTransformedRectanglesCount(); i++) { | 
|  | auto&& tr = other.getTransformedRectangle(i); | 
|  | Matrix4 totalTransform(recordedClipTransform); | 
|  | totalTransform.multiply(tr.getTransform()); | 
|  | rectList.intersectWith(tr.getBounds(), totalTransform); | 
|  | } | 
|  | } | 
|  | rectListClip->rect = rectList.calculateBounds(); | 
|  | rectListClip->rect.snapToPixelBoundaries(); | 
|  | mLastResolutionResult = rectListClip; | 
|  | } | 
|  | } | 
|  | return mLastResolutionResult; | 
|  | } | 
|  |  | 
|  | void ClipArea::applyClip(const ClipBase* clip, const Matrix4& transform) { | 
|  | if (!clip) return; // nothing to do | 
|  |  | 
|  | if (CC_LIKELY(clip->mode == ClipMode::Rectangle)) { | 
|  | clipRectWithTransform(clip->rect, &transform, SkRegion::kIntersect_Op); | 
|  | } else if (CC_LIKELY(clip->mode == ClipMode::RectangleList)) { | 
|  | auto&& rectList = getRectList(clip); | 
|  | for (int i = 0; i < rectList.getTransformedRectanglesCount(); i++) { | 
|  | auto&& tr = rectList.getTransformedRectangle(i); | 
|  | Matrix4 totalTransform(transform); | 
|  | totalTransform.multiply(tr.getTransform()); | 
|  | clipRectWithTransform(tr.getBounds(), &totalTransform, SkRegion::kIntersect_Op); | 
|  | } | 
|  | } else { | 
|  | SkRegion region(getRegion(clip)); | 
|  | applyTransformToRegion(transform, ®ion); | 
|  | clipRegion(region, SkRegion::kIntersect_Op); | 
|  | } | 
|  | } | 
|  |  | 
|  | void ClipArea::applyTransformToRegion(const Matrix4& transform, SkRegion* region) { | 
|  | if (transform.rectToRect() && !transform.isPureTranslate()) { | 
|  | // handle matrices with scale manually by mapping each rect | 
|  | SkRegion other; | 
|  | SkRegion::Iterator it(*region); | 
|  | while (!it.done()) { | 
|  | Rect rect(it.rect()); | 
|  | transform.mapRect(rect); | 
|  | rect.snapGeometryToPixelBoundaries(true); | 
|  | other.op(rect.left, rect.top, rect.right, rect.bottom, SkRegion::kUnion_Op); | 
|  | it.next(); | 
|  | } | 
|  | region->swap(other); | 
|  | } else { | 
|  | // TODO: handle non-translate transforms properly! | 
|  | region->translate(transform.getTranslateX(), transform.getTranslateY()); | 
|  | } | 
|  | } | 
|  |  | 
|  | } /* namespace uirenderer */ | 
|  | } /* namespace android */ |