c# - 画笔描边洪水填充
问题描述
晕!我正在尝试从 PhotoDirector 重新创建剪辑 https://youtu.be/GLNNCrp650Y?t=77
经过几次翻拍和接近,这就是我所在的地方。 自动填充
基本代码来自这里: http ://wiki.unity3d.com/index.php/TextureFloodFill
基本代码来自单击一次,使用该颜色和增长测试将初始颜色样本与其他每个像素进行比较,如果不相似则停止增长。
由于我需要对多个像素进行采样(在单个画笔笔划上),我使用嵌套的 bool 类进行检查(尝试将颜色添加到列表并使用 List.Contains 每个像素检查。超级慢。这要快得多)
在当前附加的代码中,我(返回)使用 RGB。我之前使用过 HSBColor,但尝试回到 RGB,因为在 HSB 中存在如果 Sat 非常低或亮度太低/太高的情况,则很难比较该像素的相邻色调。我将要恢复到 HSB 并添加 2 个嵌套布尔值,仅 1 个 Sat>Brightness(对于饱和度 < 阈值的样本,并且仍然具有亮度,所以基本上是灰度值)另一个只是亮度(对于非常暗/亮)这个这样,在采样时,低饱和样本进入 Sat>Bri 库,低/高 Bri 进入 Bri 库。在测试像素时,首先检查 Bri 是否可以跳过 Sat/Hue 检查,或者检查 Sat 是否可以跳过(完整)Hue 检查。
我还没有尝试过(厌倦了从 HSB 到 RGB 的倒退...),因为我开始认为(部分)问题不在于像素检查,而在于GROW 检查。PhotoDirector(PD) 也具有非常好的边缘检测功能,并且可以以某种方式解决我遇到的碎片问题。在我的代码中,我正在检查下一个像素并以此为基础停止/继续。也许 PD 会检查前面的几个像素,如果它始终不同,那么它实际上会停止,如果它只有几个不同的像素,那么它只会在它们之上增长,从而取出像素。
PD 的另一件事是增长限制边界框类型的功能。我的代码,即使是一个小的笔触和小样本,如果它匹配并且可以增长到整个图像,它就会。PD 获得了增长的最大宽度/高度限制。我尝试了一个统一的彩色图像,它确实填充了盒子。我想也许 PD 的容差被设置为超过限制但使用这个限制边界框,它可以产生实心填充,但在我的情况下,如果我的容差太高,那么它只会在明显的边缘上增长(并继续整个图像...)所以我没有亲自添加这个边界框限制并等到实际填充更加稳定,特别是在拉丝区域附近。
TL;DR 在像 PhotoDirector 中的“画笔采样,在画笔点击上填充”的方法中,他们是如何做到的?它是更好的采样/像素颜色测试吗?还是他们增加像素检查的一种方式?
美国广播公司
谢谢阅读!干杯,
这是相关代码,有点乱,因为它仍在不断变化..
public class GLookup
{
public bool[] bBools;
public GLookup(int num)
{
bBools = new bool[num];
}
}
public class RLookup
{
public GLookup[] gLookup;
public RLookup(int num)
{
gLookup = new GLookup[num];
for(int i = 0; i < num; i++)
{
gLookup[i] = new GLookup(num);
}
}
}
static void ResetLookup()
{
for(int i = 0; i < Grid.Paint.Options.colorLookupRes; i++)
{
rgbLookup[i] = new RLookup(Grid.Paint.Options.colorLookupRes);
rgbCheck[i] = new RLookup(Grid.Paint.Options.colorLookupRes);
}
}
static void InsertLookup(Color32 rgb, float tol)
{
int r = (int)(rgb.r/byteToLookup);// * Grid.Paint.Options.colorLookupRes);
int g = (int)(rgb.g/ byteToLookup);// * Grid.Paint.Options.colorLookupRes);
int b = (int)(rgb.b/ byteToLookup);// * Grid.Paint.Options.colorLookupRes);
if(r == Grid.Paint.Options.colorLookupRes)
r = Grid.Paint.Options.colorLookupRes - 1;
if(g == Grid.Paint.Options.colorLookupRes)
g = Grid.Paint.Options.colorLookupRes-1;
if(b == Grid.Paint.Options.colorLookupRes)
b = Grid.Paint.Options.colorLookupRes-1;
if(rgbCheck[r].gLookup[g].bBools[b])
{
return;
}
rgbCheck[r].gLookup[g].bBools[b] = true;
int tolNum = Grid.Paint.Options.maxTolNum;// (int)Mathf.Lerp(Grid.Paint.Options.minTolNum, Grid.Paint.Options.maxTolNum, tol);
// Initial
InsertLookupEntry(r, g, b);
InsertNeighborSatBri(r, g, b, tolNum);
// Hue fills
int currentHue;
// +- 5 hue if tol 1
for(int i = 0; i < Grid.Paint.Options.hueFill; i++)
{
// Higher hue
currentHue = r + (i + 1);
if(currentHue > Grid.Paint.Options.colorLookupRes-1)
currentHue = Grid.Paint.Options.colorLookupRes-1;
InsertLookupEntry(currentHue, g, b);
InsertNeighborSatBri(currentHue, g, b, tolNum);
// Lower hue
currentHue = r - (i + 1);
if(currentHue < 0)
currentHue = 0;// Grid.Paint.Options.colorLookupRes;
InsertLookupEntry(currentHue, g, b);
InsertNeighborSatBri(currentHue, g, b, tolNum);
}
}
// This used to be for Hue/Sat/Brightness. But changed back to RGB, so this is similar as above's Hue tolerance fill
static void InsertNeighborSatBri(int r, int g, int b, int tolNum)
{
int currentG;
int currentB;
for(int i = 0; i < tolNum; i++)
{
// Add the min plus from given value
currentG = g + (i + 1);
currentG = Mathf.Clamp(currentG, 0, Grid.Paint.Options.colorLookupRes - 1);
for(int j = 0; j < tolNum; j++)
{
currentB = b + (j + 1);
currentB = Mathf.Clamp(currentB, 0, Grid.Paint.Options.colorLookupRes - 1);
InsertLookupEntry(r, currentG, currentB);
currentB = b - (j + 1);
currentB = Mathf.Clamp(currentB, 0, Grid.Paint.Options.colorLookupRes - 1);
InsertLookupEntry(r, currentG, currentB);
}
currentG = g - (i + 1);
currentG = Mathf.Clamp(currentG, 0, Grid.Paint.Options.colorLookupRes - 1);
for(int j = 0; j < tolNum; j++)
{
currentB = b + (j + 1);
currentB = Mathf.Clamp(currentB, 0, Grid.Paint.Options.colorLookupRes - 1);
InsertLookupEntry(r, currentG, currentB);
currentB = b - (j + 1);
currentB = Mathf.Clamp(currentB, 0, Grid.Paint.Options.colorLookupRes - 1);
InsertLookupEntry(r, currentG, currentB);
}
}
}
static void InsertLookupEntry(int r, int g, int b)
{
rgbLookup[r].gLookup[g].bBools[b] = true;
}
// Take in list of points from a single brush stroke
public static List<Point> FloodCutBrush(this Texture2D aTex, Color32[] refPixels, List<Point> growPoints, List<Point> lastDotSample, float tolerance, bool[] history, bool isAdd)
{
int w = aTex.width;
int h = aTex.height;
List<Point> changedPixel = new List<Point>();
Color32[] photoPixels = new Color32[refPixels.Length];
System.Array.Copy(refPixels, photoPixels, refPixels.Length);
bool[] existCheck = new bool[history.Length];
Queue<Point> nodes = new Queue<Point>();
ResetLookup();
// Setting up samples
foreach(Point v in growPoints)
{
// Out of bounds is not valid
if(v.x < 0 || v.y < 0 || w < 0)
continue;
int refId = (v.x) + ((v.y) * (w/2));
if(refId >= photoPixels.Length)
continue;
Color32 hsb = photoPixels[refId];
InsertLookup(hsb, tolerance);
nodes.Enqueue(v);
}
// Fill search marks a pixel to alpha = 0. Then checks the top/bottom pixel and adds that to queue for future iteration
while(nodes.Count > 0)
{
Point current = nodes.Dequeue();
//this goes right
for(int i = current.x; i < w / 2; i++)
{
int idx = i + current.y * (w / 2);
if(idx >= history.Length)
continue;
Point fullCoord = IndexToPoint(w / 2, idx);
// Because color sampling from brush is half resolution, and photopixel is full resolution..
fullCoord.x *= 2;
fullCoord.y *= 2;
int fullIdx = fullCoord.x + (fullCoord.y * (w));
Color32 C = photoPixels[fullIdx];
if(history[idx] && (C.a == 0 || RGBTest(C) == false))// && (C.b < highBright && C.b > lowBright))
{
changedPixel.Add(current);
existCheck[idx] = true;
break;
}
C.a = 0;
photoPixels[fullIdx] = C;
// Queue up/down pix
if(current.y + 1 < h / 2)
{
C = photoPixels[fullIdx + w];
if(history[idx] && (C.a != 0 && RGBTest(C) == true))// || (C.b > highBright || C.b < lowBright))
{
nodes.Enqueue(new Point(i, current.y + 1));
}
}
if(current.y - 1 >= 0)
{
C = photoPixels[fullIdx - w];
if(history[idx] && (C.a != 0 && RGBTest(C))) // || (C.b > highBright || C.b < lowBright))
{
nodes.Enqueue(new Point(i, current.y - 1));
}
}
changedPixel.Add(current);
existCheck[idx] = true;
}
//this goes left, same as above but --x
}
return changedPixel;
}
public static bool RGBTest(Color32 c1)
{
int r = (int)(c1.r/ byteToLookup);
int g = (int)(c1.g/ byteToLookup);
int b = (int)(c1.b/ byteToLookup);
if(rgbLookup[r].gLookup[g].bBools[b])
{
count++;
return true;
} else
return false;
}
解决方案
推荐阅读
- mysql - 具有不同值的 SQL 查询
- c++ - 使用指针访问数组的值
- django - 以编程方式在 Wagtail 中创建重定向
- macos - 如何在 Mac 上安装 clangd?
- javascript - ReactNative 异步生成器
- javascript - 创建一个将字符串作为参数并返回字符串的编码 (h4ck3r 5p34k) 版本的函数
- php - .htaccess 在 Wordpress 中导致 403 Access Denied
- jquery - 如何在 Backbone 的 Jasmine 测试运行器中测试点击事件?
- python - 自定义 python 包包含 self 作为成员
- ios - Swift UI 创建列表气泡效果