Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
michael@0 | 1 | #include "SkBitmapScaler.h" |
michael@0 | 2 | #include "SkBitmapFilter.h" |
michael@0 | 3 | #include "SkRect.h" |
michael@0 | 4 | #include "SkTArray.h" |
michael@0 | 5 | #include "SkErrorInternals.h" |
michael@0 | 6 | #include "SkConvolver.h" |
michael@0 | 7 | |
michael@0 | 8 | // SkResizeFilter ---------------------------------------------------------------- |
michael@0 | 9 | |
michael@0 | 10 | // Encapsulates computation and storage of the filters required for one complete |
michael@0 | 11 | // resize operation. |
michael@0 | 12 | class SkResizeFilter { |
michael@0 | 13 | public: |
michael@0 | 14 | SkResizeFilter(SkBitmapScaler::ResizeMethod method, |
michael@0 | 15 | int srcFullWidth, int srcFullHeight, |
michael@0 | 16 | int destWidth, int destHeight, |
michael@0 | 17 | const SkIRect& destSubset, |
michael@0 | 18 | const SkConvolutionProcs& convolveProcs); |
michael@0 | 19 | ~SkResizeFilter() { |
michael@0 | 20 | SkDELETE( fBitmapFilter ); |
michael@0 | 21 | } |
michael@0 | 22 | |
michael@0 | 23 | // Returns the filled filter values. |
michael@0 | 24 | const SkConvolutionFilter1D& xFilter() { return fXFilter; } |
michael@0 | 25 | const SkConvolutionFilter1D& yFilter() { return fYFilter; } |
michael@0 | 26 | |
michael@0 | 27 | private: |
michael@0 | 28 | |
michael@0 | 29 | SkBitmapFilter* fBitmapFilter; |
michael@0 | 30 | |
michael@0 | 31 | // Computes one set of filters either horizontally or vertically. The caller |
michael@0 | 32 | // will specify the "min" and "max" rather than the bottom/top and |
michael@0 | 33 | // right/bottom so that the same code can be re-used in each dimension. |
michael@0 | 34 | // |
michael@0 | 35 | // |srcDependLo| and |srcDependSize| gives the range for the source |
michael@0 | 36 | // depend rectangle (horizontally or vertically at the caller's discretion |
michael@0 | 37 | // -- see above for what this means). |
michael@0 | 38 | // |
michael@0 | 39 | // Likewise, the range of destination values to compute and the scale factor |
michael@0 | 40 | // for the transform is also specified. |
michael@0 | 41 | |
michael@0 | 42 | void computeFilters(int srcSize, |
michael@0 | 43 | int destSubsetLo, int destSubsetSize, |
michael@0 | 44 | float scale, |
michael@0 | 45 | SkConvolutionFilter1D* output, |
michael@0 | 46 | const SkConvolutionProcs& convolveProcs); |
michael@0 | 47 | |
michael@0 | 48 | SkConvolutionFilter1D fXFilter; |
michael@0 | 49 | SkConvolutionFilter1D fYFilter; |
michael@0 | 50 | }; |
michael@0 | 51 | |
michael@0 | 52 | SkResizeFilter::SkResizeFilter(SkBitmapScaler::ResizeMethod method, |
michael@0 | 53 | int srcFullWidth, int srcFullHeight, |
michael@0 | 54 | int destWidth, int destHeight, |
michael@0 | 55 | const SkIRect& destSubset, |
michael@0 | 56 | const SkConvolutionProcs& convolveProcs) { |
michael@0 | 57 | |
michael@0 | 58 | // method will only ever refer to an "algorithm method". |
michael@0 | 59 | SkASSERT((SkBitmapScaler::RESIZE_FIRST_ALGORITHM_METHOD <= method) && |
michael@0 | 60 | (method <= SkBitmapScaler::RESIZE_LAST_ALGORITHM_METHOD)); |
michael@0 | 61 | |
michael@0 | 62 | switch(method) { |
michael@0 | 63 | case SkBitmapScaler::RESIZE_BOX: |
michael@0 | 64 | fBitmapFilter = SkNEW(SkBoxFilter); |
michael@0 | 65 | break; |
michael@0 | 66 | case SkBitmapScaler::RESIZE_TRIANGLE: |
michael@0 | 67 | fBitmapFilter = SkNEW(SkTriangleFilter); |
michael@0 | 68 | break; |
michael@0 | 69 | case SkBitmapScaler::RESIZE_MITCHELL: |
michael@0 | 70 | fBitmapFilter = SkNEW_ARGS(SkMitchellFilter, (1.f/3.f, 1.f/3.f)); |
michael@0 | 71 | break; |
michael@0 | 72 | case SkBitmapScaler::RESIZE_HAMMING: |
michael@0 | 73 | fBitmapFilter = SkNEW(SkHammingFilter); |
michael@0 | 74 | break; |
michael@0 | 75 | case SkBitmapScaler::RESIZE_LANCZOS3: |
michael@0 | 76 | fBitmapFilter = SkNEW(SkLanczosFilter); |
michael@0 | 77 | break; |
michael@0 | 78 | default: |
michael@0 | 79 | // NOTREACHED: |
michael@0 | 80 | fBitmapFilter = SkNEW_ARGS(SkMitchellFilter, (1.f/3.f, 1.f/3.f)); |
michael@0 | 81 | break; |
michael@0 | 82 | } |
michael@0 | 83 | |
michael@0 | 84 | |
michael@0 | 85 | float scaleX = static_cast<float>(destWidth) / |
michael@0 | 86 | static_cast<float>(srcFullWidth); |
michael@0 | 87 | float scaleY = static_cast<float>(destHeight) / |
michael@0 | 88 | static_cast<float>(srcFullHeight); |
michael@0 | 89 | |
michael@0 | 90 | this->computeFilters(srcFullWidth, destSubset.fLeft, destSubset.width(), |
michael@0 | 91 | scaleX, &fXFilter, convolveProcs); |
michael@0 | 92 | if (srcFullWidth == srcFullHeight && |
michael@0 | 93 | destSubset.fLeft == destSubset.fTop && |
michael@0 | 94 | destSubset.width() == destSubset.height()&& |
michael@0 | 95 | scaleX == scaleY) { |
michael@0 | 96 | fYFilter = fXFilter; |
michael@0 | 97 | } else { |
michael@0 | 98 | this->computeFilters(srcFullHeight, destSubset.fTop, destSubset.height(), |
michael@0 | 99 | scaleY, &fYFilter, convolveProcs); |
michael@0 | 100 | } |
michael@0 | 101 | } |
michael@0 | 102 | |
michael@0 | 103 | // TODO(egouriou): Take advantage of periods in the convolution. |
michael@0 | 104 | // Practical resizing filters are periodic outside of the border area. |
michael@0 | 105 | // For Lanczos, a scaling by a (reduced) factor of p/q (q pixels in the |
michael@0 | 106 | // source become p pixels in the destination) will have a period of p. |
michael@0 | 107 | // A nice consequence is a period of 1 when downscaling by an integral |
michael@0 | 108 | // factor. Downscaling from typical display resolutions is also bound |
michael@0 | 109 | // to produce interesting periods as those are chosen to have multiple |
michael@0 | 110 | // small factors. |
michael@0 | 111 | // Small periods reduce computational load and improve cache usage if |
michael@0 | 112 | // the coefficients can be shared. For periods of 1 we can consider |
michael@0 | 113 | // loading the factors only once outside the borders. |
michael@0 | 114 | void SkResizeFilter::computeFilters(int srcSize, |
michael@0 | 115 | int destSubsetLo, int destSubsetSize, |
michael@0 | 116 | float scale, |
michael@0 | 117 | SkConvolutionFilter1D* output, |
michael@0 | 118 | const SkConvolutionProcs& convolveProcs) { |
michael@0 | 119 | int destSubsetHi = destSubsetLo + destSubsetSize; // [lo, hi) |
michael@0 | 120 | |
michael@0 | 121 | // When we're doing a magnification, the scale will be larger than one. This |
michael@0 | 122 | // means the destination pixels are much smaller than the source pixels, and |
michael@0 | 123 | // that the range covered by the filter won't necessarily cover any source |
michael@0 | 124 | // pixel boundaries. Therefore, we use these clamped values (max of 1) for |
michael@0 | 125 | // some computations. |
michael@0 | 126 | float clampedScale = SkTMin(1.0f, scale); |
michael@0 | 127 | |
michael@0 | 128 | // This is how many source pixels from the center we need to count |
michael@0 | 129 | // to support the filtering function. |
michael@0 | 130 | float srcSupport = fBitmapFilter->width() / clampedScale; |
michael@0 | 131 | |
michael@0 | 132 | // Speed up the divisions below by turning them into multiplies. |
michael@0 | 133 | float invScale = 1.0f / scale; |
michael@0 | 134 | |
michael@0 | 135 | SkTArray<float> filterValues(64); |
michael@0 | 136 | SkTArray<short> fixedFilterValues(64); |
michael@0 | 137 | |
michael@0 | 138 | // Loop over all pixels in the output range. We will generate one set of |
michael@0 | 139 | // filter values for each one. Those values will tell us how to blend the |
michael@0 | 140 | // source pixels to compute the destination pixel. |
michael@0 | 141 | for (int destSubsetI = destSubsetLo; destSubsetI < destSubsetHi; |
michael@0 | 142 | destSubsetI++) { |
michael@0 | 143 | // Reset the arrays. We don't declare them inside so they can re-use the |
michael@0 | 144 | // same malloc-ed buffer. |
michael@0 | 145 | filterValues.reset(); |
michael@0 | 146 | fixedFilterValues.reset(); |
michael@0 | 147 | |
michael@0 | 148 | // This is the pixel in the source directly under the pixel in the dest. |
michael@0 | 149 | // Note that we base computations on the "center" of the pixels. To see |
michael@0 | 150 | // why, observe that the destination pixel at coordinates (0, 0) in a 5.0x |
michael@0 | 151 | // downscale should "cover" the pixels around the pixel with *its center* |
michael@0 | 152 | // at coordinates (2.5, 2.5) in the source, not those around (0, 0). |
michael@0 | 153 | // Hence we need to scale coordinates (0.5, 0.5), not (0, 0). |
michael@0 | 154 | float srcPixel = (static_cast<float>(destSubsetI) + 0.5f) * invScale; |
michael@0 | 155 | |
michael@0 | 156 | // Compute the (inclusive) range of source pixels the filter covers. |
michael@0 | 157 | int srcBegin = SkTMax(0, SkScalarFloorToInt(srcPixel - srcSupport)); |
michael@0 | 158 | int srcEnd = SkTMin(srcSize - 1, SkScalarCeilToInt(srcPixel + srcSupport)); |
michael@0 | 159 | |
michael@0 | 160 | // Compute the unnormalized filter value at each location of the source |
michael@0 | 161 | // it covers. |
michael@0 | 162 | float filterSum = 0.0f; // Sub of the filter values for normalizing. |
michael@0 | 163 | for (int curFilterPixel = srcBegin; curFilterPixel <= srcEnd; |
michael@0 | 164 | curFilterPixel++) { |
michael@0 | 165 | // Distance from the center of the filter, this is the filter coordinate |
michael@0 | 166 | // in source space. We also need to consider the center of the pixel |
michael@0 | 167 | // when comparing distance against 'srcPixel'. In the 5x downscale |
michael@0 | 168 | // example used above the distance from the center of the filter to |
michael@0 | 169 | // the pixel with coordinates (2, 2) should be 0, because its center |
michael@0 | 170 | // is at (2.5, 2.5). |
michael@0 | 171 | float srcFilterDist = |
michael@0 | 172 | ((static_cast<float>(curFilterPixel) + 0.5f) - srcPixel); |
michael@0 | 173 | |
michael@0 | 174 | // Since the filter really exists in dest space, map it there. |
michael@0 | 175 | float destFilterDist = srcFilterDist * clampedScale; |
michael@0 | 176 | |
michael@0 | 177 | // Compute the filter value at that location. |
michael@0 | 178 | float filterValue = fBitmapFilter->evaluate(destFilterDist); |
michael@0 | 179 | filterValues.push_back(filterValue); |
michael@0 | 180 | |
michael@0 | 181 | filterSum += filterValue; |
michael@0 | 182 | } |
michael@0 | 183 | SkASSERT(!filterValues.empty()); |
michael@0 | 184 | |
michael@0 | 185 | // The filter must be normalized so that we don't affect the brightness of |
michael@0 | 186 | // the image. Convert to normalized fixed point. |
michael@0 | 187 | short fixedSum = 0; |
michael@0 | 188 | for (int i = 0; i < filterValues.count(); i++) { |
michael@0 | 189 | short curFixed = output->FloatToFixed(filterValues[i] / filterSum); |
michael@0 | 190 | fixedSum += curFixed; |
michael@0 | 191 | fixedFilterValues.push_back(curFixed); |
michael@0 | 192 | } |
michael@0 | 193 | |
michael@0 | 194 | // The conversion to fixed point will leave some rounding errors, which |
michael@0 | 195 | // we add back in to avoid affecting the brightness of the image. We |
michael@0 | 196 | // arbitrarily add this to the center of the filter array (this won't always |
michael@0 | 197 | // be the center of the filter function since it could get clipped on the |
michael@0 | 198 | // edges, but it doesn't matter enough to worry about that case). |
michael@0 | 199 | short leftovers = output->FloatToFixed(1.0f) - fixedSum; |
michael@0 | 200 | fixedFilterValues[fixedFilterValues.count() / 2] += leftovers; |
michael@0 | 201 | |
michael@0 | 202 | // Now it's ready to go. |
michael@0 | 203 | output->AddFilter(srcBegin, &fixedFilterValues[0], |
michael@0 | 204 | static_cast<int>(fixedFilterValues.count())); |
michael@0 | 205 | } |
michael@0 | 206 | |
michael@0 | 207 | if (convolveProcs.fApplySIMDPadding) { |
michael@0 | 208 | convolveProcs.fApplySIMDPadding( output ); |
michael@0 | 209 | } |
michael@0 | 210 | } |
michael@0 | 211 | |
michael@0 | 212 | static SkBitmapScaler::ResizeMethod ResizeMethodToAlgorithmMethod( |
michael@0 | 213 | SkBitmapScaler::ResizeMethod method) { |
michael@0 | 214 | // Convert any "Quality Method" into an "Algorithm Method" |
michael@0 | 215 | if (method >= SkBitmapScaler::RESIZE_FIRST_ALGORITHM_METHOD && |
michael@0 | 216 | method <= SkBitmapScaler::RESIZE_LAST_ALGORITHM_METHOD) { |
michael@0 | 217 | return method; |
michael@0 | 218 | } |
michael@0 | 219 | // The call to SkBitmapScalerGtv::Resize() above took care of |
michael@0 | 220 | // GPU-acceleration in the cases where it is possible. So now we just |
michael@0 | 221 | // pick the appropriate software method for each resize quality. |
michael@0 | 222 | switch (method) { |
michael@0 | 223 | // Users of RESIZE_GOOD are willing to trade a lot of quality to |
michael@0 | 224 | // get speed, allowing the use of linear resampling to get hardware |
michael@0 | 225 | // acceleration (SRB). Hence any of our "good" software filters |
michael@0 | 226 | // will be acceptable, so we use a triangle. |
michael@0 | 227 | case SkBitmapScaler::RESIZE_GOOD: |
michael@0 | 228 | return SkBitmapScaler::RESIZE_TRIANGLE; |
michael@0 | 229 | // Users of RESIZE_BETTER are willing to trade some quality in order |
michael@0 | 230 | // to improve performance, but are guaranteed not to devolve to a linear |
michael@0 | 231 | // resampling. In visual tests we see that Hamming-1 is not as good as |
michael@0 | 232 | // Lanczos-2, however it is about 40% faster and Lanczos-2 itself is |
michael@0 | 233 | // about 30% faster than Lanczos-3. The use of Hamming-1 has been deemed |
michael@0 | 234 | // an acceptable trade-off between quality and speed. |
michael@0 | 235 | case SkBitmapScaler::RESIZE_BETTER: |
michael@0 | 236 | return SkBitmapScaler::RESIZE_HAMMING; |
michael@0 | 237 | default: |
michael@0 | 238 | #ifdef SK_HIGH_QUALITY_IS_LANCZOS |
michael@0 | 239 | return SkBitmapScaler::RESIZE_LANCZOS3; |
michael@0 | 240 | #else |
michael@0 | 241 | return SkBitmapScaler::RESIZE_MITCHELL; |
michael@0 | 242 | #endif |
michael@0 | 243 | } |
michael@0 | 244 | } |
michael@0 | 245 | |
michael@0 | 246 | // static |
michael@0 | 247 | bool SkBitmapScaler::Resize(SkBitmap* resultPtr, |
michael@0 | 248 | const SkBitmap& source, |
michael@0 | 249 | ResizeMethod method, |
michael@0 | 250 | int destWidth, int destHeight, |
michael@0 | 251 | const SkIRect& destSubset, |
michael@0 | 252 | const SkConvolutionProcs& convolveProcs, |
michael@0 | 253 | SkBitmap::Allocator* allocator) { |
michael@0 | 254 | // Ensure that the ResizeMethod enumeration is sound. |
michael@0 | 255 | SkASSERT(((RESIZE_FIRST_QUALITY_METHOD <= method) && |
michael@0 | 256 | (method <= RESIZE_LAST_QUALITY_METHOD)) || |
michael@0 | 257 | ((RESIZE_FIRST_ALGORITHM_METHOD <= method) && |
michael@0 | 258 | (method <= RESIZE_LAST_ALGORITHM_METHOD))); |
michael@0 | 259 | |
michael@0 | 260 | SkIRect dest = { 0, 0, destWidth, destHeight }; |
michael@0 | 261 | if (!dest.contains(destSubset)) { |
michael@0 | 262 | SkErrorInternals::SetError( kInvalidArgument_SkError, |
michael@0 | 263 | "Sorry, you passed me a bitmap resize " |
michael@0 | 264 | " method I have never heard of: %d", |
michael@0 | 265 | method ); |
michael@0 | 266 | } |
michael@0 | 267 | |
michael@0 | 268 | // If the size of source or destination is 0, i.e. 0x0, 0xN or Nx0, just |
michael@0 | 269 | // return empty. |
michael@0 | 270 | if (source.width() < 1 || source.height() < 1 || |
michael@0 | 271 | destWidth < 1 || destHeight < 1) { |
michael@0 | 272 | // todo: seems like we could handle negative dstWidth/Height, since that |
michael@0 | 273 | // is just a negative scale (flip) |
michael@0 | 274 | return false; |
michael@0 | 275 | } |
michael@0 | 276 | |
michael@0 | 277 | method = ResizeMethodToAlgorithmMethod(method); |
michael@0 | 278 | |
michael@0 | 279 | // Check that we deal with an "algorithm methods" from this point onward. |
michael@0 | 280 | SkASSERT((SkBitmapScaler::RESIZE_FIRST_ALGORITHM_METHOD <= method) && |
michael@0 | 281 | (method <= SkBitmapScaler::RESIZE_LAST_ALGORITHM_METHOD)); |
michael@0 | 282 | |
michael@0 | 283 | SkAutoLockPixels locker(source); |
michael@0 | 284 | if (!source.readyToDraw() || |
michael@0 | 285 | source.colorType() != kPMColor_SkColorType) { |
michael@0 | 286 | return false; |
michael@0 | 287 | } |
michael@0 | 288 | |
michael@0 | 289 | SkResizeFilter filter(method, source.width(), source.height(), |
michael@0 | 290 | destWidth, destHeight, destSubset, convolveProcs); |
michael@0 | 291 | |
michael@0 | 292 | // Get a source bitmap encompassing this touched area. We construct the |
michael@0 | 293 | // offsets and row strides such that it looks like a new bitmap, while |
michael@0 | 294 | // referring to the old data. |
michael@0 | 295 | const unsigned char* sourceSubset = |
michael@0 | 296 | reinterpret_cast<const unsigned char*>(source.getPixels()); |
michael@0 | 297 | |
michael@0 | 298 | // Convolve into the result. |
michael@0 | 299 | SkBitmap result; |
michael@0 | 300 | result.setConfig(SkImageInfo::MakeN32(destSubset.width(), |
michael@0 | 301 | destSubset.height(), |
michael@0 | 302 | source.alphaType())); |
michael@0 | 303 | result.allocPixels(allocator, NULL); |
michael@0 | 304 | if (!result.readyToDraw()) { |
michael@0 | 305 | return false; |
michael@0 | 306 | } |
michael@0 | 307 | |
michael@0 | 308 | BGRAConvolve2D(sourceSubset, static_cast<int>(source.rowBytes()), |
michael@0 | 309 | !source.isOpaque(), filter.xFilter(), filter.yFilter(), |
michael@0 | 310 | static_cast<int>(result.rowBytes()), |
michael@0 | 311 | static_cast<unsigned char*>(result.getPixels()), |
michael@0 | 312 | convolveProcs, true); |
michael@0 | 313 | |
michael@0 | 314 | *resultPtr = result; |
michael@0 | 315 | resultPtr->lockPixels(); |
michael@0 | 316 | SkASSERT(NULL != resultPtr->getPixels()); |
michael@0 | 317 | return true; |
michael@0 | 318 | } |
michael@0 | 319 | |
michael@0 | 320 | // static |
michael@0 | 321 | bool SkBitmapScaler::Resize(SkBitmap* resultPtr, |
michael@0 | 322 | const SkBitmap& source, |
michael@0 | 323 | ResizeMethod method, |
michael@0 | 324 | int destWidth, int destHeight, |
michael@0 | 325 | const SkConvolutionProcs& convolveProcs, |
michael@0 | 326 | SkBitmap::Allocator* allocator) { |
michael@0 | 327 | SkIRect destSubset = { 0, 0, destWidth, destHeight }; |
michael@0 | 328 | return Resize(resultPtr, source, method, destWidth, destHeight, destSubset, |
michael@0 | 329 | convolveProcs, allocator); |
michael@0 | 330 | } |