簡體   English   中英

使用 drawImage 從 Tile Sheet、Sprite Sheet 或 Texture Atlas 繪制多個圖像時,如何防止紋理出血?

[英]How Can I Prevent Texture Bleeding When Using drawImage To Draw Multiple Images From A Tile Sheet, Sprite Sheet, Or Texture Atlas?

我正在使用 HTML5 畫布 API 為像素藝術游戲繪制瓷磚地圖。 渲染的瓦片地圖由許多較小的圖像組成,這些圖像是從稱為瓦片表的單個源圖像中剪切出來的。 我正在使用drawImage(src_img, sx, sy, sw, sh, dx, dy, dw, dh)從源圖像中切割出單個圖塊並將它們繪制到目標畫布上。 我正在使用setTransform(sx, 0, 0, sy, tx, ty)將縮放和平移應用於最終渲染的圖像。

我需要修復的顏色“出血”問題是由采樣器引起的,它在縮放操作期間使用插值來混合顏色,以使事物看起來不會像素化。 這對於縮放數碼照片非常有用,但不適用於像素藝術。 雖然這不會對圖塊的中心造成太大的視覺損害,但采樣器會沿着源圖像中相鄰圖塊的邊緣混合顏色,從而在渲染的圖塊地圖中產生意想不到的顏色。 采樣器不僅僅使用傳遞給drawImage的源矩形內的顏色,而是混合來自其邊界之外的顏色,從而導致瓷磚之間出現間隙。

下面是我的瓷磚表的源圖像。 它的實際大小是 24x24 像素,但我在 GIMP 中將其放大到 96x96 像素,以便您可以看到它。 我在 GIMP 的縮放工具上使用了“插值:無”設置。 正如您所看到的,由於采樣器沒有對顏色進行插值,因此各個圖塊周圍沒有間隙或模糊的邊界。 即使將imageSmoothingEnabled設置為false ,畫布 API 的采樣器顯然也會插入顏色。

在此處輸入圖片說明

下面是imageSmoothingEnabled設置為true的渲染圖塊地圖的imageSmoothingEnabled 左箭頭指向灰色瓷磚底部的一些紅色出血。 這是因為紅色瓷磚在瓷磚表中的灰色瓷磚正下方。 采樣器正在將紅色混合到灰色瓷磚的底部邊緣。

右側的箭頭指向綠色圖塊的右邊緣。 如您所見,沒有顏色滲入其中。 這是因為源圖像中綠色圖塊的右側沒有任何東西,因此采樣器沒有任何東西可以混合。

啟用圖像平滑的平鋪地圖

下面是imageSmoothingEnabled設置為false的渲染圖塊地圖。 根據比例和翻譯,仍然會發生紋理滲色。 左箭頭指向從源圖像中的紅色瓷磚滲出的紅色。 視覺傷害減少了,但仍然存在。

右箭頭指向最右側綠色磁貼的問題,它有一條細灰色線從源圖像中的灰色磁貼滲入,位於綠色磁貼的左側。

禁用圖像平滑的平鋪地圖

上面的兩張圖片是從 Edge 截屏的。 Chrome 和 Firefox 在隱藏出血方面做得更好。 Edge 似乎在四面八方流血,但 Chrome 和 Firefox 似乎只在源矩形的右側和底部流血。

如果有人知道如何解決這個問題,請告訴我。 人們在很多論壇上詢問這個問題,並得到解決以下問題的答案:

  • 用邊框顏色填充源圖塊,以便采樣器沿邊緣混合相同的顏色。
  • 將您的源切片放在單獨的文件中,這樣采樣器就沒有任何東西可以超過邊界進行采樣。
  • 將所有內容繪制到未縮放的緩沖區畫布,然后縮放緩沖區,確保采樣器混合來自作為最終圖像一部分的相鄰圖塊的顏色,從而減輕視覺損壞。
  • 將所有內容繪制到未縮放的畫布上,然后使用 CSS 使用image-rendering:pixelated對其進行縮放,這與之前的工作基本相同。

