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 "core/platform/graphics/Gradient.h"
     30 
     31 #include "core/platform/graphics/Color.h"
     32 #include "core/platform/graphics/FloatRect.h"
     33 #include "core/platform/graphics/GraphicsContext.h"
     34 #include "core/platform/graphics/skia/SkiaUtils.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 #include "wtf/HashFunctions.h"
     39 #include "wtf/StringHasher.h"
     40 
     41 using WTF::pairIntHash;
     42 
     43 namespace WebCore {
     44 
     45 Gradient::Gradient(const FloatPoint& p0, const FloatPoint& p1)
     46     : m_radial(false)
     47     , m_p0(p0)
     48     , m_p1(p1)
     49     , m_r0(0)
     50     , m_r1(0)
     51     , m_aspectRatio(1)
     52     , m_stopsSorted(false)
     53     , m_spreadMethod(SpreadMethodPad)
     54     , m_cachedHash(0)
     55     , m_drawInPMColorSpace(false)
     56 {
     57 }
     58 
     59 Gradient::Gradient(const FloatPoint& p0, float r0, const FloatPoint& p1, float r1, float aspectRatio)
     60     : m_radial(true)
     61     , m_p0(p0)
     62     , m_p1(p1)
     63     , m_r0(r0)
     64     , m_r1(r1)
     65     , m_aspectRatio(aspectRatio)
     66     , m_stopsSorted(false)
     67     , m_spreadMethod(SpreadMethodPad)
     68     , m_cachedHash(0)
     69     , m_drawInPMColorSpace(false)
     70 {
     71 }
     72 
     73 Gradient::~Gradient()
     74 {
     75 }
     76 
     77 void Gradient::adjustParametersForTiledDrawing(IntSize& size, FloatRect& srcRect)
     78 {
     79     if (m_radial)
     80         return;
     81 
     82     if (srcRect.isEmpty())
     83         return;
     84 
     85     if (m_p0.x() == m_p1.x()) {
     86         size.setWidth(1);
     87         srcRect.setWidth(1);
     88         srcRect.setX(0);
     89         return;
     90     }
     91     if (m_p0.y() != m_p1.y())
     92         return;
     93 
     94     size.setHeight(1);
     95     srcRect.setHeight(1);
     96     srcRect.setY(0);
     97 }
     98 
     99 void Gradient::addColorStop(float value, const Color& color)
    100 {
    101     float r;
    102     float g;
    103     float b;
    104     float a;
    105     color.getRGBA(r, g, b, a);
    106     m_stops.append(ColorStop(value, r, g, b, a));
    107 
    108     m_stopsSorted = false;
    109     m_gradient.clear();
    110 
    111     invalidateHash();
    112 }
    113 
    114 void Gradient::addColorStop(const Gradient::ColorStop& stop)
    115 {
    116     m_stops.append(stop);
    117 
    118     m_stopsSorted = false;
    119     m_gradient.clear();
    120 
    121     invalidateHash();
    122 }
    123 
    124 static inline bool compareStops(const Gradient::ColorStop& a, const Gradient::ColorStop& b)
    125 {
    126     return a.stop < b.stop;
    127 }
    128 
    129 void Gradient::sortStopsIfNecessary()
    130 {
    131     if (m_stopsSorted)
    132         return;
    133 
    134     m_stopsSorted = true;
    135 
    136     if (!m_stops.size())
    137         return;
    138 
    139     std::stable_sort(m_stops.begin(), m_stops.end(), compareStops);
    140 
    141     invalidateHash();
    142 }
    143 
    144 bool Gradient::hasAlpha() const
    145 {
    146     for (size_t i = 0; i < m_stops.size(); i++) {
    147         if (m_stops[i].alpha < 1)
    148             return true;
    149     }
    150 
    151     return false;
    152 }
    153 
    154 void Gradient::setSpreadMethod(GradientSpreadMethod spreadMethod)
    155 {
    156     // FIXME: Should it become necessary, allow calls to this method after m_gradient has been set.
    157     ASSERT(!m_gradient);
    158 
    159     if (m_spreadMethod == spreadMethod)
    160         return;
    161 
    162     m_spreadMethod = spreadMethod;
    163 
    164     invalidateHash();
    165 }
    166 
    167 void Gradient::setDrawsInPMColorSpace(bool drawInPMColorSpace)
    168 {
    169     if (drawInPMColorSpace == m_drawInPMColorSpace)
    170         return;
    171 
    172     m_drawInPMColorSpace = drawInPMColorSpace;
    173     m_gradient.clear();
    174 
    175     invalidateHash();
    176 }
    177 
    178 void Gradient::setGradientSpaceTransform(const AffineTransform& gradientSpaceTransformation)
    179 {
    180     if (m_gradientSpaceTransformation == gradientSpaceTransformation)
    181         return;
    182 
    183     m_gradientSpaceTransformation = gradientSpaceTransformation;
    184     if (m_gradient)
    185         m_gradient->setLocalMatrix(m_gradientSpaceTransformation);
    186 
    187     invalidateHash();
    188 }
    189 
    190 unsigned Gradient::hash() const
    191 {
    192     if (m_cachedHash)
    193         return m_cachedHash;
    194 
    195     struct {
    196         AffineTransform gradientSpaceTransformation;
    197         FloatPoint p0;
    198         FloatPoint p1;
    199         float r0;
    200         float r1;
    201         float aspectRatio;
    202         GradientSpreadMethod spreadMethod;
    203         bool radial;
    204         bool drawInPMColorSpace;
    205     } parameters;
    206 
    207     // StringHasher requires that the memory it hashes be a multiple of two in size.
    208     COMPILE_ASSERT(!(sizeof(parameters) % 2), Gradient_parameters_size_should_be_multiple_of_two);
    209     COMPILE_ASSERT(!(sizeof(ColorStop) % 2), Color_stop_size_should_be_multiple_of_two);
    210 
    211     // Ensure that any padding in the struct is zero-filled, so it will not affect the hash value.
    212     memset(&parameters, 0, sizeof(parameters));
    213 
    214     parameters.gradientSpaceTransformation = m_gradientSpaceTransformation;
    215     parameters.p0 = m_p0;
    216     parameters.p1 = m_p1;
    217     parameters.r0 = m_r0;
    218     parameters.r1 = m_r1;
    219     parameters.aspectRatio = m_aspectRatio;
    220     parameters.spreadMethod = m_spreadMethod;
    221     parameters.radial = m_radial;
    222     parameters.drawInPMColorSpace = m_drawInPMColorSpace;
    223 
    224     unsigned parametersHash = StringHasher::hashMemory(&parameters, sizeof(parameters));
    225     unsigned stopHash = StringHasher::hashMemory(m_stops.data(), m_stops.size() * sizeof(ColorStop));
    226 
    227     m_cachedHash = pairIntHash(parametersHash, stopHash);
    228 
    229     return m_cachedHash;
    230 }
    231 
    232 static inline U8CPU F2B(float x)
    233 {
    234     return static_cast<int>(x * 255);
    235 }
    236 
    237 static SkColor makeSkColor(float a, float r, float g, float b)
    238 {
    239     return SkColorSetARGB(F2B(a), F2B(r), F2B(g), F2B(b));
    240 }
    241 
    242 // Determine the total number of stops needed, including pseudo-stops at the
    243 // ends as necessary.
    244 static size_t totalStopsNeeded(const Gradient::ColorStop* stopData, size_t count)
    245 {
    246     // N.B.: The tests in this function should kept in sync with the ones in
    247     // fillStops(), or badness happens.
    248     const Gradient::ColorStop* stop = stopData;
    249     size_t countUsed = count;
    250     if (count < 1 || stop->stop > 0.0)
    251         countUsed++;
    252     stop += count - 1;
    253     if (count < 1 || stop->stop < 1.0)
    254         countUsed++;
    255     return countUsed;
    256 }
    257 
    258 // Collect sorted stop position and color information into the pos and colors
    259 // buffers, ensuring stops at both 0.0 and 1.0. The buffers must be large
    260 // enough to hold information for all stops, including the new endpoints if
    261 // stops at 0.0 and 1.0 aren't already included.
    262 static void fillStops(const Gradient::ColorStop* stopData,
    263     size_t count, SkScalar* pos, SkColor* colors)
    264 {
    265     const Gradient::ColorStop* stop = stopData;
    266     size_t start = 0;
    267     if (count < 1) {
    268         // A gradient with no stops must be transparent black.
    269         pos[0] = WebCoreFloatToSkScalar(0.0);
    270         colors[0] = makeSkColor(0.0, 0.0, 0.0, 0.0);
    271         start = 1;
    272     } else if (stop->stop > 0.0) {
    273         // Copy the first stop to 0.0. The first stop position may have a slight
    274         // rounding error, but we don't care in this float comparison, since
    275         // 0.0 comes through cleanly and people aren't likely to want a gradient
    276         // with a stop at (0 + epsilon).
    277         pos[0] = WebCoreFloatToSkScalar(0.0);
    278         colors[0] = makeSkColor(stop->alpha, stop->red, stop->green, stop->blue);
    279         start = 1;
    280     }
    281 
    282     for (size_t i = start; i < start + count; i++) {
    283         pos[i] = WebCoreFloatToSkScalar(stop->stop);
    284         colors[i] = makeSkColor(stop->alpha, stop->red, stop->green, stop->blue);
    285         ++stop;
    286     }
    287 
    288     // Copy the last stop to 1.0 if needed. See comment above about this float
    289     // comparison.
    290     if (count < 1 || (--stop)->stop < 1.0) {
    291         pos[start + count] = WebCoreFloatToSkScalar(1.0);
    292         colors[start + count] = colors[start + count - 1];
    293     }
    294 }
    295 
    296 SkShader* Gradient::shader()
    297 {
    298     if (m_gradient)
    299         return m_gradient.get();
    300 
    301     sortStopsIfNecessary();
    302     ASSERT(m_stopsSorted);
    303 
    304     size_t countUsed = totalStopsNeeded(m_stops.data(), m_stops.size());
    305     ASSERT(countUsed >= 2);
    306     ASSERT(countUsed >= m_stops.size());
    307 
    308     // FIXME: Why is all this manual pointer math needed?!
    309     SkAutoMalloc storage(countUsed * (sizeof(SkColor) + sizeof(SkScalar)));
    310     SkColor* colors = (SkColor*)storage.get();
    311     SkScalar* pos = (SkScalar*)(colors + countUsed);
    312 
    313     fillStops(m_stops.data(), m_stops.size(), pos, colors);
    314 
    315     SkShader::TileMode tile = SkShader::kClamp_TileMode;
    316     switch (m_spreadMethod) {
    317     case SpreadMethodReflect:
    318         tile = SkShader::kMirror_TileMode;
    319         break;
    320     case SpreadMethodRepeat:
    321         tile = SkShader::kRepeat_TileMode;
    322         break;
    323     case SpreadMethodPad:
    324         tile = SkShader::kClamp_TileMode;
    325         break;
    326     }
    327 
    328     uint32_t shouldDrawInPMColorSpace = m_drawInPMColorSpace ? SkGradientShader::kInterpolateColorsInPremul_Flag : 0;
    329     if (m_radial) {
    330         // Since the two-point radial gradient is slower than the plain radial,
    331         // only use it if we have to.
    332         if (m_p0 == m_p1 && m_r0 <= 0.0f) {
    333             m_gradient = adoptRef(SkGradientShader::CreateRadial(m_p1, m_r1, colors, pos, static_cast<int>(countUsed), tile, 0, shouldDrawInPMColorSpace));
    334         } else {
    335             // The radii we give to Skia must be positive. If we're given a
    336             // negative radius, ask for zero instead.
    337             SkScalar radius0 = m_r0 >= 0.0f ? WebCoreFloatToSkScalar(m_r0) : 0;
    338             SkScalar radius1 = m_r1 >= 0.0f ? WebCoreFloatToSkScalar(m_r1) : 0;
    339             m_gradient = adoptRef(SkGradientShader::CreateTwoPointConical(m_p0, radius0, m_p1, radius1, colors, pos, static_cast<int>(countUsed), tile, 0, shouldDrawInPMColorSpace));
    340         }
    341 
    342         if (aspectRatio() != 1) {
    343             // CSS3 elliptical gradients: apply the elliptical scaling at the
    344             // gradient center point.
    345             m_gradientSpaceTransformation.translate(m_p0.x(), m_p0.y());
    346             m_gradientSpaceTransformation.scale(1, 1 / aspectRatio());
    347             m_gradientSpaceTransformation.translate(-m_p0.x(), -m_p0.y());
    348             ASSERT(m_p0 == m_p1);
    349         }
    350     } else {
    351         SkPoint pts[2] = { m_p0, m_p1 };
    352         m_gradient = adoptRef(SkGradientShader::CreateLinear(pts, colors, pos, static_cast<int>(countUsed), tile, 0, shouldDrawInPMColorSpace));
    353     }
    354 
    355     if (!m_gradient) {
    356         // use last color, since our "geometry" was degenerate (e.g. radius==0)
    357         m_gradient = adoptRef(new SkColorShader(colors[countUsed - 1]));
    358     } else {
    359         m_gradient->setLocalMatrix(m_gradientSpaceTransformation);
    360     }
    361     return m_gradient.get();
    362 }
    363 
    364 } //namespace
    365