0

Good day to all users!
I have a problem that my screenshots rendered on canvas are not saved correctly.
Displaying screenshot on canvas using js

function retrieveImageFromClipboardAsBlob(pasteEvent, callback){
    if(pasteEvent.clipboardData == false){
        if(typeof(callback) == "function"){
            callback(undefined);
        }
    };
    var items = pasteEvent.clipboardData.items;
    if(items == undefined){
        if(typeof(callback) == "function"){
            callback(undefined);
        }
    };
    for (var i = 0; i < items.length; i++) {
        if (items[i].type.indexOf("image") == -1) continue;
        var blob = items[i].getAsFile();

        if(typeof(callback) == "function"){
            callback(blob);
        }
    }
}

window.addEventListener("paste", function(e){
    retrieveImageFromClipboardAsBlob(e, function(imageBlob){
        if(imageBlob){
            var canvas = document.getElementById("mycanvas");
            var ctx = canvas.getContext('2d');
            var img = new Image();
            img.onload = function(){
                canvas.width = this.width;
                canvas.height = this.height;
                ctx.drawImage(img, 0, 0);
            };
            var URLObj = window.URL || window.webkitURL;
            img.src = URLObj.createObjectURL(imageBlob);
        }
    });

    var cnv = document.getElementById("mycanvas");
    var sendCanvas = function (cnv) {
        var imgs = cnv.toDataURL('image/png').replace('data:image/png;base64,','');
        var sender = new XMLHttpRequest();
        sender.open('POST', '/temp.php', true);
        sender.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
        sender.onreadystatechange = function () {
            if (sender.readyState == 4) {}
        };
        sender.send('imgs='+imgs);
    };
    sendCanvas(cnv);
}, false);

First, the screenshot is drawn on canvas and then sent to the php handler.
My html form has:

<canvas id="mycanvas">
<?php
    $imgs = str_replace(' ', '+', $_POST['imgs']);
    $imgs = base64_decode($imgs);
    $shootname = "screenshot".rand().".png";
    $screendir = "/mnt/ElmaFiles/".$shootname;
    file_put_contents($screendir, $imgs);
    $screennames .= $shootname.",";
?>
<p><input type="submit" name="submit1" class="btn success"></p>
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"</script>
<script src="/script11.js"></script>

In my understanding, files should be saved to the specified folder when I press ctrl + v. But it doesn't really work like that.
For example, I transfer 3 screenshots, but 5 screenshots are saved in my folder:

directory listing showing screenshots

Please help me understand where is my mistake?
Thank you very much in advance!
I forgot to add: when 5 screenshots were saved, only the third and fourth could be opened from them, and the rest wrote an error like "incorrect format"

1lenium
  • 13
  • 4
  • Note that you don't need parenthesis in `typeof(callback)` and it can be changed to `typeof callback`. – luek baja Mar 26 '21 at 11:45
  • @luekbaja Thanks for the advice, I recently started learning web languages. But it didn't solve my problem :( – 1lenium Mar 26 '21 at 11:57
  • What does `temp.php` do? Is that supposed to save the files? Can you add the contents of that or are you happy that works OK? – Professor Abronsius Mar 26 '21 at 12:02
  • @ProfessorAbronsius Sorry. Didn't notice how wrong I was. My html form is temp.php – 1lenium Mar 26 '21 at 12:05
  • OK - so you send the AJAX request to the same page? Is that the piece of code shown - beginning with `$imgs = str_replace(' ', '+', $_POST['imgs']);`?? – Professor Abronsius Mar 26 '21 at 12:07
  • @ProfessorAbronsius Yes, I will post an ajax request to temp.php. I only have 2 files: temp.php and script11.js – 1lenium Mar 26 '21 at 12:11
  • The PHP code you have shown does not appear to have the part that writes the files, wrapped in any check whether any image data was POSTed in the first place. So when you make that first GET request to _display_ your form in the first place, `$_POST['imgs']` will surely not be set. And yet you create a file anyway … – CBroe Mar 26 '21 at 12:18
  • @CBroe but what about `file_put_contents($screendir, $imgs);`, as far as I understand it is this function that saves my screenshot – 1lenium Mar 26 '21 at 12:24
  • Yes, and that is the _problem_, because you are executing this code before any screenshot was even posted. You make an initial request to this script via GET, right - to get your form loaded and displayed in the browser? Well where would `$_POST['imgs']` come from then, in that moment? It does not exist, so all your following operations – str_replace, base64_decode, file_put_contents – work with an “empty” value, and when you write that to the disk, you get, guess what, files with a size of zero bytes, exactly as your screenshot showed. – CBroe Mar 26 '21 at 12:31
  • @CBroe Thanks! Now I figured it out. But how, then, to load the screenshot only at the moment when there is something on the canvas? – 1lenium Mar 26 '21 at 12:37
  • Check if `$_POST['imgs']` is actually set/not empty, before you proceed. https://stackoverflow.com/questions/4261133/notice-undefined-variable-notice-undefined-index-and-notice-undefined – CBroe Mar 26 '21 at 12:41
  • Unrelated to the error but why go through a canvas? You already receive the image as Blob from the clipboard, just send that to your server. – Kaiido Mar 26 '21 at 14:23
  • @Kaiido Thanks for the advice! Sorry, but I do not know of such methods. – 1lenium Mar 27 '21 at 03:56

