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 "core/CSSValueKeywords.h"
30 #include "core/css/CSSCalculationValue.h"
31 #include "core/css/CSSToLengthConversionData.h"
32 #include "core/css/Pair.h"
33 #include "core/dom/NodeRenderStyle.h"
34 #include "core/dom/TextLinkColors.h"
35 #include "core/rendering/RenderObject.h"
36 #include "platform/geometry/IntSize.h"
37 #include "platform/graphics/Gradient.h"
38 #include "platform/graphics/GradientGeneratedImage.h"
39 #include "platform/graphics/Image.h"
40 #include "wtf/text/StringBuilder.h"
41 #include "wtf/text/WTFString.h"
45 void CSSGradientColorStop::trace(Visitor* visitor)
47 visitor->trace(m_position);
48 visitor->trace(m_color);
51 PassRefPtr<Image> CSSGradientValue::image(RenderObject* renderer, const IntSize& size)
56 bool cacheable = isCacheable();
58 if (!clients().contains(renderer))
61 // Need to look up our size. Create a string of width*height to use as a hash key.
62 Image* result = getImage(renderer, size);
67 // We need to create an image.
68 RefPtr<Gradient> gradient;
70 RenderStyle* rootStyle = renderer->document().documentElement()->renderStyle();
71 CSSToLengthConversionData conversionData(renderer->style(), rootStyle, renderer->view());
72 if (isLinearGradientValue())
73 gradient = toCSSLinearGradientValue(this)->createGradient(conversionData, size);
75 gradient = toCSSRadialGradientValue(this)->createGradient(conversionData, size);
77 RefPtr<Image> newImage = GradientGeneratedImage::create(gradient, size);
79 putImage(size, newImage);
81 return newImage.release();
84 // Should only ever be called for deprecated gradients.
85 static inline bool compareStops(const CSSGradientColorStop& a, const CSSGradientColorStop& b)
87 double aVal = a.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER);
88 double bVal = b.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER);
93 void CSSGradientValue::sortStopsIfNeeded()
95 ASSERT(m_gradientType == CSSDeprecatedLinearGradient || m_gradientType == CSSDeprecatedRadialGradient);
98 std::stable_sort(m_stops.begin(), m_stops.end(), compareStops);
103 struct GradientStop {
114 PassRefPtrWillBeRawPtr<CSSGradientValue> CSSGradientValue::gradientWithStylesResolved(const TextLinkColors& textLinkColors, Color currentColor)
116 bool derived = false;
117 for (unsigned i = 0; i < m_stops.size(); i++)
118 if (m_stops[i].m_color->colorIsDerivedFromElement()) {
119 m_stops[i].m_colorIsDerivedFromElement = true;
124 RefPtrWillBeRawPtr<CSSGradientValue> result = nullptr;
127 else if (isLinearGradientValue())
128 result = toCSSLinearGradientValue(this)->clone();
129 else if (isRadialGradientValue())
130 result = toCSSRadialGradientValue(this)->clone();
132 ASSERT_NOT_REACHED();
136 for (unsigned i = 0; i < result->m_stops.size(); i++)
137 result->m_stops[i].m_resolvedColor = textLinkColors.colorFromPrimitiveValue(result->m_stops[i].m_color.get(), currentColor);
139 return result.release();
142 void CSSGradientValue::addStops(Gradient* gradient, const CSSToLengthConversionData& conversionData, float maxLengthForRepeat)
144 if (m_gradientType == CSSDeprecatedLinearGradient || m_gradientType == CSSDeprecatedRadialGradient) {
147 for (unsigned i = 0; i < m_stops.size(); i++) {
148 const CSSGradientColorStop& stop = m_stops[i];
151 if (stop.m_position->isPercentage())
152 offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
154 offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_NUMBER);
156 gradient->addColorStop(offset, stop.m_resolvedColor);
162 size_t numStops = m_stops.size();
164 Vector<GradientStop> stops(numStops);
166 float gradientLength = 0;
167 bool computedGradientLength = false;
169 FloatPoint gradientStart = gradient->p0();
170 FloatPoint gradientEnd;
171 if (isLinearGradientValue())
172 gradientEnd = gradient->p1();
173 else if (isRadialGradientValue())
174 gradientEnd = gradientStart + FloatSize(gradient->endRadius(), 0);
176 for (size_t i = 0; i < numStops; ++i) {
177 const CSSGradientColorStop& stop = m_stops[i];
179 stops[i].color = stop.m_resolvedColor;
181 if (stop.m_position) {
182 if (stop.m_position->isPercentage())
183 stops[i].offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
184 else if (stop.m_position->isLength() || stop.m_position->isCalculatedPercentageWithLength()) {
185 if (!computedGradientLength) {
186 FloatSize gradientSize(gradientStart - gradientEnd);
187 gradientLength = gradientSize.diagonalLength();
190 if (stop.m_position->isLength())
191 length = stop.m_position->computeLength<float>(conversionData);
193 length = stop.m_position->cssCalcValue()->toCalcValue(conversionData)->evaluate(gradientLength);
194 stops[i].offset = (gradientLength > 0) ? length / gradientLength : 0;
196 ASSERT_NOT_REACHED();
199 stops[i].specified = true;
201 // If the first color-stop does not have a position, its position defaults to 0%.
202 // If the last color-stop does not have a position, its position defaults to 100%.
205 stops[i].specified = true;
206 } else if (numStops > 1 && i == numStops - 1) {
208 stops[i].specified = true;
212 // If a color-stop has a position that is less than the specified position of any
213 // color-stop before it in the list, its position is changed to be equal to the
214 // largest specified position of any color-stop before it.
215 if (stops[i].specified && i > 0) {
216 size_t prevSpecifiedIndex;
217 for (prevSpecifiedIndex = i - 1; prevSpecifiedIndex; --prevSpecifiedIndex) {
218 if (stops[prevSpecifiedIndex].specified)
222 if (stops[i].offset < stops[prevSpecifiedIndex].offset)
223 stops[i].offset = stops[prevSpecifiedIndex].offset;
227 ASSERT(stops[0].specified && stops[numStops - 1].specified);
229 // If any color-stop still does not have a position, then, for each run of adjacent
230 // color-stops without positions, set their positions so that they are evenly spaced
231 // between the preceding and following color-stops with positions.
233 size_t unspecifiedRunStart = 0;
234 bool inUnspecifiedRun = false;
236 for (size_t i = 0; i < numStops; ++i) {
237 if (!stops[i].specified && !inUnspecifiedRun) {
238 unspecifiedRunStart = i;
239 inUnspecifiedRun = true;
240 } else if (stops[i].specified && inUnspecifiedRun) {
241 size_t unspecifiedRunEnd = i;
243 if (unspecifiedRunStart < unspecifiedRunEnd) {
244 float lastSpecifiedOffset = stops[unspecifiedRunStart - 1].offset;
245 float nextSpecifiedOffset = stops[unspecifiedRunEnd].offset;
246 float delta = (nextSpecifiedOffset - lastSpecifiedOffset) / (unspecifiedRunEnd - unspecifiedRunStart + 1);
248 for (size_t j = unspecifiedRunStart; j < unspecifiedRunEnd; ++j)
249 stops[j].offset = lastSpecifiedOffset + (j - unspecifiedRunStart + 1) * delta;
252 inUnspecifiedRun = false;
257 // If the gradient is repeating, repeat the color stops.
258 // We can't just push this logic down into the platform-specific Gradient code,
259 // because we have to know the extent of the gradient, and possible move the end points.
260 if (m_repeating && numStops > 1) {
261 // If the difference in the positions of the first and last color-stops is 0,
262 // the gradient defines a solid-color image with the color of the last color-stop in the rule.
263 float gradientRange = stops[numStops - 1].offset - stops[0].offset;
264 if (!gradientRange) {
265 stops.first().offset = 0;
266 stops.first().color = stops.last().color;
271 // Radial gradients may need to extend further than the endpoints, because they have
272 // to repeat out to the corners of the box.
273 if (isRadialGradientValue()) {
274 if (!computedGradientLength) {
275 FloatSize gradientSize(gradientStart - gradientEnd);
276 gradientLength = gradientSize.diagonalLength();
279 if (maxLengthForRepeat > gradientLength)
280 maxExtent = gradientLength > 0 ? maxLengthForRepeat / gradientLength : 0;
283 size_t originalNumStops = numStops;
284 size_t originalFirstStopIndex = 0;
286 // Work backwards from the first, adding stops until we get one before 0.
287 float firstOffset = stops[0].offset;
288 if (firstOffset > 0) {
289 float currOffset = firstOffset;
290 size_t srcStopOrdinal = originalNumStops - 1;
293 GradientStop newStop = stops[originalFirstStopIndex + srcStopOrdinal];
294 newStop.offset = currOffset;
295 stops.prepend(newStop);
296 ++originalFirstStopIndex;
301 currOffset -= stops[originalFirstStopIndex + srcStopOrdinal].offset - stops[originalFirstStopIndex + srcStopOrdinal - 1].offset;
302 srcStopOrdinal = (srcStopOrdinal + originalNumStops - 1) % originalNumStops;
306 // Work forwards from the end, adding stops until we get one after 1.
307 float lastOffset = stops[stops.size() - 1].offset;
308 if (lastOffset < maxExtent) {
309 float currOffset = lastOffset;
310 size_t srcStopOrdinal = 0;
313 size_t srcStopIndex = originalFirstStopIndex + srcStopOrdinal;
314 GradientStop newStop = stops[srcStopIndex];
315 newStop.offset = currOffset;
316 stops.append(newStop);
317 if (currOffset > maxExtent)
319 if (srcStopOrdinal < originalNumStops - 1)
320 currOffset += stops[srcStopIndex + 1].offset - stops[srcStopIndex].offset;
321 srcStopOrdinal = (srcStopOrdinal + 1) % originalNumStops;
327 numStops = stops.size();
329 // If the gradient goes outside the 0-1 range, normalize it by moving the endpoints, and adjusting the stops.
330 if (numStops > 1 && (stops[0].offset < 0 || stops[numStops - 1].offset > 1)) {
331 if (isLinearGradientValue()) {
332 float firstOffset = stops[0].offset;
333 float lastOffset = stops[numStops - 1].offset;
334 float scale = lastOffset - firstOffset;
336 for (size_t i = 0; i < numStops; ++i)
337 stops[i].offset = (stops[i].offset - firstOffset) / scale;
339 FloatPoint p0 = gradient->p0();
340 FloatPoint p1 = gradient->p1();
341 gradient->setP0(FloatPoint(p0.x() + firstOffset * (p1.x() - p0.x()), p0.y() + firstOffset * (p1.y() - p0.y())));
342 gradient->setP1(FloatPoint(p1.x() + (lastOffset - 1) * (p1.x() - p0.x()), p1.y() + (lastOffset - 1) * (p1.y() - p0.y())));
343 } else if (isRadialGradientValue()) {
344 // Rather than scaling the points < 0, we truncate them, so only scale according to the largest point.
345 float firstOffset = 0;
346 float lastOffset = stops[numStops - 1].offset;
347 float scale = lastOffset - firstOffset;
349 // Reset points below 0 to the first visible color.
350 size_t firstZeroOrGreaterIndex = numStops;
351 for (size_t i = 0; i < numStops; ++i) {
352 if (stops[i].offset >= 0) {
353 firstZeroOrGreaterIndex = i;
358 if (firstZeroOrGreaterIndex > 0) {
359 if (firstZeroOrGreaterIndex < numStops && stops[firstZeroOrGreaterIndex].offset > 0) {
360 float prevOffset = stops[firstZeroOrGreaterIndex - 1].offset;
361 float nextOffset = stops[firstZeroOrGreaterIndex].offset;
363 float interStopProportion = -prevOffset / (nextOffset - prevOffset);
364 // FIXME: when we interpolate gradients using premultiplied colors, this should do premultiplication.
365 Color blendedColor = blend(stops[firstZeroOrGreaterIndex - 1].color, stops[firstZeroOrGreaterIndex].color, interStopProportion);
367 // Clamp the positions to 0 and set the color.
368 for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i) {
370 stops[i].color = blendedColor;
373 // All stops are below 0; just clamp them.
374 for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i)
379 for (size_t i = 0; i < numStops; ++i)
380 stops[i].offset /= scale;
382 gradient->setStartRadius(gradient->startRadius() * scale);
383 gradient->setEndRadius(gradient->endRadius() * scale);
387 for (unsigned i = 0; i < numStops; i++)
388 gradient->addColorStop(stops[i].offset, stops[i].color);
391 static float positionFromValue(CSSPrimitiveValue* value, const CSSToLengthConversionData& conversionData, const IntSize& size, bool isHorizontal)
395 int edgeDistance = isHorizontal ? size.width() : size.height();
397 // In this case the center of the gradient is given relative to an edge in the form of:
398 // [ top | bottom | right | left ] [ <percentage> | <length> ].
399 if (Pair* pair = value->getPairValue()) {
400 CSSValueID originID = pair->first()->getValueID();
401 value = pair->second();
403 if (originID == CSSValueRight || originID == CSSValueBottom) {
404 // For right/bottom, the offset is relative to the far edge.
405 origin = edgeDistance;
410 if (value->isNumber())
411 return origin + sign * value->getFloatValue() * conversionData.zoom();
413 if (value->isPercentage())
414 return origin + sign * value->getFloatValue() / 100.f * edgeDistance;
416 if (value->isCalculatedPercentageWithLength())
417 return origin + sign * value->cssCalcValue()->toCalcValue(conversionData)->evaluate(edgeDistance);
419 switch (value->getValueID()) {
421 ASSERT(!isHorizontal);
424 ASSERT(isHorizontal);
427 ASSERT(!isHorizontal);
428 return size.height();
430 ASSERT(isHorizontal);
436 return origin + sign * value->computeLength<float>(conversionData);
439 FloatPoint CSSGradientValue::computeEndPoint(CSSPrimitiveValue* horizontal, CSSPrimitiveValue* vertical, const CSSToLengthConversionData& conversionData, const IntSize& size)
444 result.setX(positionFromValue(horizontal, conversionData, size, true));
447 result.setY(positionFromValue(vertical, conversionData, size, false));
452 bool CSSGradientValue::isCacheable() const
454 for (size_t i = 0; i < m_stops.size(); ++i) {
455 const CSSGradientColorStop& stop = m_stops[i];
457 if (stop.m_colorIsDerivedFromElement)
460 if (!stop.m_position)
463 if (stop.m_position->isFontRelativeLength())
470 bool CSSGradientValue::knownToBeOpaque(const RenderObject*) const
472 for (size_t i = 0; i < m_stops.size(); ++i) {
473 if (m_stops[i].m_resolvedColor.hasAlpha())
479 void CSSGradientValue::traceAfterDispatch(Visitor* visitor)
481 visitor->trace(m_firstX);
482 visitor->trace(m_firstY);
483 visitor->trace(m_secondX);
484 visitor->trace(m_secondY);
485 visitor->trace(m_stops);
486 CSSImageGeneratorValue::traceAfterDispatch(visitor);
489 String CSSLinearGradientValue::customCSSText() const
491 StringBuilder result;
492 if (m_gradientType == CSSDeprecatedLinearGradient) {
493 result.appendLiteral("-webkit-gradient(linear, ");
494 result.append(m_firstX->cssText());
496 result.append(m_firstY->cssText());
497 result.appendLiteral(", ");
498 result.append(m_secondX->cssText());
500 result.append(m_secondY->cssText());
502 for (unsigned i = 0; i < m_stops.size(); i++) {
503 const CSSGradientColorStop& stop = m_stops[i];
504 result.appendLiteral(", ");
505 if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0) {
506 result.appendLiteral("from(");
507 result.append(stop.m_color->cssText());
509 } else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1) {
510 result.appendLiteral("to(");
511 result.append(stop.m_color->cssText());
514 result.appendLiteral("color-stop(");
515 result.appendNumber(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER));
516 result.appendLiteral(", ");
517 result.append(stop.m_color->cssText());
521 } else if (m_gradientType == CSSPrefixedLinearGradient) {
523 result.appendLiteral("-webkit-repeating-linear-gradient(");
525 result.appendLiteral("-webkit-linear-gradient(");
528 result.append(m_angle->cssText());
530 if (m_firstX && m_firstY) {
531 result.append(m_firstX->cssText());
533 result.append(m_firstY->cssText());
534 } else if (m_firstX || m_firstY) {
536 result.append(m_firstX->cssText());
539 result.append(m_firstY->cssText());
543 for (unsigned i = 0; i < m_stops.size(); i++) {
544 const CSSGradientColorStop& stop = m_stops[i];
545 result.appendLiteral(", ");
546 result.append(stop.m_color->cssText());
547 if (stop.m_position) {
549 result.append(stop.m_position->cssText());
554 result.appendLiteral("repeating-linear-gradient(");
556 result.appendLiteral("linear-gradient(");
558 bool wroteSomething = false;
560 if (m_angle && m_angle->computeDegrees() != 180) {
561 result.append(m_angle->cssText());
562 wroteSomething = true;
563 } else if ((m_firstX || m_firstY) && !(!m_firstX && m_firstY && m_firstY->getValueID() == CSSValueBottom)) {
564 result.appendLiteral("to ");
565 if (m_firstX && m_firstY) {
566 result.append(m_firstX->cssText());
568 result.append(m_firstY->cssText());
570 result.append(m_firstX->cssText());
572 result.append(m_firstY->cssText());
573 wroteSomething = true;
577 result.appendLiteral(", ");
579 for (unsigned i = 0; i < m_stops.size(); i++) {
580 const CSSGradientColorStop& stop = m_stops[i];
582 result.appendLiteral(", ");
583 result.append(stop.m_color->cssText());
584 if (stop.m_position) {
586 result.append(stop.m_position->cssText());
593 return result.toString();
596 // Compute the endpoints so that a gradient of the given angle covers a box of the given size.
597 static void endPointsFromAngle(float angleDeg, const IntSize& size, FloatPoint& firstPoint, FloatPoint& secondPoint, CSSGradientType type)
599 // Prefixed gradients use "polar coordinate" angles, rather than "bearing" angles.
600 if (type == CSSPrefixedLinearGradient)
601 angleDeg = 90 - angleDeg;
603 angleDeg = fmodf(angleDeg, 360);
608 firstPoint.set(0, size.height());
609 secondPoint.set(0, 0);
613 if (angleDeg == 90) {
614 firstPoint.set(0, 0);
615 secondPoint.set(size.width(), 0);
619 if (angleDeg == 180) {
620 firstPoint.set(0, 0);
621 secondPoint.set(0, size.height());
625 if (angleDeg == 270) {
626 firstPoint.set(size.width(), 0);
627 secondPoint.set(0, 0);
631 // angleDeg is a "bearing angle" (0deg = N, 90deg = E),
632 // but tan expects 0deg = E, 90deg = N.
633 float slope = tan(deg2rad(90 - angleDeg));
635 // We find the endpoint by computing the intersection of the line formed by the slope,
636 // and a line perpendicular to it that intersects the corner.
637 float perpendicularSlope = -1 / slope;
639 // Compute start corner relative to center, in Cartesian space (+y = up).
640 float halfHeight = size.height() / 2;
641 float halfWidth = size.width() / 2;
642 FloatPoint endCorner;
644 endCorner.set(halfWidth, halfHeight);
645 else if (angleDeg < 180)
646 endCorner.set(halfWidth, -halfHeight);
647 else if (angleDeg < 270)
648 endCorner.set(-halfWidth, -halfHeight);
650 endCorner.set(-halfWidth, halfHeight);
652 // Compute c (of y = mx + c) using the corner point.
653 float c = endCorner.y() - perpendicularSlope * endCorner.x();
654 float endX = c / (slope - perpendicularSlope);
655 float endY = perpendicularSlope * endX + c;
657 // We computed the end point, so set the second point,
658 // taking into account the moved origin and the fact that we're in drawing space (+y = down).
659 secondPoint.set(halfWidth + endX, halfHeight - endY);
660 // Reflect around the center for the start point.
661 firstPoint.set(halfWidth - endX, halfHeight + endY);
664 PassRefPtr<Gradient> CSSLinearGradientValue::createGradient(const CSSToLengthConversionData& conversionData, const IntSize& size)
666 ASSERT(!size.isEmpty());
668 FloatPoint firstPoint;
669 FloatPoint secondPoint;
671 float angle = m_angle->getFloatValue(CSSPrimitiveValue::CSS_DEG);
672 endPointsFromAngle(angle, size, firstPoint, secondPoint, m_gradientType);
674 switch (m_gradientType) {
675 case CSSDeprecatedLinearGradient:
676 firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
677 if (m_secondX || m_secondY)
678 secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), conversionData, size);
681 secondPoint.setX(size.width() - firstPoint.x());
683 secondPoint.setY(size.height() - firstPoint.y());
686 case CSSPrefixedLinearGradient:
687 firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
689 secondPoint.setX(size.width() - firstPoint.x());
691 secondPoint.setY(size.height() - firstPoint.y());
693 case CSSLinearGradient:
694 if (m_firstX && m_firstY) {
695 // "Magic" corners, so the 50% line touches two corners.
696 float rise = size.width();
697 float run = size.height();
698 if (m_firstX && m_firstX->getValueID() == CSSValueLeft)
700 if (m_firstY && m_firstY->getValueID() == CSSValueBottom)
702 // Compute angle, and flip it back to "bearing angle" degrees.
703 float angle = 90 - rad2deg(atan2(rise, run));
704 endPointsFromAngle(angle, size, firstPoint, secondPoint, m_gradientType);
705 } else if (m_firstX || m_firstY) {
706 secondPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
708 firstPoint.setX(size.width() - secondPoint.x());
710 firstPoint.setY(size.height() - secondPoint.y());
712 secondPoint.setY(size.height());
715 ASSERT_NOT_REACHED();
720 RefPtr<Gradient> gradient = Gradient::create(firstPoint, secondPoint);
722 gradient->setDrawsInPMColorSpace(true);
724 // Now add the stops.
725 addStops(gradient.get(), conversionData, 1);
727 return gradient.release();
730 bool CSSLinearGradientValue::equals(const CSSLinearGradientValue& other) const
732 if (m_gradientType == CSSDeprecatedLinearGradient)
733 return other.m_gradientType == m_gradientType
734 && compareCSSValuePtr(m_firstX, other.m_firstX)
735 && compareCSSValuePtr(m_firstY, other.m_firstY)
736 && compareCSSValuePtr(m_secondX, other.m_secondX)
737 && compareCSSValuePtr(m_secondY, other.m_secondY)
738 && m_stops == other.m_stops;
740 if (m_repeating != other.m_repeating)
744 return compareCSSValuePtr(m_angle, other.m_angle) && m_stops == other.m_stops;
749 bool equalXandY = false;
750 if (m_firstX && m_firstY)
751 equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && compareCSSValuePtr(m_firstY, other.m_firstY);
753 equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && !other.m_firstY;
755 equalXandY = compareCSSValuePtr(m_firstY, other.m_firstY) && !other.m_firstX;
757 equalXandY = !other.m_firstX && !other.m_firstY;
759 return equalXandY && m_stops == other.m_stops;
762 void CSSLinearGradientValue::traceAfterDispatch(Visitor* visitor)
764 visitor->trace(m_angle);
765 CSSGradientValue::traceAfterDispatch(visitor);
768 String CSSRadialGradientValue::customCSSText() const
770 StringBuilder result;
772 if (m_gradientType == CSSDeprecatedRadialGradient) {
773 result.appendLiteral("-webkit-gradient(radial, ");
774 result.append(m_firstX->cssText());
776 result.append(m_firstY->cssText());
777 result.appendLiteral(", ");
778 result.append(m_firstRadius->cssText());
779 result.appendLiteral(", ");
780 result.append(m_secondX->cssText());
782 result.append(m_secondY->cssText());
783 result.appendLiteral(", ");
784 result.append(m_secondRadius->cssText());
787 for (unsigned i = 0; i < m_stops.size(); i++) {
788 const CSSGradientColorStop& stop = m_stops[i];
789 result.appendLiteral(", ");
790 if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0) {
791 result.appendLiteral("from(");
792 result.append(stop.m_color->cssText());
794 } else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1) {
795 result.appendLiteral("to(");
796 result.append(stop.m_color->cssText());
799 result.appendLiteral("color-stop(");
800 result.appendNumber(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER));
801 result.appendLiteral(", ");
802 result.append(stop.m_color->cssText());
806 } else if (m_gradientType == CSSPrefixedRadialGradient) {
808 result.appendLiteral("-webkit-repeating-radial-gradient(");
810 result.appendLiteral("-webkit-radial-gradient(");
812 if (m_firstX && m_firstY) {
813 result.append(m_firstX->cssText());
815 result.append(m_firstY->cssText());
817 result.append(m_firstX->cssText());
819 result.append(m_firstY->cssText());
821 result.appendLiteral("center");
823 if (m_shape || m_sizingBehavior) {
824 result.appendLiteral(", ");
826 result.append(m_shape->cssText());
829 result.appendLiteral("ellipse ");
831 if (m_sizingBehavior)
832 result.append(m_sizingBehavior->cssText());
834 result.appendLiteral("cover");
836 } else if (m_endHorizontalSize && m_endVerticalSize) {
837 result.appendLiteral(", ");
838 result.append(m_endHorizontalSize->cssText());
840 result.append(m_endVerticalSize->cssText());
843 for (unsigned i = 0; i < m_stops.size(); i++) {
844 const CSSGradientColorStop& stop = m_stops[i];
845 result.appendLiteral(", ");
846 result.append(stop.m_color->cssText());
847 if (stop.m_position) {
849 result.append(stop.m_position->cssText());
854 result.appendLiteral("repeating-radial-gradient(");
856 result.appendLiteral("radial-gradient(");
858 bool wroteSomething = false;
860 // The only ambiguous case that needs an explicit shape to be provided
861 // is when a sizing keyword is used (or all sizing is omitted).
862 if (m_shape && m_shape->getValueID() != CSSValueEllipse && (m_sizingBehavior || (!m_sizingBehavior && !m_endHorizontalSize))) {
863 result.appendLiteral("circle");
864 wroteSomething = true;
867 if (m_sizingBehavior && m_sizingBehavior->getValueID() != CSSValueFarthestCorner) {
870 result.append(m_sizingBehavior->cssText());
871 wroteSomething = true;
872 } else if (m_endHorizontalSize) {
875 result.append(m_endHorizontalSize->cssText());
876 if (m_endVerticalSize) {
878 result.append(m_endVerticalSize->cssText());
880 wroteSomething = true;
883 if (m_firstX || m_firstY) {
886 result.appendLiteral("at ");
887 if (m_firstX && m_firstY) {
888 result.append(m_firstX->cssText());
890 result.append(m_firstY->cssText());
892 result.append(m_firstX->cssText());
894 result.append(m_firstY->cssText());
895 wroteSomething = true;
899 result.appendLiteral(", ");
901 for (unsigned i = 0; i < m_stops.size(); i++) {
902 const CSSGradientColorStop& stop = m_stops[i];
904 result.appendLiteral(", ");
905 result.append(stop.m_color->cssText());
906 if (stop.m_position) {
908 result.append(stop.m_position->cssText());
915 return result.toString();
918 float CSSRadialGradientValue::resolveRadius(CSSPrimitiveValue* radius, const CSSToLengthConversionData& conversionData, float* widthOrHeight)
921 if (radius->isNumber()) // Can the radius be a percentage?
922 result = radius->getFloatValue() * conversionData.zoom();
923 else if (widthOrHeight && radius->isPercentage())
924 result = *widthOrHeight * radius->getFloatValue() / 100;
926 result = radius->computeLength<float>(conversionData);
931 static float distanceToClosestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner)
934 float topLeftDistance = FloatSize(p - topLeft).diagonalLength();
936 FloatPoint topRight(size.width(), 0);
937 float topRightDistance = FloatSize(p - topRight).diagonalLength();
939 FloatPoint bottomLeft(0, size.height());
940 float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength();
942 FloatPoint bottomRight(size.width(), size.height());
943 float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength();
946 float minDistance = topLeftDistance;
947 if (topRightDistance < minDistance) {
948 minDistance = topRightDistance;
952 if (bottomLeftDistance < minDistance) {
953 minDistance = bottomLeftDistance;
957 if (bottomRightDistance < minDistance) {
958 minDistance = bottomRightDistance;
959 corner = bottomRight;
964 static float distanceToFarthestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner)
967 float topLeftDistance = FloatSize(p - topLeft).diagonalLength();
969 FloatPoint topRight(size.width(), 0);
970 float topRightDistance = FloatSize(p - topRight).diagonalLength();
972 FloatPoint bottomLeft(0, size.height());
973 float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength();
975 FloatPoint bottomRight(size.width(), size.height());
976 float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength();
979 float maxDistance = topLeftDistance;
980 if (topRightDistance > maxDistance) {
981 maxDistance = topRightDistance;
985 if (bottomLeftDistance > maxDistance) {
986 maxDistance = bottomLeftDistance;
990 if (bottomRightDistance > maxDistance) {
991 maxDistance = bottomRightDistance;
992 corner = bottomRight;
997 // Compute horizontal radius of ellipse with center at 0,0 which passes through p, and has
998 // width/height given by aspectRatio.
999 static inline float horizontalEllipseRadius(const FloatSize& p, float aspectRatio)
1001 // x^2/a^2 + y^2/b^2 = 1
1002 // a/b = aspectRatio, b = a/aspectRatio
1003 // a = sqrt(x^2 + y^2/(1/r^2))
1004 return sqrtf(p.width() * p.width() + (p.height() * p.height()) / (1 / (aspectRatio * aspectRatio)));
1007 // FIXME: share code with the linear version
1008 PassRefPtr<Gradient> CSSRadialGradientValue::createGradient(const CSSToLengthConversionData& conversionData, const IntSize& size)
1010 ASSERT(!size.isEmpty());
1012 FloatPoint firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
1014 firstPoint.setX(size.width() / 2);
1016 firstPoint.setY(size.height() / 2);
1018 FloatPoint secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), conversionData, size);
1020 secondPoint.setX(size.width() / 2);
1022 secondPoint.setY(size.height() / 2);
1024 float firstRadius = 0;
1026 firstRadius = resolveRadius(m_firstRadius.get(), conversionData);
1028 float secondRadius = 0;
1029 float aspectRatio = 1; // width / height.
1031 secondRadius = resolveRadius(m_secondRadius.get(), conversionData);
1032 else if (m_endHorizontalSize) {
1033 float width = size.width();
1034 float height = size.height();
1035 secondRadius = resolveRadius(m_endHorizontalSize.get(), conversionData, &width);
1036 if (m_endVerticalSize)
1037 aspectRatio = secondRadius / resolveRadius(m_endVerticalSize.get(), conversionData, &height);
1041 enum GradientShape { Circle, Ellipse };
1042 GradientShape shape = Ellipse;
1043 if ((m_shape && m_shape->getValueID() == CSSValueCircle)
1044 || (!m_shape && !m_sizingBehavior && m_endHorizontalSize && !m_endVerticalSize))
1047 enum GradientFill { ClosestSide, ClosestCorner, FarthestSide, FarthestCorner };
1048 GradientFill fill = FarthestCorner;
1050 switch (m_sizingBehavior ? m_sizingBehavior->getValueID() : 0) {
1051 case CSSValueContain:
1052 case CSSValueClosestSide:
1055 case CSSValueClosestCorner:
1056 fill = ClosestCorner;
1058 case CSSValueFarthestSide:
1059 fill = FarthestSide;
1062 case CSSValueFarthestCorner:
1063 fill = FarthestCorner;
1069 // Now compute the end radii based on the second point, shape and fill.
1074 float xDist = std::min(secondPoint.x(), size.width() - secondPoint.x());
1075 float yDist = std::min(secondPoint.y(), size.height() - secondPoint.y());
1076 if (shape == Circle) {
1077 float smaller = std::min(xDist, yDist);
1081 secondRadius = xDist;
1082 aspectRatio = xDist / yDist;
1085 case FarthestSide: {
1086 float xDist = std::max(secondPoint.x(), size.width() - secondPoint.x());
1087 float yDist = std::max(secondPoint.y(), size.height() - secondPoint.y());
1088 if (shape == Circle) {
1089 float larger = std::max(xDist, yDist);
1093 secondRadius = xDist;
1094 aspectRatio = xDist / yDist;
1097 case ClosestCorner: {
1099 float distance = distanceToClosestCorner(secondPoint, size, corner);
1100 if (shape == Circle)
1101 secondRadius = distance;
1103 // If <shape> is ellipse, the gradient-shape has the same ratio of width to height
1104 // that it would if closest-side or farthest-side were specified, as appropriate.
1105 float xDist = std::min(secondPoint.x(), size.width() - secondPoint.x());
1106 float yDist = std::min(secondPoint.y(), size.height() - secondPoint.y());
1108 secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist);
1109 aspectRatio = xDist / yDist;
1114 case FarthestCorner: {
1116 float distance = distanceToFarthestCorner(secondPoint, size, corner);
1117 if (shape == Circle)
1118 secondRadius = distance;
1120 // If <shape> is ellipse, the gradient-shape has the same ratio of width to height
1121 // that it would if closest-side or farthest-side were specified, as appropriate.
1122 float xDist = std::max(secondPoint.x(), size.width() - secondPoint.x());
1123 float yDist = std::max(secondPoint.y(), size.height() - secondPoint.y());
1125 secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist);
1126 aspectRatio = xDist / yDist;
1133 RefPtr<Gradient> gradient = Gradient::create(firstPoint, firstRadius, secondPoint, secondRadius, aspectRatio);
1135 gradient->setDrawsInPMColorSpace(true);
1137 // addStops() only uses maxExtent for repeating gradients.
1138 float maxExtent = 0;
1141 maxExtent = distanceToFarthestCorner(secondPoint, size, corner);
1144 // Now add the stops.
1145 addStops(gradient.get(), conversionData, maxExtent);
1147 return gradient.release();
1150 bool CSSRadialGradientValue::equals(const CSSRadialGradientValue& other) const
1152 if (m_gradientType == CSSDeprecatedRadialGradient)
1153 return other.m_gradientType == m_gradientType
1154 && compareCSSValuePtr(m_firstX, other.m_firstX)
1155 && compareCSSValuePtr(m_firstY, other.m_firstY)
1156 && compareCSSValuePtr(m_secondX, other.m_secondX)
1157 && compareCSSValuePtr(m_secondY, other.m_secondY)
1158 && compareCSSValuePtr(m_firstRadius, other.m_firstRadius)
1159 && compareCSSValuePtr(m_secondRadius, other.m_secondRadius)
1160 && m_stops == other.m_stops;
1162 if (m_repeating != other.m_repeating)
1165 bool equalXandY = false;
1166 if (m_firstX && m_firstY)
1167 equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && compareCSSValuePtr(m_firstY, other.m_firstY);
1169 equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && !other.m_firstY;
1171 equalXandY = compareCSSValuePtr(m_firstY, other.m_firstY) && !other.m_firstX;
1173 equalXandY = !other.m_firstX && !other.m_firstY;
1178 bool equalShape = true;
1179 bool equalSizingBehavior = true;
1180 bool equalHorizontalAndVerticalSize = true;
1183 equalShape = compareCSSValuePtr(m_shape, other.m_shape);
1184 else if (m_sizingBehavior)
1185 equalSizingBehavior = compareCSSValuePtr(m_sizingBehavior, other.m_sizingBehavior);
1186 else if (m_endHorizontalSize && m_endVerticalSize)
1187 equalHorizontalAndVerticalSize = compareCSSValuePtr(m_endHorizontalSize, other.m_endHorizontalSize) && compareCSSValuePtr(m_endVerticalSize, other.m_endVerticalSize);
1189 equalShape = !other.m_shape;
1190 equalSizingBehavior = !other.m_sizingBehavior;
1191 equalHorizontalAndVerticalSize = !other.m_endHorizontalSize && !other.m_endVerticalSize;
1193 return equalShape && equalSizingBehavior && equalHorizontalAndVerticalSize && m_stops == other.m_stops;
1196 void CSSRadialGradientValue::traceAfterDispatch(Visitor* visitor)
1198 visitor->trace(m_firstRadius);
1199 visitor->trace(m_secondRadius);
1200 visitor->trace(m_shape);
1201 visitor->trace(m_sizingBehavior);
1202 visitor->trace(m_endHorizontalSize);
1203 visitor->trace(m_endVerticalSize);
1204 CSSGradientValue::traceAfterDispatch(visitor);
1207 } // namespace blink