html 如何添加透明像素而不仅仅是白色像素?

kqlmhetl  于 2023-11-15  发布在  其他
关注(0)|答案(2)|浏览(152)

我刚刚做了一个小像素艺术应用程序,我遇到了一个问题,而我试图修复橡皮擦工具。当我试图用颜色'rgba擦除(0,0,0,0)'它什么也不做,所以我被迫用白色来解决。你们知道我可能做错了什么吗?(我也有另一个问题,所以我会问,当这一个回答)Here is the project下面是该项目的代码:

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var pixelSize = 10;
var color = "#000000";
var eraser = false;
var fillStack = [];

var fillButton = document.getElementById("fill");

fillButton.addEventListener("click", function() {
    processFillStack();
});

var downloadButton = document.getElementById("download");

downloadButton.addEventListener("click", function() {
    var dataURL = canvas.toDataURL("image/png");
    var link = document.createElement("a");
    link.setAttribute("href", dataURL);
    link.setAttribute("download", "pixel-art.png");
    link.click();
});

var widthInput = document.getElementById("width");
var heightInput = document.getElementById("height");
var resizeButton = document.getElementById("resize");

resizeButton.addEventListener("click", function() {
    canvas.width = widthInput.value;
    canvas.height = heightInput.value;
    var ctx = canvas.getContext("2d");
    ctx.clearRect(0, 0, canvas.width, canvas.height);
});

document.getElementById("eraser").addEventListener("change", function() {
    eraser = this.checked;
});

canvas.addEventListener("mousedown", function(e) {
    if (!fillButton.pressed) {
        var x = Math.floor(e.offsetX / pixelSize);
        var y = Math.floor(e.offsetY / pixelSize);
        if (eraser) {
            ctx.fillStyle = "rgba(255,255,255,.1)";
        } else {
            ctx.fillStyle = color;
        }
        ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
    }
});

canvas.addEventListener("mousemove", function(e) {
    if (e.buttons == 1 && !fillButton.pressed) {
        var x = Math.floor(e.offsetX / pixelSize);
        var y = Math.floor(e.offsetY / pixelSize);
        if (eraser) {
            ctx.fillStyle = "rgba(255,255,255,.1)";
        } else {
            ctx.fillStyle = color;
        }
        ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
    }
});

var imageInput = document.getElementById("image-input");

imageInput.addEventListener("change", function() {
    var file = imageInput.files[0];
    var reader = new FileReader();
    reader.onload = function(e) {
        var img = new Image();
        img.onload = function() {
            ctx.drawImage(img, 0, 0);
        };
        img.src = e.target.result;
    };
    reader.readAsDataURL(file);
});


function floodFill(x, y, fillColor) {
    var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    var pixelStack = [[x, y]];
    var pixelPos, rowStart, rowEnd, up, down, i;
    while (pixelStack.length) {
        pixelPos = pixelStack.pop();
        rowStart = pixelPos[1] * canvas.width * 4;
        rowEnd = rowStart + canvas.width * 4;
        up = false;
        down = false;
        for (i = rowStart; i < rowEnd; i += 4) {
            if (matchColor(imageData.data, i, fillColor)) {
                continue;
            }
            if (matchColor(imageData.data, i, getPixelColor(pixelPos[0], pixelPos[1]))) {
                imageData.data[i] = fillColor[0];
                imageData.data[i + 1] = fillColor[1];
                imageData.data[i + 2] = fillColor[2];
                imageData.data[i + 3] = fillColor[3];
                if (pixelPos[1] > 0) {
                    if (matchColor(imageData.data, i - canvas.width * 4, getPixelColor(pixelPos[0], pixelPos[1] - 1))) {
                        if (!up) {
                            pixelStack.push([pixelPos[0], pixelPos[1] - 1]);
                            up = true;
                        }
                    } else if (up) {
                        up = false;
                    }
                }
                if (pixelPos[1] < canvas.height - 1) {
                    if (matchColor(imageData.data, i + canvas.width * 4, getPixelColor(pixelPos[0], pixelPos[1] + 1))) {
                        if (!down) {
                            pixelStack.push([pixelPos[0], pixelPos[1] + 1]);
                            down = true;
                        }
                    } else if (down) {
                        down = false;
                    }
                }
                if (pixelPos[0] > 0) {
                    if (matchColor(imageData.data, i - 4, getPixelColor(pixelPos[0] - 1, pixelPos[1]))) {
                        pixelStack.push([pixelPos[0] - 1, pixelPos[1]]);
                    }
                }
                if (pixelPos[0] < canvas.width - 1) {
                    if (matchColor(imageData.data, i + 4, getPixelColor(pixelPos[0] + 1, pixelPos[1]))) {
                        pixelStack.push([pixelPos[0] + 1, pixelPos[1]]);
                    }
                }
            }
        }
    }
    ctx.putImageData(imageData, 0, 0);
}

