19uint16_t ftoabsu16(
float f) {
20 return static_cast<uint16_t
>(std::clamp<uint16_t>(
static_cast<int32_t
>(fabs(f)), 0, UINT16_MAX));
24int32_t edgeClamp(int32_t n, int32_t max) {
28 return (n >= max) ? max - 1 : n;
30int32_t edgeReflect(int32_t n, int32_t max) {
32 return n > -max ? -n : max - 1;
35 const int32_t max2 = 2 * max;
36 return n >= max2 ? 0 : max2 - n - 1;
40int32_t edgeWrap(int32_t n, int32_t max) {
44 const int32_t m = (-n) % max;
45 return m ? max - m : 0;
54 return (edgeReflect(v[1], h) * w + edgeReflect(v[0], w)) * pxLen + alphaOffs;
56 return (edgeWrap(v[1], h) * w + edgeWrap(v[0], w)) * pxLen + alphaOffs;
58 return (edgeClamp(v[1], h) * w + edgeClamp(v[0], w)) * pxLen + alphaOffs;
79 , alphaOffs(alphaOffs)
84 , searchRadius(ftoabsu16(2.0f * std::max<float>(reduceX, reduceY) * distanceSpread))
85 , offsX(sampleCentered ? reduceX / 2 : 0)
86 , offsY(sampleCentered ? reduceY / 2 : 0)
87 , alphaThreshold(alphaThreshold)
93 float samplePadded(Vec2i v)
const {
94 return this->srcImg[indexWith(this->edge, v - this->searchRadius, this->imgWidth, this->imgHeight, this->pxLen, this->alphaOffs)];
97 bool isInPadded(Vec2i v)
const {
98 return this->samplePadded(v) >= this->alphaThreshold;
101 const float *
const srcImg;
102 const float alphaThreshold;
103 const uint16_t imgHeight;
104 const uint16_t imgWidth;
105 const uint16_t reduceX, reduceY;
106 const uint16_t searchRadius;
107 const uint16_t offsX, offsY;
109 const uint8_t alphaOffs;
113float vecDist(Vec2f32 v0, Vec2f32 v1) {
114 return std::hypot(v0[0] - v1[0], v0[1] - v1[1]);
119static constexpr float BIG_EPSILON = 0.05f;
120static constexpr float SMALL_EPSILON = 1e-6f;
123float aaOffsetPadded(
const SampleParam *param, Vec2i v,
bool outside) {
124 const float alpha = param->samplePadded(v);
125 const float reach = outside ? 1.0f : 0.5f;
127 for (Vec2i offset : {Vec2i{-1, -1}, {0, -1}, {1, -1}, {-1, 0}, {1, 0}, {-1, 1}, {0, 1}, {1, 1}}) {
128 slope = fmax(slope, fabs(param->samplePadded(v + offset) - alpha) / std::hypot<float>(offset[0], offset[1]));
130 const float gap = fabs(alpha - 0.5f);
131 if (slope < SMALL_EPSILON || gap > reach * slope) {
132 return outside ? 0.0f : -1.0f;
134 return fmin(gap / slope, reach);
142static constexpr float inff = std::numeric_limits<float>::infinity();
143static constexpr double infd = std::numeric_limits<double>::infinity();
154void distanceTransform1D(
166 while (q0 < n && std::isinf(f[q0])) {
170 const int32_t m = (n - first + stride - 1) / stride;
171 std::fill(d, d + m, inff);
173 std::fill(argmin, argmin + m, -1);
182 for (int32_t q = q0 + 1; q < n; q++) {
183 if (std::isinf(f[q])) {
187 const double s = ((f[q] + sq<double>(q)) - (f[v[k]] + sq<double>(v[k]))) / (2.0 * (q - v[k]));
199 for (int32_t q = first, j = 0; q < n; q += stride, j++) {
200 while (z[k + 1] < q) {
203 const bool out = v[k] < q - window || v[k] > q + window - 1;
204 d[j] = out ? inff : f[v[k]] + sq<float>(q - v[k]);
206 argmin[j] = out ? -1 : v[k];
212 const SampleParam *param,
215 uint8_t dstAlphaOffs,
217 const float *tangentDiffusion,
220 const auto [padW, padH] = (math::Vec2ui{param->imgWidth, param->imgHeight} + 2 * param->searchRadius).values;
221 std::vector<uint8_t> mask(padW * padH);
222 for (uint32_t padY = 0; padY < padH; padY++) {
223 for (uint32_t padX = 0; padX < padW; padX++) {
224 mask[padX * padH + padY] = param->isInPadded(Vec2i{padX, padY});
228 float fRadius =
static_cast<float>(param->searchRadius);
229 uint16_t dstWidth = param->imgWidth / param->reduceX;
230 uint16_t dstHeight = param->imgHeight / param->reduceY;
232 std::vector<float> gradients(tangentDiffusion ?
static_cast<size_t>(dstWidth) * dstHeight : 0, -pi_f32 - BIG_EPSILON);
234 const bool needArgmin = tangentDiffusion || distanceAA;
235 const size_t padMax = std::max(padW, padH);
236 std::vector<float> df2D(padW * dstHeight);
237 std::vector<float> df1D(padMax);
238 std::vector<int32_t> argmin2D(needArgmin ? padW * dstHeight : 0);
239 std::vector<int32_t> argmin1D(padMax);
240 std::vector<float> col(padH);
241 std::vector<int32_t> seedTally(needArgmin ? padW + 1 : 0);
242 std::vector<int32_t> v(padMax);
243 std::vector<double> z(padMax + 1);
245 for (uint8_t seed : {1, 0}) {
246 for (uint32_t padX = 0; padX < padW; padX++) {
247 for (uint32_t padY = 0; padY < padH; padY++) {
248 col[padY] = mask[padX * padH + padY] == seed ? 0.0f : inff;
251 col.data(), padH, df1D.data(),
252 needArgmin ? argmin1D.data() :
nullptr,
255 param->offsY + param->searchRadius, param->reduceY
257 for (uint32_t dstY = 0; dstY < dstHeight; dstY++) {
258 df2D[dstY * padW + padX] = df1D[dstY];
260 argmin2D[dstY * padW + padX] = argmin1D[dstY];
265 for (uint32_t dstY = 0; dstY < dstHeight; dstY++) {
266 const int32_t padY = dstY * param->reduceY + param->offsY + param->searchRadius;
268 df2D.data() + dstY * padW, padW, df1D.data(),
269 needArgmin ? argmin1D.data() :
nullptr,
272 param->offsX + param->searchRadius, param->reduceX
278 for (uint32_t c = 0; c < padW; c++) {
279 seedTally[c + 1] = seedTally[c] + !std::isinf(df2D[dstY * padW + c]);
282 for (uint32_t dstX = 0; dstX < dstWidth; dstX++) {
283 const int32_t padX = dstX * param->reduceX + param->offsX + param->searchRadius;
284 if (mask[padX * padH + padY] == seed) {
287 const int32_t windowStart = padX - param->searchRadius;
288 const int32_t windowEnd = padX + param->searchRadius;
289 const bool seedInWindow = needArgmin && seedTally[windowEnd] > seedTally[windowStart];
290 float nearest = sqrtf(df1D[dstX]);
291 int32_t nearestX = seedInWindow ? argmin1D[dstX] : -1;
292 if (seedInWindow && nearest >= fRadius) {
293 float nearest2 = std::numeric_limits<float>::max();
294 int32_t windowCol = -1;
295 for (int32_t c = windowStart; c < windowEnd; c++) {
296 if (
const float dist2 = df2D[dstY * padW + c] + sq<float>(padX - c); dist2 < nearest2) {
301 if (windowCol >= 0) {
302 nearest = sqrtf(nearest2);
303 nearestX = windowCol;
306 int32_t nearestY = -1;
309 const float outer = nearest + 1;
310 const float outer2 = sq(outer);
311 const float inner2 = sq(nearest) - 1;
312 const int32_t loX = -std::min<int32_t>(outer, param->searchRadius);
313 const int32_t hiX = +std::min<int32_t>(outer + 1, param->searchRadius);
314 const float r2 = sq(param->searchRadius);
315 float preciseNearest = inff;
317 for (int32_t dX = loX; dX < hiX; dX++) {
318 const float dX2 = sq<float>(dX);
319 const float remHi = outer2 - dX2;
320 if (remHi < 0.0f || dX2 + r2 < inner2) {
323 const int32_t hi = sqrtf(remHi);
324 const int32_t lo = inner2 > dX2 ? ceilf(sqrtf(inner2 - dX2)) : 0;
325 const int32_t aaX = padX + dX;
326 const int32_t loY = -std::min<int32_t>(hi, param->searchRadius);
327 const int32_t hiY = +std::min<int32_t>(hi + 1, param->searchRadius);
328 for (int32_t dY = loY; dY < hiY; dY++) {
329 if (-lo < dY && dY < lo) {
333 const int32_t aaY = padY + dY;
334 if (mask[aaX * padH + aaY] != seed) {
348 float offsSeed = aaOffsetPadded(param, {aaX, aaY},
true);
349 if (seed && param->samplePadded({aaX, aaY}) < 0.5f) {
350 offsSeed = -offsSeed;
352 const float preciseDist =
static_cast<float>(std::hypot(dX, dY)) - offsSeed;
353 if (preciseDist < preciseNearest) {
354 preciseNearest = preciseDist;
360 if (preciseNearest < inff) {
361 nearest = preciseNearest;
364 const float offsRef = aaOffsetPadded(param, {padX, padY},
false);
365 if (offsRef >= 0.0f) {
366 nearest = fmin(nearest, offsRef);
370 if (nearestX >= 0 && nearestY < 0) {
371 nearestY = argmin2D[dstY * padW + nearestX];
373 nearest = fmax(nearest, 0.0f);
374 nearest = fmin(0.5f, nearest * 0.5f / fRadius);
379 const uint32_t iDst = dstY * dstWidth + dstX;
380 dstImg[iDst * dstPxLen + dstAlphaOffs] = 0.5 + nearest;
381 if (tangentDiffusion) {
382 gradients[iDst] = nearestX >= 0 ? atan2(nearestY - padY, nearestX - padX) : (-pi_f32 - BIG_EPSILON);
388 if (tangentDiffusion) {
426 static constexpr std::array<const int8_t, 8> hardSigns{0, 1, 1, 1, 0, -1, -1, -1};
432 static constexpr float eighth = pi_f32 / 4.0f;
434 const double quantum = *tangentDiffusion;
435 for (uint32_t diffY = 0; diffY < dstHeight; diffY++) {
436 for (uint32_t diffX = 0; diffX < dstWidth; diffX++) {
437 const float theta = gradients[diffY * dstWidth + diffX];
438 if (theta < -pi_f32 - BIG_EPSILON / 2.0f) {
441 float thetaNormal = fmod(theta + 2.5f * pi_f32, 2.0f * pi_f32);
442 uint8_t nEighths =
static_cast<uint32_t
>(std::floor(thetaNormal / eighth)) % 8;
443 Vec2i pos{diffX, diffY};
444 uint32_t iDiff = indexWith(param->edge, pos, dstWidth, dstHeight, dstPxLen, dstAlphaOffs);
445 double curPx = dstImg[iDiff];
447 double quantized = std::round(curPx / quantum) * quantum;
448 float quantErr = curPx - quantized;
449 dstImg[iDiff] = quantized;
450 bool atRight = diffX + 1 >= dstWidth;
451 bool bottomEdge = pos[1] >= dstHeight - 1;
453 if (atRight && bottomEdge) {
456 Vec2f32 normal{cos(thetaNormal), sin(thetaNormal)};
458 float dist0 = vecDist(normal, {hardSigns[(nEighths + 2) % 8], hardSigns[(nEighths + 0) % 8]});
459 float dist1 = vecDist(normal, {hardSigns[(nEighths + 3) % 8], hardSigns[(nEighths + 1) % 8]});
461 Vec2i offs0{hardSigns[(nEighths + 0) % 4 + 2], bottomEdge ? 0 : hardSigns[(nEighths + 0) % 4]};
462 Vec2i offs1{hardSigns[(nEighths + 1) % 4 + 2], bottomEdge ? 0 : hardSigns[(nEighths + 1) % 4]};
464 dstImg[indexWith(param->edge, pos + offs0, dstWidth, dstHeight, dstPxLen, dstAlphaOffs)] += quantErr * dist1 / (dist0 + dist1);
465 dstImg[indexWith(param->edge, pos + offs1, dstWidth, dstHeight, dstPxLen, dstAlphaOffs)] += quantErr * dist0 / (dist0 + dist1);
471 *valveQuirks =
false;
473 auto blackOutAndWarn = [&](
auto oi) {
474 float& px = dstImg[oi * dstPxLen + dstAlphaOffs];
475 if (fabs(px) > SMALL_EPSILON) {
481 for (
auto x = 0; x < dstWidth; x++) {
483 blackOutAndWarn(x + (dstHeight - 1) * dstWidth);
485 for (
auto y = 0; y < dstHeight; y++) {
486 blackOutAndWarn(y * dstWidth);
487 blackOutAndWarn(y * dstWidth + dstWidth - 1);
492bool operator!(
Flags flags) {
498 return red(format) == bpp(format) && bpp(format);
504[[nodiscard]] std::vector<std::byte>
DistanceMapping::alphaToDistance(std::span<const std::byte> imageData,
ImageFormat inFormat,
ImageFormat outFormat, uint16_t width, uint16_t height, uint16_t reduceX, uint16_t reduceY,
bool srgb,
float distanceSpread,
float alphaThreshold,
DistanceMapping::Flags flags,
DistanceMapping::Dither dither,
ImageConversion::ResizeFilter filter,
ImageConversion::ResizeEdge edge,
bool *valveQuirks) {
509 return isSingleChannel(format) ?
511 : (decompressedAlpha(format) || alpha(format)) ?
516 auto [intermediateRead, srcPxLen, srcAlphaOffs] = mkFormat(inFormat);
517 auto [intermediateWrite, dstPxLen, dstAlphaOffs] = mkFormat(outFormat);
524 || ftoabsu16(distanceSpread * reduceX) < 1
525 || ftoabsu16(distanceSpread * reduceY) < 1
526 || std::clamp(alphaThreshold, 0.0f, 1.0f) != alphaThreshold
532 bool inputNeedsConversion = intermediateRead != inFormat;
533 std::vector<std::byte> i_love_raii = inputNeedsConversion ?
convertImageDataToFormat(imageData, inFormat, intermediateRead, width, height) : std::vector<std::byte>(0);
534 std::span<const std::byte> srcImg = inputNeedsConversion ? i_love_raii : imageData;
536 SampleParam param(
reinterpret_cast<const float *
>(srcImg.data()), width, height, reduceX, reduceY, srcPxLen, srcAlphaOffs, distanceSpread, !!(flags &
Flags::SAMPLECENTERED), alphaThreshold, edge);
538 uint16_t dstWidth = width / reduceX, dstHeight = height / reduceY;
540 std::vector<std::byte> dstImg
541 = (!isSingleChannel(inFormat) && !isSingleChannel(outFormat)) ?
543 resizeImageData(imageData, inFormat, width, dstWidth, height, dstHeight, srgb,
true, filter, edge),
544 inFormat, intermediateWrite, dstWidth, dstHeight
547 std::vector<std::byte>(
sizeof(
float) * dstPxLen * dstWidth * dstHeight, std::byte{0x00});
549 int8_t alphaBits = decompressedAlpha(outFormat);
550 float quantum = (alphaBits > 0) ? 1.0f /
static_cast<float>((1 << alphaBits) - 1) : 1.0f;
554 reinterpret_cast<float *
>(dstImg.data()),
558 doGradientDither ? &quantum :
nullptr,
562 return (outFormat == intermediateWrite) ? dstImg :
convertImageDataToFormat(dstImg, intermediateWrite, outFormat, dstWidth, dstHeight);