Fixed minor avatar cropping bug in avatars.js (#4192) * Fixed avatar cropping bug in avatars.js Persona icons/profile pictures were forced to a 2:3 ratio despite "Never resize avatars" being enabled. Moved a misplaced line of code inside the crop condition to fix this behavior. * Fix indentation for ESLint compliance **Attempted** to correct indentation on line 49 of avatars.js to pass ESLint check. * Update avatars.js Gave my best to fix it :( At the very least the issue is known now. * Update avatars.js Removed empty characters (formatting) * Fixed avatars.js Fixed the issue of avatar's not changing after my recent revision. * Update avatars.js Accidentally left a duplicate line; removed it now * Extract reusable function for avatar resize --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
Signed| @@ -3,12 +3,12 @@ import fs from 'node:fs'; | |||
| 3 | 3 | ||
| 4 | import express from 'express'; | 4 | import express from 'express'; |
| 5 | import sanitize from 'sanitize-filename'; | 5 | import sanitize from 'sanitize-filename'; |
| 6 | import { Jimp, JimpMime } from '../jimp.js'; | 6 | import { Jimp } from '../jimp.js'; |
| 7 | import { sync as writeFileAtomicSync } from 'write-file-atomic'; | 7 | import { sync as writeFileAtomicSync } from 'write-file-atomic'; |
| 8 | 8 | ||
| 9 | import { AVATAR_WIDTH, AVATAR_HEIGHT } from '../constants.js'; | ||
| 10 | import { getImages, tryParse } from '../util.js'; | 9 | import { getImages, tryParse } from '../util.js'; |
| 11 | import { getFileNameValidationFunction } from '../middleware/validateFileName.js'; | 10 | import { getFileNameValidationFunction } from '../middleware/validateFileName.js'; |
| 11 | import { applyAvatarCropResize } from './characters.js'; | ||
| 12 | 12 | ||
| 13 | export const router = express.Router(); | 13 | export const router = express.Router(); |
| 14 | 14 | ||
| @@ -42,13 +42,7 @@ router.post('/upload', getFileNameValidationFunction('overwrite_name'), async (r | |||
| 42 | const pathToUpload = path.join(request.file.destination, request.file.filename); | 42 | const pathToUpload = path.join(request.file.destination, request.file.filename); |
| 43 | const crop = tryParse(request.query.crop); | 43 | const crop = tryParse(request.query.crop); |
| 44 | const rawImg = await Jimp.read(pathToUpload); | 44 | const rawImg = await Jimp.read(pathToUpload); |
| 45 | 45 | const image = await applyAvatarCropResize(rawImg, crop); | |
| 46 | if (typeof crop == 'object' && [crop.x, crop.y, crop.width, crop.height].every(x => typeof x === 'number')) { | ||
| 47 | rawImg.crop({ w: crop.width, h: crop.height, x: crop.x, y: crop.y }); | ||
| 48 | } | ||
| 49 | |||
| 50 | rawImg.cover({ w: AVATAR_WIDTH, h: AVATAR_HEIGHT }); | ||
| 51 | const image = await rawImg.getBuffer(JimpMime.png); | ||
| 52 | 46 | ||
| 53 | const filename = request.body.overwrite_name || `${Date.now()}.png`; | 47 | const filename = request.body.overwrite_name || `${Date.now()}.png`; |
| 54 | const pathToNewFile = path.join(request.user.directories.avatars, filename); | 48 | const pathToNewFile = path.join(request.user.directories.avatars, filename); |
| @@ -270,13 +270,14 @@ async function writeCharacterData(inputFile, data, outputFile, request, crop = u | |||
| 270 | */ | 270 | */ |
| 271 | 271 | ||
| 272 | /** | 272 | /** |
| 273 | * Parses an image buffer and applies crop if defined. | 273 | * Applies avatar crop and resize operations to an image. |
| 274 | * @param {Buffer} buffer Buffer of the image | 274 | * I couldn't fix the type issue, so the first argument has {any} type. |
| 275 | * @param {object} jimp Jimp image instance | ||
| 275 | * @param {Crop|undefined} [crop] Crop parameters | 276 | * @param {Crop|undefined} [crop] Crop parameters |
| 276 | * @returns {Promise<Buffer>} Image buffer | 277 | * @returns {Promise<Buffer>} Processed image buffer |
| 277 | */ | 278 | */ |
| 278 | async function parseImageBuffer(buffer, crop) { | 279 | export async function applyAvatarCropResize(jimp, crop) { |
| 279 | const image = await Jimp.fromBuffer(buffer); | 280 | const image = /** @type {InstanceType<typeof import('../jimp.js').Jimp>} */ (jimp); |
| 280 | let finalWidth = image.bitmap.width, finalHeight = image.bitmap.height; | 281 | let finalWidth = image.bitmap.width, finalHeight = image.bitmap.height; |
| 281 | 282 | ||
| 282 | // Apply crop if defined | 283 | // Apply crop if defined |
| @@ -297,6 +298,17 @@ async function parseImageBuffer(buffer, crop) { | |||
| 297 | } | 298 | } |
| 298 | 299 | ||
| 299 | /** | 300 | /** |
| 301 | * Parses an image buffer and applies crop if defined. | ||
| 302 | * @param {Buffer} buffer Buffer of the image | ||
| 303 | * @param {Crop|undefined} [crop] Crop parameters | ||
| 304 | * @returns {Promise<Buffer>} Image buffer | ||
| 305 | */ | ||
| 306 | async function parseImageBuffer(buffer, crop) { | ||
| 307 | const image = await Jimp.fromBuffer(buffer); | ||
| 308 | return await applyAvatarCropResize(image, crop); | ||
| 309 | } | ||
| 310 | |||
| 311 | /** | ||
| 300 | * Reads an image file and applies crop if defined. | 312 | * Reads an image file and applies crop if defined. |
| 301 | * @param {string} imgPath Path to the image file | 313 | * @param {string} imgPath Path to the image file |
| 302 | * @param {Crop|undefined} crop Crop parameters | 314 | * @param {Crop|undefined} crop Crop parameters |
| @@ -305,23 +317,7 @@ async function parseImageBuffer(buffer, crop) { | |||
| 305 | async function tryReadImage(imgPath, crop) { | 317 | async function tryReadImage(imgPath, crop) { |
| 306 | try { | 318 | try { |
| 307 | const rawImg = await Jimp.read(imgPath); | 319 | const rawImg = await Jimp.read(imgPath); |
| 308 | let finalWidth = rawImg.bitmap.width, finalHeight = rawImg.bitmap.height; | 320 | return await applyAvatarCropResize(rawImg, crop); |
| 309 | |||
| 310 | // Apply crop if defined | ||
| 311 | if (typeof crop == 'object' && [crop.x, crop.y, crop.width, crop.height].every(x => typeof x === 'number')) { | ||
| 312 | rawImg.crop({ x: crop.x, y: crop.y, w: crop.width, h: crop.height }); | ||
| 313 | // Apply standard resize if requested | ||
| 314 | if (crop.want_resize) { | ||
| 315 | finalWidth = AVATAR_WIDTH; | ||
| 316 | finalHeight = AVATAR_HEIGHT; | ||
| 317 | } else { | ||
| 318 | finalWidth = crop.width; | ||
| 319 | finalHeight = crop.height; | ||
| 320 | } | ||
| 321 | } | ||
| 322 | |||
| 323 | rawImg.cover({ w: finalWidth, h: finalHeight }); | ||
| 324 | return await rawImg.getBuffer(JimpMime.png); | ||
| 325 | } | 321 | } |
| 326 | // If it's an unsupported type of image (APNG) - just read the file as buffer | 322 | // If it's an unsupported type of image (APNG) - just read the file as buffer |
| 327 | catch (error) { | 323 | catch (error) { |