• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006, 2007, 2008, 2010 Apple Inc. All rights reserved.
3  * Copyright (C) 2007 Alp Toker <alp@atoker.com>
4  * Copyright (C) 2013 Google Inc. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
16  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include "config.h"
29 #include "platform/graphics/Gradient.h"
30 
31 #include "platform/geometry/FloatRect.h"
32 #include "platform/graphics/GraphicsContext.h"
33 #include "platform/graphics/skia/SkiaUtils.h"
34 #include "third_party/skia/include/core/SkColor.h"
35 #include "third_party/skia/include/core/SkColorShader.h"
36 #include "third_party/skia/include/core/SkShader.h"
37 #include "third_party/skia/include/effects/SkGradientShader.h"
38 
39 typedef Vector<SkScalar, 8> ColorStopOffsetVector;
40 typedef Vector<SkColor, 8> ColorStopColorVector;
41 
42 namespace WebCore {
43 
Gradient(const FloatPoint & p0,const FloatPoint & p1)44 Gradient::Gradient(const FloatPoint& p0, const FloatPoint& p1)
45     : m_p0(p0)
46     , m_p1(p1)
47     , m_r0(0)
48     , m_r1(0)
49     , m_aspectRatio(1)
50     , m_radial(false)
51     , m_stopsSorted(false)
52     , m_drawInPMColorSpace(false)
53     , m_spreadMethod(SpreadMethodPad)
54 {
55 }
56 
Gradient(const FloatPoint & p0,float r0,const FloatPoint & p1,float r1,float aspectRatio)57 Gradient::Gradient(const FloatPoint& p0, float r0, const FloatPoint& p1, float r1, float aspectRatio)
58     : m_p0(p0)
59     , m_p1(p1)
60     , m_r0(r0)
61     , m_r1(r1)
62     , m_aspectRatio(aspectRatio)
63     , m_radial(true)
64     , m_stopsSorted(false)
65     , m_drawInPMColorSpace(false)
66     , m_spreadMethod(SpreadMethodPad)
67 {
68 }
69 
~Gradient()70 Gradient::~Gradient()
71 {
72 }
73 
compareStops(const Gradient::ColorStop & a,const Gradient::ColorStop & b)74 static inline bool compareStops(const Gradient::ColorStop& a, const Gradient::ColorStop& b)
75 {
76     return a.stop < b.stop;
77 }
78 
addColorStop(const Gradient::ColorStop & stop)79 void Gradient::addColorStop(const Gradient::ColorStop& stop)
80 {
81     if (m_stops.isEmpty()) {
82         m_stopsSorted = true;
83     } else {
84         m_stopsSorted = m_stopsSorted && compareStops(m_stops.last(), stop);
85     }
86 
87     m_stops.append(stop);
88     m_gradient.clear();
89 }
90 
sortStopsIfNecessary()91 void Gradient::sortStopsIfNecessary()
92 {
93     if (m_stopsSorted)
94         return;
95 
96     m_stopsSorted = true;
97 
98     if (!m_stops.size())
99         return;
100 
101     std::stable_sort(m_stops.begin(), m_stops.end(), compareStops);
102 }
103 
hasAlpha() const104 bool Gradient::hasAlpha() const
105 {
106     for (size_t i = 0; i < m_stops.size(); i++) {
107         if (m_stops[i].color.hasAlpha())
108             return true;
109     }
110 
111     return false;
112 }
113 
setSpreadMethod(GradientSpreadMethod spreadMethod)114 void Gradient::setSpreadMethod(GradientSpreadMethod spreadMethod)
115 {
116     // FIXME: Should it become necessary, allow calls to this method after m_gradient has been set.
117     ASSERT(!m_gradient);
118 
119     if (m_spreadMethod == spreadMethod)
120         return;
121 
122     m_spreadMethod = spreadMethod;
123 }
124 
setDrawsInPMColorSpace(bool drawInPMColorSpace)125 void Gradient::setDrawsInPMColorSpace(bool drawInPMColorSpace)
126 {
127     if (drawInPMColorSpace == m_drawInPMColorSpace)
128         return;
129 
130     m_drawInPMColorSpace = drawInPMColorSpace;
131     m_gradient.clear();
132 }
133 
setGradientSpaceTransform(const AffineTransform & gradientSpaceTransformation)134 void Gradient::setGradientSpaceTransform(const AffineTransform& gradientSpaceTransformation)
135 {
136     if (m_gradientSpaceTransformation == gradientSpaceTransformation)
137         return;
138 
139     m_gradientSpaceTransformation = gradientSpaceTransformation;
140     m_gradient.clear();
141 }
142 
143 // Determine the total number of stops needed, including pseudo-stops at the
144 // ends as necessary.
totalStopsNeeded(const Gradient::ColorStop * stopData,size_t count)145 static size_t totalStopsNeeded(const Gradient::ColorStop* stopData, size_t count)
146 {
147     // N.B.: The tests in this function should kept in sync with the ones in
148     // fillStops(), or badness happens.
149     const Gradient::ColorStop* stop = stopData;
150     size_t countUsed = count;
151     if (count < 1 || stop->stop > 0.0)
152         countUsed++;
153     stop += count - 1;
154     if (count < 1 || stop->stop < 1.0)
155         countUsed++;
156     return countUsed;
157 }
158 
159 // FIXME: This would be more at home as Color::operator SkColor.
makeSkColor(const Color & c)160 static inline SkColor makeSkColor(const Color& c)
161 {
162     return SkColorSetARGB(c.alpha(), c.red(), c.green(), c.blue());
163 }
164 
165 // Collect sorted stop position and color information into the pos and colors
166 // buffers, ensuring stops at both 0.0 and 1.0. The buffers must be large
167 // enough to hold information for all stops, including the new endpoints if
168 // stops at 0.0 and 1.0 aren't already included.
fillStops(const Gradient::ColorStop * stopData,size_t count,ColorStopOffsetVector & pos,ColorStopColorVector & colors)169 static void fillStops(const Gradient::ColorStop* stopData,
170     size_t count, ColorStopOffsetVector& pos, ColorStopColorVector& colors)
171 {
172     const Gradient::ColorStop* stop = stopData;
173     size_t start = 0;
174     if (count < 1) {
175         // A gradient with no stops must be transparent black.
176         pos[0] = WebCoreFloatToSkScalar(0.0);
177         colors[0] = SK_ColorTRANSPARENT;
178         start = 1;
179     } else if (stop->stop > 0.0) {
180         // Copy the first stop to 0.0. The first stop position may have a slight
181         // rounding error, but we don't care in this float comparison, since
182         // 0.0 comes through cleanly and people aren't likely to want a gradient
183         // with a stop at (0 + epsilon).
184         pos[0] = WebCoreFloatToSkScalar(0.0);
185         colors[0] = makeSkColor(stop->color);
186         start = 1;
187     }
188 
189     for (size_t i = start; i < start + count; i++) {
190         pos[i] = WebCoreFloatToSkScalar(stop->stop);
191         colors[i] = makeSkColor(stop->color);
192         ++stop;
193     }
194 
195     // Copy the last stop to 1.0 if needed. See comment above about this float
196     // comparison.
197     if (count < 1 || (--stop)->stop < 1.0) {
198         pos[start + count] = WebCoreFloatToSkScalar(1.0);
199         colors[start + count] = colors[start + count - 1];
200     }
201 }
202 
shader()203 SkShader* Gradient::shader()
204 {
205     if (m_gradient)
206         return m_gradient.get();
207 
208     sortStopsIfNecessary();
209     ASSERT(m_stopsSorted);
210 
211     size_t countUsed = totalStopsNeeded(m_stops.data(), m_stops.size());
212     ASSERT(countUsed >= 2);
213     ASSERT(countUsed >= m_stops.size());
214 
215     ColorStopOffsetVector pos(countUsed);
216     ColorStopColorVector colors(countUsed);
217     fillStops(m_stops.data(), m_stops.size(), pos, colors);
218 
219     SkShader::TileMode tile = SkShader::kClamp_TileMode;
220     switch (m_spreadMethod) {
221     case SpreadMethodReflect:
222         tile = SkShader::kMirror_TileMode;
223         break;
224     case SpreadMethodRepeat:
225         tile = SkShader::kRepeat_TileMode;
226         break;
227     case SpreadMethodPad:
228         tile = SkShader::kClamp_TileMode;
229         break;
230     }
231 
232     uint32_t shouldDrawInPMColorSpace = m_drawInPMColorSpace ? SkGradientShader::kInterpolateColorsInPremul_Flag : 0;
233     if (m_radial) {
234         if (aspectRatio() != 1) {
235             // CSS3 elliptical gradients: apply the elliptical scaling at the
236             // gradient center point.
237             m_gradientSpaceTransformation.translate(m_p0.x(), m_p0.y());
238             m_gradientSpaceTransformation.scale(1, 1 / aspectRatio());
239             m_gradientSpaceTransformation.translate(-m_p0.x(), -m_p0.y());
240             ASSERT(m_p0 == m_p1);
241         }
242         SkMatrix localMatrix = affineTransformToSkMatrix(m_gradientSpaceTransformation);
243 
244         // Since the two-point radial gradient is slower than the plain radial,
245         // only use it if we have to.
246         if (m_p0 == m_p1 && m_r0 <= 0.0f) {
247             m_gradient = adoptRef(SkGradientShader::CreateRadial(m_p1.data(), m_r1, colors.data(), pos.data(), static_cast<int>(countUsed), tile, shouldDrawInPMColorSpace, &localMatrix));
248         } else {
249             // The radii we give to Skia must be positive. If we're given a
250             // negative radius, ask for zero instead.
251             SkScalar radius0 = m_r0 >= 0.0f ? WebCoreFloatToSkScalar(m_r0) : 0;
252             SkScalar radius1 = m_r1 >= 0.0f ? WebCoreFloatToSkScalar(m_r1) : 0;
253             m_gradient = adoptRef(SkGradientShader::CreateTwoPointConical(m_p0.data(), radius0, m_p1.data(), radius1, colors.data(), pos.data(), static_cast<int>(countUsed), tile, shouldDrawInPMColorSpace, &localMatrix));
254         }
255     } else {
256         SkPoint pts[2] = { m_p0.data(), m_p1.data() };
257         SkMatrix localMatrix = affineTransformToSkMatrix(m_gradientSpaceTransformation);
258         m_gradient = adoptRef(SkGradientShader::CreateLinear(pts, colors.data(), pos.data(), static_cast<int>(countUsed), tile, shouldDrawInPMColorSpace, &localMatrix));
259     }
260 
261     if (!m_gradient) {
262         // use last color, since our "geometry" was degenerate (e.g. radius==0)
263         m_gradient = adoptRef(new SkColorShader(colors[countUsed - 1]));
264     }
265     return m_gradient.get();
266 }
267 
268 } //namespace
269