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

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

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

  1. var canvas = document.getElementById("canvas");
  2. var ctx = canvas.getContext("2d");
  3. var pixelSize = 10;
  4. var color = "#000000";
  5. var eraser = false;
  6. var fillStack = [];
  7. var fillButton = document.getElementById("fill");
  8. fillButton.addEventListener("click", function() {
  9. processFillStack();
  10. });
  11. var downloadButton = document.getElementById("download");
  12. downloadButton.addEventListener("click", function() {
  13. var dataURL = canvas.toDataURL("image/png");
  14. var link = document.createElement("a");
  15. link.setAttribute("href", dataURL);
  16. link.setAttribute("download", "pixel-art.png");
  17. link.click();
  18. });
  19. var widthInput = document.getElementById("width");
  20. var heightInput = document.getElementById("height");
  21. var resizeButton = document.getElementById("resize");
  22. resizeButton.addEventListener("click", function() {
  23. canvas.width = widthInput.value;
  24. canvas.height = heightInput.value;
  25. var ctx = canvas.getContext("2d");
  26. ctx.clearRect(0, 0, canvas.width, canvas.height);
  27. });
  28. document.getElementById("eraser").addEventListener("change", function() {
  29. eraser = this.checked;
  30. });
  31. canvas.addEventListener("mousedown", function(e) {
  32. if (!fillButton.pressed) {
  33. var x = Math.floor(e.offsetX / pixelSize);
  34. var y = Math.floor(e.offsetY / pixelSize);
  35. if (eraser) {
  36. ctx.fillStyle = "rgba(255,255,255,.1)";
  37. } else {
  38. ctx.fillStyle = color;
  39. }
  40. ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
  41. }
  42. });
  43. canvas.addEventListener("mousemove", function(e) {
  44. if (e.buttons == 1 && !fillButton.pressed) {
  45. var x = Math.floor(e.offsetX / pixelSize);
  46. var y = Math.floor(e.offsetY / pixelSize);
  47. if (eraser) {
  48. ctx.fillStyle = "rgba(255,255,255,.1)";
  49. } else {
  50. ctx.fillStyle = color;
  51. }
  52. ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
  53. }
  54. });
  55. var imageInput = document.getElementById("image-input");
  56. imageInput.addEventListener("change", function() {
  57. var file = imageInput.files[0];
  58. var reader = new FileReader();
  59. reader.onload = function(e) {
  60. var img = new Image();
  61. img.onload = function() {
  62. ctx.drawImage(img, 0, 0);
  63. };
  64. img.src = e.target.result;
  65. };
  66. reader.readAsDataURL(file);
  67. });
  68. function floodFill(x, y, fillColor) {
  69. var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  70. var pixelStack = [[x, y]];
  71. var pixelPos, rowStart, rowEnd, up, down, i;
  72. while (pixelStack.length) {
  73. pixelPos = pixelStack.pop();
  74. rowStart = pixelPos[1] * canvas.width * 4;
  75. rowEnd = rowStart + canvas.width * 4;
  76. up = false;
  77. down = false;
  78. for (i = rowStart; i < rowEnd; i += 4) {
  79. if (matchColor(imageData.data, i, fillColor)) {
  80. continue;
  81. }
  82. if (matchColor(imageData.data, i, getPixelColor(pixelPos[0], pixelPos[1]))) {
  83. imageData.data[i] = fillColor[0];
  84. imageData.data[i + 1] = fillColor[1];
  85. imageData.data[i + 2] = fillColor[2];
  86. imageData.data[i + 3] = fillColor[3];
  87. if (pixelPos[1] > 0) {
  88. if (matchColor(imageData.data, i - canvas.width * 4, getPixelColor(pixelPos[0], pixelPos[1] - 1))) {
  89. if (!up) {
  90. pixelStack.push([pixelPos[0], pixelPos[1] - 1]);
  91. up = true;
  92. }
  93. } else if (up) {
  94. up = false;
  95. }
  96. }
  97. if (pixelPos[1] < canvas.height - 1) {
  98. if (matchColor(imageData.data, i + canvas.width * 4, getPixelColor(pixelPos[0], pixelPos[1] + 1))) {
  99. if (!down) {
  100. pixelStack.push([pixelPos[0], pixelPos[1] + 1]);
  101. down = true;
  102. }
  103. } else if (down) {
  104. down = false;
  105. }
  106. }
  107. if (pixelPos[0] > 0) {
  108. if (matchColor(imageData.data, i - 4, getPixelColor(pixelPos[0] - 1, pixelPos[1]))) {
  109. pixelStack.push([pixelPos[0] - 1, pixelPos[1]]);
  110. }
  111. }
  112. if (pixelPos[0] < canvas.width - 1) {
  113. if (matchColor(imageData.data, i + 4, getPixelColor(pixelPos[0] + 1, pixelPos[1]))) {
  114. pixelStack.push([pixelPos[0] + 1, pixelPos[1]]);
  115. }
  116. }
  117. }
  118. }
  119. }
  120. ctx.putImageData(imageData, 0, 0);
  121. }
  122. function matchColor(data, i, color) {
  123. return data[i] == color[0] && data[i + 1] == color[1] && data[i + 2] == color[2] && data[i + 3] == color[3];
  124. }
  125. function getPixelColor(x, y) {
  126. var imageData = ctx.getImageData(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
  127. var r = 0, g = 0, b = 0, a = 0;
  128. for (var i = 0; i < imageData.data.length; i += 4) {
  129. r += imageData.data[i];
  130. g += imageData.data[i + 1];
  131. b += imageData.data[i + 2];
  132. a += imageData.data[i + 3];
  133. }
  134. var n = imageData.data.length / 4;
  135. return [Math.round(r / n), Math.round(g / n), Math.round(b / n), Math.round(a / n)];
  136. }
  137. function processFillStack() {
  138. var threads = 4; // number of threads to use
  139. var stackSize = fillStack.length;
  140. var chunkSize = Math.ceil(stackSize / threads);
  141. var chunks = [];
  142. for (var i = 0; i < threads; i++) {
  143. chunks.push(fillStack.splice(0, chunkSize));
  144. }
  145. for (var i = 0; i < threads; i++) {
  146. (function(chunk) {
  147. setTimeout(function() {
  148. for (var j = 0; j < chunk.length; j++) {
  149. var x = chunk[j][0];
  150. var y = chunk[j][1];
  151. var color = chunk[j][2];
  152. floodFill(x, y, color);
  153. }
  154. }, 0);
  155. })(chunks[i]);
  156. }
  157. }
  1. canvas {
  2. background-color: white;
  3. border: 1px solid black;
  4. }
  5. #color-picker {
  6. width: 50px;
  7. height: 50px;
  8. /* position: absolute;
  9. top: 10px;
  10. left: 10px; */
  11. }
  12. #download {
  13. display: block;
  14. margin: 10px auto;
  15. padding: 10px;
  16. background-color: black;
  17. color: white;
  18. border: none;
  19. border-radius: 5px;
  20. cursor: pointer;
  21. }
  22. label, input, button {
  23. display: block;
  24. margin: 10px 0;
  25. }
  26. label {
  27. font-weight: bold;
  28. }
  29. input[type="number"] {
  30. width: 50px;
  31. }
  32. button {
  33. padding: 10px;
  34. background-color: black;
  35. color: white;
  36. border: none;
  37. border-radius: 5px;
  38. cursor: pointer;
  39. }
  40. #eraser {
  41. display: none;
  42. }
  43. #eraser + label:before {
  44. content: "";
  45. display: inline-block;
  46. width: 20px;
  47. height: 20px;
  48. background-color: white;
  49. border: 1px solid black;
  50. margin-right: 5px;
  51. }
  52. #eraser:checked + label:before {
  53. background-color: black;
  54. }
  1. <canvas id="canvas"></canvas>
  2. <input type="color" id="color-picker">
  3. <input type="checkbox" id="eraser">
  4. <label for="eraser">Eraser</label>
  5. <button id="download">Download</button>
  6. <label for="width">Width:</label>
  7. <input type="number" id="width" value="20">
  8. <label for="height">Height:</label>
  9. <input type="number" id="height" value="20">
  10. <button id="resize">Resize</button>
  11. <button id="fill">Fill</button>
  12. Import Image:
  13. <input type="file" id="image-input">

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

