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 "platform/graphics/skia/NativeImageSkia.h" 33 34 #include "platform/PlatformInstrumentation.h" 35 #include "platform/TraceEvent.h" 36 #include "platform/geometry/FloatPoint.h" 37 #include "platform/geometry/FloatRect.h" 38 #include "platform/geometry/FloatSize.h" 39 #include "platform/graphics/GraphicsContext.h" 40 #include "platform/graphics/Image.h" 41 #include "platform/graphics/DeferredImageDecoder.h" 42 #include "platform/graphics/skia/SkiaUtils.h" 43 #include "skia/ext/image_operations.h" 44 #include "third_party/skia/include/core/SkMatrix.h" 45 #include "third_party/skia/include/core/SkPaint.h" 46 #include "third_party/skia/include/core/SkScalar.h" 47 #include "third_party/skia/include/core/SkShader.h" 48 49 #include <algorithm> 50 #include <math.h> 51 #include <limits> 52 53 namespace WebCore { 54 55 static bool nearlyIntegral(float value) 56 { 57 return fabs(value - floorf(value)) < std::numeric_limits<float>::epsilon(); 58 } 59 60 InterpolationQuality NativeImageSkia::computeInterpolationQuality(const SkMatrix& matrix, float srcWidth, float srcHeight, float destWidth, float destHeight) const 61 { 62 // The percent change below which we will not resample. This usually means 63 // an off-by-one error on the web page, and just doing nearest neighbor 64 // sampling is usually good enough. 65 const float kFractionalChangeThreshold = 0.025f; 66 67 // Images smaller than this in either direction are considered "small" and 68 // are not resampled ever (see below). 69 const int kSmallImageSizeThreshold = 8; 70 71 // The amount an image can be stretched in a single direction before we 72 // say that it is being stretched so much that it must be a line or 73 // background that doesn't need resampling. 74 const float kLargeStretch = 3.0f; 75 76 // Figure out if we should resample this image. We try to prune out some 77 // common cases where resampling won't give us anything, since it is much 78 // slower than drawing stretched. 79 float diffWidth = fabs(destWidth - srcWidth); 80 float diffHeight = fabs(destHeight - srcHeight); 81 bool widthNearlyEqual = diffWidth < std::numeric_limits<float>::epsilon(); 82 bool heightNearlyEqual = diffHeight < std::numeric_limits<float>::epsilon(); 83 // We don't need to resample if the source and destination are the same. 84 if (widthNearlyEqual && heightNearlyEqual) 85 return InterpolationNone; 86 87 if (srcWidth <= kSmallImageSizeThreshold 88 || srcHeight <= kSmallImageSizeThreshold 89 || destWidth <= kSmallImageSizeThreshold 90 || destHeight <= kSmallImageSizeThreshold) { 91 // Small image detected. 92 93 // Resample in the case where the new size would be non-integral. 94 // This can cause noticeable breaks in repeating patterns, except 95 // when the source image is only one pixel wide in that dimension. 96 if ((!nearlyIntegral(destWidth) && srcWidth > 1 + std::numeric_limits<float>::epsilon()) 97 || (!nearlyIntegral(destHeight) && srcHeight > 1 + std::numeric_limits<float>::epsilon())) 98 return InterpolationLow; 99 100 // Otherwise, don't resample small images. These are often used for 101 // borders and rules (think 1x1 images used to make lines). 102 return InterpolationNone; 103 } 104 105 if (srcHeight * kLargeStretch <= destHeight || srcWidth * kLargeStretch <= destWidth) { 106 // Large image detected. 107 108 // Don't resample if it is being stretched a lot in only one direction. 109 // This is trying to catch cases where somebody has created a border 110 // (which might be large) and then is stretching it to fill some part 111 // of the page. 112 if (widthNearlyEqual || heightNearlyEqual) 113 return InterpolationNone; 114 115 // The image is growing a lot and in more than one direction. Resampling 116 // is slow and doesn't give us very much when growing a lot. 117 return InterpolationLow; 118 } 119 120 if ((diffWidth / srcWidth < kFractionalChangeThreshold) 121 && (diffHeight / srcHeight < kFractionalChangeThreshold)) { 122 // It is disappointingly common on the web for image sizes to be off by 123 // one or two pixels. We don't bother resampling if the size difference 124 // is a small fraction of the original size. 125 return InterpolationNone; 126 } 127 128 // When the image is not yet done loading, use linear. We don't cache the 129 // partially resampled images, and as they come in incrementally, it causes 130 // us to have to resample the whole thing every time. 131 if (!isDataComplete()) 132 return InterpolationLow; 133 134 // Everything else gets resampled. 135 // High quality interpolation only enabled for scaling and translation. 136 if (!(matrix.getType() & (SkMatrix::kAffine_Mask | SkMatrix::kPerspective_Mask))) 137 return InterpolationHigh; 138 139 return InterpolationLow; 140 } 141 142 static InterpolationQuality limitInterpolationQuality(GraphicsContext* context, InterpolationQuality resampling) 143 { 144 return std::min(resampling, context->imageInterpolationQuality()); 145 } 146 147 static SkPaint::FilterLevel convertToSkiaFilterLevel(bool useBicubicFilter, InterpolationQuality resampling) 148 { 149 // FIXME: If we get rid of this special case, this function can go away entirely. 150 if (useBicubicFilter) 151 return SkPaint::kHigh_FilterLevel; 152 153 // InterpolationHigh if useBicubicFilter is false means that we do 154 // a manual high quality resampling before drawing to Skia. 155 if (resampling == InterpolationHigh) 156 return SkPaint::kNone_FilterLevel; 157 158 return static_cast<SkPaint::FilterLevel>(resampling); 159 } 160 161 // This function is used to scale an image and extract a scaled fragment. 162 // 163 // ALGORITHM 164 // 165 // Because the scaled image size has to be integers, we approximate the real 166 // scale with the following formula (only X direction is shown): 167 // 168 // scaledImageWidth = round(scaleX * imageRect.width()) 169 // approximateScaleX = scaledImageWidth / imageRect.width() 170 // 171 // With this method we maintain a constant scale factor among fragments in 172 // the scaled image. This allows fragments to stitch together to form the 173 // full scaled image. The downside is there will be a small difference 174 // between |scaleX| and |approximateScaleX|. 175 // 176 // A scaled image fragment is identified by: 177 // 178 // - Scaled image size 179 // - Scaled image fragment rectangle (IntRect) 180 // 181 // Scaled image size has been determined and the next step is to compute the 182 // rectangle for the scaled image fragment which needs to be an IntRect. 183 // 184 // scaledSrcRect = srcRect * (approximateScaleX, approximateScaleY) 185 // enclosingScaledSrcRect = enclosingIntRect(scaledSrcRect) 186 // 187 // Finally we extract the scaled image fragment using 188 // (scaledImageSize, enclosingScaledSrcRect). 189 // 190 SkBitmap NativeImageSkia::extractScaledImageFragment(const SkRect& srcRect, float scaleX, float scaleY, SkRect* scaledSrcRect) const 191 { 192 SkISize imageSize = SkISize::Make(bitmap().width(), bitmap().height()); 193 SkISize scaledImageSize = SkISize::Make(clampToInteger(roundf(imageSize.width() * scaleX)), 194 clampToInteger(roundf(imageSize.height() * scaleY))); 195 196 SkRect imageRect = SkRect::MakeWH(imageSize.width(), imageSize.height()); 197 SkRect scaledImageRect = SkRect::MakeWH(scaledImageSize.width(), scaledImageSize.height()); 198 199 SkMatrix scaleTransform; 200 scaleTransform.setRectToRect(imageRect, scaledImageRect, SkMatrix::kFill_ScaleToFit); 201 scaleTransform.mapRect(scaledSrcRect, srcRect); 202 203 scaledSrcRect->intersect(scaledImageRect); 204 SkIRect enclosingScaledSrcRect = enclosingIntRect(*scaledSrcRect); 205 206 // |enclosingScaledSrcRect| can be larger than |scaledImageSize| because 207 // of float inaccuracy so clip to get inside. 208 enclosingScaledSrcRect.intersect(SkIRect::MakeSize(scaledImageSize)); 209 210 // scaledSrcRect is relative to the pixel snapped fragment we're extracting. 211 scaledSrcRect->offset(-enclosingScaledSrcRect.x(), -enclosingScaledSrcRect.y()); 212 213 return resizedBitmap(scaledImageSize, enclosingScaledSrcRect); 214 } 215 216 // This does a lot of computation to resample only the portion of the bitmap 217 // that will only be drawn. This is critical for performance since when we are 218 // scrolling, for example, we are only drawing a small strip of the image. 219 // Resampling the whole image every time is very slow, so this speeds up things 220 // dramatically. 221 // 222 // Note: this code is only used when the canvas transformation is limited to 223 // scaling or translation. 224 void NativeImageSkia::drawResampledBitmap(GraphicsContext* context, SkPaint& paint, const SkRect& srcRect, const SkRect& destRect) const 225 { 226 TRACE_EVENT0("skia", "drawResampledBitmap"); 227 if (context->paintingDisabled()) 228 return; 229 // We want to scale |destRect| with transformation in the canvas to obtain 230 // the final scale. The final scale is a combination of scale transform 231 // in canvas and explicit scaling (srcRect and destRect). 232 SkRect screenRect; 233 context->getTotalMatrix().mapRect(&screenRect, destRect); 234 float realScaleX = screenRect.width() / srcRect.width(); 235 float realScaleY = screenRect.height() / srcRect.height(); 236 237 // This part of code limits scaling only to visible portion in the 238 SkRect destRectVisibleSubset; 239 if (!context->canvas()->getClipBounds(&destRectVisibleSubset)) 240 return; 241 242 // ClipRectToCanvas often overshoots, resulting in a larger region than our 243 // original destRect. Intersecting gets us back inside. 244 if (!destRectVisibleSubset.intersect(destRect)) 245 return; // Nothing visible in destRect. 246 247 // Find the corresponding rect in the source image. 248 SkMatrix destToSrcTransform; 249 SkRect srcRectVisibleSubset; 250 destToSrcTransform.setRectToRect(destRect, srcRect, SkMatrix::kFill_ScaleToFit); 251 destToSrcTransform.mapRect(&srcRectVisibleSubset, destRectVisibleSubset); 252 253 SkRect scaledSrcRect; 254 SkBitmap scaledImageFragment = extractScaledImageFragment(srcRectVisibleSubset, realScaleX, realScaleY, &scaledSrcRect); 255 256 context->drawBitmapRect(scaledImageFragment, &scaledSrcRect, destRectVisibleSubset, &paint); 257 } 258 259 NativeImageSkia::NativeImageSkia() 260 : m_resizeRequests(0) 261 { 262 } 263 264 NativeImageSkia::NativeImageSkia(const SkBitmap& other) 265 : m_image(other) 266 , m_resizeRequests(0) 267 { 268 } 269 270 NativeImageSkia::NativeImageSkia(const SkBitmap& image, const SkBitmap& resizedImage, const ImageResourceInfo& cachedImageInfo, int resizeRequests) 271 : m_image(image) 272 , m_resizedImage(resizedImage) 273 , m_cachedImageInfo(cachedImageInfo) 274 , m_resizeRequests(resizeRequests) 275 { 276 } 277 278 NativeImageSkia::~NativeImageSkia() 279 { 280 } 281 282 int NativeImageSkia::decodedSize() const 283 { 284 return m_image.getSize() + m_resizedImage.getSize(); 285 } 286 287 bool NativeImageSkia::hasResizedBitmap(const SkISize& scaledImageSize, const SkIRect& scaledImageSubset) const 288 { 289 bool imageScaleEqual = m_cachedImageInfo.scaledImageSize == scaledImageSize; 290 bool scaledImageSubsetAvailable = m_cachedImageInfo.scaledImageSubset.contains(scaledImageSubset); 291 return imageScaleEqual && scaledImageSubsetAvailable && !m_resizedImage.empty(); 292 } 293 294 SkBitmap NativeImageSkia::resizedBitmap(const SkISize& scaledImageSize, const SkIRect& scaledImageSubset) const 295 { 296 ASSERT(!DeferredImageDecoder::isLazyDecoded(m_image)); 297 298 if (!hasResizedBitmap(scaledImageSize, scaledImageSubset)) { 299 bool shouldCache = isDataComplete() 300 && shouldCacheResampling(scaledImageSize, scaledImageSubset); 301 302 TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "ResizeImage", "cached", shouldCache); 303 // FIXME(361045): remove InspectorInstrumentation calls once DevTools Timeline migrates to tracing. 304 PlatformInstrumentation::willResizeImage(shouldCache); 305 SkBitmap resizedImage = skia::ImageOperations::Resize(m_image, skia::ImageOperations::RESIZE_LANCZOS3, scaledImageSize.width(), scaledImageSize.height(), scaledImageSubset); 306 resizedImage.setImmutable(); 307 PlatformInstrumentation::didResizeImage(); 308 309 if (!shouldCache) 310 return resizedImage; 311 312 m_resizedImage = resizedImage; 313 } 314 315 SkBitmap resizedSubset; 316 SkIRect resizedSubsetRect = m_cachedImageInfo.rectInSubset(scaledImageSubset); 317 m_resizedImage.extractSubset(&resizedSubset, resizedSubsetRect); 318 return resizedSubset; 319 } 320 321 static bool shouldDrawAntiAliased(GraphicsContext* context, const SkRect& destRect) 322 { 323 if (!context->shouldAntialias()) 324 return false; 325 const SkMatrix totalMatrix = context->getTotalMatrix(); 326 // Don't disable anti-aliasing if we're rotated or skewed. 327 if (!totalMatrix.rectStaysRect()) 328 return true; 329 // Disable anti-aliasing for scales or n*90 degree rotations. 330 // Allow to opt out of the optimization though for "hairline" geometry 331 // images - using the shouldAntialiasHairlineImages() GraphicsContext flag. 332 if (!context->shouldAntialiasHairlineImages()) 333 return false; 334 // Check if the dimensions of the destination are "small" (less than one 335 // device pixel). To prevent sudden drop-outs. Since we know that 336 // kRectStaysRect_Mask is set, the matrix either has scale and no skew or 337 // vice versa. We can query the kAffine_Mask flag to determine which case 338 // it is. 339 // FIXME: This queries the CTM while drawing, which is generally 340 // discouraged. Always drawing with AA can negatively impact performance 341 // though - that's why it's not always on. 342 SkScalar widthExpansion, heightExpansion; 343 if (totalMatrix.getType() & SkMatrix::kAffine_Mask) 344 widthExpansion = totalMatrix[SkMatrix::kMSkewY], heightExpansion = totalMatrix[SkMatrix::kMSkewX]; 345 else 346 widthExpansion = totalMatrix[SkMatrix::kMScaleX], heightExpansion = totalMatrix[SkMatrix::kMScaleY]; 347 return destRect.width() * fabs(widthExpansion) < 1 || destRect.height() * fabs(heightExpansion) < 1; 348 } 349 350 void NativeImageSkia::draw(GraphicsContext* context, const SkRect& srcRect, const SkRect& destRect, PassRefPtr<SkXfermode> compOp) const 351 { 352 TRACE_EVENT0("skia", "NativeImageSkia::draw"); 353 SkPaint paint; 354 paint.setXfermode(compOp.get()); 355 paint.setColorFilter(context->colorFilter()); 356 paint.setAlpha(context->getNormalizedAlpha()); 357 paint.setLooper(context->drawLooper()); 358 paint.setAntiAlias(shouldDrawAntiAliased(context, destRect)); 359 360 bool isLazyDecoded = DeferredImageDecoder::isLazyDecoded(bitmap()); 361 362 InterpolationQuality resampling; 363 if (context->isAccelerated()) { 364 resampling = InterpolationLow; 365 } else if (context->printing()) { 366 resampling = InterpolationNone; 367 } else if (isLazyDecoded) { 368 resampling = InterpolationHigh; 369 } else { 370 // Take into account scale applied to the canvas when computing sampling mode (e.g. CSS scale or page scale). 371 SkRect destRectTarget = destRect; 372 SkMatrix totalMatrix = context->getTotalMatrix(); 373 if (!(totalMatrix.getType() & (SkMatrix::kAffine_Mask | SkMatrix::kPerspective_Mask))) 374 totalMatrix.mapRect(&destRectTarget, destRect); 375 376 resampling = computeInterpolationQuality(totalMatrix, 377 SkScalarToFloat(srcRect.width()), SkScalarToFloat(srcRect.height()), 378 SkScalarToFloat(destRectTarget.width()), SkScalarToFloat(destRectTarget.height())); 379 } 380 381 if (resampling == InterpolationNone) { 382 // FIXME: This is to not break tests (it results in the filter bitmap flag 383 // being set to true). We need to decide if we respect InterpolationNone 384 // being returned from computeInterpolationQuality. 385 resampling = InterpolationLow; 386 } 387 resampling = limitInterpolationQuality(context, resampling); 388 389 // FIXME: Bicubic filtering in Skia is only applied to defer-decoded images 390 // as an experiment. Once this filtering code path becomes stable we should 391 // turn this on for all cases, including non-defer-decoded images. 392 bool useBicubicFilter = resampling == InterpolationHigh && isLazyDecoded; 393 394 paint.setFilterLevel(convertToSkiaFilterLevel(useBicubicFilter, resampling)); 395 396 if (resampling == InterpolationHigh && !useBicubicFilter) { 397 // Resample the image and then draw the result to canvas with bilinear 398 // filtering. 399 drawResampledBitmap(context, paint, srcRect, destRect); 400 } else { 401 // We want to filter it if we decided to do interpolation above, or if 402 // there is something interesting going on with the matrix (like a rotation). 403 // Note: for serialization, we will want to subset the bitmap first so we 404 // don't send extra pixels. 405 context->drawBitmapRect(bitmap(), &srcRect, destRect, &paint); 406 } 407 if (isLazyDecoded) 408 PlatformInstrumentation::didDrawLazyPixelRef(bitmap().getGenerationID()); 409 context->didDrawRect(destRect, paint, &bitmap()); 410 } 411 412 static SkBitmap createBitmapWithSpace(const SkBitmap& bitmap, int spaceWidth, int spaceHeight) 413 { 414 SkImageInfo info = bitmap.info(); 415 info.fWidth += spaceWidth; 416 info.fHeight += spaceHeight; 417 info.fAlphaType = kPremul_SkAlphaType; 418 419 SkBitmap result; 420 result.allocPixels(info); 421 result.eraseColor(SK_ColorTRANSPARENT); 422 bitmap.copyPixelsTo(reinterpret_cast<uint8_t*>(result.getPixels()), result.rowBytes() * result.height(), result.rowBytes()); 423 424 return result; 425 } 426 427 void NativeImageSkia::drawPattern( 428 GraphicsContext* context, 429 const FloatRect& floatSrcRect, 430 const FloatSize& scale, 431 const FloatPoint& phase, 432 CompositeOperator compositeOp, 433 const FloatRect& destRect, 434 blink::WebBlendMode blendMode, 435 const IntSize& repeatSpacing) const 436 { 437 FloatRect normSrcRect = floatSrcRect; 438 normSrcRect.intersect(FloatRect(0, 0, bitmap().width(), bitmap().height())); 439 if (destRect.isEmpty() || normSrcRect.isEmpty()) 440 return; // nothing to draw 441 442 SkMatrix totalMatrix = context->getTotalMatrix(); 443 AffineTransform ctm = context->getCTM(); 444 SkScalar ctmScaleX = ctm.xScale(); 445 SkScalar ctmScaleY = ctm.yScale(); 446 totalMatrix.preScale(scale.width(), scale.height()); 447 448 // Figure out what size the bitmap will be in the destination. The 449 // destination rect is the bounds of the pattern, we need to use the 450 // matrix to see how big it will be. 451 SkRect destRectTarget; 452 totalMatrix.mapRect(&destRectTarget, normSrcRect); 453 454 float destBitmapWidth = SkScalarToFloat(destRectTarget.width()); 455 float destBitmapHeight = SkScalarToFloat(destRectTarget.height()); 456 457 bool isLazyDecoded = DeferredImageDecoder::isLazyDecoded(bitmap()); 458 459 // Compute the resampling mode. 460 InterpolationQuality resampling; 461 if (context->isAccelerated() || context->printing()) 462 resampling = InterpolationLow; 463 else if (isLazyDecoded) 464 resampling = InterpolationHigh; 465 else 466 resampling = computeInterpolationQuality(totalMatrix, normSrcRect.width(), normSrcRect.height(), destBitmapWidth, destBitmapHeight); 467 resampling = limitInterpolationQuality(context, resampling); 468 469 SkMatrix localMatrix; 470 // We also need to translate it such that the origin of the pattern is the 471 // origin of the destination rect, which is what WebKit expects. Skia uses 472 // the coordinate system origin as the base for the pattern. If WebKit wants 473 // a shifted image, it will shift it from there using the localMatrix. 474 const float adjustedX = phase.x() + normSrcRect.x() * scale.width(); 475 const float adjustedY = phase.y() + normSrcRect.y() * scale.height(); 476 localMatrix.setTranslate(SkFloatToScalar(adjustedX), SkFloatToScalar(adjustedY)); 477 478 RefPtr<SkShader> shader; 479 480 // Bicubic filter is only applied to defer-decoded images, see 481 // NativeImageSkia::draw for details. 482 bool useBicubicFilter = resampling == InterpolationHigh && isLazyDecoded; 483 484 if (resampling == InterpolationHigh && !useBicubicFilter) { 485 // Do nice resampling. 486 float scaleX = destBitmapWidth / normSrcRect.width(); 487 float scaleY = destBitmapHeight / normSrcRect.height(); 488 SkRect scaledSrcRect; 489 490 // Since we are resizing the bitmap, we need to remove the scale 491 // applied to the pixels in the bitmap shader. This means we need 492 // CTM * localMatrix to have identity scale. Since we 493 // can't modify CTM (or the rectangle will be drawn in the wrong 494 // place), we must set localMatrix's scale to the inverse of 495 // CTM scale. 496 localMatrix.preScale(ctmScaleX ? 1 / ctmScaleX : 1, ctmScaleY ? 1 / ctmScaleY : 1); 497 498 // The image fragment generated here is not exactly what is 499 // requested. The scale factor used is approximated and image 500 // fragment is slightly larger to align to integer 501 // boundaries. 502 SkBitmap resampled = extractScaledImageFragment(normSrcRect, scaleX, scaleY, &scaledSrcRect); 503 if (repeatSpacing.isZero()) { 504 shader = adoptRef(SkShader::CreateBitmapShader(resampled, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &localMatrix)); 505 } else { 506 shader = adoptRef(SkShader::CreateBitmapShader( 507 createBitmapWithSpace(resampled, repeatSpacing.width() * ctmScaleX, repeatSpacing.height() * ctmScaleY), 508 SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &localMatrix)); 509 } 510 } else { 511 // Because no resizing occurred, the shader transform should be 512 // set to the pattern's transform, which just includes scale. 513 localMatrix.preScale(scale.width(), scale.height()); 514 515 // No need to resample before drawing. 516 SkBitmap srcSubset; 517 bitmap().extractSubset(&srcSubset, enclosingIntRect(normSrcRect)); 518 if (repeatSpacing.isZero()) { 519 shader = adoptRef(SkShader::CreateBitmapShader(srcSubset, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &localMatrix)); 520 } else { 521 shader = adoptRef(SkShader::CreateBitmapShader( 522 createBitmapWithSpace(srcSubset, repeatSpacing.width() * ctmScaleX, repeatSpacing.height() * ctmScaleY), 523 SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &localMatrix)); 524 } 525 } 526 527 SkPaint paint; 528 paint.setShader(shader.get()); 529 paint.setXfermode(WebCoreCompositeToSkiaComposite(compositeOp, blendMode).get()); 530 paint.setColorFilter(context->colorFilter()); 531 paint.setFilterLevel(convertToSkiaFilterLevel(useBicubicFilter, resampling)); 532 533 if (isLazyDecoded) 534 PlatformInstrumentation::didDrawLazyPixelRef(bitmap().getGenerationID()); 535 536 context->drawRect(destRect, paint); 537 } 538 539 bool NativeImageSkia::shouldCacheResampling(const SkISize& scaledImageSize, const SkIRect& scaledImageSubset) const 540 { 541 // Check whether the requested dimensions match previous request. 542 bool matchesPreviousRequest = m_cachedImageInfo.isEqual(scaledImageSize, scaledImageSubset); 543 if (matchesPreviousRequest) 544 ++m_resizeRequests; 545 else { 546 m_cachedImageInfo.set(scaledImageSize, scaledImageSubset); 547 m_resizeRequests = 0; 548 // Reset m_resizedImage now, because we don't distinguish 549 // between the last requested resize info and m_resizedImage's 550 // resize info. 551 m_resizedImage.reset(); 552 } 553 554 // We can not cache incomplete frames. This might be a good optimization in 555 // the future, were we know how much of the frame has been decoded, so when 556 // we incrementally draw more of the image, we only have to resample the 557 // parts that are changed. 558 if (!isDataComplete()) 559 return false; 560 561 // If the destination bitmap is excessively large, we'll never allow caching. 562 static const unsigned long long kLargeBitmapSize = 4096ULL * 4096ULL; 563 unsigned long long fullSize = static_cast<unsigned long long>(scaledImageSize.width()) * static_cast<unsigned long long>(scaledImageSize.height()); 564 unsigned long long fragmentSize = static_cast<unsigned long long>(scaledImageSubset.width()) * static_cast<unsigned long long>(scaledImageSubset.height()); 565 566 if (fragmentSize > kLargeBitmapSize) 567 return false; 568 569 // If the destination bitmap is small, we'll always allow caching, since 570 // there is not very much penalty for computing it and it may come in handy. 571 static const unsigned kSmallBitmapSize = 4096; 572 if (fragmentSize <= kSmallBitmapSize) 573 return true; 574 575 // If "too many" requests have been made for this bitmap, we assume that 576 // many more will be made as well, and we'll go ahead and cache it. 577 static const int kManyRequestThreshold = 4; 578 if (m_resizeRequests >= kManyRequestThreshold) 579 return true; 580 581 // If more than 1/4 of the resized image is requested, it's worth caching. 582 return fragmentSize > fullSize / 4; 583 } 584 585 NativeImageSkia::ImageResourceInfo::ImageResourceInfo() 586 { 587 scaledImageSize.setEmpty(); 588 scaledImageSubset.setEmpty(); 589 } 590 591 bool NativeImageSkia::ImageResourceInfo::isEqual(const SkISize& otherScaledImageSize, const SkIRect& otherScaledImageSubset) const 592 { 593 return scaledImageSize == otherScaledImageSize && scaledImageSubset == otherScaledImageSubset; 594 } 595 596 void NativeImageSkia::ImageResourceInfo::set(const SkISize& otherScaledImageSize, const SkIRect& otherScaledImageSubset) 597 { 598 scaledImageSize = otherScaledImageSize; 599 scaledImageSubset = otherScaledImageSubset; 600 } 601 602 SkIRect NativeImageSkia::ImageResourceInfo::rectInSubset(const SkIRect& otherScaledImageSubset) 603 { 604 if (!scaledImageSubset.contains(otherScaledImageSubset)) 605 return SkIRect::MakeEmpty(); 606 SkIRect subsetRect = otherScaledImageSubset; 607 subsetRect.offset(-scaledImageSubset.x(), -scaledImageSubset.y()); 608 return subsetRect; 609 } 610 611 } // namespace WebCore 612