缩放画布/矩阵并应用所有变换,以在画布中获得质量更好的图像

问题描述 投票:0回答:1

stackoferflow 的一位成员帮助修复了我的脚本,他获得了很好的结果,但我仍然需要一些帮助。

我有一个 DIV,其中包含一些其他 div,最后是一个图像。 该图像尺寸较大,但直接从 CSS 设置的宽度/高度较小(小 3 倍,数字无关)。 该图像应用了 CSS 转换(旋转/缩放、平移)。

enter image description here

该脚本创建一个与 div 大小相同的画布,并在画布中绘制图像,裁剪其外部部分(用白色填充)。最后,它在正常位置绘制图像(未应用旋转),但外部部分为白色(因此被裁剪)。 它工作完美。问题是我想将此画布缩放 3 倍(或 5 倍)以使其质量更好,因为我的图像更大(自然尺寸),但由于 CSS 精确宽度/高度设置,它显示得更小。

我尝试重新缩放(调整大小)画布,但它只会产生不好的结果,因为它重新缩放了小图像(损失了很多质量)。

我想修改脚本,因此它根据比例因子(假设 SCALE_FACTOR=3)对所有步骤应用缩放。 在我的示例中,div 大小为 400x400 像素,因此最终画布将为 1200x1200 像素,并且图像将绘制为大 3 倍,但位置完全相同(因此裁剪结果是相同的,只是质量更高)。

我尝试在矩阵和逆矩阵上应用 .scale,但它没有给出我正在寻找的结果。当然,我用*SCALE_FACTOR等增加了containerWidth...

我想获得这个(但是高质量的绘图,而不是像我现在那样重新缩放)。 enter image description here

这是我正在使用的代码:

<!DOCTYPE html>
<html>
<head>
<style>
body {
  font-family: Arial, sans-serif;
  margin: 20px;
}

.object-render--canvas {
  width: 400px;
  /* You can change this to any size */
  height: 400px;
  /* You can change this to any size */
  border: 2px solid #000;
  position: relative;
  overflow: hidden;
  margin-bottom: 20px;
}

 #dd6f9f50-780e-11ef-b763-11ebd40cd3a6 {
      width: 326px; /* Scaled-down width */
      height: 290.3px; /* Scaled-down height */
      transform: translate(230px, -130px) rotate(145deg) scale(1);
      transform-origin: center center;
      position: absolute;
      top: 0;
      left: 0;
    }

#buttons {
  margin-bottom: 20px;
}

#tempCanvases {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

#tempCanvases canvas {
  border: 1px solid #ccc;
}
</style>

</head>
<body>
<h1>Dynamic Image Transformation and Cropping</h1>
<div class="object-render--canvas">
  <img id="dd6f9f50-780e-11ef-b763-11ebd40cd3a6" src="https://i.sstatic.net/oJs20ysA.png" alt="Source Image">
</div>
<div id="buttons">
  <button id="processButton">Process</button>
  <button id="downloadButton" disabled>Download</button>
</div>
<h2>Temporary Canvases (For Debugging)</h2>

