Do not transcode images if they meet the size thresholds
This commit is contained in:
parent
9066067aa4
commit
81c57107ce
|
@ -1,6 +1,8 @@
|
||||||
// Copyright 2019-2020 Signal Messenger, LLC
|
// Copyright 2019-2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
const webpack = require('webpack');
|
||||||
|
|
||||||
module.exports = ({ config }) => {
|
module.exports = ({ config }) => {
|
||||||
config.entry.unshift(
|
config.entry.unshift(
|
||||||
'!!style-loader!css-loader!sanitize.css',
|
'!!style-loader!css-loader!sanitize.css',
|
||||||
|
@ -29,5 +31,11 @@ module.exports = ({ config }) => {
|
||||||
net: 'net',
|
net: 'net',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
config.plugins.unshift(
|
||||||
|
new webpack.IgnorePlugin({
|
||||||
|
resourceRegExp: /sharp$/,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
diff --git a/node_modules/@types/sharp/index.d.ts b/node_modules/@types/sharp/index.d.ts
|
||||||
|
index 3210332..4808af0 100755
|
||||||
|
--- a/node_modules/@types/sharp/index.d.ts
|
||||||
|
+++ b/node_modules/@types/sharp/index.d.ts
|
||||||
|
@@ -23,7 +23,21 @@ import { Duplex } from "stream";
|
||||||
|
* @returns A sharp instance that can be used to chain operations
|
||||||
|
*/
|
||||||
|
declare function sharp(options?: sharp.SharpOptions): sharp.Sharp;
|
||||||
|
-declare function sharp(input?: string | Buffer, options?: sharp.SharpOptions): sharp.Sharp;
|
||||||
|
+declare function sharp(
|
||||||
|
+ input?:
|
||||||
|
+ | Buffer
|
||||||
|
+ | Uint8Array
|
||||||
|
+ | Uint8ClampedArray
|
||||||
|
+ | Int8Array
|
||||||
|
+ | Uint16Array
|
||||||
|
+ | Int16Array
|
||||||
|
+ | Uint32Array
|
||||||
|
+ | Int32Array
|
||||||
|
+ | Float32Array
|
||||||
|
+ | Float64Array
|
||||||
|
+ | string,
|
||||||
|
+ options?: sharp.SharpOptions
|
||||||
|
+): sharp.Sharp;
|
||||||
|
|
||||||
|
declare namespace sharp {
|
||||||
|
/** Object containing nested boolean values representing the available input and output formats/methods. */
|
|
@ -378,7 +378,6 @@ try {
|
||||||
window.emojiData = require('emoji-datasource');
|
window.emojiData = require('emoji-datasource');
|
||||||
window.libphonenumber = require('google-libphonenumber').PhoneNumberUtil.getInstance();
|
window.libphonenumber = require('google-libphonenumber').PhoneNumberUtil.getInstance();
|
||||||
window.libphonenumber.PhoneNumberFormat = require('google-libphonenumber').PhoneNumberFormat;
|
window.libphonenumber.PhoneNumberFormat = require('google-libphonenumber').PhoneNumberFormat;
|
||||||
window.loadImage = require('blueimp-load-image');
|
|
||||||
window.getGuid = require('uuid/v4');
|
window.getGuid = require('uuid/v4');
|
||||||
|
|
||||||
const activeWindowService = new ActiveWindowService();
|
const activeWindowService = new ActiveWindowService();
|
||||||
|
|
|
@ -209,7 +209,11 @@ export async function autoOrientJPEG(
|
||||||
attachment.data,
|
attachment.data,
|
||||||
attachment.contentType
|
attachment.contentType
|
||||||
);
|
);
|
||||||
const xcodedDataBlob = await scaleImageToLevel(dataBlob, isIncoming);
|
const { blob: xcodedDataBlob } = await scaleImageToLevel(
|
||||||
|
dataBlob,
|
||||||
|
attachment.contentType,
|
||||||
|
isIncoming
|
||||||
|
);
|
||||||
const xcodedDataArrayBuffer = await blobToArrayBuffer(xcodedDataBlob);
|
const xcodedDataArrayBuffer = await blobToArrayBuffer(xcodedDataBlob);
|
||||||
|
|
||||||
// IMPORTANT: We overwrite the existing `data` `ArrayBuffer` losing the original
|
// IMPORTANT: We overwrite the existing `data` `ArrayBuffer` losing the original
|
||||||
|
|
|
@ -73,7 +73,19 @@ export async function autoScale({
|
||||||
return { contentType, file, fileName };
|
return { contentType, file, fileName };
|
||||||
}
|
}
|
||||||
|
|
||||||
const blob = await scaleImageToLevel(file, true);
|
const { blob, contentType: newContentType } = await scaleImageToLevel(
|
||||||
|
file,
|
||||||
|
contentType,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
if (newContentType !== IMAGE_JPEG) {
|
||||||
|
return {
|
||||||
|
contentType,
|
||||||
|
file: blob,
|
||||||
|
fileName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const { name } = path.parse(fileName);
|
const { name } = path.parse(fileName);
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import sharp from 'sharp';
|
||||||
import loadImage from 'blueimp-load-image';
|
import loadImage from 'blueimp-load-image';
|
||||||
|
|
||||||
import { IMAGE_JPEG } from '../types/MIME';
|
import { MIMEType, IMAGE_JPEG } from '../types/MIME';
|
||||||
import { canvasToBlob } from './canvasToBlob';
|
import { canvasToBlob } from './canvasToBlob';
|
||||||
import { getValue } from '../RemoteConfig';
|
import { getValue } from '../RemoteConfig';
|
||||||
|
|
||||||
|
@ -21,6 +22,7 @@ const DEFAULT_LEVEL_DATA = {
|
||||||
maxDimensions: 1600,
|
maxDimensions: 1600,
|
||||||
quality: 0.7,
|
quality: 0.7,
|
||||||
size: MiB,
|
size: MiB,
|
||||||
|
thresholdSize: 0.2 * MiB,
|
||||||
};
|
};
|
||||||
|
|
||||||
const MEDIA_QUALITY_LEVEL_DATA = new Map([
|
const MEDIA_QUALITY_LEVEL_DATA = new Map([
|
||||||
|
@ -31,6 +33,7 @@ const MEDIA_QUALITY_LEVEL_DATA = new Map([
|
||||||
maxDimensions: 2048,
|
maxDimensions: 2048,
|
||||||
quality: 0.75,
|
quality: 0.75,
|
||||||
size: MiB * 1.5,
|
size: MiB * 1.5,
|
||||||
|
thresholdSize: 0.3 * MiB,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
@ -39,6 +42,7 @@ const MEDIA_QUALITY_LEVEL_DATA = new Map([
|
||||||
maxDimensions: 4096,
|
maxDimensions: 4096,
|
||||||
quality: 0.75,
|
quality: 0.75,
|
||||||
size: MiB * 3,
|
size: MiB * 3,
|
||||||
|
thresholdSize: 0.4 * MiB,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
@ -82,7 +86,7 @@ function getMediaQualityLevel(): MediaQualityLevels {
|
||||||
return countryValues.get('*') || DEFAULT_LEVEL;
|
return countryValues.get('*') || DEFAULT_LEVEL;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getCanvasBlob(
|
async function getCanvasBlobAsJPEG(
|
||||||
image: HTMLCanvasElement,
|
image: HTMLCanvasElement,
|
||||||
dimensions: number,
|
dimensions: number,
|
||||||
quality: number
|
quality: number
|
||||||
|
@ -98,10 +102,20 @@ async function getCanvasBlob(
|
||||||
return canvasToBlob(canvas, IMAGE_JPEG, quality);
|
return canvasToBlob(canvas, IMAGE_JPEG, quality);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function stripImageFileEXIFData(file: File | Blob): Promise<Blob> {
|
||||||
|
const arrayBuffer = await file.arrayBuffer();
|
||||||
|
const xArrayBuffer = await sharp(new Uint8Array(arrayBuffer)).toBuffer();
|
||||||
|
return new Blob([xArrayBuffer]);
|
||||||
|
}
|
||||||
|
|
||||||
export async function scaleImageToLevel(
|
export async function scaleImageToLevel(
|
||||||
fileOrBlobOrURL: File | Blob,
|
fileOrBlobOrURL: File | Blob,
|
||||||
|
contentType: MIMEType,
|
||||||
sendAsHighQuality?: boolean
|
sendAsHighQuality?: boolean
|
||||||
): Promise<Blob> {
|
): Promise<{
|
||||||
|
blob: Blob;
|
||||||
|
contentType: MIMEType;
|
||||||
|
}> {
|
||||||
let image: HTMLCanvasElement;
|
let image: HTMLCanvasElement;
|
||||||
try {
|
try {
|
||||||
const data = await loadImage(fileOrBlobOrURL, {
|
const data = await loadImage(fileOrBlobOrURL, {
|
||||||
|
@ -121,9 +135,17 @@ export async function scaleImageToLevel(
|
||||||
const level = sendAsHighQuality
|
const level = sendAsHighQuality
|
||||||
? MediaQualityLevels.Three
|
? MediaQualityLevels.Three
|
||||||
: getMediaQualityLevel();
|
: getMediaQualityLevel();
|
||||||
const { maxDimensions, quality, size } =
|
const { maxDimensions, quality, size, thresholdSize } =
|
||||||
MEDIA_QUALITY_LEVEL_DATA.get(level) || DEFAULT_LEVEL_DATA;
|
MEDIA_QUALITY_LEVEL_DATA.get(level) || DEFAULT_LEVEL_DATA;
|
||||||
|
|
||||||
|
if (fileOrBlobOrURL.size <= thresholdSize) {
|
||||||
|
const blob = await stripImageFileEXIFData(fileOrBlobOrURL);
|
||||||
|
return {
|
||||||
|
blob,
|
||||||
|
contentType,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < SCALABLE_DIMENSIONS.length; i += 1) {
|
for (let i = 0; i < SCALABLE_DIMENSIONS.length; i += 1) {
|
||||||
const scalableDimensions = SCALABLE_DIMENSIONS[i];
|
const scalableDimensions = SCALABLE_DIMENSIONS[i];
|
||||||
if (maxDimensions < scalableDimensions) {
|
if (maxDimensions < scalableDimensions) {
|
||||||
|
@ -132,11 +154,18 @@ export async function scaleImageToLevel(
|
||||||
|
|
||||||
// We need these operations to be in serial
|
// We need these operations to be in serial
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
const blob = await getCanvasBlob(image, scalableDimensions, quality);
|
const blob = await getCanvasBlobAsJPEG(image, scalableDimensions, quality);
|
||||||
if (blob.size <= size) {
|
if (blob.size <= size) {
|
||||||
return blob;
|
return {
|
||||||
|
blob,
|
||||||
|
contentType: IMAGE_JPEG,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return getCanvasBlob(image, MIN_DIMENSIONS, quality);
|
const blob = await getCanvasBlobAsJPEG(image, MIN_DIMENSIONS, quality);
|
||||||
|
return {
|
||||||
|
blob,
|
||||||
|
contentType: IMAGE_JPEG,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue