填充图像的白色、超出范围的部分 - 使用 CSS 转换裁剪图像

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

我有一个 DIV 容器和其中的图像。 图像具有 CSS 转换,例如:平移、旋转、缩放

用户可以在父 div 中拖动图像(并缩放、旋转)。有时,图像的某些部分可能位于父 div 之外。

我想创建一个新图像,其大小与原始图像相同(未应用旋转,未应用缩放),但 div 中不可见的图像部分(旋转/缩放)应该最终图像中是全白的。

让我展示一下我想要使用图像获得的内容: enter image description here

我尝试了各种方法,但未能得到我想要的结果。 这是我想出的代码(也在 ChatGPT 的帮助下)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Dynamic Image Transformation and Cropping</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
        }
        .container {
            width: 300px; /* You can change this to any size */
            height: 300px; /* You can change this to any size */
            border: 2px solid #000;
            position: relative;
            overflow: hidden;
            margin-bottom: 20px;
        }
        .container img {
            width: 150px; /* You can change this to any size */
            height: 150px; /* You can change this to any size */
            /* Apply CSS transformations */
            transform: translate(-30px, -30px) rotate(45deg) scale(1.2);
            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="container">
        <img id="sourceImage" src="https://sodevrom.net/your-image.jpg" 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>
    <div id="tempCanvases"></div>

    <script>
        // Wait for the DOM to load
        document.addEventListener('DOMContentLoaded', () => {
            const processButton = document.getElementById('processButton');
            const downloadButton = document.getElementById('downloadButton');
            const tempCanvasesDiv = document.getElementById('tempCanvases');
            const sourceImage = document.getElementById('sourceImage');
            let finalCanvas = null; // To store the final processed canvas

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

                // Step 1: Get container and image dimensions
                const container = sourceImage.parentElement;
                const containerWidth = container.clientWidth;
                const containerHeight = container.clientHeight;

                const imageNaturalWidth = sourceImage.naturalWidth;
                const imageNaturalHeight = sourceImage.naturalHeight;

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

                console.log('Container Dimensions:', containerWidth, containerHeight);
                console.log('Image Natural Dimensions:', imageNaturalWidth, imageNaturalHeight);
                console.log('Image Rendered Dimensions:', imageRenderedWidth, imageRenderedHeight);

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

                // If no transform is applied, set it to identity matrix
                const matrix = transform === 'none' ? new DOMMatrix() : new DOMMatrix(transform);

                // Extract transformation components
                const scaleX = matrix.a;
                const scaleY = matrix.d;
                const rotateRadians = Math.atan2(matrix.b, matrix.a);
                const rotateDegrees = rotateRadians * (180 / Math.PI);
                const translateX = matrix.e;
                const translateY = matrix.f;

                console.log('Extracted Transformations:');
                console.log('ScaleX:', scaleX);
                console.log('ScaleY:', scaleY);
                console.log('Rotate (degrees):', rotateDegrees);
                console.log('TranslateX:', translateX);
                console.log('TranslateY:', translateY);

                // Step 3: Create the first temporary canvas (container size) with transformations applied
                const tempCanvas1 = document.createElement('canvas');
                tempCanvas1.width = containerWidth;
                tempCanvas1.height = containerHeight;
                const ctx1 = tempCanvas1.getContext('2d');

                // Fill with white
                ctx1.fillStyle = '#FFFFFF';
                ctx1.fillRect(0, 0, tempCanvas1.width, tempCanvas1.height);

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

                // Apply transformations: translate, rotate, scale around the center
                ctx1.translate(translateX + centerX, translateY + centerY); // Move to the center
                ctx1.rotate(rotateRadians); // Apply rotation
                ctx1.scale(scaleX, scaleY); // Apply scaling
                ctx1.translate(-centerX, -centerY); // Move back

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

                // Append the first temporary canvas for debugging
                appendCanvas(tempCanvas1, 'Transformed Image');

                // Step 4: Create the second temporary canvas to revert transformations and crop
                const tempCanvas2 = document.createElement('canvas');
                tempCanvas2.width = containerWidth;
                tempCanvas2.height = containerHeight;
                const ctx2 = tempCanvas2.getContext('2d');

                // Fill with white
                ctx2.fillStyle = '#FFFFFF';
                ctx2.fillRect(0, 0, tempCanvas2.width, tempCanvas2.height);

                // To revert transformations, apply inverse transformations
                // Inverse scaling
                const invScaleX = 1 / scaleX;
                const invScaleY = 1 / scaleY;
                // Inverse rotation
                const invRotateRadians = -rotateRadians;

                ctx2.translate(-translateX - centerX, -translateY - centerY); // Reverse translation
                ctx2.translate(centerX, centerY); // Move to center
                ctx2.rotate(invRotateRadians); // Apply inverse rotation
                ctx2.scale(invScaleX, invScaleY); // Apply inverse scaling
                ctx2.translate(-centerX, -centerY); // Move back

                // Draw the image
                ctx2.drawImage(sourceImage, 0, 0, imageRenderedWidth, imageRenderedHeight);

                // Append the second temporary canvas for debugging
                appendCanvas(tempCanvas2, 'Reverted Transformations');

                // Step 5: Crop the image back to original size (natural image size)
                // Create final canvas based on the image's natural size
                finalCanvas = document.createElement('canvas');
                finalCanvas.width = imageNaturalWidth;
                finalCanvas.height = imageNaturalHeight;
                const ctxFinal = finalCanvas.getContext('2d');

                // Fill with white
                ctxFinal.fillStyle = '#FFFFFF';
                ctxFinal.fillRect(0, 0, finalCanvas.width, finalCanvas.height);

                // Calculate the scaling factor between rendered and natural size
                const scaleFactorX = imageNaturalWidth / imageRenderedWidth;
                const scaleFactorY = imageNaturalHeight / imageRenderedHeight;

                // Draw the reverted image onto the final canvas
                ctxFinal.drawImage(
                    tempCanvas2,
                    0, 0, containerWidth, containerHeight, // Source rectangle
                    0, 0, finalCanvas.width, finalCanvas.height // Destination rectangle
                );

                // Append the final canvas for debugging
                appendCanvas(finalCanvas, 'Final Cropped Image');

                // 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 appendCanvas(canvas, label) {
                const wrapper = document.createElement('div');
                const caption = document.createElement('p');
                caption.textContent = label;
                wrapper.appendChild(caption);
                wrapper.appendChild(canvas);
                tempCanvasesDiv.appendChild(wrapper);
            }
            });
        </script>
