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 "platform/graphics/skia/SkiaUtils.h"
41 #include "wtf/text/StringBuilder.h"
42 #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;
105 ALLOW_ONLY_INLINE_ALLOCATION();
117 PassRefPtrWillBeRawPtr<CSSGradientValue> CSSGradientValue::gradientWithStylesResolved(const TextLinkColors& textLinkColors, Color currentColor)
119 bool derived = false;
120 for (auto& stop : m_stops) {
121 if (!stop.isHint() && stop.m_color->colorIsDerivedFromElement()) {
122 stop.m_colorIsDerivedFromElement = true;
128 RefPtrWillBeRawPtr<CSSGradientValue> result = nullptr;
131 else if (isLinearGradientValue())
132 result = toCSSLinearGradientValue(this)->clone();
133 else if (isRadialGradientValue())
134 result = toCSSRadialGradientValue(this)->clone();
136 ASSERT_NOT_REACHED();
140 for (auto& stop : result->m_stops) {
142 stop.m_resolvedColor = textLinkColors.colorFromPrimitiveValue(stop.m_color.get(), currentColor);
145 return result.release();
148 static void replaceColorHintsWithColorStops(WillBeHeapVector<GradientStop>& stops, const WillBeHeapVector<CSSGradientColorStop, 2>& cssGradientStops)
150 // This algorithm will replace each color interpolation hint with 9 regular
151 // color stops. The color values for the new color stops will be calculated
152 // using the color weighting formula defined in the spec. The new color
153 // stops will be positioned in such a way that all the pixels between the two
154 // user defined color stops have color values close to the interpolation curve.
155 // If the hint is closer to the left color stop, add 2 stops to the left and
156 // 6 to the right, else add 6 stops to the left and 2 to the right.
157 // The color stops on the side with more space start midway because
158 // the curve approximates a line in that region.
159 // Using this aproximation, it is possible to discern the color steps when
160 // the gradient is large. If this becomes an issue, we can consider improving
161 // the algorithm, or adding support for color interpolation hints to skia shaders.
165 // The first and the last color stops cannot be color hints.
166 for (size_t i = 1; i < cssGradientStops.size() - 1; ++i) {
167 if (!cssGradientStops[i].isHint())
170 // The current index of the stops vector.
171 size_t x = i + indexOffset;
174 // offsetLeft offset offsetRight
175 // |-------------------|---------------------------------|
176 // leftDist rightDist
178 float offsetLeft = stops[x - 1].offset;
179 float offsetRight = stops[x + 1].offset;
180 float offset = stops[x].offset;
181 float leftDist = offset - offsetLeft;
182 float rightDist = offsetRight - offset;
183 float totalDist = offsetRight - offsetLeft;
185 Color leftColor = stops[x - 1].color;
186 Color rightColor = stops[x + 1].color;
188 ASSERT(offsetLeft <= offset && offset <= offsetRight);
190 if (WebCoreFloatNearlyEqual(leftDist, rightDist)) {
196 if (WebCoreFloatNearlyEqual(leftDist, .0f)) {
197 stops[x].color = rightColor;
201 if (WebCoreFloatNearlyEqual(rightDist, .0f)) {
202 stops[x].color = leftColor;
206 GradientStop newStops[9];
207 // Position the new color stops.
208 if (leftDist > rightDist) {
209 for (size_t y = 0; y < 7; ++y)
210 newStops[y].offset = offsetLeft + leftDist * (7 + y) / 13;
211 newStops[7].offset = offset + rightDist / 3;
212 newStops[8].offset = offset + rightDist * 2 / 3;
214 newStops[0].offset = offsetLeft + leftDist / 3;
215 newStops[1].offset = offsetLeft + leftDist * 2 / 3;
216 for (size_t y = 0; y < 7; ++y)
217 newStops[y + 2].offset = offset + rightDist * y / 13;
220 // calculate colors for the new color hints.
221 // The color weighting for the new color stops will be pointRelativeOffset^(ln(0.5)/ln(hintRelativeOffset)).
222 float hintRelativeOffset = leftDist / totalDist;
223 for (size_t y = 0; y < 9; ++y) {
224 float pointRelativeOffset = (newStops[y].offset - offsetLeft) / totalDist;
225 float weighting = powf(pointRelativeOffset, logf(.5f) / logf(hintRelativeOffset));
226 newStops[y].color = blend(leftColor, rightColor, weighting);
229 // Replace the color hint with the new color stops.
231 stops.insert(x, newStops, 9);
236 void CSSGradientValue::addStops(Gradient* gradient, const CSSToLengthConversionData& conversionData, float maxLengthForRepeat)
238 if (m_gradientType == CSSDeprecatedLinearGradient || m_gradientType == CSSDeprecatedRadialGradient) {
241 for (unsigned i = 0; i < m_stops.size(); i++) {
242 const CSSGradientColorStop& stop = m_stops[i];
245 if (stop.m_position->isPercentage())
246 offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
248 offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_NUMBER);
250 gradient->addColorStop(offset, stop.m_resolvedColor);
256 size_t numStops = m_stops.size();
258 WillBeHeapVector<GradientStop> stops(numStops);
260 float gradientLength = 0;
261 bool computedGradientLength = false;
263 bool hasHints = false;
265 FloatPoint gradientStart = gradient->p0();
266 FloatPoint gradientEnd;
267 if (isLinearGradientValue())
268 gradientEnd = gradient->p1();
269 else if (isRadialGradientValue())
270 gradientEnd = gradientStart + FloatSize(gradient->endRadius(), 0);
272 for (size_t i = 0; i < numStops; ++i) {
273 const CSSGradientColorStop& stop = m_stops[i];
278 stops[i].color = stop.m_resolvedColor;
280 if (stop.m_position) {
281 if (stop.m_position->isPercentage())
282 stops[i].offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
283 else if (stop.m_position->isLength() || stop.m_position->isCalculatedPercentageWithLength()) {
284 if (!computedGradientLength) {
285 FloatSize gradientSize(gradientStart - gradientEnd);
286 gradientLength = gradientSize.diagonalLength();
289 if (stop.m_position->isLength())
290 length = stop.m_position->computeLength<float>(conversionData);
292 length = stop.m_position->cssCalcValue()->toCalcValue(conversionData)->evaluate(gradientLength);
293 stops[i].offset = (gradientLength > 0) ? length / gradientLength : 0;
295 ASSERT_NOT_REACHED();
298 stops[i].specified = true;
300 // If the first color-stop does not have a position, its position defaults to 0%.
301 // If the last color-stop does not have a position, its position defaults to 100%.
304 stops[i].specified = true;
305 } else if (numStops > 1 && i == numStops - 1) {
307 stops[i].specified = true;
311 // If a color-stop has a position that is less than the specified position of any
312 // color-stop before it in the list, its position is changed to be equal to the
313 // largest specified position of any color-stop before it.
314 if (stops[i].specified && i > 0) {
315 size_t prevSpecifiedIndex;
316 for (prevSpecifiedIndex = i - 1; prevSpecifiedIndex; --prevSpecifiedIndex) {
317 if (stops[prevSpecifiedIndex].specified)
321 if (stops[i].offset < stops[prevSpecifiedIndex].offset)
322 stops[i].offset = stops[prevSpecifiedIndex].offset;
326 ASSERT(stops[0].specified && stops[numStops - 1].specified);
328 // If any color-stop still does not have a position, then, for each run of adjacent
329 // color-stops without positions, set their positions so that they are evenly spaced
330 // between the preceding and following color-stops with positions.
332 size_t unspecifiedRunStart = 0;
333 bool inUnspecifiedRun = false;
335 for (size_t i = 0; i < numStops; ++i) {
336 if (!stops[i].specified && !inUnspecifiedRun) {
337 unspecifiedRunStart = i;
338 inUnspecifiedRun = true;
339 } else if (stops[i].specified && inUnspecifiedRun) {
340 size_t unspecifiedRunEnd = i;
342 if (unspecifiedRunStart < unspecifiedRunEnd) {
343 float lastSpecifiedOffset = stops[unspecifiedRunStart - 1].offset;
344 float nextSpecifiedOffset = stops[unspecifiedRunEnd].offset;
345 float delta = (nextSpecifiedOffset - lastSpecifiedOffset) / (unspecifiedRunEnd - unspecifiedRunStart + 1);
347 for (size_t j = unspecifiedRunStart; j < unspecifiedRunEnd; ++j)
348 stops[j].offset = lastSpecifiedOffset + (j - unspecifiedRunStart + 1) * delta;
351 inUnspecifiedRun = false;
356 ASSERT(stops.size() == m_stops.size());
358 replaceColorHintsWithColorStops(stops, m_stops);
359 numStops = stops.size();
362 // If the gradient is repeating, repeat the color stops.
363 // We can't just push this logic down into the platform-specific Gradient code,
364 // because we have to know the extent of the gradient, and possible move the end points.
365 if (m_repeating && numStops > 1) {
366 // If the difference in the positions of the first and last color-stops is 0,
367 // the gradient defines a solid-color image with the color of the last color-stop in the rule.
368 float gradientRange = stops[numStops - 1].offset - stops[0].offset;
369 if (!gradientRange) {
370 stops.first().offset = 0;
371 stops.first().color = stops.last().color;
376 // Radial gradients may need to extend further than the endpoints, because they have
377 // to repeat out to the corners of the box.
378 if (isRadialGradientValue()) {
379 if (!computedGradientLength) {
380 FloatSize gradientSize(gradientStart - gradientEnd);
381 gradientLength = gradientSize.diagonalLength();
384 if (maxLengthForRepeat > gradientLength)
385 maxExtent = gradientLength > 0 ? maxLengthForRepeat / gradientLength : 0;
388 size_t originalNumStops = numStops;
389 size_t originalFirstStopIndex = 0;
391 // Work backwards from the first, adding stops until we get one before 0.
392 float firstOffset = stops[0].offset;
393 if (firstOffset > 0) {
394 float currOffset = firstOffset;
395 size_t srcStopOrdinal = originalNumStops - 1;
398 GradientStop newStop = stops[originalFirstStopIndex + srcStopOrdinal];
399 newStop.offset = currOffset;
400 stops.prepend(newStop);
401 ++originalFirstStopIndex;
406 currOffset -= stops[originalFirstStopIndex + srcStopOrdinal].offset - stops[originalFirstStopIndex + srcStopOrdinal - 1].offset;
407 srcStopOrdinal = (srcStopOrdinal + originalNumStops - 1) % originalNumStops;
411 // Work forwards from the end, adding stops until we get one after 1.
412 float lastOffset = stops[stops.size() - 1].offset;
413 if (lastOffset < maxExtent) {
414 float currOffset = lastOffset;
415 size_t srcStopOrdinal = 0;
418 size_t srcStopIndex = originalFirstStopIndex + srcStopOrdinal;
419 GradientStop newStop = stops[srcStopIndex];
420 newStop.offset = currOffset;
421 stops.append(newStop);
422 if (currOffset > maxExtent)
424 if (srcStopOrdinal < originalNumStops - 1)
425 currOffset += stops[srcStopIndex + 1].offset - stops[srcStopIndex].offset;
426 srcStopOrdinal = (srcStopOrdinal + 1) % originalNumStops;
432 numStops = stops.size();
434 // If the gradient goes outside the 0-1 range, normalize it by moving the endpoints, and adjusting the stops.
435 if (numStops > 1 && (stops[0].offset < 0 || stops[numStops - 1].offset > 1)) {
436 if (isLinearGradientValue()) {
437 float firstOffset = stops[0].offset;
438 float lastOffset = stops[numStops - 1].offset;
439 float scale = lastOffset - firstOffset;
441 for (size_t i = 0; i < numStops; ++i)
442 stops[i].offset = (stops[i].offset - firstOffset) / scale;
444 FloatPoint p0 = gradient->p0();
445 FloatPoint p1 = gradient->p1();
446 gradient->setP0(FloatPoint(p0.x() + firstOffset * (p1.x() - p0.x()), p0.y() + firstOffset * (p1.y() - p0.y())));
447 gradient->setP1(FloatPoint(p1.x() + (lastOffset - 1) * (p1.x() - p0.x()), p1.y() + (lastOffset - 1) * (p1.y() - p0.y())));
448 } else if (isRadialGradientValue()) {
449 // Rather than scaling the points < 0, we truncate them, so only scale according to the largest point.
450 float firstOffset = 0;
451 float lastOffset = stops[numStops - 1].offset;
452 float scale = lastOffset - firstOffset;
454 // Reset points below 0 to the first visible color.
455 size_t firstZeroOrGreaterIndex = numStops;
456 for (size_t i = 0; i < numStops; ++i) {
457 if (stops[i].offset >= 0) {
458 firstZeroOrGreaterIndex = i;
463 if (firstZeroOrGreaterIndex > 0) {
464 if (firstZeroOrGreaterIndex < numStops && stops[firstZeroOrGreaterIndex].offset > 0) {
465 float prevOffset = stops[firstZeroOrGreaterIndex - 1].offset;
466 float nextOffset = stops[firstZeroOrGreaterIndex].offset;
468 float interStopProportion = -prevOffset / (nextOffset - prevOffset);
469 // FIXME: when we interpolate gradients using premultiplied colors, this should do premultiplication.
470 Color blendedColor = blend(stops[firstZeroOrGreaterIndex - 1].color, stops[firstZeroOrGreaterIndex].color, interStopProportion);
472 // Clamp the positions to 0 and set the color.
473 for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i) {
475 stops[i].color = blendedColor;
478 // All stops are below 0; just clamp them.
479 for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i)
484 for (size_t i = 0; i < numStops; ++i)
485 stops[i].offset /= scale;
487 gradient->setStartRadius(gradient->startRadius() * scale);
488 gradient->setEndRadius(gradient->endRadius() * scale);
492 for (unsigned i = 0; i < numStops; i++)
493 gradient->addColorStop(stops[i].offset, stops[i].color);
496 static float positionFromValue(CSSPrimitiveValue* value, const CSSToLengthConversionData& conversionData, const IntSize& size, bool isHorizontal)
500 int edgeDistance = isHorizontal ? size.width() : size.height();
502 // In this case the center of the gradient is given relative to an edge in the form of:
503 // [ top | bottom | right | left ] [ <percentage> | <length> ].
504 if (Pair* pair = value->getPairValue()) {
505 CSSValueID originID = pair->first()->getValueID();
506 value = pair->second();
508 if (originID == CSSValueRight || originID == CSSValueBottom) {
509 // For right/bottom, the offset is relative to the far edge.
510 origin = edgeDistance;
515 if (value->isNumber())
516 return origin + sign * value->getFloatValue() * conversionData.zoom();
518 if (value->isPercentage())
519 return origin + sign * value->getFloatValue() / 100.f * edgeDistance;
521 if (value->isCalculatedPercentageWithLength())
522 return origin + sign * value->cssCalcValue()->toCalcValue(conversionData)->evaluate(edgeDistance);
524 switch (value->getValueID()) {
526 ASSERT(!isHorizontal);
529 ASSERT(isHorizontal);
532 ASSERT(!isHorizontal);
533 return size.height();
535 ASSERT(isHorizontal);
541 return origin + sign * value->computeLength<float>(conversionData);
544 FloatPoint CSSGradientValue::computeEndPoint(CSSPrimitiveValue* horizontal, CSSPrimitiveValue* vertical, const CSSToLengthConversionData& conversionData, const IntSize& size)
549 result.setX(positionFromValue(horizontal, conversionData, size, true));
552 result.setY(positionFromValue(vertical, conversionData, size, false));
557 bool CSSGradientValue::isCacheable() const
559 for (size_t i = 0; i < m_stops.size(); ++i) {
560 const CSSGradientColorStop& stop = m_stops[i];
562 if (stop.m_colorIsDerivedFromElement)
565 if (!stop.m_position)
568 if (stop.m_position->isFontRelativeLength())
575 bool CSSGradientValue::knownToBeOpaque(const RenderObject*) const
577 for (auto& stop : m_stops) {
578 if (!stop.isHint() && stop.m_resolvedColor.hasAlpha())
584 void CSSGradientValue::traceAfterDispatch(Visitor* visitor)
586 visitor->trace(m_firstX);
587 visitor->trace(m_firstY);
588 visitor->trace(m_secondX);
589 visitor->trace(m_secondY);
590 visitor->trace(m_stops);
591 CSSImageGeneratorValue::traceAfterDispatch(visitor);
594 String CSSLinearGradientValue::customCSSText() const
596 StringBuilder result;
597 if (m_gradientType == CSSDeprecatedLinearGradient) {
598 result.appendLiteral("-webkit-gradient(linear, ");
599 result.append(m_firstX->cssText());
601 result.append(m_firstY->cssText());
602 result.appendLiteral(", ");
603 result.append(m_secondX->cssText());
605 result.append(m_secondY->cssText());
607 for (unsigned i = 0; i < m_stops.size(); i++) {
608 const CSSGradientColorStop& stop = m_stops[i];
609 result.appendLiteral(", ");
610 if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0) {
611 result.appendLiteral("from(");
612 result.append(stop.m_color->cssText());
614 } else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1) {
615 result.appendLiteral("to(");
616 result.append(stop.m_color->cssText());
619 result.appendLiteral("color-stop(");
620 result.appendNumber(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER));
621 result.appendLiteral(", ");
622 result.append(stop.m_color->cssText());
626 } else if (m_gradientType == CSSPrefixedLinearGradient) {
628 result.appendLiteral("-webkit-repeating-linear-gradient(");
630 result.appendLiteral("-webkit-linear-gradient(");
633 result.append(m_angle->cssText());
635 if (m_firstX && m_firstY) {
636 result.append(m_firstX->cssText());
638 result.append(m_firstY->cssText());
639 } else if (m_firstX || m_firstY) {
641 result.append(m_firstX->cssText());
644 result.append(m_firstY->cssText());
648 for (unsigned i = 0; i < m_stops.size(); i++) {
649 const CSSGradientColorStop& stop = m_stops[i];
650 result.appendLiteral(", ");
651 result.append(stop.m_color->cssText());
652 if (stop.m_position) {
654 result.append(stop.m_position->cssText());
659 result.appendLiteral("repeating-linear-gradient(");
661 result.appendLiteral("linear-gradient(");
663 bool wroteSomething = false;
665 if (m_angle && m_angle->computeDegrees() != 180) {
666 result.append(m_angle->cssText());
667 wroteSomething = true;
668 } else if ((m_firstX || m_firstY) && !(!m_firstX && m_firstY && m_firstY->getValueID() == CSSValueBottom)) {
669 result.appendLiteral("to ");
670 if (m_firstX && m_firstY) {
671 result.append(m_firstX->cssText());
673 result.append(m_firstY->cssText());
675 result.append(m_firstX->cssText());
677 result.append(m_firstY->cssText());
678 wroteSomething = true;
682 result.appendLiteral(", ");
684 for (unsigned i = 0; i < m_stops.size(); i++) {
685 const CSSGradientColorStop& stop = m_stops[i];
687 result.appendLiteral(", ");
689 result.append(stop.m_color->cssText());
690 if (stop.m_color && stop.m_position)
693 result.append(stop.m_position->cssText());
699 return result.toString();
702 // Compute the endpoints so that a gradient of the given angle covers a box of the given size.
703 static void endPointsFromAngle(float angleDeg, const IntSize& size, FloatPoint& firstPoint, FloatPoint& secondPoint, CSSGradientType type)
705 // Prefixed gradients use "polar coordinate" angles, rather than "bearing" angles.
706 if (type == CSSPrefixedLinearGradient)
707 angleDeg = 90 - angleDeg;
709 angleDeg = fmodf(angleDeg, 360);
714 firstPoint.set(0, size.height());
715 secondPoint.set(0, 0);
719 if (angleDeg == 90) {
720 firstPoint.set(0, 0);
721 secondPoint.set(size.width(), 0);
725 if (angleDeg == 180) {
726 firstPoint.set(0, 0);
727 secondPoint.set(0, size.height());
731 if (angleDeg == 270) {
732 firstPoint.set(size.width(), 0);
733 secondPoint.set(0, 0);
737 // angleDeg is a "bearing angle" (0deg = N, 90deg = E),
738 // but tan expects 0deg = E, 90deg = N.
739 float slope = tan(deg2rad(90 - angleDeg));
741 // We find the endpoint by computing the intersection of the line formed by the slope,
742 // and a line perpendicular to it that intersects the corner.
743 float perpendicularSlope = -1 / slope;
745 // Compute start corner relative to center, in Cartesian space (+y = up).
746 float halfHeight = size.height() / 2;
747 float halfWidth = size.width() / 2;
748 FloatPoint endCorner;
750 endCorner.set(halfWidth, halfHeight);
751 else if (angleDeg < 180)
752 endCorner.set(halfWidth, -halfHeight);
753 else if (angleDeg < 270)
754 endCorner.set(-halfWidth, -halfHeight);
756 endCorner.set(-halfWidth, halfHeight);
758 // Compute c (of y = mx + c) using the corner point.
759 float c = endCorner.y() - perpendicularSlope * endCorner.x();
760 float endX = c / (slope - perpendicularSlope);
761 float endY = perpendicularSlope * endX + c;
763 // We computed the end point, so set the second point,
764 // taking into account the moved origin and the fact that we're in drawing space (+y = down).
765 secondPoint.set(halfWidth + endX, halfHeight - endY);
766 // Reflect around the center for the start point.
767 firstPoint.set(halfWidth - endX, halfHeight + endY);
770 PassRefPtr<Gradient> CSSLinearGradientValue::createGradient(const CSSToLengthConversionData& conversionData, const IntSize& size)
772 ASSERT(!size.isEmpty());
774 FloatPoint firstPoint;
775 FloatPoint secondPoint;
777 float angle = m_angle->getFloatValue(CSSPrimitiveValue::CSS_DEG);
778 endPointsFromAngle(angle, size, firstPoint, secondPoint, m_gradientType);
780 switch (m_gradientType) {
781 case CSSDeprecatedLinearGradient:
782 firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
783 if (m_secondX || m_secondY)
784 secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), conversionData, size);
787 secondPoint.setX(size.width() - firstPoint.x());
789 secondPoint.setY(size.height() - firstPoint.y());
792 case CSSPrefixedLinearGradient:
793 firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
795 secondPoint.setX(size.width() - firstPoint.x());
797 secondPoint.setY(size.height() - firstPoint.y());
799 case CSSLinearGradient:
800 if (m_firstX && m_firstY) {
801 // "Magic" corners, so the 50% line touches two corners.
802 float rise = size.width();
803 float run = size.height();
804 if (m_firstX && m_firstX->getValueID() == CSSValueLeft)
806 if (m_firstY && m_firstY->getValueID() == CSSValueBottom)
808 // Compute angle, and flip it back to "bearing angle" degrees.
809 float angle = 90 - rad2deg(atan2(rise, run));
810 endPointsFromAngle(angle, size, firstPoint, secondPoint, m_gradientType);
811 } else if (m_firstX || m_firstY) {
812 secondPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
814 firstPoint.setX(size.width() - secondPoint.x());
816 firstPoint.setY(size.height() - secondPoint.y());
818 secondPoint.setY(size.height());
821 ASSERT_NOT_REACHED();
826 RefPtr<Gradient> gradient = Gradient::create(firstPoint, secondPoint);
828 gradient->setDrawsInPMColorSpace(true);
830 // Now add the stops.
831 addStops(gradient.get(), conversionData, 1);
833 return gradient.release();
836 bool CSSLinearGradientValue::equals(const CSSLinearGradientValue& other) const
838 if (m_gradientType == CSSDeprecatedLinearGradient)
839 return other.m_gradientType == m_gradientType
840 && compareCSSValuePtr(m_firstX, other.m_firstX)
841 && compareCSSValuePtr(m_firstY, other.m_firstY)
842 && compareCSSValuePtr(m_secondX, other.m_secondX)
843 && compareCSSValuePtr(m_secondY, other.m_secondY)
844 && m_stops == other.m_stops;
846 if (m_repeating != other.m_repeating)
850 return compareCSSValuePtr(m_angle, other.m_angle) && m_stops == other.m_stops;
855 bool equalXandY = false;
856 if (m_firstX && m_firstY)
857 equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && compareCSSValuePtr(m_firstY, other.m_firstY);
859 equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && !other.m_firstY;
861 equalXandY = compareCSSValuePtr(m_firstY, other.m_firstY) && !other.m_firstX;
863 equalXandY = !other.m_firstX && !other.m_firstY;
865 return equalXandY && m_stops == other.m_stops;
868 void CSSLinearGradientValue::traceAfterDispatch(Visitor* visitor)
870 visitor->trace(m_angle);
871 CSSGradientValue::traceAfterDispatch(visitor);
874 String CSSRadialGradientValue::customCSSText() const
876 StringBuilder result;
878 if (m_gradientType == CSSDeprecatedRadialGradient) {
879 result.appendLiteral("-webkit-gradient(radial, ");
880 result.append(m_firstX->cssText());
882 result.append(m_firstY->cssText());
883 result.appendLiteral(", ");
884 result.append(m_firstRadius->cssText());
885 result.appendLiteral(", ");
886 result.append(m_secondX->cssText());
888 result.append(m_secondY->cssText());
889 result.appendLiteral(", ");
890 result.append(m_secondRadius->cssText());
893 for (unsigned i = 0; i < m_stops.size(); i++) {
894 const CSSGradientColorStop& stop = m_stops[i];
895 result.appendLiteral(", ");
896 if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0) {
897 result.appendLiteral("from(");
898 result.append(stop.m_color->cssText());
900 } else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1) {
901 result.appendLiteral("to(");
902 result.append(stop.m_color->cssText());
905 result.appendLiteral("color-stop(");
906 result.appendNumber(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER));
907 result.appendLiteral(", ");
908 result.append(stop.m_color->cssText());
912 } else if (m_gradientType == CSSPrefixedRadialGradient) {
914 result.appendLiteral("-webkit-repeating-radial-gradient(");
916 result.appendLiteral("-webkit-radial-gradient(");
918 if (m_firstX && m_firstY) {
919 result.append(m_firstX->cssText());
921 result.append(m_firstY->cssText());
923 result.append(m_firstX->cssText());
925 result.append(m_firstY->cssText());
927 result.appendLiteral("center");
929 if (m_shape || m_sizingBehavior) {
930 result.appendLiteral(", ");
932 result.append(m_shape->cssText());
935 result.appendLiteral("ellipse ");
937 if (m_sizingBehavior)
938 result.append(m_sizingBehavior->cssText());
940 result.appendLiteral("cover");
942 } else if (m_endHorizontalSize && m_endVerticalSize) {
943 result.appendLiteral(", ");
944 result.append(m_endHorizontalSize->cssText());
946 result.append(m_endVerticalSize->cssText());
949 for (unsigned i = 0; i < m_stops.size(); i++) {
950 const CSSGradientColorStop& stop = m_stops[i];
951 result.appendLiteral(", ");
952 result.append(stop.m_color->cssText());
953 if (stop.m_position) {
955 result.append(stop.m_position->cssText());
960 result.appendLiteral("repeating-radial-gradient(");
962 result.appendLiteral("radial-gradient(");
964 bool wroteSomething = false;
966 // The only ambiguous case that needs an explicit shape to be provided
967 // is when a sizing keyword is used (or all sizing is omitted).
968 if (m_shape && m_shape->getValueID() != CSSValueEllipse && (m_sizingBehavior || (!m_sizingBehavior && !m_endHorizontalSize))) {
969 result.appendLiteral("circle");
970 wroteSomething = true;
973 if (m_sizingBehavior && m_sizingBehavior->getValueID() != CSSValueFarthestCorner) {
976 result.append(m_sizingBehavior->cssText());
977 wroteSomething = true;
978 } else if (m_endHorizontalSize) {
981 result.append(m_endHorizontalSize->cssText());
982 if (m_endVerticalSize) {
984 result.append(m_endVerticalSize->cssText());
986 wroteSomething = true;
989 if (m_firstX || m_firstY) {
992 result.appendLiteral("at ");
993 if (m_firstX && m_firstY) {
994 result.append(m_firstX->cssText());
996 result.append(m_firstY->cssText());
998 result.append(m_firstX->cssText());
1000 result.append(m_firstY->cssText());
1001 wroteSomething = true;
1005 result.appendLiteral(", ");
1007 for (unsigned i = 0; i < m_stops.size(); i++) {
1008 const CSSGradientColorStop& stop = m_stops[i];
1010 result.appendLiteral(", ");
1012 result.append(stop.m_color->cssText());
1013 if (stop.m_color && stop.m_position)
1015 if (stop.m_position)
1016 result.append(stop.m_position->cssText());
1022 return result.toString();
1025 float CSSRadialGradientValue::resolveRadius(CSSPrimitiveValue* radius, const CSSToLengthConversionData& conversionData, float* widthOrHeight)
1028 if (radius->isNumber()) // Can the radius be a percentage?
1029 result = radius->getFloatValue() * conversionData.zoom();
1030 else if (widthOrHeight && radius->isPercentage())
1031 result = *widthOrHeight * radius->getFloatValue() / 100;
1033 result = radius->computeLength<float>(conversionData);
1038 static float distanceToClosestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner)
1041 float topLeftDistance = FloatSize(p - topLeft).diagonalLength();
1043 FloatPoint topRight(size.width(), 0);
1044 float topRightDistance = FloatSize(p - topRight).diagonalLength();
1046 FloatPoint bottomLeft(0, size.height());
1047 float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength();
1049 FloatPoint bottomRight(size.width(), size.height());
1050 float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength();
1053 float minDistance = topLeftDistance;
1054 if (topRightDistance < minDistance) {
1055 minDistance = topRightDistance;
1059 if (bottomLeftDistance < minDistance) {
1060 minDistance = bottomLeftDistance;
1061 corner = bottomLeft;
1064 if (bottomRightDistance < minDistance) {
1065 minDistance = bottomRightDistance;
1066 corner = bottomRight;
1071 static float distanceToFarthestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner)
1074 float topLeftDistance = FloatSize(p - topLeft).diagonalLength();
1076 FloatPoint topRight(size.width(), 0);
1077 float topRightDistance = FloatSize(p - topRight).diagonalLength();
1079 FloatPoint bottomLeft(0, size.height());
1080 float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength();
1082 FloatPoint bottomRight(size.width(), size.height());
1083 float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength();
1086 float maxDistance = topLeftDistance;
1087 if (topRightDistance > maxDistance) {
1088 maxDistance = topRightDistance;
1092 if (bottomLeftDistance > maxDistance) {
1093 maxDistance = bottomLeftDistance;
1094 corner = bottomLeft;
1097 if (bottomRightDistance > maxDistance) {
1098 maxDistance = bottomRightDistance;
1099 corner = bottomRight;
1104 // Compute horizontal radius of ellipse with center at 0,0 which passes through p, and has
1105 // width/height given by aspectRatio.
1106 static inline float horizontalEllipseRadius(const FloatSize& p, float aspectRatio)
1108 // x^2/a^2 + y^2/b^2 = 1
1109 // a/b = aspectRatio, b = a/aspectRatio
1110 // a = sqrt(x^2 + y^2/(1/r^2))
1111 return sqrtf(p.width() * p.width() + (p.height() * p.height()) / (1 / (aspectRatio * aspectRatio)));
1114 // FIXME: share code with the linear version
1115 PassRefPtr<Gradient> CSSRadialGradientValue::createGradient(const CSSToLengthConversionData& conversionData, const IntSize& size)
1117 ASSERT(!size.isEmpty());
1119 FloatPoint firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
1121 firstPoint.setX(size.width() / 2);
1123 firstPoint.setY(size.height() / 2);
1125 FloatPoint secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), conversionData, size);
1127 secondPoint.setX(size.width() / 2);
1129 secondPoint.setY(size.height() / 2);
1131 float firstRadius = 0;
1133 firstRadius = resolveRadius(m_firstRadius.get(), conversionData);
1135 float secondRadius = 0;
1136 float aspectRatio = 1; // width / height.
1138 secondRadius = resolveRadius(m_secondRadius.get(), conversionData);
1139 else if (m_endHorizontalSize) {
1140 float width = size.width();
1141 float height = size.height();
1142 secondRadius = resolveRadius(m_endHorizontalSize.get(), conversionData, &width);
1143 if (m_endVerticalSize)
1144 aspectRatio = secondRadius / resolveRadius(m_endVerticalSize.get(), conversionData, &height);
1148 enum GradientShape { Circle, Ellipse };
1149 GradientShape shape = Ellipse;
1150 if ((m_shape && m_shape->getValueID() == CSSValueCircle)
1151 || (!m_shape && !m_sizingBehavior && m_endHorizontalSize && !m_endVerticalSize))
1154 enum GradientFill { ClosestSide, ClosestCorner, FarthestSide, FarthestCorner };
1155 GradientFill fill = FarthestCorner;
1157 switch (m_sizingBehavior ? m_sizingBehavior->getValueID() : 0) {
1158 case CSSValueContain:
1159 case CSSValueClosestSide:
1162 case CSSValueClosestCorner:
1163 fill = ClosestCorner;
1165 case CSSValueFarthestSide:
1166 fill = FarthestSide;
1169 case CSSValueFarthestCorner:
1170 fill = FarthestCorner;
1176 // Now compute the end radii based on the second point, shape and fill.
1181 float xDist = std::min(secondPoint.x(), size.width() - secondPoint.x());
1182 float yDist = std::min(secondPoint.y(), size.height() - secondPoint.y());
1183 if (shape == Circle) {
1184 float smaller = std::min(xDist, yDist);
1188 secondRadius = xDist;
1189 aspectRatio = xDist / yDist;
1192 case FarthestSide: {
1193 float xDist = std::max(secondPoint.x(), size.width() - secondPoint.x());
1194 float yDist = std::max(secondPoint.y(), size.height() - secondPoint.y());
1195 if (shape == Circle) {
1196 float larger = std::max(xDist, yDist);
1200 secondRadius = xDist;
1201 aspectRatio = xDist / yDist;
1204 case ClosestCorner: {
1206 float distance = distanceToClosestCorner(secondPoint, size, corner);
1207 if (shape == Circle)
1208 secondRadius = distance;
1210 // If <shape> is ellipse, the gradient-shape has the same ratio of width to height
1211 // that it would if closest-side or farthest-side were specified, as appropriate.
1212 float xDist = std::min(secondPoint.x(), size.width() - secondPoint.x());
1213 float yDist = std::min(secondPoint.y(), size.height() - secondPoint.y());
1215 secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist);
1216 aspectRatio = xDist / yDist;
1221 case FarthestCorner: {
1223 float distance = distanceToFarthestCorner(secondPoint, size, corner);
1224 if (shape == Circle)
1225 secondRadius = distance;
1227 // If <shape> is ellipse, the gradient-shape has the same ratio of width to height
1228 // that it would if closest-side or farthest-side were specified, as appropriate.
1229 float xDist = std::max(secondPoint.x(), size.width() - secondPoint.x());
1230 float yDist = std::max(secondPoint.y(), size.height() - secondPoint.y());
1232 secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist);
1233 aspectRatio = xDist / yDist;
1240 RefPtr<Gradient> gradient = Gradient::create(firstPoint, firstRadius, secondPoint, secondRadius, aspectRatio);
1242 gradient->setDrawsInPMColorSpace(true);
1244 // addStops() only uses maxExtent for repeating gradients.
1245 float maxExtent = 0;
1248 maxExtent = distanceToFarthestCorner(secondPoint, size, corner);
1251 // Now add the stops.
1252 addStops(gradient.get(), conversionData, maxExtent);
1254 return gradient.release();
1257 bool CSSRadialGradientValue::equals(const CSSRadialGradientValue& other) const
1259 if (m_gradientType == CSSDeprecatedRadialGradient)
1260 return other.m_gradientType == m_gradientType
1261 && compareCSSValuePtr(m_firstX, other.m_firstX)
1262 && compareCSSValuePtr(m_firstY, other.m_firstY)
1263 && compareCSSValuePtr(m_secondX, other.m_secondX)
1264 && compareCSSValuePtr(m_secondY, other.m_secondY)
1265 && compareCSSValuePtr(m_firstRadius, other.m_firstRadius)
1266 && compareCSSValuePtr(m_secondRadius, other.m_secondRadius)
1267 && m_stops == other.m_stops;
1269 if (m_repeating != other.m_repeating)
1272 bool equalXandY = false;
1273 if (m_firstX && m_firstY)
1274 equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && compareCSSValuePtr(m_firstY, other.m_firstY);
1276 equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && !other.m_firstY;
1278 equalXandY = compareCSSValuePtr(m_firstY, other.m_firstY) && !other.m_firstX;
1280 equalXandY = !other.m_firstX && !other.m_firstY;
1285 bool equalShape = true;
1286 bool equalSizingBehavior = true;
1287 bool equalHorizontalAndVerticalSize = true;
1290 equalShape = compareCSSValuePtr(m_shape, other.m_shape);
1291 else if (m_sizingBehavior)
1292 equalSizingBehavior = compareCSSValuePtr(m_sizingBehavior, other.m_sizingBehavior);
1293 else if (m_endHorizontalSize && m_endVerticalSize)
1294 equalHorizontalAndVerticalSize = compareCSSValuePtr(m_endHorizontalSize, other.m_endHorizontalSize) && compareCSSValuePtr(m_endVerticalSize, other.m_endVerticalSize);
1296 equalShape = !other.m_shape;
1297 equalSizingBehavior = !other.m_sizingBehavior;
1298 equalHorizontalAndVerticalSize = !other.m_endHorizontalSize && !other.m_endVerticalSize;
1300 return equalShape && equalSizingBehavior && equalHorizontalAndVerticalSize && m_stops == other.m_stops;
1303 void CSSRadialGradientValue::traceAfterDispatch(Visitor* visitor)
1305 visitor->trace(m_firstRadius);
1306 visitor->trace(m_secondRadius);
1307 visitor->trace(m_shape);
1308 visitor->trace(m_sizingBehavior);
1309 visitor->trace(m_endHorizontalSize);
1310 visitor->trace(m_endVerticalSize);
1311 CSSGradientValue::traceAfterDispatch(visitor);
1314 } // namespace blink