Uploading a three.js canvas to the server without saving it as a file

Currently, I am in the process of saving an image to the server using a three.js script that I have created. The script looks like the following:

actualCode(THREE);

function actualCode(THREE) {
    //Rendering variables
    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 and then begin rendering
    const loader = new THREE.TextureLoader();
    texture = loader.load("images/djmug2.jpg", function (_tex) {
        init();

        renderImageSolo(17.5);
    });

    function init() {
        //Initialize scene and camera
        camera.position.set(0, 1.3, 11);
        camera.lookAt(scene.position);
        renderer.setSize(400, 400);
        
        //Set 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);

        //Group mug and paint together
        paintedMug = new THREE.Group();
        paintedMug.add(mug);
        paintedMug.add(paint);
        scene.add(paintedMug);
    }


    function renderImageSolo(angle) {
        const solo_renderer = new THREE.WebGLRenderer({
            antialias: true
        });
        solo_renderer.setSize(renderer.domElement.width, renderer.domElement.height);
        solo_renderer.domElement.style.marginTop = "0em";
        solo_renderer.domElement.id = "canvas";
        document.body.appendChild(solo_renderer.domElement);
        const solo_scene = new THREE.Scene();
        const light = new THREE.AmbientLight(0xffffff);
        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);
        solo_scene.add(paint);
        paint.rotation.y = angle
        solo_scene.background = new THREE.Color(0x04F404);

        solo_renderer.render(solo_scene, camera);
        saveit();
    }
}

Afterwards, I try to save the generated image using ajax as shown below:

function saveit() {
    const canvas = document.getElementById('canvas');
    var photo = canvas.toDataURL('image/jpeg');
    $.ajax({
        method: 'POST',
        url: 'photo_upload.php',
        data: {
            photo: photo
        }
    });
}

The contents of "photo_upload.php" are as follows:

$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;

Despite my efforts, nothing seems to be saved on the server under "/photos" directory. In addition, when attempting to right-click and "save image", the saved image is just a black square instead of what is displayed on the screen.

Answer №1

Updated JavaScript code for saving to PHP server with modern approach:

  1. Refactor the JavaScript code and implement the saving function using fetch
import * as THREE from 'https://cdn.skypack.dev/three';

document.addEventListener("DOMContentLoaded", _e => {

  //Create a div element to display messages
  const messageDiv = document.createElement('div');
  messageDiv.classList.add('message');
  document.body.appendChild(messageDiv);

  let texture;

  const loader = new THREE.TextureLoader();
  messageDiv.textContent = "Loading texture...";
  texture = loader.load("https://i.imgur.com/TQZrUSP.jpeg", function(_tex) {
    console.log("Texture loaded");
    renderImageSolo(60);
  });

  function renderImageSolo(angle) {
    messageDiv.textContent = "Rendering 3D projection...";
    
    const solo_renderer = new THREE.WebGLRenderer({
      antialias: true,
      preserveDrawingBuffer: true 
    });
    solo_renderer.setSize(400, 400);
    document.body.appendChild(solo_renderer.domElement);
    const solo_scene = new THREE.Scene();

    const camera = new THREE.PerspectiveCamera(30, 400.0 / 400, 1, 1000);
    camera.position.set(0, 1.3, 11);
    camera.lookAt(solo_scene.position);
    
    const light = new THREE.AmbientLight(0xffffff); 
    solo_scene.add(light);

    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);
    
    solo_scene.add(paint);
    
    paint.rotation.y = angle
    
    solo_scene.background = new THREE.Color(0xffffff);
    solo_renderer.render(solo_scene, camera);

    saveImage(solo_renderer.domElement, "photo.jpeg")
  }

  function saveImage(canvas, filename) {
    messageDiv.textContent = "Uploading result...";

    canvas.toBlob(imgBlob => { 
      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) {
          messageDiv.classList.add('error');
          messageDiv.textContent = data.error;
        } else {
          messageDiv.classList.add('success');
          messageDiv.textContent = data.message;
        }
      })
      .catch(err => { 
        console.log(err);
        messageDiv.classList.add('error');
        messageDiv.textContent = err.message;
      });
    }, 'image/jpeg'); 
  }
});
  1. Create PHP script to handle saving on the server side
<?php

try {

  header('Content-type: application/json');

  $filename = $_POST['filename'];
  if (!$filename) {
    die(json_encode([
      'error' => "Could not read filename from request"
    ]));
  }
  
  $img = $_FILES['data'];
  if (!$img) {
    die(json_encode([
      'error' => "No image data in request"
    ]));
  }
  
  $savePath = $_SERVER['DOCUMENT_ROOT'] . "/photos/";
  if (!file_exists($savePath)) {
    if (!mkdir($savePath)) {
      die(json_encode([
        'error' => "Could not create dir $savePath"
      ]));
    }
  }
  
  $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()
  ]);
}
  1. Add CSS styles for better readability of messages
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 - Improved code using FormData in JavaScript and FILES in PHP for efficiency and clarity

Answer №2

To resolve this issue, consider initializing the renderer in the following manner:

const custom_renderer = new THREE.WebGLRenderer({
    antialias: true,
    preserveDrawingBuffer: true // CUSTOM FIX
});