我想避免變通,但是如果您知道另一個變通方法,請發布。 我想知道是否有辦法關閉采樣或插值,或者是否有任何其他方法可以阻止紋理流血,這不是我列出的解決方法之一。

這是一個展示問題的小提琴: https : //jsfiddle.net/0rv1upjf/

你可以在我的 Github Pages 頁面上看到同樣的例子: https : //frankpoth.info/pages/javascript-projects/content/texture-bleeding/texture-bleeding.html

更新:

由於在繪制像素時使用了浮點數,因此出現了問題。 解決方案是避免浮點數,只使用整數。 不幸的是,這意味着 setTransform 無法有效使用,因為縮放通常會導致浮點數,但我仍然設法在 tile 渲染循環中保留了一些數學知識。 這是代碼:

function drawRounded(source_image, context, scale) {

  var offset_x = -OFFSET.x * scale + context.canvas.width  * 0.5;
  var offset_y = -OFFSET.y * scale + context.canvas.height * 0.5;

  var map_height = (MAP_HEIGHT * scale)|0; // Similar to Math.trunc(MAP_HEIGHT * scale);
  var map_width  = (MAP_WIDTH  * scale)|0;
  var tile_size  = TILE_SIZE * scale;

  var rendered_tile_size = (tile_size + 1)|0; // Similar to Math.ceil(tile_size);

  var map_index = 0; // Track the tile index in the map. This increases once per draw loop.

  /* Loop through all tile positions in actual coordinate space so no additional calculations based on grid index are needed. */
  for (var y = 0; y < map_height; y += tile_size) { // y first so we draw rows from top to bottom

    for (var x = 0; x < map_width; x += tile_size) {

      var frame = FRAMES[MAP[map_index]]; // The frame is the source location of the tile in the source_image.

      // We have to keep the dx, dy truncation inside the loop to ensure the highest level of accuracy possible. 
      context.drawImage(source_image, frame.x, frame.y, TILE_SIZE, TILE_SIZE, (offset_x + x)|0, (offset_y + y)|0, rendered_tile_size, rendered_tile_size);

      map_index ++;

    }

  }

}

我正在使用按位或或 | 運算符來做我的四舍五入。 按位或在每個位位置返回 1,其中一個或兩個操作數的相應位為 1。 按位運算會將浮點數轉換為整數。 使用 0 作為右操作數將匹配左操作數中的所有位並截斷小數。 這樣做的缺點是它只支持 32 位,但我懷疑我的圖塊位置永遠不會超過 32 位。

例如:

-10.5 | 0 == -10

10.1 | 0 == 10

10.5 | 0 == 10

在二進制中:

1010 | 0000 == 1010

這是一個四舍五入的問題。

當上下文被精確地轉換為n.5 ,Safari 瀏覽器上已經存在關於這個問題的問題,Edge 和 IE 更糟糕並且總是以一種或另一種方式流血, n.5 Chrome 也在 n.5 上流血,但只有繪制 <img> 時,<canvas> 很好。

至少可以說,這是一個有問題的區域。

我沒有檢查規格以確切知道他們應該做什么,但有一個簡單的解決方法。

自己計算坐標的變換,以便您可以准確控制它們將如何四舍五入。
這樣您甚至不需要關閉圖像平滑算法,您將始終擁有清晰的像素,因為您將始終在像素邊界上進行繪制:

// First calculate the scaled translations
const scaled_offset_left = -OFFSET.x * scale + context.canvas.width * 0.5;
const scaled_offset_top  = -OFFSET.y * scale + context.canvas.height * 0.5;

// when drawing each tile

const dest_x = Math.floor( scaled_offset_left + (x * scale) );
const dest_y = Math.floor( scaled_offset_top  + (y * scale) );
const dest_size = Math.ceil( TILE_SIZE * scale );

