Fix center coordinate when dst == 1 and src == 32768 in scale functions

In ScalePlane and ScalePlane_16, when dst_width == src_width and
dst_height == 1 with src_height >= 32768, set y to
(src_height << 15) - 32768 for bilinear filtering and src_height << 15
for point sampling so the center row is sampled instead of row 0.

In ScaleSlope, when dst_width == 1 and Abs(src_width) >= 32768 (or
dst_height == 1 and src_height >= 32768), compute the centered *x and *y
coordinates directly instead of overwriting dst_width or dst_height,
which previously sampled pixel/row 0 (or 0.5) and added
(Abs(src_width) - 1) * *dx when horizontally mirroring src_width < 0.
Use Abs(src_width) << 15 and src_height << 15 as the non-box step values
so ScaleARGB and ScaleUV treat the 32768x downscale as an even scale
factor rather than 1x. Save Abs(src_width) in a local variable.

In ScaleAddCols1_16_C, shift x >>= 16 before indexing src_ptr[x],
matching ScaleAddCols1_C.

Bug: b:573815252
Test: libyuv_unittest --gtest_filter=*ScalePlaneOversizeHeight*:*ARGBScaleOversizeWidthTo1*
Change-Id: Ic82319dae6291d17b98b056b5d64a4bbf02f7787
This commit is contained in:
Frank Barchard 2026-10-15 22:36:38 -07:00
parent 0b9e5c922d
commit dd4ac91d3f
4 changed files with 133 additions and 39 deletions

View File

