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 |