Search code examples
javascriptdjangodjango-formsjavascript-objectsdom-events

How to resize image in a Django Imagefield before submitting it using Javascript


Intro: I have a post in which users can upload up to 8 images. My deployment method does not allow my total upload (total of all images to be more than 10mb). So I cannot use Pillow or other packages which reduce image size after upload. I was thinking If I use Javascript I can reduce the image size before I even submit the form. That way when I hit submit the images are already reduced and the total of all images is less than 9mb (just to be on the safe side)

The code is borrowed from :

Use https://github.com/josefrichter/resize/blob/master/public/preprocess.js

I am not sure how to use them. below is my form template

This is just for my main post_image I still have to figure out how to reduce the size of my formset images

{% extends 'posts/post_base.html' %}
{% load bootstrap3 %}
{% load staticfiles %}    
{% block postcontent %}

<h2> Add a new Post</h2>
<form action="" method="post" enctype="multipart/form-data" id="form">
    {% csrf_token %}
    {% bootstrap_form form %}
    <img id="preview" src="" width="100" />
    {{formset.management_form}}
    {% for f in formset %}
        <div style="border-style: inset; padding:20px;">
          <p class="text-warning">Extra Image {{forloop.counter}}</p>
          {% bootstrap_form f %}
          <img src="" width="60" id="preview-extra{{forloop.counter}}"/>
        </div>
    {% endfor %}

    <br/><br/><input type="submit" class="btn btn-primary" value="Post"/>

</form>


<script >

var fileinput = document.getElementById('fileinput');

var max_width = 500;
var max_height = 500;

var preview = document.getElementById('preview');

var form = document.getElementById('form');

function processfile(file) {

    if( !( /image/i ).test( file.type ) )
        {
            alert( "File "+ file.name +" is not an image." );
            return false;
        }

    // read the files
    var reader = new FileReader();
    reader.readAsArrayBuffer(file);

    reader.onload = function (event) {
      // blob stuff
      var blob = new Blob([event.target.result]); // create blob...
      window.URL = window.URL || window.webkitURL;
      var blobURL = window.URL.createObjectURL(blob); // and get it is URL

      // helper Image object
      var image = new Image();
      image.src = blobURL;
      //preview.appendChild(image); // preview commented out, I am using the canvas instead
      image.onload = function() {
        // have to wait till it is loaded
        var resized = resizeMe(image); // send it to canvas
        var newinput = document.createElement("input");
        newinput.type = 'hidden';
        newinput.name = 'images[]';
        newinput.value = resized; // put result from canvas into new hidden input
        form.appendChild(newinput);
      }
    };
}

function readfiles(files) {

    // remove the existing canvases and hidden inputs if user re-selects new pics
    var existinginputs = document.getElementsByName('images[]');
    var existingcanvases = document.getElementsByTagName('canvas');
    // it is a live list so removing the first element each time DOMNode.prototype.remove = function() {this.parentNode.removeChild(this);}
    while (existinginputs.length > 0) {
      form.removeChild(existinginputs[0]);
      preview.removeChild(existingcanvases[0]);
    }

    for (var i = 0; i < files.length; i++) {
      processfile(files[i]); // process each file at once
    }
    fileinput.value = ""; //remove the original files from fileinput
    // TODO remove the previous hidden inputs if user selects other files
}

// this is where it starts. event triggered when user selects files
fileinput.onchange = function(){
  if ( !( window.File && window.FileReader && window.FileList && window.Blob ) ) {
    alert('The File APIs are not fully supported in this browser.');
    return false;
    }
  readfiles(fileinput.files);
};

// === RESIZE ====

function resizeMe(img) {

  var canvas = document.createElement('canvas');

  var width = img.width;
  var height = img.height;

  // calculate the width and height, constraining the proportions
  if (width > height) {
    if (width > max_width) {
      //height *= max_width / width;
      height = Math.round(height *= max_width / width);
      width = max_width;
    }
  } else {
    if (height > max_height) {
      //width *= max_height / height;
      width = Math.round(width *= max_height / height);
      height = max_height;
    }
  }

  // resize the canvas and draw the image data into it
  canvas.width = width;
  canvas.height = height;
  var ctx = canvas.getContext("2d");
  ctx.drawImage(img, 0, 0, width, height);

  preview.appendChild(canvas); // do the actual resized preview

  return canvas.toDataURL("image/jpeg",0.7); // get the data from canvas as 70% JPG (can be also PNG, etc.)

}
</script>
{% endblock %}

