Files
Desktop/ts/util/handleImageAttachment.preload.ts
2026-03-30 12:42:37 -07:00

100 lines
2.5 KiB
TypeScript

// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { ipcRenderer } from 'electron';
import { v4 as genUuid } from 'uuid';
import { blobToArrayBuffer } from '../types/VisualAttachment.dom.ts';
import type { MIMEType } from '../types/MIME.std.ts';
import { IMAGE_JPEG, isHeic, stringToMIMEType } from '../types/MIME.std.ts';
import type { InMemoryAttachmentDraftType } from '../types/Attachment.std.ts';
import { canBeTranscoded } from './Attachment.std.ts';
import { imageToBlurHash } from './imageToBlurHash.dom.ts';
import { scaleImageToLevel } from './scaleImageToLevel.preload.ts';
export async function handleImageAttachment(
file: File
): Promise<InMemoryAttachmentDraftType> {
let processedFile: File | Blob = file;
if (isHeic(file.type, file.name)) {
const uuid = genUuid();
const bytes = new Uint8Array(await file.arrayBuffer());
const convertedData = await new Promise<Uint8Array<ArrayBuffer>>(
(resolve, reject) => {
ipcRenderer.once(`convert-image:${uuid}`, (_, { error, response }) => {
if (response) {
resolve(response);
} else {
reject(error);
}
});
ipcRenderer.send('convert-image', uuid, bytes);
}
);
processedFile = new Blob([convertedData]);
}
const { contentType, file: resizedBlob } = await autoScale({
contentType: isHeic(file.type, file.name)
? IMAGE_JPEG
: stringToMIMEType(file.type),
file: processedFile,
// We always store draft attachments as HQ
highQuality: true,
});
const data = await blobToArrayBuffer(resizedBlob);
const blurHash = await imageToBlurHash(resizedBlob);
return {
blurHash,
clientUuid: genUuid(),
contentType,
data: new Uint8Array(data),
// We strip fileNames from visual attachments
fileName: undefined,
path: file.name,
pending: false,
size: data.byteLength,
};
}
export async function autoScale({
contentType,
file,
highQuality,
}: {
contentType: MIMEType;
file: File | Blob;
highQuality: boolean;
}): Promise<{
contentType: MIMEType;
file: Blob;
}> {
if (!canBeTranscoded({ contentType })) {
return { contentType, file };
}
const { blob, contentType: newContentType } = await scaleImageToLevel({
fileOrBlobOrURL: file,
contentType,
size: file.size,
highQuality,
});
if (newContentType !== IMAGE_JPEG) {
return {
contentType,
file: blob,
};
}
return {
contentType: IMAGE_JPEG,
file: blob,
};
}