| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright (c) 2008, Google Inc. All rights reserved. | 2 * Copyright (c) 2008, Google Inc. All rights reserved. |
| 3 * | 3 * |
| 4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
| 5 * modification, are permitted provided that the following conditions are | 5 * modification, are permitted provided that the following conditions are |
| 6 * met: | 6 * met: |
| 7 * | 7 * |
| 8 * * Redistributions of source code must retain the above copyright | 8 * * Redistributions of source code must retain the above copyright |
| 9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
| 10 * * Redistributions in binary form must reproduce the above | 10 * * Redistributions in binary form must reproduce the above |
| (...skipping 28 matching lines...) Expand all Loading... |
| 39 #include "platform/graphics/GraphicsContext.h" | 39 #include "platform/graphics/GraphicsContext.h" |
| 40 #include "platform/graphics/Image.h" | 40 #include "platform/graphics/Image.h" |
| 41 #include "platform/graphics/DeferredImageDecoder.h" | 41 #include "platform/graphics/DeferredImageDecoder.h" |
| 42 #include "platform/graphics/skia/SkiaUtils.h" | 42 #include "platform/graphics/skia/SkiaUtils.h" |
| 43 #include "skia/ext/image_operations.h" | 43 #include "skia/ext/image_operations.h" |
| 44 #include "third_party/skia/include/core/SkMatrix.h" | 44 #include "third_party/skia/include/core/SkMatrix.h" |
| 45 #include "third_party/skia/include/core/SkPaint.h" | 45 #include "third_party/skia/include/core/SkPaint.h" |
| 46 #include "third_party/skia/include/core/SkScalar.h" | 46 #include "third_party/skia/include/core/SkScalar.h" |
| 47 #include "third_party/skia/include/core/SkShader.h" | 47 #include "third_party/skia/include/core/SkShader.h" |
| 48 | 48 |
| 49 #include <algorithm> | |
| 50 #include <math.h> | 49 #include <math.h> |
| 51 #include <limits> | |
| 52 | 50 |
| 53 namespace blink { | 51 namespace blink { |
| 54 | 52 |
| 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) co
nst | |
| 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<fl
oat>::epsilon()) | |
| 97 || (!nearlyIntegral(destHeight) && srcHeight > 1 + std::numeric_limi
ts<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 <= d
estWidth) { | |
| 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_Ma
sk))) | |
| 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, Inte
rpolationQuality resampling) | |
| 148 { | |
| 149 // FIXME: If we get rid of this special case, this function can go away enti
rely. | |
| 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. | 53 // This function is used to scale an image and extract a scaled fragment. |
| 162 // | 54 // |
| 163 // ALGORITHM | 55 // ALGORITHM |
| 164 // | 56 // |
| 165 // Because the scaled image size has to be integers, we approximate the real | 57 // Because the scaled image size has to be integers, we approximate the real |
| 166 // scale with the following formula (only X direction is shown): | 58 // scale with the following formula (only X direction is shown): |
| 167 // | 59 // |
| 168 // scaledImageWidth = round(scaleX * imageRect.width()) | 60 // scaledImageWidth = round(scaleX * imageRect.width()) |
| 169 // approximateScaleX = scaledImageWidth / imageRect.width() | 61 // approximateScaleX = scaledImageWidth / imageRect.width() |
| 170 // | 62 // |
| (...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 268 | 160 |
| 269 m_resizedImage = resizedImage; | 161 m_resizedImage = resizedImage; |
| 270 } | 162 } |
| 271 | 163 |
| 272 SkBitmap resizedSubset; | 164 SkBitmap resizedSubset; |
| 273 SkIRect resizedSubsetRect = m_cachedImageInfo.rectInSubset(scaledImageSubset
); | 165 SkIRect resizedSubsetRect = m_cachedImageInfo.rectInSubset(scaledImageSubset
); |
| 274 m_resizedImage.extractSubset(&resizedSubset, resizedSubsetRect); | 166 m_resizedImage.extractSubset(&resizedSubset, resizedSubsetRect); |
| 275 return resizedSubset; | 167 return resizedSubset; |
| 276 } | 168 } |
| 277 | 169 |
| 278 static bool shouldDrawAntiAliased(GraphicsContext* context, const SkRect& destRe
ct) | 170 void NativeImageSkia::draw( |
| 279 { | 171 GraphicsContext* context, |
| 280 if (!context->shouldAntialias()) | 172 const SkRect& srcRect, |
| 281 return false; | 173 const SkRect& destRect, |
| 282 const SkMatrix totalMatrix = context->getTotalMatrix(); | 174 CompositeOperator compositeOp, |
| 283 // Don't disable anti-aliasing if we're rotated or skewed. | 175 blink::WebBlendMode blendMode) const |
| 284 if (!totalMatrix.rectStaysRect()) | |
| 285 return true; | |
| 286 // Disable anti-aliasing for scales or n*90 degree rotations. | |
| 287 // Allow to opt out of the optimization though for "hairline" geometry | |
| 288 // images - using the shouldAntialiasHairlineImages() GraphicsContext flag. | |
| 289 if (!context->shouldAntialiasHairlineImages()) | |
| 290 return false; | |
| 291 // Check if the dimensions of the destination are "small" (less than one | |
| 292 // device pixel). To prevent sudden drop-outs. Since we know that | |
| 293 // kRectStaysRect_Mask is set, the matrix either has scale and no skew or | |
| 294 // vice versa. We can query the kAffine_Mask flag to determine which case | |
| 295 // it is. | |
| 296 // FIXME: This queries the CTM while drawing, which is generally | |
| 297 // discouraged. Always drawing with AA can negatively impact performance | |
| 298 // though - that's why it's not always on. | |
| 299 SkScalar widthExpansion, heightExpansion; | |
| 300 if (totalMatrix.getType() & SkMatrix::kAffine_Mask) | |
| 301 widthExpansion = totalMatrix[SkMatrix::kMSkewY], heightExpansion = total
Matrix[SkMatrix::kMSkewX]; | |
| 302 else | |
| 303 widthExpansion = totalMatrix[SkMatrix::kMScaleX], heightExpansion = tota
lMatrix[SkMatrix::kMScaleY]; | |
| 304 return destRect.width() * fabs(widthExpansion) < 1 || destRect.height() * fa
bs(heightExpansion) < 1; | |
| 305 } | |
| 306 | |
| 307 void NativeImageSkia::draw(GraphicsContext* context, const SkRect& srcRect, cons
t SkRect& destRect, PassRefPtr<SkXfermode> compOp) const | |
| 308 { | 176 { |
| 309 TRACE_EVENT0("skia", "NativeImageSkia::draw"); | 177 TRACE_EVENT0("skia", "NativeImageSkia::draw"); |
| 310 SkPaint paint; | |
| 311 paint.setXfermode(compOp.get()); | |
| 312 paint.setColorFilter(context->colorFilter()); | |
| 313 paint.setAlpha(context->getNormalizedAlpha()); | |
| 314 paint.setLooper(context->drawLooper()); | |
| 315 paint.setAntiAlias(shouldDrawAntiAliased(context, destRect)); | |
| 316 | 178 |
| 317 bool isLazyDecoded = DeferredImageDecoder::isLazyDecoded(bitmap()); | 179 bool isLazyDecoded = DeferredImageDecoder::isLazyDecoded(bitmap()); |
| 318 | 180 |
| 319 InterpolationQuality resampling; | 181 SkPaint paint; |
| 320 if (context->isAccelerated()) { | 182 context->preparePaintForDrawRectToRect(&paint, srcRect, destRect, compositeO
p, blendMode, isLazyDecoded, isDataComplete()); |
| 321 resampling = InterpolationLow; | |
| 322 } else if (context->printing()) { | |
| 323 resampling = InterpolationNone; | |
| 324 } else if (isLazyDecoded) { | |
| 325 resampling = InterpolationHigh; | |
| 326 } else { | |
| 327 // Take into account scale applied to the canvas when computing sampling
mode (e.g. CSS scale or page scale). | |
| 328 SkRect destRectTarget = destRect; | |
| 329 SkMatrix totalMatrix = context->getTotalMatrix(); | |
| 330 if (!(totalMatrix.getType() & (SkMatrix::kAffine_Mask | SkMatrix::kPersp
ective_Mask))) | |
| 331 totalMatrix.mapRect(&destRectTarget, destRect); | |
| 332 | |
| 333 resampling = computeInterpolationQuality(totalMatrix, | |
| 334 SkScalarToFloat(srcRect.width()), SkScalarToFloat(srcRect.height()), | |
| 335 SkScalarToFloat(destRectTarget.width()), SkScalarToFloat(destRectTar
get.height())); | |
| 336 } | |
| 337 | |
| 338 if (resampling == InterpolationNone) { | |
| 339 // FIXME: This is to not break tests (it results in the filter bitmap fl
ag | |
| 340 // being set to true). We need to decide if we respect InterpolationNone | |
| 341 // being returned from computeInterpolationQuality. | |
| 342 resampling = InterpolationLow; | |
| 343 } | |
| 344 resampling = limitInterpolationQuality(context, resampling); | |
| 345 | |
| 346 bool useBicubicFilter = resampling == InterpolationHigh; | |
| 347 paint.setFilterLevel(convertToSkiaFilterLevel(useBicubicFilter, resampling))
; | |
| 348 | |
| 349 // We want to filter it if we decided to do interpolation above, or if | 183 // We want to filter it if we decided to do interpolation above, or if |
| 350 // there is something interesting going on with the matrix (like a rotation)
. | 184 // there is something interesting going on with the matrix (like a rotation)
. |
| 351 // Note: for serialization, we will want to subset the bitmap first so we | 185 // Note: for serialization, we will want to subset the bitmap first so we |
| 352 // don't send extra pixels. | 186 // don't send extra pixels. |
| 353 context->drawBitmapRect(bitmap(), &srcRect, destRect, &paint); | 187 context->drawBitmapRect(bitmap(), &srcRect, destRect, &paint); |
| 354 | 188 |
| 355 if (isLazyDecoded) | 189 if (isLazyDecoded) |
| 356 PlatformInstrumentation::didDrawLazyPixelRef(bitmap().getGenerationID())
; | 190 PlatformInstrumentation::didDrawLazyPixelRef(bitmap().getGenerationID())
; |
| 357 context->didDrawRect(destRect, paint, &bitmap()); | 191 context->didDrawRect(destRect, paint, &bitmap()); |
| 358 } | 192 } |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 404 | 238 |
| 405 bool isLazyDecoded = DeferredImageDecoder::isLazyDecoded(bitmap()); | 239 bool isLazyDecoded = DeferredImageDecoder::isLazyDecoded(bitmap()); |
| 406 | 240 |
| 407 // Compute the resampling mode. | 241 // Compute the resampling mode. |
| 408 InterpolationQuality resampling; | 242 InterpolationQuality resampling; |
| 409 if (context->isAccelerated() || context->printing()) | 243 if (context->isAccelerated() || context->printing()) |
| 410 resampling = InterpolationLow; | 244 resampling = InterpolationLow; |
| 411 else if (isLazyDecoded) | 245 else if (isLazyDecoded) |
| 412 resampling = InterpolationHigh; | 246 resampling = InterpolationHigh; |
| 413 else | 247 else |
| 414 resampling = computeInterpolationQuality(totalMatrix, normSrcRect.width(
), normSrcRect.height(), destBitmapWidth, destBitmapHeight); | 248 resampling = computeInterpolationQuality(totalMatrix, normSrcRect.width(
), normSrcRect.height(), destBitmapWidth, destBitmapHeight, isDataComplete()); |
| 415 resampling = limitInterpolationQuality(context, resampling); | 249 resampling = limitInterpolationQuality(context, resampling); |
| 416 | 250 |
| 417 SkMatrix localMatrix; | 251 SkMatrix localMatrix; |
| 418 // We also need to translate it such that the origin of the pattern is the | 252 // We also need to translate it such that the origin of the pattern is the |
| 419 // origin of the destination rect, which is what WebKit expects. Skia uses | 253 // origin of the destination rect, which is what WebKit expects. Skia uses |
| 420 // the coordinate system origin as the base for the pattern. If WebKit wants | 254 // the coordinate system origin as the base for the pattern. If WebKit wants |
| 421 // a shifted image, it will shift it from there using the localMatrix. | 255 // a shifted image, it will shift it from there using the localMatrix. |
| 422 const float adjustedX = phase.x() + normSrcRect.x() * scale.width(); | 256 const float adjustedX = phase.x() + normSrcRect.x() * scale.width(); |
| 423 const float adjustedY = phase.y() + normSrcRect.y() * scale.height(); | 257 const float adjustedY = phase.y() + normSrcRect.y() * scale.height(); |
| 424 localMatrix.setTranslate(SkFloatToScalar(adjustedX), SkFloatToScalar(adjuste
dY)); | 258 localMatrix.setTranslate(SkFloatToScalar(adjustedX), SkFloatToScalar(adjuste
dY)); |
| (...skipping 125 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 550 SkIRect NativeImageSkia::ImageResourceInfo::rectInSubset(const SkIRect& otherSca
ledImageSubset) | 384 SkIRect NativeImageSkia::ImageResourceInfo::rectInSubset(const SkIRect& otherSca
ledImageSubset) |
| 551 { | 385 { |
| 552 if (!scaledImageSubset.contains(otherScaledImageSubset)) | 386 if (!scaledImageSubset.contains(otherScaledImageSubset)) |
| 553 return SkIRect::MakeEmpty(); | 387 return SkIRect::MakeEmpty(); |
| 554 SkIRect subsetRect = otherScaledImageSubset; | 388 SkIRect subsetRect = otherScaledImageSubset; |
| 555 subsetRect.offset(-scaledImageSubset.x(), -scaledImageSubset.y()); | 389 subsetRect.offset(-scaledImageSubset.x(), -scaledImageSubset.y()); |
| 556 return subsetRect; | 390 return subsetRect; |
| 557 } | 391 } |
| 558 | 392 |
| 559 } // namespace blink | 393 } // namespace blink |
| OLD | NEW |