/* * Copyright (C) 2021 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. */ #pragma once #include "Properties.h" #include "utils/MathUtils.h" #include #include #include #include #include #include namespace android::uirenderer { class StretchEffect { public: StretchEffect(const SkVector& direction, float maxStretchAmountX, float maxStretchAmountY) : maxStretchAmountX(maxStretchAmountX) , maxStretchAmountY(maxStretchAmountY) , mStretchDirection(direction) { } StretchEffect() {} bool isEmpty() const { return isZero(mStretchDirection.x()) && isZero(mStretchDirection.y()); } void setEmpty() { *this = StretchEffect{}; } StretchEffect& operator=(const StretchEffect& other) { this->mStretchDirection = other.mStretchDirection; this->maxStretchAmountX = other.maxStretchAmountX; this->maxStretchAmountY = other.maxStretchAmountY; return *this; } bool operator==(const StretchEffect& other) const { return mStretchDirection == other.mStretchDirection && maxStretchAmountX == other.maxStretchAmountX && maxStretchAmountY == other.maxStretchAmountY; } void mergeWith(const StretchEffect& other) { if (other.isEmpty()) { return; } if (isEmpty()) { *this = other; return; } mStretchDirection += other.mStretchDirection; if (isEmpty()) { return setEmpty(); } maxStretchAmountX = std::max(maxStretchAmountX, other.maxStretchAmountX); maxStretchAmountY = std::max(maxStretchAmountY, other.maxStretchAmountY); } /** * Return the stretched x position given the normalized x position with * the current horizontal stretch direction * @param normalizedX x position on the input texture from 0 to 1 * @return x position when the horizontal stretch direction applied */ float computeStretchedPositionX(float normalizedX) const; /** * Return the stretched y position given the normalized y position with * the current horizontal stretch direction * @param normalizedX y position on the input texture from 0 to 1 * @return y position when the horizontal stretch direction applied */ float computeStretchedPositionY(float normalizedY) const; sk_sp getShader(float width, float height, const sk_sp& snapshotImage, const SkMatrix* matrix) const; float maxStretchAmountX = 0; float maxStretchAmountY = 0; const SkVector getStretchDirection() const { return mStretchDirection; } SkMatrix makeLinearStretch(float width, float height) const { SkMatrix matrix; auto [sX, sY] = getStretchDirection(); matrix.setScale(1 + std::abs(sX), 1 + std::abs(sY), sX > 0 ? 0 : width, sY > 0 ? 0 : height); return matrix; } bool requiresLayer() const { return !isEmpty(); } void clear() { mBuilder = nullptr; } private: // The epsilon for StretchEffect is less than in MathUtils because // the range is 0-1 for an entire screen and should be significantly // less than 1 pixel for a smooth stretch animation. inline static bool isZero(float value) { // Using fabsf is more performant as ARM computes // fabsf in a single instruction. return fabsf(value) <= NON_ZERO_EPSILON; } // This should be good for 1/25,000 of a screen and should be good for // screens with less than ~8000 pixels in one dimension with only 1/4 pixel // cut-off. static constexpr float NON_ZERO_EPSILON = 0.00004f; static sk_sp getStretchEffect(); mutable SkVector mStretchDirection{0, 0}; mutable std::unique_ptr mBuilder; }; } // namespace android::uirenderer