Bug Summary

File:root/firefox-clang/obj-x86_64-pc-linux-gnu/gfx/2d/./../../../gfx/2d/SkConvolver.cpp
Warning:line 597, column 50
Value stored to 'filterValues' during its initialization is never read

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -O2 -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name Unified_cpp_gfx_2d1.cpp -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -analyzer-config-compatibility-mode=true -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=all -relaxed-aliasing -ffp-contract=off -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/root/firefox-clang/obj-x86_64-pc-linux-gnu/gfx/2d -fcoverage-compilation-dir=/root/firefox-clang/obj-x86_64-pc-linux-gnu/gfx/2d -resource-dir /usr/lib/llvm-23/lib/clang/23 -include /root/firefox-clang/config/gcc_hidden.h -include /root/firefox-clang/obj-x86_64-pc-linux-gnu/mozilla-config.h -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/stl_wrappers -D _GLIBCXX_ASSERTIONS=1 -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/system_wrappers -U _FORTIFY_SOURCE -D _FORTIFY_SOURCE=2 -D DEBUG=1 -D USE_SSE2 -D USE_CAIRO -D MOZ2D_HAS_MOZ_CAIRO -D MOZ_ENABLE_FREETYPE -D MOZ_HAS_MOZGLUE -D MOZILLA_INTERNAL_API -D IMPL_LIBXUL -D MOZ_SUPPORT_LEAKCHECKING -D STATIC_EXPORTABLE_JS_API -I /root/firefox-clang/gfx/2d -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/gfx/2d -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/ipc/ipdl/_ipdlheaders -I /root/firefox-clang/ipc/chromium/src -I /root/firefox-clang/third_party/abseil-cpp -I /root/firefox-clang/toolkit/components/telemetry -I /root/firefox-clang/xpcom/base -I /root/firefox-clang/gfx/skia -I /root/firefox-clang/gfx/skia/skia -I /root/firefox-clang/gfx/cairo/cairo/src -I /root/firefox-clang/third_party/xsimd/include -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/include -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/include/nspr -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/include/nss -D MOZILLA_CLIENT -I /usr/include/freetype2 -I /usr/include/libpng16 -I /usr/include/freetype2 -I /usr/include/libpng16 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../include/c++/16 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../include/x86_64-linux-gnu/c++/16 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../include/c++/16/backward -internal-isystem /usr/lib/llvm-23/lib/clang/23/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -Wno-error=pessimizing-move -Wno-error=large-by-value-copy=128 -Wno-error=implicit-int-float-conversion -Wno-error=thread-safety-analysis -Wno-error=tautological-type-limit-compare -Wno-invalid-offsetof -Wno-range-loop-analysis -Wno-deprecated-anon-enum-enum-conversion -Wno-deprecated-enum-enum-conversion -Wno-inline-new-delete -Wno-error=deprecated-declarations -Wno-error=array-bounds -Wno-error=free-nonheap-object -Wno-error=atomic-alignment -Wno-error=deprecated-builtins -Wno-psabi -Wno-error=builtin-macro-redefined -Wno-vla-cxx-extension -Wno-unknown-warning-option -Wno-character-conversion -std=gnu++20 -fdeprecated-macro -ferror-limit 19 -fstrict-flex-arrays=1 -stack-protector 2 -fstack-clash-protection -ftrivial-auto-var-init=pattern -fno-rtti -fgnuc-version=4.2.1 -fno-implicit-modules -fskip-odr-check-in-gmf -fno-sized-deallocation -fno-aligned-allocation -fdiagnostics-absolute-paths -vectorize-loops -vectorize-slp -analyzer-checker optin.performance.Padding -analyzer-output=html -analyzer-config stable-report-filename=true -mllvm -dwarf-linkage-names=Abstract -faddrsig -fdwarf2-cfi-asm -o /tmp/scan-build-2026-09-01-224014-2642839-1 -x c++ Unified_cpp_gfx_2d1.cpp
1// Copyright (c) 2011-2016 Google Inc.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the gfx/skia/LICENSE file.
4
5#include "SkConvolver.h"
6
7#include <algorithm>
8
9#ifdef USE_SSE21
10# include "mozilla/SSE.h"
11#endif
12
13#ifdef USE_NEON
14# include "mozilla/arm.h"
15#endif
16
17namespace skia {
18
19using mozilla::gfx::BytesPerPixel;
20using mozilla::gfx::IsOpaque;
21using mozilla::gfx::SurfaceFormat;
22
23// Converts the argument to an 8-bit unsigned value by clamping to the range
24// 0-255.
25static inline unsigned char ClampTo8(int a) {
26 if (static_cast<unsigned>(a) < 256) {
27 return a; // Avoid the extra check in the common case.
28 }
29 if (a < 0) {
30 return 0;
31 }
32 return 255;
33}
34
35// Convolves horizontally along a single row. The row data is given in
36// |srcData| and continues for the numValues() of the filter.
37template <bool hasAlpha>
38void ConvolveHorizontally(const unsigned char* srcData,
39 const SkConvolutionFilter1D& filter,
40 unsigned char* outRow) {
41 // Loop over each pixel on this row in the output image.
42 int numValues = filter.numValues();
43 for (int outX = 0; outX < numValues; outX++) {
44 // Get the filter that determines the current output pixel.
45 int filterOffset, filterLength;
46 const SkConvolutionFilter1D::ConvolutionFixed* filterValues =
47 filter.FilterForValue(outX, &filterOffset, &filterLength);
48
49 // Compute the first pixel in this row that the filter affects. It will
50 // touch |filterLength| pixels (4 bytes each) after this.
51 const unsigned char* rowToFilter = &srcData[filterOffset * 4];
52
53 // Apply the filter to the row to get the destination pixel in |accum|.
54 int accum[4] = {0};
55 for (int filterX = 0; filterX < filterLength; filterX++) {
56 SkConvolutionFilter1D::ConvolutionFixed curFilter = filterValues[filterX];
57 accum[0] += curFilter * rowToFilter[filterX * 4 + 0];
58 accum[1] += curFilter * rowToFilter[filterX * 4 + 1];
59 accum[2] += curFilter * rowToFilter[filterX * 4 + 2];
60 if (hasAlpha) {
61 accum[3] += curFilter * rowToFilter[filterX * 4 + 3];
62 }
63 }
64
65 // Bring this value back in range. All of the filter scaling factors
66 // are in fixed point with kShiftBits bits of fractional part.
67 // Add rounding bias before truncating to avoid systematic darkening.
68 constexpr int kRound = 1 << (SkConvolutionFilter1D::kShiftBits - 1);
69 accum[0] = (accum[0] + kRound) >> SkConvolutionFilter1D::kShiftBits;
70 accum[1] = (accum[1] + kRound) >> SkConvolutionFilter1D::kShiftBits;
71 accum[2] = (accum[2] + kRound) >> SkConvolutionFilter1D::kShiftBits;
72
73 if (hasAlpha) {
74 accum[3] = (accum[3] + kRound) >> SkConvolutionFilter1D::kShiftBits;
75 }
76
77 // Store the new pixel.
78 outRow[outX * 4 + 0] = ClampTo8(accum[0]);
79 outRow[outX * 4 + 1] = ClampTo8(accum[1]);
80 outRow[outX * 4 + 2] = ClampTo8(accum[2]);
81 if (hasAlpha) {
82 outRow[outX * 4 + 3] = ClampTo8(accum[3]);
83 }
84 }
85}
86
87// Does vertical convolution to produce one output row. The filter values and
88// length are given in the first two parameters. These are applied to each
89// of the rows pointed to in the |sourceDataRows| array, with each row
90// being |pixelWidth| wide.
91//
92// The output must have room for |pixelWidth * 4| bytes.
93template <bool hasAlpha>
94void ConvolveVertically(
95 const SkConvolutionFilter1D::ConvolutionFixed* filterValues,
96 int filterLength, unsigned char* const* sourceDataRows, int pixelWidth,
97 unsigned char* outRow) {
98 // We go through each column in the output and do a vertical convolution,
99 // generating one output pixel each time.
100 for (int outX = 0; outX < pixelWidth; outX++) {
101 // Compute the number of bytes over in each row that the current column
102 // we're convolving starts at. The pixel will cover the next 4 bytes.
103 int byteOffset = outX * 4;
104
105 // Apply the filter to one column of pixels.
106 int accum[4] = {0};
107 for (int filterY = 0; filterY < filterLength; filterY++) {
108 SkConvolutionFilter1D::ConvolutionFixed curFilter = filterValues[filterY];
109 accum[0] += curFilter * sourceDataRows[filterY][byteOffset + 0];
110 accum[1] += curFilter * sourceDataRows[filterY][byteOffset + 1];
111 accum[2] += curFilter * sourceDataRows[filterY][byteOffset + 2];
112 if (hasAlpha) {
113 accum[3] += curFilter * sourceDataRows[filterY][byteOffset + 3];
114 }
115 }
116
117 // Bring this value back in range. All of the filter scaling factors
118 // are in fixed point with kShiftBits bits of precision.
119 constexpr int kRound = 1 << (SkConvolutionFilter1D::kShiftBits - 1);
120 accum[0] = (accum[0] + kRound) >> SkConvolutionFilter1D::kShiftBits;
121 accum[1] = (accum[1] + kRound) >> SkConvolutionFilter1D::kShiftBits;
122 accum[2] = (accum[2] + kRound) >> SkConvolutionFilter1D::kShiftBits;
123 if (hasAlpha) {
124 accum[3] = (accum[3] + kRound) >> SkConvolutionFilter1D::kShiftBits;
125 }
126
127 // Store the new pixel.
128 outRow[byteOffset + 0] = ClampTo8(accum[0]);
129 outRow[byteOffset + 1] = ClampTo8(accum[1]);
130 outRow[byteOffset + 2] = ClampTo8(accum[2]);
131
132 if (hasAlpha) {
133 unsigned char alpha = ClampTo8(accum[3]);
134
135 // Make sure the alpha channel doesn't come out smaller than any of the
136 // color channels. We use premultipled alpha channels, so this should
137 // never happen, but rounding errors will cause this from time to time.
138 // These "impossible" colors will cause overflows (and hence random pixel
139 // values) when the resulting bitmap is drawn to the screen.
140 //
141 // We only need to do this when generating the final output row (here).
142 int maxColorChannel =
143 std::max(outRow[byteOffset + 0],
144 std::max(outRow[byteOffset + 1], outRow[byteOffset + 2]));
145 if (alpha < maxColorChannel) {
146 outRow[byteOffset + 3] = maxColorChannel;
147 } else {
148 outRow[byteOffset + 3] = alpha;
149 }
150 } else {
151 // No alpha channel, the image is opaque.
152 outRow[byteOffset + 3] = 0xff;
153 }
154 }
155}
156
157// Convolves horizontally along a single row. The row data is given in
158// |srcData| and continues for the numValues() of the filter.
159void ConvolveHorizontallyA8(const unsigned char* srcData,
160 const SkConvolutionFilter1D& filter,
161 unsigned char* outRow) {
162 // Loop over each pixel on this row in the output image.
163 int numValues = filter.numValues();
164 for (int outX = 0; outX < numValues; outX++) {
165 // Get the filter that determines the current output pixel.
166 int filterOffset, filterLength;
167 const SkConvolutionFilter1D::ConvolutionFixed* filterValues =
168 filter.FilterForValue(outX, &filterOffset, &filterLength);
169
170 // Compute the first pixel in this row that the filter affects. It will
171 // touch |filterLength| pixels (4 bytes each) after this.
172 const unsigned char* rowToFilter = &srcData[filterOffset];
173
174 // Apply the filter to the row to get the destination pixel in |accum|.
175 int accum = 0;
176 for (int filterX = 0; filterX < filterLength; filterX++) {
177 SkConvolutionFilter1D::ConvolutionFixed curFilter = filterValues[filterX];
178 accum += curFilter * rowToFilter[filterX];
179 }
180
181 // Bring this value back in range. All of the filter scaling factors
182 // are in fixed point with kShiftBits bits of fractional part.
183 constexpr int kRound = 1 << (SkConvolutionFilter1D::kShiftBits - 1);
184 accum = (accum + kRound) >> SkConvolutionFilter1D::kShiftBits;
185
186 // Store the new pixel.
187 outRow[outX] = ClampTo8(accum);
188 }
189}
190
191// Does vertical convolution to produce one output row. The filter values and
192// length are given in the first two parameters. These are applied to each
193// of the rows pointed to in the |sourceDataRows| array, with each row
194// being |pixelWidth| wide.
195//
196// The output must have room for |pixelWidth| bytes.
197void ConvolveVerticallyA8(
198 const SkConvolutionFilter1D::ConvolutionFixed* filterValues,
199 int filterLength, unsigned char* const* sourceDataRows, int pixelWidth,
200 unsigned char* outRow) {
201 // We go through each column in the output and do a vertical convolution,
202 // generating one output pixel each time.
203 for (int outX = 0; outX < pixelWidth; outX++) {
204 // Apply the filter to one column of pixels.
205 int accum = 0;
206 for (int filterY = 0; filterY < filterLength; filterY++) {
207 SkConvolutionFilter1D::ConvolutionFixed curFilter = filterValues[filterY];
208 accum += curFilter * sourceDataRows[filterY][outX];
209 }
210
211 // Bring this value back in range. All of the filter scaling factors
212 // are in fixed point with kShiftBits bits of precision.
213 constexpr int kRound = 1 << (SkConvolutionFilter1D::kShiftBits - 1);
214 accum = (accum + kRound) >> SkConvolutionFilter1D::kShiftBits;
215
216 // Store the new pixel.
217 outRow[outX] = ClampTo8(accum);
218 }
219}
220
221#ifdef USE_SSE21
222void convolve_vertically_avx2(const int16_t* filter, int filterLen,
223 uint8_t* const* srcRows, int width, uint8_t* out,
224 bool hasAlpha);
225void convolve_horizontally_avx2(const unsigned char* srcData,
226 const SkConvolutionFilter1D& filter,
227 unsigned char* outRow, bool hasAlpha);
228void convolve_horizontally_sse2(const unsigned char* srcData,
229 const SkConvolutionFilter1D& filter,
230 unsigned char* outRow, bool hasAlpha);
231void convolve_vertically_sse2(const int16_t* filter, int filterLen,
232 uint8_t* const* srcRows, int width, uint8_t* out,
233 bool hasAlpha);
234#elif defined(USE_NEON)
235void convolve_horizontally_neon(const unsigned char* srcData,
236 const SkConvolutionFilter1D& filter,
237 unsigned char* outRow, bool hasAlpha);
238void convolve_vertically_neon(const int16_t* filter, int filterLen,
239 uint8_t* const* srcRows, int width, uint8_t* out,
240 bool hasAlpha);
241#endif
242
243void convolve_horizontally(const unsigned char* srcData,
244 const SkConvolutionFilter1D& filter,
245 unsigned char* outRow, SurfaceFormat format) {
246 if (format == SurfaceFormat::A8) {
247 ConvolveHorizontallyA8(srcData, filter, outRow);
248 return;
249 }
250
251 bool hasAlpha = !IsOpaque(format);
252#ifdef USE_SSE21
253 if (mozilla::supports_avx2()) {
254 convolve_horizontally_avx2(srcData, filter, outRow, hasAlpha);
255 return;
256 }
257 if (mozilla::supports_sse2()) {
258 convolve_horizontally_sse2(srcData, filter, outRow, hasAlpha);
259 return;
260 }
261#elif defined(USE_NEON)
262 if (mozilla::supports_neon()) {
263 convolve_horizontally_neon(srcData, filter, outRow, hasAlpha);
264 return;
265 }
266#endif
267 if (hasAlpha) {
268 ConvolveHorizontally<true>(srcData, filter, outRow);
269 } else {
270 ConvolveHorizontally<false>(srcData, filter, outRow);
271 }
272}
273
274void convolve_vertically(
275 const SkConvolutionFilter1D::ConvolutionFixed* filterValues,
276 int filterLength, unsigned char* const* sourceDataRows, int pixelWidth,
277 unsigned char* outRow, SurfaceFormat format) {
278 if (format == SurfaceFormat::A8) {
279 ConvolveVerticallyA8(filterValues, filterLength, sourceDataRows, pixelWidth,
280 outRow);
281 return;
282 }
283
284 bool hasAlpha = !IsOpaque(format);
285#ifdef USE_SSE21
286 if (mozilla::supports_avx2()) {
287 convolve_vertically_avx2(filterValues, filterLength, sourceDataRows,
288 pixelWidth, outRow, hasAlpha);
289 return;
290 }
291 if (mozilla::supports_sse2()) {
292 convolve_vertically_sse2(filterValues, filterLength, sourceDataRows,
293 pixelWidth, outRow, hasAlpha);
294 return;
295 }
296#elif defined(USE_NEON)
297 if (mozilla::supports_neon()) {
298 convolve_vertically_neon(filterValues, filterLength, sourceDataRows,
299 pixelWidth, outRow, hasAlpha);
300 return;
301 }
302#endif
303 if (hasAlpha) {
304 ConvolveVertically<true>(filterValues, filterLength, sourceDataRows,
305 pixelWidth, outRow);
306 } else {
307 ConvolveVertically<false>(filterValues, filterLength, sourceDataRows,
308 pixelWidth, outRow);
309 }
310}
311
312// Stores a list of rows in a circular buffer. The usage is you write into it
313// by calling AdvanceRow. It will keep track of which row in the buffer it
314// should use next, and the total number of rows added.
315class CircularRowBuffer {
316 public:
317 // The number of pixels in each row is given in |sourceRowPixelWidth|.
318 // The maximum number of rows needed in the buffer is |maxYFilterSize|
319 // (we only need to store enough rows for the biggest filter).
320 //
321 // We use the |firstInputRow| to compute the coordinates of all of the
322 // following rows returned by Advance().
323 CircularRowBuffer(int destRowPixelWidth, int maxYFilterSize,
324 int firstInputRow)
325 : fRowByteWidth(destRowPixelWidth * 4),
326 fNumRows(maxYFilterSize),
327 fNextRow(0),
328 fNextRowCoordinate(firstInputRow) {}
329
330 bool AllocBuffer() {
331 return fBuffer.resize(fRowByteWidth * fNumRows) &&
332 fRowAddresses.resize(fNumRows);
333 }
334
335 // Moves to the next row in the buffer, returning a pointer to the beginning
336 // of it.
337 unsigned char* advanceRow() {
338 unsigned char* row = &fBuffer[fNextRow * fRowByteWidth];
339 fNextRowCoordinate++;
340
341 // Set the pointer to the next row to use, wrapping around if necessary.
342 fNextRow++;
343 if (fNextRow == fNumRows) {
344 fNextRow = 0;
345 }
346 return row;
347 }
348
349 // Returns a pointer to an "unrolled" array of rows. These rows will start
350 // at the y coordinate placed into |*firstRowIndex| and will continue in
351 // order for the maximum number of rows in this circular buffer.
352 //
353 // The |firstRowIndex_| may be negative. This means the circular buffer
354 // starts before the top of the image (it hasn't been filled yet).
355 unsigned char* const* GetRowAddresses(int* firstRowIndex) {
356 // Example for a 4-element circular buffer holding coords 6-9.
357 // Row 0 Coord 8
358 // Row 1 Coord 9
359 // Row 2 Coord 6 <- fNextRow = 2, fNextRowCoordinate = 10.
360 // Row 3 Coord 7
361 //
362 // The "next" row is also the first (lowest) coordinate. This computation
363 // may yield a negative value, but that's OK, the math will work out
364 // since the user of this buffer will compute the offset relative
365 // to the firstRowIndex and the negative rows will never be used.
366 *firstRowIndex = fNextRowCoordinate - fNumRows;
367
368 int curRow = fNextRow;
369 for (int i = 0; i < fNumRows; i++) {
370 fRowAddresses[i] = &fBuffer[curRow * fRowByteWidth];
371
372 // Advance to the next row, wrapping if necessary.
373 curRow++;
374 if (curRow == fNumRows) {
375 curRow = 0;
376 }
377 }
378 return &fRowAddresses[0];
379 }
380
381 private:
382 // The buffer storing the rows. They are packed, each one fRowByteWidth.
383 mozilla::Vector<unsigned char> fBuffer;
384
385 // Number of bytes per row in the |buffer|.
386 int fRowByteWidth;
387
388 // The number of rows available in the buffer.
389 int fNumRows;
390
391 // The next row index we should write into. This wraps around as the
392 // circular buffer is used.
393 int fNextRow;
394
395 // The y coordinate of the |fNextRow|. This is incremented each time a
396 // new row is appended and does not wrap.
397 int fNextRowCoordinate;
398
399 // Buffer used by GetRowAddresses().
400 mozilla::Vector<unsigned char*> fRowAddresses;
401};
402
403SkConvolutionFilter1D::SkConvolutionFilter1D() : fMaxFilter(0) {}
404
405bool SkConvolutionFilter1D::AddFilter(int filterOffset,
406 const ConvolutionFixed* filterValues,
407 int filterLength) {
408 // It is common for leading/trailing filter values to be zeros. In such
409 // cases it is beneficial to only store the central factors.
410 // For a scaling to 1/4th in each dimension using a Lanczos-2 filter on
411 // a 1080p image this optimization gives a ~10% speed improvement.
412 int filterSize = filterLength;
413 int firstNonZero = 0;
414 while (firstNonZero < filterLength && filterValues[firstNonZero] == 0) {
415 firstNonZero++;
416 }
417
418 if (firstNonZero < filterLength) {
419 // Here we have at least one non-zero factor.
420 int lastNonZero = filterLength - 1;
421 while (lastNonZero >= 0 && filterValues[lastNonZero] == 0) {
422 lastNonZero--;
423 }
424
425 filterOffset += firstNonZero;
426 filterLength = lastNonZero + 1 - firstNonZero;
427 MOZ_ASSERT(filterLength > 0)do { static_assert( mozilla::detail::AssertionConditionType<
decltype(filterLength > 0)>::isValid, "invalid assertion condition"
); if ((__builtin_expect(!!(!(!!(filterLength > 0))), 0)))
{ do { } while (false); MOZ_ReportAssertionFailure("filterLength > 0"
, "./../../../gfx/2d/SkConvolver.cpp", 427); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "filterLength > 0" ")"); do { MOZ_CrashSequence
(__null, 427); __attribute__((nomerge)) ::abort(); } while (false
); } } while (false)
;
428
429 if (!fFilterValues.append(&filterValues[firstNonZero], filterLength)) {
430 return false;
431 }
432 } else {
433 // Here all the factors were zeroes.
434 filterLength = 0;
435 }
436
437 FilterInstance instance = {
438 // We pushed filterLength elements onto fFilterValues
439 int(fFilterValues.length()) - filterLength, filterOffset, filterLength,
440 filterSize};
441 if (!fFilters.append(instance)) {
442 if (filterLength > 0) {
443 fFilterValues.shrinkBy(filterLength);
444 }
445 return false;
446 }
447
448 fMaxFilter = std::max(fMaxFilter, filterLength);
449 return true;
450}
451
452bool SkConvolutionFilter1D::ComputeFilterValues(
453 const SkBitmapFilter& aBitmapFilter, int32_t aSrcSize, int32_t aDstSize) {
454 // When we're doing a magnification, the scale will be larger than one. This
455 // means the destination pixels are much smaller than the source pixels, and
456 // that the range covered by the filter won't necessarily cover any source
457 // pixel boundaries. Therefore, we use these clamped values (max of 1) for
458 // some computations.
459 float scale = float(aDstSize) / float(aSrcSize);
460 float clampedScale = std::min(1.0f, scale);
461 // This is how many source pixels from the center we need to count
462 // to support the filtering function.
463 float srcSupport = aBitmapFilter.width() / clampedScale;
464 float invScale = 1.0f / scale;
465
466 mozilla::Vector<float, 64> filterValues;
467 mozilla::Vector<ConvolutionFixed, 64> fixedFilterValues;
468
469 // Loop over all pixels in the output range. We will generate one set of
470 // filter values for each one. Those values will tell us how to blend the
471 // source pixels to compute the destination pixel.
472
473 // This value is computed based on how SkTDArray::resizeStorageToAtLeast works
474 // in order to ensure that it does not overflow or assert. That functions
475 // computes
476 // n+4 + (n+4)/4
477 // and we want to to fit in a 32 bit signed int. Equating that to 2^31-1 and
478 // solving n gives n = (2^31-6)*4/5 = 1717986913.6
479 const int32_t maxToPassToReserveAdditional = 1717986913;
480
481 int32_t filterValueCount = int32_t(ceilf(aDstSize * srcSupport * 2));
482 if (aDstSize > maxToPassToReserveAdditional || filterValueCount < 0 ||
483 filterValueCount > maxToPassToReserveAdditional ||
484 !reserveAdditional(aDstSize, filterValueCount)) {
485 return false;
486 }
487 size_t oldFiltersLength = fFilters.length();
488 size_t oldFilterValuesLength = fFilterValues.length();
489 int oldMaxFilter = fMaxFilter;
490 for (int32_t destI = 0; destI < aDstSize; destI++) {
491 // This is the pixel in the source directly under the pixel in the dest.
492 // Note that we base computations on the "center" of the pixels. To see
493 // why, observe that the destination pixel at coordinates (0, 0) in a 5.0x
494 // downscale should "cover" the pixels around the pixel with *its center*
495 // at coordinates (2.5, 2.5) in the source, not those around (0, 0).
496 // Hence we need to scale coordinates (0.5, 0.5), not (0, 0).
497 float srcPixel = (static_cast<float>(destI) + 0.5f) * invScale;
498
499 // Compute the (inclusive) range of source pixels the filter covers.
500 // Clamp in the integer domain to avoid float rounding imprecision with
501 // values near int32 extremes.
502 int32_t srcBegin =
503 int32_t(std::clamp(int64_t(floorf(srcPixel - srcSupport)), int64_t(0),
504 int64_t(aSrcSize) - 1));
505 int32_t srcEnd = int32_t(std::clamp(int64_t(ceilf(srcPixel + srcSupport)),
506 int64_t(0), int64_t(aSrcSize) - 1));
507
508 // Compute the unnormalized filter value at each location of the source
509 // it covers.
510
511 // Sum of the filter values for normalizing.
512 // Distance from the center of the filter, this is the filter coordinate
513 // in source space. We also need to consider the center of the pixel
514 // when comparing distance against 'srcPixel'. In the 5x downscale
515 // example used above the distance from the center of the filter to
516 // the pixel with coordinates (2, 2) should be 0, because its center
517 // is at (2.5, 2.5).
518 int32_t filterCount = srcEnd - srcBegin + 1;
519 if (filterCount <= 0 || !filterValues.resize(filterCount) ||
520 !fixedFilterValues.resize(filterCount)) {
521 return false;
522 }
523
524 float destFilterDist =
525 (static_cast<float>(srcBegin) + 0.5f - srcPixel) * clampedScale;
526 float filterSum = 0.0f;
527 for (int32_t index = 0; index < filterCount; index++) {
528 float filterValue = aBitmapFilter.evaluate(destFilterDist);
529 filterValues[index] = filterValue;
530 filterSum += filterValue;
531 destFilterDist += clampedScale;
532 }
533
534 // The filter must be normalized so that we don't affect the brightness of
535 // the image. Convert to normalized fixed point.
536 ConvolutionFixed fixedSum = 0;
537 float invFilterSum = 1.0f / filterSum;
538 for (int32_t fixedI = 0; fixedI < filterCount; fixedI++) {
539 ConvolutionFixed curFixed = ToFixed(filterValues[fixedI] * invFilterSum);
540 fixedSum += curFixed;
541 fixedFilterValues[fixedI] = curFixed;
542 }
543
544 // The conversion to fixed point will leave some rounding errors, which
545 // we add back in to avoid affecting the brightness of the image. We
546 // arbitrarily add this to the center of the filter array (this won't always
547 // be the center of the filter function since it could get clipped on the
548 // edges, but it doesn't matter enough to worry about that case).
549 ConvolutionFixed leftovers = ToFixed(1) - fixedSum;
550 fixedFilterValues[filterCount / 2] += leftovers;
551
552 if (!AddFilter(srcBegin, fixedFilterValues.begin(), filterCount)) {
553 fFilters.shrinkTo(oldFiltersLength);
554 fFilterValues.shrinkTo(oldFilterValuesLength);
555 fMaxFilter = oldMaxFilter;
556 return false;
557 }
558 }
559
560 return maxFilter() > 0 && numValues() == aDstSize;
561}
562
563// Does a two-dimensional convolution on the given source image.
564//
565// It is assumed the source pixel offsets referenced in the input filters
566// reference only valid pixels, so the source image size is not required. Each
567// row of the source image starts |sourceByteRowStride| after the previous
568// one (this allows you to have rows with some padding at the end).
569//
570// The result will be put into the given output buffer. The destination image
571// size will be xfilter.numValues() * yfilter.numValues() pixels. It will be
572// in rows of exactly xfilter.numValues() * 4 bytes.
573//
574// |sourceHasAlpha| is a hint that allows us to avoid doing computations on
575// the alpha channel if the image is opaque. If you don't know, set this to
576// true and it will work properly, but setting this to false will be a few
577// percent faster if you know the image is opaque.
578//
579// The layout in memory is assumed to be 4-bytes per pixel in B-G-R-A order
580// (this is ARGB when loaded into 32-bit words on a little-endian machine).
581/**
582 * Returns false if it was unable to perform the convolution/rescale. in which
583 * case the output buffer is assumed to be undefined.
584 */
585bool BGRAConvolve2D(const unsigned char* sourceData, int sourceByteRowStride,
586 SurfaceFormat format, const SkConvolutionFilter1D& filterX,
587 const SkConvolutionFilter1D& filterY,
588 int outputByteRowStride, unsigned char* output) {
589 int maxYFilterSize = filterY.maxFilter();
590
591 // The next row in the input that we will generate a horizontally
592 // convolved row for. If the filter doesn't start at the beginning of the
593 // image (this is the case when we are only resizing a subset), then we
594 // don't want to generate any output rows before that. Compute the starting
595 // row for convolution as the first pixel for the first vertical filter.
596 int filterOffset = 0, filterLength = 0;
597 const SkConvolutionFilter1D::ConvolutionFixed* filterValues =
Value stored to 'filterValues' during its initialization is never read
598 filterY.FilterForValue(0, &filterOffset, &filterLength);
599 int nextXRow = filterOffset;
600
601 // We loop over each row in the input doing a horizontal convolution. This
602 // will result in a horizontally convolved image. We write the results into
603 // a circular buffer of convolved rows and do vertical convolution as rows
604 // are available. This prevents us from having to store the entire
605 // intermediate image and helps cache coherency.
606 // We will need four extra rows to allow horizontal convolution could be done
607 // simultaneously. We also pad each row in row buffer to be aligned-up to
608 // 32 bytes.
609 // TODO(jiesun): We do not use aligned load from row buffer in vertical
610 // convolution pass yet. Somehow Windows does not like it.
611 int rowBufferWidth = (filterX.numValues() + 31) & ~0x1F;
612 int rowBufferHeight = maxYFilterSize;
613
614 // check for too-big allocation requests : crbug.com/528628
615 {
616 int64_t size = int64_t(rowBufferWidth) * int64_t(rowBufferHeight);
617 // need some limit, to avoid over-committing success from malloc, but then
618 // crashing when we try to actually use the memory.
619 // 100meg seems big enough to allow "normal" zoom factors and image sizes
620 // through while avoiding the crash seen by the bug (crbug.com/528628)
621 if (size > 100 * 1024 * 1024) {
622 // printf_stderr("BGRAConvolve2D: tmp allocation [%lld] too
623 // big\n", size);
624 return false;
625 }
626 }
627
628 CircularRowBuffer rowBuffer(rowBufferWidth, rowBufferHeight, filterOffset);
629 if (!rowBuffer.AllocBuffer()) {
630 return false;
631 }
632
633 // Loop over every possible output row, processing just enough horizontal
634 // convolutions to run each subsequent vertical convolution.
635 MOZ_ASSERT(outputByteRowStride >=do { static_assert( mozilla::detail::AssertionConditionType<
decltype(outputByteRowStride >= filterX.numValues() * BytesPerPixel
(format))>::isValid, "invalid assertion condition"); if ((
__builtin_expect(!!(!(!!(outputByteRowStride >= filterX.numValues
() * BytesPerPixel(format)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("outputByteRowStride >= filterX.numValues() * BytesPerPixel(format)"
, "./../../../gfx/2d/SkConvolver.cpp", 636); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "outputByteRowStride >= filterX.numValues() * BytesPerPixel(format)"
")"); do { MOZ_CrashSequence(__null, 636); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
636 filterX.numValues() * BytesPerPixel(format))do { static_assert( mozilla::detail::AssertionConditionType<
decltype(outputByteRowStride >= filterX.numValues() * BytesPerPixel
(format))>::isValid, "invalid assertion condition"); if ((
__builtin_expect(!!(!(!!(outputByteRowStride >= filterX.numValues
() * BytesPerPixel(format)))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("outputByteRowStride >= filterX.numValues() * BytesPerPixel(format)"
, "./../../../gfx/2d/SkConvolver.cpp", 636); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "outputByteRowStride >= filterX.numValues() * BytesPerPixel(format)"
")"); do { MOZ_CrashSequence(__null, 636); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
637 int numOutputRows = filterY.numValues();
638
639 // We need to check which is the last line to convolve before we advance 4
640 // lines in one iteration.
641 int lastFilterOffset, lastFilterLength;
642 filterY.FilterForValue(numOutputRows - 1, &lastFilterOffset,
643 &lastFilterLength);
644
645 for (int outY = 0; outY < numOutputRows; outY++) {
646 filterValues = filterY.FilterForValue(outY, &filterOffset, &filterLength);
647
648 // Generate output rows until we have enough to run the current filter.
649 while (nextXRow < filterOffset + filterLength) {
650 convolve_horizontally(
651 &sourceData[(uint64_t)nextXRow * sourceByteRowStride], filterX,
652 rowBuffer.advanceRow(), format);
653 nextXRow++;
654 }
655
656 // Compute where in the output image this row of final data will go.
657 unsigned char* curOutputRow = &output[(uint64_t)outY * outputByteRowStride];
658
659 // Get the list of rows that the circular buffer has, in order.
660 int firstRowInCircularBuffer;
661 unsigned char* const* rowsToConvolve =
662 rowBuffer.GetRowAddresses(&firstRowInCircularBuffer);
663
664 // Now compute the start of the subset of those rows that the filter needs.
665 unsigned char* const* firstRowForFilter =
666 &rowsToConvolve[filterOffset - firstRowInCircularBuffer];
667
668 convolve_vertically(filterValues, filterLength, firstRowForFilter,
669 filterX.numValues(), curOutputRow, format);
670 }
671 return true;
672}
673
674} // namespace skia