| 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 |