CSSGradientValue.cpp revision 65f03d4f644ce73618e5f4f50dd694b26f55ae12
1/*
2 * Copyright (C) 2008 Apple Inc.  All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
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.
12 *
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.
24 */
25
26#include "config.h"
27#include "CSSGradientValue.h"
28
29#include "CSSValueKeywords.h"
30#include "CSSStyleSelector.h"
31#include "GeneratedImage.h"
32#include "Gradient.h"
33#include "Image.h"
34#include "IntSize.h"
35#include "IntSizeHash.h"
36#include "NodeRenderStyle.h"
37#include "PlatformString.h"
38#include "RenderObject.h"
39
40using namespace std;
41
42namespace WebCore {
43
44Image* CSSGradientValue::image(RenderObject* renderer, const IntSize& size)
45{
46    if (!m_clients.contains(renderer))
47        return 0;
48
49    // Need to look up our size.  Create a string of width*height to use as a hash key.
50    // FIXME: hashing based only on size is not sufficient. Color stops may use context-sensitive units (like em)
51    // that should force the color stop positions to be recomputed.
52    Image* result = getImage(renderer, size);
53    if (result)
54        return result;
55
56    if (size.isEmpty())
57        return 0;
58
59    // We need to create an image.
60    RefPtr<Image> newImage = GeneratedImage::create(createGradient(renderer, size), size);
61    result = newImage.get();
62    putImage(size, newImage.release());
63
64    return result;
65}
66
67// Should only ever be called for deprecated gradients.
68static inline bool compareStops(const CSSGradientColorStop& a, const CSSGradientColorStop& b)
69{
70    double aVal = a.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER);
71    double bVal = b.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER);
72
73    return aVal < bVal;
74}
75
76void CSSGradientValue::sortStopsIfNeeded()
77{
78    ASSERT(m_deprecatedType);
79    if (!m_stopsSorted) {
80        if (m_stops.size())
81            std::stable_sort(m_stops.begin(), m_stops.end(), compareStops);
82        m_stopsSorted = true;
83    }
84}
85
86static inline int blend(int from, int to, float progress)
87{
88    return int(from + (to - from) * progress);
89}
90
91static inline Color blend(const Color& from, const Color& to, float progress)
92{
93    // FIXME: when we interpolate gradients using premultiplied colors, this should also do premultiplication.
94    return Color(blend(from.red(), to.red(), progress),
95        blend(from.green(), to.green(), progress),
96        blend(from.blue(), to.blue(), progress),
97        blend(from.alpha(), to.alpha(), progress));
98}
99
100struct GradientStop {
101    Color color;
102    float offset;
103    bool specified;
104
105    GradientStop()
106        : offset(0)
107        , specified(false)
108    { }
109};
110
111void CSSGradientValue::addStops(Gradient* gradient, RenderObject* renderer, RenderStyle* rootStyle, float maxLengthForRepeat)
112{
113    RenderStyle* style = renderer->style();
114
115    if (m_deprecatedType) {
116        sortStopsIfNeeded();
117
118        // We have to resolve colors.
119        for (unsigned i = 0; i < m_stops.size(); i++) {
120            const CSSGradientColorStop& stop = m_stops[i];
121            Color color = renderer->document()->styleSelector()->getColorFromPrimitiveValue(stop.m_color.get());
122
123            float offset;
124            if (stop.m_position->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE)
125                offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
126            else
127                offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_NUMBER);
128
129            gradient->addColorStop(offset, color);
130        }
131
132        // The back end already sorted the stops.
133        gradient->setStopsSorted(true);
134        return;
135    }
136
137    size_t numStops = m_stops.size();
138
139    Vector<GradientStop> stops(numStops);
140
141    float gradientLength = 0;
142    bool computedGradientLength = false;
143
144    FloatPoint gradientStart = gradient->p0();
145    FloatPoint gradientEnd;
146    if (isLinearGradient())
147        gradientEnd = gradient->p1();
148    else if (isRadialGradient())
149        gradientEnd = gradientStart + FloatSize(gradient->endRadius(), 0);
150
151    for (size_t i = 0; i < numStops; ++i) {
152        const CSSGradientColorStop& stop = m_stops[i];
153
154        stops[i].color = renderer->document()->styleSelector()->getColorFromPrimitiveValue(stop.m_color.get());
155
156        if (stop.m_position) {
157            int type = stop.m_position->primitiveType();
158            if (type == CSSPrimitiveValue::CSS_PERCENTAGE)
159                stops[i].offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
160            else if (CSSPrimitiveValue::isUnitTypeLength(type)) {
161                float length = stop.m_position->computeLengthFloat(style, rootStyle, style->effectiveZoom());
162                if (!computedGradientLength) {
163                    FloatSize gradientSize(gradientStart - gradientEnd);
164                    gradientLength = gradientSize.diagonalLength();
165                }
166                stops[i].offset = (gradientLength > 0) ? length / gradientLength : 0;
167            } else {
168                ASSERT_NOT_REACHED();
169                stops[i].offset = 0;
170            }
171            stops[i].specified = true;
172        } else {
173            // If the first color-stop does not have a position, its position defaults to 0%.
174            // If the last color-stop does not have a position, its position defaults to 100%.
175            if (!i) {
176                stops[i].offset = 0;
177                stops[i].specified = true;
178            } else if (numStops > 1 && i == numStops - 1) {
179                stops[i].offset = 1;
180                stops[i].specified = true;
181            }
182        }
183
184        // If a color-stop has a position that is less than the specified position of any
185        // color-stop before it in the list, its position is changed to be equal to the
186        // largest specified position of any color-stop before it.
187        if (stops[i].specified && i > 0) {
188            size_t prevSpecifiedIndex;
189            for (prevSpecifiedIndex = i - 1; prevSpecifiedIndex; --prevSpecifiedIndex) {
190                if (stops[prevSpecifiedIndex].specified)
191                    break;
192            }
193
194            if (stops[i].offset < stops[prevSpecifiedIndex].offset)
195                stops[i].offset = stops[prevSpecifiedIndex].offset;
196        }
197    }
198
199    ASSERT(stops[0].specified && stops[numStops - 1].specified);
200
201    // If any color-stop still does not have a position, then, for each run of adjacent
202    // color-stops without positions, set their positions so that they are evenly spaced
203    // between the preceding and following color-stops with positions.
204    if (numStops > 2) {
205        size_t unspecifiedRunStart = 0;
206        bool inUnspecifiedRun = false;
207
208        for (size_t i = 0; i < numStops; ++i) {
209            if (!stops[i].specified && !inUnspecifiedRun) {
210                unspecifiedRunStart = i;
211                inUnspecifiedRun = true;
212            } else if (stops[i].specified && inUnspecifiedRun) {
213                size_t unspecifiedRunEnd = i;
214
215                if (unspecifiedRunStart < unspecifiedRunEnd) {
216                    float lastSpecifiedOffset = stops[unspecifiedRunStart - 1].offset;
217                    float nextSpecifiedOffset = stops[unspecifiedRunEnd].offset;
218                    float delta = (nextSpecifiedOffset - lastSpecifiedOffset) / (unspecifiedRunEnd - unspecifiedRunStart + 1);
219
220                    for (size_t j = unspecifiedRunStart; j < unspecifiedRunEnd; ++j)
221                        stops[j].offset = lastSpecifiedOffset + (j - unspecifiedRunStart + 1) * delta;
222                }
223
224                inUnspecifiedRun = false;
225            }
226        }
227    }
228
229    // If the gradient is repeating, repeat the color stops.
230    // We can't just push this logic down into the platform-specific Gradient code,
231    // because we have to know the extent of the gradient, and possible move the end points.
232    if (m_repeating && numStops > 1) {
233        float maxExtent = 1;
234
235        // Radial gradients may need to extend further than the endpoints, because they have
236        // to repeat out to the corners of the box.
237        if (isRadialGradient()) {
238            if (!computedGradientLength) {
239                FloatSize gradientSize(gradientStart - gradientEnd);
240                gradientLength = gradientSize.diagonalLength();
241            }
242
243            if (maxLengthForRepeat > gradientLength)
244                maxExtent = maxLengthForRepeat / gradientLength;
245        }
246
247        size_t originalNumStops = numStops;
248        size_t originalFirstStopIndex = 0;
249
250        // Work backwards from the first, adding stops until we get one before 0.
251        float firstOffset = stops[0].offset;
252        if (firstOffset > 0) {
253            float currOffset = firstOffset;
254            size_t srcStopOrdinal = originalNumStops - 1;
255
256            while (true) {
257                GradientStop newStop = stops[originalFirstStopIndex + srcStopOrdinal];
258                newStop.offset = currOffset;
259                stops.prepend(newStop);
260                ++originalFirstStopIndex;
261                if (currOffset < 0)
262                    break;
263
264                if (srcStopOrdinal)
265                    currOffset -= stops[originalFirstStopIndex + srcStopOrdinal].offset - stops[originalFirstStopIndex + srcStopOrdinal - 1].offset;
266                srcStopOrdinal = (srcStopOrdinal + originalNumStops - 1) % originalNumStops;
267            }
268        }
269
270        // Work forwards from the end, adding stops until we get one after 1.
271        float lastOffset = stops[stops.size() - 1].offset;
272        if (lastOffset < maxExtent) {
273            float currOffset = lastOffset;
274            size_t srcStopOrdinal = 0;
275
276            while (true) {
277                GradientStop newStop = stops[srcStopOrdinal];
278                newStop.offset = currOffset;
279                stops.append(newStop);
280                if (currOffset > maxExtent)
281                    break;
282                if (srcStopOrdinal < originalNumStops - 1)
283                    currOffset += stops[originalFirstStopIndex + srcStopOrdinal + 1].offset - stops[originalFirstStopIndex + srcStopOrdinal].offset;
284                srcStopOrdinal = (srcStopOrdinal + 1) % originalNumStops;
285            }
286        }
287    }
288
289    numStops = stops.size();
290
291    // If the gradient goes outside the 0-1 range, normalize it by moving the endpoints, and adjusting the stops.
292    if (numStops > 1 && (stops[0].offset < 0 || stops[numStops - 1].offset > 1)) {
293        if (isLinearGradient()) {
294            float firstOffset = stops[0].offset;
295            float lastOffset = stops[numStops - 1].offset;
296            float scale = lastOffset - firstOffset;
297
298            for (size_t i = 0; i < numStops; ++i)
299                stops[i].offset = (stops[i].offset - firstOffset) / scale;
300
301            FloatPoint p0 = gradient->p0();
302            FloatPoint p1 = gradient->p1();
303            gradient->setP0(FloatPoint(p0.x() + firstOffset * (p1.x() - p0.x()), p0.y() + firstOffset * (p1.y() - p0.y())));
304            gradient->setP1(FloatPoint(p1.x() + (lastOffset - 1) * (p1.x() - p0.x()), p1.y() + (lastOffset - 1) * (p1.y() - p0.y())));
305        } else if (isRadialGradient()) {
306            // Rather than scaling the points < 0, we truncate them, so only scale according to the largest point.
307            float firstOffset = 0;
308            float lastOffset = stops[numStops - 1].offset;
309            float scale = lastOffset - firstOffset;
310
311            // Reset points below 0 to the first visible color.
312            size_t firstZeroOrGreaterIndex = numStops;
313            for (size_t i = 0; i < numStops; ++i) {
314                if (stops[i].offset >= 0) {
315                    firstZeroOrGreaterIndex = i;
316                    break;
317                }
318            }
319
320            if (firstZeroOrGreaterIndex > 0) {
321                if (firstZeroOrGreaterIndex < numStops && stops[firstZeroOrGreaterIndex].offset > 0) {
322                    float prevOffset = stops[firstZeroOrGreaterIndex - 1].offset;
323                    float nextOffset = stops[firstZeroOrGreaterIndex].offset;
324
325                    float interStopProportion = -prevOffset / (nextOffset - prevOffset);
326                    Color blendedColor = blend(stops[firstZeroOrGreaterIndex - 1].color, stops[firstZeroOrGreaterIndex].color, interStopProportion);
327
328                    // Clamp the positions to 0 and set the color.
329                    for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i) {
330                        stops[i].offset = 0;
331                        stops[i].color = blendedColor;
332                    }
333                } else {
334                    // All stops are below 0; just clamp them.
335                    for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i)
336                        stops[i].offset = 0;
337                }
338            }
339
340            for (size_t i = 0; i < numStops; ++i)
341                stops[i].offset /= scale;
342
343            gradient->setStartRadius(gradient->startRadius() * scale);
344            gradient->setEndRadius(gradient->endRadius() * scale);
345        }
346    }
347
348    for (unsigned i = 0; i < numStops; i++)
349        gradient->addColorStop(stops[i].offset, stops[i].color);
350
351    gradient->setStopsSorted(true);
352}
353
354static float positionFromValue(CSSPrimitiveValue* value, RenderStyle* style, RenderStyle* rootStyle, const IntSize& size, bool isHorizontal)
355{
356    float zoomFactor = style->effectiveZoom();
357
358    switch (value->primitiveType()) {
359    case CSSPrimitiveValue::CSS_NUMBER:
360        return value->getFloatValue() * zoomFactor;
361
362    case CSSPrimitiveValue::CSS_PERCENTAGE:
363        return value->getFloatValue() / 100.f * (isHorizontal ? size.width() : size.height());
364
365    case CSSPrimitiveValue::CSS_IDENT:
366        switch (value->getIdent()) {
367            case CSSValueTop:
368                ASSERT(!isHorizontal);
369                return 0;
370            case CSSValueLeft:
371                ASSERT(isHorizontal);
372                return 0;
373            case CSSValueBottom:
374                ASSERT(!isHorizontal);
375                return size.height();
376            case CSSValueRight:
377                ASSERT(isHorizontal);
378                return size.width();
379        }
380
381    default:
382        return value->computeLengthFloat(style, rootStyle, zoomFactor);
383    }
384}
385
386FloatPoint CSSGradientValue::computeEndPoint(CSSPrimitiveValue* first, CSSPrimitiveValue* second, RenderStyle* style, RenderStyle* rootStyle, const IntSize& size)
387{
388    FloatPoint result;
389
390    if (first)
391        result.setX(positionFromValue(first, style, rootStyle, size, true));
392
393    if (second)
394        result.setY(positionFromValue(second, style, rootStyle, size, false));
395
396    return result;
397}
398
399String CSSLinearGradientValue::cssText() const
400{
401    String result;
402    if (m_deprecatedType) {
403        result = "-webkit-gradient(linear, ";
404        result += m_firstX->cssText() + " ";
405        result += m_firstY->cssText() + ", ";
406        result += m_secondX->cssText() + " ";
407        result += m_secondY->cssText();
408
409        for (unsigned i = 0; i < m_stops.size(); i++) {
410            const CSSGradientColorStop& stop = m_stops[i];
411            result += ", ";
412            if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0)
413                result += "from(" + stop.m_color->cssText() + ")";
414            else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1)
415                result += "to(" + stop.m_color->cssText() + ")";
416            else
417                result += "color-stop(" + String::number(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER)) + ", " + stop.m_color->cssText() + ")";
418        }
419    } else {
420        result = m_repeating ? "-webkit-repeating-linear-gradient(" : "-webkit-linear-gradient(";
421        if (m_angle)
422            result += m_angle->cssText();
423        else {
424            if (m_firstX && m_firstY)
425                result += m_firstX->cssText() + " " + m_firstY->cssText();
426            else if (m_firstX || m_firstY) {
427                if (m_firstX)
428                    result += m_firstX->cssText();
429
430                if (m_firstY)
431                    result += m_firstY->cssText();
432            }
433        }
434
435        for (unsigned i = 0; i < m_stops.size(); i++) {
436            const CSSGradientColorStop& stop = m_stops[i];
437            result += ", ";
438            result += stop.m_color->cssText();
439            if (stop.m_position)
440                result += " " + stop.m_position->cssText();
441        }
442    }
443
444    result += ")";
445    return result;
446}
447
448// Compute the endpoints so that a gradient of the given angle covers a box of the given size.
449static void endPointsFromAngle(float angleDeg, const IntSize& size, FloatPoint& firstPoint, FloatPoint& secondPoint)
450{
451    angleDeg = fmodf(angleDeg, 360);
452    if (angleDeg < 0)
453        angleDeg += 360;
454
455    if (!angleDeg) {
456        firstPoint.set(0, 0);
457        secondPoint.set(size.width(), 0);
458        return;
459    }
460
461    if (angleDeg == 90) {
462        firstPoint.set(0, size.height());
463        secondPoint.set(0, 0);
464        return;
465    }
466
467    if (angleDeg == 180) {
468        firstPoint.set(size.width(), 0);
469        secondPoint.set(0, 0);
470        return;
471    }
472
473    float slope = tan(deg2rad(angleDeg));
474
475    // We find the endpoint by computing the intersection of the line formed by the slope,
476    // and a line perpendicular to it that intersects the corner.
477    float perpendicularSlope = -1 / slope;
478
479    // Compute start corner relative to center.
480    float halfHeight = size.height() / 2;
481    float halfWidth = size.width() / 2;
482    FloatPoint endCorner;
483    if (angleDeg < 90)
484        endCorner.set(halfWidth, halfHeight);
485    else if (angleDeg < 180)
486        endCorner.set(-halfWidth, halfHeight);
487    else if (angleDeg < 270)
488        endCorner.set(-halfWidth, -halfHeight);
489    else
490        endCorner.set(halfWidth, -halfHeight);
491
492    // Compute c (of y = mx + c) using the corner point.
493    float c = endCorner.y() - perpendicularSlope * endCorner.x();
494    float endX = c / (slope - perpendicularSlope);
495    float endY = perpendicularSlope * endX + c;
496
497    // We computed the end point, so set the second point, flipping the Y to account for angles going anticlockwise.
498    secondPoint.set(halfWidth + endX, size.height() - (halfHeight + endY));
499    // Reflect around the center for the start point.
500    firstPoint.set(size.width() - secondPoint.x(), size.height() - secondPoint.y());
501}
502
503PassRefPtr<Gradient> CSSLinearGradientValue::createGradient(RenderObject* renderer, const IntSize& size)
504{
505    ASSERT(!size.isEmpty());
506
507    RenderStyle* rootStyle = renderer->document()->documentElement()->renderStyle();
508
509    FloatPoint firstPoint;
510    FloatPoint secondPoint;
511    if (m_angle) {
512        float angle = m_angle->getFloatValue(CSSPrimitiveValue::CSS_DEG);
513        endPointsFromAngle(angle, size, firstPoint, secondPoint);
514    } else {
515        firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), renderer->style(), rootStyle, size);
516
517        if (m_secondX || m_secondY)
518            secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), renderer->style(), rootStyle, size);
519        else {
520            if (m_firstX)
521                secondPoint.setX(size.width() - firstPoint.x());
522            if (m_firstY)
523                secondPoint.setY(size.height() - firstPoint.y());
524        }
525    }
526
527    RefPtr<Gradient> gradient = Gradient::create(firstPoint, secondPoint);
528
529    // Now add the stops.
530    addStops(gradient.get(), renderer, rootStyle, 1);
531
532    return gradient.release();
533}
534
535String CSSRadialGradientValue::cssText() const
536{
537    String result;
538
539    if (m_deprecatedType) {
540        result = "-webkit-gradient(radial, ";
541
542        result += m_firstX->cssText() + " ";
543        result += m_firstY->cssText() + ", ";
544        result += m_firstRadius->cssText() + ", ";
545        result += m_secondX->cssText() + " ";
546        result += m_secondY->cssText();
547        result += ", ";
548        result += m_secondRadius->cssText();
549
550        // FIXME: share?
551        for (unsigned i = 0; i < m_stops.size(); i++) {
552            const CSSGradientColorStop& stop = m_stops[i];
553            result += ", ";
554            if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0)
555                result += "from(" + stop.m_color->cssText() + ")";
556            else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1)
557                result += "to(" + stop.m_color->cssText() + ")";
558            else
559                result += "color-stop(" + String::number(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER)) + ", " + stop.m_color->cssText() + ")";
560        }
561    } else {
562
563        result = m_repeating ? "-webkit-repeating-radial-gradient(" : "-webkit-radial-gradient(";
564        if (m_firstX && m_firstY) {
565            result += m_firstX->cssText() + " " + m_firstY->cssText();
566        } else if (m_firstX)
567            result += m_firstX->cssText();
568         else if (m_firstY)
569            result += m_firstY->cssText();
570        else
571            result += "center";
572
573
574        if (m_shape || m_sizingBehavior) {
575            result += ", ";
576            if (m_shape)
577                result += m_shape->cssText() + " ";
578            else
579                result += "ellipse ";
580
581            if (m_sizingBehavior)
582                result += m_sizingBehavior->cssText();
583            else
584                result += "cover";
585
586        } else if (m_endHorizontalSize && m_endVerticalSize) {
587            result += ", ";
588            result += m_endHorizontalSize->cssText() + " " + m_endVerticalSize->cssText();
589        }
590
591        for (unsigned i = 0; i < m_stops.size(); i++) {
592            const CSSGradientColorStop& stop = m_stops[i];
593            result += ", ";
594            result += stop.m_color->cssText();
595            if (stop.m_position)
596                result += " " + stop.m_position->cssText();
597        }
598    }
599
600    result += ")";
601    return result;
602}
603
604float CSSRadialGradientValue::resolveRadius(CSSPrimitiveValue* radius, RenderStyle* style, RenderStyle* rootStyle, float* widthOrHeight)
605{
606    float zoomFactor = style->effectiveZoom();
607
608    float result = 0;
609    if (radius->primitiveType() == CSSPrimitiveValue::CSS_NUMBER)  // Can the radius be a percentage?
610        result = radius->getFloatValue() * zoomFactor;
611    else if (widthOrHeight && radius->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE)
612        result = *widthOrHeight * radius->getFloatValue() / 100;
613    else
614        result = radius->computeLengthFloat(style, rootStyle, zoomFactor);
615
616    return result;
617}
618
619static float distanceToClosestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner)
620{
621    FloatPoint topLeft;
622    float topLeftDistance = FloatSize(p - topLeft).diagonalLength();
623
624    FloatPoint topRight(size.width(), 0);
625    float topRightDistance = FloatSize(p - topRight).diagonalLength();
626
627    FloatPoint bottomLeft(0, size.height());
628    float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength();
629
630    FloatPoint bottomRight(size.width(), size.height());
631    float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength();
632
633    corner = topLeft;
634    float minDistance = topLeftDistance;
635    if (topRightDistance < minDistance) {
636        minDistance = topRightDistance;
637        corner = topRight;
638    }
639
640    if (bottomLeftDistance < minDistance) {
641        minDistance = bottomLeftDistance;
642        corner = bottomLeft;
643    }
644
645    if (bottomRightDistance < minDistance) {
646        minDistance = bottomRightDistance;
647        corner = bottomRight;
648    }
649    return minDistance;
650}
651
652static float distanceToFarthestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner)
653{
654    FloatPoint topLeft;
655    float topLeftDistance = FloatSize(p - topLeft).diagonalLength();
656
657    FloatPoint topRight(size.width(), 0);
658    float topRightDistance = FloatSize(p - topRight).diagonalLength();
659
660    FloatPoint bottomLeft(0, size.height());
661    float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength();
662
663    FloatPoint bottomRight(size.width(), size.height());
664    float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength();
665
666    corner = topLeft;
667    float maxDistance = topLeftDistance;
668    if (topRightDistance > maxDistance) {
669        maxDistance = topRightDistance;
670        corner = topRight;
671    }
672
673    if (bottomLeftDistance > maxDistance) {
674        maxDistance = bottomLeftDistance;
675        corner = bottomLeft;
676    }
677
678    if (bottomRightDistance > maxDistance) {
679        maxDistance = bottomRightDistance;
680        corner = bottomRight;
681    }
682    return maxDistance;
683}
684
685// Compute horizontal radius of ellipse with center at 0,0 which passes through p, and has
686// width/height given by aspectRatio.
687static inline float horizontalEllipseRadius(const FloatSize& p, float aspectRatio)
688{
689    // x^2/a^2 + y^2/b^2 = 1
690    // a/b = aspectRatio, b = a/aspectRatio
691    // a = sqrt(x^2 + y^2/(1/r^2))
692    return sqrtf(p.width() * p.width() + (p.height() * p.height()) / (1 / (aspectRatio * aspectRatio)));
693}
694
695// FIXME: share code with the linear version
696PassRefPtr<Gradient> CSSRadialGradientValue::createGradient(RenderObject* renderer, const IntSize& size)
697{
698    ASSERT(!size.isEmpty());
699
700    RenderStyle* rootStyle = renderer->document()->documentElement()->renderStyle();
701
702    FloatPoint firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), renderer->style(), rootStyle, size);
703    if (!m_firstX)
704        firstPoint.setX(size.width() / 2);
705    if (!m_firstY)
706        firstPoint.setY(size.height() / 2);
707
708    FloatPoint secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), renderer->style(), rootStyle, size);
709    if (!m_secondX)
710        secondPoint.setX(size.width() / 2);
711    if (!m_secondY)
712        secondPoint.setY(size.height() / 2);
713
714    float firstRadius = 0;
715    if (m_firstRadius)
716        firstRadius = resolveRadius(m_firstRadius.get(), renderer->style(), rootStyle);
717
718    float secondRadius = 0;
719    float aspectRatio = 1; // width / height.
720    if (m_secondRadius)
721        secondRadius = resolveRadius(m_secondRadius.get(), renderer->style(), rootStyle);
722    else if (m_endHorizontalSize || m_endVerticalSize) {
723        float width = size.width();
724        float height = size.height();
725        secondRadius = resolveRadius(m_endHorizontalSize.get(), renderer->style(), rootStyle, &width);
726        aspectRatio = secondRadius / resolveRadius(m_endVerticalSize.get(), renderer->style(), rootStyle, &height);
727    } else {
728        enum GradientShape { Circle, Ellipse };
729        GradientShape shape = Ellipse;
730        if (m_shape && m_shape->primitiveType() == CSSPrimitiveValue::CSS_IDENT && m_shape->getIdent() == CSSValueCircle)
731            shape = Circle;
732
733        enum GradientFill { ClosestSide, ClosestCorner, FarthestSide, FarthestCorner };
734        GradientFill fill = FarthestCorner;
735
736        if (m_sizingBehavior && m_sizingBehavior->primitiveType() == CSSPrimitiveValue::CSS_IDENT) {
737            switch (m_sizingBehavior->getIdent()) {
738            case CSSValueContain:
739            case CSSValueClosestSide:
740                fill = ClosestSide;
741                break;
742            case CSSValueClosestCorner:
743                fill = ClosestCorner;
744                break;
745            case CSSValueFarthestSide:
746                fill = FarthestSide;
747                break;
748            case CSSValueCover:
749            case CSSValueFarthestCorner:
750                fill = FarthestCorner;
751                break;
752            }
753        }
754
755        // Now compute the end radii based on the second point, shape and fill.
756
757        // Horizontal
758        switch (fill) {
759        case ClosestSide: {
760            float xDist = min(secondPoint.x(), size.width() - secondPoint.x());
761            float yDist = min(secondPoint.y(), size.height() - secondPoint.y());
762            if (shape == Circle) {
763                float smaller = min(xDist, yDist);
764                xDist = smaller;
765                yDist = smaller;
766            }
767            secondRadius = xDist;
768            aspectRatio = xDist / yDist;
769            break;
770        }
771        case FarthestSide: {
772            float xDist = max(secondPoint.x(), size.width() - secondPoint.x());
773            float yDist = max(secondPoint.y(), size.height() - secondPoint.y());
774            if (shape == Circle) {
775                float larger = max(xDist, yDist);
776                xDist = larger;
777                yDist = larger;
778            }
779            secondRadius = xDist;
780            aspectRatio = xDist / yDist;
781            break;
782        }
783        case ClosestCorner: {
784            FloatPoint corner;
785            float distance = distanceToClosestCorner(secondPoint, size, corner);
786            if (shape == Circle)
787                secondRadius = distance;
788            else {
789                // If <shape> is ellipse, the gradient-shape has the same ratio of width to height
790                // that it would if closest-side or farthest-side were specified, as appropriate.
791                float xDist = min(secondPoint.x(), size.width() - secondPoint.x());
792                float yDist = min(secondPoint.y(), size.height() - secondPoint.y());
793
794                secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist);
795                aspectRatio = xDist / yDist;
796            }
797            break;
798        }
799
800        case FarthestCorner: {
801            FloatPoint corner;
802            float distance = distanceToFarthestCorner(secondPoint, size, corner);
803            if (shape == Circle)
804                secondRadius = distance;
805            else {
806                // If <shape> is ellipse, the gradient-shape has the same ratio of width to height
807                // that it would if closest-side or farthest-side were specified, as appropriate.
808                float xDist = max(secondPoint.x(), size.width() - secondPoint.x());
809                float yDist = max(secondPoint.y(), size.height() - secondPoint.y());
810
811                secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist);
812                aspectRatio = xDist / yDist;
813            }
814            break;
815        }
816        }
817    }
818
819    RefPtr<Gradient> gradient = Gradient::create(firstPoint, firstRadius, secondPoint, secondRadius, aspectRatio);
820
821    // addStops() only uses maxExtent for repeating gradients.
822    float maxExtent = 0;
823    if (m_repeating) {
824        FloatPoint corner;
825        maxExtent = distanceToFarthestCorner(secondPoint, size, corner);
826    }
827
828    // Now add the stops.
829    addStops(gradient.get(), renderer, rootStyle, maxExtent);
830
831    return gradient.release();
832}
833
834} // namespace WebCore
835