import assert from 'node:assert/strict'; import { test } from 'node:test'; import sharp from 'sharp'; import { renderWatermark } from '../src/media-watermark.ts'; const template = { kind: 'image', opacity: '1', image_size_px: 40, border_radius_px: 0, position_x: 1, position_y: 1, position: 'bottom_right', }; const pixel = async (buffer, x, y) => { const { data, info } = await sharp(buffer).raw().toBuffer({ resolveWithObject: true }); return [...data.subarray((y * info.width + x) * info.channels, (y * info.width + x) * info.channels + 3)]; }; test('图片水印按坐标贴近右下边,并保持源图尺寸', async () => { const source = await sharp({ create: { width: 120, height: 100, channels: 3, background: '#ffffff' } }).png().toBuffer(); const mark = await sharp({ create: { width: 40, height: 40, channels: 4, background: '#ff0000ff' } }).png().toBuffer(); const output = await renderWatermark(source, template, mark); assert.equal(output.width, 120); assert.equal(output.height, 100); assert.ok((await pixel(output.bytes, 90, 70))[0] > 200); assert.ok((await pixel(output.bytes, 90, 70))[1] < 60); assert.ok((await pixel(output.bytes, 15, 15))[1] > 240); }); test('大图中的水印大小和底边距与 240×320 管理预览等比一致', async () => { const source = await sharp({ create: { width: 1200, height: 1600, channels: 3, background: '#ffffff' } }).png().toBuffer(); const mark = await sharp({ create: { width: 80, height: 80, channels: 4, background: '#ff0000ff' } }).png().toBuffer(); const output = await renderWatermark(source, { ...template, image_size_px: 80 }, mark); assert.ok((await pixel(output.bytes, 800, 1200))[1] < 60); assert.ok((await pixel(output.bytes, 700, 1200))[1] > 240); assert.ok((await pixel(output.bytes, 1100, 1500))[1] < 60); assert.ok((await pixel(output.bytes, 1100, 1585))[1] > 240); }); test('图片水印允许 10px 最小尺寸', async () => { const source = await sharp({ create: { width: 240, height: 320, channels: 3, background: '#ffffff' } }).png().toBuffer(); const mark = await sharp({ create: { width: 20, height: 20, channels: 4, background: '#ff0000ff' } }).png().toBuffer(); const output = await renderWatermark(source, { ...template, image_size_px: 10 }, mark); assert.ok((await pixel(output.bytes, 227, 307))[1] < 80); assert.ok((await pixel(output.bytes, 217, 297))[1] > 220); }); test('圆角使图片水印角落保持底图颜色', async () => { const source = await sharp({ create: { width: 120, height: 100, channels: 3, background: '#ffffff' } }).png().toBuffer(); const mark = await sharp({ create: { width: 40, height: 40, channels: 4, background: '#ff0000ff' } }).png().toBuffer(); const output = await renderWatermark(source, { ...template, border_radius_px: 18 }, mark); assert.ok((await pixel(output.bytes, 76, 56))[1] > 200); assert.ok((await pixel(output.bytes, 95, 75))[1] < 60); }); test('文字水印支持 XML 特殊字符,不会把内容当成 SVG', async () => { const source = await sharp({ create: { width: 200, height: 100, channels: 3, background: '#ffffff' } }).png().toBuffer(); const output = await renderWatermark(source, { ...template, kind: 'text', text_content: '', font_code: 'NotoSans', font_size_px: 24, position_x: 0, position_y: 0, }); const { data } = await sharp(output.bytes).raw().toBuffer({ resolveWithObject: true }); assert.ok(data.some((value) => value < 100)); });