blob: 3c3e69413ab2e12f3dbbc66ca28fd56aa996a446 [file] [log] [blame]
reed64045422015-06-04 06:31:31 -07001/*
2 * Copyright 2015 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "SkBitmap.h"
9#include "SkBitmapController.h"
10#include "SkMatrix.h"
bungemanf3c15b72015-08-19 11:56:48 -070011#include "SkTemplates.h"
reed64045422015-06-04 06:31:31 -070012
reed99138872015-08-31 15:16:17 -070013// RESIZE_LANCZOS3 is another good option, but chrome prefers mitchell at the moment
14#define kHQ_RESIZE_METHOD SkBitmapScaler::RESIZE_MITCHELL
15
reed64045422015-06-04 06:31:31 -070016///////////////////////////////////////////////////////////////////////////////////////////////////
17
18static bool valid_for_drawing(const SkBitmap& bm) {
19 if (0 == bm.width() || 0 == bm.height()) {
20 return false; // nothing to draw
21 }
halcanary96fcdcc2015-08-27 07:41:13 -070022 if (nullptr == bm.pixelRef()) {
reed64045422015-06-04 06:31:31 -070023 return false; // no pixels to read
24 }
25 if (bm.getTexture()) {
26 // we can handle texture (ugh) since lockPixels will perform a read-back
27 return true;
28 }
29 if (kIndex_8_SkColorType == bm.colorType()) {
30 SkAutoLockPixels alp(bm); // but we need to call it before getColorTable() is safe.
31 if (!bm.getColorTable()) {
32 return false;
33 }
34 }
35 return true;
36}
37
38SkBitmapController::State* SkBitmapController::requestBitmap(const SkBitmap& bm,
39 const SkMatrix& inv,
40 SkFilterQuality quality,
41 void* storage, size_t storageSize) {
42
43 if (!valid_for_drawing(bm)) {
halcanary96fcdcc2015-08-27 07:41:13 -070044 return nullptr;
reed64045422015-06-04 06:31:31 -070045 }
46
47 State* state = this->onRequestBitmap(bm, inv, quality, storage, storageSize);
48 if (state) {
halcanary96fcdcc2015-08-27 07:41:13 -070049 if (nullptr == state->fPixmap.addr()) {
reedad7ae6c2015-06-04 14:12:25 -070050 SkInPlaceDeleteCheck(state, storage);
halcanary96fcdcc2015-08-27 07:41:13 -070051 state = nullptr;
reed64045422015-06-04 06:31:31 -070052 }
53 }
54 return state;
55}
56
57///////////////////////////////////////////////////////////////////////////////////////////////////
58
59#include "SkBitmapCache.h"
60#include "SkBitmapScaler.h"
61#include "SkMipMap.h"
62#include "SkResourceCache.h"
63
64class SkDefaultBitmapControllerState : public SkBitmapController::State {
65public:
66 SkDefaultBitmapControllerState(const SkBitmap& src, const SkMatrix& inv, SkFilterQuality qual);
67
68private:
reedad7ae6c2015-06-04 14:12:25 -070069 SkBitmap fResultBitmap;
reed64045422015-06-04 06:31:31 -070070 SkAutoTUnref<const SkMipMap> fCurrMip;
71
72 bool processHQRequest(const SkBitmap& orig);
73 bool processMediumRequest(const SkBitmap& orig);
74};
75
76// Check to see that the size of the bitmap that would be produced by
77// scaling by the given inverted matrix is less than the maximum allowed.
78static inline bool cache_size_okay(const SkBitmap& bm, const SkMatrix& invMat) {
79 size_t maximumAllocation = SkResourceCache::GetEffectiveSingleAllocationByteLimit();
80 if (0 == maximumAllocation) {
81 return true;
82 }
83 // float matrixScaleFactor = 1.0 / (invMat.scaleX * invMat.scaleY);
84 // return ((origBitmapSize * matrixScaleFactor) < maximumAllocationSize);
85 // Skip the division step:
86 const size_t size = bm.info().getSafeSize(bm.info().minRowBytes());
87 return size < (maximumAllocation * invMat.getScaleX() * invMat.getScaleY());
88}
89
90/*
91 * High quality is implemented by performing up-right scale-only filtering and then
92 * using bilerp for any remaining transformations.
93 */
94bool SkDefaultBitmapControllerState::processHQRequest(const SkBitmap& origBitmap) {
95 if (fQuality != kHigh_SkFilterQuality) {
96 return false;
97 }
98
99 // Our default return state is to downgrade the request to Medium, w/ or w/o setting fBitmap
100 // to a valid bitmap. If we succeed, we will set this to Low instead.
101 fQuality = kMedium_SkFilterQuality;
102
103 if (kN32_SkColorType != origBitmap.colorType() || !cache_size_okay(origBitmap, fInvMatrix) ||
104 fInvMatrix.hasPerspective())
105 {
106 return false; // can't handle the reqeust
107 }
108
109 SkScalar invScaleX = fInvMatrix.getScaleX();
110 SkScalar invScaleY = fInvMatrix.getScaleY();
111 if (fInvMatrix.getType() & SkMatrix::kAffine_Mask) {
112 SkSize scale;
113 if (!fInvMatrix.decomposeScale(&scale)) {
114 return false;
115 }
116 invScaleX = scale.width();
117 invScaleY = scale.height();
118 }
119 if (SkScalarNearlyEqual(invScaleX, 1) && SkScalarNearlyEqual(invScaleY, 1)) {
120 return false; // no need for HQ
121 }
122
reed99138872015-08-31 15:16:17 -0700123 const int dstW = SkScalarRoundToScalar(origBitmap.width() / invScaleX);
124 const int dstH = SkScalarRoundToScalar(origBitmap.height() / invScaleY);
reed64045422015-06-04 06:31:31 -0700125
reed99138872015-08-31 15:16:17 -0700126 if (!SkBitmapCache::FindWH(origBitmap, dstW, dstH, &fResultBitmap)) {
reed3c834322015-06-12 07:09:59 -0700127 SkAutoPixmapUnlock src;
128 if (!origBitmap.requestLock(&src)) {
129 return false;
130 }
reed99138872015-08-31 15:16:17 -0700131 if (!SkBitmapScaler::Resize(&fResultBitmap, src.pixmap(), kHQ_RESIZE_METHOD,
132 dstW, dstH, SkResourceCache::GetAllocator())) {
reed64045422015-06-04 06:31:31 -0700133 return false; // we failed to create fScaledBitmap
134 }
135
reedad7ae6c2015-06-04 14:12:25 -0700136 SkASSERT(fResultBitmap.getPixels());
137 fResultBitmap.setImmutable();
reed99138872015-08-31 15:16:17 -0700138 SkBitmapCache::AddWH(origBitmap, dstW, dstH, fResultBitmap);
reed64045422015-06-04 06:31:31 -0700139 }
140
reedad7ae6c2015-06-04 14:12:25 -0700141 SkASSERT(fResultBitmap.getPixels());
reed64045422015-06-04 06:31:31 -0700142
reed99138872015-08-31 15:16:17 -0700143 fInvMatrix.postScale(SkIntToScalar(dstW) / origBitmap.width(),
144 SkIntToScalar(dstH) / origBitmap.height());
reed64045422015-06-04 06:31:31 -0700145 fQuality = kLow_SkFilterQuality;
146 return true;
147}
148
149/*
150 * Modulo internal errors, this should always succeed *if* the matrix is downscaling
151 * (in this case, we have the inverse, so it succeeds if fInvMatrix is upscaling)
152 */
153bool SkDefaultBitmapControllerState::processMediumRequest(const SkBitmap& origBitmap) {
154 SkASSERT(fQuality <= kMedium_SkFilterQuality);
155 if (fQuality != kMedium_SkFilterQuality) {
156 return false;
157 }
158
159 // Our default return state is to downgrade the request to Low, w/ or w/o setting fBitmap
160 // to a valid bitmap.
161 fQuality = kLow_SkFilterQuality;
162
163 SkSize invScaleSize;
halcanary96fcdcc2015-08-27 07:41:13 -0700164 if (!fInvMatrix.decomposeScale(&invScaleSize, nullptr)) {
reed64045422015-06-04 06:31:31 -0700165 return false;
166 }
167 SkScalar invScale = SkScalarSqrt(invScaleSize.width() * invScaleSize.height());
168
169 if (invScale > SK_Scalar1) {
170 fCurrMip.reset(SkMipMapCache::FindAndRef(origBitmap));
halcanary96fcdcc2015-08-27 07:41:13 -0700171 if (nullptr == fCurrMip.get()) {
reed64045422015-06-04 06:31:31 -0700172 fCurrMip.reset(SkMipMapCache::AddAndRef(origBitmap));
halcanary96fcdcc2015-08-27 07:41:13 -0700173 if (nullptr == fCurrMip.get()) {
reed64045422015-06-04 06:31:31 -0700174 return false;
175 }
176 }
177 // diagnostic for a crasher...
halcanary96fcdcc2015-08-27 07:41:13 -0700178 if (nullptr == fCurrMip->data()) {
reed64045422015-06-04 06:31:31 -0700179 sk_throw();
180 }
181
182 SkScalar levelScale = SkScalarInvert(invScale);
183 SkMipMap::Level level;
184 if (fCurrMip->extractLevel(levelScale, &level)) {
185 SkScalar invScaleFixup = level.fScale;
186 fInvMatrix.postScale(invScaleFixup, invScaleFixup);
187
188 const SkImageInfo info = origBitmap.info().makeWH(level.fWidth, level.fHeight);
189 // todo: if we could wrap the fCurrMip in a pixelref, then we could just install
190 // that here, and not need to explicitly track it ourselves.
reedad7ae6c2015-06-04 14:12:25 -0700191 return fResultBitmap.installPixels(info, level.fPixels, level.fRowBytes);
reed64045422015-06-04 06:31:31 -0700192 } else {
193 // failed to extract, so release the mipmap
halcanary96fcdcc2015-08-27 07:41:13 -0700194 fCurrMip.reset(nullptr);
reed64045422015-06-04 06:31:31 -0700195 }
196 }
197 return false;
198}
199
200SkDefaultBitmapControllerState::SkDefaultBitmapControllerState(const SkBitmap& src,
201 const SkMatrix& inv,
202 SkFilterQuality qual) {
203 fInvMatrix = inv;
204 fQuality = qual;
205
reedad7ae6c2015-06-04 14:12:25 -0700206 if (this->processHQRequest(src) || this->processMediumRequest(src)) {
207 SkASSERT(fResultBitmap.getPixels());
208 } else {
209 fResultBitmap = src;
210 fResultBitmap.lockPixels();
211 // lock may fail to give us pixels
reed64045422015-06-04 06:31:31 -0700212 }
213 SkASSERT(fQuality <= kLow_SkFilterQuality);
reedad7ae6c2015-06-04 14:12:25 -0700214
215 // fResultBitmap.getPixels() may be null, but our caller knows to check fPixmap.addr()
halcanary96fcdcc2015-08-27 07:41:13 -0700216 // and will destroy us if it is nullptr.
reedad7ae6c2015-06-04 14:12:25 -0700217 fPixmap.reset(fResultBitmap.info(), fResultBitmap.getPixels(), fResultBitmap.rowBytes(),
218 fResultBitmap.getColorTable());
reed64045422015-06-04 06:31:31 -0700219}
220
221SkBitmapController::State* SkDefaultBitmapController::onRequestBitmap(const SkBitmap& bm,
222 const SkMatrix& inverse,
223 SkFilterQuality quality,
224 void* storage, size_t size) {
225 return SkInPlaceNewCheck<SkDefaultBitmapControllerState>(storage, size, bm, inverse, quality);
226}
227