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/DeferredImageDecoder.h" 40 #include "platform/graphics/GraphicsContext.h" 41 #include "platform/graphics/Image.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 <math.h> 50 51 namespace blink { 52 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, float scaleX, float scaleY, SkRect* scaledSrcRect) const 83 { 84 SkISize imageSize = SkISize::Make(bitmap().width(), bitmap().height()); 85 SkISize scaledImageSize = SkISize::Make(clampToInteger(roundf(imageSize.width() * scaleX)), 86 clampToInteger(roundf(imageSize.height() * scaleY))); 87 88 SkRect imageRect = SkRect::MakeWH(imageSize.width(), imageSize.height()); 89 SkRect scaledImageRect = SkRect::MakeWH(scaledImageSize.width(), scaledImageSize.height()); 90 91 SkMatrix scaleTransform; 92 scaleTransform.setRectToRect(imageRect, scaledImageRect, SkMatrix::kFill_ScaleToFit); 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 SkIRect& scaledImageSubset) const 124 { 125 bool imageScaleEqual = m_cachedImageInfo.scaledImageSize == scaledImageSize; 126 bool scaledImageSubsetAvailable = m_cachedImageInfo.scaledImageSubset.contains(scaledImageSubset); 127 return imageScaleEqual && scaledImageSubsetAvailable && !m_resizedImage.empty(); 128 } 129 130 SkBitmap NativeImageSkia::resizedBitmap(const SkISize& scaledImageSize, const SkIRect& 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"), "ResizeImage", "cached", shouldCache); 139 // FIXME(361045): remove InspectorInstrumentation calls once DevTools Timeline migrates to tracing. 140 PlatformInstrumentation::willResizeImage(shouldCache); 141 SkBitmap resizedImage = skia::ImageOperations::Resize(bitmap(), skia::ImageOperations::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( 158 GraphicsContext* context, 159 const SkRect& srcRect, 160 const SkRect& destRect, 161 CompositeOperator compositeOp, 162 WebBlendMode blendMode) const 163 { 164 TRACE_EVENT0("skia", "NativeImageSkia::draw"); 165 166 bool isLazyDecoded = DeferredImageDecoder::isLazyDecoded(bitmap()); 167 168 SkPaint paint; 169 context->preparePaintForDrawRectToRect(&paint, srcRect, destRect, compositeOp, blendMode, isLazyDecoded, isDataComplete()); 170 // We want to filter it if we decided to do interpolation above, or if 171 // there is something interesting going on with the matrix (like a rotation). 172 // Note: for serialization, we will want to subset the bitmap first so we 173 // don't send extra pixels. 174 context->drawBitmapRect(bitmap(), &srcRect, destRect, &paint); 175 176 if (isLazyDecoded) 177 PlatformInstrumentation::didDrawLazyPixelRef(bitmap().getGenerationID()); 178 context->didDrawRect(destRect, paint, &bitmap()); 179 } 180 181 static SkBitmap createBitmapWithSpace(const SkBitmap& bitmap, int spaceWidth, int spaceHeight) 182 { 183 SkImageInfo info = bitmap.info(); 184 info.fWidth += spaceWidth; 185 info.fHeight += spaceHeight; 186 info.fAlphaType = kPremul_SkAlphaType; 187 188 SkBitmap result; 189 result.allocPixels(info); 190 result.eraseColor(SK_ColorTRANSPARENT); 191 bitmap.copyPixelsTo(reinterpret_cast<uint8_t*>(result.getPixels()), result.rowBytes() * result.height(), result.rowBytes()); 192 193 return result; 194 } 195 196 void NativeImageSkia::drawPattern( 197 GraphicsContext* context, 198 const FloatRect& floatSrcRect, 199 const FloatSize& scale, 200 const FloatPoint& phase, 201 CompositeOperator compositeOp, 202 const FloatRect& destRect, 203 WebBlendMode blendMode, 204 const IntSize& repeatSpacing) const 205 { 206 FloatRect normSrcRect = floatSrcRect; 207 normSrcRect.intersect(FloatRect(0, 0, bitmap().width(), bitmap().height())); 208 if (destRect.isEmpty() || normSrcRect.isEmpty()) 209 return; // nothing to draw 210 211 SkMatrix totalMatrix = context->getTotalMatrix(); 212 AffineTransform ctm = context->getCTM(); 213 SkScalar ctmScaleX = ctm.xScale(); 214 SkScalar ctmScaleY = ctm.yScale(); 215 totalMatrix.preScale(scale.width(), scale.height()); 216 217 // Figure out what size the bitmap will be in the destination. The 218 // destination rect is the bounds of the pattern, we need to use the 219 // matrix to see how big it will be. 220 SkRect destRectTarget; 221 totalMatrix.mapRect(&destRectTarget, normSrcRect); 222 223 float destBitmapWidth = SkScalarToFloat(destRectTarget.width()); 224 float destBitmapHeight = SkScalarToFloat(destRectTarget.height()); 225 226 bool isLazyDecoded = DeferredImageDecoder::isLazyDecoded(bitmap()); 227 228 // Compute the resampling mode. 229 InterpolationQuality resampling; 230 if (context->isAccelerated() || context->printing()) 231 resampling = InterpolationLow; 232 else if (isLazyDecoded) 233 resampling = InterpolationHigh; 234 else 235 resampling = computeInterpolationQuality(totalMatrix, normSrcRect.width(), normSrcRect.height(), destBitmapWidth, destBitmapHeight, isDataComplete()); 236 resampling = limitInterpolationQuality(context, resampling); 237 238 SkMatrix localMatrix; 239 // We also need to translate it such that the origin of the pattern is the 240 // origin of the destination rect, which is what WebKit expects. Skia uses 241 // the coordinate system origin as the base for the pattern. If WebKit wants 242 // a shifted image, it will shift it from there using the localMatrix. 243 const float adjustedX = phase.x() + normSrcRect.x() * scale.width(); 244 const float adjustedY = phase.y() + normSrcRect.y() * scale.height(); 245 localMatrix.setTranslate(SkFloatToScalar(adjustedX), SkFloatToScalar(adjustedY)); 246 247 RefPtr<SkShader> shader; 248 SkPaint::FilterLevel filterLevel = static_cast<SkPaint::FilterLevel>(resampling); 249 250 // Bicubic filter is only applied to defer-decoded images, see 251 // NativeImageSkia::draw for details. 252 if (resampling == InterpolationHigh && !isLazyDecoded) { 253 // Do nice resampling. 254 filterLevel = SkPaint::kNone_FilterLevel; 255 float scaleX = destBitmapWidth / normSrcRect.width(); 256 float scaleY = destBitmapHeight / normSrcRect.height(); 257 SkRect scaledSrcRect; 258 259 // Since we are resizing the bitmap, we need to remove the scale 260 // applied to the pixels in the bitmap shader. This means we need 261 // CTM * localMatrix to have identity scale. Since we 262 // can't modify CTM (or the rectangle will be drawn in the wrong 263 // place), we must set localMatrix's scale to the inverse of 264 // CTM scale. 265 localMatrix.preScale(ctmScaleX ? 1 / ctmScaleX : 1, ctmScaleY ? 1 / ctmScaleY : 1); 266 267 // The image fragment generated here is not exactly what is 268 // requested. The scale factor used is approximated and image 269 // fragment is slightly larger to align to integer 270 // boundaries. 271 SkBitmap resampled = extractScaledImageFragment(normSrcRect, scaleX, scaleY, &scaledSrcRect); 272 if (repeatSpacing.isZero()) { 273 shader = adoptRef(SkShader::CreateBitmapShader(resampled, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &localMatrix)); 274 } else { 275 shader = adoptRef(SkShader::CreateBitmapShader( 276 createBitmapWithSpace(resampled, repeatSpacing.width() * ctmScaleX, repeatSpacing.height() * ctmScaleY), 277 SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &localMatrix)); 278 } 279 } else { 280 // Because no resizing occurred, the shader transform should be 281 // set to the pattern's transform, which just includes scale. 282 localMatrix.preScale(scale.width(), scale.height()); 283 284 // No need to resample before drawing. 285 SkBitmap srcSubset; 286 bitmap().extractSubset(&srcSubset, enclosingIntRect(normSrcRect)); 287 if (repeatSpacing.isZero()) { 288 shader = adoptRef(SkShader::CreateBitmapShader(srcSubset, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &localMatrix)); 289 } else { 290 shader = adoptRef(SkShader::CreateBitmapShader( 291 createBitmapWithSpace(srcSubset, repeatSpacing.width() * ctmScaleX, repeatSpacing.height() * ctmScaleY), 292 SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &localMatrix)); 293 } 294 } 295 296 SkPaint paint; 297 paint.setShader(shader.get()); 298 paint.setXfermodeMode(WebCoreCompositeToSkiaComposite(compositeOp, blendMode)); 299 paint.setColorFilter(context->colorFilter()); 300 paint.setFilterLevel(filterLevel); 301 302 if (isLazyDecoded) 303 PlatformInstrumentation::didDrawLazyPixelRef(bitmap().getGenerationID()); 304 305 context->drawRect(destRect, paint); 306 } 307 308 bool NativeImageSkia::shouldCacheResampling(const SkISize& scaledImageSize, const SkIRect& scaledImageSubset) const 309 { 310 // Check whether the requested dimensions match previous request. 311 bool matchesPreviousRequest = m_cachedImageInfo.isEqual(scaledImageSize, scaledImageSubset); 312 if (matchesPreviousRequest) 313 ++m_resizeRequests; 314 else { 315 m_cachedImageInfo.set(scaledImageSize, scaledImageSubset); 316 m_resizeRequests = 0; 317 // Reset m_resizedImage now, because we don't distinguish 318 // between the last requested resize info and m_resizedImage's 319 // resize info. 320 m_resizedImage.reset(); 321 } 322 323 // We can not cache incomplete frames. This might be a good optimization in 324 // the future, were we know how much of the frame has been decoded, so when 325 // we incrementally draw more of the image, we only have to resample the 326 // parts that are changed. 327 if (!isDataComplete()) 328 return false; 329 330 // If the destination bitmap is excessively large, we'll never allow caching. 331 static const unsigned long long kLargeBitmapSize = 4096ULL * 4096ULL; 332 unsigned long long fullSize = static_cast<unsigned long long>(scaledImageSize.width()) * static_cast<unsigned long long>(scaledImageSize.height()); 333 unsigned long long fragmentSize = static_cast<unsigned long long>(scaledImageSubset.width()) * static_cast<unsigned long long>(scaledImageSubset.height()); 334 335 if (fragmentSize > kLargeBitmapSize) 336 return false; 337 338 // If the destination bitmap is small, we'll always allow caching, since 339 // there is not very much penalty for computing it and it may come in handy. 340 static const unsigned kSmallBitmapSize = 4096; 341 if (fragmentSize <= kSmallBitmapSize) 342 return true; 343 344 // If "too many" requests have been made for this bitmap, we assume that 345 // many more will be made as well, and we'll go ahead and cache it. 346 static const int kManyRequestThreshold = 4; 347 if (m_resizeRequests >= kManyRequestThreshold) 348 return true; 349 350 // If more than 1/4 of the resized image is requested, it's worth caching. 351 return fragmentSize > fullSize / 4; 352 } 353 354 NativeImageSkia::ImageResourceInfo::ImageResourceInfo() 355 { 356 scaledImageSize.setEmpty(); 357 scaledImageSubset.setEmpty(); 358 } 359 360 bool NativeImageSkia::ImageResourceInfo::isEqual(const SkISize& otherScaledImageSize, const SkIRect& otherScaledImageSubset) const 361 { 362 return scaledImageSize == otherScaledImageSize && scaledImageSubset == otherScaledImageSubset; 363 } 364 365 void NativeImageSkia::ImageResourceInfo::set(const SkISize& otherScaledImageSize, const SkIRect& otherScaledImageSubset) 366 { 367 scaledImageSize = otherScaledImageSize; 368 scaledImageSubset = otherScaledImageSubset; 369 } 370 371 SkIRect NativeImageSkia::ImageResourceInfo::rectInSubset(const SkIRect& otherScaledImageSubset) 372 { 373 if (!scaledImageSubset.contains(otherScaledImageSubset)) 374 return SkIRect::MakeEmpty(); 375 SkIRect subsetRect = otherScaledImageSubset; 376 subsetRect.offset(-scaledImageSubset.x(), -scaledImageSubset.y()); 377 return subsetRect; 378 } 379 380 } // namespace blink 381