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
52namespace WebCore {
53
54static bool nearlyIntegral(float value)
55{
56    return fabs(value - floorf(value)) < std::numeric_limits<float>::epsilon();
57}
58
59ResamplingMode 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
141static 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//
189SkBitmap 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.
223void 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
255NativeImageSkia::NativeImageSkia()
256    : m_resolutionScale(1)
257    , m_resizeRequests(0)
258{
259}
260
261NativeImageSkia::NativeImageSkia(const SkBitmap& other, float resolutionScale)
262    : m_image(other)
263    , m_resolutionScale(resolutionScale)
264    , m_resizeRequests(0)
265{
266}
267
268NativeImageSkia::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
277NativeImageSkia::~NativeImageSkia()
278{
279}
280
281int NativeImageSkia::decodedSize() const
282{
283    return m_image.getSize() + m_resizedImage.getSize();
284}
285
286bool 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
293SkBitmap 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
318static bool hasNon90rotation(GraphicsContext* context)
319{
320    return !context->getTotalMatrix().rectStaysRect();
321}
322
323void 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
380void 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
473bool 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
519NativeImageSkia::ImageResourceInfo::ImageResourceInfo()
520{
521    scaledImageSize.setEmpty();
522    scaledImageSubset.setEmpty();
523}
524
525bool NativeImageSkia::ImageResourceInfo::isEqual(const SkISize& otherScaledImageSize, const SkIRect& otherScaledImageSubset) const
526{
527    return scaledImageSize == otherScaledImageSize && scaledImageSubset == otherScaledImageSubset;
528}
529
530void NativeImageSkia::ImageResourceInfo::set(const SkISize& otherScaledImageSize, const SkIRect& otherScaledImageSubset)
531{
532    scaledImageSize = otherScaledImageSize;
533    scaledImageSubset = otherScaledImageSubset;
534}
535
536SkIRect 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