OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2015 Google Inc. | 2 * Copyright 2015 Google Inc. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
6 */ | 6 */ |
7 | 7 |
8 #include "SkBitmapScaler.h" | 8 #include "SkBitmapScaler.h" |
9 #include "SkBitmapFilter.h" | 9 #include "SkBitmapFilter.h" |
10 #include "SkConvolver.h" | 10 #include "SkConvolver.h" |
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
102 // For Lanczos, a scaling by a (reduced) factor of p/q (q pixels in the | 102 // For Lanczos, a scaling by a (reduced) factor of p/q (q pixels in the |
103 // source become p pixels in the destination) will have a period of p. | 103 // source become p pixels in the destination) will have a period of p. |
104 // A nice consequence is a period of 1 when downscaling by an integral | 104 // A nice consequence is a period of 1 when downscaling by an integral |
105 // factor. Downscaling from typical display resolutions is also bound | 105 // factor. Downscaling from typical display resolutions is also bound |
106 // to produce interesting periods as those are chosen to have multiple | 106 // to produce interesting periods as those are chosen to have multiple |
107 // small factors. | 107 // small factors. |
108 // Small periods reduce computational load and improve cache usage if | 108 // Small periods reduce computational load and improve cache usage if |
109 // the coefficients can be shared. For periods of 1 we can consider | 109 // the coefficients can be shared. For periods of 1 we can consider |
110 // loading the factors only once outside the borders. | 110 // loading the factors only once outside the borders. |
111 void SkResizeFilter::computeFilters(int srcSize, | 111 void SkResizeFilter::computeFilters(int srcSize, |
112 float destSubsetLo, float destSubsetSize, | 112 float destSubsetLo, float destSubsetSize, |
113 float scale, | 113 float scale, |
114 SkConvolutionFilter1D* output) { | 114 SkConvolutionFilter1D* output) { |
115 float destSubsetHi = destSubsetLo + destSubsetSize; // [lo, hi) | 115 float destSubsetHi = destSubsetLo + destSubsetSize; // [lo, hi) |
116 | 116 |
117 // When we're doing a magnification, the scale will be larger than one. This | 117 // When we're doing a magnification, the scale will be larger than one. This |
118 // means the destination pixels are much smaller than the source pixels, and | 118 // means the destination pixels are much smaller than the source pixels, and |
119 // that the range covered by the filter won't necessarily cover any source | 119 // that the range covered by the filter won't necessarily cover any source |
120 // pixel boundaries. Therefore, we use these clamped values (max of 1) for | 120 // pixel boundaries. Therefore, we use these clamped values (max of 1) for |
121 // some computations. | 121 // some computations. |
122 float clampedScale = SkTMin(1.0f, scale); | 122 float clampedScale = SkTMin(1.0f, scale); |
123 | 123 |
124 // This is how many source pixels from the center we need to count | 124 // This is how many source pixels from the center we need to count |
125 // to support the filtering function. | 125 // to support the filtering function. |
126 float srcSupport = fBitmapFilter->width() / clampedScale; | 126 float srcSupport = fBitmapFilter->width() / clampedScale; |
127 | 127 |
128 float invScale = 1.0f / scale; | 128 float invScale = 1.0f / scale; |
129 | 129 |
130 SkSTArray<64, float, true> filterValuesArray; | 130 SkSTArray<64, float, true> filterValuesArray; |
131 SkSTArray<64, SkConvolutionFilter1D::ConvolutionFixed, true> fixedFilterValues
Array; | 131 SkSTArray<64, SkConvolutionFilter1D::ConvolutionFixed, true> fixedFilterValu
esArray; |
132 | 132 |
133 // Loop over all pixels in the output range. We will generate one set of | 133 // Loop over all pixels in the output range. We will generate one set of |
134 // filter values for each one. Those values will tell us how to blend the | 134 // filter values for each one. Those values will tell us how to blend the |
135 // source pixels to compute the destination pixel. | 135 // source pixels to compute the destination pixel. |
136 | 136 |
137 // This is the pixel in the source directly under the pixel in the dest. | 137 // This is the pixel in the source directly under the pixel in the dest. |
138 // Note that we base computations on the "center" of the pixels. To see | 138 // Note that we base computations on the "center" of the pixels. To see |
139 // why, observe that the destination pixel at coordinates (0, 0) in a 5.0x | 139 // why, observe that the destination pixel at coordinates (0, 0) in a 5.0x |
140 // downscale should "cover" the pixels around the pixel with *its center* | 140 // downscale should "cover" the pixels around the pixel with *its center* |
141 // at coordinates (2.5, 2.5) in the source, not those around (0, 0). | 141 // at coordinates (2.5, 2.5) in the source, not those around (0, 0). |
142 // Hence we need to scale coordinates (0.5, 0.5), not (0, 0). | 142 // Hence we need to scale coordinates (0.5, 0.5), not (0, 0). |
143 destSubsetLo = SkScalarFloorToScalar(destSubsetLo); | 143 destSubsetLo = SkScalarFloorToScalar(destSubsetLo); |
144 destSubsetHi = SkScalarCeilToScalar(destSubsetHi); | 144 destSubsetHi = SkScalarCeilToScalar(destSubsetHi); |
145 float srcPixel = (destSubsetLo + 0.5f) * invScale; | 145 float srcPixel = (destSubsetLo + 0.5f) * invScale; |
146 int destLimit = SkScalarTruncToInt(destSubsetHi - destSubsetLo); | 146 int destLimit = SkScalarTruncToInt(destSubsetHi - destSubsetLo); |
147 output->reserveAdditional(destLimit, SkScalarCeilToInt(destLimit * srcSupport
* 2)); | 147 output->reserveAdditional(destLimit, SkScalarCeilToInt(destLimit * srcSuppor
t * 2)); |
148 for (int destI = 0; destI < destLimit; srcPixel += invScale, destI++) | 148 for (int destI = 0; destI < destLimit; srcPixel += invScale, destI++) { |
149 { | 149 // Compute the (inclusive) range of source pixels the filter covers. |
150 // Compute the (inclusive) range of source pixels the filter covers. | 150 float srcBegin = SkTMax(0.f, SkScalarFloorToScalar(srcPixel - srcSupport
)); |
151 float srcBegin = SkTMax(0.f, SkScalarFloorToScalar(srcPixel - srcSupport)); | 151 float srcEnd = SkTMin(srcSize - 1.f, SkScalarCeilToScalar(srcPixel + src
Support)); |
152 float srcEnd = SkTMin(srcSize - 1.f, SkScalarCeilToScalar(srcPixel + srcSupp
ort)); | |
153 | 152 |
154 // Compute the unnormalized filter value at each location of the source | 153 // Compute the unnormalized filter value at each location of the source |
155 // it covers. | 154 // it covers. |
156 | 155 |
157 // Sum of the filter values for normalizing. | 156 // Sum of the filter values for normalizing. |
158 // Distance from the center of the filter, this is the filter coordinate | 157 // Distance from the center of the filter, this is the filter coordinate |
159 // in source space. We also need to consider the center of the pixel | 158 // in source space. We also need to consider the center of the pixel |
160 // when comparing distance against 'srcPixel'. In the 5x downscale | 159 // when comparing distance against 'srcPixel'. In the 5x downscale |
161 // example used above the distance from the center of the filter to | 160 // example used above the distance from the center of the filter to |
162 // the pixel with coordinates (2, 2) should be 0, because its center | 161 // the pixel with coordinates (2, 2) should be 0, because its center |
163 // is at (2.5, 2.5). | 162 // is at (2.5, 2.5). |
164 float destFilterDist = (srcBegin + 0.5f - srcPixel) * clampedScale; | 163 float destFilterDist = (srcBegin + 0.5f - srcPixel) * clampedScale; |
165 int filterCount = SkScalarTruncToInt(srcEnd - srcBegin) + 1; | 164 int filterCount = SkScalarTruncToInt(srcEnd - srcBegin) + 1; |
166 if (filterCount <= 0) { | 165 if (filterCount <= 0) { |
167 // true when srcSize is equal to srcPixel - srcSupport; this may be a bu
g | 166 // true when srcSize is equal to srcPixel - srcSupport; this may be
a bug |
168 return; | 167 return; |
169 } | 168 } |
170 filterValuesArray.reset(filterCount); | 169 filterValuesArray.reset(filterCount); |
171 float filterSum = fBitmapFilter->evaluate_n(destFilterDist, clampedScale, fi
lterCount, | 170 float filterSum = fBitmapFilter->evaluate_n(destFilterDist, clampedScale
, filterCount, |
172 filterValuesArray.begin()); | 171 filterValuesArray.begin()); |
173 | 172 |
174 // The filter must be normalized so that we don't affect the brightness of | 173 // The filter must be normalized so that we don't affect the brightness
of |
175 // the image. Convert to normalized fixed point. | 174 // the image. Convert to normalized fixed point. |
176 int fixedSum = 0; | 175 int fixedSum = 0; |
177 fixedFilterValuesArray.reset(filterCount); | 176 fixedFilterValuesArray.reset(filterCount); |
178 const float* filterValues = filterValuesArray.begin(); | 177 const float* filterValues = filterValuesArray.begin(); |
179 SkConvolutionFilter1D::ConvolutionFixed* fixedFilterValues = fixedFilterValu
esArray.begin(); | 178 SkConvolutionFilter1D::ConvolutionFixed* fixedFilterValues = fixedFilter
ValuesArray.begin(); |
180 float invFilterSum = 1 / filterSum; | 179 float invFilterSum = 1 / filterSum; |
181 for (int fixedI = 0; fixedI < filterCount; fixedI++) { | 180 for (int fixedI = 0; fixedI < filterCount; fixedI++) { |
182 int curFixed = SkConvolutionFilter1D::FloatToFixed(filterValues[fixedI] *
invFilterSum); | 181 int curFixed = SkConvolutionFilter1D::FloatToFixed(filterValues[fixe
dI] * invFilterSum); |
183 fixedSum += curFixed; | 182 fixedSum += curFixed; |
184 fixedFilterValues[fixedI] = SkToS16(curFixed); | 183 fixedFilterValues[fixedI] = SkToS16(curFixed); |
| 184 } |
| 185 SkASSERT(fixedSum <= 0x7FFF); |
| 186 |
| 187 // The conversion to fixed point will leave some rounding errors, which |
| 188 // we add back in to avoid affecting the brightness of the image. We |
| 189 // arbitrarily add this to the center of the filter array (this won't al
ways |
| 190 // be the center of the filter function since it could get clipped on th
e |
| 191 // edges, but it doesn't matter enough to worry about that case). |
| 192 int leftovers = SkConvolutionFilter1D::FloatToFixed(1) - fixedSum; |
| 193 fixedFilterValues[filterCount / 2] += leftovers; |
| 194 |
| 195 // Now it's ready to go. |
| 196 output->AddFilter(SkScalarFloorToInt(srcBegin), fixedFilterValues, filte
rCount); |
185 } | 197 } |
186 SkASSERT(fixedSum <= 0x7FFF); | |
187 | |
188 // The conversion to fixed point will leave some rounding errors, which | |
189 // we add back in to avoid affecting the brightness of the image. We | |
190 // arbitrarily add this to the center of the filter array (this won't always | |
191 // be the center of the filter function since it could get clipped on the | |
192 // edges, but it doesn't matter enough to worry about that case). | |
193 int leftovers = SkConvolutionFilter1D::FloatToFixed(1) - fixedSum; | |
194 fixedFilterValues[filterCount / 2] += leftovers; | |
195 | |
196 // Now it's ready to go. | |
197 output->AddFilter(SkScalarFloorToInt(srcBegin), fixedFilterValues, filterCou
nt); | |
198 } | |
199 } | 198 } |
200 | 199 |
201 ////////////////////////////////////////////////////////////////////////////////
/////////////////// | 200 ////////////////////////////////////////////////////////////////////////////////
/////////////////// |
202 | 201 |
203 static bool valid_for_resize(const SkPixmap& source, int dstW, int dstH) { | 202 static bool valid_for_resize(const SkPixmap& source, int dstW, int dstH) { |
204 // TODO: Seems like we shouldn't care about the swizzle of source, just that
it's 8888 | 203 // TODO: Seems like we shouldn't care about the swizzle of source, just that
it's 8888 |
205 return source.addr() && source.colorType() == kN32_SkColorType && | 204 return source.addr() && source.colorType() == kN32_SkColorType && |
206 source.width() >= 1 && source.height() >= 1 && dstW >= 1 && dstH >= 1
; | 205 source.width() >= 1 && source.height() >= 1 && dstW >= 1 && dstH >= 1
; |
207 } | 206 } |
208 | 207 |
209 bool SkBitmapScaler::Resize(const SkPixmap& result, const SkPixmap& source, Resi
zeMethod method) { | 208 bool SkBitmapScaler::Resize(const SkPixmap& result, const SkPixmap& source, Resi
zeMethod method) { |
210 if (!valid_for_resize(source, result.width(), result.height())) { | 209 if (!valid_for_resize(source, result.width(), result.height())) { |
211 return false; | 210 return false; |
212 } | 211 } |
213 if (!result.addr() || result.colorType() != source.colorType()) { | 212 if (!result.addr() || result.colorType() != source.colorType()) { |
214 return false; | 213 return false; |
215 } | 214 } |
216 | 215 |
217 SkConvolutionProcs convolveProcs= { nullptr, nullptr, nullptr }; | |
218 PlatformConvolutionProcs(&convolveProcs); | |
219 | |
220 SkRect destSubset = SkRect::MakeIWH(result.width(), result.height()); | 216 SkRect destSubset = SkRect::MakeIWH(result.width(), result.height()); |
221 | 217 |
222 SkResizeFilter filter(method, source.width(), source.height(), | 218 SkResizeFilter filter(method, source.width(), source.height(), |
223 result.width(), result.height(), destSubset); | 219 result.width(), result.height(), destSubset); |
224 | 220 |
225 // Get a subset encompassing this touched area. We construct the | 221 // Get a subset encompassing this touched area. We construct the |
226 // offsets and row strides such that it looks like a new bitmap, while | 222 // offsets and row strides such that it looks like a new bitmap, while |
227 // referring to the old data. | 223 // referring to the old data. |
228 const uint8_t* sourceSubset = reinterpret_cast<const uint8_t*>(source.addr()
); | 224 const uint8_t* sourceSubset = reinterpret_cast<const uint8_t*>(source.addr()
); |
229 | 225 |
230 return BGRAConvolve2D(sourceSubset, static_cast<int>(source.rowBytes()), | 226 return BGRAConvolve2D(sourceSubset, static_cast<int>(source.rowBytes()), |
231 !source.isOpaque(), filter.xFilter(), filter.yFilter()
, | 227 !source.isOpaque(), filter.xFilter(), filter.yFilter()
, |
232 static_cast<int>(result.rowBytes()), | 228 static_cast<int>(result.rowBytes()), |
233 static_cast<unsigned char*>(result.writable_addr()), | 229 static_cast<unsigned char*>(result.writable_addr())); |
234 convolveProcs, true); | |
235 } | 230 } |
236 | 231 |
237 bool SkBitmapScaler::Resize(SkBitmap* resultPtr, const SkPixmap& source, ResizeM
ethod method, | 232 bool SkBitmapScaler::Resize(SkBitmap* resultPtr, const SkPixmap& source, ResizeM
ethod method, |
238 int destWidth, int destHeight, SkBitmap::Allocator*
allocator) { | 233 int destWidth, int destHeight, SkBitmap::Allocator*
allocator) { |
239 // Preflight some of the checks, to avoid allocating the result if we don't
need it. | 234 // Preflight some of the checks, to avoid allocating the result if we don't
need it. |
240 if (!valid_for_resize(source, destWidth, destHeight)) { | 235 if (!valid_for_resize(source, destWidth, destHeight)) { |
241 return false; | 236 return false; |
242 } | 237 } |
243 | 238 |
244 SkBitmap result; | 239 SkBitmap result; |
245 // Note: pass along the profile information even thought this is no the righ
t answer because | 240 // Note: pass along the profile information even thought this is no the righ
t answer because |
246 // this could be scaling in sRGB. | 241 // this could be scaling in sRGB. |
247 result.setInfo(SkImageInfo::MakeN32(destWidth, destHeight, source.alphaType(
), | 242 result.setInfo(SkImageInfo::MakeN32(destWidth, destHeight, source.alphaType(
), |
248 sk_ref_sp(source.info().colorSpace()))); | 243 sk_ref_sp(source.info().colorSpace()))); |
249 result.allocPixels(allocator, nullptr); | 244 result.allocPixels(allocator, nullptr); |
250 | 245 |
251 SkPixmap resultPM; | 246 SkPixmap resultPM; |
252 if (!result.peekPixels(&resultPM) || !Resize(resultPM, source, method)) { | 247 if (!result.peekPixels(&resultPM) || !Resize(resultPM, source, method)) { |
253 return false; | 248 return false; |
254 } | 249 } |
255 | 250 |
256 *resultPtr = result; | 251 *resultPtr = result; |
257 resultPtr->lockPixels(); | 252 resultPtr->lockPixels(); |
258 SkASSERT(resultPtr->getPixels()); | 253 SkASSERT(resultPtr->getPixels()); |
259 return true; | 254 return true; |
260 } | 255 } |
OLD | NEW |