blob: ede76da65957e2e6b85e99e6b6f02caff1bf7434 [file] [log] [blame]
/*
* Copyright (C) 2008 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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 APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. 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.
*/
#include "config.h"
#include "CSSGradientValue.h"
#include "CSSValueKeywords.h"
#include "CSSStyleSelector.h"
#include "GeneratedImage.h"
#include "Gradient.h"
#include "Image.h"
#include "IntSize.h"
#include "IntSizeHash.h"
#include "NodeRenderStyle.h"
#include "PlatformString.h"
#include "RenderObject.h"
using namespace std;
namespace WebCore {
PassRefPtr<Image> CSSGradientValue::image(RenderObject* renderer, const IntSize& size)
{
if (size.isEmpty())
return 0;
bool cacheable = isCacheable();
if (cacheable) {
if (!m_clients.contains(renderer))
return 0;
// Need to look up our size. Create a string of width*height to use as a hash key.
Image* result = getImage(renderer, size);
if (result)
return result;
}
// We need to create an image.
RefPtr<Image> newImage = GeneratedImage::create(createGradient(renderer, size), size);
if (cacheable)
putImage(size, newImage);
return newImage.release();
}
// Should only ever be called for deprecated gradients.
static inline bool compareStops(const CSSGradientColorStop& a, const CSSGradientColorStop& b)
{
double aVal = a.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER);
double bVal = b.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER);
return aVal < bVal;
}
void CSSGradientValue::sortStopsIfNeeded()
{
ASSERT(m_deprecatedType);
if (!m_stopsSorted) {
if (m_stops.size())
std::stable_sort(m_stops.begin(), m_stops.end(), compareStops);
m_stopsSorted = true;
}
}
static inline int blend(int from, int to, float progress)
{
return int(from + (to - from) * progress);
}
static inline Color blend(const Color& from, const Color& to, float progress)
{
// FIXME: when we interpolate gradients using premultiplied colors, this should also do premultiplication.
return Color(blend(from.red(), to.red(), progress),
blend(from.green(), to.green(), progress),
blend(from.blue(), to.blue(), progress),
blend(from.alpha(), to.alpha(), progress));
}
struct GradientStop {
Color color;
float offset;
bool specified;
GradientStop()
: offset(0)
, specified(false)
{ }
};
void CSSGradientValue::addStops(Gradient* gradient, RenderObject* renderer, RenderStyle* rootStyle, float maxLengthForRepeat)
{
RenderStyle* style = renderer->style();
if (m_deprecatedType) {
sortStopsIfNeeded();
// We have to resolve colors.
for (unsigned i = 0; i < m_stops.size(); i++) {
const CSSGradientColorStop& stop = m_stops[i];
Color color = renderer->document()->styleSelector()->getColorFromPrimitiveValue(stop.m_color.get());
float offset;
if (stop.m_position->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE)
offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
else
offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_NUMBER);
gradient->addColorStop(offset, color);
}
// The back end already sorted the stops.
gradient->setStopsSorted(true);
return;
}
size_t numStops = m_stops.size();
Vector<GradientStop> stops(numStops);
float gradientLength = 0;
bool computedGradientLength = false;
FloatPoint gradientStart = gradient->p0();
FloatPoint gradientEnd;
if (isLinearGradient())
gradientEnd = gradient->p1();
else if (isRadialGradient())
gradientEnd = gradientStart + FloatSize(gradient->endRadius(), 0);
for (size_t i = 0; i < numStops; ++i) {
const CSSGradientColorStop& stop = m_stops[i];
stops[i].color = renderer->document()->styleSelector()->getColorFromPrimitiveValue(stop.m_color.get());
if (stop.m_position) {
int type = stop.m_position->primitiveType();
if (type == CSSPrimitiveValue::CSS_PERCENTAGE)
stops[i].offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
else if (CSSPrimitiveValue::isUnitTypeLength(type)) {
float length = stop.m_position->computeLengthFloat(style, rootStyle, style->effectiveZoom());
if (!computedGradientLength) {
FloatSize gradientSize(gradientStart - gradientEnd);
gradientLength = gradientSize.diagonalLength();
}
stops[i].offset = (gradientLength > 0) ? length / gradientLength : 0;
} else {
ASSERT_NOT_REACHED();
stops[i].offset = 0;
}
stops[i].specified = true;
} else {
// If the first color-stop does not have a position, its position defaults to 0%.
// If the last color-stop does not have a position, its position defaults to 100%.
if (!i) {
stops[i].offset = 0;
stops[i].specified = true;
} else if (numStops > 1 && i == numStops - 1) {
stops[i].offset = 1;
stops[i].specified = true;
}
}
// If a color-stop has a position that is less than the specified position of any
// color-stop before it in the list, its position is changed to be equal to the
// largest specified position of any color-stop before it.
if (stops[i].specified && i > 0) {
size_t prevSpecifiedIndex;
for (prevSpecifiedIndex = i - 1; prevSpecifiedIndex; --prevSpecifiedIndex) {
if (stops[prevSpecifiedIndex].specified)
break;
}
if (stops[i].offset < stops[prevSpecifiedIndex].offset)
stops[i].offset = stops[prevSpecifiedIndex].offset;
}
}
ASSERT(stops[0].specified && stops[numStops - 1].specified);
// If any color-stop still does not have a position, then, for each run of adjacent
// color-stops without positions, set their positions so that they are evenly spaced
// between the preceding and following color-stops with positions.
if (numStops > 2) {
size_t unspecifiedRunStart = 0;
bool inUnspecifiedRun = false;
for (size_t i = 0; i < numStops; ++i) {
if (!stops[i].specified && !inUnspecifiedRun) {
unspecifiedRunStart = i;
inUnspecifiedRun = true;
} else if (stops[i].specified && inUnspecifiedRun) {
size_t unspecifiedRunEnd = i;
if (unspecifiedRunStart < unspecifiedRunEnd) {
float lastSpecifiedOffset = stops[unspecifiedRunStart - 1].offset;
float nextSpecifiedOffset = stops[unspecifiedRunEnd].offset;
float delta = (nextSpecifiedOffset - lastSpecifiedOffset) / (unspecifiedRunEnd - unspecifiedRunStart + 1);
for (size_t j = unspecifiedRunStart; j < unspecifiedRunEnd; ++j)
stops[j].offset = lastSpecifiedOffset + (j - unspecifiedRunStart + 1) * delta;
}
inUnspecifiedRun = false;
}
}
}
// If the gradient is repeating, repeat the color stops.
// We can't just push this logic down into the platform-specific Gradient code,
// because we have to know the extent of the gradient, and possible move the end points.
if (m_repeating && numStops > 1) {
// If the difference in the positions of the first and last color-stops is 0,
// the gradient defines a solid-color image with the color of the last color-stop in the rule.
float gradientRange = stops[numStops - 1].offset - stops[0].offset;
if (!gradientRange) {
stops.first().offset = 0;
stops.first().color = stops.last().color;
stops.shrink(1);
numStops = 1;
} else {
float maxExtent = 1;
// Radial gradients may need to extend further than the endpoints, because they have
// to repeat out to the corners of the box.
if (isRadialGradient()) {
if (!computedGradientLength) {
FloatSize gradientSize(gradientStart - gradientEnd);
gradientLength = gradientSize.diagonalLength();
}
if (maxLengthForRepeat > gradientLength)
maxExtent = maxLengthForRepeat / gradientLength;
}
size_t originalNumStops = numStops;
size_t originalFirstStopIndex = 0;
// Work backwards from the first, adding stops until we get one before 0.
float firstOffset = stops[0].offset;
if (firstOffset > 0) {
float currOffset = firstOffset;
size_t srcStopOrdinal = originalNumStops - 1;
while (true) {
GradientStop newStop = stops[originalFirstStopIndex + srcStopOrdinal];
newStop.offset = currOffset;
stops.prepend(newStop);
++originalFirstStopIndex;
if (currOffset < 0)
break;
if (srcStopOrdinal)
currOffset -= stops[originalFirstStopIndex + srcStopOrdinal].offset - stops[originalFirstStopIndex + srcStopOrdinal - 1].offset;
srcStopOrdinal = (srcStopOrdinal + originalNumStops - 1) % originalNumStops;
}
}
// Work forwards from the end, adding stops until we get one after 1.
float lastOffset = stops[stops.size() - 1].offset;
if (lastOffset < maxExtent) {
float currOffset = lastOffset;
size_t srcStopOrdinal = originalFirstStopIndex;
while (true) {
GradientStop newStop = stops[srcStopOrdinal];
newStop.offset = currOffset;
stops.append(newStop);
if (currOffset > maxExtent)
break;
if (srcStopOrdinal < originalNumStops - 1)
currOffset += stops[srcStopOrdinal + 1].offset - stops[srcStopOrdinal].offset;
srcStopOrdinal = (srcStopOrdinal + 1) % originalNumStops;
}
}
}
}
numStops = stops.size();
// If the gradient goes outside the 0-1 range, normalize it by moving the endpoints, and adjusting the stops.
if (numStops > 1 && (stops[0].offset < 0 || stops[numStops - 1].offset > 1)) {
if (isLinearGradient()) {
float firstOffset = stops[0].offset;
float lastOffset = stops[numStops - 1].offset;
float scale = lastOffset - firstOffset;
for (size_t i = 0; i < numStops; ++i)
stops[i].offset = (stops[i].offset - firstOffset) / scale;
FloatPoint p0 = gradient->p0();
FloatPoint p1 = gradient->p1();
gradient->setP0(FloatPoint(p0.x() + firstOffset * (p1.x() - p0.x()), p0.y() + firstOffset * (p1.y() - p0.y())));
gradient->setP1(FloatPoint(p1.x() + (lastOffset - 1) * (p1.x() - p0.x()), p1.y() + (lastOffset - 1) * (p1.y() - p0.y())));
} else if (isRadialGradient()) {
// Rather than scaling the points < 0, we truncate them, so only scale according to the largest point.
float firstOffset = 0;
float lastOffset = stops[numStops - 1].offset;
float scale = lastOffset - firstOffset;
// Reset points below 0 to the first visible color.
size_t firstZeroOrGreaterIndex = numStops;
for (size_t i = 0; i < numStops; ++i) {
if (stops[i].offset >= 0) {
firstZeroOrGreaterIndex = i;
break;
}
}
if (firstZeroOrGreaterIndex > 0) {
if (firstZeroOrGreaterIndex < numStops && stops[firstZeroOrGreaterIndex].offset > 0) {
float prevOffset = stops[firstZeroOrGreaterIndex - 1].offset;
float nextOffset = stops[firstZeroOrGreaterIndex].offset;
float interStopProportion = -prevOffset / (nextOffset - prevOffset);
Color blendedColor = blend(stops[firstZeroOrGreaterIndex - 1].color, stops[firstZeroOrGreaterIndex].color, interStopProportion);
// Clamp the positions to 0 and set the color.
for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i) {
stops[i].offset = 0;
stops[i].color = blendedColor;
}
} else {
// All stops are below 0; just clamp them.
for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i)
stops[i].offset = 0;
}
}
for (size_t i = 0; i < numStops; ++i)
stops[i].offset /= scale;
gradient->setStartRadius(gradient->startRadius() * scale);
gradient->setEndRadius(gradient->endRadius() * scale);
}
}
for (unsigned i = 0; i < numStops; i++)
gradient->addColorStop(stops[i].offset, stops[i].color);
gradient->setStopsSorted(true);
}
static float positionFromValue(CSSPrimitiveValue* value, RenderStyle* style, RenderStyle* rootStyle, const IntSize& size, bool isHorizontal)
{
float zoomFactor = style->effectiveZoom();
switch (value->primitiveType()) {
case CSSPrimitiveValue::CSS_NUMBER:
return value->getFloatValue() * zoomFactor;
case CSSPrimitiveValue::CSS_PERCENTAGE:
return value->getFloatValue() / 100.f * (isHorizontal ? size.width() : size.height());
case CSSPrimitiveValue::CSS_IDENT:
switch (value->getIdent()) {
case CSSValueTop:
ASSERT(!isHorizontal);
return 0;
case CSSValueLeft:
ASSERT(isHorizontal);
return 0;
case CSSValueBottom:
ASSERT(!isHorizontal);
return size.height();
case CSSValueRight:
ASSERT(isHorizontal);
return size.width();
}
default:
return value->computeLengthFloat(style, rootStyle, zoomFactor);
}
}
FloatPoint CSSGradientValue::computeEndPoint(CSSPrimitiveValue* first, CSSPrimitiveValue* second, RenderStyle* style, RenderStyle* rootStyle, const IntSize& size)
{
FloatPoint result;
if (first)
result.setX(positionFromValue(first, style, rootStyle, size, true));
if (second)
result.setY(positionFromValue(second, style, rootStyle, size, false));
return result;
}
bool CSSGradientValue::isCacheable() const
{
for (size_t i = 0; i < m_stops.size(); ++i) {
const CSSGradientColorStop& stop = m_stops[i];
if (!stop.m_position)
continue;
unsigned short unitType = stop.m_position->primitiveType();
if (unitType == CSSPrimitiveValue::CSS_EMS || unitType == CSSPrimitiveValue::CSS_EXS || unitType == CSSPrimitiveValue::CSS_REMS)
return false;
}
return true;
}
String CSSLinearGradientValue::cssText() const
{
String result;
if (m_deprecatedType) {
result = "-webkit-gradient(linear, ";
result += m_firstX->cssText() + " ";
result += m_firstY->cssText() + ", ";
result += m_secondX->cssText() + " ";
result += m_secondY->cssText();
for (unsigned i = 0; i < m_stops.size(); i++) {
const CSSGradientColorStop& stop = m_stops[i];
result += ", ";
if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0)
result += "from(" + stop.m_color->cssText() + ")";
else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1)
result += "to(" + stop.m_color->cssText() + ")";
else
result += "color-stop(" + String::number(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER)) + ", " + stop.m_color->cssText() + ")";
}
} else {
result = m_repeating ? "-webkit-repeating-linear-gradient(" : "-webkit-linear-gradient(";
if (m_angle)
result += m_angle->cssText();
else {
if (m_firstX && m_firstY)
result += m_firstX->cssText() + " " + m_firstY->cssText();
else if (m_firstX || m_firstY) {
if (m_firstX)
result += m_firstX->cssText();
if (m_firstY)
result += m_firstY->cssText();
}
}
for (unsigned i = 0; i < m_stops.size(); i++) {
const CSSGradientColorStop& stop = m_stops[i];
result += ", ";
result += stop.m_color->cssText();
if (stop.m_position)
result += " " + stop.m_position->cssText();
}
}
result += ")";
return result;
}
// Compute the endpoints so that a gradient of the given angle covers a box of the given size.
static void endPointsFromAngle(float angleDeg, const IntSize& size, FloatPoint& firstPoint, FloatPoint& secondPoint)
{
angleDeg = fmodf(angleDeg, 360);
if (angleDeg < 0)
angleDeg += 360;
if (!angleDeg) {
firstPoint.set(0, 0);
secondPoint.set(size.width(), 0);
return;
}
if (angleDeg == 90) {
firstPoint.set(0, size.height());
secondPoint.set(0, 0);
return;
}
if (angleDeg == 180) {
firstPoint.set(size.width(), 0);
secondPoint.set(0, 0);
return;
}
float slope = tan(deg2rad(angleDeg));
// We find the endpoint by computing the intersection of the line formed by the slope,
// and a line perpendicular to it that intersects the corner.
float perpendicularSlope = -1 / slope;
// Compute start corner relative to center.
float halfHeight = size.height() / 2;
float halfWidth = size.width() / 2;
FloatPoint endCorner;
if (angleDeg < 90)
endCorner.set(halfWidth, halfHeight);
else if (angleDeg < 180)
endCorner.set(-halfWidth, halfHeight);
else if (angleDeg < 270)
endCorner.set(-halfWidth, -halfHeight);
else
endCorner.set(halfWidth, -halfHeight);
// Compute c (of y = mx + c) using the corner point.
float c = endCorner.y() - perpendicularSlope * endCorner.x();
float endX = c / (slope - perpendicularSlope);
float endY = perpendicularSlope * endX + c;
// We computed the end point, so set the second point, flipping the Y to account for angles going anticlockwise.
secondPoint.set(halfWidth + endX, size.height() - (halfHeight + endY));
// Reflect around the center for the start point.
firstPoint.set(size.width() - secondPoint.x(), size.height() - secondPoint.y());
}
PassRefPtr<Gradient> CSSLinearGradientValue::createGradient(RenderObject* renderer, const IntSize& size)
{
ASSERT(!size.isEmpty());
RenderStyle* rootStyle = renderer->document()->documentElement()->renderStyle();
FloatPoint firstPoint;
FloatPoint secondPoint;
if (m_angle) {
float angle = m_angle->getFloatValue(CSSPrimitiveValue::CSS_DEG);
endPointsFromAngle(angle, size, firstPoint, secondPoint);
} else {
firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), renderer->style(), rootStyle, size);
if (m_secondX || m_secondY)
secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), renderer->style(), rootStyle, size);
else {
if (m_firstX)
secondPoint.setX(size.width() - firstPoint.x());
if (m_firstY)
secondPoint.setY(size.height() - firstPoint.y());
}
}
RefPtr<Gradient> gradient = Gradient::create(firstPoint, secondPoint);
// Now add the stops.
addStops(gradient.get(), renderer, rootStyle, 1);
return gradient.release();
}
String CSSRadialGradientValue::cssText() const
{
String result;
if (m_deprecatedType) {
result = "-webkit-gradient(radial, ";
result += m_firstX->cssText() + " ";
result += m_firstY->cssText() + ", ";
result += m_firstRadius->cssText() + ", ";
result += m_secondX->cssText() + " ";
result += m_secondY->cssText();
result += ", ";
result += m_secondRadius->cssText();
// FIXME: share?
for (unsigned i = 0; i < m_stops.size(); i++) {
const CSSGradientColorStop& stop = m_stops[i];
result += ", ";
if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0)
result += "from(" + stop.m_color->cssText() + ")";
else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1)
result += "to(" + stop.m_color->cssText() + ")";
else
result += "color-stop(" + String::number(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER)) + ", " + stop.m_color->cssText() + ")";
}
} else {
result = m_repeating ? "-webkit-repeating-radial-gradient(" : "-webkit-radial-gradient(";
if (m_firstX && m_firstY) {
result += m_firstX->cssText() + " " + m_firstY->cssText();
} else if (m_firstX)
result += m_firstX->cssText();
else if (m_firstY)
result += m_firstY->cssText();
else
result += "center";
if (m_shape || m_sizingBehavior) {
result += ", ";
if (m_shape)
result += m_shape->cssText() + " ";
else
result += "ellipse ";
if (m_sizingBehavior)
result += m_sizingBehavior->cssText();
else
result += "cover";
} else if (m_endHorizontalSize && m_endVerticalSize) {
result += ", ";
result += m_endHorizontalSize->cssText() + " " + m_endVerticalSize->cssText();
}
for (unsigned i = 0; i < m_stops.size(); i++) {
const CSSGradientColorStop& stop = m_stops[i];
result += ", ";
result += stop.m_color->cssText();
if (stop.m_position)
result += " " + stop.m_position->cssText();
}
}
result += ")";
return result;
}
float CSSRadialGradientValue::resolveRadius(CSSPrimitiveValue* radius, RenderStyle* style, RenderStyle* rootStyle, float* widthOrHeight)
{
float zoomFactor = style->effectiveZoom();
float result = 0;
if (radius->primitiveType() == CSSPrimitiveValue::CSS_NUMBER) // Can the radius be a percentage?
result = radius->getFloatValue() * zoomFactor;
else if (widthOrHeight && radius->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE)
result = *widthOrHeight * radius->getFloatValue() / 100;
else
result = radius->computeLengthFloat(style, rootStyle, zoomFactor);
return result;
}
static float distanceToClosestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner)
{
FloatPoint topLeft;
float topLeftDistance = FloatSize(p - topLeft).diagonalLength();
FloatPoint topRight(size.width(), 0);
float topRightDistance = FloatSize(p - topRight).diagonalLength();
FloatPoint bottomLeft(0, size.height());
float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength();
FloatPoint bottomRight(size.width(), size.height());
float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength();
corner = topLeft;
float minDistance = topLeftDistance;
if (topRightDistance < minDistance) {
minDistance = topRightDistance;
corner = topRight;
}
if (bottomLeftDistance < minDistance) {
minDistance = bottomLeftDistance;
corner = bottomLeft;
}
if (bottomRightDistance < minDistance) {
minDistance = bottomRightDistance;
corner = bottomRight;
}
return minDistance;
}
static float distanceToFarthestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner)
{
FloatPoint topLeft;
float topLeftDistance = FloatSize(p - topLeft).diagonalLength();
FloatPoint topRight(size.width(), 0);
float topRightDistance = FloatSize(p - topRight).diagonalLength();
FloatPoint bottomLeft(0, size.height());
float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength();
FloatPoint bottomRight(size.width(), size.height());
float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength();
corner = topLeft;
float maxDistance = topLeftDistance;
if (topRightDistance > maxDistance) {
maxDistance = topRightDistance;
corner = topRight;
}
if (bottomLeftDistance > maxDistance) {
maxDistance = bottomLeftDistance;
corner = bottomLeft;
}
if (bottomRightDistance > maxDistance) {
maxDistance = bottomRightDistance;
corner = bottomRight;
}
return maxDistance;
}
// Compute horizontal radius of ellipse with center at 0,0 which passes through p, and has
// width/height given by aspectRatio.
static inline float horizontalEllipseRadius(const FloatSize& p, float aspectRatio)
{
// x^2/a^2 + y^2/b^2 = 1
// a/b = aspectRatio, b = a/aspectRatio
// a = sqrt(x^2 + y^2/(1/r^2))
return sqrtf(p.width() * p.width() + (p.height() * p.height()) / (1 / (aspectRatio * aspectRatio)));
}
// FIXME: share code with the linear version
PassRefPtr<Gradient> CSSRadialGradientValue::createGradient(RenderObject* renderer, const IntSize& size)
{
ASSERT(!size.isEmpty());
RenderStyle* rootStyle = renderer->document()->documentElement()->renderStyle();
FloatPoint firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), renderer->style(), rootStyle, size);
if (!m_firstX)
firstPoint.setX(size.width() / 2);
if (!m_firstY)
firstPoint.setY(size.height() / 2);
FloatPoint secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), renderer->style(), rootStyle, size);
if (!m_secondX)
secondPoint.setX(size.width() / 2);
if (!m_secondY)
secondPoint.setY(size.height() / 2);
float firstRadius = 0;
if (m_firstRadius)
firstRadius = resolveRadius(m_firstRadius.get(), renderer->style(), rootStyle);
float secondRadius = 0;
float aspectRatio = 1; // width / height.
if (m_secondRadius)
secondRadius = resolveRadius(m_secondRadius.get(), renderer->style(), rootStyle);
else if (m_endHorizontalSize || m_endVerticalSize) {
float width = size.width();
float height = size.height();
secondRadius = resolveRadius(m_endHorizontalSize.get(), renderer->style(), rootStyle, &width);
aspectRatio = secondRadius / resolveRadius(m_endVerticalSize.get(), renderer->style(), rootStyle, &height);
} else {
enum GradientShape { Circle, Ellipse };
GradientShape shape = Ellipse;
if (m_shape && m_shape->primitiveType() == CSSPrimitiveValue::CSS_IDENT && m_shape->getIdent() == CSSValueCircle)
shape = Circle;
enum GradientFill { ClosestSide, ClosestCorner, FarthestSide, FarthestCorner };
GradientFill fill = FarthestCorner;
if (m_sizingBehavior && m_sizingBehavior->primitiveType() == CSSPrimitiveValue::CSS_IDENT) {
switch (m_sizingBehavior->getIdent()) {
case CSSValueContain:
case CSSValueClosestSide:
fill = ClosestSide;
break;
case CSSValueClosestCorner:
fill = ClosestCorner;
break;
case CSSValueFarthestSide:
fill = FarthestSide;
break;
case CSSValueCover:
case CSSValueFarthestCorner:
fill = FarthestCorner;
break;
}
}
// Now compute the end radii based on the second point, shape and fill.
// Horizontal
switch (fill) {
case ClosestSide: {
float xDist = min(secondPoint.x(), size.width() - secondPoint.x());
float yDist = min(secondPoint.y(), size.height() - secondPoint.y());
if (shape == Circle) {
float smaller = min(xDist, yDist);
xDist = smaller;
yDist = smaller;
}
secondRadius = xDist;
aspectRatio = xDist / yDist;
break;
}
case FarthestSide: {
float xDist = max(secondPoint.x(), size.width() - secondPoint.x());
float yDist = max(secondPoint.y(), size.height() - secondPoint.y());
if (shape == Circle) {
float larger = max(xDist, yDist);
xDist = larger;
yDist = larger;
}
secondRadius = xDist;
aspectRatio = xDist / yDist;
break;
}
case ClosestCorner: {
FloatPoint corner;
float distance = distanceToClosestCorner(secondPoint, size, corner);
if (shape == Circle)
secondRadius = distance;
else {
// If <shape> is ellipse, the gradient-shape has the same ratio of width to height
// that it would if closest-side or farthest-side were specified, as appropriate.
float xDist = min(secondPoint.x(), size.width() - secondPoint.x());
float yDist = min(secondPoint.y(), size.height() - secondPoint.y());
secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist);
aspectRatio = xDist / yDist;
}
break;
}
case FarthestCorner: {
FloatPoint corner;
float distance = distanceToFarthestCorner(secondPoint, size, corner);
if (shape == Circle)
secondRadius = distance;
else {
// If <shape> is ellipse, the gradient-shape has the same ratio of width to height
// that it would if closest-side or farthest-side were specified, as appropriate.
float xDist = max(secondPoint.x(), size.width() - secondPoint.x());
float yDist = max(secondPoint.y(), size.height() - secondPoint.y());
secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist);
aspectRatio = xDist / yDist;
}
break;
}
}
}
RefPtr<Gradient> gradient = Gradient::create(firstPoint, firstRadius, secondPoint, secondRadius, aspectRatio);
// addStops() only uses maxExtent for repeating gradients.
float maxExtent = 0;
if (m_repeating) {
FloatPoint corner;
maxExtent = distanceToFarthestCorner(secondPoint, size, corner);
}
// Now add the stops.
addStops(gradient.get(), renderer, rootStyle, maxExtent);
return gradient.release();
}
} // namespace WebCore