function matchColor(data, i, color) {
    return data[i] == color[0] && data[i + 1] == color[1] && data[i + 2] == color[2] && data[i + 3] == color[3];
}

function getPixelColor(x, y) {
    var imageData = ctx.getImageData(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
    var r = 0, g = 0, b = 0, a = 0;
    for (var i = 0; i < imageData.data.length; i += 4) {
        r += imageData.data[i];
        g += imageData.data[i + 1];
        b += imageData.data[i + 2];
        a += imageData.data[i + 3];
    }
    var n = imageData.data.length / 4;
    return [Math.round(r / n), Math.round(g / n), Math.round(b / n), Math.round(a / n)];
}

function processFillStack() {
    var threads = 4; // number of threads to use
    var stackSize = fillStack.length;
    var chunkSize = Math.ceil(stackSize / threads);
    var chunks = [];
    for (var i = 0; i < threads; i++) {
        chunks.push(fillStack.splice(0, chunkSize));
    }
    for (var i = 0; i < threads; i++) {
        (function(chunk) {
            setTimeout(function() {
                for (var j = 0; j < chunk.length; j++) {
                    var x = chunk[j][0];
                    var y = chunk[j][1];
                    var color = chunk[j][2];
                    floodFill(x, y, color);
                }
            }, 0);
        })(chunks[i]);
    }
}
canvas {
    background-color: white;
    border: 1px solid black;
}
#color-picker {
    width: 50px;
    height: 50px;
/*  position: absolute;
    top: 10px;
    left: 10px; */
}
#download {
    display: block;
    margin: 10px auto;
    padding: 10px;
    background-color: black;
    color: white;
    border: none;
    border-radius: 5px;
    cursor: pointer;
}

label, input, button {
    display: block;
    margin: 10px 0;
}

label {
    font-weight: bold;
}

input[type="number"] {
    width: 50px;
}

button {
    padding: 10px;
    background-color: black;
    color: white;
    border: none;
    border-radius: 5px;
    cursor: pointer;
}

#eraser {
    display: none;
}
#eraser + label:before {
    content: "";
    display: inline-block;
    width: 20px;
    height: 20px;
    background-color: white;
    border: 1px solid black;
    margin-right: 5px;
}
#eraser:checked + label:before {
    background-color: black;
}
<canvas id="canvas"></canvas>

<input type="color" id="color-picker">

<input type="checkbox" id="eraser">
<label for="eraser">Eraser</label>

<button id="download">Download</button>

<label for="width">Width:</label>
<input type="number" id="width" value="20">
<label for="height">Height:</label>
<input type="number" id="height" value="20">
<button id="resize">Resize</button>

<button id="fill">Fill</button>

Import Image:
<input type="file" id="image-input">

我试图将彩色像素转换为完全透明的像素,但我最终得到的颜色是白色或什么都没有。

cnwbcb6i

cnwbcb6i1#

就像其他人说的那样,fillRect会在已经存在的东西上作画,而不是替换。这让你可以像在真实的画布上一样混合透明的颜色。
您可以使用clearRect来删除。
mousedownmousemove中的事件侦听器更改为:

if (eraser) {
      ctx.clearRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
    } else {
      ctx.fillStyle = color;
      ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
    }

字符串
关于mozilla docs
Canvas 2D API的CanvasRenderingContext2D.clearRect()方法通过将矩形区域中的像素设置为透明黑色来擦除这些像素。
演示:

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var pixelSize = 10;
var color = '#000000';
var eraser = false;
var fillStack = [];

var fillButton = document.getElementById('fill');

fillButton.addEventListener('click', function () {
  processFillStack();
});

var downloadButton = document.getElementById('download');

downloadButton.addEventListener('click', function () {
  var dataURL = canvas.toDataURL('image/png');
  var link = document.createElement('a');
  link.setAttribute('href', dataURL);
  link.setAttribute('download', 'pixel-art.png');
  link.click();
});

