Refactor resizeImage method (#7236)

- Use URL.createObjectURL (replace from FileReader)
- Use HTMLCanvasElement.prototype.toBlob
  (replace from HTMLCanvasElement.prototype.toDataURL)
- Use Promise (replace callback interface)
This commit is contained in:
Yamagishi Kazutoshi 2018-04-23 16:15:51 +09:00 committed by Eugen Rochko
parent 660cb058e1
commit 0758b00bfd
7 changed files with 120 additions and 95 deletions

View file

@ -4,6 +4,7 @@ import { throttle } from 'lodash';
import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light'; import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light';
import { tagHistory } from '../settings'; import { tagHistory } from '../settings';
import { useEmoji } from './emojis'; import { useEmoji } from './emojis';
import resizeImage from '../utils/resize_image';
import { importFetchedAccounts } from './importer'; import { importFetchedAccounts } from './importer';
import { updateTimeline } from './timelines'; import { updateTimeline } from './timelines';
import { showAlertForError } from './alerts'; import { showAlertForError } from './alerts';
@ -174,79 +175,6 @@ 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) { export function uploadCompose(files) {
return function (dispatch, getState) { return function (dispatch, getState) {
if (getState().getIn(['compose', 'media_attachments']).size > 3) { if (getState().getIn(['compose', 'media_attachments']).size > 3) {
@ -255,20 +183,14 @@ export function uploadCompose(files) {
dispatch(uploadComposeRequest()); dispatch(uploadComposeRequest());
resizeImage(files[0], file => { resizeImage(files[0]).then(file => {
let data = new FormData(); const data = new FormData();
data.append('file', file); data.append('file', file);
api(getState).post('/api/v1/media', data, { return api(getState).post('/api/v1/media', data, {
onUploadProgress: function (e) { onUploadProgress: ({ loaded, total }) => dispatch(uploadComposeProgress(loaded, total)),
dispatch(uploadComposeProgress(e.loaded, e.total)); }).then(({ data }) => dispatch(uploadComposeSuccess(data)));
}, }).catch(error => dispatch(uploadComposeFail(error)));
}).then(function (response) {
dispatch(uploadComposeSuccess(response.data));
}).catch(function (error) {
dispatch(uploadComposeFail(error));
});
});
}; };
}; };

View file

@ -1,4 +1,5 @@
import api from '../../api'; import api from '../../api';
import { decode as decodeBase64 } from '../../utils/base64';
import { pushNotificationsSetting } from '../../settings'; import { pushNotificationsSetting } from '../../settings';
import { setBrowserSupport, setSubscription, clearSubscription } from './setter'; import { setBrowserSupport, setSubscription, clearSubscription } from './setter';
import { me } from '../../initial_state'; import { me } from '../../initial_state';
@ -10,13 +11,7 @@ const urlBase64ToUint8Array = (base64String) => {
.replace(/\-/g, '+') .replace(/\-/g, '+')
.replace(/_/g, '/'); .replace(/_/g, '/');
const rawData = window.atob(base64); return decodeBase64(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}; };
const getApplicationServerKey = () => document.querySelector('[name="applicationServerKey"]').getAttribute('content'); const getApplicationServerKey = () => document.querySelector('[name="applicationServerKey"]').getAttribute('content');

View file

@ -5,6 +5,7 @@ import includes from 'array-includes';
import assign from 'object-assign'; import assign from 'object-assign';
import values from 'object.values'; import values from 'object.values';
import isNaN from 'is-nan'; import isNaN from 'is-nan';
import { decode as decodeBase64 } from './utils/base64';
if (!Array.prototype.includes) { if (!Array.prototype.includes) {
includes.shim(); includes.shim();
@ -21,3 +22,23 @@ if (!Object.values) {
if (!Number.isNaN) { if (!Number.isNaN) {
Number.isNaN = isNaN; Number.isNaN = isNaN;
} }
if (!HTMLCanvasElement.prototype.toBlob) {
const BASE64_MARKER = ';base64,';
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
value(callback, type = 'image/png', quality) {
const dataURL = this.toDataURL(type, quality);
let data;
if (dataURL.indexOf(BASE64_MARKER) >= 0) {
const [, base64] = dataURL.split(BASE64_MARKER);
data = decodeBase64(base64);
} else {
[, data] = dataURL.split(',');
}
callback(new Blob([data], { type }));
},
});
}

View file

@ -12,12 +12,13 @@ function importExtraPolyfills() {
function loadPolyfills() { function loadPolyfills() {
const needsBasePolyfills = !( const needsBasePolyfills = !(
Array.prototype.includes &&
HTMLCanvasElement.prototype.toBlob &&
window.Intl && window.Intl &&
Number.isNaN &&
Object.assign && Object.assign &&
Object.values && Object.values &&
Number.isNaN && window.Symbol
window.Symbol &&
Array.prototype.includes
); );
// Latest version of Firefox and Safari do not have IntersectionObserver. // Latest version of Firefox and Safari do not have IntersectionObserver.

View file

@ -0,0 +1,10 @@
import * as base64 from '../base64';
describe('base64', () => {
describe('decode', () => {
it('returns a uint8 array', () => {
const arr = base64.decode('dGVzdA==');
expect(arr).toEqual(new Uint8Array([116, 101, 115, 116]));
});
});
});

View file

@ -0,0 +1,10 @@
export const decode = base64 => {
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
};

View file

@ -0,0 +1,66 @@
const MAX_IMAGE_DIMENSION = 1280;
const getImageUrl = inputFile => new Promise((resolve, reject) => {
if (window.URL && URL.createObjectURL) {
try {
resolve(URL.createObjectURL(inputFile));
} catch (error) {
reject(error);
}
return;
}
const reader = new FileReader();
reader.onerror = (...args) => reject(...args);
reader.onload = ({ target }) => resolve(target.result);
reader.readAsDataURL(inputFile);
});
const loadImage = inputFile => new Promise((resolve, reject) => {
getImageUrl(inputFile).then(url => {
const img = new Image();
img.onerror = (...args) => reject(...args);
img.onload = () => resolve(img);
img.src = url;
}).catch(reject);
});
export default inputFile => new Promise((resolve, reject) => {
if (!inputFile.type.match(/image.*/) || inputFile.type === 'image/gif') {
resolve(inputFile);
return;
}
loadImage(inputFile).then(img => {
const canvas = document.createElement('canvas');
const { width, height } = img;
let newWidth, newHeight;
if (width < MAX_IMAGE_DIMENSION && height < MAX_IMAGE_DIMENSION) {
resolve(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);
canvas.toBlob(resolve, inputFile.type);
}).catch(reject);
});