签名距离场计算错误

时间:2017-04-25 14:18:29

标签: c graphics

我正在编写一个小工具来计算图形应用程序的signed distance field纹理。我正在做真正的有符号距离场,而不是近似值,所以我首先将每个字形路径转换为弧样条以加速点到路径距离的计算。问题是我在某些角落上得到了奇怪的文物:

Signed distance field of glyph A from DejaVuSerif

路径是从EPS生成的FontForge中提取的,无需任何操作。计算距离,找出从每个像素坐标到任何路径线段或弧的最小距离(三个嵌套循环:for (x;...) { for (y; ...) { for (i; ...) { ... }}})。迭代计算的每像素距离以提取最小值和最大值,并重新调整为0-255范围并直接写入原始图像文件,并使用PNG转换为ImageMagick

我能想到的这个错误的唯一来源是用于计算点到段距离的函数内部的数值误差。这是:

double dist_to_segment(double px, double py, /* query point */
                       double x0, double y0, /* first segment end-point */
                       double x1, double y1) /* second segment end-point */
{
  const double t0 = dist2(x0, y0, x1, y1);
  if (t0 == 0.0) { return dist2(px, py, x0, x1); }
  const double t1 = dot(px-x0, py-y0, x1-x0, y1-y0)/t0;
  const double t2 = clamp(t1, 0.0, 1.0);
  const double t3 = sqrt(dist2(px, py, lerp(x0,x1,t2), lerp(y0,y1,t2)));
  const double t4 = (x1-x0)*(py-y0) - (y1-y0)*(px-x0);
  return (t4 < 0.0)? -t3 : t3;
}

dotclamplerp定义为OpenGL着色语言,dist2定义为:

double dist2(double x0, double y0, double x1, double y1)
{
  return (x0-x1)*(x0-x1) + (y0-y1)*(y0-y1);
}

如果我在return (t4 < 0.0)? -t3 : t3;上使用return t3;替换dist_to_segment,我会收到此无符号距离字段:

Unsigned distance field of glyph A from DejaVuSerif

修改

我解决了小三角形的伪像,在现有的边缘迭代循环中添加了多边形点测试,因此额外的成本并不高。锐利的特征沿着锐角的平分线。有新的样本图像。

New result

3 个答案:

答案 0 :(得分:1)

您的功能对我来说是正确的,但需要注意的是结果的符号是否正确取决于根据对象边界周围路径的正确约定排序的段端点。这些图像确实表明它可以产生正确的结果。某些角落附近的三角形异常似乎与您如何组合此函数的多个结果有关,而不是与任何单个调用返回的值相关。

特别是,如果您将一个点的距离的负结果添加到一个点到另一个段的距离的正结果,或者您采用签名的的最小值或最大值两个距离的值,你将得到毫无意义的结果。不仅异常,而且沿着角平分线的尖锐特征表明你正在做这样的事情。

无符号距离场的异常缺失与该分析一致,但沿着锐角平分线的尖锐特征的持续存在是奇怪的。我还没有确定你在做什么,但你应该做的只是使用每个点到图的最近边缘的距离。您还必须确保线段在相对于图形内部的相同方向上跟踪每个边界,因为函数结果的正确符号取决于它。此外,要重现字形,您应该以相同的阴影渲染所有负(内部)距离。

答案 1 :(得分:1)

double dist_to_segment()返回不一致的单位。

return (t4 < 0.0)? -t3 : t3;return t3;(OP的替代代码)返回距离

return dist2(px, py, x0, x1);返回距离平方。当(x0,y0)(x1,y1)相同或非常几乎是在那些讨厌的角落时使用。我期待return sqrt(dist2(px, py, x0, x1));

sqrt(a*a + b*b)的简化为hypot(a,b)

  

hypot函数计算xy的平方和的平方根,而不会出现过度上溢或下溢。 C11§7.12.7.32

// example
if (t0 == 0.0) { 
  return hypot(px - x0, py - y0);
}

答案 2 :(得分:1)

这是一个扩展注释,用于说明如何使用netpbm工具将灰度级转换为轮廓。从OP的最后一张图片中,这会产生this

