UNPKG

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
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;