cnwbcb6i

cnwbcb6i1#

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

  1. if (eraser) {
  2. ctx.clearRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
  3. } else {
  4. ctx.fillStyle = color;
  5. ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
  6. }

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

  1. var canvas = document.getElementById('canvas');
  2. var ctx = canvas.getContext('2d');
  3. var pixelSize = 10;
  4. var color = '#000000';
  5. var eraser = false;
  6. var fillStack = [];
  7. var fillButton = document.getElementById('fill');
  8. fillButton.addEventListener('click', function () {
  9. processFillStack();
  10. });
  11. var downloadButton = document.getElementById('download');
  12. downloadButton.addEventListener('click', function () {
  13. var dataURL = canvas.toDataURL('image/png');
  14. var link = document.createElement('a');
  15. link.setAttribute('href', dataURL);
  16. link.setAttribute('download', 'pixel-art.png');
  17. link.click();
  18. });
  19. var widthInput = document.getElementById('width');
  20. var heightInput = document.getElementById('height');
  21. var resizeButton = document.getElementById('resize');
  22. resizeButton.addEventListener('click', function () {
  23. canvas.width = widthInput.value;
  24. canvas.height = heightInput.value;
  25. var ctx = canvas.getContext('2d');
  26. ctx.clearRect(0, 0, canvas.width, canvas.height);
  27. });
  28. document.getElementById('eraser').addEventListener('change', function () {
  29. eraser = this.checked;
  30. });
  31. canvas.addEventListener('mousedown', function (e) {
  32. if (!fillButton.pressed) {
  33. var x = Math.floor(e.offsetX / pixelSize);
  34. var y = Math.floor(e.offsetY / pixelSize);
  35. if (eraser) {
  36. ctx.clearRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
  37. } else {
  38. ctx.fillStyle = color;
  39. ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
  40. }
  41. }
  42. });
  43. canvas.addEventListener('mousemove', function (e) {
  44. if (e.buttons == 1 && !fillButton.pressed) {
  45. var x = Math.floor(e.offsetX / pixelSize);
  46. var y = Math.floor(e.offsetY / pixelSize);
  47. if (eraser) {
  48. ctx.clearRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
  49. } else {
  50. ctx.fillStyle = color;
  51. ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
  52. }
  53. }
  54. });
  55. var imageInput = document.getElementById('image-input');
  56. imageInput.addEventListener('change', function () {
  57. var file = imageInput.files[0];
  58. var reader = new FileReader();
  59. reader.onload = function (e) {
  60. var img = new Image();
  61. img.onload = function () {
  62. ctx.drawImage(img, 0, 0);
  63. };
  64. img.src = e.target.result;
  65. };
  66. reader.readAsDataURL(file);
  67. });
  68. function floodFill(x, y, fillColor) {
  69. var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  70. var pixelStack = [[x, y]];
  71. var pixelPos, rowStart, rowEnd, up, down, i;
  72. while (pixelStack.length) {
  73. pixelPos = pixelStack.pop();
  74. rowStart = pixelPos[1] * canvas.width * 4;
  75. rowEnd = rowStart + canvas.width * 4;
  76. up = false;
  77. down = false;
  78. for (i = rowStart; i < rowEnd; i += 4) {
  79. if (matchColor(imageData.data, i, fillColor)) {
  80. continue;
  81. }
  82. if (
  83. matchColor(imageData.data, i, getPixelColor(pixelPos[0], pixelPos[1]))
  84. ) {
  85. imageData.data[i] = fillColor[0];
  86. imageData.data[i + 1] = fillColor[1];
  87. imageData.data[i + 2] = fillColor[2];
  88. imageData.data[i + 3] = fillColor[3];
  89. if (pixelPos[1] > 0) {
  90. if (
  91. matchColor(
  92. imageData.data,
  93. i - canvas.width * 4,
  94. getPixelColor(pixelPos[0], pixelPos[1] - 1)
  95. )
  96. ) {
  97. if (!up) {
  98. pixelStack.push([pixelPos[0], pixelPos[1] - 1]);
  99. up = true;
  100. }
  101. } else if (up) {
  102. up = false;
  103. }
  104. }
  105. if (pixelPos[1] < canvas.height - 1) {
  106. if (
  107. matchColor(
  108. imageData.data,
  109. i + canvas.width * 4,
  110. getPixelColor(pixelPos[0], pixelPos[1] + 1)
  111. )
  112. ) {
  113. if (!down) {
  114. pixelStack.push([pixelPos[0], pixelPos[1] + 1]);
  115. down = true;
  116. }
  117. } else if (down) {
  118. down = false;
  119. }
  120. }
  121. if (pixelPos[0] > 0) {
  122. if (
  123. matchColor(
  124. imageData.data,
  125. i - 4,
  126. getPixelColor(pixelPos[0] - 1, pixelPos[1])
  127. )
  128. ) {
  129. pixelStack.push([pixelPos[0] - 1, pixelPos[1]]);
  130. }
  131. }
  132. if (pixelPos[0] < canvas.width - 1) {
  133. if (
  134. matchColor(
  135. imageData.data,
  136. i + 4,
  137. getPixelColor(pixelPos[0] + 1, pixelPos[1])
  138. )
  139. ) {
  140. pixelStack.push([pixelPos[0] + 1, pixelPos[1]]);
  141. }
  142. }
  143. }
  144. }
  145. }
  146. ctx.putImageData(imageData, 0, 0);
  147. }
  148. function matchColor(data, i, color) {
  149. return (
  150. data[i] == color[0] &&
  151. data[i + 1] == color[1] &&
  152. data[i + 2] == color[2] &&
  153. data[i + 3] == color[3]
  154. );
  155. }
  156. function getPixelColor(x, y) {
  157. var imageData = ctx.getImageData(
  158. x * pixelSize,
  159. y * pixelSize,
  160. pixelSize,
  161. pixelSize
  162. );
  163. var r = 0,
  164. g = 0,
  165. b = 0,
  166. a = 0;
  167. for (var i = 0; i < imageData.data.length; i += 4) {
  168. r += imageData.data[i];
  169. g += imageData.data[i + 1];
  170. b += imageData.data[i + 2];
  171. a += imageData.data[i + 3];
  172. }
  173. var n = imageData.data.length / 4;
  174. return [
  175. Math.round(r / n),
  176. Math.round(g / n),
  177. Math.round(b / n),
  178. Math.round(a / n),
  179. ];
  180. }
  181. function processFillStack() {
  182. var threads = 4; // number of threads to use
  183. var stackSize = fillStack.length;
  184. var chunkSize = Math.ceil(stackSize / threads);
  185. var chunks = [];
  186. for (var i = 0; i < threads; i++) {
  187. chunks.push(fillStack.splice(0, chunkSize));
  188. }
  189. for (var i = 0; i < threads; i++) {
  190. (function (chunk) {
  191. setTimeout(function () {
  192. for (var j = 0; j < chunk.length; j++) {
  193. var x = chunk[j][0];
  194. var y = chunk[j][1];
  195. var color = chunk[j][2];
  196. floodFill(x, y, color);
  197. }
  198. }, 0);
  199. })(chunks[i]);
  200. }
  201. }
  1. canvas {
  2. background-color: white;
  3. border: 1px solid black;
  4. }
  5. #color-picker {
  6. width: 50px;
  7. height: 50px;
  8. /* position: absolute;
  9. top: 10px;
  10. left: 10px; */
  11. }
  12. #download {
  13. display: block;
  14. margin: 10px auto;
  15. padding: 10px;
  16. background-color: black;
  17. color: white;
  18. border: none;
  19. border-radius: 5px;
  20. cursor: pointer;
  21. }
  22. label, input, button {
  23. display: block;
  24. margin: 10px 0;
  25. }
  26. label {
  27. font-weight: bold;
  28. }
  29. input[type="number"] {
  30. width: 50px;
  31. }
  32. button {
  33. padding: 10px;
  34. background-color: black;
  35. color: white;
  36. border: none;
  37. border-radius: 5px;
  38. cursor: pointer;
  39. }
  40. #eraser {
  41. display: none;
  42. }
  43. #eraser + label:before {
  44. content: "";
  45. display: inline-block;
  46. width: 20px;
  47. height: 20px;
  48. background-color: white;
  49. border: 1px solid black;
  50. margin-right: 5px;
  51. }
  52. #eraser:checked + label:before {
  53. background-color: black;
  54. }
  1. <canvas id="canvas"></canvas>
  2. <input type="color" id="color-picker">
  3. <input type="checkbox" id="eraser">
  4. <label for="eraser">Eraser</label>
  5. <button id="download">Download</button>
  6. <label for="width">Width:</label>
  7. <input type="number" id="width" value="20">
  8. <label for="height">Height:</label>
  9. <input type="number" id="height" value="20">
  10. <button id="resize">Resize</button>
  11. <button id="fill">Fill</button>
  12. Import Image:
  13. <input type="file" id="image-input">
