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