Reduce image size /compression before uploading using editor

Reduce image size /compression before uploading using editor

schwaluckschwaluck Posts: 103Questions: 27Answers: 1
edited August 2021 in General

Hi all,

I have the following issue:
I worked out a small js-code, which basically take the image and resizes it.
This code works perfectly fine if I am using it with a normal HTML form.

I now wanted to apply it to the editor instances as well in order to decrease the size of uploaded images dramatically.

I used the pre-upload event for this in my js-code. As I logged everything to the console, I can see that the image compression is working properly.

However, I am not able to override the given image file by the user with my compressed version.

This what I have so far:


/***** DEFINE PARAMETER *****/ const MAX_WIDTH = 1000; const MAX_HEIGHT = 1000; const MIME_TYPE = "image/jpeg"; const QUALITY = 0.8; //CALCULATE SIZE FUNCTION function calculateSize(img, maxWidth, maxHeight) { let width = img.width; let height = img.height; // calculate the width and height, constraining the proportions if (width > height) { if (width > maxWidth) { height = Math.round((height * maxWidth) / width); width = maxWidth; } } else { if (height > maxHeight) { width = Math.round((width * maxHeight) / height); height = maxHeight; } } return [width, height]; } editor.on( 'preUpload', function ( e, fieldName, file, ajaxData ) { console.log(file); var name = file.name; const blobURL = URL.createObjectURL(file); const img = new Image(); img.src = blobURL; img.onerror = function () { URL.revokeObjectURL(this.src); // Handle the failure properly console.log("Cannot load image"); }; img.onload = function () { URL.revokeObjectURL(this.src); const [newWidth, newHeight] = calculateSize(img, MAX_WIDTH, MAX_HEIGHT); const canvas = document.createElement("canvas"); canvas.width = newWidth; canvas.height = newHeight; const ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0, newWidth, newHeight); canvas.style = "display: none"; canvas.toBlob( (blob) => { // Handle the compressed image. es. upload or save in local state let file_small = new File([blob], name,{type:"image/jpeg", lastModified:new Date().getTime()}); console.log(file_small); let container = new DataTransfer(); container.items.add(file_small); file = container.files[0]; }, MIME_TYPE, QUALITY ); }; } );

Does anyone have an idea on how to achieve this / or if this is even possible here?

Thanks a lot,
Daniel

This question has an accepted answers - jump to answer

