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 "third_party/blink/renderer/platform/graphics/gradient.h"
29
30 #include <algorithm>
31 #include "base/optional.h"
32 #include "third_party/blink/renderer/platform/geometry/float_rect.h"
33 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
34 #include "third_party/blink/renderer/platform/graphics/paint/paint_shader.h"
35 #include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h"
36 #include "third_party/skia/include/core/SkColor.h"
37 #include "third_party/skia/include/core/SkMatrix.h"
38 #include "third_party/skia/include/core/SkShader.h"
39 #include "third_party/skia/include/effects/SkGradientShader.h"
40
41 namespace blink {
42
Gradient(Type type,GradientSpreadMethod spread_method,ColorInterpolation interpolation,DegenerateHandling degenerate_handling)43 Gradient::Gradient(Type type,
44 GradientSpreadMethod spread_method,
45 ColorInterpolation interpolation,
46 DegenerateHandling degenerate_handling)
47 : type_(type),
48 spread_method_(spread_method),
49 color_interpolation_(interpolation),
50 degenerate_handling_(degenerate_handling),
51 stops_sorted_(true) {}
52
53 Gradient::~Gradient() = default;
54
CompareStops(const Gradient::ColorStop & a,const Gradient::ColorStop & b)55 static inline bool CompareStops(const Gradient::ColorStop& a,
56 const Gradient::ColorStop& b) {
57 return a.stop < b.stop;
58 }
59
AddColorStop(const Gradient::ColorStop & stop)60 void Gradient::AddColorStop(const Gradient::ColorStop& stop) {
61 if (stops_.IsEmpty()) {
62 stops_sorted_ = true;
63 } else {
64 stops_sorted_ = stops_sorted_ && CompareStops(stops_.back(), stop);
65 }
66
67 stops_.push_back(stop);
68 cached_shader_.reset();
69 }
70
AddColorStops(const Vector<Gradient::ColorStop> & stops)71 void Gradient::AddColorStops(const Vector<Gradient::ColorStop>& stops) {
72 for (const auto& stop : stops)
73 AddColorStop(stop);
74 }
75
SortStopsIfNecessary()76 void Gradient::SortStopsIfNecessary() {
77 if (stops_sorted_)
78 return;
79
80 stops_sorted_ = true;
81
82 if (!stops_.size())
83 return;
84
85 std::stable_sort(stops_.begin(), stops_.end(), CompareStops);
86 }
87
88 // FIXME: This would be more at home as Color::operator SkColor.
MakeSkColor(const Color & c)89 static inline SkColor MakeSkColor(const Color& c) {
90 return SkColorSetARGB(c.Alpha(), c.Red(), c.Green(), c.Blue());
91 }
92
93 // Collect sorted stop position and color information into the pos and colors
94 // buffers, ensuring stops at both 0.0 and 1.0.
95 // TODO(fmalita): theoretically Skia should provide the same 0.0/1.0 padding
96 // (making this logic redundant), but in practice there are rendering diffs;
97 // investigate.
FillSkiaStops(ColorBuffer & colors,OffsetBuffer & pos) const98 void Gradient::FillSkiaStops(ColorBuffer& colors, OffsetBuffer& pos) const {
99 if (stops_.IsEmpty()) {
100 // A gradient with no stops must be transparent black.
101 pos.push_back(WebCoreDoubleToSkScalar(0));
102 colors.push_back(SK_ColorTRANSPARENT);
103 } else if (stops_.front().stop > 0) {
104 // Copy the first stop to 0.0. The first stop position may have a slight
105 // rounding error, but we don't care in this float comparison, since
106 // 0.0 comes through cleanly and people aren't likely to want a gradient
107 // with a stop at (0 + epsilon).
108 pos.push_back(WebCoreDoubleToSkScalar(0));
109 if (color_filter_) {
110 colors.push_back(
111 color_filter_->filterColor(MakeSkColor(stops_.front().color)));
112 } else {
113 colors.push_back(MakeSkColor(stops_.front().color));
114 }
115 }
116
117 for (const auto& stop : stops_) {
118 pos.push_back(WebCoreDoubleToSkScalar(stop.stop));
119 if (color_filter_)
120 colors.push_back(color_filter_->filterColor(MakeSkColor(stop.color)));
121 else
122 colors.push_back(MakeSkColor(stop.color));
123 }
124
125 // Copy the last stop to 1.0 if needed. See comment above about this float
126 // comparison.
127 DCHECK(!pos.IsEmpty());
128 if (pos.back() < 1) {
129 pos.push_back(WebCoreDoubleToSkScalar(1));
130 colors.push_back(colors.back());
131 }
132 }
133
CreateShaderInternal(const SkMatrix & local_matrix)134 sk_sp<PaintShader> Gradient::CreateShaderInternal(
135 const SkMatrix& local_matrix) {
136 SortStopsIfNecessary();
137 DCHECK(stops_sorted_);
138
139 ColorBuffer colors;
140 colors.ReserveCapacity(stops_.size());
141 OffsetBuffer pos;
142 pos.ReserveCapacity(stops_.size());
143
144 FillSkiaStops(colors, pos);
145 DCHECK_GE(colors.size(), 2ul);
146 DCHECK_EQ(pos.size(), colors.size());
147
148 SkTileMode tile = SkTileMode::kClamp;
149 switch (spread_method_) {
150 case kSpreadMethodReflect:
151 tile = SkTileMode::kMirror;
152 break;
153 case kSpreadMethodRepeat:
154 tile = SkTileMode::kRepeat;
155 break;
156 case kSpreadMethodPad:
157 tile = SkTileMode::kClamp;
158 break;
159 }
160
161 uint32_t flags = color_interpolation_ == ColorInterpolation::kPremultiplied
162 ? SkGradientShader::kInterpolateColorsInPremul_Flag
163 : 0;
164 sk_sp<PaintShader> shader =
165 CreateShader(colors, pos, tile, flags, local_matrix, colors.back());
166 DCHECK(shader);
167
168 return shader;
169 }
170
ApplyToFlags(PaintFlags & flags,const SkMatrix & local_matrix)171 void Gradient::ApplyToFlags(PaintFlags& flags, const SkMatrix& local_matrix) {
172 if (!cached_shader_ || local_matrix != cached_shader_->GetLocalMatrix() ||
173 flags.getColorFilter().get() != color_filter_.get()) {
174 color_filter_ = flags.getColorFilter();
175 flags.setColorFilter(nullptr);
176 cached_shader_ = CreateShaderInternal(local_matrix);
177 }
178
179 flags.setShader(cached_shader_);
180
181 // Legacy behavior: gradients are always dithered.
182 flags.setDither(true);
183 }
184
185 namespace {
186
187 class LinearGradient final : public Gradient {
188 public:
LinearGradient(const FloatPoint & p0,const FloatPoint & p1,GradientSpreadMethod spread_method,ColorInterpolation interpolation,DegenerateHandling degenerate_handling)189 LinearGradient(const FloatPoint& p0,
190 const FloatPoint& p1,
191 GradientSpreadMethod spread_method,
192 ColorInterpolation interpolation,
193 DegenerateHandling degenerate_handling)
194 : Gradient(Type::kLinear,
195 spread_method,
196 interpolation,
197 degenerate_handling),
198 p0_(p0),
199 p1_(p1) {}
200
201 protected:
CreateShader(const ColorBuffer & colors,const OffsetBuffer & pos,SkTileMode tile_mode,uint32_t flags,const SkMatrix & local_matrix,SkColor fallback_color) const202 sk_sp<PaintShader> CreateShader(const ColorBuffer& colors,
203 const OffsetBuffer& pos,
204 SkTileMode tile_mode,
205 uint32_t flags,
206 const SkMatrix& local_matrix,
207 SkColor fallback_color) const override {
208 if (GetDegenerateHandling() == DegenerateHandling::kDisallow &&
209 p0_ == p1_) {
210 return PaintShader::MakeEmpty();
211 }
212
213 SkPoint pts[2] = {FloatPointToSkPoint(p0_), FloatPointToSkPoint(p1_)};
214 return PaintShader::MakeLinearGradient(
215 pts, colors.data(), pos.data(), static_cast<int>(colors.size()),
216 tile_mode, flags, &local_matrix, fallback_color);
217 }
218
219 private:
220 const FloatPoint p0_;
221 const FloatPoint p1_;
222 };
223
224 class RadialGradient final : public Gradient {
225 public:
RadialGradient(const FloatPoint & p0,float r0,const FloatPoint & p1,float r1,float aspect_ratio,GradientSpreadMethod spread_method,ColorInterpolation interpolation,DegenerateHandling degenerate_handling)226 RadialGradient(const FloatPoint& p0,
227 float r0,
228 const FloatPoint& p1,
229 float r1,
230 float aspect_ratio,
231 GradientSpreadMethod spread_method,
232 ColorInterpolation interpolation,
233 DegenerateHandling degenerate_handling)
234 : Gradient(Type::kRadial,
235 spread_method,
236 interpolation,
237 degenerate_handling),
238 p0_(p0),
239 p1_(p1),
240 r0_(r0),
241 r1_(r1),
242 aspect_ratio_(aspect_ratio) {}
243
244 protected:
CreateShader(const ColorBuffer & colors,const OffsetBuffer & pos,SkTileMode tile_mode,uint32_t flags,const SkMatrix & local_matrix,SkColor fallback_color) const245 sk_sp<PaintShader> CreateShader(const ColorBuffer& colors,
246 const OffsetBuffer& pos,
247 SkTileMode tile_mode,
248 uint32_t flags,
249 const SkMatrix& local_matrix,
250 SkColor fallback_color) const override {
251 const SkMatrix* matrix = &local_matrix;
252 base::Optional<SkMatrix> adjusted_local_matrix;
253 if (aspect_ratio_ != 1) {
254 // CSS3 elliptical gradients: apply the elliptical scaling at the
255 // gradient center point.
256 DCHECK(p0_ == p1_);
257 adjusted_local_matrix.emplace(local_matrix);
258 adjusted_local_matrix->preScale(1, 1 / aspect_ratio_, p0_.X(), p0_.Y());
259 matrix = &*adjusted_local_matrix;
260 }
261
262 // The radii we give to Skia must be positive. If we're given a
263 // negative radius, ask for zero instead.
264 const SkScalar radius0 = std::max(WebCoreFloatToSkScalar(r0_), 0.0f);
265 const SkScalar radius1 = std::max(WebCoreFloatToSkScalar(r1_), 0.0f);
266
267 if (GetDegenerateHandling() == DegenerateHandling::kDisallow &&
268 p0_ == p1_ && radius0 == radius1) {
269 return PaintShader::MakeEmpty();
270 }
271
272 return PaintShader::MakeTwoPointConicalGradient(
273 FloatPointToSkPoint(p0_), radius0, FloatPointToSkPoint(p1_), radius1,
274 colors.data(), pos.data(), static_cast<int>(colors.size()), tile_mode,
275 flags, matrix, fallback_color);
276 }
277
278 private:
279 const FloatPoint p0_;
280 const FloatPoint p1_;
281 const float r0_;
282 const float r1_;
283 const float aspect_ratio_; // For elliptical gradient, width / height.
284 };
285
286 class ConicGradient final : public Gradient {
287 public:
ConicGradient(const FloatPoint & position,float rotation,float start_angle,float end_angle,GradientSpreadMethod spread_method,ColorInterpolation interpolation,DegenerateHandling degenerate_handling)288 ConicGradient(const FloatPoint& position,
289 float rotation,
290 float start_angle,
291 float end_angle,
292 GradientSpreadMethod spread_method,
293 ColorInterpolation interpolation,
294 DegenerateHandling degenerate_handling)
295 : Gradient(Type::kConic,
296 spread_method,
297 interpolation,
298 degenerate_handling),
299 position_(position),
300 rotation_(rotation),
301 start_angle_(start_angle),
302 end_angle_(end_angle) {}
303
304 protected:
CreateShader(const ColorBuffer & colors,const OffsetBuffer & pos,SkTileMode tile_mode,uint32_t flags,const SkMatrix & local_matrix,SkColor fallback_color) const305 sk_sp<PaintShader> CreateShader(const ColorBuffer& colors,
306 const OffsetBuffer& pos,
307 SkTileMode tile_mode,
308 uint32_t flags,
309 const SkMatrix& local_matrix,
310 SkColor fallback_color) const override {
311 if (GetDegenerateHandling() == DegenerateHandling::kDisallow &&
312 start_angle_ == end_angle_) {
313 return PaintShader::MakeEmpty();
314 }
315
316 // Skia's sweep gradient angles are relative to the x-axis, not the y-axis.
317 const float skia_rotation = rotation_ - 90;
318 const SkMatrix* matrix = &local_matrix;
319 base::Optional<SkMatrix> adjusted_local_matrix;
320 if (skia_rotation) {
321 adjusted_local_matrix.emplace(local_matrix);
322 adjusted_local_matrix->preRotate(skia_rotation, position_.X(),
323 position_.Y());
324 matrix = &*adjusted_local_matrix;
325 }
326
327 return PaintShader::MakeSweepGradient(
328 position_.X(), position_.Y(), colors.data(), pos.data(),
329 static_cast<int>(colors.size()), tile_mode, start_angle_, end_angle_,
330 flags, matrix, fallback_color);
331 }
332
333 private:
334 const FloatPoint position_; // center point
335 const float rotation_; // global rotation (deg)
336 const float start_angle_; // angle (deg) corresponding to color position 0
337 const float end_angle_; // angle (deg) corresponding to color position 1
338 };
339
340 } // namespace
341
CreateLinear(const FloatPoint & p0,const FloatPoint & p1,GradientSpreadMethod spread_method,ColorInterpolation interpolation,DegenerateHandling degenerate_handling)342 scoped_refptr<Gradient> Gradient::CreateLinear(
343 const FloatPoint& p0,
344 const FloatPoint& p1,
345 GradientSpreadMethod spread_method,
346 ColorInterpolation interpolation,
347 DegenerateHandling degenerate_handling) {
348 return base::AdoptRef(new LinearGradient(p0, p1, spread_method, interpolation,
349 degenerate_handling));
350 }
351
CreateRadial(const FloatPoint & p0,float r0,const FloatPoint & p1,float r1,float aspect_ratio,GradientSpreadMethod spread_method,ColorInterpolation interpolation,DegenerateHandling degenerate_handling)352 scoped_refptr<Gradient> Gradient::CreateRadial(
353 const FloatPoint& p0,
354 float r0,
355 const FloatPoint& p1,
356 float r1,
357 float aspect_ratio,
358 GradientSpreadMethod spread_method,
359 ColorInterpolation interpolation,
360 DegenerateHandling degenerate_handling) {
361 return base::AdoptRef(new RadialGradient(p0, r0, p1, r1, aspect_ratio,
362 spread_method, interpolation,
363 degenerate_handling));
364 }
365
CreateConic(const FloatPoint & position,float rotation,float start_angle,float end_angle,GradientSpreadMethod spread_method,ColorInterpolation interpolation,DegenerateHandling degenerate_handling)366 scoped_refptr<Gradient> Gradient::CreateConic(
367 const FloatPoint& position,
368 float rotation,
369 float start_angle,
370 float end_angle,
371 GradientSpreadMethod spread_method,
372 ColorInterpolation interpolation,
373 DegenerateHandling degenerate_handling) {
374 return base::AdoptRef(new ConicGradient(position, rotation, start_angle,
375 end_angle, spread_method,
376 interpolation, degenerate_handling));
377 }
378
379 } // namespace blink
380