I wanted image size to be reduced to 400kb. if the user uploads less than that then no resize needed

On trying your solution getting the below error

enter image description here

enter image description here


Solution

  • below parts of codes helping you to resolve this problem:

    views.py :

    import re
    import io
    import base64
    
    from django.core.files import File
    from django.shortcuts import render
    
    from .forms import StoreImageForm
    
    
    def upload_canvas(request):
        form = StoreImageForm()
        if request.method == 'POST':
            image_base64 = request.POST.get('image_base64', '')
            res = re.match(r'^([^,]*),(.*)$', image_base64)
            if res:
                ext = re.match(r'^data:image/(.+);base64$', res.groups()[0]).groups()[0]
                image = base64.b64decode(res.groups()[-1])
                buf = io.BytesIO(image)
                form = StoreImageForm(files={'upload_file': File(buf, name=f'name.{ext}')})
                try:
                    form.is_valid()
                except Exception as err:
                    return render(request, 'form.html', {'message': err, 'form': form})
                instance = form.save()
                return render(request, 'form.html', {'message': 'Image Uploaded Successfuly', 'form': form})
            return render(request, 'form.html', {'message': 'Image Upload Failed', 'form': form})
        return render(request, 'form.html', {'message': 'Upload Image...', 'form': form})
    

    form.html :

    <html>
        <head>
            <title>Upload Canvas</title>
            <script lang="javascript">
                function resize_image(event) {
                    var canvas = document.getElementById("my_canvas");
                    var ctx = canvas.getContext("2d");
                    var reader = new FileReader();
                    var img = new Image();
                    var type = '';
                    var ratio = 1;
    
                    img.onerror = function(e) {
                      console.log("Not ok", e);
                    }
    
                    img.onload = function (img_onload_event) {
                        canvas.height = canvas.width * (img.height / img.width);
    
                        // step 1 - resize to 50%
                        var oc = document.createElement('canvas'),
                            octx = oc.getContext('2d');
    
                        oc.width = img.width * ratio;
                        oc.height = img.height * ratio;
                        octx.drawImage(img, 0, 0, oc.width, oc.height);
    
                        // step 2
                        octx.drawImage(oc, 0, 0, oc.width, oc.height);
    
                        // step 3, resize to final size
                        ctx.drawImage(oc, 0, 0, oc.width, oc.height, 0, 0, canvas.width, canvas.height);
    
                        var dataURL = oc.toDataURL(type, ratio)
                        // var blob = dataURItoBlob(dataURL)
    
                        b64 = dataURL;
                        padding = (b64.charAt(b64.length - 2) === '=') ? 2 : ((b64.charAt(b64.length - 1) === '=') ? 1 : 0);
                        fileSize = (b64.length * 0.75 - padding) / 1024;
                        if(fileSize > 500) {
                            img.src = b64;
                            return;
                        }
    
                        var my_image_base64 = document.getElementById('my_image_base64')
                        my_image_base64.setAttribute("value", dataURL)
                    }
    
                    reader.onload = function (e) {
                        b64 = reader.result;
                        padding = (b64.charAt(b64.length - 2) === '=') ? 2 : ((b64.charAt(b64.length - 1) === '=') ? 1 : 0);
                        fileSize = (b64.length * 0.75 - padding) / 1024;
                        if(fileSize > 500){
                            ratio = 0.8
                        }
    
                        img.src = e.target.result;
                    }
                    type = event.target.files[0].type || 'image/jpeg';
                    reader.readAsDataURL(event.target.files[0]);
                }
    
    
                window.onload = function(){
                    document.getElementById('my_image').addEventListener('change', resize_image, false);
                }
            </script>
        </head>
    
        <body>
            <h2>{{ message }}</h2>
            <form method="post">
                {% csrf_token %}
                <input  type="file"  id="my_image" />
                <input  type="hidden" id="my_image_base64" name="image_base64" />
                <button id="upload_button"> Upload </button>
            </form>
            <br/>
            <canvas id="my_canvas" width="500" />
        </body>
    </html>
    

    forms.py :

    from django import forms
    
    from .models import UploadModel
    
    
    class StoreImageForm(forms.ModelForm):
        class Meta:
            model = UploadModel
            fields = ['upload_file']
    

    models.py :

    from django.db import models
    
    
    class UploadModel(models.Model):
        upload_file = models.ImageField()
    

    See my image-minimizer-uploader project in github.