</body>
</html>

javascript css matrix transform
1个回答
0
投票

2D 上下文

setTransform
方法接受
DOMMatrix
对象作为参数。完全使用此类对象会使事情变得更容易,因为一旦获得它,您只需应用它的
inverse()
即可返回到初始变换。

棘手的部分可能是将

transform-origin
应用于此
DOMMatrix
对象。有多种路径可以到达那里,但基本上它意味着乘以第一个转换为原点的
DOMMatrix
,然后乘以实际矩阵,最后乘以最后一个矩阵以将原点重置为新的左上角。

然后您可以使用合成来使用单个画布来渲染每个步骤。

// Wait for the DOM to load
document.addEventListener('DOMContentLoaded', () => {
  const processButton = document.getElementById('processButton');
  const downloadButton = document.getElementById('downloadButton');
  const tempCanvasesDiv = document.getElementById('tempCanvases');
  const sourceImage = document.getElementById('sourceImage');
  let finalCanvas = null;

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

    // Step 1: Get container and image dimensions
    const container = sourceImage.parentElement;
    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 });


    finalCanvas = document.createElement('canvas');
    finalCanvas.width = containerWidth;
    finalCanvas.height = containerHeight;
    const ctx1 = finalCanvas.getContext('2d');


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

    // 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');

    // 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 appendCanvas(canvas, label) {
    const wrapper = document.createElement('div');
    const caption = document.createElement('p');
    caption.textContent = label;
    wrapper.appendChild(caption);
    wrapper.appendChild(canvas);
    tempCanvasesDiv.appendChild(wrapper);
  }
});
body {
  font-family: Arial, sans-serif;
  margin: 20px;
}

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

.container img {
  width: 150px;
  /* You can change this to any size */
  height: 150px;
  /* You can change this to any size */
  /* Apply CSS transformations */
  transform: translate(-30px, -30px) rotate(45deg) scale(1.2);
  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;
}
<h1>Dynamic Image Transformation and Cropping</h1>
<div class="container">
  <img id="sourceImage" src="https://sodevrom.net/your-image.jpg" 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>
<div id="tempCanvases"></div>

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