1 /* 2 * Copyright (c) 2008, Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include "config.h" 32 #include "Gradient.h" 33 34 #include "CSSParser.h" 35 #include "GraphicsContext.h" 36 37 #include "SkGradientShader.h" 38 #include "SkiaUtils.h" 39 40 namespace WebCore { 41 42 void Gradient::platformDestroy() 43 { 44 if (m_gradient) 45 SkSafeUnref(m_gradient); 46 m_gradient = 0; 47 } 48 49 static inline U8CPU F2B(float x) 50 { 51 return static_cast<int>(x * 255); 52 } 53 54 static SkColor makeSkColor(float a, float r, float g, float b) 55 { 56 return SkColorSetARGB(F2B(a), F2B(r), F2B(g), F2B(b)); 57 } 58 59 // Determine the total number of stops needed, including pseudo-stops at the 60 // ends as necessary. 61 static size_t totalStopsNeeded(const Gradient::ColorStop* stopData, size_t count) 62 { 63 // N.B.: The tests in this function should kept in sync with the ones in 64 // fillStops(), or badness happens. 65 const Gradient::ColorStop* stop = stopData; 66 size_t countUsed = count; 67 if (count < 1 || stop->stop > 0.0) 68 countUsed++; 69 stop += count - 1; 70 if (count < 1 || stop->stop < 1.0) 71 countUsed++; 72 return countUsed; 73 } 74 75 // Collect sorted stop position and color information into the pos and colors 76 // buffers, ensuring stops at both 0.0 and 1.0. The buffers must be large 77 // enough to hold information for all stops, including the new endpoints if 78 // stops at 0.0 and 1.0 aren't already included. 79 static void fillStops(const Gradient::ColorStop* stopData, 80 size_t count, SkScalar* pos, SkColor* colors) 81 { 82 const Gradient::ColorStop* stop = stopData; 83 size_t start = 0; 84 if (count < 1) { 85 // A gradient with no stops must be transparent black. 86 pos[0] = WebCoreFloatToSkScalar(0.0); 87 colors[0] = makeSkColor(0.0, 0.0, 0.0, 0.0); 88 start = 1; 89 } else if (stop->stop > 0.0) { 90 // Copy the first stop to 0.0. The first stop position may have a slight 91 // rounding error, but we don't care in this float comparison, since 92 // 0.0 comes through cleanly and people aren't likely to want a gradient 93 // with a stop at (0 + epsilon). 94 pos[0] = WebCoreFloatToSkScalar(0.0); 95 colors[0] = makeSkColor(stop->alpha, stop->red, stop->green, stop->blue); 96 start = 1; 97 } 98 99 for (size_t i = start; i < start + count; i++) { 100 pos[i] = WebCoreFloatToSkScalar(stop->stop); 101 colors[i] = makeSkColor(stop->alpha, stop->red, stop->green, stop->blue); 102 ++stop; 103 } 104 105 // Copy the last stop to 1.0 if needed. See comment above about this float 106 // comparison. 107 if (count < 1 || (--stop)->stop < 1.0) { 108 pos[start + count] = WebCoreFloatToSkScalar(1.0); 109 colors[start + count] = colors[start + count - 1]; 110 } 111 } 112 113 SkShader* Gradient::platformGradient() 114 { 115 if (m_gradient) 116 return m_gradient; 117 118 sortStopsIfNecessary(); 119 ASSERT(m_stopsSorted); 120 121 size_t countUsed = totalStopsNeeded(m_stops.data(), m_stops.size()); 122 ASSERT(countUsed >= 2); 123 ASSERT(countUsed >= m_stops.size()); 124 125 // FIXME: Why is all this manual pointer math needed?! 126 SkAutoMalloc storage(countUsed * (sizeof(SkColor) + sizeof(SkScalar))); 127 SkColor* colors = (SkColor*)storage.get(); 128 SkScalar* pos = (SkScalar*)(colors + countUsed); 129 130 fillStops(m_stops.data(), m_stops.size(), pos, colors); 131 132 SkShader::TileMode tile = SkShader::kClamp_TileMode; 133 switch (m_spreadMethod) { 134 case SpreadMethodReflect: 135 tile = SkShader::kMirror_TileMode; 136 break; 137 case SpreadMethodRepeat: 138 tile = SkShader::kRepeat_TileMode; 139 break; 140 case SpreadMethodPad: 141 tile = SkShader::kClamp_TileMode; 142 break; 143 } 144 145 if (m_radial) { 146 // Since the two-point radial gradient is slower than the plain radial, 147 // only use it if we have to. 148 if (m_p0 == m_p1 && m_r0 <= 0.0f) { 149 // The radius we give to Skia must be positive (and non-zero). If 150 // we're given a zero radius, just ask for a very small radius so 151 // Skia will still return an object. 152 SkScalar radius = m_r1 > 0 ? WebCoreFloatToSkScalar(m_r1) : SK_ScalarMin; 153 m_gradient = SkGradientShader::CreateRadial(m_p1, radius, colors, pos, static_cast<int>(countUsed), tile); 154 } else { 155 // The radii we give to Skia must be positive. If we're given a 156 // negative radius, ask for zero instead. 157 SkScalar radius0 = m_r0 >= 0.0f ? WebCoreFloatToSkScalar(m_r0) : 0; 158 SkScalar radius1 = m_r1 >= 0.0f ? WebCoreFloatToSkScalar(m_r1) : 0; 159 m_gradient = SkGradientShader::CreateTwoPointRadial(m_p0, radius0, m_p1, radius1, colors, pos, static_cast<int>(countUsed), tile); 160 } 161 162 if (aspectRatio() != 1) { 163 // CSS3 elliptical gradients: apply the elliptical scaling at the 164 // gradient center point. 165 m_gradientSpaceTransformation.translate(m_p0.x(), m_p0.y()); 166 m_gradientSpaceTransformation.scale(1, 1 / aspectRatio()); 167 m_gradientSpaceTransformation.translate(-m_p0.x(), -m_p0.y()); 168 ASSERT(m_p0 == m_p1); 169 } 170 } else { 171 SkPoint pts[2] = { m_p0, m_p1 }; 172 m_gradient = SkGradientShader::CreateLinear(pts, colors, pos, static_cast<int>(countUsed), tile); 173 } 174 175 ASSERT(m_gradient); 176 SkMatrix matrix = m_gradientSpaceTransformation; 177 m_gradient->setLocalMatrix(matrix); 178 return m_gradient; 179 } 180 181 void Gradient::fill(GraphicsContext* context, const FloatRect& rect) 182 { 183 context->setFillGradient(this); 184 context->fillRect(rect); 185 } 186 187 void Gradient::setPlatformGradientSpaceTransform(const AffineTransform& matrix) 188 { 189 if (m_gradient) 190 m_gradient->setLocalMatrix(m_gradientSpaceTransformation); 191 } 192 193 } // namespace WebCore 194