images-watermark
Version:
A simple and customizable Node.js library for adding text or image watermarks to images, supporting various styling options and configurations. `images-watermark` is a Node.js library that uses the **Sharp** library to add watermarks (text or logos) to im
203 lines (172 loc) • 9.54 kB
JavaScript
const fs = require('fs');
const NodeCache = require('node-cache');
const sharp = require('sharp');
const cache = new NodeCache();
const Watermark = {
normalizeReferrer: (referrer) => {
if (referrer) {
referrer = referrer.replace(/^https?:\/\//, '').replace(/\/$/, '');
}
return referrer;
},
generateRepeatingTextWatermark: (text, width, height, textColor = 'black', opacity = '0.3', fontWeight = "800", textLineSpacing = '5', fontFamily = 'Inter, Arial, sans-serif') => {
try {
const cacheKey = `${text}-${width}-${height}-${textColor}-${opacity}-${fontWeight}-${textLineSpacing}-${fontFamily}`;
const cachedSvgData = cache.get(cacheKey);
if (cachedSvgData) {
return cachedSvgData;
} else {
const fontSize = Math.min(width, height) * 0.025;
const lineSpacing = fontSize * parseInt(textLineSpacing);
const textWidth = text.length * fontSize * 0.55;
const repeatCount = Math.ceil(width / textWidth) + 1;
const textRepeat = Array(repeatCount).fill(text).join(' ');
const textLines = Array.from({ length: Math.ceil(height / lineSpacing) })?.map((_, index) => `<text x="0" y="${index * lineSpacing}" font-size="${fontSize}" fill="${textColor}" opacity="${opacity}" style="font-family:${fontFamily};" font-weight="${fontWeight}">${textRepeat}</text>`)?.join('');
let svgData = `<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">${textLines}</svg>`;
cache.set(cacheKey, svgData, 86400);
return svgData;
}
} catch (error) {
console.error('Error in generateRepeatingTextWatermark -->> ', error);
throw new Error(error);
}
},
singleImageWatermark: async (options) => {
const {
imagePath = '',
watermarkPath = '',
allowedReferrers = [],
headers = {},
textWatermark = '',
appName = '',
textColor = 'black',
opacity = '0.3',
fontWeight = '800',
textLineSpacing = '5',
fontFamily = 'Inter, Arial, sans-serif',
} = options;
return new Promise(async (resolve, reject) => {
try {
if (!imagePath || imagePath == null || imagePath == '') {
throw new Error(`Unable to locate the image at the specified path: ${imagePath}.`);
}
if (!allowedReferrers?.length) {
throw new Error(`Please provide the 'allowedReferrers' configuration for the specified domain to enable access to images without watermarks.`);
}
if (!headers) {
throw new Error(`Could you kindly provide the appropriate 'headers' configuration to allow access to images with or without watermarks?`);
}
const referrer = headers?.referer || '';
const normalizedReferrer = Watermark.normalizeReferrer(referrer);
const isValidReferrer = allowedReferrers.some(r => Watermark.normalizeReferrer(r) === normalizedReferrer);
const isCookieSet = headers?.cookie;
const isDirectAccess = headers?.['user-agent'] !== 'undici' && !isValidReferrer && isCookieSet;
if (isDirectAccess) {
const tWatermark = (appName || textWatermark)
if ((!watermarkPath || watermarkPath == undefined || watermarkPath == null || watermarkPath == '') && (!tWatermark || tWatermark == '' || tWatermark == null)) {
fs.readFile(imagePath, (err, data) => {
if (err) {
console.error('Error reading the file:', err);
reject(err);
} else {
resolve(data);
}
});
} else {
const baseImage = sharp(imagePath);
const { width: baseWidth, height: baseHeight } = await baseImage.metadata();
const resizedWatermark = watermarkPath ? await sharp(watermarkPath)
.resize({
width: Math.floor(baseWidth * 0.1),
height: Math.floor(baseHeight * 0.1),
fit: 'inside',
}).toBuffer() : null;
const watermarkText = tWatermark != '' && tWatermark != null ? Watermark.generateRepeatingTextWatermark(tWatermark, baseWidth, baseHeight, textColor, opacity, fontWeight, textLineSpacing, fontFamily) : '';
const compositeImages = [];
if (watermarkText) {
compositeImages.push({ input: Buffer.from(watermarkText), gravity: 'center' })
}
if (resizedWatermark) {
compositeImages.push({ input: resizedWatermark, gravity: 'southeast' })
}
const finalImage = await baseImage.composite(compositeImages).toFormat('png').toBuffer();
resolve(finalImage);
}
} else {
resolve(imagePath);
}
} catch (error) {
console.error("Error in singleImageWatermark =---->> ",error);
reject(error);
}
})
},
multiImageWatermark: async (options) => {
const {
imagePaths = [],
watermarkPath = '',
allowedReferrers = [],
headers = {},
textWatermark = '',
appName = '',
textColor = 'black',
opacity = '0.3',
fontWeight = '800',
textLineSpacing = '5',
fontFamily = 'Inter, Arial, sans-serif',
} = options;
return new Promise(async (resolve, reject) => {
try {
if (!imagePaths?.length) {
throw new Error("Please provide an array of image paths.");
}
if (!allowedReferrers?.length) {
throw new Error(`Please provide the 'allowedReferrers' configuration for the specified domain to enable access to images without watermarks.`);
}
if (!headers) {
throw new Error(`Could you kindly provide the appropriate 'headers' configuration to allow access to images with or without watermarks?`);
}
const referrer = headers?.referer || '';
const normalizedReferrer = Watermark.normalizeReferrer(referrer);
const isValidReferrer = allowedReferrers.some(r => Watermark.normalizeReferrer(r) === normalizedReferrer);
const isCookieSet = headers?.cookie;
const isDirectAccess = headers?.['user-agent'] !== 'undici' && !isValidReferrer && isCookieSet;
const processedImages = await Promise.all(imagePaths?.map(async (imagePath) => {
if (!imagePath || imagePath === '') {
throw new Error(`Unable to locate the image at the specified path: ${imagePath}.`);
}
if (isDirectAccess) {
const tWatermark = (appName || textWatermark);
if ((!watermarkPath || watermarkPath === '') && (!tWatermark || tWatermark === '')) {
return fs.promises.readFile(imagePath);
} else {
const baseImage = sharp(imagePath);
const { width: baseWidth, height: baseHeight } = await baseImage.metadata();
const resizedWatermark = watermarkPath ? await sharp(watermarkPath).resize({
width: Math.floor(baseWidth * 0.1),
height: Math.floor(baseHeight * 0.1),
fit: 'inside',
}).toBuffer() : null;
const watermarkText = tWatermark ? Watermark.generateRepeatingTextWatermark(tWatermark, baseWidth, baseHeight, textColor, opacity, fontWeight, textLineSpacing, fontFamily) : '';
const compositeImages = [];
if (watermarkText) {
compositeImages.push({ input: Buffer.from(watermarkText), gravity: 'center' });
}
if (resizedWatermark) {
compositeImages.push({ input: resizedWatermark, gravity: 'southeast' });
}
return await baseImage.composite(compositeImages).toFormat('png').toBuffer();
}
} else {
return imagePath;
}
}));
resolve(processedImages);
} catch (error) {
console.error("Error in multiImageWatermark =---->> ",error);
reject(error);
}
});
},
};
module.exports = Watermark;