Home | History | Annotate | Download | only in graphics
      1 /*
      2  * Copyright (C) 2006, 2007, 2008, 2010 Apple Inc. All rights reserved.
      3  * Copyright (C) 2007 Alp Toker <alp (at) 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 blink {
     43 
     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 
     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 
     70 Gradient::~Gradient()
     71 {
     72 }
     73 
     74 static inline bool compareStops(const Gradient::ColorStop& a, const Gradient::ColorStop& b)
     75 {
     76     return a.stop < b.stop;
     77 }
     78 
     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 
     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 
    104 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 
    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 
    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 
    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.
    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.
    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.
    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 
    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 blink
    269