1 /*
2 * Copyright (c) 2006,2007,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
33 #include "platform/graphics/skia/SkiaUtils.h"
34
35 #include "SkColorPriv.h"
36 #include "SkRegion.h"
37 #include "platform/graphics/GraphicsContext.h"
38 #include "platform/graphics/ImageBuffer.h"
39
40 namespace blink {
41
42 static const struct CompositOpToXfermodeMode {
43 CompositeOperator mCompositOp;
44 SkXfermode::Mode m_xfermodeMode;
45 } gMapCompositOpsToXfermodeModes[] = {
46 { CompositeClear, SkXfermode::kClear_Mode },
47 { CompositeCopy, SkXfermode::kSrc_Mode },
48 { CompositeSourceOver, SkXfermode::kSrcOver_Mode },
49 { CompositeSourceIn, SkXfermode::kSrcIn_Mode },
50 { CompositeSourceOut, SkXfermode::kSrcOut_Mode },
51 { CompositeSourceAtop, SkXfermode::kSrcATop_Mode },
52 { CompositeDestinationOver, SkXfermode::kDstOver_Mode },
53 { CompositeDestinationIn, SkXfermode::kDstIn_Mode },
54 { CompositeDestinationOut, SkXfermode::kDstOut_Mode },
55 { CompositeDestinationAtop, SkXfermode::kDstATop_Mode },
56 { CompositeXOR, SkXfermode::kXor_Mode },
57 { CompositePlusDarker, SkXfermode::kDarken_Mode },
58 { CompositePlusLighter, SkXfermode::kPlus_Mode }
59 };
60
61 // keep this array in sync with WebBlendMode enum in public/platform/WebBlendMode.h
62 static const SkXfermode::Mode gMapBlendOpsToXfermodeModes[] = {
63 SkXfermode::kClear_Mode, // WebBlendModeNormal
64 SkXfermode::kMultiply_Mode, // WebBlendModeMultiply
65 SkXfermode::kScreen_Mode, // WebBlendModeScreen
66 SkXfermode::kOverlay_Mode, // WebBlendModeOverlay
67 SkXfermode::kDarken_Mode, // WebBlendModeDarken
68 SkXfermode::kLighten_Mode, // WebBlendModeLighten
69 SkXfermode::kColorDodge_Mode, // WebBlendModeColorDodge
70 SkXfermode::kColorBurn_Mode, // WebBlendModeColorBurn
71 SkXfermode::kHardLight_Mode, // WebBlendModeHardLight
72 SkXfermode::kSoftLight_Mode, // WebBlendModeSoftLight
73 SkXfermode::kDifference_Mode, // WebBlendModeDifference
74 SkXfermode::kExclusion_Mode, // WebBlendModeExclusion
75 SkXfermode::kHue_Mode, // WebBlendModeHue
76 SkXfermode::kSaturation_Mode, // WebBlendModeSaturation
77 SkXfermode::kColor_Mode, // WebBlendModeColor
78 SkXfermode::kLuminosity_Mode // WebBlendModeLuminosity
79 };
80
WebCoreCompositeToSkiaComposite(CompositeOperator op,WebBlendMode blendMode)81 SkXfermode::Mode WebCoreCompositeToSkiaComposite(CompositeOperator op, WebBlendMode blendMode)
82 {
83 if (blendMode != WebBlendModeNormal) {
84 if (static_cast<uint8_t>(blendMode) >= SK_ARRAY_COUNT(gMapBlendOpsToXfermodeModes)) {
85 SkDEBUGF(("GraphicsContext::setPlatformCompositeOperation unknown WebBlendMode %d\n", blendMode));
86 return SkXfermode::kSrcOver_Mode;
87 }
88 return gMapBlendOpsToXfermodeModes[static_cast<uint8_t>(blendMode)];
89 }
90
91 const CompositOpToXfermodeMode* table = gMapCompositOpsToXfermodeModes;
92 if (static_cast<uint8_t>(op) >= SK_ARRAY_COUNT(gMapCompositOpsToXfermodeModes)) {
93 SkDEBUGF(("GraphicsContext::setPlatformCompositeOperation unknown CompositeOperator %d\n", op));
94 return SkXfermode::kSrcOver_Mode;
95 }
96 SkASSERT(table[static_cast<uint8_t>(op)].mCompositOp == op);
97 return table[static_cast<uint8_t>(op)].m_xfermodeMode;
98 }
99
InvScaleByte(U8CPU component,uint32_t scale)100 static U8CPU InvScaleByte(U8CPU component, uint32_t scale)
101 {
102 SkASSERT(component == (uint8_t)component);
103 return (component * scale + 0x8000) >> 16;
104 }
105
SkPMColorToColor(SkPMColor pm)106 SkColor SkPMColorToColor(SkPMColor pm)
107 {
108 if (!pm)
109 return 0;
110 unsigned a = SkGetPackedA32(pm);
111 if (!a) {
112 // A zero alpha value when there are non-zero R, G, or B channels is an
113 // invalid premultiplied color (since all channels should have been
114 // multiplied by 0 if a=0).
115 SkASSERT(false);
116 // In production, return 0 to protect against division by zero.
117 return 0;
118 }
119
120 uint32_t scale = (255 << 16) / a;
121
122 return SkColorSetARGB(a,
123 InvScaleByte(SkGetPackedR32(pm), scale),
124 InvScaleByte(SkGetPackedG32(pm), scale),
125 InvScaleByte(SkGetPackedB32(pm), scale));
126 }
127
SkPathContainsPoint(const SkPath & originalPath,const FloatPoint & point,SkPath::FillType ft)128 bool SkPathContainsPoint(const SkPath& originalPath, const FloatPoint& point, SkPath::FillType ft)
129 {
130 SkRect bounds = originalPath.getBounds();
131
132 // We can immediately return false if the point is outside the bounding
133 // rect. We don't use bounds.contains() here, since it would exclude
134 // points on the right and bottom edges of the bounding rect, and we want
135 // to include them.
136 SkScalar fX = SkFloatToScalar(point.x());
137 SkScalar fY = SkFloatToScalar(point.y());
138 if (fX < bounds.fLeft || fX > bounds.fRight || fY < bounds.fTop || fY > bounds.fBottom)
139 return false;
140
141 // Scale the path to a large size before hit testing for two reasons:
142 // 1) Skia has trouble with coordinates close to the max signed 16-bit values, so we scale larger paths down.
143 // TODO: when Skia is patched to work properly with large values, this will not be necessary.
144 // 2) Skia does not support analytic hit testing, so we scale paths up to do raster hit testing with subpixel accuracy.
145 SkScalar biggestCoord = std::max(std::max(std::max(bounds.fRight, bounds.fBottom), -bounds.fLeft), -bounds.fTop);
146 if (SkScalarNearlyZero(biggestCoord))
147 return false;
148 biggestCoord = std::max(std::max(biggestCoord, fX + 1), fY + 1);
149
150 const SkScalar kMaxCoordinate = SkIntToScalar(1 << 15);
151 SkScalar scale = SkScalarDiv(kMaxCoordinate, biggestCoord);
152
153 SkRegion rgn;
154 SkRegion clip;
155 SkMatrix m;
156 SkPath scaledPath(originalPath);
157
158 scaledPath.setFillType(ft);
159 m.setScale(scale, scale);
160 scaledPath.transform(m, 0);
161
162 int x = static_cast<int>(floorf(0.5f + point.x() * scale));
163 int y = static_cast<int>(floorf(0.5f + point.y() * scale));
164 clip.setRect(x - 1, y - 1, x + 1, y + 1);
165
166 return rgn.setPath(scaledPath, clip);
167 }
168
affineTransformToSkMatrix(const AffineTransform & source)169 SkMatrix affineTransformToSkMatrix(const AffineTransform& source)
170 {
171 SkMatrix result;
172
173 result.setScaleX(WebCoreDoubleToSkScalar(source.a()));
174 result.setSkewX(WebCoreDoubleToSkScalar(source.c()));
175 result.setTranslateX(WebCoreDoubleToSkScalar(source.e()));
176
177 result.setScaleY(WebCoreDoubleToSkScalar(source.d()));
178 result.setSkewY(WebCoreDoubleToSkScalar(source.b()));
179 result.setTranslateY(WebCoreDoubleToSkScalar(source.f()));
180
181 // FIXME: Set perspective properly.
182 result.setPerspX(0);
183 result.setPerspY(0);
184 result.set(SkMatrix::kMPersp2, SK_Scalar1);
185
186 return result;
187 }
188
nearlyIntegral(float value)189 bool nearlyIntegral(float value)
190 {
191 return fabs(value - floorf(value)) < std::numeric_limits<float>::epsilon();
192 }
193
limitInterpolationQuality(const GraphicsContext * context,InterpolationQuality resampling)194 InterpolationQuality limitInterpolationQuality(const GraphicsContext* context, InterpolationQuality resampling)
195 {
196 return std::min(resampling, context->imageInterpolationQuality());
197 }
198
computeInterpolationQuality(const SkMatrix & matrix,float srcWidth,float srcHeight,float destWidth,float destHeight,bool isDataComplete)199 InterpolationQuality computeInterpolationQuality(
200 const SkMatrix& matrix,
201 float srcWidth,
202 float srcHeight,
203 float destWidth,
204 float destHeight,
205 bool isDataComplete)
206 {
207 // The percent change below which we will not resample. This usually means
208 // an off-by-one error on the web page, and just doing nearest neighbor
209 // sampling is usually good enough.
210 const float kFractionalChangeThreshold = 0.025f;
211
212 // Images smaller than this in either direction are considered "small" and
213 // are not resampled ever (see below).
214 const int kSmallImageSizeThreshold = 8;
215
216 // The amount an image can be stretched in a single direction before we
217 // say that it is being stretched so much that it must be a line or
218 // background that doesn't need resampling.
219 const float kLargeStretch = 3.0f;
220
221 // Figure out if we should resample this image. We try to prune out some
222 // common cases where resampling won't give us anything, since it is much
223 // slower than drawing stretched.
224 float diffWidth = fabs(destWidth - srcWidth);
225 float diffHeight = fabs(destHeight - srcHeight);
226 bool widthNearlyEqual = diffWidth < std::numeric_limits<float>::epsilon();
227 bool heightNearlyEqual = diffHeight < std::numeric_limits<float>::epsilon();
228 // We don't need to resample if the source and destination are the same.
229 if (widthNearlyEqual && heightNearlyEqual)
230 return InterpolationNone;
231
232 if (srcWidth <= kSmallImageSizeThreshold
233 || srcHeight <= kSmallImageSizeThreshold
234 || destWidth <= kSmallImageSizeThreshold
235 || destHeight <= kSmallImageSizeThreshold) {
236 // Small image detected.
237
238 // Resample in the case where the new size would be non-integral.
239 // This can cause noticeable breaks in repeating patterns, except
240 // when the source image is only one pixel wide in that dimension.
241 if ((!nearlyIntegral(destWidth) && srcWidth > 1 + std::numeric_limits<float>::epsilon())
242 || (!nearlyIntegral(destHeight) && srcHeight > 1 + std::numeric_limits<float>::epsilon()))
243 return InterpolationLow;
244
245 // Otherwise, don't resample small images. These are often used for
246 // borders and rules (think 1x1 images used to make lines).
247 return InterpolationNone;
248 }
249
250 if (srcHeight * kLargeStretch <= destHeight || srcWidth * kLargeStretch <= destWidth) {
251 // Large image detected.
252
253 // Don't resample if it is being stretched a lot in only one direction.
254 // This is trying to catch cases where somebody has created a border
255 // (which might be large) and then is stretching it to fill some part
256 // of the page.
257 if (widthNearlyEqual || heightNearlyEqual)
258 return InterpolationNone;
259
260 // The image is growing a lot and in more than one direction. Resampling
261 // is slow and doesn't give us very much when growing a lot.
262 return InterpolationLow;
263 }
264
265 if ((diffWidth / srcWidth < kFractionalChangeThreshold)
266 && (diffHeight / srcHeight < kFractionalChangeThreshold)) {
267 // It is disappointingly common on the web for image sizes to be off by
268 // one or two pixels. We don't bother resampling if the size difference
269 // is a small fraction of the original size.
270 return InterpolationNone;
271 }
272
273 // When the image is not yet done loading, use linear. We don't cache the
274 // partially resampled images, and as they come in incrementally, it causes
275 // us to have to resample the whole thing every time.
276 if (!isDataComplete)
277 return InterpolationLow;
278
279 // Everything else gets resampled.
280 // High quality interpolation only enabled for scaling and translation.
281 if (!(matrix.getType() & (SkMatrix::kAffine_Mask | SkMatrix::kPerspective_Mask)))
282 return InterpolationHigh;
283
284 return InterpolationLow;
285 }
286
287
shouldDrawAntiAliased(const GraphicsContext * context,const SkRect & destRect)288 bool shouldDrawAntiAliased(const GraphicsContext* context, const SkRect& destRect)
289 {
290 if (!context->shouldAntialias())
291 return false;
292 const SkMatrix totalMatrix = context->getTotalMatrix();
293 // Don't disable anti-aliasing if we're rotated or skewed.
294 if (!totalMatrix.rectStaysRect())
295 return true;
296 // Disable anti-aliasing for scales or n*90 degree rotations.
297 // Allow to opt out of the optimization though for "hairline" geometry
298 // images - using the shouldAntialiasHairlineImages() GraphicsContext flag.
299 if (!context->shouldAntialiasHairlineImages())
300 return false;
301 // Check if the dimensions of the destination are "small" (less than one
302 // device pixel). To prevent sudden drop-outs. Since we know that
303 // kRectStaysRect_Mask is set, the matrix either has scale and no skew or
304 // vice versa. We can query the kAffine_Mask flag to determine which case
305 // it is.
306 // FIXME: This queries the CTM while drawing, which is generally
307 // discouraged. Always drawing with AA can negatively impact performance
308 // though - that's why it's not always on.
309 SkScalar widthExpansion, heightExpansion;
310 if (totalMatrix.getType() & SkMatrix::kAffine_Mask)
311 widthExpansion = totalMatrix[SkMatrix::kMSkewY], heightExpansion = totalMatrix[SkMatrix::kMSkewX];
312 else
313 widthExpansion = totalMatrix[SkMatrix::kMScaleX], heightExpansion = totalMatrix[SkMatrix::kMScaleY];
314 return destRect.width() * fabs(widthExpansion) < 1 || destRect.height() * fabs(heightExpansion) < 1;
315 }
316
317 } // namespace blink
318