Templatize bilinear up/down scaler

This commit is contained in:
Elias Batek 2025-01-28 02:29:45 +01:00
parent 7f91abfc0a
commit 31308e0777
1 changed files with 294 additions and 263 deletions

View File

@ -3019,237 +3019,108 @@ private void scaleToImpl(ScalingFilter filter)(const Pixmap source, Pixmap targe
// ==== Bilinear ==== // ==== Bilinear ====
static if ((filter == ScalingFilter.bilinear) || (filter == ScalingFilter.fauxLinear)) { static if ((filter == ScalingFilter.bilinear) || (filter == ScalingFilter.fauxLinear)) {
const Size delta = (target.size - source.size); void scaleToLinearImpl(ScalingDirection directionX, ScalingDirection directionY)() {
auto dst = PixmapScannerRW(target);
const ScalingDirection[2] directions = [ size_t y = 0;
scalingDirectionFromDelta(delta.width), foreach (dstLine; dst) {
scalingDirectionFromDelta(delta.height), foreach (x, ref pxDst; dstLine) {
]; const posDst = Point(x.castTo!int, y.castTo!int);
auto dst = PixmapScannerRW(target); const UDecimal[2] posSrc = [
posDst.x * ratios[idxX],
posDst.y * ratios[idxY],
];
size_t y = 0; const int[2] posSrcX = () {
foreach (dstLine; dst) { int[2] result;
foreach (x, ref pxDst; dstLine) { static if (directionX == none) {
const posDst = Point(x.castTo!int, y.castTo!int); result = [
posSrc[idxX].castTo!int,
const UDecimal[2] posSrc = [ posSrc[idxX].castTo!int,
posDst.x * ratios[idxX], ];
posDst.y * ratios[idxY], } else static if (directionX == up) {
]; result = [
min(sourceMaxX, posSrc[idxX].floor().castTo!int),
const int[2] posSrcX = () { min(sourceMaxX, posSrc[idxX].ceil().castTo!int),
int[2] result; ];
if (directions[idxX] == none) { } else /* if (directionX == down) */ {
result = [ const ratioXHalf = (ratios[idxX] >> 1);
posSrc[idxX].castTo!int, result = [
posSrc[idxX].castTo!int, max((posSrc[idxX] - ratioXHalf).roundEven().castTo!int, 0),
]; min((posSrc[idxX] + ratioXHalf).roundEven().castTo!int, sourceMaxX),
} else if (directions[idxX] == up) { ];
result = [ }
min(sourceMaxX, posSrc[idxX].floor().castTo!int), return result;
min(sourceMaxX, posSrc[idxX].ceil().castTo!int), }();
];
} else /* if (directions[0] == down) */ { const int[2] posSrcY = () {
const ratioXHalf = (ratios[idxX] >> 1); int[2] result;
result = [ static if (directionY == none) {
max((posSrc[idxX] - ratioXHalf).roundEven().castTo!int, 0), result = [
min((posSrc[idxX] + ratioXHalf).roundEven().castTo!int, sourceMaxX), posSrc[idxY].castTo!int,
]; posSrc[idxY].castTo!int,
} ];
return result; } else static if (directionY == up) {
}(); result = [
min(sourceMaxY, posSrc[idxY].floor().castTo!int),
const int[2] posSrcY = () { min(sourceMaxY, posSrc[idxY].ceil().castTo!int),
int[2] result; ];
if (directions[idxY] == none) { } else /* if (directionY == down) */ {
result = [ const ratioHalf = (ratios[idxY] >> 1);
posSrc[idxY].castTo!int, result = [
posSrc[idxY].castTo!int, max((posSrc[idxY] - ratioHalf).roundEven().castTo!int, 0),
]; min((posSrc[idxY] + ratioHalf).roundEven().castTo!int, sourceMaxY),
} else if (directions[idxY] == up) { ];
result = [ }
min(sourceMaxY, posSrc[idxY].floor().castTo!int), return result;
min(sourceMaxY, posSrc[idxY].ceil().castTo!int), }();
];
} else /* if (directions[idxY] == down) */ { enum idxL = 0, idxR = 1;
const ratioHalf = (ratios[idxY] >> 1); enum idxT = 0, idxB = 1;
result = [
max((posSrc[idxY] - ratioHalf).roundEven().castTo!int, 0), const Point[4] posNeighs = [
min((posSrc[idxY] + ratioHalf).roundEven().castTo!int, sourceMaxY), Point(posSrcX[idxL], posSrcY[idxT]),
]; Point(posSrcX[idxR], posSrcY[idxT]),
} Point(posSrcX[idxL], posSrcY[idxB]),
return result; Point(posSrcX[idxR], posSrcY[idxB]),
}(); ];
enum idxL = 0, idxR = 1; const Color[4] pxNeighs = [
enum idxT = 0, idxB = 1; source.getPixel(posNeighs[0]),
source.getPixel(posNeighs[1]),
const Point[4] posNeighs = [ source.getPixel(posNeighs[2]),
Point(posSrcX[idxL], posSrcY[idxT]), source.getPixel(posNeighs[3]),
Point(posSrcX[idxR], posSrcY[idxT]), ];
Point(posSrcX[idxL], posSrcY[idxB]),
Point(posSrcX[idxR], posSrcY[idxB]), enum idxTL = 0, idxTR = 1, idxBL = 2, idxBR = 3;
];
// ====== Faux bilinear ======
const Color[4] pxNeighs = [ static if (filter == ScalingFilter.fauxLinear) {
source.getPixel(posNeighs[0]), auto pxInt = Pixel(0, 0, 0, 0);
source.getPixel(posNeighs[1]),
source.getPixel(posNeighs[2]), foreach (immutable ib, ref c; pxInt.components) {
source.getPixel(posNeighs[3]), uint sum = 0;
]; foreach (const pxNeigh; pxNeighs) {
sum += (() @trusted => pxNeigh.components.ptr[ib])();
enum idxTL = 0, idxTR = 1, idxBL = 2, idxBR = 3; }
c = (sum >> 2).castTo!ubyte;
// ====== Faux bilinear ======
static if (filter == ScalingFilter.fauxLinear) {
auto pxInt = Pixel(0, 0, 0, 0);
foreach (immutable ib, ref c; pxInt.components) {
uint sum = 0;
foreach (const pxNeigh; pxNeighs) {
sum += (() @trusted => pxNeigh.components.ptr[ib])();
} }
c = (sum >> 2).castTo!ubyte;
} }
}
// ====== Proper bilinear (up) + Avg (down) ====== // ====== Proper bilinear (up) + Avg (down) ======
static if (filter == ScalingFilter.bilinear) { static if (filter == ScalingFilter.bilinear) {
auto pxInt = Pixel(0, 0, 0, 0); auto pxInt = Pixel(0, 0, 0, 0);
foreach (immutable ib, ref c; pxInt.components) { foreach (immutable ib, ref c; pxInt.components) {
ulong sampleX() { ulong sampleX() {
pragma(inline, true); pragma(inline, true);
if (directions[0] == none) { static if (directionX == none) {
return (() @trusted => pxNeighs[idxTL].components.ptr[ib])(); return (() @trusted => pxNeighs[idxTL].components.ptr[ib])();
} else if (directions[0] == down) { } else static if (directionX == down) {
const nSamples = 1 + posSrcX[idxR] - posSrcX[idxL]; const nSamples = 1 + posSrcX[idxR] - posSrcX[idxL];
const posSampling = Point(posSrcX[idxL], posSrcY[idxT]); const posSampling = Point(posSrcX[idxL], posSrcY[idxT]);
const samplingOffset = source.scanTo(posSampling); const samplingOffset = source.scanTo(posSampling);
const srcSamples = () @trusted {
return source.data.ptr[samplingOffset .. (samplingOffset + nSamples)];
}();
ulong xSum = 0;
foreach (srcSample; srcSamples) {
xSum += (() @trusted => srcSample.components.ptr[ib])();
}
return (xSum / nSamples);
} else /* if (directions[0] == up) */ {
ulong xSum = 0;
const ulong[2] weightsX = () {
ulong[2] result;
result[1] = posSrc[0].fractionalDigits;
result[0] = ulong(uint.max) + 1 - result[1];
return result;
}();
() @trusted {
xSum += (pxNeighs[idxTL].components.ptr[ib] * weightsX[0]);
xSum += (pxNeighs[idxTR].components.ptr[ib] * weightsX[1]);
}();
return (xSum >> 32);
}
}
ulong[2] sampleXDual() {
pragma(inline, true);
if (directions[0] == none) {
return () @trusted {
ulong[2] result = [
pxNeighs[idxTL].components.ptr[ib],
pxNeighs[idxBL].components.ptr[ib],
];
return result;
}();
} else if (directions[0] == down) {
const nSamples = 1 + posSrcX[idxR] - posSrcX[idxL];
const Point[2] posSampling = [
Point(posSrcX[idxL], posSrcY[idxT]),
Point(posSrcX[idxL], posSrcY[idxB]),
];
const int[2] samplingOffsets = [
source.scanTo(posSampling[0]),
source.scanTo(posSampling[1]),
];
const srcSamples2 = () @trusted {
const(const(Pixel)[])[2] result = [
source.data.ptr[samplingOffsets[0] .. (samplingOffsets[0] + nSamples)],
source.data.ptr[samplingOffsets[1] .. (samplingOffsets[1] + nSamples)],
];
return result;
}();
ulong[2] xSums = [0, 0];
foreach (idx, srcSamples; srcSamples2) {
foreach (srcSample; srcSamples) {
() @trusted { xSums.ptr[idx] += srcSample.components.ptr[ib]; }();
}
}
xSums[] /= nSamples;
return xSums;
} else /* if (directions[0] == up) */ {
ulong[2] xSums = [0, 0];
const ulong[2] weightsX = () {
ulong[2] result;
result[1] = posSrc[0].fractionalDigits;
result[0] = ulong(uint.max) + 1 - result[1];
return result;
}();
() @trusted {
xSums[0] += (pxNeighs[idxTL].components.ptr[ib] * weightsX[0]);
xSums[0] += (pxNeighs[idxTR].components.ptr[ib] * weightsX[1]);
xSums[1] += (pxNeighs[idxBL].components.ptr[ib] * weightsX[0]);
xSums[1] += (pxNeighs[idxBR].components.ptr[ib] * weightsX[1]);
}();
foreach (ref sum; xSums) {
sum >>= 32;
}
return xSums;
}
}
ulong sampleXMulti() {
pragma(inline, true);
const nLines = 1 + posSrcY[idxB] - posSrcY[idxT];
ulong ySum = 0;
alias ForeachLineCallback = ulong delegate(const Point posLine) @safe pure nothrow @nogc;
ulong foreachLine(scope ForeachLineCallback apply) {
ulong linesSum = 0;
foreach (lineY; posSrcY[idxT] .. (1 + posSrcY[idxB])) {
const posLine = Point(posSrcX[idxL], lineY);
linesSum += apply(posLine);
}
return linesSum;
}
if (directions[0] == none) {
ySum = foreachLine(delegate(const Point posLine) {
const pxSrc = source.getPixel(posLine);
return ulong((() @trusted => pxSrc.components.ptr[ib])());
});
} else if (directions[0] == down) {
const nSamples = 1 + posSrcX[idxR] - posSrcX[idxL];
ySum = foreachLine(delegate(const Point posLine) {
const samplingOffset = source.scanTo(posLine);
const srcSamples = () @trusted { const srcSamples = () @trusted {
return source.data.ptr[samplingOffset .. (samplingOffset + nSamples)]; return source.data.ptr[samplingOffset .. (samplingOffset + nSamples)];
}(); }();
@ -3260,14 +3131,8 @@ private void scaleToImpl(ScalingFilter filter)(const Pixmap source, Pixmap targe
xSum += (() @trusted => srcSample.components.ptr[ib])(); xSum += (() @trusted => srcSample.components.ptr[ib])();
} }
return xSum; return (xSum / nSamples);
}); } else /* if (directionX == up) */ {
ySum /= nSamples;
} else /* if (directions[0] == up) */ {
const nSamples = 1 + posSrcX[idxR] - posSrcX[idxL];
ySum = foreachLine(delegate(const Point posLine) {
ulong xSum = 0; ulong xSum = 0;
const ulong[2] weightsX = () { const ulong[2] weightsX = () {
@ -3277,55 +3142,221 @@ private void scaleToImpl(ScalingFilter filter)(const Pixmap source, Pixmap targe
return result; return result;
}(); }();
const samplingOffset = source.scanTo(posLine); () @trusted {
ubyte[2] pxcLR = () @trusted { xSum += (pxNeighs[idxTL].components.ptr[ib] * weightsX[0]);
ubyte[2] result = [ xSum += (pxNeighs[idxTR].components.ptr[ib] * weightsX[1]);
source.data.ptr[samplingOffset].components.ptr[ib], }();
source.data.ptr[samplingOffset + nSamples].components.ptr[ib],
return (xSum >> 32);
}
}
ulong[2] sampleXDual() {
pragma(inline, true);
static if (directionX == none) {
return () @trusted {
ulong[2] result = [
pxNeighs[idxTL].components.ptr[ib],
pxNeighs[idxBL].components.ptr[ib],
];
return result;
}();
} else static if (directionX == down) {
const nSamples = 1 + posSrcX[idxR] - posSrcX[idxL];
const Point[2] posSampling = [
Point(posSrcX[idxL], posSrcY[idxT]),
Point(posSrcX[idxL], posSrcY[idxB]),
];
const int[2] samplingOffsets = [
source.scanTo(posSampling[0]),
source.scanTo(posSampling[1]),
];
const srcSamples2 = () @trusted {
const(const(Pixel)[])[2] result = [
source.data.ptr[samplingOffsets[0] .. (samplingOffsets[0] + nSamples)],
source.data.ptr[samplingOffsets[1] .. (samplingOffsets[1] + nSamples)],
]; ];
return result; return result;
}(); }();
xSum += (pxcLR[idxL] * weightsX[idxL]); ulong[2] xSums = [0, 0];
xSum += (pxcLR[idxR] * weightsX[idxR]);
return (xSum >> 32); foreach (idx, srcSamples; srcSamples2) {
}); foreach (srcSample; srcSamples) {
() @trusted { xSums.ptr[idx] += srcSample.components.ptr[ib]; }();
}
}
xSums[] /= nSamples;
return xSums;
} else /* if (directionX == up) */ {
ulong[2] xSums = [0, 0];
const ulong[2] weightsX = () {
ulong[2] result;
result[1] = posSrc[0].fractionalDigits;
result[0] = ulong(uint.max) + 1 - result[1];
return result;
}();
() @trusted {
xSums[0] += (pxNeighs[idxTL].components.ptr[ib] * weightsX[0]);
xSums[0] += (pxNeighs[idxTR].components.ptr[ib] * weightsX[1]);
xSums[1] += (pxNeighs[idxBL].components.ptr[ib] * weightsX[0]);
xSums[1] += (pxNeighs[idxBR].components.ptr[ib] * weightsX[1]);
}();
foreach (ref sum; xSums) {
sum >>= 32;
}
return xSums;
}
} }
return (ySum / nLines); ulong sampleXMulti() {
} pragma(inline, true);
if (directions[idxY] == none) { const nLines = 1 + posSrcY[idxB] - posSrcY[idxT];
c = clamp255(sampleX()); ulong ySum = 0;
} else if (directions[idxY] == down) {
c = clamp255(sampleXMulti());
} else /* if (directions[idxY] == up) */ {
// looks ass
const ulong[2] weightsY = () {
ulong[2] result;
result[idxB] = posSrc[1].fractionalDigits;
result[idxT] = ulong(uint.max) + 1 - result[idxB];
return result;
}();
const xSums = sampleXDual(); alias ForeachLineCallback = ulong delegate(const Point posLine) @safe pure nothrow @nogc;
ulong foreachLine(scope ForeachLineCallback apply) {
ulong linesSum = 0;
foreach (lineY; posSrcY[idxT] .. (1 + posSrcY[idxB])) {
const posLine = Point(posSrcX[idxL], lineY);
linesSum += apply(posLine);
}
return linesSum;
}
ulong ySum = 0; static if (directionX == none) {
ySum += (xSums[idxT] * weightsY[idxT]); ySum = foreachLine(delegate(const Point posLine) {
ySum += (xSums[idxB] * weightsY[idxB]); const pxSrc = source.getPixel(posLine);
return ulong((() @trusted => pxSrc.components.ptr[ib])());
});
} else static if (directionX == down) {
const nSamples = 1 + posSrcX[idxR] - posSrcX[idxL];
const xySum = (ySum >> 32); ySum = foreachLine(delegate(const Point posLine) {
const samplingOffset = source.scanTo(posLine);
const srcSamples = () @trusted {
return source.data.ptr[samplingOffset .. (samplingOffset + nSamples)];
}();
c = clamp255(xySum); ulong xSum = 0;
foreach (srcSample; srcSamples) {
xSum += (() @trusted => srcSample.components.ptr[ib])();
}
return xSum;
});
ySum /= nSamples;
} else /* if (directionX == up) */ {
const nSamples = 1 + posSrcX[idxR] - posSrcX[idxL];
ySum = foreachLine(delegate(const Point posLine) {
ulong xSum = 0;
const ulong[2] weightsX = () {
ulong[2] result;
result[1] = posSrc[0].fractionalDigits;
result[0] = ulong(uint.max) + 1 - result[1];
return result;
}();
const samplingOffset = source.scanTo(posLine);
ubyte[2] pxcLR = () @trusted {
ubyte[2] result = [
source.data.ptr[samplingOffset].components.ptr[ib],
source.data.ptr[samplingOffset + nSamples].components.ptr[ib],
];
return result;
}();
xSum += (pxcLR[idxL] * weightsX[idxL]);
xSum += (pxcLR[idxR] * weightsX[idxR]);
return (xSum >> 32);
});
}
return (ySum / nLines);
}
static if (directionY == none) {
c = clamp255(sampleX());
} else static if (directionY == down) {
c = clamp255(sampleXMulti());
} else /* if (directionY == up) */ {
// looks ass
const ulong[2] weightsY = () {
ulong[2] result;
result[idxB] = posSrc[1].fractionalDigits;
result[idxT] = ulong(uint.max) + 1 - result[idxB];
return result;
}();
const xSums = sampleXDual();
ulong ySum = 0;
ySum += (xSums[idxT] * weightsY[idxT]);
ySum += (xSums[idxB] * weightsY[idxB]);
const xySum = (ySum >> 32);
c = clamp255(xySum);
}
} }
} }
pxDst = pxInt;
} }
pxDst = pxInt; ++y;
} }
}
++y; const Size delta = (target.size - source.size);
const ScalingDirection[2] directions = [
scalingDirectionFromDelta(delta.width),
scalingDirectionFromDelta(delta.height),
];
if (directions[0] == none) {
if (directions[1] == none) {
version (none)
scaleToLinearImpl!(none, none)();
else
return;
} else if (directions[1] == up) {
scaleToLinearImpl!(none, up)();
} else /* if (directions[1] == down) */ {
scaleToLinearImpl!(none, down)();
}
} else if (directions[0] == up) {
if (directions[1] == none) {
scaleToLinearImpl!(up, none)();
} else if (directions[1] == up) {
scaleToLinearImpl!(up, up)();
} else /* if (directions[1] == down) */ {
scaleToLinearImpl!(up, down)();
}
} else /* if (directions[0] == down) */ {
if (directions[1] == none) {
scaleToLinearImpl!(down, none)();
} else if (directions[1] == up) {
scaleToLinearImpl!(down, up)();
} else /* if (directions[1] == down) */ {
scaleToLinearImpl!(down, down)();
}
} }
} }
} }