Unravel Engine C++ Reference
Loading...
Searching...
No Matches
sdf.h
Go to the documentation of this file.
1/*
2 Copyright (C) 2014 Mikko Mononen (memon@inside.org)
3 Copyright (C) 2009-2012 Stefan Gustavson (stefan.gustavson@gmail.com)
4
5Permission is hereby granted, free of charge, to any person obtaining a copy
6of this software and associated documentation files (the "Software"), to deal
7in the Software without restriction, including without limitation the rights
8to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9copies of the Software, and to permit persons to whom the Software is
10furnished to do so, subject to the following conditions:
11
12The above copyright notice and this permission notice shall be included in
13all copies or substantial portions of the Software.
14
15THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21THE SOFTWARE.
22*/
23
24#ifndef SDF_FIXED_H
25#define SDF_FIXED_H
26
27// Sweep-and-update Euclidean distance transform of an antialised image for contour textures.
28// Based on edtaa3func.c by Stefan Gustavson.
29//
30// White (255) pixels are treated as object pixels, zero pixels are treated as background.
31// An attempt is made to treat antialiased edges correctly. The input image must have
32// pixels in the range [0,255], and the antialiased image should be a box-filter
33// sampling of the ideal, crisp edge. If the antialias region is more than 1 pixel wide,
34// the result from this transform will be inaccurate.
35// Pixels at image border are not calculated and are set to 0.
36//
37// The output distance field is encoded as bytes, where 0 = radius (outside) and 255 = -radius (inside).
38// Input and output can be the same buffer.
39// out - Output of the distance transform, one byte per pixel.
40// outstride - Bytes per row on output image.
41// radius - The radius of the distance field narrow band in pixels.
42// img - Input image, one byte per pixel.
43// width - Width if the image.
44// height - Height if the image.
45// stride - Bytes per row on input image.
46int sdfBuildDistanceField(unsigned char* out, int outstride, float radius,
47 const unsigned char* img, int width, int height, int stride);
48
49// Same as distXform, but does not allocate any memory.
50// The 'temp' array should be enough to fit width * height * sizeof(float) * 3 bytes.
51void sdfBuildDistanceFieldNoAlloc(unsigned char* out, int outstride, float radius,
52 const unsigned char* img, int width, int height, int stride,
53 unsigned char* temp);
54
55// This function converts the antialiased image where each pixel represents coverage (box-filter
56// sampling of the ideal, crisp edge) to a distance field with narrow band radius of sqrt(2).
57// This is the fastest way to turn antialised image to contour texture. This function is good
58// if you don't need the distance field for effects (i.e. fat outline or dropshadow).
59// Input and output buffers must be different.
60// out - Output of the distance transform, one byte per pixel.
61// outstride - Bytes per row on output image.
62// img - Input image, one byte per pixel.
63// width - Width if the image.
64// height - Height if the image.
65// stride - Bytes per row on input image.
66void sdfCoverageToDistanceField(unsigned char* out, int outstride,
67 const unsigned char* img, int width, int height, int stride);
68
69#endif //SDF_FIXED_H
70
71
72#ifdef SDF_IMPLEMENTATION
73
74#include <math.h>
75#include <stdlib.h>
76
77#define SDF_MAX_PASSES 10 // Maximum number of distance transform passes
78#define SDF_SLACK 0.001f // Controls how much smaller the neighbour value must be to cosnider, too small slack increse iteration count.
79#define SDF_SQRT2 1.4142136f // sqrt(2)
80#define SDF_BIG 1e+37f // Big value used to initialize the distance field.
81
82static float sdf__clamp01(float x)
83{
84 return x < 0.0f ? 0.0f : (x > 1.0f ? 1.0f : x);
85}
86
87void sdfCoverageToDistanceField(unsigned char* out, int outstride,
88 const unsigned char* img, int width, int height, int stride)
89{
90 int x, y;
91
92 // Zero out borders
93 for (x = 0; x < width; x++)
94 out[x] = 0;
95 for (y = 1; y < height; y++) {
96 out[y*outstride] = 0;
97 out[width-1+y*outstride] = 0;
98 }
99 for (x = 0; x < width; x++)
100 out[x+(height-1)*outstride] = 0;
101
102 for (y = 1; y < height-1; y++) {
103 for (x = 1; x < width-1; x++) {
104 int k = x + y * stride;
105 float d, gx, gy, glen, a, a1;
106
107 // Skip flat areas.
108 if (img[k] == 255) {
109 out[x+y*outstride] = 255;
110 continue;
111 }
112 if (img[k] == 0) {
113 // Special handling for cases where full opaque pixels are next to full transparent pixels.
114 // See: https://github.com/memononen/SDF/issues/2
115 int he = img[k-1] == 255 || img[k+1] == 255;
116 int ve = img[k-stride] == 255 || img[k+stride] == 255;
117 if (!he && !ve) {
118 out[x+y*outstride] = 0;
119 continue;
120 }
121 }
122
123 gx = -(float)img[k-stride-1] - SDF_SQRT2*(float)img[k-1] - (float)img[k+stride-1] + (float)img[k-stride+1] + SDF_SQRT2*(float)img[k+1] + (float)img[k+stride+1];
124 gy = -(float)img[k-stride-1] - SDF_SQRT2*(float)img[k-stride] - (float)img[k-stride+1] + (float)img[k+stride-1] + SDF_SQRT2*(float)img[k+stride] + (float)img[k+stride+1];
125 a = (float)img[k]/255.0f;
126 gx = fabsf(gx);
127 gy = fabsf(gy);
128 if (gx < 0.0001f || gy < 0.000f) {
129 d = (0.5f - a) * SDF_SQRT2;
130 } else {
131 glen = gx*gx + gy*gy;
132 glen = 1.0f / sqrtf(glen);
133 gx *= glen;
134 gy *= glen;
135 if (gx < gy) {
136 float temp = gx;
137 gx = gy;
138 gy = temp;
139 }
140 a1 = 0.5f*gy/gx;
141 if (a < a1) { // 0 <= a < a1
142 d = 0.5f*(gx + gy) - sqrtf(2.0f*gx*gy*a);
143 } else if (a < (1.0-a1)) { // a1 <= a <= 1-a1
144 d = (0.5f-a)*gx;
145 } else { // 1-a1 < a <= 1
146 d = -0.5f*(gx + gy) + sqrt(2.0f*gx*gy*(1.0f-a));
147 }
148 }
149 d *= 1.0f / SDF_SQRT2;
150 out[x+y*outstride] = (unsigned char)(sdf__clamp01(0.5f - d) * 255.0f);
151 }
152 }
153}
154
155static float sdf__edgedf(float gx, float gy, float a)
156{
157 float df, a1;
158 if ((gx == 0) || (gy == 0)) {
159 // Either A) gu or gv are zero, or B) both
160 // Linear approximation is A) correct or B) a fair guess
161 df = 0.5f - a;
162 } else {
163 // Everything is symmetric wrt sign and transposition,
164 // so move to first octant (gx>=0, gy>=0, gx>=gy) to
165 // avoid handling all possible edge directions.
166 gx = fabsf(gx);
167 gy = fabsf(gy);
168 if (gx < gy) {
169 float temp = gx;
170 gx = gy;
171 gy = temp;
172 }
173 a1 = 0.5f*gy/gx;
174 if (a < a1) { // 0 <= a < a1
175 df = 0.5f*(gx + gy) - sqrtf(2.0f*gx*gy*a);
176 } else if (a < (1.0-a1)) { // a1 <= a <= 1-a1
177 df = (0.5f-a)*gx;
178 } else { // 1-a1 < a <= 1
179 df = -0.5f*(gx + gy) + sqrt(2.0f*gx*gy*(1.0f-a));
180 }
181 }
182 return df;
183}
184
185struct SDFpoint {
186 float x,y;
187};
188
189static float sdf__distsqr(struct SDFpoint* a, struct SDFpoint* b)
190{
191 float dx = b->x - a->x, dy = b->y - a->y;
192 return dx*dx + dy*dy;
193}
194
195void sdfBuildDistanceFieldNoAlloc(unsigned char* out, int outstride, float radius,
196 const unsigned char* img, int width, int height, int stride,
197 unsigned char* temp)
198{
199 int i, x, y, pass;
200 float scale;
201 float* tdist = (float*)&temp[0];
202 struct SDFpoint* tpt = (struct SDFpoint*)&temp[width * height * sizeof(float)];
203
204 // Initialize buffers
205 for (i = 0; i < width*height; i++) {
206 tpt[i].x = 0;
207 tpt[i].y = 0;
208 tdist[i] = SDF_BIG;
209 }
210
211 // Calculate position of the anti-aliased pixels and distance to the boundary of the shape.
212 for (y = 1; y < height-1; y++) {
213 for (x = 1; x < width-1; x++) {
214 int tk, k = x + y * stride;
215 struct SDFpoint c = { (float)x, (float)y };
216 float d, gx, gy, glen;
217
218 // Skip flat areas.
219 if (img[k] == 255) continue;
220 if (img[k] == 0) {
221 // Special handling for cases where full opaque pixels are next to full transparent pixels.
222 // See: https://github.com/memononen/SDF/issues/2
223 int he = img[k-1] == 255 || img[k+1] == 255;
224 int ve = img[k-stride] == 255 || img[k+stride] == 255;
225 if (!he && !ve) continue;
226 }
227
228 // Calculate gradient direction
229 gx = -(float)img[k-stride-1] - SDF_SQRT2*(float)img[k-1] - (float)img[k+stride-1] + (float)img[k-stride+1] + SDF_SQRT2*(float)img[k+1] + (float)img[k+stride+1];
230 gy = -(float)img[k-stride-1] - SDF_SQRT2*(float)img[k-stride] - (float)img[k-stride+1] + (float)img[k+stride-1] + SDF_SQRT2*(float)img[k+stride] + (float)img[k+stride+1];
231 if (fabsf(gx) < 0.001f && fabsf(gy) < 0.001f) continue;
232 glen = gx*gx + gy*gy;
233 if (glen > 0.0001f) {
234 glen = 1.0f / sqrtf(glen);
235 gx *= glen;
236 gy *= glen;
237 }
238
239 // Find nearest point on contour.
240 tk = x + y * width;
241 d = sdf__edgedf(gx, gy, (float)img[k]/255.0f);
242 tpt[tk].x = x + gx*d;
243 tpt[tk].y = y + gy*d;
244 tdist[tk] = sdf__distsqr(&c, &tpt[tk]);
245 }
246 }
247
248 // Calculate distance transform using sweep-and-update.
249 for (pass = 0; pass < SDF_MAX_PASSES; pass++){
250 int changed = 0;
251
252 // Bottom-left to top-right.
253 for (y = 1; y < height-1; y++) {
254 for (x = 1; x < width-1; x++) {
255 int k = x+y*width, kn, ch = 0;
256 struct SDFpoint c = { (float)x, (float)y }, pt;
257 float pd = tdist[k], d;
258 // (-1,-1)
259 kn = k - 1 - width;
260 if (tdist[kn] < pd) {
261 d = sdf__distsqr(&c, &tpt[kn]);
262 if (d + SDF_SLACK < pd) {
263 pt = tpt[kn];
264 pd = d;
265 ch = 1;
266 }
267 }
268 // (0,-1)
269 kn = k - width;
270 if (tdist[kn] < pd) {
271 d = sdf__distsqr(&c, &tpt[kn]);
272 if (d + SDF_SLACK < pd) {
273 pt = tpt[kn];
274 pd = d;
275 ch = 1;
276 }
277 }
278 // (1,-1)
279 kn = k + 1 - width;
280 if (tdist[kn] < pd) {
281 d = sdf__distsqr(&c, &tpt[kn]);
282 if (d + SDF_SLACK < pd) {
283 pt = tpt[kn];
284 pd = d;
285 ch = 1;
286 }
287 }
288 // (-1,0)
289 kn = k - 1;
290 if (tdist[kn] < pd) {
291 d = sdf__distsqr(&c, &tpt[kn]);
292 if (d + SDF_SLACK < pd) {
293 pt = tpt[kn];
294 pd = d;
295 ch = 1;
296 }
297 }
298 if (ch) {
299 tpt[k] = pt;
300 tdist[k] = pd;
301 changed++;
302 }
303 }
304 }
305
306 // Top-right to bottom-left.
307 for (y = height-2; y > 0 ; y--) {
308 for (x = width-2; x > 0; x--) {
309 int k = x+y*width, kn, ch = 0;
310 struct SDFpoint c = { (float)x, (float)y }, pt;
311 float pd = tdist[k], d;
312 // (1,0)
313 kn = k + 1;
314 if (tdist[kn] < pd) {
315 d = sdf__distsqr(&c, &tpt[kn]);
316 if (d + SDF_SLACK < pd) {
317 pt = tpt[kn];
318 pd = d;
319 ch = 1;
320 }
321 }
322 // (-1,1)
323 kn = k - 1 + width;
324 if (tdist[kn] < pd) {
325 d = sdf__distsqr(&c, &tpt[kn]);
326 if (d + SDF_SLACK < pd) {
327 pt = tpt[kn];
328 pd = d;
329 ch = 1;
330 }
331 }
332 // (0,1)
333 kn = k + width;
334 if (tdist[kn] < pd) {
335 d = sdf__distsqr(&c, &tpt[kn]);
336 if (d + SDF_SLACK < pd) {
337 pt = tpt[kn];
338 pd = d;
339 ch = 1;
340 }
341 }
342 // (1,1)
343 kn = k + 1 + width;
344 if (tdist[kn] < pd) {
345 d = sdf__distsqr(&c, &tpt[kn]);
346 if (d + SDF_SLACK < pd) {
347 pt = tpt[kn];
348 pd = d;
349 ch = 1;
350 }
351 }
352 if (ch) {
353 tpt[k] = pt;
354 tdist[k] = pd;
355 changed++;
356 }
357 }
358 }
359
360 if (changed == 0) break;
361 }
362
363 // Map to good range.
364 scale = 1.0f / radius;
365 for (y = 0; y < height; y++) {
366 for (x = 0; x < width; x++) {
367 float d = sqrtf(tdist[x+y*width]) * scale;
368 if (img[x+y*stride] > 127) d = -d;
369 out[x+y*outstride] = (unsigned char)(sdf__clamp01(0.5f - d*0.5f) * 255.0f);
370 }
371 }
372
373}
374
375int sdfBuildDistanceField(unsigned char* out, int outstride, float radius,
376 const unsigned char* img, int width, int height, int stride)
377{
378 unsigned char* temp = (unsigned char*)malloc(width*height*sizeof(float)*3);
379 if (temp == NULL) return 0;
380 sdfBuildDistanceFieldNoAlloc(out, outstride, radius, img, width, height, stride, temp);
381 free(temp);
382 return 1;
383}
384
385#endif //SDF_IMPLEMENTATION
entt::handle b
entt::handle a
float scale
Definition hub.cpp:25
float y
float x
void sdfCoverageToDistanceField(unsigned char *out, int outstride, const unsigned char *img, int width, int height, int stride)
void sdfBuildDistanceFieldNoAlloc(unsigned char *out, int outstride, float radius, const unsigned char *img, int width, int height, int stride, unsigned char *temp)
int sdfBuildDistanceField(unsigned char *out, int outstride, float radius, const unsigned char *img, int width, int height, int stride)