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