about summary refs log tree commit diff
path: root/app/javascript
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2018-04-23 00:43:36 +0200
committerGitHub <noreply@github.com>2018-04-23 00:43:36 +0200
commit05fb6f096db7c58698f8e0bc7fc79e129b241176 (patch)
tree7f85df6c958e83e708bc263ad179b0d1f66cae24 /app/javascript
parent75c4ab9d12d3a2f3de52c51b5006fe9d5d9afae4 (diff)
Resize images before upload in web UI to reduce bandwidth (#7223)
* Resize images before upload in web UI to reduce bandwidth

Fix #7218

* Fix issues

* Do not resize GIFs in JS
Diffstat (limited to 'app/javascript')
-rw-r--r--app/javascript/mastodon/actions/compose.js97
1 files changed, 86 insertions, 11 deletions
diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js
index eee9c6928..c8ea5aaba 100644
--- a/app/javascript/mastodon/actions/compose.js
+++ b/app/javascript/mastodon/actions/compose.js
@@ -174,6 +174,79 @@ export function submitComposeFail(error) {
   };
 };
 
+const MAX_IMAGE_DIMENSION = 1280;
+
+const dataURLtoBlob = dataURL => {
+  const BASE64_MARKER = ';base64,';
+
+  if (dataURL.indexOf(BASE64_MARKER) === -1) {
+    const parts       = dataURL.split(',');
+    const contentType = parts[0].split(':')[1];
+    const raw         = parts[1];
+
+    return new Blob([raw], { type: contentType });
+  }
+
+  const parts       = dataURL.split(BASE64_MARKER);
+  const contentType = parts[0].split(':')[1];
+  const raw         = window.atob(parts[1]);
+  const rawLength   = raw.length;
+
+  const uInt8Array = new Uint8Array(rawLength);
+
+  for (let i = 0; i < rawLength; ++i) {
+    uInt8Array[i] = raw.charCodeAt(i);
+  }
+
+  return new Blob([uInt8Array], { type: contentType });
+};
+
+const resizeImage = (inputFile, callback) => {
+  if (inputFile.type.match(/image.*/) && inputFile.type !== 'image/gif') {
+    const reader = new FileReader();
+
+    reader.onload = e => {
+      const img = new Image();
+
+      img.onload = () => {
+        const canvas = document.createElement('canvas');
+        const { width, height } = img;
+
+        let newWidth, newHeight;
+
+        if (width < MAX_IMAGE_DIMENSION && height < MAX_IMAGE_DIMENSION) {
+          callback(inputFile);
+          return;
+        }
+
+        if (width > height) {
+          newHeight = height * MAX_IMAGE_DIMENSION / width;
+          newWidth  = MAX_IMAGE_DIMENSION;
+        } else if (height > width) {
+          newWidth  = width * MAX_IMAGE_DIMENSION / height;
+          newHeight = MAX_IMAGE_DIMENSION;
+        } else {
+          newWidth  = MAX_IMAGE_DIMENSION;
+          newHeight = MAX_IMAGE_DIMENSION;
+        }
+
+        canvas.width  = newWidth;
+        canvas.height = newHeight;
+
+        canvas.getContext('2d').drawImage(img, 0, 0, newWidth, newHeight);
+
+        callback(dataURLtoBlob(canvas.toDataURL(inputFile.type)));
+      };
+
+      img.src = e.target.result;
+    };
+
+    reader.readAsDataURL(inputFile);
+  } else {
+    callback(inputFile);
+  }
+};
+
 export function uploadCompose(files) {
   return function (dispatch, getState) {
     if (getState().getIn(['compose', 'media_attachments']).size > 3) {
@@ -182,17 +255,19 @@ export function uploadCompose(files) {
 
     dispatch(uploadComposeRequest());
 
-    let data = new FormData();
-    data.append('file', files[0]);
-
-    api(getState).post('/api/v1/media', data, {
-      onUploadProgress: function (e) {
-        dispatch(uploadComposeProgress(e.loaded, e.total));
-      },
-    }).then(function (response) {
-      dispatch(uploadComposeSuccess(response.data));
-    }).catch(function (error) {
-      dispatch(uploadComposeFail(error));
+    resizeImage(files[0], file => {
+      let data = new FormData();
+      data.append('file', file);
+
+      api(getState).post('/api/v1/media', data, {
+        onUploadProgress: function (e) {
+          dispatch(uploadComposeProgress(e.loaded, e.total));
+        },
+      }).then(function (response) {
+        dispatch(uploadComposeSuccess(response.data));
+      }).catch(function (error) {
+        dispatch(uploadComposeFail(error));
+      });
     });
   };
 };