Answers

  • schwaluckschwaluck Posts: 103Questions: 27Answers: 1

    Follow up information:

    So I implemented some logs and discovered the following:
    The img.onload function is executed the last. So if I do a

    console.log(file);
    

    below the img.onload function, the original file is returned. However, the right file is returned within the img.onload function.
    IT seems like the img.onload is executed "too late".

    Does anyone have a hint?

    Thanks!

  • allanallan Posts: 63,679Questions: 1Answers: 10,498 Site admin

    Hi,

    What you need to do is, rather than changing the file parameter that is passed in (which just gives you access to the file - it doesn't actually get sent to the server), is modify the data inside ajaxData with the new value for the file. ajaxData.get('upload') will get you the file, so it might be as simple as:

    ajaxData.set('upload', container.files[0]);
    

    I haven't tried that I'm afraid, but following it though, that looks right...!

    Allan

  • schwaluckschwaluck Posts: 103Questions: 27Answers: 1

    Hi Allan,

    thanks for the fast reply.
    Yeah, I missed that one as well.

    However, the last problem I am trying to solve is that the img.onload function inside the preUpload function is actually called after the upload begins.

    So that's why it always uploads the "original" image.

    Any idea on how to solve that?

    Thanks a lot already!

  • allanallan Posts: 63,679Questions: 1Answers: 10,498 Site admin

    Ah! Unfortunately no, at the moment there isn't a way to do that as the onload is async, while Editor is expecting the preUpload event handler to be asynchronous. I'll have a think about what we can do there - we do allow a Promise to be returned from other pre event handlers, so that might work nicely here as well.

    Allan

  • schwaluckschwaluck Posts: 103Questions: 27Answers: 1

    Ah, got you.
    Yeah, that was I was thinking about as well.

    Which other events could come in handy besides pre Upload?
    Because I would then need to use the preUpload to resize the image,but would then need to call another event which then fires the upload.

    Thanks a lot already and have a nice weekend.

  • allanallan Posts: 63,679Questions: 1Answers: 10,498 Site admin

    The main event this is useful for is preSubmit - but that isn't going to help in your case here. No question for this - if you want to do resizing of images client-side it is going to need a modification to Editor to support async actions in preUpload. I'll look into this today and let you know.

    Allan

  • schwaluckschwaluck Posts: 103Questions: 27Answers: 1

    Thanks a lot Allan!
    If I can help with anything, let me know.

  • allanallan Posts: 63,679Questions: 1Answers: 10,498 Site admin
    Answer ✓

    Hi,

    I've just committed a change into Editor which means things like this can now be done:

        editor.on('preUpload', function (e, name, file, data) {
            var p = new Promise((resolve, reject) => {
                setTimeout(() => {
                    resolve();
                }, 5000);
            });
    
            return p;
        });
    

    I've just used setTimeout there since it is a short and handy way of showing the Promise in action, but basically it means you can use an async action in the event handler now.

    We'll be tagging up Editor 2.0.5 later this week, but I can send you a preview if you like? Is your e-mail address using for the forum the best one to get you on?

    Allan

  • schwaluckschwaluck Posts: 103Questions: 27Answers: 1

    Thanks a lot Allan!
    That sounds awesome and will definitely help.

    Yeah, that would be perfect. The E-Mail is the correct one.
    Thanks a lot.
    I'll report back here when I got a working solution.

    Thanks!

  • schwaluckschwaluck Posts: 103Questions: 27Answers: 1

    Since we installed Editor 2.0.5 in our system, we were able to get client-side image compression working before uploading the files.
    Thanks a lot, Allan for doing the needed changes to Editor.

    I'll just share our solution here, in case it might be helpful for others.

    1. We used these parameters and function to resize the image and define the compression-rate:
    /*****
    Defining parameters (Quality = compression rate)
    *****/      
        const MAX_WIDTH = 1000;
        const MAX_HEIGHT = 1000;
        const MIME_TYPE = "image/jpeg";
        const QUALITY = 0.75;
            
    /*****
    Function to do the resizing
    *****/
           function calculateSize(img, maxWidth, maxHeight) {
              let width = img.width;
              let height = img.height;
    
              // calculate the width and height, constraining the proportions
              if (width > height) {
                if (width > maxWidth) {
                  height = Math.round((height * maxWidth) / width);
                  width = maxWidth;
                }
              } else {
                if (height > maxHeight) {
                  width = Math.round((width * maxHeight) / height);
                  height = maxHeight;
                }
              }
              return [width, height];
        }
    

    We placed those both outside the editor.on(preUpload) function.
    Now the inside of the pre-upload function:

    editor.on( 'preUpload', function ( e, fieldName, file, ajaxData ) {
            console.log(file);
        
            var p = new Promise((resolve, reject) => {
                var name = file.name;   
                var x = ''; 
                const blobURL =  URL.createObjectURL(file);
                const img =  new Image();
                img.src =  blobURL; 
                
                img.onload = function () {
                    URL.revokeObjectURL(this.src);
                    const [newWidth, newHeight] = calculateSize(img, MAX_WIDTH, MAX_HEIGHT);
                    const canvas = document.createElement("canvas");
                    canvas.width = newWidth;
                    canvas.height = newHeight;
                    const ctx = canvas.getContext("2d");
                    ctx.drawImage(img, 0, 0, newWidth, newHeight);
                    canvas.style = "display: none";
                        canvas.toBlob(
                          (blob) => {
                                // Handle the compressed images upload or save
                                let file_small = new File([blob], name,{type:"image/jpeg", lastModified:new Date().getTime()});
                                let container = new DataTransfer();
                                container.items.add(file_small);
                                x = container.files[0];
                                ajaxData.set('upload', container.files[0]);
                                resolve();
                            },
                            MIME_TYPE,
                            QUALITY
                        );                  
                };              
            });
         
            return p;   
            
        } );
    

    It might not be the most elegant way to resize the images client-side before uploading, but it works indeed quite nice.
    It's also nice if your user are mainly accessing Datatables and Editor from mobiles, since you can save a lot of mobile data.

    Hope that helps someone and thanks again @Allan.

    Kind regards,
    schwaluck

  • colincolin Posts: 15,240Questions: 1Answers: 2,599

    Excellent, thanks for posting,

    Colin

Sign In or Register to comment.