0

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

4
  • I don't think its possible to do that without help of Javascript. Commented Dec 1, 2018 at 20:12
  • yes, the relationship should be many to many Commented Dec 1, 2018 at 20:28
  • take a look at this answer stackoverflow.com/questions/19262141/…. You can use javascript canvas Commented Feb 24, 2019 at 10:20
  • @NathanDo will try to implement this in my code thanks Commented Feb 24, 2019 at 13:20

1 Answer 1

1
+50

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.

Sign up to request clarification or add additional context in comments.

9 Comments

Thanks Moh. Just a small doubt I want my final images to be around 400kb-500kb. If I do 50% It will do 50% of a 10 mb image which is too large and also 50% of a 100kb image which is way too small. I wanted something that will ignore images below 400kb and reduce bigger images to 400kb
@MarcoBianchi, The form.html part of my answer updated to resolve this problem.
Added a pycham error in above question. Should it be
Getting a error on (screenshot in question above)octx.drawImage(oc, 0, 0, oc.width, oc.height); Should this be octx.drawImage(**img**, 0, 0, oc.width, oc.height);
I am just adding the screenshots. I haven't made any changes to your code. Except changing var to const as var is getting obsolete. But even with declaring variables with var I see the same errors
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.