- Home /
Sweeping trough a 2D Color array
I am currently working on painting functionality for a Unity iOS game. I have gotten it working, but its a little slow so i am working on optimizing it.
My current problem is with the brush method, here is the relevant code so far:
private void Draw(Vector2 canvasPos)
{
if(lastCanvasPos == Vector2.zero)
{
DrawBrush(canvasPos, currentColor);
}
else
{
int lerpNum = (int)Vector3.Distance(canvasPos, lastCanvasPos);
for(int i = 0; i <= lerpNum; i++)
{
float lerpPos = Mathf.InverseLerp(0, lerpNum, i);
Vector2 lerpedCanvasPos = Vector2.Lerp(canvasPos, lastCanvasPos, lerpPos);
DrawBrush(lerpedCanvasPos, currentColor);
}
}
lastCanvasPos = canvasPos;
}
private void DrawBrush(Vector2 brushPos, Color brushColor)
{
int brushRigth = Mathf.RoundToInt(brushPos.x - (brushSize * 0.5f));
int brushTop = Mathf.RoundToInt(brushPos.y - (brushSize * 0.5f));
for(int ix = brushRigth; ix < brushRigth + (int)brushSize; ix++)
{
for(int iy = brushTop; iy < brushTop + (int)brushSize; iy++)
{
if(ix >= 0 && ix < canvasSizeX) // Check if we are within the canvas
{
if(iy >= 0 && iy < canvasSizeY)
{
float brushCenterDistance = (brushPos - new Vector2(ix, iy)).magnitude; // Calculate the distance from the center of the brush
float colorLerp = Mathf.InverseLerp((int)brushSize * 0.5f, 0.0f, brushCenterDistance);
pixelColorArray[ix, iy] = Color.Lerp((pixelColorArray[ix, iy]), brushColor, colorLerp * brushColor.a); // Lerp the color towards the brush color based on the distance from the center of the brush
}
}
}
}
}
The code works by manipulating a 2D color array which is later later applied to a texture. The "Draw" function is called each update with the current position of the mouse/touch on the canvas. The "Draw" function then calls the "DrawBrush" function on each pixel point between the current position of the touch and the position of the previous update. The "DrawBrush" functions lerps the color at the position its given and nearby points towards the brush color, based on that's point distance from the center of the brush.
As you can see, this isn't very efficient. If the brush size is 20, then at each point in a brush stroke a 20*20 square of color objects is lerped towards the brush color. Causing the color points accessed by the brush, to be lerped many times in a single brush stroke. Because the brush areas overlap.
I think i have thought of a way to optimize this. By calling the "DrawBrush" function at the start and the end of a brush stroke. Then have a BrushSweep function that basically functions by making a single pixel wide line from start to end that is lerped fully towards the brush color. And then have the pixels outside the line be gradually lerped less and less towards the brush color based on distance from the line.
Making this work for straight lines would be easy. Problem is that i am not quite sure how to make this work for brush strokes that are at a angle. Anyone got a suggestion on how to achieve this?
Or do someone have a idea for another way to do this that don't access each color object more than one time?
Pleas let me know if i have not explained my problem sufficiently, and i will try to provide more detail.
Answer by Mattivc · Jul 11, 2011 at 02:24 PM
After a bit of head scratching, i found a solution that works pretty good.
I replaced the DrawBrush function. And made it so the function is only called each update instead of for each pixel between the current and last position. My new code looks like this for anyone interested.
private void DrawBrushSweep(Vector2 sweepStart, Vector2 sweepEnd, Color brushColor)
{
int drawAreaTop = (int)Mathf.Max(sweepStart.y, sweepEnd.y) + (brushSize / 2);
int drawAreaBottom = (int)Mathf.Min(sweepStart.y, sweepEnd.y) - (brushSize / 2);
int drawAreaRigth = (int)Mathf.Max(sweepStart.x, sweepEnd.x) + (brushSize / 2);
int drawAreaLeft = (int)Mathf.Min(sweepStart.x, sweepEnd.x) - (brushSize / 2);
for (int ix = drawAreaLeft; ix < drawAreaRigth; ix++)
{
for (int iy = drawAreaBottom; iy < drawAreaTop; iy++)
{
if(ix >= 0 && ix < canvasSizeX) // Check if we are within the canvas
{
if(iy >= 0 && iy < canvasSizeY)
{
int flattenArrayPos = FlattenMultiDimArray(ix, iy);
if(!pixelArray[flattenArrayPos].readOnly)
{
float brushCenterDistance = (GetClosestPointOnLine(sweepStart, sweepEnd, new Vector2(ix, iy)) - new Vector2(ix, iy)).magnitude;
float colorLerp = Mathf.InverseLerp(brushSize * 0.5f, 0.0f, brushCenterDistance);
pixelColorArray[flattenArrayPos] = Color.Lerp((pixelColorArray[flattenArrayPos]), brushColor, brushFalloff.Evaluate(colorLerp) * brushColor.a);
}
}
}
}
}
}
private Vector2 GetClosestPointOnLine(Vector2 lineStart, Vector2 lineEnd, Vector2 point)
{
Vector2 AP = point - lineStart;
Vector2 AB = lineEnd - lineStart;
float ab2 = AB.x * AB.x + AB.y * AB.y;
float ap_ab = AP.x * AB.x + AP.y * AB.y;
float t = ap_ab / ab2;
t = Mathf.Clamp01(t);
Vector2 closest = lineStart + AB * t;
return closest;
}
The code works by first calculating a square that contains the entire sweep area. Then it loops trough all pixels within the square. Each pixel is lerped towards the brush color based on its distance from the nearest point on the line from the start to the end of the sweep.
Can you post you Update $$anonymous$$ethod as well? I am curious on how sweepStart and sweepEnd are send in?
So I think I have made my own solution now, However, I am curious about the brushRollOff. What type of variable is this?
Answer by DaveA · Jul 08, 2011 at 04:17 PM
FWIW I wrote such a thing back in '87 that did just what you propose, with extra logic for beveling the angles. Was in Windows2 and/or ASM86. So not much help to you except that, given the limited resources we had at the time, that was the best optimization we found to accomplish it, so you're on the right track.