展开查看全部
yrdbyhpb

yrdbyhpb2#

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

  1. canvas.addEventListener("mousedown", function(e) {
  2. if (!fillButton.pressed) {
  3. var x = Math.floor(e.offsetX / pixelSize);
  4. var y = Math.floor(e.offsetY / pixelSize);
  5. if (eraser) {
  6. ctx.globalCompositeOperation = "destination-out";
  7. ctx.fillStyle = "rgba(0,0,0,1)";
  8. ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
  9. } else {
  10. ctx.fillStyle = color;
  11. ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
  12. }
  13. ctx.globalCompositeOperation = "source-over";
  14. }
  15. });
  16. canvas.addEventListener("mousemove", function(e) {
  17. if (e.buttons == 1 && !fillButton.pressed) {
  18. var x = Math.floor(e.offsetX / pixelSize);
  19. var y = Math.floor(e.offsetY / pixelSize);
  20. if (eraser) {
  21. ctx.globalCompositeOperation = "destination-out";
  22. ctx.fillStyle = "rgba(0,0,0,1)";
  23. ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
  24. } else {
  25. ctx.fillStyle = color;
  26. ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
  27. }
  28. ctx.globalCompositeOperation = "source-over";
  29. }
  30. });

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

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


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

展开查看全部

相关问题