<script type='text/javascript'>
// Wait for the DOM to load
document.addEventListener('DOMContentLoaded', () => {
  const processButton = document.getElementById('processButton');
  const downloadButton = document.getElementById('downloadButton');
  
  let finalCanvas = null;
  let finalImageCanvas=null;

  processButton.addEventListener('click', () => {
    // Clear previous temporary canvases
    downloadButton.disabled = true;

    processCanvas();

    // Enable the download button
    downloadButton.disabled = false;
  });

  downloadButton.addEventListener('click', () => {
    if (!finalCanvas) return;

    // Convert the final canvas to a data URL
    const dataURL = finalCanvas.toDataURL('image/png');

    // Create a temporary link to trigger download
    const link = document.createElement('a');
    link.href = dataURL;
    link.download = 'processed_image.png';
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  });

  /**
   * Utility function to append a canvas with a label for debugging
   * @param {HTMLCanvasElement} canvas 
   * @param {string} label 
   */
   
  function processCanvas(){

    const sourceImage = document.getElementById('dd6f9f50-780e-11ef-b763-11ebd40cd3a6');
  // Step 1: Get container and image dimensions
    const container = document.getElementsByClassName("object-render--canvas")[0];
    const containerWidth = container.clientWidth;
    const containerHeight = container.clientHeight;

    const imageRenderedWidth = sourceImage.width;
    const imageRenderedHeight = sourceImage.height;
    

    // Step 2: Get computed styles of the image
    const style = window.getComputedStyle(sourceImage);
    const transform = style.transform;

    // Calculate the center of the image
    const centerX = imageRenderedWidth / 2;
    const centerY = imageRenderedHeight / 2;

    // If no transform is applied, set it to identity matrix
    const matrix = transform === 'none' ?
      new DOMMatrix() :
      new DOMMatrix(transform)
    
      // Apply the transform-origin
        .preMultiplySelf({ e: centerX, f: centerY })
        .multiply({ e: -centerX, f: -centerY })
    

    
    //this is the canvas where we revert transformations and crop image
    finalCanvas = document.createElement('canvas');
    finalCanvas.width = containerWidth;
    finalCanvas.height = containerHeight;
    finalCanvas.style.border="1px solid red";
    const ctx1 = finalCanvas.getContext('2d');


    ctx1.setTransform(matrix);
    // Draw the image transformed
    ctx1.drawImage(sourceImage, 0, 0, imageRenderedWidth, imageRenderedHeight);


    //above code draws the image, identical as in the div, after this line, we reset transformations and crop everything that is outside the canvas with white, and display the image in normal (non rotated, not css scaled) position.
    // Draw again, using the inverse transform
    ctx1.setTransform(matrix.inverse());
    // Replace the previous content by the new content
    ctx1.globalCompositeOperation = "copy";
    ctx1.drawImage(ctx1.canvas, 0, 0);

    // Fill with white below the current drawing
    ctx1.fillStyle = '#FFFFFF';
    ctx1.globalCompositeOperation = "destination-over"; // Draw below
    ctx1.resetTransform(); // No transform
    ctx1.fillRect(0, 0, finalCanvas.width, finalCanvas.height);
    
    
    
    

    // Append the canvas for debugging
    appendCanvas(finalCanvas, 'Result');
  }
  function appendCanvas(canvas, label) {
    const wrapper = document.createElement('div');
    const caption = document.createElement('p');
    caption.textContent = label;
    wrapper.appendChild(caption);
    wrapper.appendChild(canvas);
    document.body.appendChild(wrapper);
  }
});
</script>
</body>
</html>

javascript matrix html5-canvas scaling css-transforms
1个回答
0
投票

您需要将比例因子应用于变换矩阵及其逆矩阵:

new DOMMatrix(transform)
  // Apply the transform-origin
  .preMultiplySelf({ e: centerX, f: centerY })
  .multiply({ e: -centerX, f: -centerY })
  // Apply the scale factor
  // We want it to apply even on the CSS transform we just set,
  // so either we use it as the initial DOMMatrix,
  // or we pre-multiply with it last.
  .preMultiplySelf(scaleMatrix);

然后,当应用逆函数来拉回未变换的部分时,我们重新应用它,因为

inverse()
也会反转该缩放。

// Draw again, using the inverse transform
ctx1.setTransform(matrix.inverse().preMultiplySelf(scaleMatrix));

<!DOCTYPE html>
<html>
<head>
<style>
body {
  font-family: Arial, sans-serif;
  margin: 20px;
}

.object-render--canvas {
  width: 400px;
  /* You can change this to any size */
  height: 400px;
  /* You can change this to any size */
  border: 2px solid #000;
  position: relative;
  overflow: hidden;
  margin-bottom: 20px;
}

 #dd6f9f50-780e-11ef-b763-11ebd40cd3a6 {
      width: 326px; /* Scaled-down width */
      height: 290.3px; /* Scaled-down height */
      transform: translate(230px, -130px) rotate(145deg) scale(1);
      transform-origin: center center;
      position: absolute;
      top: 0;
      left: 0;
    }

#buttons {
  margin-bottom: 20px;
}

#tempCanvases {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

#tempCanvases canvas {
  border: 1px solid #ccc;
}
</style>

</head>
<body>
<h1>Dynamic Image Transformation and Cropping</h1>
<div class="object-render--canvas">
  <img id="dd6f9f50-780e-11ef-b763-11ebd40cd3a6" src="https://i.sstatic.net/oJs20ysA.png" alt="Source Image">