@ -917,6 +917,7 @@ static void ScaleAddCols1_16_C(int dst_width,
int boxwidth = MIN1(dx >> 16);
int scaleval = 65536 / (boxwidth * boxheight);
int i;
x >>= 16;
for (i = 0; i < dst_width; ++i) {
*dst_ptr++ = SumPixels_16(boxwidth, src_ptr + x) * scaleval >> 16;
x += boxwidth;
@ -2125,11 +2126,9 @@ int ScalePlane(const uint8_t* src,
int dy = 0;
int y = 0;
// Check for 1 pixel and avoid FixedDiv overflow.
// TODO(b/573815252): Fix center row calculation when dst_height == 1 and
// src_height == 32768.
if (dst_height == 1 && src_height >= 32768) {
dy = FixedDiv(src_height, src_height);
y = CENTERSTART(dy, -32768);
dy = 1 << 16;
y = filtering ? ((src_height << 15) - 32768) : (src_height << 15);
} else if (dst_height <= src_height) {
// When scaling down, use the center 2 rows to filter.
dy = FixedDiv(src_height, dst_height);
@ -2246,11 +2245,9 @@ int ScalePlane_16(const uint16_t* src,
int dy = 0;
int y = 0;
// Check for 1 pixel and avoid FixedDiv overflow.
// TODO(b/573815252): Fix center row calculation when dst_height == 1 and
// src_height == 32768.
if (dst_height == 1 && src_height >= 32768) {
dy = FixedDiv(src_height, src_height);
y = CENTERSTART(dy, -32768);
dy = 1 << 16;
y = filtering ? ((src_height << 15) - 32768) : (src_height << 15);
} else if (dst_height <= src_height) {
// When scaling down, use the center 2 rows to filter.
dy = FixedDiv(src_height, dst_height);

View File

@ -1639,31 +1639,41 @@ void ScaleSlope(int src_width,
assert(src_height != 0);
assert(dst_width > 0);
assert(dst_height > 0);
// Check for 1 pixel and avoid FixedDiv overflow.
// TODO(b/573815252): Fix center coordinate calculation when dst == 1 and
// src == 32768.
if (dst_width == 1 && Abs(src_width) >= 32768) {
dst_width = Abs(src_width);
}
if (dst_height == 1 && src_height >= 32768) {
dst_height = src_height;
}
const int abs_src_width = Abs(src_width);
if (filtering == kFilterBox) {
// Scale step for point sampling duplicates all pixels equally.
*dx = FixedDiv(Abs(src_width), dst_width);
*dy = FixedDiv(src_height, dst_height);
*x = 0;
*y = 0;
} else if (filtering == kFilterBilinear) {
// Scale step for bilinear sampling renders last pixel once for upsample.
if (dst_width <= Abs(src_width)) {
*dx = FixedDiv(Abs(src_width), dst_width);
*x = CENTERSTART(*dx, -32768); // Subtract 0.5 (32768) to center filter.
} else if (src_width > 1 && dst_width > 1) {
*dx = FixedDiv1(Abs(src_width), dst_width);
if (dst_width == 1 && abs_src_width >= 32768) {
// Avoid overflowing 32-bit signed int in FixedDiv(32768, 1) and box
// filter accumulation in row16.
*dx = 1 << 16;
*x = abs_src_width << 15;
} else {
*dx = FixedDiv(abs_src_width, dst_width);
*x = 0;
}
if (dst_height <= src_height) {
if (dst_height == 1 && src_height >= 32768) {
*dy = 1 << 16;
*y = src_height << 15;
} else {
*dy = FixedDiv(src_height, dst_height);
*y = 0;
}
} else if (filtering == kFilterBilinear) {
// Scale step for bilinear sampling renders last pixel once for upsample.
if (dst_width == 1 && abs_src_width >= 32768) {
*dx = abs_src_width << 15;
*x = (abs_src_width << 15) - 32768;
} else if (dst_width <= abs_src_width) {
*dx = FixedDiv(abs_src_width, dst_width);
*x = CENTERSTART(*dx, -32768); // Subtract 0.5 (32768) to center filter.
} else if (src_width > 1 && dst_width > 1) {
*dx = FixedDiv1(abs_src_width, dst_width);
*x = 0;
}
if (dst_height == 1 && src_height >= 32768) {
*dy = src_height << 15;
*y = (src_height << 15) - 32768;
} else if (dst_height <= src_height) {
*dy = FixedDiv(src_height, dst_height);
*y = CENTERSTART(*dy, -32768); // Subtract 0.5 (32768) to center filter.
} else if (src_height > 1 && dst_height > 1) {
@ -1672,21 +1682,39 @@ void ScaleSlope(int src_width,
}
} else if (filtering == kFilterLinear) {
// Scale step for bilinear sampling renders last pixel once for upsample.
if (dst_width <= Abs(src_width)) {
*dx = FixedDiv(Abs(src_width), dst_width);
if (dst_width == 1 && abs_src_width >= 32768) {
*dx = abs_src_width << 15;
*x = (abs_src_width << 15) - 32768;
} else if (dst_width <= abs_src_width) {
*dx = FixedDiv(abs_src_width, dst_width);
*x = CENTERSTART(*dx, -32768); // Subtract 0.5 (32768) to center filter.
} else if (src_width > 1 && dst_width > 1) {
*dx = FixedDiv1(Abs(src_width), dst_width);
*dx = FixedDiv1(abs_src_width, dst_width);
*x = 0;
}
*dy = FixedDiv(src_height, dst_height);
*y = *dy >> 1;
if (dst_height == 1 && src_height >= 32768) {
*dy = src_height << 15;
*y = src_height << 15;
} else {
*dy = FixedDiv(src_height, dst_height);
*y = *dy >> 1;
}
} else {
// Scale step for point sampling duplicates all pixels equally.
*dx = FixedDiv(Abs(src_width), dst_width);
*dy = FixedDiv(src_height, dst_height);
*x = CENTERSTART(*dx, 0);
*y = CENTERSTART(*dy, 0);
if (dst_width == 1 && abs_src_width >= 32768) {
*dx = abs_src_width << 15;
*x = abs_src_width << 15;
} else {
*dx = FixedDiv(abs_src_width, dst_width);
*x = CENTERSTART(*dx, 0);
}
if (dst_height == 1 && src_height >= 32768) {
*dy = src_height << 15;
*y = src_height << 15;
} else {
*dy = FixedDiv(src_height, dst_height);
*y = CENTERSTART(*dy, 0);
}
}
// Negative src_width means horizontally mirror.
if (src_width < 0) {

View File

@ -653,6 +653,34 @@ TEST_F(LibYUVScaleTest, ARGBScaleDimensionOverflow) {
kFilterNone));
}
TEST_F(LibYUVScaleTest, ARGBScaleOversizeWidthTo1) {
const int kSrcWidth = 32768;
const int kSrcHeight = 2;
uint8_t* src = new uint8_t[kSrcWidth * kSrcHeight * 4];
memset(src, 0, kSrcWidth * kSrcHeight * 4);
for (int y = 0; y < kSrcHeight; ++y) {
memset(src + (y * kSrcWidth + 16383) * 4, 100, 4);
memset(src + (y * kSrcWidth + 16384) * 4, 200, 4);
}
uint8_t dst[2 * 4] = {};
for (int sign : {1, -1}) {
for (int dst_h : {1, 2}) {
for (FilterMode f :
{kFilterNone, kFilterLinear, kFilterBilinear, kFilterBox}) {
EXPECT_EQ(0, ARGBScale(src, kSrcWidth * 4, sign * kSrcWidth, kSrcHeight,
dst, 4, 1, dst_h, f));
if (f == kFilterNone) {
EXPECT_EQ(200, dst[0]);
} else {
EXPECT_NEAR(150, dst[0], 1);
}
}
}
}
delete[] src;
}
#if defined(__LP64__) || defined(_WIN64)
TEST_F(LibYUVScaleTest, ARGBScaleLargeImageOver2GB) {
const int kSrcWidth = 4;

View File

@ -811,20 +811,61 @@ TEST_F(LibYUVScaleTest, ScalePlaneOversizeHeight) {
const int kSrcHeight = 32768;
uint8_t* src = new uint8_t[kSrcWidth * kSrcHeight];
memset(src, 0, kSrcWidth * kSrcHeight);
memset(src + 16383 * kSrcWidth, 100, kSrcWidth);
memset(src + 16384 * kSrcWidth, 200, kSrcWidth);
uint8_t dst[4 * 2] = {};
for (int dst_w : {4, 2}) {
for (int dst_w : {4, 2, 1}) {
for (int dst_h : {1, 2}) {
for (FilterMode f :
{kFilterNone, kFilterLinear, kFilterBilinear, kFilterBox}) {
EXPECT_EQ(0, ScalePlane(src, kSrcWidth, kSrcWidth, kSrcHeight, dst,
dst_w, dst_w, dst_h, f));
if (dst_h == 1) {
const uint8_t expected =
(f == kFilterBilinear || (f == kFilterBox && dst_w >= 2)) ? 150
: 200;
EXPECT_EQ(expected, dst[0]);
}
EXPECT_EQ(0, ScalePlane(src, kSrcWidth, kSrcWidth, -kSrcHeight, dst,
dst_w, dst_w, dst_h, f));
if (dst_h == 1) {
const uint8_t expected =
(f == kFilterBilinear || (f == kFilterBox && dst_w >= 2)) ? 150
: 100;
EXPECT_EQ(expected, dst[0]);
}
}
}
}
delete[] src;
uint16_t* src16 = new uint16_t[kSrcWidth * kSrcHeight];
memset(src16, 0, kSrcWidth * kSrcHeight * sizeof(uint16_t));
for (int x = 0; x < kSrcWidth; ++x) {
src16[16383 * kSrcWidth + x] = 1000;
src16[16384 * kSrcWidth + x] = 2000;
}
uint16_t dst16[4] = {};
for (int dst_w : {4, 2, 1}) {
for (FilterMode f :
{kFilterNone, kFilterLinear, kFilterBilinear, kFilterBox}) {
EXPECT_EQ(0, ScalePlane_16(src16, kSrcWidth, kSrcWidth, kSrcHeight, dst16,
dst_w, dst_w, 1, f));
const uint16_t expected =
(f == kFilterBilinear || (f == kFilterBox && dst_w >= 2)) ? 1500
: 2000;
EXPECT_EQ(expected, dst16[0]);
}
}
// Test ScalePlane_16 horizontal 32768 -> 1 box filter (ScaleAddCols1_16_C).
for (int y = 0; y < kSrcWidth; ++y) {
src16[y * kSrcHeight + 16384] = 3000;
}
EXPECT_EQ(0, ScalePlane_16(src16, kSrcHeight, kSrcHeight, kSrcWidth, dst16, 1,
1, 1, kFilterBox));
EXPECT_EQ(3000, dst16[0]);
delete[] src16;
}
TEST_F(LibYUVScaleTest, ScalePlaneBoxMaxSrcHeight) {