<canvas id="canvas"></canvas>
<h4 id="fill-area"></h4>
canvas {
  background-color: lightgrey;
}
// 이미지 로드 함수
const loadImage = (src) => {
  return fetch(src)
    .then((r) => {
      if (!r.ok) throw new Error("Network response was not ok");

      return r.blob();
    })
    .then((blob) => createImageBitmap(blob));
};

// 이미지 데이터 압축 함수
const getCompressedImageData = (imageData, scale) => {
  const compressSize = Math.floor(1 / scale);
  const compressWidth = Math.ceil(imageData.width / compressSize);
  const compressHeight = Math.ceil(imageData.height / compressSize);

  const tempData = new Uint8ClampedArray(
    compressWidth * compressHeight * 4
  );

  for (let y = 0; y < compressHeight; y++) {
    for (let x = 0; x < compressWidth; x++) {
      const index = y * compressWidth * 4 + x * 4;
      const rgba = imageData.data.slice(
        (y * compressSize * imageData.width + x * compressSize) * 4,
        (y * compressSize * imageData.width + x * compressSize) * 4 + 4
      );

      tempData.set(rgba, index);
    }
  }

  return new ImageData(tempData, compressWidth, compressHeight);
};

// 영역 비율 계산함수
const calculateFillArea = (baseImageData, colorImageData) => {
  let total = 0;
  let progress = 0;

  for (let y = 0; y < baseImageData.height; y++) {
    for (let x = 0; x < baseImageData.width; x++) {
      const index = (y * baseImageData.width + x) * 4;
      const baseRgba = baseImageData.data.slice(index, index + 4);
      const colorRgba = colorImageData.data.slice(index, index + 4);

      if (baseRgba[3] === 255) total += 1;
      if (baseRgba[3] === 255 && colorRgba[3] === 255) progress += 1;
    }
  }

  return ((progress / total) * 100).toFixed(2) + "%";
};

// 이미지 합성 함수
const compositeImageData = (baseImageData, colorImageData) => {
  const tempData = new Uint8ClampedArray(
    baseImageData.width * baseImageData.height * 4
  );

  for (let y = 0; y < baseImageData.height; y++) {
    for (let x = 0; x < baseImageData.width; x++) {
      const index = (y * baseImageData.width + x) * 4;
      const baseRgba = baseImageData.data.slice(index, index + 4);
      const colorRgba = colorImageData.data.slice(index, index + 4);

      if (baseRgba[3] === 255 && colorRgba[3] === 255) {
        tempData.set(colorRgba, index);
      } else {
        tempData.set(baseRgba, index);
      }
    }
  }

  return new ImageData(
    tempData,
    baseImageData.width,
    baseImageData.height
  );
};

const task = async () => {
  const canvas = document.querySelector("#canvas");
  const fillArea = document.querySelector("#fill-area");
  const baseImageSource = `https://w-log.dev/content/images/2024/05/baseImage-1.png`;
  const colorImageSource = `https://w-log.dev/content/images/2024/05/colorImage-1.png`;

  const offCanvas = new OffscreenCanvas(640, 360);
  const ctx = offCanvas.getContext("2d");

  const [baseImageData, colorImageData] = await Promise.all(
    [baseImageSource, colorImageSource].map(async (src) => {
      const bitmap = await loadImage(src);
      offCanvas.width = bitmap.width;
      offCanvas.height = bitmap.height;
      ctx.clearRect(0, 0, 9999, 9999);
      ctx.drawImage(bitmap, 0, 0);

      return getCompressedImageData(
        ctx.getImageData(0, 0, bitmap.width, bitmap.height),
        0.25 // 이미지 데이터를 1/4로 압축
      );
    })
  );

  const fillPercentage = calculateFillArea(baseImageData, colorImageData);
  const compositedImageData = compositeImageData(baseImageData, colorImageData);

  fillArea.textContent = fillPercentage;
  canvas.width = compositedImageData.width;
  canvas.height = compositedImageData.height;
  canvas.getContext("2d").putImageData(compositedImageData, 0, 0);
};

task();
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.