</div>
<div id="buttons">
  <button id="processButton">Process</button>
  <button id="downloadButton" disabled>Download</button>
</div>
<h2>Temporary Canvases (For Debugging)</h2>

<script type='text/javascript'>
// Wait for the DOM to load
document.addEventListener('DOMContentLoaded', () => {
  const processButton = document.getElementById('processButton');
  const downloadButton = document.getElementById('downloadButton');
  
  let finalCanvas = null;
  let finalImageCanvas=null;

  processButton.addEventListener('click', () => {
    // Clear previous temporary canvases
    downloadButton.disabled = true;

    processCanvas();

    // Enable the download button
    downloadButton.disabled = false;
  });

  downloadButton.addEventListener('click', () => {
    if (!finalCanvas) return;

    // Convert the final canvas to a data URL
    const dataURL = finalCanvas.toDataURL('image/png');

    // Create a temporary link to trigger download
    const link = document.createElement('a');
    link.href = dataURL;
    link.download = 'processed_image.png';
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  });

  /**
   * Utility function to append a canvas with a label for debugging
   * @param {HTMLCanvasElement} canvas 
   * @param {string} label 
   */
   
  function processCanvas(){

    const sourceImage = document.getElementById('dd6f9f50-780e-11ef-b763-11ebd40cd3a6');
  // Step 1: Get container and image dimensions
    const container = document.getElementsByClassName("object-render--canvas")[0];
    const containerWidth = container.clientWidth;
    const containerHeight = container.clientHeight;

    const imageRenderedWidth = sourceImage.width;
    const imageRenderedHeight = sourceImage.height;
    const scale = sourceImage.naturalWidth / imageRenderedWidth;
    const scaleMatrix = DOMMatrix.fromMatrix({ a: scale, d: scale });

    // Step 2: Get computed styles of the image
    const style = window.getComputedStyle(sourceImage);
    const transform = style.transform;

    // Calculate the center of the image
    const centerX = imageRenderedWidth / 2;
    const centerY = imageRenderedHeight / 2;

    // If no transform is applied, set it to identity matrix
    const matrix = transform === 'none' ?
      scaleMatrix :
      new DOMMatrix(transform)
        // Apply the transform-origin
        .preMultiplySelf({ e: centerX, f: centerY })
        .multiply({ e: -centerX, f: -centerY })
        // Apply scale factor
        .preMultiplySelf(scaleMatrix);
    
    //this is the canvas where we revert transformations and crop image
    finalCanvas = document.createElement('canvas');
    finalCanvas.width = containerWidth * scale;
    finalCanvas.height = containerHeight * scale;
    finalCanvas.style.border="1px solid red";
    const ctx1 = finalCanvas.getContext('2d');


    ctx1.setTransform(matrix);
    // Draw the image transformed
    ctx1.drawImage(sourceImage, 0, 0, imageRenderedWidth, imageRenderedHeight);


    //above code draws the image, identical as in the div, after this line, we reset transformations and crop everything that is outside the canvas with white, and display the image in normal (non rotated, not css scaled) position.
    // Draw again, using the inverse transform
    ctx1.setTransform(matrix.inverse().preMultiplySelf(scaleMatrix));
    // Replace the previous content by the new content
    ctx1.globalCompositeOperation = "copy";
    ctx1.drawImage(ctx1.canvas, 0, 0);

    // Fill with white below the current drawing
    ctx1.fillStyle = '#FFFFFF';
    ctx1.globalCompositeOperation = "destination-over"; // Draw below
    ctx1.resetTransform(); // No transform
    ctx1.fillRect(0, 0, finalCanvas.width, finalCanvas.height);

    // Append the canvas for debugging
    appendCanvas(finalCanvas, 'Result');
  }
  function appendCanvas(canvas, label) {
    const wrapper = document.createElement('div');
    const caption = document.createElement('p');
    caption.textContent = label;
    wrapper.appendChild(caption);
    wrapper.appendChild(canvas);
    document.body.appendChild(wrapper);
  }
});
</script>
</body>
</html>

© www.soinside.com 2019 - 2024. All rights reserved.