Reputation: 326
I am trying to save an image to the server that is being created with the following three.js script..
actualCode(THREE);
function actualCode(THREE) {
//Variables for rendering
const renderer = new THREE.WebGLRenderer({
antialias: true
});
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(30, 400.0 / 400, 1, 1000);
//Object variables
let texture;
let paintedMug;
//Preload image, then trigger rendering
const loader = new THREE.TextureLoader();
texture = loader.load("images/djmug2.jpg", function (_tex) {
// /*Debugging:*/ setTimeout(() => document.body.appendChild(texture.image), 100);
init();
//views 17.5=front | 355=side | 139.6=back
renderImageSolo(17.5);
});
function init() {
//Init scene and camera
camera.position.set(0, 1.3, 11);
camera.lookAt(scene.position);
renderer.setSize(400, 400);
//Set an ambient light
const light = new THREE.AmbientLight(0xffffff); // soft white light
scene.add(light);
//Draw white mug
const muggeom = new THREE.CylinderGeometry(1.5, 1.5, 3.5, 240, 1);
const mugmaterial = new THREE.MeshStandardMaterial({
color: "#fff",
});
const mug = new THREE.Mesh(muggeom, mugmaterial);
//Draw painting on mug with slightly larger radius
const paintgeom = new THREE.CylinderGeometry(1.5001, 1.5001, 3.3, 240, 1, true);
const paintmaterial = new THREE.MeshStandardMaterial({
map: texture,
});
const paint = new THREE.Mesh(paintgeom, paintmaterial);
//Define a group as mug + paint
paintedMug = new THREE.Group();
paintedMug.add(mug);
paintedMug.add(paint);
//Add group to scene
scene.add(paintedMug);
}
function renderImageSolo(angle) {
//Init just like main renderer / scene, will use same camera
const solo_renderer = new THREE.WebGLRenderer({
antialias: true
});
solo_renderer.setSize(renderer.domElement.width, renderer.domElement.height);
solo_renderer.domElement.style.marginTop = "0em"; //Space out canvas
solo_renderer.domElement.id = "canvas"; //give canvas id
document.body.appendChild(solo_renderer.domElement);
const solo_scene = new THREE.Scene();
//Set an ambient light
const light = new THREE.AmbientLight(0xffffff); // soft white light
solo_scene.add(light);
//Draw painting alone
const paintgeom = new THREE.CylinderGeometry(1.5, 1.5, 3.3, 240, 1, true);
const paintmaterial = new THREE.MeshStandardMaterial({
map: texture,
});
const paint = new THREE.Mesh(paintgeom, paintmaterial);
//Add paint to scene
solo_scene.add(paint);
//Rotate paint by angle
paint.rotation.y = angle
//Draw result with green screen bg
solo_scene.background = new THREE.Color(0x04F404);
//Draw result with trans bg (not working showing as black atm)
//solo_scene.background = new THREE.WebGLRenderer( { alpha: true } );
solo_renderer.render(solo_scene, camera);
saveit();
}
}
I then attempt to save the generated image with ajax as follows..
function saveit() {
const canvas = document.getElementById('canvas');
var photo = canvas.toDataURL('image/jpeg');
$.ajax({
method: 'POST',
url: 'photo_upload.php',
data: {
photo: photo
}
});
}
photo_upload.php contents..
$data = $_POST['photo'];
list($type, $data) = explode(';', $data);
list(, $data) = explode(',', $data);
$data = base64_decode($data);
mkdir($_SERVER['DOCUMENT_ROOT'] . "/photos");
file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/photos/".time().'.png', $data);
die;
but nothing gets saved and /photos on the server remains empty, also, as a seperate issue if i right click and "save image" the saved image is just a black square and not what is shown on the screen.
Upvotes: 2
Views: 1109
Reputation: 2619
Code for saving to PHP server re-written with modern javascript and tested:
fetch
import * as THREE from 'https://cdn.skypack.dev/three';
document.addEventListener("DOMContentLoaded", _e => {
//Create a div to receive results
const messDiv = document.createElement('div');
messDiv.classList.add('message');
document.body.appendChild(messDiv);
//Object variables
let texture;
//Preload image, then trigger rendering
const loader = new THREE.TextureLoader();
//Example with image hosted from Imgur:
messDiv.textContent = "Loading texture...";
texture = loader.load("https://i.imgur.com/TQZrUSP.jpeg", function(_tex) {
console.log("texture loaded");
// /*Debugging:*/ setTimeout(() => document.body.appendChild(texture.image), 100);
renderImageSolo(60);
});
function renderImageSolo(angle) {
messDiv.textContent = "Rendering 3D projection...";
//Init just main renderer / scene
const solo_renderer = new THREE.WebGLRenderer({
antialias: true,
preserveDrawingBuffer: true // <-- avoid plain black image
});
solo_renderer.setSize(400, 400);
document.body.appendChild(solo_renderer.domElement);
const solo_scene = new THREE.Scene();
//Init camera
const camera = new THREE.PerspectiveCamera(30, 400.0 / 400, 1, 1000);
camera.position.set(0, 1.3, 11);
camera.lookAt(solo_scene.position);
//Set an ambient light
const light = new THREE.AmbientLight(0xffffff); // soft white light
solo_scene.add(light);
//Draw painting alone
const paintgeom = new THREE.CylinderGeometry(1.5, 1.5, 3.3, 240, 1, true);
const paintmaterial = new THREE.MeshStandardMaterial({
//color: "#ddd",
map: texture,
});
const paint = new THREE.Mesh(paintgeom, paintmaterial);
//Add paint to scene
solo_scene.add(paint);
//Rotate paint by angle
paint.rotation.y = angle
//Draw result
solo_scene.background = new THREE.Color(0xffffff);
solo_renderer.render(solo_scene, camera);
//Save result
saveImage(solo_renderer.domElement, "photo.jpeg")
}
//Save canvas as image by posting it to special url on server
function saveImage(canvas, filename) {
messDiv.textContent = "Uploading result...";
canvas.toBlob(imgBlob => { //Specifying image/jpeg, otherwise you'd get a png
const fileform = new FormData();
fileform.append('filename', filename);
fileform.append('data', imgBlob);
fetch('./photo_upload.php', {
method: 'POST',
body: fileform,
})
.then(response => {
return response.json();
})
.then(data => {
if (data.error) { //Show server errors
messDiv.classList.add('error');
messDiv.textContent = data.error;
} else { //Show success message
messDiv.classList.add('success');
messDiv.textContent = data.message;
}
})
.catch(err => { //Handle js errors
console.log(err);
messDiv.classList.add('error');
messDiv.textContent = err.message;
});
}, 'image/jpeg'); //<- image type for canvas.toBlob (defaults to png)
}
});
<?php
//photo_upload.php
try {
header('Content-type: application/json');
//get file name
$filename = $_POST['filename'];
if (!$filename) {
die(json_encode([
'error' => "Could not read filename from request"
]));
}
//get image data
$img = $_FILES['data'];
if (!$filename) {
die(json_encode([
'error' => "No image data in request"
]));
}
//Create save dir
$savePath = $_SERVER['DOCUMENT_ROOT'] . "/photos/";
if (!file_exists($savePath)) {
if (!mkdir($savePath)) {
die(json_encode([
'error' => "Could not create dir $savePath"
]));
}
}
//Save file
$savePath .= $filename;
if (!move_uploaded_file($img['tmp_name'], $savePath)) {
echo json_encode([
'error' => "Could not write to $savePath"
]);
} else {
$bytes = filesize($savePath);
echo json_encode([
'message' => "Image uploaded and saved to $savePath ($bytes bytes)"
]);
}
} catch (Exception $err) {
echo json_encode([
'error' => $err->getMessage()
]);
}
body {
font-family: Arial, Helvetica, sans-serif;
}
.message {
text-align: center;
padding: 1em;
font-style: italic;
color: dimgray;
}
.message.success {
font-style: normal;
font-weight: bold;
color: forestgreen;
}
.message.error {
font-style: normal;
font-family: 'Courier New', Courier, monospace;
white-space: pre-wrap;
color: darkred;
}
2021-09-07 - I've edited the code to use js FormData
and PHP $_FILES
for better efficiency and readbility
Upvotes: 1
Reputation: 31046
You should be able to solve this issue by creating the renderer like so:
const solo_renderer = new THREE.WebGLRenderer({
antialias: true,
preserveDrawingBuffer: true // FIX
});
I also suggest you study existing resources that explain how to save a screenshot of your canvas with. Try it with:
Three.js: How can I make a 2D SnapShot of a Scene as a JPG Image?
Upvotes: 0