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 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 100 static U8CPU InvScaleByte(U8CPU component, uint32_t scale) 101 { 102 SkASSERT(component == (uint8_t)component); 103 return (component * scale + 0x8000) >> 16; 104 } 105 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 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 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 189 bool nearlyIntegral(float value) 190 { 191 return fabs(value - floorf(value)) < std::numeric_limits<float>::epsilon(); 192 } 193 194 InterpolationQuality limitInterpolationQuality(const GraphicsContext* context, InterpolationQuality resampling) 195 { 196 return std::min(resampling, context->imageInterpolationQuality()); 197 } 198 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 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