| 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 15 matching lines...) Expand all Loading... |
| 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 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. | 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 29 */ | 29 */ |
| 30 | 30 |
| 31 #include "config.h" | 31 #include "config.h" |
| 32 #include "platform/graphics/skia/NativeImageSkia.h" | 32 #include "platform/graphics/skia/NativeImageSkia.h" |
| 33 | 33 |
| 34 #include "platform/PlatformInstrumentation.h" | 34 #include "platform/PlatformInstrumentation.h" |
| 35 #include "platform/TraceEvent.h" | 35 #include "platform/TraceEvent.h" |
| 36 #include "platform/geometry/FloatPoint.h" | |
| 37 #include "platform/geometry/FloatRect.h" | 36 #include "platform/geometry/FloatRect.h" |
| 38 #include "platform/geometry/FloatSize.h" | |
| 39 #include "platform/graphics/DeferredImageDecoder.h" | 37 #include "platform/graphics/DeferredImageDecoder.h" |
| 40 #include "platform/graphics/GraphicsContext.h" | 38 #include "platform/graphics/GraphicsContext.h" |
| 41 #include "platform/graphics/Image.h" | |
| 42 #include "platform/graphics/skia/SkiaUtils.h" | 39 #include "platform/graphics/skia/SkiaUtils.h" |
| 43 #include "skia/ext/image_operations.h" | 40 #include "platform/transforms/AffineTransform.h" |
| 41 #include "third_party/skia/include/core/SkColor.h" |
| 42 #include "third_party/skia/include/core/SkImageInfo.h" |
| 44 #include "third_party/skia/include/core/SkMatrix.h" | 43 #include "third_party/skia/include/core/SkMatrix.h" |
| 45 #include "third_party/skia/include/core/SkPaint.h" | 44 #include "third_party/skia/include/core/SkPaint.h" |
| 45 #include "third_party/skia/include/core/SkRect.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 <math.h> | |
| 50 | |
| 51 namespace blink { | 49 namespace blink { |
| 52 | 50 |
| 53 // This function is used to scale an image and extract a scaled fragment. | |
| 54 // | |
| 55 // ALGORITHM | |
| 56 // | |
| 57 // Because the scaled image size has to be integers, we approximate the real | |
| 58 // scale with the following formula (only X direction is shown): | |
| 59 // | |
| 60 // scaledImageWidth = round(scaleX * imageRect.width()) | |
| 61 // approximateScaleX = scaledImageWidth / imageRect.width() | |
| 62 // | |
| 63 // With this method we maintain a constant scale factor among fragments in | |
| 64 // the scaled image. This allows fragments to stitch together to form the | |
| 65 // full scaled image. The downside is there will be a small difference | |
| 66 // between |scaleX| and |approximateScaleX|. | |
| 67 // | |
| 68 // A scaled image fragment is identified by: | |
| 69 // | |
| 70 // - Scaled image size | |
| 71 // - Scaled image fragment rectangle (IntRect) | |
| 72 // | |
| 73 // Scaled image size has been determined and the next step is to compute the | |
| 74 // rectangle for the scaled image fragment which needs to be an IntRect. | |
| 75 // | |
| 76 // scaledSrcRect = srcRect * (approximateScaleX, approximateScaleY) | |
| 77 // enclosingScaledSrcRect = enclosingIntRect(scaledSrcRect) | |
| 78 // | |
| 79 // Finally we extract the scaled image fragment using | |
| 80 // (scaledImageSize, enclosingScaledSrcRect). | |
| 81 // | |
| 82 SkBitmap NativeImageSkia::extractScaledImageFragment(const SkRect& srcRect, floa
t scaleX, float scaleY, SkRect* scaledSrcRect) const | |
| 83 { | |
| 84 SkISize imageSize = SkISize::Make(bitmap().width(), bitmap().height()); | |
| 85 SkISize scaledImageSize = SkISize::Make(clampTo<int>(roundf(imageSize.width(
) * scaleX)), | |
| 86 clampTo<int>(roundf(imageSize.height() * scaleY))); | |
| 87 | |
| 88 SkRect imageRect = SkRect::MakeWH(imageSize.width(), imageSize.height()); | |
| 89 SkRect scaledImageRect = SkRect::MakeWH(scaledImageSize.width(), scaledImage
Size.height()); | |
| 90 | |
| 91 SkMatrix scaleTransform; | |
| 92 scaleTransform.setRectToRect(imageRect, scaledImageRect, SkMatrix::kFill_Sca
leToFit); | |
| 93 scaleTransform.mapRect(scaledSrcRect, srcRect); | |
| 94 | |
| 95 scaledSrcRect->intersect(scaledImageRect); | |
| 96 SkIRect enclosingScaledSrcRect = enclosingIntRect(*scaledSrcRect); | |
| 97 | |
| 98 // |enclosingScaledSrcRect| can be larger than |scaledImageSize| because | |
| 99 // of float inaccuracy so clip to get inside. | |
| 100 enclosingScaledSrcRect.intersect(SkIRect::MakeSize(scaledImageSize)); | |
| 101 | |
| 102 // scaledSrcRect is relative to the pixel snapped fragment we're extracting. | |
| 103 scaledSrcRect->offset(-enclosingScaledSrcRect.x(), -enclosingScaledSrcRect.y
()); | |
| 104 | |
| 105 return resizedBitmap(scaledImageSize, enclosingScaledSrcRect); | |
| 106 } | |
| 107 | |
| 108 NativeImageSkia::NativeImageSkia() | |
| 109 : m_resizeRequests(0) | |
| 110 { | |
| 111 } | |
| 112 | |
| 113 NativeImageSkia::NativeImageSkia(const SkBitmap& other) | |
| 114 : m_bitmap(other) | |
| 115 , m_resizeRequests(0) | |
| 116 { | |
| 117 } | |
| 118 | |
| 119 NativeImageSkia::~NativeImageSkia() | |
| 120 { | |
| 121 } | |
| 122 | |
| 123 bool NativeImageSkia::hasResizedBitmap(const SkISize& scaledImageSize, const SkI
Rect& scaledImageSubset) const | |
| 124 { | |
| 125 bool imageScaleEqual = m_cachedImageInfo.scaledImageSize == scaledImageSize; | |
| 126 bool scaledImageSubsetAvailable = m_cachedImageInfo.scaledImageSubset.contai
ns(scaledImageSubset); | |
| 127 return imageScaleEqual && scaledImageSubsetAvailable && !m_resizedImage.empt
y(); | |
| 128 } | |
| 129 | |
| 130 SkBitmap NativeImageSkia::resizedBitmap(const SkISize& scaledImageSize, const Sk
IRect& scaledImageSubset) const | |
| 131 { | |
| 132 ASSERT(!DeferredImageDecoder::isLazyDecoded(bitmap())); | |
| 133 | |
| 134 if (!hasResizedBitmap(scaledImageSize, scaledImageSubset)) { | |
| 135 bool shouldCache = isDataComplete() | |
| 136 && shouldCacheResampling(scaledImageSize, scaledImageSubset); | |
| 137 | |
| 138 TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "ResizeImag
e", "cached", shouldCache); | |
| 139 // FIXME(361045): remove InspectorInstrumentation calls once DevTools Ti
meline migrates to tracing. | |
| 140 PlatformInstrumentation::willResizeImage(shouldCache); | |
| 141 SkBitmap resizedImage = skia::ImageOperations::Resize(bitmap(), skia::Im
ageOperations::RESIZE_LANCZOS3, scaledImageSize.width(), scaledImageSize.height(
), scaledImageSubset); | |
| 142 resizedImage.setImmutable(); | |
| 143 PlatformInstrumentation::didResizeImage(); | |
| 144 | |
| 145 if (!shouldCache) | |
| 146 return resizedImage; | |
| 147 | |
| 148 m_resizedImage = resizedImage; | |
| 149 } | |
| 150 | |
| 151 SkBitmap resizedSubset; | |
| 152 SkIRect resizedSubsetRect = m_cachedImageInfo.rectInSubset(scaledImageSubset
); | |
| 153 m_resizedImage.extractSubset(&resizedSubset, resizedSubsetRect); | |
| 154 return resizedSubset; | |
| 155 } | |
| 156 | |
| 157 void NativeImageSkia::draw( | 51 void NativeImageSkia::draw( |
| 158 GraphicsContext* context, | 52 GraphicsContext* context, |
| 159 const SkRect& srcRect, | 53 const SkRect& srcRect, |
| 160 const SkRect& destRect, | 54 const SkRect& destRect, |
| 161 CompositeOperator compositeOp, | 55 CompositeOperator compositeOp, |
| 162 WebBlendMode blendMode) const | 56 WebBlendMode blendMode) const |
| 163 { | 57 { |
| 164 TRACE_EVENT0("skia", "NativeImageSkia::draw"); | 58 TRACE_EVENT0("skia", "NativeImageSkia::draw"); |
| 165 | 59 |
| 166 bool isLazyDecoded = DeferredImageDecoder::isLazyDecoded(bitmap()); | 60 bool isLazyDecoded = DeferredImageDecoder::isLazyDecoded(bitmap()); |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 228 InterpolationQuality resampling; | 122 InterpolationQuality resampling; |
| 229 if (context->isAccelerated() || context->printing()) | 123 if (context->isAccelerated() || context->printing()) |
| 230 resampling = InterpolationLow; | 124 resampling = InterpolationLow; |
| 231 else if (isLazyDecoded) | 125 else if (isLazyDecoded) |
| 232 resampling = InterpolationHigh; | 126 resampling = InterpolationHigh; |
| 233 else | 127 else |
| 234 resampling = computeInterpolationQuality(totalMatrix, normSrcRect.width(
), normSrcRect.height(), destBitmapWidth, destBitmapHeight, isDataComplete()); | 128 resampling = computeInterpolationQuality(totalMatrix, normSrcRect.width(
), normSrcRect.height(), destBitmapWidth, destBitmapHeight, isDataComplete()); |
| 235 resampling = limitInterpolationQuality(context, resampling); | 129 resampling = limitInterpolationQuality(context, resampling); |
| 236 | 130 |
| 237 SkMatrix localMatrix; | 131 SkMatrix localMatrix; |
| 132 |
| 238 // We also need to translate it such that the origin of the pattern is the | 133 // We also need to translate it such that the origin of the pattern is the |
| 239 // origin of the destination rect, which is what WebKit expects. Skia uses | 134 // origin of the destination rect, which is what WebKit expects. Skia uses |
| 240 // the coordinate system origin as the base for the pattern. If WebKit wants | 135 // the coordinate system origin as the base for the pattern. If WebKit wants |
| 241 // a shifted image, it will shift it from there using the localMatrix. | 136 // a shifted image, it will shift it from there using the localMatrix. |
| 242 const float adjustedX = phase.x() + normSrcRect.x() * scale.width(); | 137 const float adjustedX = phase.x() + normSrcRect.x() * scale.width(); |
| 243 const float adjustedY = phase.y() + normSrcRect.y() * scale.height(); | 138 const float adjustedY = phase.y() + normSrcRect.y() * scale.height(); |
| 244 localMatrix.setTranslate(SkFloatToScalar(adjustedX), SkFloatToScalar(adjuste
dY)); | 139 localMatrix.setTranslate(SkFloatToScalar(adjustedX), SkFloatToScalar(adjuste
dY)); |
| 245 | 140 |
| 246 RefPtr<SkShader> shader; | 141 // Because no resizing occurred, the shader transform should be |
| 247 SkPaint::FilterLevel filterLevel = static_cast<SkPaint::FilterLevel>(resampl
ing); | 142 // set to the pattern's transform, which just includes scale. |
| 143 localMatrix.preScale(scale.width(), scale.height()); |
| 248 | 144 |
| 249 // Bicubic filter is only applied to defer-decoded images, see | 145 SkBitmap bitmapToPaint; |
| 250 // NativeImageSkia::draw for details. | 146 bitmap().extractSubset(&bitmapToPaint, enclosingIntRect(normSrcRect)); |
| 251 if (resampling == InterpolationHigh && !isLazyDecoded) { | 147 if (!repeatSpacing.isZero()) { |
| 252 // Do nice resampling. | 148 bitmapToPaint = createBitmapWithSpace( |
| 253 filterLevel = SkPaint::kNone_FilterLevel; | 149 bitmapToPaint, |
| 254 float scaleX = destBitmapWidth / normSrcRect.width(); | 150 repeatSpacing.width() * ctmScaleX / scale.width(), |
| 255 float scaleY = destBitmapHeight / normSrcRect.height(); | 151 repeatSpacing.height() * ctmScaleY / scale.height()); |
| 256 SkRect scaledSrcRect; | |
| 257 | |
| 258 // Since we are resizing the bitmap, we need to remove the scale | |
| 259 // applied to the pixels in the bitmap shader. This means we need | |
| 260 // CTM * localMatrix to have identity scale. Since we | |
| 261 // can't modify CTM (or the rectangle will be drawn in the wrong | |
| 262 // place), we must set localMatrix's scale to the inverse of | |
| 263 // CTM scale. | |
| 264 localMatrix.preScale(ctmScaleX ? 1 / ctmScaleX : 1, ctmScaleY ? 1 / ctmS
caleY : 1); | |
| 265 | |
| 266 // The image fragment generated here is not exactly what is | |
| 267 // requested. The scale factor used is approximated and image | |
| 268 // fragment is slightly larger to align to integer | |
| 269 // boundaries. | |
| 270 SkBitmap resampled = extractScaledImageFragment(normSrcRect, scaleX, sca
leY, &scaledSrcRect); | |
| 271 if (repeatSpacing.isZero()) { | |
| 272 shader = adoptRef(SkShader::CreateBitmapShader(resampled, SkShader::
kRepeat_TileMode, SkShader::kRepeat_TileMode, &localMatrix)); | |
| 273 } else { | |
| 274 shader = adoptRef(SkShader::CreateBitmapShader( | |
| 275 createBitmapWithSpace(resampled, repeatSpacing.width() * ctmScal
eX, repeatSpacing.height() * ctmScaleY), | |
| 276 SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &localMa
trix)); | |
| 277 } | |
| 278 } else { | |
| 279 // Because no resizing occurred, the shader transform should be | |
| 280 // set to the pattern's transform, which just includes scale. | |
| 281 localMatrix.preScale(scale.width(), scale.height()); | |
| 282 | |
| 283 // No need to resample before drawing. | |
| 284 SkBitmap srcSubset; | |
| 285 bitmap().extractSubset(&srcSubset, enclosingIntRect(normSrcRect)); | |
| 286 if (repeatSpacing.isZero()) { | |
| 287 shader = adoptRef(SkShader::CreateBitmapShader(srcSubset, SkShader::
kRepeat_TileMode, SkShader::kRepeat_TileMode, &localMatrix)); | |
| 288 } else { | |
| 289 shader = adoptRef(SkShader::CreateBitmapShader( | |
| 290 createBitmapWithSpace(srcSubset, repeatSpacing.width() * ctmScal
eX, repeatSpacing.height() * ctmScaleY), | |
| 291 SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &localMa
trix)); | |
| 292 } | |
| 293 } | 152 } |
| 153 RefPtr<SkShader> shader = adoptRef(SkShader::CreateBitmapShader(bitmapToPain
t, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &localMatrix)); |
| 294 | 154 |
| 295 SkPaint paint; | 155 SkPaint paint; |
| 296 paint.setShader(shader.get()); | 156 paint.setShader(shader.get()); |
| 297 paint.setXfermodeMode(WebCoreCompositeToSkiaComposite(compositeOp, blendMode
)); | 157 paint.setXfermodeMode(WebCoreCompositeToSkiaComposite(compositeOp, blendMode
)); |
| 298 paint.setColorFilter(context->colorFilter()); | 158 paint.setColorFilter(context->colorFilter()); |
| 299 paint.setFilterLevel(filterLevel); | 159 paint.setFilterLevel(static_cast<SkPaint::FilterLevel>(resampling)); |
| 300 | 160 |
| 301 if (isLazyDecoded) | 161 if (isLazyDecoded) |
| 302 PlatformInstrumentation::didDrawLazyPixelRef(bitmap().getGenerationID())
; | 162 PlatformInstrumentation::didDrawLazyPixelRef(bitmap().getGenerationID())
; |
| 303 | 163 |
| 304 context->drawRect(destRect, paint); | 164 context->drawRect(destRect, paint); |
| 305 } | 165 } |
| 306 | 166 |
| 307 bool NativeImageSkia::shouldCacheResampling(const SkISize& scaledImageSize, cons
t SkIRect& scaledImageSubset) const | |
| 308 { | |
| 309 // Check whether the requested dimensions match previous request. | |
| 310 bool matchesPreviousRequest = m_cachedImageInfo.isEqual(scaledImageSize, sca
ledImageSubset); | |
| 311 if (matchesPreviousRequest) | |
| 312 ++m_resizeRequests; | |
| 313 else { | |
| 314 m_cachedImageInfo.set(scaledImageSize, scaledImageSubset); | |
| 315 m_resizeRequests = 0; | |
| 316 // Reset m_resizedImage now, because we don't distinguish | |
| 317 // between the last requested resize info and m_resizedImage's | |
| 318 // resize info. | |
| 319 m_resizedImage.reset(); | |
| 320 } | |
| 321 | |
| 322 // We can not cache incomplete frames. This might be a good optimization in | |
| 323 // the future, were we know how much of the frame has been decoded, so when | |
| 324 // we incrementally draw more of the image, we only have to resample the | |
| 325 // parts that are changed. | |
| 326 if (!isDataComplete()) | |
| 327 return false; | |
| 328 | |
| 329 // If the destination bitmap is excessively large, we'll never allow caching
. | |
| 330 static const unsigned long long kLargeBitmapSize = 4096ULL * 4096ULL; | |
| 331 unsigned long long fullSize = static_cast<unsigned long long>(scaledImageSiz
e.width()) * static_cast<unsigned long long>(scaledImageSize.height()); | |
| 332 unsigned long long fragmentSize = static_cast<unsigned long long>(scaledImag
eSubset.width()) * static_cast<unsigned long long>(scaledImageSubset.height()); | |
| 333 | |
| 334 if (fragmentSize > kLargeBitmapSize) | |
| 335 return false; | |
| 336 | |
| 337 // If the destination bitmap is small, we'll always allow caching, since | |
| 338 // there is not very much penalty for computing it and it may come in handy. | |
| 339 static const unsigned kSmallBitmapSize = 4096; | |
| 340 if (fragmentSize <= kSmallBitmapSize) | |
| 341 return true; | |
| 342 | |
| 343 // If "too many" requests have been made for this bitmap, we assume that | |
| 344 // many more will be made as well, and we'll go ahead and cache it. | |
| 345 static const int kManyRequestThreshold = 4; | |
| 346 if (m_resizeRequests >= kManyRequestThreshold) | |
| 347 return true; | |
| 348 | |
| 349 // If more than 1/4 of the resized image is requested, it's worth caching. | |
| 350 return fragmentSize > fullSize / 4; | |
| 351 } | |
| 352 | |
| 353 NativeImageSkia::ImageResourceInfo::ImageResourceInfo() | |
| 354 { | |
| 355 scaledImageSize.setEmpty(); | |
| 356 scaledImageSubset.setEmpty(); | |
| 357 } | |
| 358 | |
| 359 bool NativeImageSkia::ImageResourceInfo::isEqual(const SkISize& otherScaledImage
Size, const SkIRect& otherScaledImageSubset) const | |
| 360 { | |
| 361 return scaledImageSize == otherScaledImageSize && scaledImageSubset == other
ScaledImageSubset; | |
| 362 } | |
| 363 | |
| 364 void NativeImageSkia::ImageResourceInfo::set(const SkISize& otherScaledImageSize
, const SkIRect& otherScaledImageSubset) | |
| 365 { | |
| 366 scaledImageSize = otherScaledImageSize; | |
| 367 scaledImageSubset = otherScaledImageSubset; | |
| 368 } | |
| 369 | |
| 370 SkIRect NativeImageSkia::ImageResourceInfo::rectInSubset(const SkIRect& otherSca
ledImageSubset) | |
| 371 { | |
| 372 if (!scaledImageSubset.contains(otherScaledImageSubset)) | |
| 373 return SkIRect::MakeEmpty(); | |
| 374 SkIRect subsetRect = otherScaledImageSubset; | |
| 375 subsetRect.offset(-scaledImageSubset.x(), -scaledImageSubset.y()); | |
| 376 return subsetRect; | |
| 377 } | |
| 378 | |
| 379 } // namespace blink | 167 } // namespace blink |
| OLD | NEW |