Skip to content

Commit

Permalink
Use 'sharp' to resize thumbnails
Browse files Browse the repository at this point in the history
This works around an Electron crash that occurs when creating thumbnails
for very large images when using Canvas for resizing.

* Add 'sharp' library
* Bump electron minor version to deal with lovell/sharp#3384
  • Loading branch information
apage43 committed Apr 13, 2023
1 parent 77e825a commit 5deec3e
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 39 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
"@typescript-eslint/eslint-plugin": "^5.52.0",
"@typescript-eslint/parser": "^5.52.0",
"css-loader": "^6.7.3",
"electron": "21.3.0",
"electron": "21.3.4",
"electron-builder": "23.6.0",
"eslint": "^8.34.0",
"eslint-config-prettier": "^8.6.0",
Expand Down Expand Up @@ -135,6 +135,7 @@
"react-colorful": "^5.6.1",
"react-dom": "^18.2.0",
"react-window": "^1.8.8",
"sharp": "^0.32.0",
"sourcemapped-stacktrace": "^1.1.11",
"utif": "^3.1.0",
"wasm-feature-detect": "^1.2.11"
Expand Down
45 changes: 18 additions & 27 deletions src/frontend/workers/thumbnailGenerator.worker.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,35 @@
import fse from 'fs-extra';

import * as sharp from 'sharp';
import { thumbnailFormat, thumbnailMaxSize } from 'common/config';
import { IThumbnailMessage, IThumbnailMessageResponse } from '../image/ThumbnailGeneration';
import { Thumbnail } from '../containers/ContentView/GalleryItem';

// TODO: Merge this with the generateThumbnail func from frontend/image/utils.ts, it's duplicate code
const generateThumbnailData = async (filePath: string): Promise<ArrayBuffer | null> => {
const inputBuffer = await fse.readFile(filePath);
const inputBlob = new Blob([inputBuffer]);
const img = await createImageBitmap(inputBlob);
const img = await sharp(inputBuffer);
const metadata = await img.metadata();

// Scale the image so that either width or height becomes `thumbnailMaxSize`
let width = img.width;
let height = img.height;
if (img.width >= img.height) {
if (typeof metadata.width == 'undefined' || typeof metadata.height == 'undefined') {
throw 'could not read image dimensions';
}
const iwidth = metadata.width;
const iheight = metadata.height;
let width = iwidth;
let height = iheight;
if (iwidth >= iheight) {
width = thumbnailMaxSize;
height = (thumbnailMaxSize * img.height) / img.width;
height = Math.trunc((thumbnailMaxSize * iheight) / iwidth);
} else {
height = thumbnailMaxSize;
width = (thumbnailMaxSize * img.width) / img.height;
}

const canvas = new OffscreenCanvas(width, height);

const ctx2D = canvas.getContext('2d');
if (!ctx2D) {
console.warn('No canvas context 2D (should never happen)');
return null;
width = Math.trunc((thumbnailMaxSize * iwidth) / iheight);
}

// Todo: Take into account rotation. Can be found with https://www.npmjs.com/package/node-exiftool

// TODO: Could maybe use https://www.electronjs.org/docs/api/native-image#imageresizeoptions

ctx2D.drawImage(img, 0, 0, width, height);

const thumbBlob = await canvas.convertToBlob({ type: `image/${thumbnailFormat}`, quality: 0.75 });
// TODO: is canvas.toDataURL faster?
const reader = new FileReaderSync();
const buffer = reader.readAsArrayBuffer(thumbBlob);
return buffer;
const resized = img
.resize({ width: width, height: height, fit: sharp.fit.cover })
.toFormat(thumbnailFormat, { quality: 75 });
return await resized.toBuffer();
};

const generateAndStoreThumbnail = async (filePath: string, thumbnailFilePath: string) => {
Expand Down
3 changes: 3 additions & 0 deletions webpack.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ let rendererConfig = {
wasm: path.resolve(__dirname, 'wasm/'),
},
},
externals: {
'sharp': 'commonjs sharp'
},
module: {
rules: [
{
Expand Down
3 changes: 3 additions & 0 deletions webpack.prod.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ let rendererConfig = {
wasm: path.resolve(__dirname, 'wasm/'),
},
},
externals: {
'sharp': 'commonjs sharp'
},
module: {
rules: [
{
Expand Down
Loading

0 comments on commit 5deec3e

Please sign in to comment.