summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKagami Sascha Rosylight <saschanaz@outlook.com>2022-12-18 15:40:38 +0900
committerGitHub <noreply@github.com>2022-12-18 15:40:38 +0900
commita47d172d60b1ae712738e0ba5dfbb6b81be3f809 (patch)
tree8aeba3a1880507fc6b48458dd3c3632d97185a78
parentchore: deploy vue-tsc for client typecheck (#9341) (diff)
downloadmisskey-a47d172d60b1ae712738e0ba5dfbb6b81be3f809.tar.gz
misskey-a47d172d60b1ae712738e0ba5dfbb6b81be3f809.tar.bz2
misskey-a47d172d60b1ae712738e0ba5dfbb6b81be3f809.zip
enhance(client): Compress non-animated PNG files (#9334)
* style: fix TS lint errors about `ev.target` * enhance: compress non-animated PNG * PNG to PNG? * defer jest things (add it later) * Delete jest.config.cjs * check the compressed file size * log compression stats * use ?? * handle if ($i == null) Co-authored-by: tamaina <tamaina@hotmail.co.jp>
-rw-r--r--packages/client/package.json1
-rw-r--r--packages/client/src/scripts/upload.ts47
-rw-r--r--packages/client/src/scripts/upload/compress-config.ts23
-rw-r--r--yarn.lock8
4 files changed, 55 insertions, 24 deletions
diff --git a/packages/client/package.json b/packages/client/package.json
index 9f86a471b0..2c9b3cbb21 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -31,6 +31,7 @@
"eventemitter3": "5.0.0",
"idb-keyval": "6.2.0",
"insert-text-at-cursor": "0.3.0",
+ "is-file-animated": "1.0.1",
"json5": "2.2.1",
"katex": "0.15.6",
"matter-js": "0.18.0",
diff --git a/packages/client/src/scripts/upload.ts b/packages/client/src/scripts/upload.ts
index 51f1c1b86f..9a39652ef5 100644
--- a/packages/client/src/scripts/upload.ts
+++ b/packages/client/src/scripts/upload.ts
@@ -1,6 +1,7 @@
import { reactive, ref } from 'vue';
import * as Misskey from 'misskey-js';
import { readAndCompressImage } from 'browser-image-resizer';
+import { getCompressionConfig } from './upload/compress-config';
import { defaultStore } from '@/store';
import { apiUrl } from '@/config';
import { $i } from '@/account';
@@ -16,12 +17,6 @@ type Uploading = {
};
export const uploads = ref<Uploading[]>([]);
-const compressTypeMap = {
- 'image/jpeg': { quality: 0.85, mimeType: 'image/jpeg' },
- 'image/webp': { quality: 0.85, mimeType: 'image/jpeg' },
- 'image/svg+xml': { quality: 1, mimeType: 'image/png' },
-} as const;
-
const mimeTypeMap = {
'image/webp': 'webp',
'image/jpeg': 'jpg',
@@ -34,16 +29,18 @@ export function uploadFile(
name?: string,
keepOriginal: boolean = defaultStore.state.keepOriginalUploading,
): Promise<Misskey.entities.DriveFile> {
+ if ($i == null) throw new Error('Not logged in');
+
if (folder && typeof folder === 'object') folder = folder.id;
return new Promise((resolve, reject) => {
const id = Math.random().toString();
const reader = new FileReader();
- reader.onload = async (ev) => {
+ reader.onload = async (): Promise<void> => {
const ctx = reactive<Uploading>({
id: id,
- name: name || file.name || 'untitled',
+ name: name ?? file.name ?? 'untitled',
progressMax: undefined,
progressValue: undefined,
img: window.URL.createObjectURL(file),
@@ -51,20 +48,22 @@ export function uploadFile(
uploads.value.push(ctx);
- let resizedImage: any;
- if (!keepOriginal && file.type in compressTypeMap) {
- const imgConfig = compressTypeMap[file.type];
-
- const config = {
- maxWidth: 2048,
- maxHeight: 2048,
- debug: true,
- ...imgConfig,
- };
-
+ const config = !keepOriginal ? await getCompressionConfig(file) : undefined;
+ let resizedImage: Blob | undefined;
+ if (config) {
try {
- resizedImage = await readAndCompressImage(file, config);
- ctx.name = file.type !== imgConfig.mimeType ? `${ctx.name}.${mimeTypeMap[compressTypeMap[file.type].mimeType]}` : ctx.name;
+ const resized = await readAndCompressImage(file, config);
+ if (resized.size < file.size || file.type === 'image/webp') {
+ // The compression may not always reduce the file size
+ // (and WebP is not browser safe yet)
+ resizedImage = resized;
+ }
+ if (_DEV_) {
+ const saved = ((1 - resized.size / file.size) * 100).toFixed(2);
+ console.log(`Image compression: before ${file.size} bytes, after ${resized.size} bytes, saved ${saved}%`);
+ }
+
+ ctx.name = file.type !== config.mimeType ? `${ctx.name}.${mimeTypeMap[config.mimeType]}` : ctx.name;
} catch (err) {
console.error('Failed to resize image', err);
}
@@ -73,13 +72,13 @@ export function uploadFile(
const formData = new FormData();
formData.append('i', $i.token);
formData.append('force', 'true');
- formData.append('file', resizedImage || file);
+ formData.append('file', resizedImage ?? file);
formData.append('name', ctx.name);
if (folder) formData.append('folderId', folder);
const xhr = new XMLHttpRequest();
xhr.open('POST', apiUrl + '/drive/files/create', true);
- xhr.onload = (ev) => {
+ xhr.onload = ((ev: ProgressEvent<XMLHttpRequest>) => {
if (xhr.status !== 200 || ev.target == null || ev.target.response == null) {
// TODO: 消すのではなくて(ネットワーク的なエラーなら)再送できるようにしたい
uploads.value = uploads.value.filter(x => x.id !== id);
@@ -122,7 +121,7 @@ export function uploadFile(
resolve(driveFile);
uploads.value = uploads.value.filter(x => x.id !== id);
- };
+ }) as (ev: ProgressEvent<EventTarget>) => any;
xhr.upload.onprogress = ev => {
if (ev.lengthComputable) {
diff --git a/packages/client/src/scripts/upload/compress-config.ts b/packages/client/src/scripts/upload/compress-config.ts
new file mode 100644
index 0000000000..793c78ad20
--- /dev/null
+++ b/packages/client/src/scripts/upload/compress-config.ts
@@ -0,0 +1,23 @@
+import isAnimated from 'is-file-animated';
+import type { BrowserImageResizerConfig } from 'browser-image-resizer';
+
+const compressTypeMap = {
+ 'image/jpeg': { quality: 0.85, mimeType: 'image/jpeg' },
+ 'image/png': { quality: 1, mimeType: 'image/png' },
+ 'image/webp': { quality: 0.85, mimeType: 'image/jpeg' },
+ 'image/svg+xml': { quality: 1, mimeType: 'image/png' },
+} as const;
+
+export async function getCompressionConfig(file: File): Promise<BrowserImageResizerConfig | undefined> {
+ const imgConfig = compressTypeMap[file.type];
+ if (!imgConfig || await isAnimated(file)) {
+ return;
+ }
+
+ return {
+ maxWidth: 2048,
+ maxHeight: 2048,
+ debug: true,
+ ...imgConfig,
+ };
+}
diff --git a/yarn.lock b/yarn.lock
index dc9ac7ccd8..96db703b28 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4975,6 +4975,7 @@ __metadata:
eventemitter3: 5.0.0
idb-keyval: 6.2.0
insert-text-at-cursor: 0.3.0
+ is-file-animated: 1.0.1
json5: 2.2.1
katex: 0.15.6
matter-js: 0.18.0
@@ -9574,6 +9575,13 @@ __metadata:
languageName: node
linkType: hard
+"is-file-animated@npm:1.0.1":
+ version: 1.0.1
+ resolution: "is-file-animated@npm:1.0.1"
+ checksum: bcc281e0694e1ba74adfdef75f83f1637ab6470eceecef867d21b4a98e112c32188514b3172348dd137b82cbe8771b6d683de1439d8e1e86011fed77da896c4e
+ languageName: node
+ linkType: hard
+
"is-fullwidth-code-point@npm:^1.0.0":
version: 1.0.0
resolution: "is-fullwidth-code-point@npm:1.0.0"