context.drawImage( source_image,
  frame.x, frame.y, TILE_SIZE, TILE_SIZE,
  dest_x, dest_y, dest_size, dest_size,
);

 /* This is the tile map. Each value is a frame index in the FRAMES array. Each frame tells drawImage where to blit the source from */ const MAP = [ 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 0, 1, 0, 1, 2, 2, 1, 2, 3, 2, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 3, 4, 3, 4, 5, 5, 4, 5, 6, 5, 3, 4, 3, 4, 5, 5, 4, 5, 6, 5, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 6, 7, 6, 7, 8, 8, 7, 8, 0, 8, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8 ]; const TILE_SIZE = 8; // Each tile is 8x8 pixels const MAP_HEIGHT = 80; // The map is 80 pixels tall by 80 pixels wide const MAP_WIDTH = 80; /* Each frame represents the source x, y coordinates of a tile in the source image. They are indexed according to the map values */ const FRAMES = [ { x:0, y:0 }, // map value = 0 { x:8, y:0 }, // map value = 1 { x:16, y:0 }, // map value = 2 { x:0, y:8 }, // etc. { x:8, y:8 }, { x:16, y:8}, { x:0, y:16}, { x:8, y:16}, { x:16, y:16} ]; /* These represent the state of the keyboard keys being used. false is up and true is down */ const KEYS = { down: false, left: false, right: false, scale_down: false, // the D key scale_up: false, // the F key up: false } /* This is the scroll offset. You can also think of it as the position of the red dot in the map. */ const OFFSET = { x: MAP_WIDTH * 0.5, y: MAP_HEIGHT * 0.5 }; // It starts out centered in the map. const MAX_SCALE = 75; // Max scale is 75 times larger than the actual image size. const MIN_SCALE = 0; // Texture bleeding seems to only occur on upscale, but min scale is 0 in case you want to try it. var scale = 4.71; // some arbitrary number that will hopefully cause the issue in your browser /* Get the canvas drawing context. */ var context = document.querySelector('canvas').getContext('2d', { alpha: false, desynchronized: true }); /* The toggle button is the div */ var toggle = document.querySelector('div'); /* The source image is a 24x24 square with 9 tile images of various colors in it. */ var base_64_image_source = 'data:image/png;base64,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'; var source_image = new Image(); // This will be the source image /* The keyboard event handler */ function keyDownUp(event) { var state = event.type == 'keydown' ? true : false; switch (event.keyCode) { case 37: KEYS.left = state; break; case 38: KEYS.up = state; break; case 39: KEYS.right = state; break; case 40: KEYS.down = state; break; case 68: KEYS.scale_down = state; break; case 70: KEYS.scale_up = state; } } /* This is the update and rendering loop. It handles input and draws the images. */ function loop() { window.requestAnimationFrame(loop); // Perpetuate the loop /* Prepare to move and scale the image with the keyboard input */ if (KEYS.left) OFFSET.x -= 0.5; if (KEYS.right) OFFSET.x += 0.5; if (KEYS.up) OFFSET.y -= 0.5; if (KEYS.down) OFFSET.y += 0.5; if (KEYS.scale_down) scale -= 0.5 * scale / MAX_SCALE; if (KEYS.scale_up) scale += 0.5 * scale / MAX_SCALE; /* Keep the scale size within a defined range */ if (scale > MAX_SCALE) scale = MAX_SCALE; else if (scale < MIN_SCALE) scale = MIN_SCALE; /* Clear the canvas to gray. */ context.setTransform(1, 0, 0, 1, 0, 0); // Set the transform back to the identity matrix context.fillStyle = "#202830"; // Set the fill color to gray context.fillRect(0, 0, context.canvas.width, context.canvas.height); // fill the entire canvas /* [EDIT] Don't set the transform, we will calculate it ourselves // context.setTransform(scale, 0, 0, scale, -OFFSET.x * scale + context.canvas.width * 0.5, -OFFSET.y * scale + context.canvas.height * 0.5); First step is calculating the scaled translation */ const scaled_offset_left = -OFFSET.x * scale + context.canvas.width * 0.5; const scaled_offset_top = -OFFSET.y * scale + context.canvas.height * 0.5; let map_index = 0; // Track the tile index in the map. This increases once per draw loop. /* Loop through all tile positions in actual coordinate space so no additional calculations based on grid index are needed. */ for (let y = 0; y < MAP_HEIGHT; y += TILE_SIZE) { // y first so we draw rows from top to bottom for (let x = 0; x < MAP_WIDTH; x += TILE_SIZE) { const frame = FRAMES[MAP[map_index]]; // The frame is the source location of the tile in the source_image. /* [EDIT] We transform the coordinates ourselves We can control a uniform rounding by using floor and ceil */ const dest_x = Math.floor( scaled_offset_left + (x * scale) ); const dest_y = Math.floor( scaled_offset_top + (y * scale) ); const dest_size = Math.ceil(TILE_SIZE * scale); context.drawImage( source_image, frame.x, frame.y, TILE_SIZE, TILE_SIZE, dest_x, dest_y, dest_size, dest_size ); map_index++; } } /* Draw the red dot in the center of the screen. */ context.fillStyle = "#ff0000"; /* [EDIT] Do the same kind of calculations for the "dot" if you don't want antialiasing // const dot_x = Math.floor( scaled_offset_left + ((OFFSET.x - 0.5) * scale) ); // const dot_y = Math.floor( scaled_offset_top + ((OFFSET.y - 0.5) * scale) ); // const dot_size = Math.ceil( scale ); // context.fillRect( dot_x, dot_y, dot_size, dot_size ); // center on the dot But if you do want antialiasing for the dot, then just set the transformation for this drawing */ context.setTransform(scale, 0, 0, scale, scaled_offset_left, scaled_offset_top); context.fillRect( (OFFSET.x - 0.5), (OFFSET.y - 0.5), 1, 1 ); // center on the dot var smoothing = context.imageSmoothingEnabled; // Get the current smoothing value because we are going to ignore it briefly. /* Draw the source image in the top left corner for reference. */ context.setTransform(4, 0, 0, 4, 0, 0); // Zoom in on it so it's visible. context.imageSmoothingEnabled = false; // Set smoothing to false so we get a crisp source image representation (the real source image is not scaled at all). context.drawImage( source_image, 0, 0 ); context.imageSmoothingEnabled = smoothing; // Set smoothing back the way it was according to the toggle choice. } /* Turn image smoothing on and off when you press the toggle. */ function toggleSmoothing(event) { context.imageSmoothingEnabled = !context.imageSmoothingEnabled; if (context.imageSmoothingEnabled) toggle.innerText = 'Smoothing Enabled'; // Make sure the button has appropriate text in it. else toggle.innerText = 'Smoothing Disabled'; } /* The main loop will start after the source image is loaded to ensure there is something to draw. */ source_image.addEventListener('load', (event) => { window.requestAnimationFrame(loop); // Start the loop }, { once: true }); /* Add the toggle smoothing click handler to the div. */ toggle.addEventListener('click', toggleSmoothing); /* Add keyboard input */ window.addEventListener('keydown', keyDownUp); window.addEventListener('keyup', keyDownUp); /* Resize the canvas. */ context.canvas.width = 480; context.canvas.height = 480; toggleSmoothing(); // Set imageSmoothingEnabled /* Load the source image from the base64 string. */ source_image.setAttribute('src', base_64_image_source);
 * { box-sizing: border-box; margin: 0; overflow: hidden; padding: 0; user-select: none; } body, html { background-color: #202830; color: #ffffff; height: 100%; width: 100%; } body { align-items: center; display: grid; justify-items: center; } p { max-width: 640px; } div { border: #ffffff 2px solid; bottom: 4px; cursor: pointer; padding: 8px; position: fixed; right: 4px }
 <div>Smoothing Disabled</div> <p>Use the arrow keys to scroll and the D and F keys to scale. The source image is represented on the top left. Notice the vertical and horizontal lines that appear between tiles as you scroll and scale. They are the color of the tile's neighbor in the source image. This may be due to color sampling that occurs during scaling. Click the toggle to set imageSmoothingEnabled on the drawing context.</p> <canvas></canvas>

請注意,要繪制您的“播放器”點,您可以選擇手動執行相同的校准以避免由抗鋸齒引起的模糊,或者如果您確實想要這種模糊,那么您可以簡單地僅為該點設置變換。 在你的位置上,我什至可能會在經過一定比例的回合后制作一些模塊化的東西,並在下面平滑,但我會讓讀者去做那個實現。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM