u
This commit is contained in:
@@ -5,6 +5,7 @@ const PIMAGE_DEFAULT_W = 200;
|
||||
const PIMAGE_DEFAULT_H = 200;
|
||||
const CIMAGE_DEFAULT_W = 200;
|
||||
const CIMAGE_DEFAULT_H = 200;
|
||||
const CIMAGE_MAX_DIMENSION = 200;
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
@@ -106,7 +107,7 @@ export async function handlePimage(req, res) {
|
||||
* tags: [POS]
|
||||
* summary: POS category image delivery
|
||||
* description: |
|
||||
* Returns category image by hash (`path` query param) scaled to preview size (padded to 1:1 with adaptive aspect-ratio borders: thinner borders for vertical images, wider borders for horizontal images).
|
||||
* Returns category image by hash (`path` query param) scaled to preview size (padded to 1:1 with white background).
|
||||
* x-markdown: pos/cimage.md
|
||||
* parameters:
|
||||
* - in: query
|
||||
@@ -122,21 +123,24 @@ export async function handlePimage(req, res) {
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 200
|
||||
* description: Max dimension (width & height)
|
||||
* maximum: 200
|
||||
* description: Max dimension (width & height, capped to 200)
|
||||
* - in: query
|
||||
* name: w
|
||||
* required: false
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 200
|
||||
* description: Max width
|
||||
* maximum: 200
|
||||
* description: Max width (capped to 200)
|
||||
* - in: query
|
||||
* name: h
|
||||
* required: false
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 200
|
||||
* description: Max height
|
||||
* maximum: 200
|
||||
* description: Max height (capped to 200)
|
||||
* - in: query
|
||||
* name: format
|
||||
* required: false
|
||||
@@ -207,16 +211,16 @@ async function servePosImage(req, res, { isCimage = false } = {}) {
|
||||
|
||||
if (isCimage) {
|
||||
if (hasW && !hasH) {
|
||||
targetW = Math.min(Math.round(Number(req.query.w)), 2000);
|
||||
targetW = Math.min(Math.round(Number(req.query.w)), CIMAGE_MAX_DIMENSION);
|
||||
targetH = targetW;
|
||||
} else if (hasH && !hasW) {
|
||||
targetH = Math.min(Math.round(Number(req.query.h)), 2000);
|
||||
targetH = Math.min(Math.round(Number(req.query.h)), CIMAGE_MAX_DIMENSION);
|
||||
targetW = targetH;
|
||||
} else if (hasW && hasH) {
|
||||
targetW = Math.min(Math.round(Number(req.query.w)), 2000);
|
||||
targetH = Math.min(Math.round(Number(req.query.h)), 2000);
|
||||
targetW = Math.min(Math.round(Number(req.query.w)), CIMAGE_MAX_DIMENSION);
|
||||
targetH = Math.min(Math.round(Number(req.query.h)), CIMAGE_MAX_DIMENSION);
|
||||
} else if (hasSize) {
|
||||
const s = Math.min(Math.round(Number(req.query.size)), 2000);
|
||||
const s = Math.min(Math.round(Number(req.query.size)), CIMAGE_MAX_DIMENSION);
|
||||
targetW = s;
|
||||
targetH = s;
|
||||
} else {
|
||||
@@ -238,7 +242,7 @@ async function servePosImage(req, res, { isCimage = false } = {}) {
|
||||
return res.status(404).json({ Message: `No image was found for path '${rawPath}'.` });
|
||||
}
|
||||
|
||||
const etag = `"${image.fullHash}-${targetW}x${targetH}${isCimage ? '-c-v5' : ''}"`;
|
||||
const etag = `"${image.fullHash}-${targetW}x${targetH}${isCimage ? '-c-v6' : ''}"`;
|
||||
const ifNoneMatch = req.headers['if-none-match'];
|
||||
if (ifNoneMatch === etag || ifNoneMatch === image.fullHash || ifNoneMatch === `"${image.fullHash}"`) {
|
||||
res.setHeader('ETag', etag);
|
||||
@@ -248,53 +252,20 @@ async function servePosImage(req, res, { isCimage = false } = {}) {
|
||||
|
||||
const imageSharp = sharp(image.buffer);
|
||||
|
||||
let pipeline;
|
||||
if (isCimage) {
|
||||
const meta = await imageSharp.metadata();
|
||||
const origW = meta.width || targetW;
|
||||
const origH = meta.height || targetH;
|
||||
const r = origW / origH;
|
||||
|
||||
// Adaptive fill ratio:
|
||||
// Vertical images (r < 1) get much less border (height fills up to ~94% of tile).
|
||||
// Horizontal images (r > 1) get a bit more border (width fills down to ~65% of tile).
|
||||
// Square images (r = 1) get ~75% fill (~12.5% border on all sides).
|
||||
let fillRatio;
|
||||
if (r < 1.0) {
|
||||
const t = Math.max(0, Math.min(1, (1.0 - r) / 0.5));
|
||||
fillRatio = 0.75 + t * (0.94 - 0.75);
|
||||
} else {
|
||||
const t = Math.max(0, Math.min(1, (r - 1.0) / 0.8));
|
||||
fillRatio = 0.75 - t * (0.75 - 0.65);
|
||||
const resizeOptions = isCimage
|
||||
? {
|
||||
fit: 'contain',
|
||||
background: '#ffffff',
|
||||
withoutEnlargement: true,
|
||||
}
|
||||
: {
|
||||
fit: 'inside',
|
||||
withoutEnlargement: true,
|
||||
};
|
||||
|
||||
const innerW = Math.max(1, Math.round(targetW * fillRatio));
|
||||
const innerH = Math.max(1, Math.round(targetH * fillRatio));
|
||||
|
||||
const intermediateBuffer = await imageSharp
|
||||
.resize(innerW, innerH, {
|
||||
fit: 'contain',
|
||||
background: '#ffffff',
|
||||
withoutEnlargement: true,
|
||||
})
|
||||
.flatten({ background: '#ffffff' })
|
||||
.toBuffer();
|
||||
|
||||
pipeline = sharp(intermediateBuffer)
|
||||
.resize(targetW, targetH, {
|
||||
fit: 'contain',
|
||||
background: '#ffffff',
|
||||
withoutEnlargement: true,
|
||||
})
|
||||
.flatten({ background: '#ffffff' });
|
||||
} else {
|
||||
pipeline = imageSharp
|
||||
.resize(targetW, targetH, {
|
||||
fit: 'inside',
|
||||
withoutEnlargement: true,
|
||||
})
|
||||
.flatten({ background: '#ffffff' });
|
||||
}
|
||||
let pipeline = imageSharp
|
||||
.resize(targetW, targetH, resizeOptions)
|
||||
.flatten({ background: '#ffffff' });
|
||||
|
||||
const accept = String(req.headers.accept || '');
|
||||
const formatParam = typeof req.query.format === 'string' ? req.query.format.toLowerCase() : '';
|
||||
|
||||
Reference in New Issue
Block a user