diff --git a/main-site/photo-gallery-app/backend/bpb-watermark.svg b/main-site/photo-gallery-app/backend/bpb-watermark.svg
new file mode 100644
index 0000000..960828f
--- /dev/null
+++ b/main-site/photo-gallery-app/backend/bpb-watermark.svg
@@ -0,0 +1,56 @@
+
+
+
+
diff --git a/main-site/photo-gallery-app/backend/routes/photos.js b/main-site/photo-gallery-app/backend/routes/photos.js
index 399a05a..a152efd 100644
--- a/main-site/photo-gallery-app/backend/routes/photos.js
+++ b/main-site/photo-gallery-app/backend/routes/photos.js
@@ -40,6 +40,19 @@ const VARIANTS = {
thumb: { size: 640, quality: 76, suffix: '-sm' },
};
+// Watermark logo — loaded once at startup, black fills replaced with semi-transparent white
+let WM_LOGO_BUF = null;
+(function loadWatermarkLogo() {
+ const wmPath = path.join(__dirname, '..', 'bpb-watermark.svg');
+ try {
+ const raw = fs.readFileSync(wmPath, 'utf8');
+ const styled = raw.replace(/style="fill:#000000"/g, 'style="fill:#ffffff;fill-opacity:0.35"');
+ WM_LOGO_BUF = Buffer.from(styled);
+ } catch (e) {
+ console.warn('[watermark] bpb-watermark.svg not found — watermark disabled.');
+ }
+}());
+
const HEIF_BRANDS = new Set([
'heic', 'heix', 'hevc', 'heim', 'heis', 'hevm', 'hevs', 'mif1', 'msf1', 'avif', 'avis'
]);
@@ -191,48 +204,27 @@ router.route('/upload').post(requireAuth, upload.array('photos'), async (req, re
parseInt(hash.substring(4, 6), 16),
];
- const diagonalOverlay = Buffer.from(`
-
- `);
-
let buffer;
try {
- // Prepare base image first so we know its post-resize dimensions, then scale overlay slightly smaller to avoid size conflicts
const base = sharp(inputBuffer)
.rotate()
.resize({ width: VARIANTS.main.size, height: VARIANTS.main.size, fit: 'inside', withoutEnlargement: true })
.toColorspace('srgb');
const { data: baseBuffer, info } = await base.toBuffer({ resolveWithObject: true });
- const targetWidth = Math.max(Math.floor((info.width || VARIANTS.main.size) * 0.98), 1);
- const targetHeight = Math.max(Math.floor((info.height || VARIANTS.main.size) * 0.98), 1);
- // Scale the diagonal overlay to slightly smaller than the image to ensure it composites cleanly
- const overlayBuffer = await sharp(diagonalOverlay, { density: 300 })
- .resize({ width: targetWidth, height: targetHeight, fit: 'cover' })
- .png()
- .toBuffer();
+ const compositeInputs = [];
+ if (WM_LOGO_BUF) {
+ const wmW = Math.max(Math.round(info.width * 0.45), 150);
+ const wmBuf = await sharp(WM_LOGO_BUF, { density: 300 })
+ .resize({ width: wmW, fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } })
+ .png()
+ .toBuffer();
+ compositeInputs.push({ input: wmBuf, gravity: 'center' });
+ }
buffer = await sharp(baseBuffer)
- .composite([
- { input: overlayBuffer, gravity: 'center' },
- ])
+ .composite(compositeInputs)
.toFormat('webp', { quality: VARIANTS.main.quality, effort: 5 })
.toBuffer();
} catch (err) {
@@ -247,19 +239,19 @@ router.route('/upload').post(requireAuth, upload.array('photos'), async (req, re
.toColorspace('srgb');
const { data: baseBufferRetry, info: infoRetry } = await baseRetry.toBuffer({ resolveWithObject: true });
- const overlayRetry = await sharp(diagonalOverlay, { density: 300 })
- .resize({
- width: Math.max(Math.floor((infoRetry.width || VARIANTS.main.size) * 0.98), 1),
- height: Math.max(Math.floor((infoRetry.height || VARIANTS.main.size) * 0.98), 1),
- fit: 'cover'
- })
- .png()
- .toBuffer();
+
+ const compositeRetry = [];
+ if (WM_LOGO_BUF) {
+ const wmWRetry = Math.max(Math.round(infoRetry.width * 0.45), 150);
+ const wmBufRetry = await sharp(WM_LOGO_BUF, { density: 300 })
+ .resize({ width: wmWRetry, fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } })
+ .png()
+ .toBuffer();
+ compositeRetry.push({ input: wmBufRetry, gravity: 'center' });
+ }
buffer = await sharp(baseBufferRetry)
- .composite([
- { input: overlayRetry, gravity: 'center' },
- ])
+ .composite(compositeRetry)
.toFormat('webp', { quality: VARIANTS.main.quality, effort: 5 })
.toBuffer();
} catch (secondErr) {