Projet

Général

Profil

0001-misc-add-option-to-resize-images-before-upload-23152.patch

Frédéric Péters, 13 mai 2019 16:51

Télécharger (15,5 ko)

Voir les différences:

Subject: [PATCH] misc: add option to resize images before upload (#23152)

 wcs/fields.py                                 |  15 +-
 wcs/qommon/form.py                            |   1 +
 wcs/qommon/static/js/qommon.fileupload.js     | 278 +++++++++++++++++-
 .../templates/qommon/forms/widgets/file.html  |   4 +-
 4 files changed, 287 insertions(+), 11 deletions(-)
wcs/fields.py
858 858
    description = N_('File Upload')
859 859
    document_type = None
860 860
    max_file_size = None
861
    automatic_image_resize = False
861 862
    allow_portfolio_picking = False
862 863

  
863 864
    widget_class = FileWithPreviewWidget
864
    extra_attributes = ['file_type', 'max_file_size', 'allow_portfolio_picking']
865
    extra_attributes = [
866
            'file_type',
867
            'max_file_size',
868
            'allow_portfolio_picking',
869
            'automatic_image_resize',
870
            ]
865 871

  
866 872
    def __init__(self, *args, **kwargs):
867 873
        super(FileField, self).__init__(*args, **kwargs)
......
890 896
        form.add(FileSizeWidget, 'max_file_size', title=_('Max file size'),
891 897
                value=self.max_file_size,
892 898
                advanced=not(self.max_file_size))
899
        form.add(CheckboxWidget, 'automatic_image_resize',
900
                title=_('Automatically resize uploaded images'),
901
                value=self.automatic_image_resize,
902
                advanced=True)
893 903
        if portfolio.has_portfolio():
894 904
            form.add(CheckboxWidget, 'allow_portfolio_picking',
895 905
                    title=_('Allow user to pick a file from a portfolio'),
......
898 908

  
899 909
    def get_admin_attributes(self):
900 910
        return WidgetField.get_admin_attributes(self) + [
901
                'document_type', 'max_file_size', 'allow_portfolio_picking']
911
                'document_type', 'max_file_size', 'allow_portfolio_picking',
912
                'automatic_image_resize']
902 913

  
903 914
    @classmethod
904 915
    def convert_value_from_anything(cls, value):
wcs/qommon/form.py
655 655
        self.value = value
656 656
        self.readonly = kwargs.get('readonly')
657 657
        self.max_file_size = kwargs.pop('max_file_size', None)
658
        self.automatic_image_resize = kwargs.pop('automatic_image_resize', False)
658 659
        self.allow_portfolio_picking = has_portfolio() and kwargs.pop('allow_portfolio_picking', True)
659 660
        if self.max_file_size:
660 661
            self.max_file_size_bytes = FileSizeWidget.parse_file_size(self.max_file_size)
wcs/qommon/static/js/qommon.fileupload.js
1
var ExifRestorer = (function()
2
{
3
    // from http://www.perry.cz/files/ExifRestorer.js
4
    // based on MinifyJpeg
5
    // http://elicon.blog57.fc2.com/blog-entry-206.html
6

  
7
    var ExifRestorer = {};
8

  
9
    ExifRestorer.KEY_STR = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
10

  
11
    ExifRestorer.encode64 = function(input)
12
    {
13
        var output = "",
14
            chr1, chr2, chr3 = "",
15
            enc1, enc2, enc3, enc4 = "",
16
            i = 0;
17

  
18
        do {
19
            chr1 = input[i++];
20
            chr2 = input[i++];
21
            chr3 = input[i++];
22

  
23
            enc1 = chr1 >> 2;
24
            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
25
            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
26
            enc4 = chr3 & 63;
27

  
28
            if (isNaN(chr2)) {
29
               enc3 = enc4 = 64;
30
            } else if (isNaN(chr3)) {
31
               enc4 = 64;
32
            }
33

  
34
            output = output +
35
               this.KEY_STR.charAt(enc1) +
36
               this.KEY_STR.charAt(enc2) +
37
               this.KEY_STR.charAt(enc3) +
38
               this.KEY_STR.charAt(enc4);
39
            chr1 = chr2 = chr3 = "";
40
            enc1 = enc2 = enc3 = enc4 = "";
41
        } while (i < input.length);
42

  
43
        return output;
44
    };
45

  
46
    ExifRestorer.restore = function(origFileBase64, resizedFileBase64)
47
    {
48
        if (!origFileBase64.match("data:image/jpeg;base64,"))
49
        {
50
            return resizedFileBase64;
51
        }
52

  
53
        var rawImage = this.decode64(origFileBase64.replace("data:image/jpeg;base64,", ""));
54
        var segments = this.slice2Segments(rawImage);
55

  
56
        var image = this.exifManipulation(resizedFileBase64, segments);
57

  
58
        return this.encode64(image);
59
    };
60

  
61
    ExifRestorer.restore_as_blob = function(origFileBase64, resizedFileBase64) {
62
        var b64Data = ExifRestorer.restore(origFileBase64, resizedFileBase64);
63
        contentType = 'image/jpeg';
64
        sliceSize = 512;
65

  
66
        var byteCharacters = atob(b64Data);
67
        var byteArrays = [];
68

  
69
        for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
70
            var slice = byteCharacters.slice(offset, offset + sliceSize);
71
            var byteNumbers = new Array(slice.length);
72
            for (var i = 0; i < slice.length; i++) {
73
                byteNumbers[i] = slice.charCodeAt(i);
74
            }
75
            var byteArray = new Uint8Array(byteNumbers);
76
            byteArrays.push(byteArray);
77
        }
78
        return new Blob(byteArrays, {type: contentType});
79
    };
80

  
81
    ExifRestorer.exifManipulation = function(resizedFileBase64, segments)
82
    {
83
        var exifArray = this.getExifArray(segments),
84
            newImageArray = this.insertExif(resizedFileBase64, exifArray),
85
            aBuffer = new Uint8Array(newImageArray);
86

  
87
        return aBuffer;
88
    };
89

  
90
    ExifRestorer.getExifArray = function(segments)
91
    {
92
        var seg;
93
        for (var x = 0; x < segments.length; x++)
94
        {
95
            seg = segments[x];
96
            if (seg[0] == 255 & seg[1] == 225) //(ff e1)
97
            {
98
                return seg;
99
            }
100
        }
101
        return [];
102
    };
103

  
104
    ExifRestorer.insertExif = function(resizedFileBase64, exifArray)
105
    {
106
        var imageData = resizedFileBase64.replace("data:image/jpeg;base64,", ""),
107
            buf = this.decode64(imageData),
108
            separatePoint = buf.indexOf(255,3),
109
            mae = buf.slice(0, separatePoint),
110
            ato = buf.slice(separatePoint),
111
            array = mae;
112

  
113
        array = array.concat(exifArray);
114
        array = array.concat(ato);
115
        return array;
116
    };
117

  
118

  
119
    ExifRestorer.slice2Segments = function(rawImageArray)
120
    {
121
        var head = 0,
122
            segments = [];
123

  
124
        while (1)
125
        {
126
            if (rawImageArray[head] == 255 & rawImageArray[head + 1] == 218){break;}
127
            if (rawImageArray[head] == 255 & rawImageArray[head + 1] == 216)
128
            {
129
                head += 2;
130
            }
131
            else
132
            {
133
                var length = rawImageArray[head + 2] * 256 + rawImageArray[head + 3],
134
                    endPoint = head + length + 2,
135
                    seg = rawImageArray.slice(head, endPoint);
136
                segments.push(seg);
137
                head = endPoint;
138
            }
139
            if (head > rawImageArray.length){break;}
140
        }
141

  
142
        return segments;
143
    };
144

  
145
    ExifRestorer.decode64 = function(input)
146
    {
147
        var output = "",
148
            chr1, chr2, chr3 = "",
149
            enc1, enc2, enc3, enc4 = "",
150
            i = 0,
151
            buf = [];
152

  
153
        // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
154
        var base64test = /[^A-Za-z0-9\+\/\=]/g;
155
        if (base64test.exec(input)) {
156
            alert("There were invalid base64 characters in the input text.\n" +
157
                  "Valid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\n" +
158
                  "Expect errors in decoding.");
159
        }
160
        input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
161

  
162
        do {
163
            enc1 = this.KEY_STR.indexOf(input.charAt(i++));
164
            enc2 = this.KEY_STR.indexOf(input.charAt(i++));
165
            enc3 = this.KEY_STR.indexOf(input.charAt(i++));
166
            enc4 = this.KEY_STR.indexOf(input.charAt(i++));
167

  
168
            chr1 = (enc1 << 2) | (enc2 >> 4);
169
            chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
170
            chr3 = ((enc3 & 3) << 6) | enc4;
171

  
172
            buf.push(chr1);
173

  
174
            if (enc3 != 64) {
175
               buf.push(chr2);
176
            }
177
            if (enc4 != 64) {
178
               buf.push(chr3);
179
            }
180

  
181
            chr1 = chr2 = chr3 = "";
182
            enc1 = enc2 = enc3 = enc4 = "";
183

  
184
        } while (i < input.length);
185

  
186
        return buf;
187
    };
188

  
189
    return ExifRestorer;
190
})();
191

  
1 192
$.WcsFileUpload = {
2 193
    prepare: function() {
3 194
        var base_widget = $(this);
195
        var image_resize = $(this).find('.file-button').data('image-resize');
196
        if (typeof(FileReader) === "undefined") {
197
            image_resize = false;
198
        }
4 199
        if ($(base_widget).find('input[type=hidden]').val()) {
5 200
            $(base_widget).find('input[type=file]').hide();
6 201
            $(base_widget).find('.use-file-from-fargo').hide();
......
14 209
            pasteZone: base_widget,
15 210
            dataType: 'json',
16 211
            add: function (e, data) {
17
                $(base_widget).find('.fileprogress').removeClass('upload-error');
18
                $(base_widget).find('.fileprogress .bar').text(
19
                        $(base_widget).find('.fileprogress .bar').data('upload'));
20
                $(base_widget).find('.fileprogress .bar').css('width', '0%');
21
                $(base_widget).find('.fileprogress').show();
22
                $(base_widget).find('.fileinfo').hide();
23
                $(base_widget).parents('form').find('input[name=submit]').prop('disabled', true);
24
                var jqXHR = data.submit();
212
                if (image_resize && (
213
                        data.files[0].type == 'image/jpeg' ||
214
                        data.files[0].type == 'image/png')) {
215

  
216
                    $(base_widget).find('.fileprogress .bar').css('width', '100%');
217
                    $(base_widget).find('.fileprogress .bar').text(
218
                        $(base_widget).find('.fileprogress .bar').data('resize'));
219
                    $(base_widget).find('.fileprogress').show();
220

  
221
                    var reader = new FileReader();
222
                    reader.onload = function(e) {
223
                        var original_image_64 = e.target.result;
224
                        var img = document.createElement("img");
225
                        img.onload = function() {
226
                            var canvas = document.createElement("canvas");
227
                            var ctx = canvas.getContext('2d');
228
                            ctx.drawImage(img, 0, 0);
229

  
230
                            var MAX_WIDTH = 2000;
231
                            var MAX_HEIGHT = 2000;
232
                            var width = img.width;
233
                            var height = img.height;
234

  
235
                            if (width > height && width > MAX_WIDTH) {
236
                                height *= MAX_WIDTH / width;
237
                                width = MAX_WIDTH;
238
                            } else if (height > MAX_HEIGHT) {
239
                                width *= MAX_HEIGHT / height;
240
                                height = MAX_HEIGHT;
241
                            }
242
                            if (img.width != width || img.height != height) {
243
                                canvas.width = width;
244
                                canvas.height = height;
245
                                var ctx = canvas.getContext('2d');
246
                                ctx.drawImage(img, 0, 0, width, height);
247
                                var new_image_64 = canvas.toDataURL('image/jpeg', 0.95);
248
                                var blob = null;
249
                                if (data.files[0].type == 'image/jpeg') {
250
                                    blob = ExifRestorer.restore_as_blob(original_image_64, new_image_64);
251
                                    blob.name = data.files[0].name;
252
                                } else {
253
                                    // adapted from dataURItoBlob, from
254
                                    // https://stackoverflow.com/questions/12168909/blob-from-dataurl#12300351
255
                                    var byteString = atob(new_image_64.split(',')[1]);
256
                                    var mimeString = new_image_64.split(',')[0].split(':')[1].split(';')[0]
257
                                    var ab = new ArrayBuffer(byteString.length);
258
                                    var ia = new Uint8Array(ab);
259
                                    for (var i = 0; i < byteString.length; i++) {
260
                                        ia[i] = byteString.charCodeAt(i);
261
                                    }
262
                                    blob = new Blob([ab], {type: mimeString});
263
                                    blob.name = data.files[0].name + '.jpg';
264
                                }
265
                                data.files[0] = blob;
266
                            }
267
                            return $.WcsFileUpload.upload(base_widget, data);
268
                        }
269
                        img.src = e.target.result;
270
                    }
271
                    reader.readAsDataURL(data.files[0]);
272
                } else {
273
                  return $.WcsFileUpload.upload(base_widget, data);
274
                }
25 275
            },
26 276
            done: function(e, data) {
27 277
                $(base_widget).find('.fileprogress').hide();
......
57 307
            $(base_widget).find('input[type=file]').click();
58 308
            return false;
59 309
        });
310
    },
311

  
312
    upload: function(base_widget, data) {
313
        $(base_widget).find('.fileprogress').removeClass('upload-error');
314
        $(base_widget).find('.fileprogress .bar').text(
315
                $(base_widget).find('.fileprogress .bar').data('upload'));
316
        $(base_widget).find('.fileprogress .bar').css('width', '0%');
317
        $(base_widget).find('.fileprogress').show();
318
        $(base_widget).find('.fileinfo').hide();
319
        $(base_widget).parents('form').find('input[name=submit]').prop('disabled', true);
320
        var jqXHR = data.submit();
60 321
    }
322

  
61 323
}
62 324

  
63 325
$(function() {
wcs/qommon/templates/qommon/forms/widgets/file.html
2 2
{% load i18n %}
3 3

  
4 4
{% block widget-control %}
5
<div class="file-button {% if widget.is_image %}file-image{% endif %}">
5
<div class="file-button {% if widget.is_image %}file-image{% endif %}"
6
     {% if widget.automatic_image_resize %}data-image-resize="true"{% endif %}>
6 7
{% for w in widget.get_widgets %}
7 8
  {{ w.render|safe }}
8 9
{% endfor %}
......
16 17
<div class="fileprogress" style="display: none;">
17 18
  <div class="bar"
18 19
       data-upload="{% trans "Upload in progress..." %}"
20
       data-resize="{% trans "Resizing image..." %}"
19 21
       data-error="{% trans "Error during upload." %}"></div>
20 22
</div>
21 23
<div class="fileinfo {% if widget.readonly and widget.has_tempfile_image %}thumbnail{% endif %}">
22
-