|
- const fs = require('fs-extra');
- const Sharp = require('sharp');
- const debug = require('debug')('server-connect:image');
- const { basename, extname, join } = require('path');
- const { toAppPath, toSystemPath, parseTemplate, getUniqFile } = require('../core/path');
-
- const positions = {
- 'center': 0,
- 'centre': 0,
- 'top': 1,
- 'north': 1,
- 'right': 2,
- 'east': 2,
- 'bottom': 3,
- 'south': 3,
- 'left': 4,
- 'west': 4,
- 'top right': 5,
- 'right top': 5,
- 'northeast': 5,
- 'bottom right': 6,
- 'right bottom': 6,
- 'southeast': 6,
- 'bottom left': 7,
- 'left bottom': 7,
- 'southwest': 7,
- 'top left': 8,
- 'left top': 8,
- 'northwest': 8,
- 'entropy': 16,
- 'attention': 17
- };
-
- function cw(w, meta) {
- if (typeof w == 'string') {
- if (/%$/.test(w)) {
- w = meta.width * parseFloat(w) / 100;
- }
- }
-
- if (w < 0) {
- w = meta.width + w;
- }
-
- return parseInt(w);
- }
-
- function ch(h, meta) {
- if (typeof h == 'string') {
- if (/%$/.test(h)) {
- h = meta.height * parseFloat(h) / 100;
- }
- }
-
- if (h < 0) {
- h = meta.height + h;
- }
-
- return parseInt(h);
- }
-
- function cx(x, w, meta) {
- if (typeof x == 'string') {
- switch (x) {
- case 'left':
- x = 0;
- break;
- case 'center':
- x = (meta.width - w) / 2;
- break;
- case 'right':
- x = meta.width - w;
- break;
- default:
- if (/%$/.test(x)) {
- x = (meta.width - w) * parseFloat(x) / 100;
- }
- }
- }
-
- if (x < 0) {
- x = meta.width - w + x;
- }
-
- return parseInt(x);
- }
-
- function cy(y, h, meta) {
- if (typeof y == 'string') {
- switch (y) {
- case 'top':
- y = 0;
- break;
- case 'middle':
- y = (meta.height - h) / 2;
- break;
- case 'bottom':
- y = meta.height - h;
- break;
- default:
- if (/%$/.test(y)) {
- y = (meta.height - h) * parseFloat(y) / 100;
- }
- }
- }
-
- if (y < 0) {
- y = meta.height - h + y;
- }
-
- return parseInt(y);
- }
-
- async function updateImage(sharp) {
- sharp.image = Sharp(await sharp.image.toBuffer());
- sharp.metadata = await sharp.image.metadata();
- }
-
- module.exports = {
-
- getImageSize: async function (options) {
- let path = toSystemPath(this.parseRequired(options.path, 'string', 'image.getImageSize: path is required.'));
-
- const image = Sharp(path);
- const metadata = await image.metadata();
-
- return {
- width: metadata.width,
- height: metadata.height
- };
- },
-
- load: async function (options, name) {
- let path = toSystemPath(this.parseRequired(options.path, 'string', 'image.load: path is required.'));
- let orient = this.parseOptional(options.autoOrient, 'boolean', false);
-
- this.req.image = this.req.image || {};
- this.req.image[name] = { name: basename(path), image: Sharp(path), metadata: null };
-
- const sharp = this.req.image[name];
- if (orient) sharp.image.rotate();
-
- await updateImage(sharp);
-
- return {
- name: basename(path),
- width: sharp.metadata.width,
- height: sharp.metadata.height
- };
- },
-
- save: async function (options) {
- const sharp = this.req.image[options.instance];
- if (!sharp) throw new Error(`image.save: instance "${options.instance} doesn't exist.`);
-
- let path = toSystemPath(this.parseRequired(options.path, 'string', 'image.save: path is required.'));
- let format = this.parseOptional(options.format, 'string', 'jpeg').toLowerCase();
- let template = this.parseOptional(options.template, 'string', '{name}{ext}');
- let overwrite = this.parseOptional(options.overwrite, 'boolean', false);
- let createPath = this.parseOptional(options.createPath, 'boolean', true);
- let background = this.parseOptional(options.background, 'string', '#FFFFFF');
- let quality = this.parseOptional(options.quality, 'number', 75);
-
- if (!fs.existsSync(path)) {
- if (createPath) {
- await fs.ensureDir(path);
- } else {
- throw new Error(`image.save: path "${path}" doesn't exist.`);
- }
- }
-
- let file = join(path, sharp.name);
-
- if (template) {
- file = parseTemplate(file, template);
- }
-
- if (format == 'auto') {
- switch (extname(file).toLowerCase()) {
- case '.png': format = 'png'; break;
- case '.gif': format = 'gif'; break;
- case '.webp': format = 'webp'; break;
- default: format = 'jpeg';
- }
- }
-
- if (format == 'jpeg') {
- sharp.image.flatten({ background });
- sharp.image.toFormat(format, { quality });
- } else if (format == 'webp') {
- sharp.image.toFormat(format, { quality });
- } else {
- sharp.image.toFormat(format);
- }
-
- const data = await sharp.image.toBuffer();
-
- file = file.replace(extname(file), '.' + format.replace('jpeg', 'jpg'));
-
- if (fs.existsSync(file)) {
- if (overwrite) {
- await fs.unlink(file);
- } else {
- file = getUniqFile(file);
- }
- }
-
- await fs.writeFile(file, data) //.toFile(file);
-
- return toAppPath(file);
- },
-
- resize: async function (options) {
- const sharp = this.req.image[options.instance];
- if (!sharp) throw new Error(`image.resize: instance "${options.instance} doesn't exist.`);
-
- let width = this.parseOptional(cw(this.parse(options.width), sharp.metadata), 'number', null);
- let height = this.parseOptional(ch(this.parse(options.height), sharp.metadata), 'number', null);
- let upscale = this.parseOptional(options.upscale, 'boolean', false);
-
- if (isNaN(width)) width = null;
- if (isNaN(height)) height = null;
-
- sharp.image.resize(width, height, { fit: width && height ? 'fill' : 'cover', withoutEnlargement: !upscale });
-
- await updateImage(sharp);
- },
-
- crop: async function (options) {
- const sharp = this.req.image[options.instance];
- if (!sharp) throw new Error(`image.crop: instance "${options.instance} doesn't exist.`);
-
- let width = this.parseRequired(cw(this.parse(options.width)), 'number', 'image.crop: width is required.');
- let height = this.parseRequired(ch(this.parse(options.height)), 'number', 'image.crop: height is required.');
- if (width > sharp.metadata.width) width = sharp.metadata.width;
- if (height > sharp.metadata.height) height = sharp.metadata.height;
- let left = this.parseRequired(cx(this.parse(options.x), width, sharp.metadata), 'number', 'image.crop: x is required.');
- let top = this.parseRequired(cy(this.parse(options.y), height, sharp.metadata), 'number', 'image.crop: y is required.');
-
- sharp.image.extract({ left, top, width, height });
-
- await updateImage(sharp);
- },
-
- cover: async function (options) {
- const sharp = this.req.image[options.instance];
- if (!sharp) throw new Error(`image.cover: instance "${options.instance}" doesn't exist.`);
-
- let width = this.parseRequired(options.width, 'number', 'image.cover: width is required.');
- let height = this.parseRequired(options.height, 'number', 'image.cover: height is required.');
- // position: see positions object for options
- let position = this.parseOptional(options.position, 'string', 'center');
- // kernel: 'nearest', 'cubic', 'mitchell', 'lanczos2', 'lanczos3'
- let kernel = this.parseOptional(options.kernel, 'string', 'lanczos3');
-
- position = positions[position] || 0;
-
- sharp.image.resize({ width, height, position, kernel });
-
- await updateImage(sharp);
- },
-
- watermark: async function (options) {
- const sharp = this.req.image[options.instance];
- if (!sharp) throw new Error(`image.watermark: instance "${options.instance} doesn't exist.`);
-
- let path = toSystemPath(this.parseRequired(options.path, 'string', 'image.watermark: path is required.'));
- let image = Sharp(path);
- let metadata = await image.metadata();
- let input = await image.toBuffer();
- let left = this.parseRequired(cx(this.parse(options.x), metadata.width, sharp.metadata), 'number', 'image.watermark: x is required.');
- let top = this.parseRequired(cy(this.parse(options.y), metadata.height, sharp.metadata), 'number', 'image.watermark: y is required.');
-
- sharp.image.composite([{ input, left, top }]);
- },
-
- text: async function (options) {
- const sharp = this.req.image[options.instance];
- if (!sharp) throw new Error(`image.text: instance "${options.instance} doesn't exist.`);
-
- let x = this.parse(options.x);
- let y = this.parse(options.y);
- let text = this.parseRequired(options.text, 'string', 'image.text: text is required.');
- let font = this.parseOptional(options.font, 'string', 'Verdana');
- let size = this.parseOptional(options.size, 'number', 24);
- let color = this.parseOptional(options.color, 'string', '#ffffff');
-
- let width = sharp.metadata.width;
- let height = sharp.metadata.height;
- let anchor = 'start';
-
- switch (x) {
- case 'left':
- x = '0%';
- anchor = 'start';
- break;
- case 'center':
- x = '50%';
- anchor = 'middle';
- break;
- case 'right':
- x = '100%';
- anchor = 'end';
- break;
- default:
- if (x < 0) {
- x = width - x;
- anchor = 'end';
- }
- }
-
- switch (y) {
- case 'top':
- y = size;
- break;
- case 'middle':
- y = (height / 2) - (size / 2);
- break;
- case 'bottom':
- y = height;
- break;
- default:
- if (y < 0) {
- y = height - size - y;
- }
- }
-
- let svg = `
- <svg width="${width}" height="${height}">
- <style>
- .text {
- fill: ${color};
- font-family: "${font}";
- font-size: ${size}px;
- line-height: 1;
- }
- </style>
- <text x="${x}" y="${y}" text-anchor="${anchor}" class="text">${text}</text>
- </svg>
- `;
-
- const input = await Sharp(Buffer.from(svg)).toBuffer();
-
- sharp.image.composite([{ input, left: 0, top: 0 }]);
- },
-
- tiled: async function (options) {
- const sharp = this.req.image[options.instance];
- if (!sharp) throw new Error(`image.tiled: instance "${options.instance} doesn't exist.`);
-
- let input = toSystemPath(this.parseRequired(options.path, 'string', 'image.tiled: path is required.'));
- let padding = this.parseOptional(options.padding, 'number', 0);
-
- if (padding) {
- input = await Sharp(input).extend({
- top: padding, left: padding, bottom: 0, right: 0, background: { r: 0, g: 0, b: 0, alpha: 0 }
- }).toBuffer();
- }
-
- sharp.image.composite([{ input, left: 0, top: 0, tile: true }]);
- },
-
- flip: async function (options) {
- const sharp = this.req.image[options.instance];
- if (!sharp) throw new Error(`image.flip: instance "${options.instance} doesn't exist.`);
-
- let horizontal = this.parseOptional(options.horizontal, 'boolean', false);
- let vertical = this.parseOptional(options.vertical, 'boolean', false);
-
- if (horizontal) sharp.image.flop();
- if (vertical) sharp.image.flip();
- },
-
- rotateLeft: async function (options) {
- const sharp = this.req.image[options.instance];
- if (!sharp) throw new Error(`image.rotateLeft: instance "${options.instance} doesn't exist.`);
-
- sharp.image.rotate(-90);
-
- await updateImage(sharp);
- },
-
- rotateRight: async function (options) {
- const sharp = this.req.image[options.instance];
- if (!sharp) throw new Error(`image.rotateRight: instance "${options.instance} doesn't exist.`);
-
- sharp.image.rotate(90);
-
- await updateImage(sharp);
- },
-
- smooth: async function (options) {
- const sharp = this.req.image[options.instance];
- if (!sharp) throw new Error(`image.smooth: instance "${options.instance} doesn't exist.`);
-
- sharp.image.convolve({
- width: 3,
- height: 3,
- kernel: [
- 1, 1, 1,
- 1, 1, 1,
- 1, 1, 1
- ]
- });
- },
-
- blur: async function (options) {
- const sharp = this.req.image[options.instance];
- if (!sharp) throw new Error(`image.blur: instance "${options.instance} doesn't exist.`);
-
- sharp.image.convolve({
- width: 3,
- height: 3,
- kernel: [
- 1, 2, 1,
- 2, 4, 2,
- 1, 2, 1
- ]
- });
- },
-
- sharpen: async function (options) {
- const sharp = this.req.image[options.instance];
- if (!sharp) throw new Error(`image.sharpen: instance "${options.instance} doesn't exist.`);
-
- sharp.image.convolve({
- width: 3,
- height: 3,
- kernel: [
- 0, -2, 0,
- -2, 15, -2,
- 0, -2, 0
- ]
- });
- },
-
- meanRemoval: async function (options) {
- const sharp = this.req.image[options.instance];
- if (!sharp) throw new Error(`image.meanRemoval: instance "${options.instance} doesn't exist.`);
-
- sharp.image.convolve({
- width: 3,
- height: 3,
- kernel: [
- -1, -1, -1,
- -1, 9, -1,
- -1, -1, -1
- ]
- });
- },
-
- emboss: async function (options) {
- const sharp = this.req.image[options.instance];
- if (!sharp) throw new Error(`image.emboss: instance "${options.instance} doesn't exist.`);
-
- sharp.image.convolve({
- width: 3,
- height: 3,
- kernel: [
- -1, 0, -1,
- 0, 4, 0,
- -1, 0, -1
- ],
- offset: 127
- });
- },
-
- edgeDetect: async function (options) {
- const sharp = this.req.image[options.instance];
- if (!sharp) throw new Error(`image.edgeDetect: instance "${options.instance} doesn't exist.`);
-
- sharp.image.convolve({
- width: 3,
- height: 3,
- kernel: [
- -1, -1, -1,
- 0, 0, 0,
- 1, 1, 1
- ],
- offset: 127
- });
- },
-
- grayscale: async function (options) {
- const sharp = this.req.image[options.instance];
- if (!sharp) throw new Error(`image.grayscale: instance "${options.instance} doesn't exist.`);
-
- sharp.image.grayscale();
- },
-
- sepia: async function (options) {
- const sharp = this.req.image[options.instance];
- if (!sharp) throw new Error(`image.sepia: instance "${options.instance} doesn't exist.`);
-
- sharp.image.tint({ r: 112, g: 66, b: 20 });
- },
-
- invert: async function (options) {
- const sharp = this.req.image[options.instance];
- if (!sharp) throw new Error(`image.invert: instance "${options.instance} doesn't exist.`);
-
- sharp.image.negate();
- },
-
- };
|