Skip to content

Commit 1875157

Browse files
committed
Use jimp for image processing when starting Electron app with flag "--alternativeImageProcessing"
1 parent 4c4efdb commit 1875157

File tree

14 files changed

+240
-75
lines changed

14 files changed

+240
-75
lines changed

desktop/electron/main.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,8 @@ global.manualPath = global.mode === 'production'
227227
? electron.app.getAppPath().replace('app.asar', 'manual')
228228
: './manual';
229229

230+
global.imageProcessing = process.argv.includes('--alternativeImageProcessing') ? 'jimp' : 'sharp';
231+
230232
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = true;
231233

232234

desktop/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@
152152
"geotiff": "2.0.7",
153153
"idai-field-core": "^1.4.0",
154154
"image-size": "2.0.2",
155+
"jimp": "1.6.0",
155156
"leaflet": "1.7.1",
156157
"panzoom": "9.4.3",
157158
"pouchdb-browser": "9.0.0",

desktop/src/app/components/app-initializer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { ExpressServer } from '../services/express-server/express-server';
1414
import { ConfigurationIndex } from '../services/configuration/index/configuration-index';
1515
import { copyThumbnailsFromDatabase } from '../migration/thumbnail-copy';
1616
import { Languages } from '../services/languages';
17-
import { createDisplayVariant } from '../services/imagestore/create-display-variant';
17+
import { createDisplayVariant } from '../services/imagestore/manipulation/create-display-variant';
1818
import { Backup } from '../services/backup/model/backup';
1919
import { BackupService, RestoreBackupResult } from '../services/backup/backup-service';
2020
import { getExistingBackups } from '../services/backup/auto-backup/get-existing-backups';

desktop/src/app/components/image/upload/image-uploader.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ import { M } from '../../messages/m';
1111
import { ImageUploadMetadataModalComponent } from './image-upload-metadata-modal.component';
1212
import { UploadModalComponent } from './upload-modal.component';
1313
import { UploadStatus } from './upload-status';
14-
import { ImageManipulationErrors } from '../../../services/imagestore/image-manipulation';
14+
import { ImageManipulationErrors } from '../../../services/imagestore/manipulation/image-manipulation';
1515
import { ImageMetadata, extendMetadataByFileData } from '../../../services/imagestore/file-metadata';
1616
import { getGeoreferenceFromGeotiff } from '../georeference/geotiff-import';
17-
import { createDisplayVariant } from '../../../services/imagestore/create-display-variant';
17+
import { createDisplayVariant } from '../../../services/imagestore/manipulation/create-display-variant';
1818
import { ImagesState } from '../overview/view/images-state';
1919
import { getAsynchronousFs } from '../../../services/get-asynchronous-fs';
2020
import { getSystemTimezone } from '../../../util/timezones';

desktop/src/app/services/imagestore/create-display-variant.ts

Lines changed: 0 additions & 49 deletions
This file was deleted.

desktop/src/app/services/imagestore/image-url-maker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Injectable, SecurityContext } from '@angular/core';
22
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
33
import { Datastore, ImageDocument, ImageStore, ImageVariant } from 'idai-field-core';
4-
import { createDisplayVariant } from './create-display-variant';
4+
import { createDisplayVariant } from './manipulation/create-display-variant';
55

66

