Как я могу сохранить краевые пиксели линий полупрозрачными?

Я работаю с canvasом HTML, который взорван на 32x шириной и высотой пикселя. Однако, когда я рисую строки, я замечаю, что крайние пиксели линий нарисованы полупрозрачно (ровно наполовину прозрачными). Есть ли способ остановить это?

На этом рисунке красная линия представляет собой одну линию от одной точки к другой. Я хотел бы, чтобы все блоки были либо черными, либо # FF0000 красным.

NB Я уже использую canvas.translate () для правильного выравнивания пикселей и использую решение в этом сообщении для рендеринга расширенных пикселей в отдельных блоках.

Проблемный фон

Canvas использует сглаживание, чтобы сделать рисунки более гладкими, поэтому он заполняет полупрозрачные пиксели здесь и там (см. Это объяснение, как это работает).

Сглаживание (например, интерполяция) можно отключить, но только для изображений ( ctx.imageSmoothingEnabled = false , как следует из названия).

Решения

Для этого необходимо выполнить «рендеринг линии». Однако типичные линейные алгоритмы поддерживают только ширину 1 пиксель. Это включает в себя Bresenham, а также EFLA (чрезвычайно быстрый алгоритм линии по Po-Han Lin ), последний быстрее, чем Bresenham.

Для линий толщиной более 1 пикселя вам нужно будет найти тангенциальный угол, а затем отобразить каждый сегмент вдоль основной линии.

Я предоставляю обе реализации, ниже которых я в некоторой степени оптимизирован. Ни один из них не требует доступа к самому растровому изображению, просто поставьте контекст.

Единственное, что вам нужно запомнить, это использовать fillStylefill() ) вместо strokeStylestroke() ), чтобы установить его цвет. Вы можете создавать несколько строк перед заполнением, что обычно быстрее, чем заполнение каждого сегмента линии при условии, что они используют один и тот же цвет.

При желании вы можете использовать данные изображения и устанавливать пиксели там напрямую, но это медленнее и требует CORS, если вы используете изображения (используйте это bitmap с видом Uint32, если это предпочтительнее. Существуют также специальные трюки, чтобы ускорить этот подход, но они здесь не рассматриваются).

EFLA (чрезвычайно быстрый алгоритм линии)

Этот алгоритм предназначен, где вы хотите нарисовать непрерывные многоугольные линии, т.е. последняя точка не задана. Но в следующей реализации мы устанавливаем его вручную, чтобы его можно было использовать для отдельных сегментов.

Перейдите на связанный сайт выше, чтобы получить более подробное объяснение (как и для лицензии).

Просто убедитесь, что входные значения являются целыми значениями:

 function lineEFLA(ctx, x1, y1, x2, y2) { var dlt, mul, yl = false, i, sl = y2 - y1, ll = x2 - x1, lls = ll >> 31, sls = sl >> 31; if ((sl ^ sls) - sls > (ll ^ lls) - lls) { sl ^= ll; ll ^= sl; sl ^= ll; yl = true; } dlt = ll < 0 ? -1 : 1; mul = (ll === 0) ? sl : sl / ll; if (yl) { x1 += 0.5; // preset for rounding for (i = 0; i !== ll; i += dlt) setPixel((x1 + i * mul)|0, y1 + i); } else { y1 += 0.5; for (i = 0; i !== ll; i += dlt) setPixel(x1 + i, (y1 + i * mul)|0); } setPixel(x2, y2); // sets last pixel function setPixel(x, y) {ctx.rect(x, y, 1, 1)} } 

Bresenham

Это classический линейный алгоритм, используемый во многих приложениях и компьютерах в старые времена, когда требуется простая строка.

Алгоритм более подробно объясняется здесь .

 function lineBresenham(ctx, x1, y1, x2, y2) { if (x1 === x2) { // special case, vertical line ctx.rect(x1, Math.min(y1, y2), 1, Math.abs(y2 - y1) + 1); return; } if (y1 === y2) { // special case, horizontal line ctx.rect(Math.min(x1, x2), y1, Math.abs(x2 - x1) + 1, 1); return; } var dx = Math.abs(x2 - x1), sx = x1 < x2 ? 1 : -1, dy = Math.abs(y2 - y1), sy = y1 < y2 ? 1 : -1, err = (dx > dy ? dx : -dy) * 0.5; while(!0) { ctx.rect(x1, y1, 1, 1); if (x1 === x2 && y1 === y2) break; var e2 = err; if (e2 > -dx) { err -= dy; x1 += sx; } if (e2 < dy) { err += dx; y1 += sy; } } } 

Живая демонстрация, включая зум

 var ctx = document.querySelector("canvas").getContext("2d"); ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); // bg color ctx.scale(20, 20); // scale ctx.fillStyle = "#f00"; // color for line in this case lineEFLA(ctx, 0, 0, 17, 20); // algo 1 lineBresenham(ctx, 3, 0, 20, 20); // algo 2 ctx.fill(); // fill the rects, use beginPath() for next function lineEFLA(ctx, x1, y1, x2, y2) { /* x1 |= 0; // make sure values are integer values x2 |= 0; y1 |= 0; y2 |= 0;*/ var dlt, mul, sl = y2 - y1, ll = x2 - x1, yl = false, lls = ll >> 31, sls = sl >> 31, i; if ((sl ^ sls) - sls > (ll ^ lls) - lls) { sl ^= ll; ll ^= sl; sl ^= ll; yl = true; } dlt = ll < 0 ? -1 : 1; mul = (ll === 0) ? sl : sl / ll; if (yl) { x1 += 0.5; for (i = 0; i !== ll; i += dlt) setPixel((x1 + i * mul) | 0, y1 + i); } else { y1 += 0.5; for (i = 0; i !== ll; i += dlt) setPixel(x1 + i, (y1 + i * mul) | 0); } setPixel(x2, y2); // sets last pixel function setPixel(x, y) { ctx.rect(x, y, 1, 1) } } function lineBresenham(ctx, x1, y1, x2, y2) { if (x1 === x2) { // special case, vertical line ctx.rect(x1, Math.min(y1, y2), 1, Math.abs(y2 - y1) + 1); return; } if (y1 === y2) { // special case, horizontal line ctx.rect(Math.min(x1, x2), y1, Math.abs(x2 - x1) + 1, 1); return; } var dx = Math.abs(x2 - x1), sx = x1 < x2 ? 1 : -1, dy = Math.abs(y2 - y1), sy = y1 < y2 ? 1 : -1, err = (dx > dy ? dx : -dy) * 0.5; while (!0) { ctx.rect(x1, y1, 1, 1); if (x1 === x2 && y1 === y2) break; var e2 = err; if (e2 > -dx) { err -= dy; x1 += sx; } if (e2 < dy) { err += dx; y1 += sy; } } }