1
0
mirror of https://github.com/xuthus83/pigallery2.git synced 2025-01-14 14:43:17 +08:00

adding graphicsmagick support for thumbnail

This commit is contained in:
Braun Patrik 2017-06-11 23:33:47 +02:00
parent b7c8570249
commit b336a91fe0
11 changed files with 400 additions and 293 deletions

View File

@ -61,7 +61,7 @@ To configure it. Run `PiGallery2` first to create `config.json` file, then edit
* 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 ([sharp](https://github.com/lovell/sharp) as optional and JS-based [Jimp](https://github.com/oliver-moran/jimp) as fallback)
* supporting hardware acceleration ([sharp](https://github.com/lovell/sharp) and [gm](https://github.com/aheckmann/gm) 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

View File

@ -1,4 +1,7 @@
import {Metadata, SharpInstance} from "@types/sharp";
import {Dimensions, State} from "@types/gm";
export module ThumbnailRenderers {
export interface RendererInput {
imagePath: string;
@ -9,14 +12,14 @@ export interface RendererInput {
__dirname: string;
}
export const softwareRenderer = (input: RendererInput, done) => {
export const jimp = (input: RendererInput, done) => {
//generate thumbnail
const Jimp = require("jimp");
Jimp.read(input.imagePath).then((image) => {
const Logger = require(input.__dirname + "/../../Logger").Logger;
Logger.silly("[SoftwareThRenderer] rendering thumbnail:", input.imagePath);
Logger.silly("[JimpThRenderer] rendering thumbnail:", input.imagePath);
/**
* newWidth * newHeight = size*size
* newHeight/newWidth = height/width
@ -47,7 +50,7 @@ export const softwareRenderer = (input: RendererInput, done) => {
});
};
export const hardwareRenderer = (input: RendererInput, done) => {
export const sharp = (input: RendererInput, done) => {
//generate thumbnail
const sharp = require("sharp");
@ -58,7 +61,7 @@ export const hardwareRenderer = (input: RendererInput, done) => {
.then((metadata: Metadata) => {
const Logger = require(input.__dirname + "/../../Logger").Logger;
Logger.silly("[SoftwareThRenderer] rendering thumbnail:", input.imagePath);
Logger.silly("[SharpThRenderer] rendering thumbnail:", input.imagePath);
/**
* newWidth * newHeight = size*size
* newHeight/newWidth = height/width
@ -104,3 +107,63 @@ export const hardwareRenderer = (input: RendererInput, done) => {
});
};
export const gm = (input: RendererInput, done) => {
//generate thumbnail
const gm = require("gm");
let image: State = gm(input.imagePath);
image
.size((err, value: Dimensions) => {
if (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));
}
const Logger = require(input.__dirname + "/../../Logger").Logger;
Logger.silly("[GMThRenderer] rendering thumbnail:", input.imagePath);
/**
* newWidth * newHeight = size*size
* newHeight/newWidth = height/width
*
* newHeight = (height/width)*newWidth
* newWidth * newWidth = (size*size) / (height/width)
*
* @type {number}
*/
try {
const ratio = value.height / value.width;
const filter = input.qualityPriority == true ? 'Lanczos' : 'Point';
image.filter(filter);
if (input.makeSquare == false) {
const newWidth = Math.round(Math.sqrt((input.size * input.size) / ratio));
image = image.resize(newWidth);
} else {
image = image.resize(input.size, input.size)
.crop(input.size, input.size);
}
image.write(input.thPath, (err) => {
if (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));
}
return done();
});
} 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));
}
});
};
}

View File

@ -9,8 +9,10 @@ import {ContentWrapper} from "../../../common/entities/ConentWrapper";
import {DirectoryDTO} from "../../../common/entities/DirectoryDTO";
import {ProjectPath} from "../../ProjectPath";
import {PhotoDTO} from "../../../common/entities/PhotoDTO";
import {hardwareRenderer, RendererInput, softwareRenderer} from "./THRenderers";
import {ThumbnailRenderers} from "./THRenderers";
import {Config} from "../../../common/config/private/Config";
import {ThumbnailProcessingLib} from "../../../common/config/private/IPrivateConfig";
import RendererInput = ThumbnailRenderers.RendererInput;
Config.Client.concurrentThumbnailGenerations = Math.max(1, os.cpus().length - 1);
@ -21,16 +23,28 @@ const pool = new Pool(Config.Client.concurrentThumbnailGenerations);
export class ThumbnailGeneratorMWs {
private static poolsInited = false;
private static ThumbnailFunction = null
private static initPools() {
if (this.poolsInited == true) {
return
}
if (Config.Server.thumbnail.hardwareAcceleration == true) {
pool.run(hardwareRenderer);
} else {
pool.run(softwareRenderer);
switch (Config.Server.thumbnail.processingLibrary) {
case ThumbnailProcessingLib.Jimp:
this.ThumbnailFunction = ThumbnailRenderers.jimp;
break;
case ThumbnailProcessingLib.gm:
this.ThumbnailFunction = ThumbnailRenderers.gm;
break;
case ThumbnailProcessingLib.sharp:
this.ThumbnailFunction = ThumbnailRenderers.sharp;
break;
default:
throw "Unknown thumbnail processing lib";
}
pool.run(this.ThumbnailFunction);
this.poolsInited = true;
}
@ -158,11 +172,7 @@ export class ThumbnailGeneratorMWs {
});
} else {
try {
if (Config.Server.thumbnail.hardwareAcceleration == true) {
hardwareRenderer(input, out => next(out));
} else {
softwareRenderer(input, out => next(out));
}
ThumbnailGeneratorMWs.ThumbnailFunction(input, out => next(out));
} catch (error) {
console.log(error);
return next(new Error(ErrorCodes.THUMBNAIL_GENERATION_ERROR, error));

View File

@ -13,7 +13,7 @@ import {SharingRouter} from "./routes/SharingRouter";
import {ObjectManagerRepository} from "./model/ObjectManagerRepository";
import {Logger} from "./Logger";
import {Config} from "../common/config/private/Config";
import {DatabaseType} from "../common/config/private/IPrivateConfig";
import {DatabaseType, ThumbnailProcessingLib} from "../common/config/private/IPrivateConfig";
const LOG_TAG = "[server]";
export class Server {
@ -98,7 +98,7 @@ export class Server {
await ObjectManagerRepository.InitMemoryManagers();
}
if (Config.Server.thumbnail.hardwareAcceleration == true) {
if (Config.Server.thumbnail.processingLibrary == ThumbnailProcessingLib.sharp) {
try {
const sharp = require("sharp");
sharp();
@ -109,7 +109,29 @@ export class Server {
Logger.warn(LOG_TAG, "Thumbnail hardware acceleration is not possible." +
" 'Sharp' node module is not found." +
" Falling back to JS based thumbnail generation");
Config.Server.thumbnail.hardwareAcceleration = false;
Config.Server.thumbnail.processingLibrary = ThumbnailProcessingLib.Jimp;
}
}
if (Config.Server.thumbnail.processingLibrary == ThumbnailProcessingLib.gm) {
try {
const gm = require("gm");
gm(1, 1).stream((err) => {
Logger.warn(LOG_TAG, "[Thumbnail hardware acceleration] gm module error: ", err);
Logger.warn(LOG_TAG, "Thumbnail hardware acceleration is not possible." +
" 'gm' node module is not found." +
" Falling back to JS based thumbnail generation");
Config.Server.thumbnail.processingLibrary = ThumbnailProcessingLib.Jimp;
});
} catch (err) {
Logger.warn(LOG_TAG, "[Thumbnail hardware acceleration] gm module error: ", err);
Logger.warn(LOG_TAG, "Thumbnail hardware acceleration is not possible." +
" 'gm' node module is not found." +
" Falling back to JS based thumbnail generation");
Config.Server.thumbnail.processingLibrary = ThumbnailProcessingLib.Jimp;
}
}

View File

@ -9,4 +9,3 @@ export let Config = new PrivateConfigClass();
ConfigLoader.loadBackendConfig(Config,
path.join(__dirname, './../../../config.json'),
[["PORT", "Server-port"]]);

View File

@ -5,6 +5,12 @@ export enum LogLevel {
error, warn, info, debug, verbose
}
export enum ThumbnailProcessingLib{
Jimp = 0,
gm = 1,
sharp = 2
}
export interface MySQLConfig {
host: string;
database: string;
@ -17,7 +23,7 @@ export interface DataBaseConfig {
}
export interface ThumbnailConfig {
folder: string;
hardwareAcceleration: boolean;
processingLibrary: ThumbnailProcessingLib;
qualityPriority: boolean;
}

View File

@ -1,5 +1,5 @@
import {PublicConfigClass} from "../public/ConfigClass";
import {DatabaseType, ServerConfig} from "./IPrivateConfig";
import {DatabaseType, ServerConfig, ThumbnailProcessingLib} from "./IPrivateConfig";
/**
@ -12,7 +12,7 @@ export class PrivateConfigClass extends PublicConfigClass {
imagesFolder: "demo/images",
thumbnail: {
folder: "demo/TEMP",
hardwareAcceleration: true,
processingLibrary: ThumbnailProcessingLib.sharp,
qualityPriority: true
},
database: {

View File

@ -12,4 +12,3 @@ if (typeof ServerInject !== "undefined" && typeof ServerInject.ConfigInject !==
WebConfigLoader.loadFrontendConfig(Config.Client, ServerInject.ConfigInject);
}

View File

@ -1,5 +1,11 @@
import {platformBrowserDynamic} from "@angular/platform-browser-dynamic";
import {enableProdMode} from "@angular/core";
import {environment} from "./environments/environment";
import {AppModule} from "./app/app.module";
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule).catch(err => console.error(err));

View File

@ -63,6 +63,7 @@
"@angular/language-service": "^4.0.0",
"@types/express": "^4.0.35",
"@types/express-session": "1.15.0",
"@types/gm": "^1.17.31",
"@types/jasmine": "^2.5.51",
"@types/node": "^7.0.27",
"@types/optimist": "0.0.29",
@ -97,6 +98,7 @@
"typescript": "^2.3.4"
},
"optionalDependencies": {
"gm": "^1.23.0",
"sharp": "^0.18.1"
}
}