var widthInput = document.getElementById('width');
var heightInput = document.getElementById('height');
var resizeButton = document.getElementById('resize');

resizeButton.addEventListener('click', function () {
  canvas.width = widthInput.value;
  canvas.height = heightInput.value;
  var ctx = canvas.getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);
});

document.getElementById('eraser').addEventListener('change', function () {
  eraser = this.checked;
});

canvas.addEventListener('mousedown', function (e) {
  if (!fillButton.pressed) {
    var x = Math.floor(e.offsetX / pixelSize);
    var y = Math.floor(e.offsetY / pixelSize);
    if (eraser) {
      ctx.clearRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
    } else {
      ctx.fillStyle = color;
      ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
    }
  }
});

canvas.addEventListener('mousemove', function (e) {
  if (e.buttons == 1 && !fillButton.pressed) {
    var x = Math.floor(e.offsetX / pixelSize);
    var y = Math.floor(e.offsetY / pixelSize);
    if (eraser) {
      ctx.clearRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
    } else {
      ctx.fillStyle = color;
      ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
    }
  }
});

var imageInput = document.getElementById('image-input');

imageInput.addEventListener('change', function () {
  var file = imageInput.files[0];
  var reader = new FileReader();
  reader.onload = function (e) {
    var img = new Image();
    img.onload = function () {
      ctx.drawImage(img, 0, 0);
    };
    img.src = e.target.result;
  };
  reader.readAsDataURL(file);
});

function floodFill(x, y, fillColor) {
  var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  var pixelStack = [[x, y]];
  var pixelPos, rowStart, rowEnd, up, down, i;
  while (pixelStack.length) {
    pixelPos = pixelStack.pop();
    rowStart = pixelPos[1] * canvas.width * 4;
    rowEnd = rowStart + canvas.width * 4;
    up = false;
    down = false;
    for (i = rowStart; i < rowEnd; i += 4) {
      if (matchColor(imageData.data, i, fillColor)) {
        continue;
      }
      if (
        matchColor(imageData.data, i, getPixelColor(pixelPos[0], pixelPos[1]))
      ) {
        imageData.data[i] = fillColor[0];
        imageData.data[i + 1] = fillColor[1];
        imageData.data[i + 2] = fillColor[2];
        imageData.data[i + 3] = fillColor[3];
        if (pixelPos[1] > 0) {
          if (
            matchColor(
              imageData.data,
              i - canvas.width * 4,
              getPixelColor(pixelPos[0], pixelPos[1] - 1)
            )
          ) {
            if (!up) {
              pixelStack.push([pixelPos[0], pixelPos[1] - 1]);
              up = true;
            }
          } else if (up) {
            up = false;
          }
        }
        if (pixelPos[1] < canvas.height - 1) {
          if (
            matchColor(
              imageData.data,
              i + canvas.width * 4,
              getPixelColor(pixelPos[0], pixelPos[1] + 1)
            )
          ) {
            if (!down) {
              pixelStack.push([pixelPos[0], pixelPos[1] + 1]);
              down = true;
            }
          } else if (down) {
            down = false;
          }
        }
        if (pixelPos[0] > 0) {
          if (
            matchColor(
              imageData.data,
              i - 4,
              getPixelColor(pixelPos[0] - 1, pixelPos[1])
            )
          ) {
            pixelStack.push([pixelPos[0] - 1, pixelPos[1]]);
          }
        }
        if (pixelPos[0] < canvas.width - 1) {
          if (
            matchColor(
              imageData.data,
              i + 4,
              getPixelColor(pixelPos[0] + 1, pixelPos[1])
            )
          ) {
            pixelStack.push([pixelPos[0] + 1, pixelPos[1]]);
          }
        }
      }
    }
  }
  ctx.putImageData(imageData, 0, 0);
}

function matchColor(data, i, color) {
  return (
    data[i] == color[0] &&
    data[i + 1] == color[1] &&
    data[i + 2] == color[2] &&
    data[i + 3] == color[3]
  );
}

function getPixelColor(x, y) {
  var imageData = ctx.getImageData(
    x * pixelSize,
    y * pixelSize,
    pixelSize,
    pixelSize
  );
  var r = 0,
    g = 0,
    b = 0,
    a = 0;
  for (var i = 0; i < imageData.data.length; i += 4) {
    r += imageData.data[i];
    g += imageData.data[i + 1];
    b += imageData.data[i + 2];
    a += imageData.data[i + 3];
  }
  var n = imageData.data.length / 4;
  return [
    Math.round(r / n),
    Math.round(g / n),
    Math.round(b / n),
    Math.round(a / n),
  ];
}

