Skip to content

Commit 0011de0

Browse files
bug: image rescaling fix (#1292)
* image rescaling fix * implemented copilot suggestions --------- Co-authored-by: Mario Behling <mb@mariobehling.de>
1 parent 67d2ac5 commit 0011de0

File tree

9 files changed

+108
-17
lines changed

9 files changed

+108
-17
lines changed

app/eventyay/webapp/src/components/profile/ChangeAvatar.vue

Lines changed: 84 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ import api from 'lib/api'
2121
import Identicon from 'components/Identicon'
2222
import UploadButton from 'components/UploadButton'
2323
24-
const MAX_AVATAR_SIZE = 128
24+
const MIN_AVATAR_SIZE = 128
25+
const UPLOAD_AVATAR_SIZE = 256
2526
const MAX_FILE_SIZE_MB = 10
2627
const MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024
2728
const ALLOWED_FORMATS = ['image/png', 'image/jpg', 'image/jpeg']
@@ -42,6 +43,7 @@ const avatarImage = ref(null)
4243
const fileError = ref(null)
4344
const changedImage = ref(false)
4445
const cropperRef = ref(null)
46+
const selectedFileSize = ref(null)
4547
4648
// Derived
4749
const identiconUser = computed(() => ({
@@ -55,16 +57,19 @@ const identiconUser = computed(() => ({
5557
function changeIdenticon() {
5658
fileError.value = null
5759
avatarImage.value = null
60+
selectedFileSize.value = null
5861
identiconValue.value = uuid()
5962
emit('blockSave', false)
6063
}
6164
6265
function fileSelected(event) {
6366
fileError.value = null
6467
avatarImage.value = null
68+
selectedFileSize.value = null
6569
emit('blockSave', false)
6670
if (event.target.files.length !== 1) return
6771
const avatarFile = event.target.files[0]
72+
selectedFileSize.value = avatarFile.size
6873
6974
// Validate file size (10MB max)
7075
if (avatarFile.size > MAX_FILE_SIZE_BYTES) {
@@ -89,7 +94,7 @@ function fileSelected(event) {
8994
if (readerEvent.target.readyState !== FileReader.DONE) return
9095
const img = new Image()
9196
img.onload = () => {
92-
if (img.width < 128 || img.height < 128) {
97+
if (img.width < MIN_AVATAR_SIZE || img.height < MIN_AVATAR_SIZE) {
9398
fileError.value = proxy.$t('profile/ChangeAvatar:error:image-too-small')
9499
emit('blockSave', true)
95100
} else {
@@ -105,29 +110,91 @@ function fileSelected(event) {
105110
106111
function pixelsRestrictions({ minWidth, minHeight, maxWidth, maxHeight }) {
107112
return {
108-
minWidth: Math.max(128, minWidth),
109-
minHeight: Math.max(128, minHeight),
113+
minWidth: Math.max(MIN_AVATAR_SIZE, minWidth),
114+
minHeight: Math.max(MIN_AVATAR_SIZE, minHeight),
110115
maxWidth,
111116
maxHeight,
112117
}
113118
}
114119
115-
function update() {
116-
return new Promise((resolve) => {
117-
const { canvas } = cropperRef.value?.getResult() || {}
118-
if (!canvas) {
119-
emit('update:modelValue', { identicon: identiconValue.value })
120-
return resolve()
121-
}
122-
if (!changedImage.value) return resolve()
120+
async function update() {
121+
const { canvas } = cropperRef.value?.getResult() || {}
122+
if (!canvas) {
123+
emit('update:modelValue', { identicon: identiconValue.value })
124+
return
125+
}
126+
if (!changedImage.value) return
127+
128+
const processed = await createAvatarBlob(canvas)
129+
if (!processed) {
130+
fileError.value = proxy.$t('profile/ChangeAvatar:error:process-failed')
131+
emit('blockSave', true)
132+
return
133+
}
134+
const { blob: resizedBlob, dimension } = processed
135+
if (ENV_DEVELOPMENT) {
136+
console.info('[avatar-upload] original size:', selectedFileSize.value || 0, 'bytes; upload size:', resizedBlob.size, 'bytes; dimension:', dimension + 'px')
137+
}
123138
124-
canvas.toBlob((blob) => {
125-
const request = api.uploadFile(blob, 'avatar.png', null, MAX_AVATAR_SIZE, MAX_AVATAR_SIZE)
126-
request.addEventListener('load', () => {
127-
const response = JSON.parse(request.responseText)
139+
await new Promise((resolve) => {
140+
const request = api.uploadFile(resizedBlob, 'avatar.png', null, dimension, dimension)
141+
const handleFailure = (status, responseText) => {
142+
let message = proxy.$t('profile/ChangeAvatar:error:upload-failed')
143+
if (status === 413) {
144+
message = proxy.$t('profile/ChangeAvatar:error:file-too-large')
145+
}
146+
console.error('[avatar-upload]', status, responseText)
147+
fileError.value = message
148+
emit('blockSave', true)
149+
resolve()
150+
}
151+
request.addEventListener('load', () => {
152+
const status = request.status
153+
const responseText = request.responseText || ''
154+
const contentType = request.getResponseHeader('content-type') || ''
155+
if (status < 200 || status >= 300 || !contentType.includes('application/json')) {
156+
return handleFailure(status, responseText)
157+
}
158+
try {
159+
const response = JSON.parse(responseText)
128160
emit('update:modelValue', { url: response.url })
161+
emit('blockSave', false)
129162
resolve()
130-
})
163+
} catch (error) {
164+
return handleFailure(status, responseText)
165+
}
166+
})
167+
request.addEventListener('error', () => {
168+
handleFailure(request.status, request.responseText)
169+
})
170+
})
171+
}
172+
173+
function createAvatarBlob(sourceCanvas) {
174+
return new Promise((resolve) => {
175+
const sourceSize = Math.min(sourceCanvas.width, sourceCanvas.height)
176+
const targetSize = Math.min(UPLOAD_AVATAR_SIZE, Math.max(MIN_AVATAR_SIZE, sourceSize))
177+
const canvas = document.createElement('canvas')
178+
canvas.width = targetSize
179+
canvas.height = targetSize
180+
const ctx = canvas.getContext('2d')
181+
if (!ctx) return resolve(null)
182+
ctx.imageSmoothingEnabled = true
183+
ctx.imageSmoothingQuality = 'high'
184+
ctx.drawImage(
185+
sourceCanvas,
186+
0,
187+
0,
188+
sourceCanvas.width,
189+
sourceCanvas.height,
190+
0,
191+
0,
192+
targetSize,
193+
targetSize
194+
)
195+
canvas.toBlob(blob => {
196+
if (!blob) return resolve(null)
197+
resolve({ blob, dimension: targetSize })
131198
}, 'image/png')
132199
})
133200
}

app/eventyay/webapp/src/locales/ar.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,9 @@
260260
"profile/ChangeAvatar:or": "أو",
261261
"profile/ChangeAvatar:button-upload:label": "رفع",
262262
"profile/ChangeAvatar:error:image-too-small": "يجب أن يكون حجم الصورة على الأقل 128 بكسل × 128 بكسل.",
263+
"profile/ChangeAvatar:error:process-failed": "تعذر معالجة الصورة. يرجى تجربة ملف آخر.",
264+
"profile/ChangeAvatar:error:upload-failed": "فشل رفع الصورة الرمزية. يرجى المحاولة مرة أخرى.",
265+
"profile/ChangeAvatar:error:file-too-large": "الصورة المرفوعة كبيرة جدًا. يرجى اختيار ملف أصغر.",
263266
"profile/ConnectGravatar:gravatar-connect:label": "اتصال",
264267
"profile/ConnectGravatar:gravatar-email:label": "عنوان البريد الإلكتروني للغرفاتار",
265268
"profile/ConnectGravatar:headline": "إحضار من الغرفاتار",

app/eventyay/webapp/src/locales/de.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,9 @@
258258
"profile/ChangeAvatar:or": "oder",
259259
"profile/ChangeAvatar:button-upload:label": "hochladen",
260260
"profile/ChangeAvatar:error:image-too-small": "Das Bild muss mindestens 128px mal 128px groß sein.",
261+
"profile/ChangeAvatar:error:process-failed": "Bild konnte nicht verarbeitet werden. Bitte versuchen Sie eine andere Datei.",
262+
"profile/ChangeAvatar:error:upload-failed": "Avatar konnte nicht hochgeladen werden. Bitte versuchen Sie es erneut.",
263+
"profile/ChangeAvatar:error:file-too-large": "Das hochgeladene Bild ist zu groß. Bitte wählen Sie eine kleinere Datei.",
261264
"profile/ConnectGravatar:gravatar-connect:label": "Verbinden",
262265
"profile/ConnectGravatar:gravatar-email:label": "Gravatar-E-Mail-Adresse",
263266
"profile/ConnectGravatar:headline": "Von Gravatar abrufen",

app/eventyay/webapp/src/locales/en.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,9 @@
260260
"profile/ChangeAvatar:or": "or",
261261
"profile/ChangeAvatar:button-upload:label": "upload",
262262
"profile/ChangeAvatar:error:image-too-small": "Image must be at least 128px by 128px large.",
263+
"profile/ChangeAvatar:error:process-failed": "Failed to process image. Please try a different file.",
264+
"profile/ChangeAvatar:error:upload-failed": "Failed to upload avatar. Please try again.",
265+
"profile/ChangeAvatar:error:file-too-large": "Uploaded image is too large. Please choose a smaller file.",
263266
"profile/ConnectGravatar:gravatar-connect:label": "Connect",
264267
"profile/ConnectGravatar:gravatar-email:label": "Gravatar email address",
265268
"profile/ConnectGravatar:headline": "Fetch from gravatar",

app/eventyay/webapp/src/locales/es.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,9 @@
260260
"profile/ChangeAvatar:or": "o",
261261
"profile/ChangeAvatar:button-upload:label": "subir",
262262
"profile/ChangeAvatar:error:image-too-small": "La imagen debe tener al menos 128px por 128px.",
263+
"profile/ChangeAvatar:error:process-failed": "Error al procesar la imagen. Intenta con otro archivo.",
264+
"profile/ChangeAvatar:error:upload-failed": "No se pudo subir el avatar. Vuelve a intentarlo.",
265+
"profile/ChangeAvatar:error:file-too-large": "La imagen subida es demasiado grande. Elige un archivo más pequeño.",
263266
"profile/ConnectGravatar:gravatar-connect:label": "Conectar",
264267
"profile/ConnectGravatar:gravatar-email:label": "Correo electrónico de Gravatar",
265268
"profile/ConnectGravatar:headline": "Obtener de Gravatar",

app/eventyay/webapp/src/locales/fr.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,9 @@
260260
"profile/ChangeAvatar:or": "ou",
261261
"profile/ChangeAvatar:button-upload:label": "télécharger",
262262
"profile/ChangeAvatar:error:image-too-small": "L'image doit mesurer au moins 128px par 128px.",
263+
"profile/ChangeAvatar:error:process-failed": "Échec du traitement de l'image. Veuillez essayer un autre fichier.",
264+
"profile/ChangeAvatar:error:upload-failed": "Échec du téléversement de l'avatar. Veuillez réessayer.",
265+
"profile/ChangeAvatar:error:file-too-large": "L'image téléversée est trop grande. Veuillez choisir un fichier plus petit.",
263266
"profile/ConnectGravatar:gravatar-connect:label": "Connecter",
264267
"profile/ConnectGravatar:gravatar-email:label": "Adresse email Gravatar",
265268
"profile/ConnectGravatar:headline": "Obtenir de Gravatar",

app/eventyay/webapp/src/locales/pt_BR.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,9 @@
258258
"profile/ChangeAvatar:or": "ou",
259259
"profile/ChangeAvatar:button-upload:label": "carregar",
260260
"profile/ChangeAvatar:error:image-too-small": "A imagem deve ter pelo menos 128px por 128px.",
261+
"profile/ChangeAvatar:error:process-failed": "Não foi possível processar a imagem. Tente outro arquivo.",
262+
"profile/ChangeAvatar:error:upload-failed": "Não foi possível enviar o avatar. Tente novamente.",
263+
"profile/ChangeAvatar:error:file-too-large": "A imagem enviada é grande demais. Escolha um arquivo menor.",
261264
"profile/ConnectGravatar:gravatar-connect:label": "Conectar",
262265
"profile/ConnectGravatar:gravatar-email:label": "E-mail do Gravatar",
263266
"profile/ConnectGravatar:headline": "Obter do Gravatar",

app/eventyay/webapp/src/locales/ru.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,9 @@
260260
"profile/ChangeAvatar:or": "или",
261261
"profile/ChangeAvatar:button-upload:label": "загрузить",
262262
"profile/ChangeAvatar:error:image-too-small": "Изображение должно быть не менее 128 пикселей на 128 пикселей.",
263+
"profile/ChangeAvatar:error:process-failed": "Не удалось обработать изображение. Пожалуйста, попробуйте другой файл.",
264+
"profile/ChangeAvatar:error:upload-failed": "Не удалось загрузить аватар. Повторите попытку.",
265+
"profile/ChangeAvatar:error:file-too-large": "Загруженное изображение слишком большое. Пожалуйста, выберите файл меньшего размера.",
263266
"profile/ConnectGravatar:gravatar-connect:label": "Подключить",
264267
"profile/ConnectGravatar:gravatar-email:label": "Email Gravatar",
265268
"profile/ConnectGravatar:headline": "Получить из Gravatar",

app/eventyay/webapp/src/locales/uk.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,9 @@
260260
"profile/ChangeAvatar:or": "або",
261261
"profile/ChangeAvatar:button-upload:label": "завантажити",
262262
"profile/ChangeAvatar:error:image-too-small": "Зображення повинно бути щонайменше 128 пікселів на 128 пікселів.",
263+
"profile/ChangeAvatar:error:process-failed": "Не вдалося обробити зображення. Спробуйте інший файл.",
264+
"profile/ChangeAvatar:error:upload-failed": "Не вдалося завантажити аватар. Спробуйте ще раз.",
265+
"profile/ChangeAvatar:error:file-too-large": "Завантажене зображення занадто велике. Виберіть менший файл.",
263266
"profile/ConnectGravatar:gravatar-connect:label": "Підключити",
264267
"profile/ConnectGravatar:gravatar-email:label": "Email Gravatar",
265268
"profile/ConnectGravatar:headline": "Отримати з Gravatar",

0 commit comments

Comments
 (0)