Кто-либо реализовал алгоритм заливки в JavaScript для использования с Холстом HTML?
Мои требования просты: лавинная рассылка с единственным цветом, начинающим с единственной точки, где цвет границ является любым цветом, больше, чем определенная дельта цвета в указанной точке.
var r1, r2; // red values
var g1, g2; // green values
var b1, b2; // blue values
var actualColorDelta = Math.sqrt((r1 - r2)*(r1 - r2) + (g1 - g2)*(g1 - g2) + (b1 - b2)*(b1 - b2))
function floodFill(canvas, x, y, fillColor, borderColorDelta) {
...
}
Обновление:
Я записал свою собственную реализацию заливки, которая следует. Это медленно, но точно. Приблизительно 37% времени подняты в двух функциях массива низкого уровня, которые являются частью опытной платформы. Их называют нажатие и поп, я предполагаю. Большая часть остальной части времени потрачена в основном цикле.
var ImageProcessing;
ImageProcessing = {
/* Convert HTML color (e.g. "#rrggbb" or "#rrggbbaa") to object with properties r, g, b, a.
* If no alpha value is given, 255 (0xff) will be assumed.
*/
toRGB: function (color) {
var r, g, b, a, html;
html = color;
// Parse out the RGBA values from the HTML Code
if (html.substring(0, 1) === "#")
{
html = html.substring(1);
}
if (html.length === 3 || html.length === 4)
{
r = html.substring(0, 1);
r = r + r;
g = html.substring(1, 2);
g = g + g;
b = html.substring(2, 3);
b = b + b;
if (html.length === 4) {
a = html.substring(3, 4);
a = a + a;
}
else {
a = "ff";
}
}
else if (html.length === 6 || html.length === 8)
{
r = html.substring(0, 2);
g = html.substring(2, 4);
b = html.substring(4, 6);
a = html.length === 6 ? "ff" : html.substring(6, 8);
}
// Convert from Hex (Hexidecimal) to Decimal
r = parseInt(r, 16);
g = parseInt(g, 16);
b = parseInt(b, 16);
a = parseInt(a, 16);
return {r: r, g: g, b: b, a: a};
},
/* Get the color at the given x,y location from the pixels array, assuming the array has a width and height as given.
* This interprets the 1-D array as a 2-D array.
*
* If useColor is defined, its values will be set. This saves on object creation.
*/
getColor: function (pixels, x, y, width, height, useColor) {
var redIndex = y * width * 4 + x * 4;
if (useColor === undefined) {
useColor = { r: pixels[redIndex], g: pixels[redIndex + 1], b: pixels[redIndex + 2], a: pixels[redIndex + 3] };
}
else {
useColor.r = pixels[redIndex];
useColor.g = pixels[redIndex + 1]
useColor.b = pixels[redIndex + 2];
useColor.a = pixels[redIndex + 3];
}
return useColor;
},
setColor: function (pixels, x, y, width, height, color) {
var redIndex = y * width * 4 + x * 4;
pixels[redIndex] = color.r;
pixels[redIndex + 1] = color.g,
pixels[redIndex + 2] = color.b;
pixels[redIndex + 3] = color.a;
},
/*
* fill: Flood a canvas with the given fill color.
*
* Returns a rectangle { x, y, width, height } that defines the maximum extent of the pixels that were changed.
*
* canvas .................... Canvas to modify.
* fillColor ................. RGBA Color to fill with.
* This may be a string ("#rrggbbaa") or an object of the form { r: red, g: green, b: blue, a: alpha }.
* x, y ...................... Coordinates of seed point to start flooding.
* bounds .................... Restrict flooding to this rectangular region of canvas.
* This object has these attributes: { x, y, width, height }.
* If undefined or null, use the whole of the canvas.
* stopFunction .............. Function that decides if a pixel is a boundary that should cause
* flooding to stop. If omitted, any pixel that differs from seedColor
* will cause flooding to stop. seedColor is the color under the seed point (x,y).
* Parameters: stopFunction(fillColor, seedColor, pixelColor).
* Returns true if flooding shoud stop.
* The colors are objects of the form { r: red, g: green, b: blue, a: alpha }
*/
fill: function (canvas, fillColor, x, y, bounds, stopFunction) {
// Supply default values if necessary.
var ctx, minChangedX, minChangedY, maxChangedX, maxChangedY, wasTested, shouldTest, imageData, pixels, currentX, currentY, currentColor, currentIndex, seedColor, tryX, tryY, tryIndex, boundsWidth, boundsHeight, pixelStart, fillRed, fillGreen, fillBlue, fillAlpha;
if (Object.isString(fillColor)) {
fillColor = ImageProcessing.toRGB(fillColor);
}
x = Math.round(x);
y = Math.round(y);
if (bounds === null || bounds === undefined) {
bounds = { x: 0, y: 0, width: canvas.width, height: canvas.height };
}
else {
bounds = { x: Math.round(bounds.x), y: Math.round(bounds.y), width: Math.round(bounds.y), height: Math.round(bounds.height) };
}
if (stopFunction === null || stopFunction === undefined) {
stopFunction = new function (fillColor, seedColor, pixelColor) {
return pixelColor.r != seedColor.r || pixelColor.g != seedColor.g || pixelColor.b != seedColor.b || pixelColor.a != seedColor.a;
}
}
minChangedX = maxChangedX = x - bounds.x;
minChangedY = maxChangedY = y - bounds.y;
boundsWidth = bounds.width;
boundsHeight = bounds.height;
// Initialize wasTested to false. As we check each pixel to decide if it should be painted with the new color,
// we will mark it with a true value at wasTested[row = y][column = x];
wasTested = new Array(boundsHeight * boundsWidth);
/*
$R(0, bounds.height - 1).each(function (row) {
var subArray = new Array(bounds.width);
wasTested[row] = subArray;
});
*/
// Start with a single point that we know we should test: (x, y).
// Convert (x,y) to image data coordinates by subtracting the bounds' origin.
currentX = x - bounds.x;
currentY = y - bounds.y;
currentIndex = currentY * boundsWidth + currentX;
shouldTest = [ currentIndex ];
ctx = canvas.getContext("2d");
//imageData = ctx.getImageData(bounds.x, bounds.y, bounds.width, bounds.height);
imageData = ImageProcessing.getImageData(ctx, bounds.x, bounds.y, bounds.width, bounds.height);
pixels = imageData.data;
seedColor = ImageProcessing.getColor(pixels, currentX, currentY, boundsWidth, boundsHeight);
currentColor = { r: 0, g: 0, b: 0, a: 1 };
fillRed = fillColor.r;
fillGreen = fillColor.g;
fillBlue = fillColor.b;
fillAlpha = fillColor.a;
while (shouldTest.length > 0) {
currentIndex = shouldTest.pop();
currentX = currentIndex % boundsWidth;
currentY = (currentIndex - currentX) / boundsWidth;
if (! wasTested[currentIndex]) {
wasTested[currentIndex] = true;
//currentColor = ImageProcessing.getColor(pixels, currentX, currentY, boundsWidth, boundsHeight, currentColor);
// Inline getColor for performance.
pixelStart = currentIndex * 4;
currentColor.r = pixels[pixelStart];
currentColor.g = pixels[pixelStart + 1]
currentColor.b = pixels[pixelStart + 2];
currentColor.a = pixels[pixelStart + 3];
if (! stopFunction(fillColor, seedColor, currentColor)) {
// Color the pixel with the fill color.
//ImageProcessing.setColor(pixels, currentX, currentY, boundsWidth, boundsHeight, fillColor);
// Inline setColor for performance
pixels[pixelStart] = fillRed;
pixels[pixelStart + 1] = fillGreen;
pixels[pixelStart + 2] = fillBlue;
pixels[pixelStart + 3] = fillAlpha;
if (minChangedX < currentX) { minChangedX = currentX; }
else if (maxChangedX > currentX) { maxChangedX = currentX; }
if (minChangedY < currentY) { minChangedY = currentY; }
else if (maxChangedY > currentY) { maxChangedY = currentY; }
// Add the adjacent four pixels to the list to be tested, unless they have already been tested.
tryX = currentX - 1;
tryY = currentY;
tryIndex = tryY * boundsWidth + tryX;
if (tryX >= 0 && ! wasTested[tryIndex]) {
shouldTest.push(tryIndex);
}
tryX = currentX;
tryY = currentY + 1;
tryIndex = tryY * boundsWidth + tryX;
if (tryY < boundsHeight && ! wasTested[tryIndex]) {
shouldTest.push(tryIndex);
}
tryX = currentX + 1;
tryY = currentY;
tryIndex = tryY * boundsWidth + tryX;
if (tryX < boundsWidth && ! wasTested[tryIndex]) {
shouldTest.push(tryIndex);
}
tryX = currentX;
tryY = currentY - 1;
tryIndex = tryY * boundsWidth + tryX;
if (tryY >= 0 && ! wasTested[tryIndex]) {
shouldTest.push(tryIndex);
}
}
}
}
//ctx.putImageData(imageData, bounds.x, bounds.y);
ImageProcessing.putImageData(ctx, imageData, bounds.x, bounds.y);
return { x: minChangedX + bounds.x, y: minChangedY + bounds.y, width: maxChangedX - minChangedX + 1, height: maxChangedY - minChangedY + 1 };
},
getImageData: function (ctx, x, y, w, h) {
return ctx.getImageData(x, y, w, h);
},
putImageData: function (ctx, data, x, y) {
ctx.putImageData(data, x, y);
}
};
BTW, когда я называю это, я использую пользовательский stopFunction:
stopFill : function (fillColor, seedColor, pixelColor) {
// Ignore alpha difference for now.
return Math.abs(pixelColor.r - seedColor.r) > this.colorTolerance || Math.abs(pixelColor.g - seedColor.g) > this.colorTolerance || Math.abs(pixelColor.b - seedColor.b) > this.colorTolerance;
},
Если бы кто-либо видит способ улучшить производительность этого кода, я ценил бы его. Основная идея: 1) Цвет семени является начальным цветом в точке, чтобы начать лавинно рассылать. 2) Попробуйте четыре смежных точки: право, вниз и оставленный один пиксель. 3) Если точка вне диапазона или уже была посещена, пропустите его. 4) Иначе продвиньте точку на к стопке интересных моментов. 5) Pop следующий интересный момент от стека. 6) Если цвет в той точке является цветом остановки (как определено в stopFunction), затем прекращают обрабатывать ту точку и пропуск к шагу 5. 7), Иначе, пропустите к шагу 2. 8), Когда нет никаких более интересных моментов для посещения, остановите цикличное выполнение.
Запоминание, что точку посетили, требует массива с тем же числом элементов, поскольку существуют пиксели.
Чтобы найти значение с помощью правильного синтаксического анализатора XML, можно написать что-то подобное:
using(var xr = XmlReader.Create(input))
{
while(xr.Read())
{
if(xr.NodeType == XmlNodeType.ProcessingInstruction && xr.Name == "xml-stylesheet")
{
string s = xr.Value;
int i = s.IndexOf("href=\"") + 6;
s = s.Substring(i, s.IndexOf('\"', i) - i);
Console.WriteLine(s);
break;
}
}
}
-121--3339000- Вот реализация, над которой я работал. Он может стать очень медленным, если цвет замены слишком близок к исходному цвету. Это немного быстрее в Chrome, чем Firefox (я не тестировал его в других браузерах).
Я также еще не провел исчерпывающего тестирования, поэтому могут быть крайние случаи, когда это не работает.
function getPixel(pixelData, x, y) {
if (x < 0 || y < 0 || x >= pixelData.width || y >= pixelData.height) {
return NaN;
}
var pixels = pixelData.data;
var i = (y * pixelData.width + x) * 4;
return ((pixels[i + 0] & 0xFF) << 24) |
((pixels[i + 1] & 0xFF) << 16) |
((pixels[i + 2] & 0xFF) << 8) |
((pixels[i + 3] & 0xFF) << 0);
}
function setPixel(pixelData, x, y, color) {
var i = (y * pixelData.width + x) * 4;
var pixels = pixelData.data;
pixels[i + 0] = (color >>> 24) & 0xFF;
pixels[i + 1] = (color >>> 16) & 0xFF;
pixels[i + 2] = (color >>> 8) & 0xFF;
pixels[i + 3] = (color >>> 0) & 0xFF;
}
function diff(c1, c2) {
if (isNaN(c1) || isNaN(c2)) {
return Infinity;
}
var dr = ((c1 >>> 24) & 0xFF) - ((c2 >>> 24) & 0xFF);
var dg = ((c1 >>> 16) & 0xFF) - ((c2 >>> 16) & 0xFF);
var db = ((c1 >>> 8) & 0xFF) - ((c2 >>> 8) & 0xFF);
var da = ((c1 >>> 0) & 0xFF) - ((c2 >>> 0) & 0xFF);
return dr*dr + dg*dg + db*db + da*da;
}
function floodFill(canvas, x, y, replacementColor, delta) {
var current, w, e, stack, color, cx, cy;
var context = canvas.getContext("2d");
var pixelData = context.getImageData(0, 0, canvas.width, canvas.height);
var done = [];
for (var i = 0; i < canvas.width; i++) {
done[i] = [];
}
var targetColor = getPixel(pixelData, x, y);
delta *= delta;
stack = [ [x, y] ];
done[x][y] = true;
while ((current = stack.pop())) {
cx = current[0];
cy = current[1];
if (diff(getPixel(pixelData, cx, cy), targetColor) <= delta) {
setPixel(pixelData, cx, cy, replacementColor);
w = e = cx;
while (w > 0 && diff(getPixel(pixelData, w - 1, cy), targetColor) <= delta) {
--w;
if (done[w][cy]) break;
setPixel(pixelData, w, cy, replacementColor);
}
while (e < pixelData.width - 1 && diff(getPixel(pixelData, e + 1, cy), targetColor) <= delta) {
++e;
if (done[e][cy]) break;
setPixel(pixelData, e, cy, replacementColor);
}
for (cx = w; cx <= e; cx++) {
if (cy > 0) {
color = getPixel(pixelData, cx, cy - 1);
if (diff(color, targetColor) <= delta) {
if (!done[cx][cy - 1]) {
stack.push([cx, cy - 1]);
done[cx][cy - 1] = true;
}
}
}
if (cy < canvas.height - 1) {
color = getPixel(pixelData, cx, cy + 1);
if (diff(color, targetColor) <= delta) {
if (!done[cx][cy + 1]) {
stack.push([cx, cy + 1]);
done[cx][cy + 1] = true;
}
}
}
}
}
}
context.putImageData(pixelData, 0, 0, 0, 0, canvas.width, canvas.height);
}
Посмотрите на log4jdbc . Это позволяет вам посмотреть на все вещи, проходящие через ваш jdbc, включая открытие/закрытие соединений, а также информацию о номере соединения.
-121--2835576-В течение времени кодирования преимущество состоит в том, что вам не нужно складывать объекты в определенный тип, таким образом, существует некоторая безопасность во времени компиляции. Во время выполнения разница отсутствует (в Java).
-121--4716501-Я бы не рассматривал холст как растровое изображение.
Вместо этого я бы сохранил коллекцию объектов рисования и изменил эту коллекцию. Например, можно заполнить контур или фигуру или добавить новую фигуру с границами объектов, которые вы пытаетесь заполнить.
Я не вижу, как «нормальный» floodFill имеет смысл в векторном рисунке.