function processFillStack() {
  var threads = 4; // number of threads to use
  var stackSize = fillStack.length;
  var chunkSize = Math.ceil(stackSize / threads);
  var chunks = [];
  for (var i = 0; i < threads; i++) {
    chunks.push(fillStack.splice(0, chunkSize));
  }
  for (var i = 0; i < threads; i++) {
    (function (chunk) {
      setTimeout(function () {
        for (var j = 0; j < chunk.length; j++) {
          var x = chunk[j][0];
          var y = chunk[j][1];
          var color = chunk[j][2];
          floodFill(x, y, color);
        }
      }, 0);
    })(chunks[i]);
  }
}
canvas {
  background-color: white;
  border: 1px solid black;
}
#color-picker {
  width: 50px;
  height: 50px;
/*  position: absolute;
  top: 10px;
  left: 10px; */
}
#download {
  display: block;
  margin: 10px auto;
  padding: 10px;
  background-color: black;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
}

label, input, button {
  display: block;
  margin: 10px 0;
}

label {
  font-weight: bold;
}

input[type="number"] {
  width: 50px;
}

button {
  padding: 10px;
  background-color: black;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
}

#eraser {
  display: none;
}
#eraser + label:before {
  content: "";
  display: inline-block;
  width: 20px;
  height: 20px;
  background-color: white;
  border: 1px solid black;
  margin-right: 5px;
}
#eraser:checked + label:before {
  background-color: black;
}
<canvas id="canvas"></canvas>

<input type="color" id="color-picker">

<input type="checkbox" id="eraser">
<label for="eraser">Eraser</label>

<button id="download">Download</button>

<label for="width">Width:</label>
<input type="number" id="width" value="20">
<label for="height">Height:</label>
<input type="number" id="height" value="20">
<button id="resize">Resize</button>

<button id="fill">Fill</button>

Import Image:
<input type="file" id="image-input">
yrdbyhpb

yrdbyhpb2#

正如我在这里看到的,当你模拟橡皮擦效果时,你使用了一种半透明的颜色rgba(255,255,255,.1)。我认为这种半透明的颜色与画布的白色背景混合在一起,所以它会给你一个白色区域。
更好的方法是,在这里使用globalCompositeOperation,值为destination-out,这样新的绘图只会在现有绘图透明的区域中绘制。
mousedownmousemove中,当橡皮擦工具处于活动状态时,将globalCompositeOperation属性设置为destination-out
在下面的代码中,globalCompositeOperation在每次绘制操作后设置为source-over,以便正常绘制后续图形;

canvas.addEventListener("mousedown", function(e) {
  if (!fillButton.pressed) {
      var x = Math.floor(e.offsetX / pixelSize);
      var y = Math.floor(e.offsetY / pixelSize);
      if (eraser) {
          ctx.globalCompositeOperation = "destination-out";
          ctx.fillStyle = "rgba(0,0,0,1)";
          ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
      } else {
          ctx.fillStyle = color;
          ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
      }
      ctx.globalCompositeOperation = "source-over";
  }
});

canvas.addEventListener("mousemove", function(e) {
  if (e.buttons == 1 && !fillButton.pressed) {
      var x = Math.floor(e.offsetX / pixelSize);
      var y = Math.floor(e.offsetY / pixelSize);
      if (eraser) {
          ctx.globalCompositeOperation = "destination-out";
          ctx.fillStyle = "rgba(0,0,0,1)";
          ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
      } else {
          ctx.fillStyle = color;
          ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
      }
      ctx.globalCompositeOperation = "source-over";
  }
});

字符串
然后,在显示最终图像时,您需要使用globalCompositeOperation属性将原始画布绘制到其自身上,并设置为destination-out。这将从原始绘图中删除删除的区域;

function displayFinalImage() {
  ctx.globalCompositeOperation = "destination-out";
  ctx.drawImage(canvas, 0, 0);
  ctx.globalCompositeOperation = "source-over";
}


这应该允许您使用任何颜色擦除,包括rgba(0, 0, 0, 0),并且仍然可以看到擦除的区域是透明的。

相关问题