I recommend exploring available resources that provide guidance on capturing a screenshot of your canvas. You can start by checking out:

Creating a 2D Snapshot of a Scene in Three.js

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The process of retrieving data from a dropdown list and populating checkboxes using Ajax/Jquery

I'm looking for a way to dynamically retrieve data in checkboxes based on a selected user from a dropdown list. The dropdown list contains users, and the checkboxes display pages data. Assuming we have a table called user with columns user_id and use ...

Tips for creating $http calls in AngularJS

Having some issues with my code, as I'm unsure of the correct placement for making an $http request to a local server. api.js var express = require('express'); var router = express.Router(); var mongoose = require('mongoose'); va ...

Changing styles with the use of bootstrap.css themes on the fly

I'm experimenting with toggling between a custom dark and light theme in my MVC application. On the _Layout.cshtml page, the default theme is loaded by the following code: <link id="theme" rel="stylesheet" href="~/lib/bootstrap/dist/css/Original. ...

Determining if an item is located within a nested scroll container

How can one detect if a specific element within a scrollable container is currently visible to the user's view (i.e. within the visible area of the scrolling parent)? Is there a universal method available that does not require iterating through all p ...

The responsive navigation bar yielded an unforeseen outcome

Looking to create a responsive navigation bar similar to the one shown here My code successfully shows and hides the navigation items, but it's not updating the burger icon No console errors or warnings are present This is my HTML: <nav> ...

I am experiencing difficulties with opening an email attachment after it was successfully sent from the SMTP server in MVC

When I send a file using Ajax in FormData to the controller, I then proceed to send an email with an attachment. The email is sent successfully, but when I attempt to view it in my email account, I encounter an error message preventing me from opening the ...

Connecting multiple TypeScript files to a single template file with Angular: A comprehensive guide

Imagine you are working with a typescript file similar to the one below: @Component({ selector: 'app-product-alerts', templateUrl: './product-alerts.component.html', styleUrls: ['./product-alerts.component.css'] }) expo ...

Utilize the v-for second argument in VueJS 2 to showcase the index and index+1

For a VueJS 2 project, I am tasked with enhancing the display of the first and second elements in an array by making them stand out from the rest. To achieve this, I am utilizing the v-for syntax to iterate over a child component within a parent component. ...

Combining various postponed JavaScript file imports in the HTML header into a single group

I've been facing an issue with my code structure, particularly with the duplication of header script imports in multiple places. Every time I need to add a new script, I find myself manually inserting <script type="text/javascript" src=&q ...

JQuery does not support manipulated content

I'm facing a frustrating issue. I am using Ajax to call a PHP file and then incorporating the response into my HTML content. This method allows me to have dynamic content without having to reload the page. The problem I'm encountering is that th ...

Creating variables in styled-components allows you to easily reuse values throughout

Can a variable be defined inside a styled-components component? The code snippet below, although not functioning properly, demonstrates my intention: const Example = styled.div` ${const length = calc(vw - props.someValue)} width: ${length}; &. ...

Create an interactive mechanism where one scrollbar dynamically adjusts another scrollbar and vice versa

Using the provided js-bin, how can I synchronize scrolling between the "left" div and the "right" div when using the mouse wheel? Currently, if you uncomment the code block, the scrolling behavior changes from scrolling by 100px per scroll to scrolling pi ...

JavaScript call to enlarge text upon click

I'm struggling to figure out why my font enlargement function isn't working as expected. Below is the code for my font enlargement function: <script type="text/javascript> function growText() { var text = document.getElementBy ...

What is the best way to manage error handling in various locations within an Angular stream?

Currently, I am working on ensuring that errors are handled properly in a stream where the id of a group is retrieved first and then used to obtain profile information. I want to easily identify whether the error is occurring during the retrieval of the g ...

React and Material-UI issue: Unable to remove component as reference from (...)

My React components are built using Material-UI: Everything is running smoothly MainView.js import React, { Component } from 'react'; import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; import { List, ListItem } from ...

What is the process for customizing a form in the "add event" feature of wdCalender?

I am looking to customize the form in wdCalendar to add events with fields like first name and last name instead of "what". I need to modify the quickadd function in jquery.calendar.js, which is triggered when a user clicks on the calendar to add an event. ...

Dropdown menu for selecting state in multiple countries built with React JS

In my project, I'm attempting to create a dropdown menu that lists multiple countries along with their associated states by mapping over an array. The sample data from the array is shown below: [ { "country" : "USA", ...

What could be causing the Gruntfile to throw an error?

Getting an unexpected error while trying to run grunt $ grunt Loading "Gruntfile.js" tasks...ERROR >> SyntaxError: Unexpected token : Warning: Task "default" not found. Use --force to continue. Execution terminated due to warnings. Here is my ...

Guide on creating an HTML5 rectangle for reuse using the Prototypal Pattern

I'm struggling to grasp Prototypal Inheritance through the use of the Prototypal pattern by creating a rectangle object and an instance of that rectangle. It seems like it should be straightforward, but I'm having trouble understanding why the Re ...

Utilize JavaScript to trigger a function depending on the selected options from dropdown menus

I've been working on a fun little project for a web page where users can select two items from a drop-down menu and then click a button to play a sound corresponding to their selections. However, I'm facing some challenges with getting my JavaScr ...