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
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.