bool GraphicsLayerTextureMapper::addAnimation(const KeyframeValueList& valueList, const FloatSize& boxSize, const Animation* anim, const String& keyframesName, double timeOffset) { ASSERT(!keyframesName.isEmpty()); if (!anim || anim->isEmptyOrZeroDuration() || valueList.size() < 2 || (valueList.property() != AnimatedPropertyTransform && valueList.property() != AnimatedPropertyOpacity)) return false; if (valueList.property() == AnimatedPropertyFilter) { int listIndex = validateFilterOperations(valueList); if (listIndex < 0) return false; const auto& filters = static_cast<const FilterAnimationValue&>(valueList.at(listIndex)).value(); if (!filtersCanBeComposited(filters)) return false; } bool listsMatch = false; bool hasBigRotation; if (valueList.property() == AnimatedPropertyTransform) listsMatch = validateTransformOperations(valueList, hasBigRotation) >= 0; const double currentTime = monotonicallyIncreasingTime(); m_animations.add(TextureMapperAnimation(keyframesName, valueList, boxSize, *anim, listsMatch, currentTime - timeOffset, 0, TextureMapperAnimation::AnimationState::Playing)); // m_animationStartTime is the time of the first real frame of animation, now or delayed by a negative offset. if (timeOffset > 0) m_animationStartTime = currentTime; else m_animationStartTime = currentTime - timeOffset; notifyChange(AnimationChange); notifyChange(AnimationStarted); return true; }
bool GraphicsLayerBlackBerry::addAnimation(const KeyframeValueList& values, const IntSize& boxSize, const Animation* animation, const String& animationName, double timeOffset) { // This is what GraphicsLayerCA checks for. if (!animation || animation->isEmptyOrZeroDuration() || values.size() < 2) return false; // We only support these two kinds of properties at the moment. if (values.property() != AnimatedPropertyWebkitTransform && values.property() != AnimatedPropertyOpacity) return false; // Remove any running animation for the same property. // FIXME: Maybe this is superstition, I got the idea from GraphicsLayerQt // WebCore might be adding an animation with the same name, but for a different property removeAnimationByIdAndProperty(LayerAnimation::idFromAnimation(animation), values.property(), m_runningAnimations); removeAnimationByIdAndProperty(LayerAnimation::idFromAnimation(animation), values.property(), m_suspendedAnimations); RefPtr<LayerAnimation> layerAnimation = LayerAnimation::create(values, boxSize, animation, animationName, timeOffset); #if DEBUG_LAYER_ANIMATION fprintf(stderr, "LayerAnimation 0x%08x: Adding animation %s for property %d\n", layerAnimation.get(), animationName.latin1().data(), values.property()); #endif m_runningAnimations.append(layerAnimation); updateAnimations(); return true; }
bool GraphicsLayerAndroid::addAnimation(const KeyframeValueList& valueList, const IntSize& boxSize, const Animation* anim, const String& keyframesName, double beginTime) { if (!anim || anim->isEmptyOrZeroDuration() || valueList.size() < 2) return false; bool createdAnimations = false; if (valueList.property() == AnimatedPropertyWebkitTransform) { createdAnimations = createTransformAnimationsFromKeyframes(valueList, anim, keyframesName, beginTime, boxSize); } else { createdAnimations = createAnimationFromKeyframes(valueList, anim, keyframesName, beginTime); } if (createdAnimations) askForSync(); return createdAnimations; }
int GraphicsLayer::validateFilterOperations(const KeyframeValueList& valueList) { ASSERT(valueList.property() == AnimatedPropertyWebkitFilter); if (valueList.size() < 2) return -1; // Empty filters match anything, so find the first non-empty entry as the reference size_t firstIndex = 0; for ( ; firstIndex < valueList.size(); ++firstIndex) { if (filterOperationsAt(valueList, firstIndex)->operations().size() > 0) break; } if (firstIndex >= valueList.size()) return -1; const FilterOperations* firstVal = filterOperationsAt(valueList, firstIndex); for (size_t i = firstIndex + 1; i < valueList.size(); ++i) { const FilterOperations* val = filterOperationsAt(valueList, i); // An emtpy filter list matches anything. if (val->operations().isEmpty()) continue; if (!firstVal->operationsMatch(*val)) return -1; } return firstIndex; }
bool CCLayerAnimationController::addAnimation(const KeyframeValueList& valueList, const IntSize&, const Animation* animation, int animationId, int groupId, double timeOffset) { if (!animation) return false; OwnPtr<CCActiveAnimation> toAdd; if (valueList.property() == AnimatedPropertyWebkitTransform) toAdd = createActiveAnimation<TransformAnimationValue, CCTransformKeyframe, CCKeyframedTransformAnimationCurve>(valueList, animation, animationId, groupId, timeOffset, CCActiveAnimation::Transform); else if (valueList.property() == AnimatedPropertyOpacity) toAdd = createActiveAnimation<FloatAnimationValue, CCFloatKeyframe, CCKeyframedFloatAnimationCurve>(valueList, animation, animationId, groupId, timeOffset, CCActiveAnimation::Opacity); if (toAdd.get()) { // Remove any existing animations with the same animation id and target property. for (size_t i = 0; i < m_activeAnimations.size();) { if (m_activeAnimations[i]->id() == animationId && m_activeAnimations[i]->targetProperty() == toAdd->targetProperty()) m_activeAnimations.remove(i); else i++; } m_activeAnimations.append(toAdd.release()); return true; } return false; }
PassOwnPtr<WebKit::WebAnimation> createWebAnimation(const KeyframeValueList& values, const Animation* animation, int animationId, double timeOffset, const FloatSize& boxSize) { if (values.property() == AnimatedPropertyWebkitTransform) return createWebAnimation<TransformAnimationValue, WebTransformKeyframe, WebTransformAnimationCurve>(values, animation, animationId, timeOffset, WebKit::WebAnimation::TargetPropertyTransform, FloatSize(boxSize)); if (values.property() == AnimatedPropertyOpacity) return createWebAnimation<FloatAnimationValue, WebFloatKeyframe, WebFloatAnimationCurve>(values, animation, animationId, timeOffset, WebKit::WebAnimation::TargetPropertyOpacity, FloatSize()); return nullptr; }
PassOwnPtr<WebKit::WebAnimation> createWebAnimation(const KeyframeValueList& values, const CSSAnimationData* animation, int animationId, double timeOffset, const FloatSize& boxSize) { if (values.property() == AnimatedPropertyWebkitTransform) { OwnPtr<WebTransformAnimationCurve> curve = adoptPtr(Platform::current()->compositorSupport()->createTransformAnimationCurve()); return createWebAnimation<TransformAnimationValue, WebTransformKeyframe, WebTransformAnimationCurve>(values, animation, animationId, timeOffset, curve.get(), WebKit::WebAnimation::TargetPropertyTransform, FloatSize(boxSize)); } if (values.property() == AnimatedPropertyOpacity) { OwnPtr<WebFloatAnimationCurve> curve = adoptPtr(Platform::current()->compositorSupport()->createFloatAnimationCurve()); return createWebAnimation<FloatAnimationValue, WebFloatKeyframe, WebFloatAnimationCurve>(values, animation, animationId, timeOffset, curve.get(), WebKit::WebAnimation::TargetPropertyOpacity, FloatSize()); } return nullptr; }
bool WebGraphicsLayer::addAnimation(const KeyframeValueList& valueList, const IntSize& boxSize, const Animation* anim, const String& keyframesName, double timeOffset) { if (!anim || anim->isEmptyOrZeroDuration() || valueList.size() < 2 || (valueList.property() != AnimatedPropertyWebkitTransform && valueList.property() != AnimatedPropertyOpacity)) return false; WebLayerAnimation webAnimation(valueList); webAnimation.name = keyframesName; webAnimation.operation = WebLayerAnimation::AddAnimation; webAnimation.boxSize = boxSize; webAnimation.animation = Animation::create(anim); webAnimation.startTime = timeOffset; m_layerInfo.animations.append(webAnimation); m_hasPendingAnimations = true; notifyChange(); return true; }
bool GraphicsLayerTextureMapper::addAnimation(const KeyframeValueList& valueList, const IntSize& boxSize, const Animation* anim, const String& keyframesName, double timeOffset) { ASSERT(!keyframesName.isEmpty()); if (!anim || anim->isEmptyOrZeroDuration() || valueList.size() < 2 || (valueList.property() != AnimatedPropertyWebkitTransform && valueList.property() != AnimatedPropertyOpacity)) return false; bool listsMatch = false; bool hasBigRotation; if (valueList.property() == AnimatedPropertyWebkitTransform) listsMatch = validateTransformOperations(valueList, hasBigRotation) >= 0; m_animations.add(GraphicsLayerAnimation(keyframesName, valueList, boxSize, anim, WTF::currentTime() - timeOffset, listsMatch)); notifyChange(TextureMapperLayer::AnimationChange); m_animationStartedTimer.startOneShot(0); return true; }
bool CCLayerAnimationController::addAnimation(const KeyframeValueList& valueList, const IntSize&, const Animation* animation, int animationId, int groupId, double timeOffset) { if (!animation) return false; OwnPtr<CCActiveAnimation> toAdd; if (valueList.property() == AnimatedPropertyWebkitTransform) toAdd = createActiveAnimation<TransformAnimationValue, CCTransformKeyframe, CCKeyframedTransformAnimationCurve>(valueList, animation, animationId, groupId, timeOffset, CCActiveAnimation::Transform); else if (valueList.property() == AnimatedPropertyOpacity) toAdd = createActiveAnimation<FloatAnimationValue, CCFloatKeyframe, CCKeyframedFloatAnimationCurve>(valueList, animation, animationId, groupId, timeOffset, CCActiveAnimation::Opacity); if (toAdd.get()) { m_activeAnimations.append(toAdd.release()); return true; } return false; }
bool CoordinatedGraphicsLayer::addAnimation(const KeyframeValueList& valueList, const FloatSize& boxSize, const Animation* anim, const String& keyframesName, double delayAsNegativeTimeOffset) { ASSERT(!keyframesName.isEmpty()); if (!anim || anim->isEmptyOrZeroDuration() || valueList.size() < 2 || (valueList.property() != AnimatedPropertyWebkitTransform && valueList.property() != AnimatedPropertyOpacity && valueList.property() != AnimatedPropertyWebkitFilter)) return false; bool listsMatch = false; bool ignoredHasBigRotation; if (valueList.property() == AnimatedPropertyWebkitTransform) listsMatch = validateTransformOperations(valueList, ignoredHasBigRotation) >= 0; m_lastAnimationStartTime = monotonicallyIncreasingTime() - delayAsNegativeTimeOffset; m_animations.add(GraphicsLayerAnimation(keyframesName, valueList, boxSize, anim, m_lastAnimationStartTime, listsMatch)); m_animationStartedTimer.startOneShot(0); didChangeAnimations(); return true; }
bool GraphicsLayerTextureMapper::addAnimation(const KeyframeValueList& valueList, const IntSize& boxSize, const Animation* anim, const String& keyframesName, double timeOffset) { ASSERT(!keyframesName.isEmpty()); if (!anim || anim->isEmptyOrZeroDuration() || valueList.size() < 2 || (valueList.property() != AnimatedPropertyWebkitTransform && valueList.property() != AnimatedPropertyOpacity)) return false; for (size_t i = 0; i < m_animations.size(); ++i) { // The same animation name can be used for two animations with different properties. if (m_animations[i]->name != keyframesName || m_animations[i]->keyframes.property() != valueList.property()) continue; // We already have a copy of this animation, that means that we're resuming it rather than adding it. RefPtr<TextureMapperAnimation>& animation = m_animations[i]; animation->animation = Animation::create(anim); animation->paused = false; animation->startTime = WTF::currentTime() - timeOffset; notifyChange(TextureMapperNode::AnimationChange); m_animationStartedTimer.startOneShot(0); return true; } RefPtr<TextureMapperAnimation> animation = TextureMapperAnimation::create(valueList); animation->boxSize = boxSize; animation->name = keyframesName; animation->animation = Animation::create(anim); animation->paused = false; animation->startTime = WTF::currentTime() - timeOffset; if (valueList.property() == AnimatedPropertyWebkitTransform) { bool hasBigRotation; // Not used, but required as a pointer parameter for the function. fetchTransformOperationList(valueList, animation->functionList, animation->listsMatch, hasBigRotation); } m_animations.append(animation); notifyChange(TextureMapperNode::AnimationChange); m_animationStartedTimer.startOneShot(0); return true; }
bool GraphicsLayerAndroid::createTransformAnimationsFromKeyframes(const KeyframeValueList& valueList, const Animation* animation, const String& keyframesName, double beginTime, const IntSize& boxSize) { ASSERT(valueList.property() == AnimatedPropertyWebkitTransform); TLOG("createTransformAnimationFromKeyframes, name(%s) beginTime(%.2f)", keyframesName.latin1().data(), beginTime); KeyframeValueList* operationsList = new KeyframeValueList(AnimatedPropertyWebkitTransform); for (unsigned int i = 0; i < valueList.size(); i++) { TransformAnimationValue* originalValue = (TransformAnimationValue*)valueList.at(i); PassRefPtr<TimingFunction> timingFunction(const_cast<TimingFunction*>(originalValue->timingFunction())); TransformAnimationValue* value = new TransformAnimationValue(originalValue->keyTime(), originalValue->value(), timingFunction); operationsList->insert(value); } RefPtr<AndroidTransformAnimation> anim = AndroidTransformAnimation::create(animation, operationsList, beginTime); if (keyframesName.isEmpty()) anim->setName(propertyIdToString(valueList.property())); else anim->setName(keyframesName); m_contentLayer->addAnimation(anim.release()); needsNotifyClient(); return true; }
bool GraphicsLayerTextureMapper::addAnimation(const KeyframeValueList& valueList, const IntSize& boxSize, const Animation* anim, const String& keyframesName, double timeOffset) { ASSERT(!keyframesName.isEmpty()); if (!anim || anim->isEmptyOrZeroDuration() || valueList.size() < 2 || (valueList.property() != AnimatedPropertyWebkitTransform && valueList.property() != AnimatedPropertyOpacity)) return false; bool listsMatch = false; bool hasBigRotation; if (valueList.property() == AnimatedPropertyWebkitTransform) listsMatch = validateTransformOperations(valueList, hasBigRotation) >= 0; const double currentTime = WTF::currentTime(); m_animations.add(GraphicsLayerAnimation(keyframesName, valueList, boxSize, anim, currentTime - timeOffset, listsMatch)); // m_animationStartTime is the time of the first real frame of animation, now or delayed by a negative offset. if (timeOffset > 0) m_animationStartTime = currentTime; else m_animationStartTime = currentTime - timeOffset; notifyChange(AnimationChange); notifyChange(AnimationStarted); return true; }
bool GraphicsLayerAndroid::createAnimationFromKeyframes(const KeyframeValueList& valueList, const Animation* animation, const String& keyframesName, double beginTime) { bool isKeyframe = valueList.size() > 2; TLOG("createAnimationFromKeyframes(%d), name(%s) beginTime(%.2f)", isKeyframe, keyframesName.latin1().data(), beginTime); switch (valueList.property()) { case AnimatedPropertyInvalid: break; case AnimatedPropertyWebkitTransform: break; case AnimatedPropertyBackgroundColor: break; case AnimatedPropertyOpacity: { MLOG("ANIMATEDPROPERTYOPACITY"); KeyframeValueList* operationsList = new KeyframeValueList(AnimatedPropertyOpacity); for (unsigned int i = 0; i < valueList.size(); i++) { FloatAnimationValue* originalValue = (FloatAnimationValue*)valueList.at(i); PassRefPtr<TimingFunction> timingFunction(const_cast<TimingFunction*>(originalValue->timingFunction())); FloatAnimationValue* value = new FloatAnimationValue(originalValue->keyTime(), originalValue->value(), timingFunction); operationsList->insert(value); } RefPtr<AndroidOpacityAnimation> anim = AndroidOpacityAnimation::create(animation, operationsList, beginTime); if (keyframesName.isEmpty()) anim->setName(propertyIdToString(valueList.property())); else anim->setName(keyframesName); m_contentLayer->addAnimation(anim.release()); needsNotifyClient(); return true; } break; } return false; }
int GraphicsLayer::validateFilterOperations(const KeyframeValueList& valueList) { #if ENABLE(FILTERS_LEVEL_2) ASSERT(valueList.property() == AnimatedPropertyFilter || valueList.property() == AnimatedPropertyWebkitBackdropFilter); #else ASSERT(valueList.property() == AnimatedPropertyFilter); #endif if (valueList.size() < 2) return -1; // Empty filters match anything, so find the first non-empty entry as the reference size_t firstIndex = 0; for ( ; firstIndex < valueList.size(); ++firstIndex) { if (!filterOperationsAt(valueList, firstIndex).operations().isEmpty()) break; } if (firstIndex >= valueList.size()) return -1; const FilterOperations& firstVal = filterOperationsAt(valueList, firstIndex); for (size_t i = firstIndex + 1; i < valueList.size(); ++i) { const FilterOperations& val = filterOperationsAt(valueList, i); // An emtpy filter list matches anything. if (val.operations().isEmpty()) continue; if (!firstVal.operationsMatch(val)) return -1; } return firstIndex; }
void GraphicsLayer::fetchTransformOperationList(const KeyframeValueList& valueList, TransformOperationList& list, bool& isValid, bool& hasBigRotation) { ASSERT(valueList.property() == AnimatedPropertyWebkitTransform); list.clear(); isValid = false; hasBigRotation = false; if (valueList.size() < 2) return; // Empty transforms match anything, so find the first non-empty entry as the reference. size_t firstIndex = 0; for ( ; firstIndex < valueList.size(); ++firstIndex) { if (operationsAt(valueList, firstIndex)->operations().size() > 0) break; } if (firstIndex >= valueList.size()) return; const TransformOperations* firstVal = operationsAt(valueList, firstIndex); // See if the keyframes are valid. for (size_t i = firstIndex + 1; i < valueList.size(); ++i) { const TransformOperations* val = operationsAt(valueList, i); // a null transform matches anything if (val->operations().isEmpty()) continue; if (firstVal->operations().size() != val->operations().size()) return; for (size_t j = 0; j < firstVal->operations().size(); ++j) { if (!firstVal->operations().at(j)->isSameType(*val->operations().at(j))) return; } } // Keyframes are valid, fill in the list. isValid = true; double lastRotAngle = 0.0; double maxRotAngle = -1.0; list.resize(firstVal->operations().size()); for (size_t j = 0; j < firstVal->operations().size(); ++j) { TransformOperation::OperationType type = firstVal->operations().at(j)->getOperationType(); list[j] = type; // if this is a rotation entry, we need to see if any angle differences are >= 180 deg if (type == TransformOperation::ROTATE_X || type == TransformOperation::ROTATE_Y || type == TransformOperation::ROTATE_Z || type == TransformOperation::ROTATE_3D) { lastRotAngle = static_cast<RotateTransformOperation*>(firstVal->operations().at(j).get())->angle(); if (maxRotAngle < 0) maxRotAngle = fabs(lastRotAngle); for (size_t i = firstIndex + 1; i < valueList.size(); ++i) { const TransformOperations* val = operationsAt(valueList, i); double rotAngle = val->operations().isEmpty() ? 0 : (static_cast<RotateTransformOperation*>(val->operations().at(j).get())->angle()); double diffAngle = fabs(rotAngle - lastRotAngle); if (diffAngle > maxRotAngle) maxRotAngle = diffAngle; lastRotAngle = rotAngle; } } } hasBigRotation = maxRotAngle >= 180.0; }
PassOwnPtr<WebKit::WebAnimation> createWebAnimation(const KeyframeValueList& valueList, const CSSAnimationData* animation, int animationId, double timeOffset, Curve* curve, WebKit::WebAnimation::TargetProperty targetProperty, const FloatSize& boxSize) { bool alternate = false; bool reverse = false; if (animation && animation->isDirectionSet()) { CSSAnimationData::AnimationDirection direction = animation->direction(); if (direction == CSSAnimationData::AnimationDirectionAlternate || direction == CSSAnimationData::AnimationDirectionAlternateReverse) alternate = true; if (direction == CSSAnimationData::AnimationDirectionReverse || direction == CSSAnimationData::AnimationDirectionAlternateReverse) reverse = true; } for (size_t i = 0; i < valueList.size(); i++) { size_t index = reverse ? valueList.size() - i - 1 : i; const Value* originalValue = static_cast<const Value*>(valueList.at(index)); const Value* lastOriginalValue = 0; if (valueList.size() > 1 && ((reverse && index + 1 < valueList.size()) || (!reverse && index > 0))) lastOriginalValue = static_cast<const Value*>(valueList.at(reverse ? index + 1 : index - 1)); const TimingFunction* originalTimingFunction = originalValue->timingFunction(); // If there hasn't been a timing function associated with this keyframe, use the // animation's timing function, if we have one. if (!originalTimingFunction && animation->isTimingFunctionSet()) originalTimingFunction = animation->timingFunction().get(); // Ease is the default timing function. WebKit::WebAnimationCurve::TimingFunctionType timingFunctionType = WebKit::WebAnimationCurve::TimingFunctionTypeEase; bool isUsingCustomBezierTimingFunction = false; double x1 = 0; double y1 = 0; double x2 = 1; double y2 = 1; if (originalTimingFunction) { switch (originalTimingFunction->type()) { case TimingFunction::StepsFunction: // FIXME: add support for steps timing function. return nullptr; case TimingFunction::LinearFunction: timingFunctionType = WebKit::WebAnimationCurve::TimingFunctionTypeLinear; break; case TimingFunction::CubicBezierFunction: const CubicBezierTimingFunction* originalBezierTimingFunction = static_cast<const CubicBezierTimingFunction*>(originalTimingFunction); isUsingCustomBezierTimingFunction = true; x1 = originalBezierTimingFunction->x1(); y1 = originalBezierTimingFunction->y1(); x2 = originalBezierTimingFunction->x2(); y2 = originalBezierTimingFunction->y2(); break; } // switch } double duration = (animation && animation->isDurationSet()) ? animation->duration() : 1; double keyTime = originalValue->keyTime() * duration; if (reverse) keyTime = duration - keyTime; bool addedKeyframe = false; if (isUsingCustomBezierTimingFunction) addedKeyframe = appendKeyframeWithCustomBezierTimingFunction<Value, Keyframe, Curve>(curve, keyTime, originalValue, lastOriginalValue, x1, y1, x2, y2, boxSize); else addedKeyframe = appendKeyframeWithStandardTimingFunction<Value, Keyframe, Curve>(curve, keyTime, originalValue, lastOriginalValue, timingFunctionType, boxSize); if (!addedKeyframe) return nullptr; } OwnPtr<WebKit::WebAnimation> webAnimation = adoptPtr(Platform::current()->compositorSupport()->createAnimation(*curve, targetProperty, animationId)); int iterations = (animation && animation->isIterationCountSet()) ? animation->iterationCount() : 1; webAnimation->setIterations(iterations); webAnimation->setAlternatesDirection(alternate); // If timeOffset > 0, then the animation has started in the past. webAnimation->setTimeOffset(timeOffset); return webAnimation.release(); }
int GraphicsLayer::validateTransformOperations(const KeyframeValueList& valueList, bool& hasBigRotation) { ASSERT(valueList.property() == AnimatedPropertyWebkitTransform); hasBigRotation = false; if (valueList.size() < 2) return -1; // Empty transforms match anything, so find the first non-empty entry as the reference. size_t firstIndex = 0; for ( ; firstIndex < valueList.size(); ++firstIndex) { if (operationsAt(valueList, firstIndex)->operations().size() > 0) break; } if (firstIndex >= valueList.size()) return -1; const TransformOperations* firstVal = operationsAt(valueList, firstIndex); // See if the keyframes are valid. for (size_t i = firstIndex + 1; i < valueList.size(); ++i) { const TransformOperations* val = operationsAt(valueList, i); // An emtpy transform list matches anything. if (val->operations().isEmpty()) continue; if (!firstVal->operationsMatch(*val)) return -1; } // Keyframes are valid, check for big rotations. double lastRotAngle = 0.0; double maxRotAngle = -1.0; for (size_t j = 0; j < firstVal->operations().size(); ++j) { TransformOperation::OperationType type = firstVal->operations().at(j)->getOperationType(); // if this is a rotation entry, we need to see if any angle differences are >= 180 deg if (type == TransformOperation::ROTATE_X || type == TransformOperation::ROTATE_Y || type == TransformOperation::ROTATE_Z || type == TransformOperation::ROTATE_3D) { lastRotAngle = static_cast<RotateTransformOperation*>(firstVal->operations().at(j).get())->angle(); if (maxRotAngle < 0) maxRotAngle = fabs(lastRotAngle); for (size_t i = firstIndex + 1; i < valueList.size(); ++i) { const TransformOperations* val = operationsAt(valueList, i); double rotAngle = val->operations().isEmpty() ? 0 : (static_cast<RotateTransformOperation*>(val->operations().at(j).get())->angle()); double diffAngle = fabs(rotAngle - lastRotAngle); if (diffAngle > maxRotAngle) maxRotAngle = diffAngle; lastRotAngle = rotAngle; } } } hasBigRotation = maxRotAngle >= 180.0; return firstIndex; }