1 Answers1

0

I did a little rewrite of the above which I hope will help solve some of the issues you were having.

The PHP that saves the image to disk should be explicitly targeted so that trying to call $_POST['imgs'] for a regular GET request will not cause issue. For sending the binary data to the server rather than XMLHttpRequest instead use fetch ~ a considerably more powerful api.

<?php

    error_reporting( E_ALL );
    ini_set( 'display_errors', 1 );
    
    if( $_SERVER['REQUEST_METHOD']=='POST' && !empty( $_POST['imgs'] ) ){
        ob_clean();
        
        $decoded=base64_decode( $_POST['imgs'] );
        
        #edit directory to suit. This is a sub-folder to where the script is currently running.
        $dir=sprintf('%s/images/tmp',__DIR__);
        
        
        
        $filename=sprintf('screenshot%s.png', rand() );
        $targetfile=sprintf('%s%s%s', realpath($dir), DIRECTORY_SEPARATOR, $filename );
        file_put_contents( $targetfile, $decoded );
        
        exit( $filename );
    }
?>
<!DOCTYPE html>
<html lang='en'>
    <head>
        <meta charset='utf-8' />
        <title></title>
        <script>
            document.addEventListener('DOMContentLoaded',()=>{
            
                function getBlob(evt,callback){
                    if( evt.clipboardData == false || evt.clipboardData.items=='undefined' ){
                        return false;
                    };
                    let items = evt.clipboardData.items;
                    for( let i=0; i < items.length; i++){
                        let item=items[i];
                        if( ~item.type.indexOf('image') )callback( item.getAsFile() );
                    }
                }

                window.addEventListener('paste', function(e){
                    let canvas = document.querySelector('canvas');
                    let ctxt = canvas.getContext('2d');
                    
                    const callback=function( blob ){
                        if( blob ){
                            let img = new Image();
                                img.onload = function(){
                                    canvas.width=this.width;
                                    canvas.height=this.height;
                                    ctxt.drawImage( img, 0, 0 );
                                };
                                img.src = URL.createObjectURL(blob);
                            return true;
                        }
                        return false;
                    };
                    
                    getBlob( e, callback );
                    
                    let fd=new FormData();
                        fd.append('imgs', canvas.toDataURL('image/png').replace('data:image/png;base64,',''));
                        
                    fetch( location.href,{ method:'post', body:fd })
                        .then( r=>r.text() )
                        .then( text=>{
                            console.info('Screenshot %s saved',text )
                        })
                });
            });
        </script>
    </head>
    <body>
        <canvas></canvas>
    </body>
</html>
Professor Abronsius
  • 33,063
  • 5
  • 32
  • 46
  • Thanks! It really helped. But still, the first screenshot that goes into the canvas remains empty. The rest of the screenshots are created without problems. – 1lenium Mar 26 '21 at 12:51
  • Really? Seemed to work fine...ah~ found one little error. change `str_replace(' ', '+', $_POST['imgs'] )` for simply `$_POST['imgs']` – Professor Abronsius Mar 26 '21 at 13:46
  • Thanks for helping me! But this method still did not help me. The first screenshot is just a white canvas. :( – 1lenium Mar 27 '21 at 04:31
  • After I removed that portion ( mentioned above ) it works fine every time?! – Professor Abronsius Mar 27 '21 at 08:07
  • If I change the method that you described above, then the first screenshot remains just a white canvas, and the rest of the screenshots are cropped from the original. I decided not to change the method, but to try to figure out why the first screenshot is loaded as a white canvas. – 1lenium Mar 27 '21 at 08:35
  • hmmm - tried again and the 1st one is pure black - 2nd one is fine – Professor Abronsius Mar 27 '21 at 08:39
  • weird. do a screenshot (ie: Print Scrn in Windows ) and then paste. Change wallpaper and repeat... the paste event lags behind. – Professor Abronsius Mar 27 '21 at 08:42
  • I tried everything they said, but for some reason my first screenshot still remains an empty canvas. I also tried adding a regular clipboard image and the result is a blank canvas all the time. Could it be the encoding for the first screenshot? – 1lenium Mar 27 '21 at 08:53
  • try what I described above. Print scrn + Paste, then change background ( or make it so you know the difference between 1st and 2nd screenshots then repeat process-Print Scrn + Paste – Professor Abronsius Mar 27 '21 at 08:58
  • All my attempts look like this (I refreshed the page and tried again as you described above) https://ibb.co/82PNdTK – 1lenium Mar 27 '21 at 09:11