77
@Injectable()
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { ImageDocument, ImageStore } from 'idai-field-core';
2+
import { SharpDisplayVariantCreation } from './sharp/sharp-display-variant-creation';
3+
import { JimpDisplayVariantCreation } from './jimp/jimp-display-variant-creation';
4+
5+
const remote = window.require('@electron/remote');
6+
const imageProcessing: 'sharp'|'jimp' = remote?.getGlobal('imageProcessing') ?? 'sharp';
7+
8+
console.log('Using image processing library: ' + imageProcessing);
9+
10+
11+
/**
12+
* @author Thomas Kleinke
13+
*
14+
* @returns A buffer containing data of a newly created display variant, or undefined if no display variant is needed.
15+
*/
16+
export async function createDisplayVariant(document: ImageDocument, imagestore: ImageStore,
17+
originalData: Buffer): Promise<Buffer|undefined> {
18+
19+
return imageProcessing === 'sharp'
20+
? SharpDisplayVariantCreation.createDisplayVariant(document, imagestore, originalData)
21+
: JimpDisplayVariantCreation.createDisplayVariant(document, imagestore, originalData);
22+
};
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { JimpImageManipulation } from './jimp/jimp-image-manipulation';
2+
import { SharpImageManipulation } from './sharp/sharp-image-manipulation';
3+
4+
const remote = window.require('@electron/remote');
5+
const imageProcessing: 'sharp'|'jimp' = remote?.getGlobal('imageProcessing') ?? 'sharp';
6+
7+
8+
export module ImageManipulationErrors {
9+
10+
export const MAX_INPUT_PIXELS_EXCEEDED = 'imageManipulation/maxInputPixelsExceeded';
11+
}
12+
13+
14+
/**
15+
* @author Thomas Kleinke
16+
*/
17+
export module ImageManipulation {
18+
19+
export const MAX_INPUT_PIXELS = 2500000000;
20+
export const MAX_ORIGINAL_PIXELS = 25000000;
21+
export const MAX_DISPLAY_WIDTH = 10000;
22+
export const MAX_DISPLAY_HEIGHT = 10000;
23+
24+
25+
export async function createThumbnail(buffer: Buffer, targetHeight: number,
26+
targetJpegQuality: number): Promise<Buffer> {
27+
28+
return imageProcessing === 'sharp'
29+
? SharpImageManipulation.createThumbnail(buffer, targetHeight, targetJpegQuality)
30+
: JimpImageManipulation.createThumbnail(buffer, targetHeight, targetJpegQuality);
31+
}
32+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { ImageDocument, ImageStore, ImageVariant } from 'idai-field-core';
2+
import { ImageManipulation } from '../image-manipulation';
3+
import { JimpImageManipulation } from './jimp-image-manipulation';
4+
5+
6+
/**
7+
* @author Thomas Kleinke
8+
*/
9+
export module JimpDisplayVariantCreation {
10+
11+
/**
12+
* @returns A buffer containing data of a newly created display variant, or undefined if no display variant is needed.
13+
*/
14+
export async function createDisplayVariant(document: ImageDocument, imagestore: ImageStore,
15+
originalData: Buffer): Promise<Buffer|undefined> {
16+
17+
const imageId: string = document.resource.id;
18+
const fileExtension: string = ImageDocument.getOriginalFileExtension(document);
19+
const width: number = document.resource.width;
20+
const height: number = document.resource.height;
21+
22+
const image = await JimpImageManipulation.getImageObject(originalData);
23+
24+
const convertToJpeg: boolean = shouldConvertToJpeg(
25+
width * height, fileExtension, image
26+
);
27+
28+
const resize: boolean = width > ImageManipulation.MAX_DISPLAY_WIDTH
29+
|| height > ImageManipulation.MAX_DISPLAY_HEIGHT;
30+
31+
if (!convertToJpeg && !resize) {
32+
await imagestore.addUseOriginalMarker(imageId);
33+
return undefined;
34+
}
35+
36+
const displayVariantData: Buffer = await JimpImageManipulation.createDisplayImage(
37+
image, convertToJpeg, resize
38+
);
39+
40+
await imagestore.store(imageId, displayVariantData, undefined, ImageVariant.DISPLAY);
41+
42+
return displayVariantData;
43+
};
44+
45+
46+
function shouldConvertToJpeg(pixels: number, fileExtension: string, image: any): boolean {
47+
48+
if (fileExtension.toLowerCase().includes('tif')) return true;
49+
50+
if (fileExtension.toLowerCase().includes('png') && pixels > ImageManipulation.MAX_ORIGINAL_PIXELS) {
51+
return JimpImageManipulation.isOpaque(image);
52+
}
53+
}
54+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { Jimp } from 'jimp';
2+
import { ImageManipulation } from '../image-manipulation';
3+
4+
5+
/**
6+
* @author Thomas Kleinke
7+
*/
8+
export module JimpImageManipulation {
9+
10+
export function getImageObject(buffer: Buffer): Promise<any> {
11+
12+
return Jimp.fromBuffer(buffer);
13+
}
14+
15+
16+
export async function createThumbnail(buffer: Buffer, targetHeight: number,
17+
targetJpegQuality: number): Promise<Buffer> {
18+
19+
try {
20+
const image = await getImageObject(buffer);
21+
return image.resize({ h: targetHeight })
22+
.getBuffer('image/jpeg', { quality: targetJpegQuality });
23+
} catch (err) {
24+
console.error('Failed to generate thumbnail:', err);
25+
return undefined;
26+
}
27+
}
28+
29+
30+
export function isOpaque(image: any): boolean {
31+
32+
return image.hasAlpha();
33+
}
34+
35+
36+
export async function createDisplayImage(image: any, convertToJpeg: boolean, resize: boolean): Promise<Buffer> {
37+
38+
if (resize) {
39+
image = image.scaleToFit({
40+
w: ImageManipulation.MAX_DISPLAY_WIDTH,
41+
h: ImageManipulation.MAX_DISPLAY_HEIGHT
42+
});
43+
}
44+
45+
const mimeType: string = convertToJpeg || !image.mime
46+
? 'image/jpeg'
47+
: image.mime;
48+
49+
return image.getBuffer(mimeType);
50+
}
51+
}

0 commit comments

Comments
 (0)