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(¶meters, 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(¶meters, 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