diff --git a/README.md b/README.md
index 641204c..41c0137 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@ Feature list:
* prioritizes thumbnail generation (generating thumbnail first for the visible photos)
* saving generated thumbnails to TEMP folder for reuse
* supporting several core CPU
- * supporting hardware acceleration - `In progress`
+ * supporting hardware acceleration ([sharp](https://github.com/lovell/sharp) as optional and JS-based [Jimp](https://github.com/oliver-moran/jimp) as fallback)
* Custom lightbox for full screen photo viewing
* keyboard support for navigation - `In progress`
* showing low-res thumbnail while full image loads
diff --git a/backend/ProjectPath.ts b/backend/ProjectPath.ts
index 25d4dd0..e65324f 100644
--- a/backend/ProjectPath.ts
+++ b/backend/ProjectPath.ts
@@ -17,7 +17,7 @@ class ProjectPathClass {
constructor() {
this.Root = path.join(__dirname, "/../");
this.ImageFolder = this.isAbsolutePath(Config.Server.imagesFolder) ? Config.Server.imagesFolder : path.join(this.Root, Config.Server.imagesFolder);
- this.ThumbnailFolder = this.isAbsolutePath(Config.Server.thumbnailFolder) ? Config.Server.thumbnailFolder : path.join(this.Root, Config.Server.thumbnailFolder);
+ this.ThumbnailFolder = this.isAbsolutePath(Config.Server.thumbnail.folder) ? Config.Server.thumbnail.folder : path.join(this.Root, Config.Server.thumbnail.folder);
}
}
diff --git a/backend/config/Config.ts b/backend/config/Config.ts
index a4c717c..f14bf9e 100644
--- a/backend/config/Config.ts
+++ b/backend/config/Config.ts
@@ -7,7 +7,10 @@ export let Config = new ConfigClass();
Config.Server = {
port: 80,
imagesFolder: "demo/images",
- thumbnailFolder: "demo/TEMP",
+ thumbnail: {
+ folder: "demo/TEMP",
+ hardwareAcceleration: true
+ },
database: {
type: DatabaseType.mysql,
mysql: {
diff --git a/backend/middlewares/thumbnail/THRenderers.ts b/backend/middlewares/thumbnail/THRenderers.ts
new file mode 100644
index 0000000..5a3189d
--- /dev/null
+++ b/backend/middlewares/thumbnail/THRenderers.ts
@@ -0,0 +1,91 @@
+import {Metadata, SharpInstance} from "@types/sharp";
+
+export interface RendererInput {
+ imagePath: string;
+ size: number;
+ makeSquare: boolean;
+ thPath: string;
+ __dirname: string;
+}
+
+export const softwareRenderer = (input: RendererInput, done) => {
+
+ //generate thumbnail
+ const Jimp = require("jimp");
+ Jimp.read(input.imagePath).then((image) => {
+ /**
+ * newWidth * newHeight = size*size
+ * newHeight/newWidth = height/width
+ *
+ * newHeight = (height/width)*newWidth
+ * newWidth * newWidth = (size*size) / (height/width)
+ *
+ * @type {number}
+ */
+ const ratio = image.bitmap.height / image.bitmap.width;
+ if (input.makeSquare == false) {
+ let newWidth = Math.sqrt((input.size * input.size) / ratio);
+
+ image.resize(newWidth, Jimp.AUTO, Jimp.RESIZE_BEZIER);
+ } else {
+ image.resize(input.size / Math.min(ratio, 1), Jimp.AUTO, Jimp.RESIZE_BEZIER);
+ image.crop(0, 0, input.size, input.size);
+ }
+ image.quality(60); // set JPEG quality
+ image.write(input.thPath, () => { // save
+ return done();
+ });
+ }).catch(function (err) {
+ const Error = require(input.__dirname + "/../../../common/entities/Error").Error;
+ const ErrorCodes = require(input.__dirname + "/../../../common/entities/Error").ErrorCodes;
+ return done(new Error(ErrorCodes.GENERAL_ERROR, err));
+ });
+};
+
+export const hardwareRenderer = (input: RendererInput, done) => {
+
+ //generate thumbnail
+ const sharp = require("sharp");
+
+ const image: SharpInstance = sharp(input.imagePath);
+ image
+ .metadata()
+ .then((metadata: Metadata) => {
+ /**
+ * newWidth * newHeight = size*size
+ * newHeight/newWidth = height/width
+ *
+ * newHeight = (height/width)*newWidth
+ * newWidth * newWidth = (size*size) / (height/width)
+ *
+ * @type {number}
+ */
+ try {
+ const ratio = metadata.height / metadata.width;
+ if (input.makeSquare == false) {
+ const newWidth = Math.round(Math.sqrt((input.size * input.size) / ratio));
+ console.log(image
+ .resize(newWidth));
+
+ } else {
+ image
+ .resize(input.size, input.size)
+ .crop(sharp.strategy.center);
+ }
+ image
+ .jpeg()
+ .toFile(input.thPath).then(() => {
+ return done();
+ }).catch(function (err) {
+ const Error = require(input.__dirname + "/../../../common/entities/Error").Error;
+ const ErrorCodes = require(input.__dirname + "/../../../common/entities/Error").ErrorCodes;
+ return done(new Error(ErrorCodes.GENERAL_ERROR, err));
+ });
+ } catch (err) {
+ const Error = require(input.__dirname + "/../../../common/entities/Error").Error;
+ const ErrorCodes = require(input.__dirname + "/../../../common/entities/Error").ErrorCodes;
+ return done(new Error(ErrorCodes.GENERAL_ERROR, err));
+ }
+ });
+
+};
\ No newline at end of file
diff --git a/backend/middlewares/ThumbnailGeneratorMWs.ts b/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts
similarity index 70%
rename from backend/middlewares/ThumbnailGeneratorMWs.ts
rename to backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts
index d7685d9..c68e760 100644
--- a/backend/middlewares/ThumbnailGeneratorMWs.ts
+++ b/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts
@@ -1,15 +1,16 @@
-///
+///
import * as path from "path";
import * as crypto from "crypto";
import * as fs from "fs";
import * as os from "os";
import {NextFunction, Request, Response} from "express";
-import {Error, ErrorCodes} from "../../common/entities/Error";
-import {Config} from "../config/Config";
-import {ContentWrapper} from "../../common/entities/ConentWrapper";
-import {DirectoryDTO} from "../../common/entities/DirectoryDTO";
-import {ProjectPath} from "../ProjectPath";
-import {PhotoDTO} from "../../common/entities/PhotoDTO";
+import {Error, ErrorCodes} from "../../../common/entities/Error";
+import {Config} from "../../config/Config";
+import {ContentWrapper} from "../../../common/entities/ConentWrapper";
+import {DirectoryDTO} from "../../../common/entities/DirectoryDTO";
+import {ProjectPath} from "../../ProjectPath";
+import {PhotoDTO} from "../../../common/entities/PhotoDTO";
+import {hardwareRenderer, softwareRenderer} from "./THRenderers";
Config.Client.concurrentThumbnailGenerations = Math.max(1, os.cpus().length - 1);
@@ -17,41 +18,11 @@ Config.Client.concurrentThumbnailGenerations = Math.max(1, os.cpus().length - 1)
const Pool = require('threads').Pool;
const pool = new Pool(Config.Client.concurrentThumbnailGenerations);
-pool.run(
- (input: {imagePath: string, size: number, makeSquare: boolean, thPath: string}, done) => {
-
- //generate thumbnail
- let Jimp = require("jimp");
- Jimp.read(input.imagePath).then((image) => {
- /**
- * newWidth * newHeight = size*size
- * newHeight/newWidth = height/width
- *
- * newHeight = (height/width)*newWidth
- * newWidth * newWidth = (size*size) / (height/width)
- *
- * @type {number}
- */
- if (input.makeSquare == false) {
- let ratio = image.bitmap.height / image.bitmap.width;
- let newWidth = Math.sqrt((input.size * input.size) / ratio);
-
- image.resize(newWidth, Jimp.AUTO, Jimp.RESIZE_BEZIER);
- } else {
- let ratio = image.bitmap.height / image.bitmap.width;
- image.resize(input.size / Math.min(ratio, 1), Jimp.AUTO, Jimp.RESIZE_BEZIER);
- image.crop(0, 0, input.size, input.size);
- }
- image.quality(60); // set JPEG quality
- image.write(input.thPath, () => { // save
- return done();
- });
- }).catch(function (err) {
- return done(new Error(ErrorCodes.GENERAL_ERROR));
- });
- }
-);
-
+if (Config.Server.thumbnail.hardwareAcceleration == true) {
+ pool.run(hardwareRenderer);
+} else {
+ pool.run(softwareRenderer);
+}
export class ThumbnailGeneratorMWs {
@@ -159,11 +130,12 @@ export class ThumbnailGeneratorMWs {
}
//run on other thread
- pool.send({imagePath: imagePath, size: size, thPath: thPath, makeSquare: makeSquare})
+ pool.send({imagePath: imagePath, size: size, thPath: thPath, makeSquare: makeSquare, __dirname: __dirname})
.on('done', (out) => {
return next(out);
- }).on('error', (job, error) => {
- return next(new Error(ErrorCodes.GENERAL_ERROR, error));
+ }).on('error', (error) => {
+ console.log(error);
+ return next(new Error(ErrorCodes.THUMBNAIL_GENERATION_ERROR, error));
});
}
diff --git a/backend/routes/GalleryRouter.ts b/backend/routes/GalleryRouter.ts
index 9c28668..7868294 100644
--- a/backend/routes/GalleryRouter.ts
+++ b/backend/routes/GalleryRouter.ts
@@ -1,7 +1,7 @@
import {AuthenticationMWs} from "../middlewares/user/AuthenticationMWs";
import {GalleryMWs} from "../middlewares/GalleryMWs";
import {RenderingMWs} from "../middlewares/RenderingMWs";
-import {ThumbnailGeneratorMWs} from "../middlewares/ThumbnailGeneratorMWs";
+import {ThumbnailGeneratorMWs} from "../middlewares/thumbnail/ThumbnailGeneratorMWs";
export class GalleryRouter {
constructor(private app: any) {
diff --git a/backend/server.ts b/backend/server.ts
index e38b187..d7c53b4 100644
--- a/backend/server.ts
+++ b/backend/server.ts
@@ -63,6 +63,17 @@ export class Server {
ObjectManagerRepository.InitMemoryManagers();
});
+ if (Config.Server.thumbnail.hardwareAcceleration == true) {
+ try {
+ const sharp = require.resolve("sharp");
+ } catch (err) {
+ console.error("Thumbnail hardware acceleration is not possible." +
+ " 'Sharp' node module is not found." +
+ " Falling back to JS based thumbnail generation");
+ Config.Server.thumbnail.hardwareAcceleration = false;
+ }
+ }
+
new PublicRouter(this.app);
new UserRouter(this.app);
diff --git a/common/config/Config.ts b/common/config/Config.ts
index d17b609..613cbbb 100644
--- a/common/config/Config.ts
+++ b/common/config/Config.ts
@@ -12,11 +12,15 @@ interface DataBaseConfig {
type: DatabaseType;
mysql?: MySQLConfig;
}
+interface ThumbnailConfig {
+ folder: string;
+ hardwareAcceleration: boolean;
+}
interface ServerConfig {
port: number;
imagesFolder: string;
- thumbnailFolder: string;
+ thumbnail: ThumbnailConfig;
database: DataBaseConfig;
}
diff --git a/common/entities/Error.ts b/common/entities/Error.ts
index 7ac5976..03436e9 100644
--- a/common/entities/Error.ts
+++ b/common/entities/Error.ts
@@ -9,6 +9,7 @@ export enum ErrorCodes{
GENERAL_ERROR,
+ THUMBNAIL_GENERATION_ERROR,
SERVER_ERROR,
USER_MANAGEMENT_DISABLED
diff --git a/package.json b/package.json
index bd4c5e5..8d8fb7e 100644
--- a/package.json
+++ b/package.json
@@ -59,6 +59,7 @@
"@types/jasmine": "^2.5.47",
"@types/node": "^7.0.22",
"@types/optimist": "0.0.29",
+ "@types/sharp": "^0.17.1",
"chai": "^4.0.0",
"jasmine-core": "^2.6.2",
"karma": "^1.7.0",
@@ -76,5 +77,8 @@
"ts-helpers": "^1.1.2",
"tslint": "^5.3.2",
"typescript": "^2.3.3"
+ },
+ "optionalDependencies": {
+ "sharp": "^0.17.3"
}
}