| /* |
| * 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 |