2 * Copyright (C) 2008 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #include "core/css/CSSGradientValue.h"
29 #include "CSSValueKeywords.h"
30 #include "core/css/CSSCalculationValue.h"
31 #include "core/css/CSSToLengthConversionData.h"
32 #include "core/dom/NodeRenderStyle.h"
33 #include "core/dom/TextLinkColors.h"
34 #include "core/rendering/RenderObject.h"
35 #include "platform/geometry/IntSize.h"
36 #include "platform/graphics/Gradient.h"
37 #include "platform/graphics/GradientGeneratedImage.h"
38 #include "platform/graphics/Image.h"
39 #include "wtf/text/StringBuilder.h"
40 #include "wtf/text/WTFString.h"
46 void CSSGradientColorStop::trace(Visitor* visitor)
48 visitor->trace(m_position);
49 visitor->trace(m_color);
52 PassRefPtr<Image> CSSGradientValue::image(RenderObject* renderer, const IntSize& size)
57 bool cacheable = isCacheable();
59 if (!clients().contains(renderer))
62 // Need to look up our size. Create a string of width*height to use as a hash key.
63 Image* result = getImage(renderer, size);
68 // We need to create an image.
69 RefPtr<Gradient> gradient;
71 RenderStyle* rootStyle = renderer->document().documentElement()->renderStyle();
72 CSSToLengthConversionData conversionData(renderer->style(), rootStyle, renderer->view());
73 if (isLinearGradientValue())
74 gradient = toCSSLinearGradientValue(this)->createGradient(conversionData, size);
76 gradient = toCSSRadialGradientValue(this)->createGradient(conversionData, size);
78 RefPtr<Image> newImage = GradientGeneratedImage::create(gradient, size);
80 putImage(size, newImage);
82 return newImage.release();
85 // Should only ever be called for deprecated gradients.
86 static inline bool compareStops(const CSSGradientColorStop& a, const CSSGradientColorStop& b)
88 double aVal = a.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER);
89 double bVal = b.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER);
94 void CSSGradientValue::sortStopsIfNeeded()
96 ASSERT(m_gradientType == CSSDeprecatedLinearGradient || m_gradientType == CSSDeprecatedRadialGradient);
99 std::stable_sort(m_stops.begin(), m_stops.end(), compareStops);
100 m_stopsSorted = true;
104 struct GradientStop {
115 PassRefPtrWillBeRawPtr<CSSGradientValue> CSSGradientValue::gradientWithStylesResolved(const TextLinkColors& textLinkColors, Color currentColor)
117 bool derived = false;
118 for (unsigned i = 0; i < m_stops.size(); i++)
119 if (m_stops[i].m_color->colorIsDerivedFromElement()) {
120 m_stops[i].m_colorIsDerivedFromElement = true;
125 RefPtrWillBeRawPtr<CSSGradientValue> result;
128 else if (isLinearGradientValue())
129 result = toCSSLinearGradientValue(this)->clone();
130 else if (isRadialGradientValue())
131 result = toCSSRadialGradientValue(this)->clone();
133 ASSERT_NOT_REACHED();
137 for (unsigned i = 0; i < result->m_stops.size(); i++)
138 result->m_stops[i].m_resolvedColor = textLinkColors.colorFromPrimitiveValue(result->m_stops[i].m_color.get(), currentColor);
140 return result.release();
143 void CSSGradientValue::addStops(Gradient* gradient, const CSSToLengthConversionData& conversionData, float maxLengthForRepeat)
145 if (m_gradientType == CSSDeprecatedLinearGradient || m_gradientType == CSSDeprecatedRadialGradient) {
148 for (unsigned i = 0; i < m_stops.size(); i++) {
149 const CSSGradientColorStop& stop = m_stops[i];
152 if (stop.m_position->isPercentage())
153 offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
155 offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_NUMBER);
157 gradient->addColorStop(offset, stop.m_resolvedColor);
160 // The back end already sorted the stops.
161 gradient->setStopsSorted(true);
165 size_t numStops = m_stops.size();
167 Vector<GradientStop> stops(numStops);
169 float gradientLength = 0;
170 bool computedGradientLength = false;
172 FloatPoint gradientStart = gradient->p0();
173 FloatPoint gradientEnd;
174 if (isLinearGradientValue())
175 gradientEnd = gradient->p1();
176 else if (isRadialGradientValue())
177 gradientEnd = gradientStart + FloatSize(gradient->endRadius(), 0);
179 for (size_t i = 0; i < numStops; ++i) {
180 const CSSGradientColorStop& stop = m_stops[i];
182 stops[i].color = stop.m_resolvedColor;
184 if (stop.m_position) {
185 if (stop.m_position->isPercentage())
186 stops[i].offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
187 else if (stop.m_position->isLength() || stop.m_position->isCalculatedPercentageWithLength()) {
188 if (!computedGradientLength) {
189 FloatSize gradientSize(gradientStart - gradientEnd);
190 gradientLength = gradientSize.diagonalLength();
193 if (stop.m_position->isLength())
194 length = stop.m_position->computeLength<float>(conversionData);
196 length = stop.m_position->cssCalcValue()->toCalcValue(conversionData)->evaluate(gradientLength);
197 stops[i].offset = (gradientLength > 0) ? length / gradientLength : 0;
199 ASSERT_NOT_REACHED();
202 stops[i].specified = true;
204 // If the first color-stop does not have a position, its position defaults to 0%.
205 // If the last color-stop does not have a position, its position defaults to 100%.
208 stops[i].specified = true;
209 } else if (numStops > 1 && i == numStops - 1) {
211 stops[i].specified = true;
215 // If a color-stop has a position that is less than the specified position of any
216 // color-stop before it in the list, its position is changed to be equal to the
217 // largest specified position of any color-stop before it.
218 if (stops[i].specified && i > 0) {
219 size_t prevSpecifiedIndex;
220 for (prevSpecifiedIndex = i - 1; prevSpecifiedIndex; --prevSpecifiedIndex) {
221 if (stops[prevSpecifiedIndex].specified)
225 if (stops[i].offset < stops[prevSpecifiedIndex].offset)
226 stops[i].offset = stops[prevSpecifiedIndex].offset;
230 ASSERT(stops[0].specified && stops[numStops - 1].specified);
232 // If any color-stop still does not have a position, then, for each run of adjacent
233 // color-stops without positions, set their positions so that they are evenly spaced
234 // between the preceding and following color-stops with positions.
236 size_t unspecifiedRunStart = 0;
237 bool inUnspecifiedRun = false;
239 for (size_t i = 0; i < numStops; ++i) {
240 if (!stops[i].specified && !inUnspecifiedRun) {
241 unspecifiedRunStart = i;
242 inUnspecifiedRun = true;
243 } else if (stops[i].specified && inUnspecifiedRun) {
244 size_t unspecifiedRunEnd = i;
246 if (unspecifiedRunStart < unspecifiedRunEnd) {
247 float lastSpecifiedOffset = stops[unspecifiedRunStart - 1].offset;
248 float nextSpecifiedOffset = stops[unspecifiedRunEnd].offset;
249 float delta = (nextSpecifiedOffset - lastSpecifiedOffset) / (unspecifiedRunEnd - unspecifiedRunStart + 1);
251 for (size_t j = unspecifiedRunStart; j < unspecifiedRunEnd; ++j)
252 stops[j].offset = lastSpecifiedOffset + (j - unspecifiedRunStart + 1) * delta;
255 inUnspecifiedRun = false;
260 // If the gradient is repeating, repeat the color stops.
261 // We can't just push this logic down into the platform-specific Gradient code,
262 // because we have to know the extent of the gradient, and possible move the end points.
263 if (m_repeating && numStops > 1) {
264 // If the difference in the positions of the first and last color-stops is 0,
265 // the gradient defines a solid-color image with the color of the last color-stop in the rule.
266 float gradientRange = stops[numStops - 1].offset - stops[0].offset;
267 if (!gradientRange) {
268 stops.first().offset = 0;
269 stops.first().color = stops.last().color;
274 // Radial gradients may need to extend further than the endpoints, because they have
275 // to repeat out to the corners of the box.
276 if (isRadialGradientValue()) {
277 if (!computedGradientLength) {
278 FloatSize gradientSize(gradientStart - gradientEnd);
279 gradientLength = gradientSize.diagonalLength();
282 if (maxLengthForRepeat > gradientLength)
283 maxExtent = gradientLength > 0 ? maxLengthForRepeat / gradientLength : 0;
286 size_t originalNumStops = numStops;
287 size_t originalFirstStopIndex = 0;
289 // Work backwards from the first, adding stops until we get one before 0.
290 float firstOffset = stops[0].offset;
291 if (firstOffset > 0) {
292 float currOffset = firstOffset;
293 size_t srcStopOrdinal = originalNumStops - 1;
296 GradientStop newStop = stops[originalFirstStopIndex + srcStopOrdinal];
297 newStop.offset = currOffset;
298 stops.prepend(newStop);
299 ++originalFirstStopIndex;
304 currOffset -= stops[originalFirstStopIndex + srcStopOrdinal].offset - stops[originalFirstStopIndex + srcStopOrdinal - 1].offset;
305 srcStopOrdinal = (srcStopOrdinal + originalNumStops - 1) % originalNumStops;
309 // Work forwards from the end, adding stops until we get one after 1.
310 float lastOffset = stops[stops.size() - 1].offset;
311 if (lastOffset < maxExtent) {
312 float currOffset = lastOffset;
313 size_t srcStopOrdinal = 0;
316 size_t srcStopIndex = originalFirstStopIndex + srcStopOrdinal;
317 GradientStop newStop = stops[srcStopIndex];
318 newStop.offset = currOffset;
319 stops.append(newStop);
320 if (currOffset > maxExtent)
322 if (srcStopOrdinal < originalNumStops - 1)
323 currOffset += stops[srcStopIndex + 1].offset - stops[srcStopIndex].offset;
324 srcStopOrdinal = (srcStopOrdinal + 1) % originalNumStops;
330 numStops = stops.size();
332 // If the gradient goes outside the 0-1 range, normalize it by moving the endpoints, and adjusting the stops.
333 if (numStops > 1 && (stops[0].offset < 0 || stops[numStops - 1].offset > 1)) {
334 if (isLinearGradientValue()) {
335 float firstOffset = stops[0].offset;
336 float lastOffset = stops[numStops - 1].offset;
337 float scale = lastOffset - firstOffset;
339 for (size_t i = 0; i < numStops; ++i)
340 stops[i].offset = (stops[i].offset - firstOffset) / scale;
342 FloatPoint p0 = gradient->p0();
343 FloatPoint p1 = gradient->p1();
344 gradient->setP0(FloatPoint(p0.x() + firstOffset * (p1.x() - p0.x()), p0.y() + firstOffset * (p1.y() - p0.y())));
345 gradient->setP1(FloatPoint(p1.x() + (lastOffset - 1) * (p1.x() - p0.x()), p1.y() + (lastOffset - 1) * (p1.y() - p0.y())));
346 } else if (isRadialGradientValue()) {
347 // Rather than scaling the points < 0, we truncate them, so only scale according to the largest point.
348 float firstOffset = 0;
349 float lastOffset = stops[numStops - 1].offset;
350 float scale = lastOffset - firstOffset;
352 // Reset points below 0 to the first visible color.
353 size_t firstZeroOrGreaterIndex = numStops;
354 for (size_t i = 0; i < numStops; ++i) {
355 if (stops[i].offset >= 0) {
356 firstZeroOrGreaterIndex = i;
361 if (firstZeroOrGreaterIndex > 0) {
362 if (firstZeroOrGreaterIndex < numStops && stops[firstZeroOrGreaterIndex].offset > 0) {
363 float prevOffset = stops[firstZeroOrGreaterIndex - 1].offset;
364 float nextOffset = stops[firstZeroOrGreaterIndex].offset;
366 float interStopProportion = -prevOffset / (nextOffset - prevOffset);
367 // FIXME: when we interpolate gradients using premultiplied colors, this should do premultiplication.
368 Color blendedColor = blend(stops[firstZeroOrGreaterIndex - 1].color, stops[firstZeroOrGreaterIndex].color, interStopProportion);
370 // Clamp the positions to 0 and set the color.
371 for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i) {
373 stops[i].color = blendedColor;
376 // All stops are below 0; just clamp them.
377 for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i)
382 for (size_t i = 0; i < numStops; ++i)
383 stops[i].offset /= scale;
385 gradient->setStartRadius(gradient->startRadius() * scale);
386 gradient->setEndRadius(gradient->endRadius() * scale);
390 for (unsigned i = 0; i < numStops; i++)
391 gradient->addColorStop(stops[i].offset, stops[i].color);
393 gradient->setStopsSorted(true);
396 static float positionFromValue(CSSPrimitiveValue* value, const CSSToLengthConversionData& conversionData, const IntSize& size, bool isHorizontal)
398 if (value->isNumber())
399 return value->getFloatValue() * conversionData.zoom();
401 int edgeDistance = isHorizontal ? size.width() : size.height();
402 if (value->isPercentage())
403 return value->getFloatValue() / 100.f * edgeDistance;
405 if (value->isCalculatedPercentageWithLength())
406 return value->cssCalcValue()->toCalcValue(conversionData)->evaluate(edgeDistance);
408 switch (value->getValueID()) {
410 ASSERT(!isHorizontal);
413 ASSERT(isHorizontal);
416 ASSERT(!isHorizontal);
417 return size.height();
419 ASSERT(isHorizontal);
425 return value->computeLength<float>(conversionData);
428 FloatPoint CSSGradientValue::computeEndPoint(CSSPrimitiveValue* horizontal, CSSPrimitiveValue* vertical, const CSSToLengthConversionData& conversionData, const IntSize& size)
433 result.setX(positionFromValue(horizontal, conversionData, size, true));
436 result.setY(positionFromValue(vertical, conversionData, size, false));
441 bool CSSGradientValue::isCacheable() const
443 for (size_t i = 0; i < m_stops.size(); ++i) {
444 const CSSGradientColorStop& stop = m_stops[i];
446 if (stop.m_colorIsDerivedFromElement)
449 if (!stop.m_position)
452 if (stop.m_position->isFontRelativeLength())
459 bool CSSGradientValue::knownToBeOpaque(const RenderObject*) const
461 for (size_t i = 0; i < m_stops.size(); ++i) {
462 if (m_stops[i].m_resolvedColor.hasAlpha())
468 void CSSGradientValue::traceAfterDispatch(Visitor* visitor)
470 visitor->trace(m_firstX);
471 visitor->trace(m_firstY);
472 visitor->trace(m_secondX);
473 visitor->trace(m_secondY);
474 visitor->trace(m_stops);
475 CSSImageGeneratorValue::traceAfterDispatch(visitor);
478 String CSSLinearGradientValue::customCSSText() const
480 StringBuilder result;
481 if (m_gradientType == CSSDeprecatedLinearGradient) {
482 result.appendLiteral("-webkit-gradient(linear, ");
483 result.append(m_firstX->cssText());
485 result.append(m_firstY->cssText());
486 result.appendLiteral(", ");
487 result.append(m_secondX->cssText());
489 result.append(m_secondY->cssText());
491 for (unsigned i = 0; i < m_stops.size(); i++) {
492 const CSSGradientColorStop& stop = m_stops[i];
493 result.appendLiteral(", ");
494 if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0) {
495 result.appendLiteral("from(");
496 result.append(stop.m_color->cssText());
498 } else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1) {
499 result.appendLiteral("to(");
500 result.append(stop.m_color->cssText());
503 result.appendLiteral("color-stop(");
504 result.append(String::number(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER)));
505 result.appendLiteral(", ");
506 result.append(stop.m_color->cssText());
510 } else if (m_gradientType == CSSPrefixedLinearGradient) {
512 result.appendLiteral("-webkit-repeating-linear-gradient(");
514 result.appendLiteral("-webkit-linear-gradient(");
517 result.append(m_angle->cssText());
519 if (m_firstX && m_firstY) {
520 result.append(m_firstX->cssText());
522 result.append(m_firstY->cssText());
523 } else if (m_firstX || m_firstY) {
525 result.append(m_firstX->cssText());
528 result.append(m_firstY->cssText());
532 for (unsigned i = 0; i < m_stops.size(); i++) {
533 const CSSGradientColorStop& stop = m_stops[i];
534 result.appendLiteral(", ");
535 result.append(stop.m_color->cssText());
536 if (stop.m_position) {
538 result.append(stop.m_position->cssText());
543 result.appendLiteral("repeating-linear-gradient(");
545 result.appendLiteral("linear-gradient(");
547 bool wroteSomething = false;
549 if (m_angle && m_angle->computeDegrees() != 180) {
550 result.append(m_angle->cssText());
551 wroteSomething = true;
552 } else if ((m_firstX || m_firstY) && !(!m_firstX && m_firstY && m_firstY->getValueID() == CSSValueBottom)) {
553 result.appendLiteral("to ");
554 if (m_firstX && m_firstY) {
555 result.append(m_firstX->cssText());
557 result.append(m_firstY->cssText());
559 result.append(m_firstX->cssText());
561 result.append(m_firstY->cssText());
562 wroteSomething = true;
566 result.appendLiteral(", ");
568 for (unsigned i = 0; i < m_stops.size(); i++) {
569 const CSSGradientColorStop& stop = m_stops[i];
571 result.appendLiteral(", ");
572 result.append(stop.m_color->cssText());
573 if (stop.m_position) {
575 result.append(stop.m_position->cssText());
582 return result.toString();
585 // Compute the endpoints so that a gradient of the given angle covers a box of the given size.
586 static void endPointsFromAngle(float angleDeg, const IntSize& size, FloatPoint& firstPoint, FloatPoint& secondPoint, CSSGradientType type)
588 // Prefixed gradients use "polar coordinate" angles, rather than "bearing" angles.
589 if (type == CSSPrefixedLinearGradient)
590 angleDeg = 90 - angleDeg;
592 angleDeg = fmodf(angleDeg, 360);
597 firstPoint.set(0, size.height());
598 secondPoint.set(0, 0);
602 if (angleDeg == 90) {
603 firstPoint.set(0, 0);
604 secondPoint.set(size.width(), 0);
608 if (angleDeg == 180) {
609 firstPoint.set(0, 0);
610 secondPoint.set(0, size.height());
614 if (angleDeg == 270) {
615 firstPoint.set(size.width(), 0);
616 secondPoint.set(0, 0);
620 // angleDeg is a "bearing angle" (0deg = N, 90deg = E),
621 // but tan expects 0deg = E, 90deg = N.
622 float slope = tan(deg2rad(90 - angleDeg));
624 // We find the endpoint by computing the intersection of the line formed by the slope,
625 // and a line perpendicular to it that intersects the corner.
626 float perpendicularSlope = -1 / slope;
628 // Compute start corner relative to center, in Cartesian space (+y = up).
629 float halfHeight = size.height() / 2;
630 float halfWidth = size.width() / 2;
631 FloatPoint endCorner;
633 endCorner.set(halfWidth, halfHeight);
634 else if (angleDeg < 180)
635 endCorner.set(halfWidth, -halfHeight);
636 else if (angleDeg < 270)
637 endCorner.set(-halfWidth, -halfHeight);
639 endCorner.set(-halfWidth, halfHeight);
641 // Compute c (of y = mx + c) using the corner point.
642 float c = endCorner.y() - perpendicularSlope * endCorner.x();
643 float endX = c / (slope - perpendicularSlope);
644 float endY = perpendicularSlope * endX + c;
646 // We computed the end point, so set the second point,
647 // taking into account the moved origin and the fact that we're in drawing space (+y = down).
648 secondPoint.set(halfWidth + endX, halfHeight - endY);
649 // Reflect around the center for the start point.
650 firstPoint.set(halfWidth - endX, halfHeight + endY);
653 PassRefPtr<Gradient> CSSLinearGradientValue::createGradient(const CSSToLengthConversionData& conversionData, const IntSize& size)
655 ASSERT(!size.isEmpty());
657 FloatPoint firstPoint;
658 FloatPoint secondPoint;
660 float angle = m_angle->getFloatValue(CSSPrimitiveValue::CSS_DEG);
661 endPointsFromAngle(angle, size, firstPoint, secondPoint, m_gradientType);
663 switch (m_gradientType) {
664 case CSSDeprecatedLinearGradient:
665 firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
666 if (m_secondX || m_secondY)
667 secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), conversionData, size);
670 secondPoint.setX(size.width() - firstPoint.x());
672 secondPoint.setY(size.height() - firstPoint.y());
675 case CSSPrefixedLinearGradient:
676 firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
678 secondPoint.setX(size.width() - firstPoint.x());
680 secondPoint.setY(size.height() - firstPoint.y());
682 case CSSLinearGradient:
683 if (m_firstX && m_firstY) {
684 // "Magic" corners, so the 50% line touches two corners.
685 float rise = size.width();
686 float run = size.height();
687 if (m_firstX && m_firstX->getValueID() == CSSValueLeft)
689 if (m_firstY && m_firstY->getValueID() == CSSValueBottom)
691 // Compute angle, and flip it back to "bearing angle" degrees.
692 float angle = 90 - rad2deg(atan2(rise, run));
693 endPointsFromAngle(angle, size, firstPoint, secondPoint, m_gradientType);
694 } else if (m_firstX || m_firstY) {
695 secondPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
697 firstPoint.setX(size.width() - secondPoint.x());
699 firstPoint.setY(size.height() - secondPoint.y());
701 secondPoint.setY(size.height());
704 ASSERT_NOT_REACHED();
709 RefPtr<Gradient> gradient = Gradient::create(firstPoint, secondPoint);
711 gradient->setDrawsInPMColorSpace(true);
713 // Now add the stops.
714 addStops(gradient.get(), conversionData, 1);
716 return gradient.release();
719 bool CSSLinearGradientValue::equals(const CSSLinearGradientValue& other) const
721 if (m_gradientType == CSSDeprecatedLinearGradient)
722 return other.m_gradientType == m_gradientType
723 && compareCSSValuePtr(m_firstX, other.m_firstX)
724 && compareCSSValuePtr(m_firstY, other.m_firstY)
725 && compareCSSValuePtr(m_secondX, other.m_secondX)
726 && compareCSSValuePtr(m_secondY, other.m_secondY)
727 && m_stops == other.m_stops;
729 if (m_repeating != other.m_repeating)
733 return compareCSSValuePtr(m_angle, other.m_angle) && m_stops == other.m_stops;
738 bool equalXandY = false;
739 if (m_firstX && m_firstY)
740 equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && compareCSSValuePtr(m_firstY, other.m_firstY);
742 equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && !other.m_firstY;
744 equalXandY = compareCSSValuePtr(m_firstY, other.m_firstY) && !other.m_firstX;
746 equalXandY = !other.m_firstX && !other.m_firstY;
748 return equalXandY && m_stops == other.m_stops;
751 void CSSLinearGradientValue::traceAfterDispatch(Visitor* visitor)
753 visitor->trace(m_angle);
754 CSSGradientValue::traceAfterDispatch(visitor);
757 String CSSRadialGradientValue::customCSSText() const
759 StringBuilder result;
761 if (m_gradientType == CSSDeprecatedRadialGradient) {
762 result.appendLiteral("-webkit-gradient(radial, ");
763 result.append(m_firstX->cssText());
765 result.append(m_firstY->cssText());
766 result.appendLiteral(", ");
767 result.append(m_firstRadius->cssText());
768 result.appendLiteral(", ");
769 result.append(m_secondX->cssText());
771 result.append(m_secondY->cssText());
772 result.appendLiteral(", ");
773 result.append(m_secondRadius->cssText());
776 for (unsigned i = 0; i < m_stops.size(); i++) {
777 const CSSGradientColorStop& stop = m_stops[i];
778 result.appendLiteral(", ");
779 if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0) {
780 result.appendLiteral("from(");
781 result.append(stop.m_color->cssText());
783 } else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1) {
784 result.appendLiteral("to(");
785 result.append(stop.m_color->cssText());
788 result.appendLiteral("color-stop(");
789 result.append(String::number(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER)));
790 result.appendLiteral(", ");
791 result.append(stop.m_color->cssText());
795 } else if (m_gradientType == CSSPrefixedRadialGradient) {
797 result.appendLiteral("-webkit-repeating-radial-gradient(");
799 result.appendLiteral("-webkit-radial-gradient(");
801 if (m_firstX && m_firstY) {
802 result.append(m_firstX->cssText());
804 result.append(m_firstY->cssText());
806 result.append(m_firstX->cssText());
808 result.append(m_firstY->cssText());
810 result.appendLiteral("center");
812 if (m_shape || m_sizingBehavior) {
813 result.appendLiteral(", ");
815 result.append(m_shape->cssText());
818 result.appendLiteral("ellipse ");
820 if (m_sizingBehavior)
821 result.append(m_sizingBehavior->cssText());
823 result.appendLiteral("cover");
825 } else if (m_endHorizontalSize && m_endVerticalSize) {
826 result.appendLiteral(", ");
827 result.append(m_endHorizontalSize->cssText());
829 result.append(m_endVerticalSize->cssText());
832 for (unsigned i = 0; i < m_stops.size(); i++) {
833 const CSSGradientColorStop& stop = m_stops[i];
834 result.appendLiteral(", ");
835 result.append(stop.m_color->cssText());
836 if (stop.m_position) {
838 result.append(stop.m_position->cssText());
843 result.appendLiteral("repeating-radial-gradient(");
845 result.appendLiteral("radial-gradient(");
847 bool wroteSomething = false;
849 // The only ambiguous case that needs an explicit shape to be provided
850 // is when a sizing keyword is used (or all sizing is omitted).
851 if (m_shape && m_shape->getValueID() != CSSValueEllipse && (m_sizingBehavior || (!m_sizingBehavior && !m_endHorizontalSize))) {
852 result.appendLiteral("circle");
853 wroteSomething = true;
856 if (m_sizingBehavior && m_sizingBehavior->getValueID() != CSSValueFarthestCorner) {
859 result.append(m_sizingBehavior->cssText());
860 wroteSomething = true;
861 } else if (m_endHorizontalSize) {
864 result.append(m_endHorizontalSize->cssText());
865 if (m_endVerticalSize) {
867 result.append(m_endVerticalSize->cssText());
869 wroteSomething = true;
872 if (m_firstX || m_firstY) {
875 result.appendLiteral("at ");
876 if (m_firstX && m_firstY) {
877 result.append(m_firstX->cssText());
879 result.append(m_firstY->cssText());
881 result.append(m_firstX->cssText());
883 result.append(m_firstY->cssText());
884 wroteSomething = true;
888 result.appendLiteral(", ");
890 for (unsigned i = 0; i < m_stops.size(); i++) {
891 const CSSGradientColorStop& stop = m_stops[i];
893 result.appendLiteral(", ");
894 result.append(stop.m_color->cssText());
895 if (stop.m_position) {
897 result.append(stop.m_position->cssText());
904 return result.toString();
907 float CSSRadialGradientValue::resolveRadius(CSSPrimitiveValue* radius, const CSSToLengthConversionData& conversionData, float* widthOrHeight)
910 if (radius->isNumber()) // Can the radius be a percentage?
911 result = radius->getFloatValue() * conversionData.zoom();
912 else if (widthOrHeight && radius->isPercentage())
913 result = *widthOrHeight * radius->getFloatValue() / 100;
915 result = radius->computeLength<float>(conversionData);
920 static float distanceToClosestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner)
923 float topLeftDistance = FloatSize(p - topLeft).diagonalLength();
925 FloatPoint topRight(size.width(), 0);
926 float topRightDistance = FloatSize(p - topRight).diagonalLength();
928 FloatPoint bottomLeft(0, size.height());
929 float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength();
931 FloatPoint bottomRight(size.width(), size.height());
932 float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength();
935 float minDistance = topLeftDistance;
936 if (topRightDistance < minDistance) {
937 minDistance = topRightDistance;
941 if (bottomLeftDistance < minDistance) {
942 minDistance = bottomLeftDistance;
946 if (bottomRightDistance < minDistance) {
947 minDistance = bottomRightDistance;
948 corner = bottomRight;
953 static float distanceToFarthestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner)
956 float topLeftDistance = FloatSize(p - topLeft).diagonalLength();
958 FloatPoint topRight(size.width(), 0);
959 float topRightDistance = FloatSize(p - topRight).diagonalLength();
961 FloatPoint bottomLeft(0, size.height());
962 float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength();
964 FloatPoint bottomRight(size.width(), size.height());
965 float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength();
968 float maxDistance = topLeftDistance;
969 if (topRightDistance > maxDistance) {
970 maxDistance = topRightDistance;
974 if (bottomLeftDistance > maxDistance) {
975 maxDistance = bottomLeftDistance;
979 if (bottomRightDistance > maxDistance) {
980 maxDistance = bottomRightDistance;
981 corner = bottomRight;
986 // Compute horizontal radius of ellipse with center at 0,0 which passes through p, and has
987 // width/height given by aspectRatio.
988 static inline float horizontalEllipseRadius(const FloatSize& p, float aspectRatio)
990 // x^2/a^2 + y^2/b^2 = 1
991 // a/b = aspectRatio, b = a/aspectRatio
992 // a = sqrt(x^2 + y^2/(1/r^2))
993 return sqrtf(p.width() * p.width() + (p.height() * p.height()) / (1 / (aspectRatio * aspectRatio)));
996 // FIXME: share code with the linear version
997 PassRefPtr<Gradient> CSSRadialGradientValue::createGradient(const CSSToLengthConversionData& conversionData, const IntSize& size)
999 ASSERT(!size.isEmpty());
1001 FloatPoint firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
1003 firstPoint.setX(size.width() / 2);
1005 firstPoint.setY(size.height() / 2);
1007 FloatPoint secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), conversionData, size);
1009 secondPoint.setX(size.width() / 2);
1011 secondPoint.setY(size.height() / 2);
1013 float firstRadius = 0;
1015 firstRadius = resolveRadius(m_firstRadius.get(), conversionData);
1017 float secondRadius = 0;
1018 float aspectRatio = 1; // width / height.
1020 secondRadius = resolveRadius(m_secondRadius.get(), conversionData);
1021 else if (m_endHorizontalSize) {
1022 float width = size.width();
1023 float height = size.height();
1024 secondRadius = resolveRadius(m_endHorizontalSize.get(), conversionData, &width);
1025 if (m_endVerticalSize)
1026 aspectRatio = secondRadius / resolveRadius(m_endVerticalSize.get(), conversionData, &height);
1030 enum GradientShape { Circle, Ellipse };
1031 GradientShape shape = Ellipse;
1032 if ((m_shape && m_shape->getValueID() == CSSValueCircle)
1033 || (!m_shape && !m_sizingBehavior && m_endHorizontalSize && !m_endVerticalSize))
1036 enum GradientFill { ClosestSide, ClosestCorner, FarthestSide, FarthestCorner };
1037 GradientFill fill = FarthestCorner;
1039 switch (m_sizingBehavior ? m_sizingBehavior->getValueID() : 0) {
1040 case CSSValueContain:
1041 case CSSValueClosestSide:
1044 case CSSValueClosestCorner:
1045 fill = ClosestCorner;
1047 case CSSValueFarthestSide:
1048 fill = FarthestSide;
1051 case CSSValueFarthestCorner:
1052 fill = FarthestCorner;
1058 // Now compute the end radii based on the second point, shape and fill.
1063 float xDist = min(secondPoint.x(), size.width() - secondPoint.x());
1064 float yDist = min(secondPoint.y(), size.height() - secondPoint.y());
1065 if (shape == Circle) {
1066 float smaller = min(xDist, yDist);
1070 secondRadius = xDist;
1071 aspectRatio = xDist / yDist;
1074 case FarthestSide: {
1075 float xDist = max(secondPoint.x(), size.width() - secondPoint.x());
1076 float yDist = max(secondPoint.y(), size.height() - secondPoint.y());
1077 if (shape == Circle) {
1078 float larger = max(xDist, yDist);
1082 secondRadius = xDist;
1083 aspectRatio = xDist / yDist;
1086 case ClosestCorner: {
1088 float distance = distanceToClosestCorner(secondPoint, size, corner);
1089 if (shape == Circle)
1090 secondRadius = distance;
1092 // If <shape> is ellipse, the gradient-shape has the same ratio of width to height
1093 // that it would if closest-side or farthest-side were specified, as appropriate.
1094 float xDist = min(secondPoint.x(), size.width() - secondPoint.x());
1095 float yDist = min(secondPoint.y(), size.height() - secondPoint.y());
1097 secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist);
1098 aspectRatio = xDist / yDist;
1103 case FarthestCorner: {
1105 float distance = distanceToFarthestCorner(secondPoint, size, corner);
1106 if (shape == Circle)
1107 secondRadius = distance;
1109 // If <shape> is ellipse, the gradient-shape has the same ratio of width to height
1110 // that it would if closest-side or farthest-side were specified, as appropriate.
1111 float xDist = max(secondPoint.x(), size.width() - secondPoint.x());
1112 float yDist = max(secondPoint.y(), size.height() - secondPoint.y());
1114 secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist);
1115 aspectRatio = xDist / yDist;
1122 RefPtr<Gradient> gradient = Gradient::create(firstPoint, firstRadius, secondPoint, secondRadius, aspectRatio);
1124 gradient->setDrawsInPMColorSpace(true);
1126 // addStops() only uses maxExtent for repeating gradients.
1127 float maxExtent = 0;
1130 maxExtent = distanceToFarthestCorner(secondPoint, size, corner);
1133 // Now add the stops.
1134 addStops(gradient.get(), conversionData, maxExtent);
1136 return gradient.release();
1139 bool CSSRadialGradientValue::equals(const CSSRadialGradientValue& other) const
1141 if (m_gradientType == CSSDeprecatedRadialGradient)
1142 return other.m_gradientType == m_gradientType
1143 && compareCSSValuePtr(m_firstX, other.m_firstX)
1144 && compareCSSValuePtr(m_firstY, other.m_firstY)
1145 && compareCSSValuePtr(m_secondX, other.m_secondX)
1146 && compareCSSValuePtr(m_secondY, other.m_secondY)
1147 && compareCSSValuePtr(m_firstRadius, other.m_firstRadius)
1148 && compareCSSValuePtr(m_secondRadius, other.m_secondRadius)
1149 && m_stops == other.m_stops;
1151 if (m_repeating != other.m_repeating)
1154 bool equalXandY = false;
1155 if (m_firstX && m_firstY)
1156 equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && compareCSSValuePtr(m_firstY, other.m_firstY);
1158 equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && !other.m_firstY;
1160 equalXandY = compareCSSValuePtr(m_firstY, other.m_firstY) && !other.m_firstX;
1162 equalXandY = !other.m_firstX && !other.m_firstY;
1167 bool equalShape = true;
1168 bool equalSizingBehavior = true;
1169 bool equalHorizontalAndVerticalSize = true;
1172 equalShape = compareCSSValuePtr(m_shape, other.m_shape);
1173 else if (m_sizingBehavior)
1174 equalSizingBehavior = compareCSSValuePtr(m_sizingBehavior, other.m_sizingBehavior);
1175 else if (m_endHorizontalSize && m_endVerticalSize)
1176 equalHorizontalAndVerticalSize = compareCSSValuePtr(m_endHorizontalSize, other.m_endHorizontalSize) && compareCSSValuePtr(m_endVerticalSize, other.m_endVerticalSize);
1178 equalShape = !other.m_shape;
1179 equalSizingBehavior = !other.m_sizingBehavior;
1180 equalHorizontalAndVerticalSize = !other.m_endHorizontalSize && !other.m_endVerticalSize;
1182 return equalShape && equalSizingBehavior && equalHorizontalAndVerticalSize && m_stops == other.m_stops;
1185 void CSSRadialGradientValue::traceAfterDispatch(Visitor* visitor)
1187 visitor->trace(m_firstRadius);
1188 visitor->trace(m_secondRadius);
1189 visitor->trace(m_shape);
1190 visitor->trace(m_sizingBehavior);
1191 visitor->trace(m_endHorizontalSize);
1192 visitor->trace(m_endVerticalSize);
1193 CSSGradientValue::traceAfterDispatch(visitor);
1196 } // namespace WebCore