Search code examples
javascriptphpcanvasjcropinternal-server-error

Jcrop & Canvas: Size of cropped area is weird & 500 Internal Server Error


Introduction

Process flow

So, I'm making a web application, where users should be able to upload an image and crop it:

  1. The user selects a local image
  2. An object url is created from the image
  3. The object url is the source of an <img>
  4. Jcrop is applied to this <img>
  5. The selected crop area is drawn into a canvas
  6. A data url is created from this canvas
  7. This data url is written into a file which is stored on the server

Problems

  1. The size of the cropped area stored in the canvas is multiple times bigger than the one of the original image
  2. When I eventually try to write the decoded data url into a file I get the 500 Internal Server Error

Code

Note: Code is simplified

event_handlers.js

jQuery('#my_upload_input').change(function(my_event)
{
    my_load_image(my_event.currentTarget.files);
});

jQuery(document).keyup(function(my_event)
{
    if (my_event.which == 13)
    {
        my_crop_image();
    }
});

image_handling.js

var my_file_glob;
var my_image_glob;
var my_jcrop_api_glob;

function my_load_image(my_files)
{
    var my_file_glob = my_files[0];
    var my_image_glob = new Image();

    my_image_glob.onload = function()
    {
        my_show_image();
    };
    my_image_glob.src = URL.createObjectURL(my_file_glob);
}

function my_show_image()
{
    jQuery('#my_container').html('<img id="my_img" src="' + my_image_glob.src + '" />');

    jQuery('#my_img').Jcrop(
    {
        boxWidth: 1280,
        boxHeight: 720,
        trueSize: [my_image_glob.width, my_image_glob.height],
        setSelect: [0, 0, 1920, 1080],
        aspectRatio: 1920 / 1080,
        minSize: [1920, 1080],
        bgColor: '',
        allowSelect: false
    }, function()
    {
        my_jcrop_api_glob = this;
    });
}

function my_crop_image()
{
    if (typeof my_jcrop_api_glob !== 'undefined')
    {
        var my_selection = my_jcrop_api_glob.tellSelect();
        var my_canvas = document.createElement('canvas');
        var my_canvas_context = my_canvas.getContext('2d');

        my_canvas.width = my_selection.w;
        my_canvas.height = my_selection.h;

        my_canvas_context.drawImage(my_image_glob, my_selection.x, my_selection.y, my_selection.w, my_selection.h, 0, 0, my_selection.w, my_selection.h);

        my_upload_canvas(my_canvas);
    }
}

function my_upload_canvas(my_canvas)
{
    var my_canvas_url = my_canvas.toDataURL('image/png');

    jQuery.ajax(
    {
        type: 'POST',
        url: 'ajax_calls.php',
        data:
        {
            my_canvas_url: my_canvas_url
        },
        success: function(my_response)
        {
            alert(my_response);
            window.location.reload(true);
        }
    });
}

ajax_calls.js

if(isset($_POST['my_canvas_url']))
{
    $my_canvas_url = $_POST['my_canvas_url'];
    my_upload_canvas($my_canvas_url);
}

function my_upload_canvas($my_canvas_url)
{
    $my_canvas_data_enc = explode(',', $my_canvas_url)[1];
    $my_canvas_data_enc = str_replace(' ', '+', $my_canvas_data_enc);

    $my_canvas_data_dec = base64_decode($my_canvas_data_enc);

    file_put_contents(dirname(__FILE__) . '/menu.png', $my_canvas_data_dec);

    if($my_png_created !== false)
    {
        echo 'success';
    }
    else
    {
        echo 'failure';
    }
}

Additional

In the function my_upload_canvas(my_canvas), I've written these few lines to compare the size of the original image and the cropped area:

var head = 'data:image/png;base64,';
var imgfilesize = Math.round((my_canvas_url.length - head.length) * 3 / 4);
console.log('size orig: ' + Math.round(my_file_glob.size));
console.log('size crop: ' + Math.round(imgfilesize));
console.log('cropped area is ' + Math.round(imgfilesize / my_file_glob.size) + ' times bigger than the original image');

The outputs for three different images are the following:

JPEG (807 KB)

size orig: 826730
size crop: 2445081
cropped area is 3 times bigger than the original image

JPG (141 KB)

size orig: 144837
size crop: 1201146
cropped area is 8 times bigger than the original image

PNG (334 KB)

size orig: 342617
size crop: 53799
cropped area is 0 times bigger than the original image

Note that the sizes of the cropped areas of image #1 and #2 are bigger than the ones of the original images themselves, but not for image #3.


Update #1

The 500 Internal Server Error was caused by the $ in front of the function plugins_url()...silly me. But, unfortunately, saving the .png still won't work (I can't even create the file). Introduction and code updated.

Update #2

Uploading the image works now! I replaced
file_put_contents(plugins_url('/menu.png', __FILE__), $my_canvas_data_dec)
with
file_put_contents(dirname(__FILE__) . '/menu.png', $my_canvas_data_dec)
as apparently a URL won't work, but a full path will. Introduction and code updated.

Update #3

Mystery solved! As you can read here, the canvas contains no more information than the pixels of the image. Therefore the size of the data url depends on how the browser encodes these pixels and can result in being larger than the one of the image. Furthermore, a PNG will in most cases be much larger than a JPEG of the same canvas.


Solution

  • I figured it out myself. Please have a look at the updates of my post if you're interested in the answers.