以下Bash脚本使用ppmchange将确切的颜色值重新映射到以白色分隔的色带:

#!/bin/bash
colormap=()
for ((i = 0; i < 256; i++)); do
    colormap+=( $(printf '#%02x%02x%02x' $i $i $i) )
    if (( (i & 15) < 6 )); then
        colormap+=( $(printf '#%02x00%02x' $[(i/16)*17] $[255-(i/16)*17]) )
    else
        colormap+=( "#ffffff" )
    fi
done
exec ppmchange -closeness 0 ${colormap[@]} "$@"

我喜欢称之为gray-to-contour。如果要指定确切的颜色,可以使用

#!/bin/sh
exec ppmchange -closeness 0 \
    '#000000' '#0000ff' \
    '#010101' '#0000ff' \
    '#020202' '#0000ff' \
    '#030303' '#0000ff' \
    '#040404' '#0000ff' \
    '#050505' '#0000ff' \
    '#060606' '#ffffff' \
    '#070707' '#ffffff' \
    '#080808' '#ffffff' \
    '#090909' '#ffffff' \
    '#0a0a0a' '#ffffff' \
    '#0b0b0b' '#ffffff' \
    '#0c0c0c' '#ffffff' \
    '#0d0d0d' '#ffffff' \
    '#0e0e0e' '#ffffff' \
    '#0f0f0f' '#ffffff' \
    '#101010' '#1100ee' \
    '#111111' '#1100ee' \
    '#121212' '#1100ee' \
    '#131313' '#1100ee' \
    '#141414' '#1100ee' \
    '#151515' '#1100ee' \
    '#161616' '#ffffff' \
    '#171717' '#ffffff' \
    '#181818' '#ffffff' \
    '#191919' '#ffffff' \
    '#1a1a1a' '#ffffff' \
    '#1b1b1b' '#ffffff' \
    '#1c1c1c' '#ffffff' \
    '#1d1d1d' '#ffffff' \
    '#1e1e1e' '#ffffff' \
    '#1f1f1f' '#ffffff' \
    '#202020' '#2200dd' \
    '#212121' '#2200dd' \
    '#222222' '#2200dd' \
    '#232323' '#2200dd' \
    '#242424' '#2200dd' \
    '#252525' '#2200dd' \
    '#262626' '#ffffff' \
    '#272727' '#ffffff' \
    '#282828' '#ffffff' \
    '#292929' '#ffffff' \
    '#2a2a2a' '#ffffff' \
    '#2b2b2b' '#ffffff' \
    '#2c2c2c' '#ffffff' \
    '#2d2d2d' '#ffffff' \
    '#2e2e2e' '#ffffff' \
    '#2f2f2f' '#ffffff' \
    '#303030' '#3300cc' \
    '#313131' '#3300cc' \
    '#323232' '#3300cc' \
    '#333333' '#3300cc' \
    '#343434' '#3300cc' \
    '#353535' '#3300cc' \
    '#363636' '#ffffff' \
    '#373737' '#ffffff' \
    '#383838' '#ffffff' \
    '#393939' '#ffffff' \
    '#3a3a3a' '#ffffff' \
    '#3b3b3b' '#ffffff' \
    '#3c3c3c' '#ffffff' \
    '#3d3d3d' '#ffffff' \
    '#3e3e3e' '#ffffff' \
    '#3f3f3f' '#ffffff' \
    '#404040' '#4400bb' \
    '#414141' '#4400bb' \
    '#424242' '#4400bb' \
    '#434343' '#4400bb' \
    '#444444' '#4400bb' \
    '#454545' '#4400bb' \
    '#464646' '#ffffff' \
    '#474747' '#ffffff' \
    '#484848' '#ffffff' \
    '#494949' '#ffffff' \
    '#4a4a4a' '#ffffff' \
    '#4b4b4b' '#ffffff' \
    '#4c4c4c' '#ffffff' \
    '#4d4d4d' '#ffffff' \
    '#4e4e4e' '#ffffff' \
    '#4f4f4f' '#ffffff' \
    '#505050' '#5500aa' \
    '#515151' '#5500aa' \
    '#525252' '#5500aa' \
    '#535353' '#5500aa' \
    '#545454' '#5500aa' \
    '#555555' '#5500aa' \
    '#565656' '#ffffff' \
    '#575757' '#ffffff' \
    '#585858' '#ffffff' \
    '#595959' '#ffffff' \
    '#5a5a5a' '#ffffff' \
    '#5b5b5b' '#ffffff' \
    '#5c5c5c' '#ffffff' \
    '#5d5d5d' '#ffffff' \
    '#5e5e5e' '#ffffff' \
    '#5f5f5f' '#ffffff' \
    '#606060' '#660099' \
    '#616161' '#660099' \
    '#626262' '#660099' \
    '#636363' '#660099' \
    '#646464' '#660099' \
    '#656565' '#660099' \
    '#666666' '#ffffff' \
    '#676767' '#ffffff' \
    '#686868' '#ffffff' \
    '#696969' '#ffffff' \
    '#6a6a6a' '#ffffff' \
    '#6b6b6b' '#ffffff' \
    '#6c6c6c' '#ffffff' \
    '#6d6d6d' '#ffffff' \
    '#6e6e6e' '#ffffff' \
    '#6f6f6f' '#ffffff' \
    '#707070' '#770088' \
    '#717171' '#770088' \
    '#727272' '#770088' \
    '#737373' '#770088' \
    '#747474' '#770088' \
    '#757575' '#770088' \
    '#767676' '#ffffff' \
    '#777777' '#ffffff' \
    '#787878' '#ffffff' \
    '#797979' '#ffffff' \
    '#7a7a7a' '#ffffff' \
    '#7b7b7b' '#ffffff' \
    '#7c7c7c' '#ffffff' \
    '#7d7d7d' '#ffffff' \
    '#7e7e7e' '#ffffff' \
    '#7f7f7f' '#ffffff' \
    '#808080' '#880077' \
    '#818181' '#880077' \
    '#828282' '#880077' \
    '#838383' '#880077' \
    '#848484' '#880077' \
    '#858585' '#880077' \
    '#868686' '#ffffff' \
    '#878787' '#ffffff' \
    '#888888' '#ffffff' \
    '#898989' '#ffffff' \
    '#8a8a8a' '#ffffff' \
    '#8b8b8b' '#ffffff' \
    '#8c8c8c' '#ffffff' \
    '#8d8d8d' '#ffffff' \
    '#8e8e8e' '#ffffff' \
    '#8f8f8f' '#ffffff' \
    '#909090' '#990066' \
    '#919191' '#990066' \
    '#929292' '#990066' \
    '#939393' '#990066' \
    '#949494' '#990066' \
    '#959595' '#990066' \
    '#969696' '#ffffff' \
    '#979797' '#ffffff' \
    '#989898' '#ffffff' \
    '#999999' '#ffffff' \
    '#9a9a9a' '#ffffff' \
    '#9b9b9b' '#ffffff' \
    '#9c9c9c' '#ffffff' \
    '#9d9d9d' '#ffffff' \
    '#9e9e9e' '#ffffff' \
    '#9f9f9f' '#ffffff' \
    '#a0a0a0' '#aa0055' \
    '#a1a1a1' '#aa0055' \
    '#a2a2a2' '#aa0055' \
    '#a3a3a3' '#aa0055' \
    '#a4a4a4' '#aa0055' \
    '#a5a5a5' '#aa0055' \
    '#a6a6a6' '#ffffff' \
    '#a7a7a7' '#ffffff' \
    '#a8a8a8' '#ffffff' \
    '#a9a9a9' '#ffffff' \
    '#aaaaaa' '#ffffff' \
    '#ababab' '#ffffff' \
    '#acacac' '#ffffff' \
    '#adadad' '#ffffff' \
    '#aeaeae' '#ffffff' \
    '#afafaf' '#ffffff' \
    '#b0b0b0' '#bb0044' \
    '#b1b1b1' '#bb0044' \
    '#b2b2b2' '#bb0044' \
    '#b3b3b3' '#bb0044' \
    '#b4b4b4' '#bb0044' \
    '#b5b5b5' '#bb0044' \
    '#b6b6b6' '#ffffff' \
    '#b7b7b7' '#ffffff' \
    '#b8b8b8' '#ffffff' \
    '#b9b9b9' '#ffffff' \
    '#bababa' '#ffffff' \
    '#bbbbbb' '#ffffff' \
    '#bcbcbc' '#ffffff' \
    '#bdbdbd' '#ffffff' \
    '#bebebe' '#ffffff' \
    '#bfbfbf' '#ffffff' \
    '#c0c0c0' '#cc0033' \
    '#c1c1c1' '#cc0033' \
    '#c2c2c2' '#cc0033' \
    '#c3c3c3' '#cc0033' \
    '#c4c4c4' '#cc0033' \
    '#c5c5c5' '#cc0033' \
    '#c6c6c6' '#ffffff' \
    '#c7c7c7' '#ffffff' \
    '#c8c8c8' '#ffffff' \
    '#c9c9c9' '#ffffff' \
    '#cacaca' '#ffffff' \
    '#cbcbcb' '#ffffff' \
    '#cccccc' '#ffffff' \
    '#cdcdcd' '#ffffff' \
    '#cecece' '#ffffff' \
    '#cfcfcf' '#ffffff' \
    '#d0d0d0' '#dd0022' \
    '#d1d1d1' '#dd0022' \
    '#d2d2d2' '#dd0022' \
    '#d3d3d3' '#dd0022' \
    '#d4d4d4' '#dd0022' \
    '#d5d5d5' '#dd0022' \
    '#d6d6d6' '#ffffff' \
    '#d7d7d7' '#ffffff' \
    '#d8d8d8' '#ffffff' \
    '#d9d9d9' '#ffffff' \
    '#dadada' '#ffffff' \
    '#dbdbdb' '#ffffff' \
    '#dcdcdc' '#ffffff' \
    '#dddddd' '#ffffff' \
    '#dedede' '#ffffff' \
    '#dfdfdf' '#ffffff' \
    '#e0e0e0' '#ee0011' \
    '#e1e1e1' '#ee0011' \
    '#e2e2e2' '#ee0011' \
    '#e3e3e3' '#ee0011' \
    '#e4e4e4' '#ee0011' \
    '#e5e5e5' '#ee0011' \
    '#e6e6e6' '#ffffff' \
    '#e7e7e7' '#ffffff' \
    '#e8e8e8' '#ffffff' \
    '#e9e9e9' '#ffffff' \
    '#eaeaea' '#ffffff' \
    '#ebebeb' '#ffffff' \
    '#ececec' '#ffffff' \
    '#ededed' '#ffffff' \
    '#eeeeee' '#ffffff' \
    '#efefef' '#ffffff' \
    '#f0f0f0' '#ff0000' \
    '#f1f1f1' '#ff0000' \
    '#f2f2f2' '#ff0000' \
    '#f3f3f3' '#ff0000' \
    '#f4f4f4' '#ff0000' \
    '#f5f5f5' '#ff0000' \
    '#f6f6f6' '#ffffff' \
    '#f7f7f7' '#ffffff' \
    '#f8f8f8' '#ffffff' \
    '#f9f9f9' '#ffffff' \
    '#fafafa' '#ffffff' \
    '#fbfbfb' '#ffffff' \
    '#fcfcfc' '#ffffff' \
    '#fdfdfd' '#ffffff' \
    '#fefefe' '#ffffff' \
    '#ffffff' '#ffffff' "$@"

其中左侧对应于256个灰度级中的每一个,右侧是对应的颜色。

如果原始图片为gray.png,您可以使用

从中创建contour.png
pngtopnm gray.png | ./gray-to-contour | pnmtopng -compress 9 > contour.png

正如我在评论中提到的,我们人类将渐变的急剧变化视为边缘,而在OP的最终图像中,只有看起来像角平分线太亮/暗。我试图找到一些关于效果的参考文献,但是我现在掌握了这些术语。

虽然灰度图像易于处理和使用,但有些情况下我们的人类心理视觉怪异会欺骗我们。出于这个原因,我个人会看到灰度和轮廓形式的凹凸贴图和距离场;在我看来,这两种表述相互补充。