My code loads an image onto a plane. Then, it draws a rectangle on the image. It attempts to get the portion of the image underneath the rectangle. Finally, on button click, it downloads the extracted image. The problem is, the extracted image only contains part of the top of what’s under the rectangle. I’m not sure why my calculation is wrong. Any ideas? Or a working example?
let imgSrc = "image4.jpg";
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 2;
const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true, preserveDrawingBuffer: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
let planeGeom = new THREE.PlaneGeometry(4, 4);
let planeMaterial, plane;
let tex = new THREE.TextureLoader().load(imgSrc, (texture) => {
texture.needsUpdate = true;
const aspect = texture.image.height / texture.image.width;
planeMaterial = new THREE.MeshBasicMaterial({
color: 0xffffff,
map: texture
});
plane = new THREE.Mesh(planeGeom, planeMaterial);
plane.scale.set(1.0, aspect, 1.0);
scene.add(plane);
// Trigger the download
document.getElementById("download").addEventListener("click", function () {
adjustRectangleAndDownload(texture.image, plane);
});
});
// Rectangle geometry setup
let vertices = new Float32Array([
-1.0, -1.0, 0.0, // Vertex 1: bottom left
1.0, -1.0, 0.0, // Vertex 2: bottom right
1.0, 1.0, 0.0, // Vertex 3: top right
-1.0, 1.0, 0.0 // Vertex 4: top left
]);
let geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
// Use LineLoop to draw the rectangle
let material = new THREE.LineBasicMaterial({ color: 0x0000ff });
let rectangle = new THREE.LineLoop(geometry, material);
rectangle.renderOrder = 1;
// Add the rectangle to the scene
scene.add(rectangle);
// GET THE FOUR CORNERS OF THE RECTANGLE
const verticesA = rectangle.geometry.attributes.position.array;
// Function to adjust and download the image portion
function adjustRectangleAndDownload(image, plane) {
const imageWidth = image.width;
const imageHeight = image.height;
const planeScale = { x: plane.scale.x, y: plane.scale.y };
// Convert scene coordinates to image pixel coordinates
const pixelVertices = convertSceneCoordsToImageCoords(verticesA, imageWidth, imageHeight, planeGeom, planeScale);
// Extracting the bounding box of the rectangle in image coordinates
let minX = Math.min(...pixelVertices.filter((v, i) => i % 2 === 0));
let minY = Math.min(...pixelVertices.filter((v, i) => i % 2 !== 0));
let maxX = Math.max(...pixelVertices.filter((v, i) => i % 2 === 0));
let maxY = Math.max(...pixelVertices.filter((v, i) => i % 2 !== 0));
// Now use these points to crop the image and download it
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
// Set canvas size to the size of the rectangle
canvas.width = maxX - minX;
canvas.height = maxY - minY;
// Crop and draw the image portion on the canvas
ctx.drawImage(image, -minX, -minY, imageWidth, imageHeight);
canvas.toBlob(function (blob) {
let link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = 'cropped_image.png';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}, 'image/png');
}
// Convert Three.js coordinates to image pixel coordinates, taking plane scaling into account
function convertSceneCoordsToImageCoords(vertices, imageWidth, imageHeight, planeGeometry, planeScale) {
const scaleX = imageWidth / (planeGeometry.parameters.width * planeScale.x);
const scaleY = imageHeight / (planeGeometry.parameters.height * planeScale.y);
let pixelVertices = [];
for (let i = 0; i < vertices.length; i += 3) {
const x = (vertices[i] + planeGeometry.parameters.width / 2) * scaleX;
const y = (1 - (vertices[i + 1] + planeGeometry.parameters.height / 2) / planeGeometry.parameters.height) * scaleY;
pixelVertices.push(x, y);
}
return pixelVertices;
}
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
(function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
})();