@@ -363,14 +363,15 @@ async function resizeImageToDataURL(
363363 img : HTMLImageElement ,
364364 width : number ,
365365 height : number ,
366- mimeType = 'image/jpeg'
366+ mimeType = 'image/jpeg' ,
367+ quality ?: number
367368) : Promise < string > {
368369 const canvas = document . createElement ( 'canvas' ) ;
369370 canvas . width = width ;
370371 canvas . height = height ;
371372 canvas . getContext ( '2d' ) ?. drawImage ( img , 0 , 0 , width , height ) ;
372373
373- const toDataURL = ( ) => canvas . toDataURL ( mimeType ) ;
374+ const toDataURL = ( ) => canvas . toDataURL ( mimeType , quality ) ;
374375
375376 if (
376377 ! resizeImageWarmupDone &&
@@ -379,74 +380,84 @@ async function resizeImageToDataURL(
379380 ) {
380381 resizeImageWarmupDone = true ;
381382 return new Promise ( ( resolve ) => {
382- canvas . toBlob ( ( blob ) => {
383- if ( ! blob ) {
384- resolve ( toDataURL ( ) ) ;
385- return ;
386- }
387- const reader = new FileReader ( ) ;
388- reader . onload = ( ) => resolve ( String ( reader . result ) ) ;
389- reader . onerror = ( ) => resolve ( toDataURL ( ) ) ;
390- reader . readAsDataURL ( blob ) ;
391- } , mimeType ) ;
383+ canvas . toBlob (
384+ ( blob ) => {
385+ if ( ! blob ) {
386+ resolve ( toDataURL ( ) ) ;
387+ return ;
388+ }
389+ const reader = new FileReader ( ) ;
390+ reader . onload = ( ) => resolve ( String ( reader . result ) ) ;
391+ reader . onerror = ( ) => resolve ( toDataURL ( ) ) ;
392+ reader . readAsDataURL ( blob ) ;
393+ } ,
394+ mimeType ,
395+ quality
396+ ) ;
392397 } ) ;
393398 }
394399 return Promise . resolve ( toDataURL ( ) ) ;
395400}
396401
397- export const compressImage = async ( imageUrl , maxWidth , maxHeight ) => {
402+ /**
403+ * Calculate constrained dimensions preserving aspect ratio (pure function).
404+ */
405+ function calcResize (
406+ srcW : number ,
407+ srcH : number ,
408+ maxW ?: number | null ,
409+ maxH ?: number | null
410+ ) : { width : number ; height : number ; resized : boolean } {
411+ let w = srcW ,
412+ h = srcH ;
413+ if ( maxW && maxH ) {
414+ if ( w > maxW || h > maxH ) {
415+ if ( w / h > maxW / maxH ) {
416+ h = Math . round ( ( maxW * h ) / w ) ;
417+ w = maxW ;
418+ } else {
419+ w = Math . round ( ( maxH * w ) / h ) ;
420+ h = maxH ;
421+ }
422+ return { width : w , height : h , resized : true } ;
423+ }
424+ } else if ( maxW && w > maxW ) {
425+ h = Math . round ( ( maxW * h ) / w ) ;
426+ w = maxW ;
427+ return { width : w , height : h , resized : true } ;
428+ } else if ( maxH && h > maxH ) {
429+ w = Math . round ( ( maxH * w ) / h ) ;
430+ h = maxH ;
431+ return { width : w , height : h , resized : true } ;
432+ }
433+ return { width : w , height : h , resized : false } ;
434+ }
435+
436+ export const compressImage = async (
437+ imageUrl : string ,
438+ maxWidth ?: number | null ,
439+ maxHeight ?: number | null ,
440+ outputFormat ?: string ,
441+ quality ?: number
442+ ) => {
398443 return new Promise ( ( resolve , reject ) => {
399444 const img = new Image ( ) ;
400445 img . onload = async ( ) => {
401- let width = img . width ;
402- let height = img . height ;
403-
404- // Maintain aspect ratio while resizing
446+ const { width, height, resized } = calcResize ( img . width , img . height , maxWidth , maxHeight ) ;
405447
406- if ( maxWidth && maxHeight ) {
407- // Resize with both dimensions defined (preserves aspect ratio)
408-
409- if ( width <= maxWidth && height <= maxHeight ) {
410- resolve ( imageUrl ) ;
411- return ;
412- }
413-
414- if ( width / height > maxWidth / maxHeight ) {
415- height = Math . round ( ( maxWidth * height ) / width ) ;
416- width = maxWidth ;
417- } else {
418- width = Math . round ( ( maxHeight * width ) / height ) ;
419- height = maxHeight ;
420- }
421- } else if ( maxWidth ) {
422- // Only maxWidth defined
423-
424- if ( width <= maxWidth ) {
425- resolve ( imageUrl ) ;
426- return ;
427- }
428-
429- height = Math . round ( ( maxWidth * height ) / width ) ;
430- width = maxWidth ;
431- } else if ( maxHeight ) {
432- // Only maxHeight defined
433-
434- if ( height <= maxHeight ) {
435- resolve ( imageUrl ) ;
436- return ;
437- }
438-
439- width = Math . round ( ( maxHeight * width ) / height ) ;
440- height = maxHeight ;
448+ if ( ! resized && ! outputFormat ) {
449+ resolve ( imageUrl ) ;
450+ return ;
441451 }
442452
443- const mimeType = imageUrl . match ( / ^ d a t a : ( [ ^ ; ] + ) ; / ) ?. [ 1 ] ?? 'image/jpeg' ;
444- resolve ( await resizeImageToDataURL ( img , width , height , mimeType ) ) ;
453+ const mimeType = outputFormat || ( imageUrl . match ( / ^ d a t a : ( [ ^ ; ] + ) ; / ) ?. [ 1 ] ?? 'image/jpeg' ) ;
454+ resolve ( await resizeImageToDataURL ( img , width , height , mimeType , quality ) ) ;
445455 } ;
446456 img . onerror = ( error ) => reject ( error ) ;
447457 img . src = imageUrl ;
448458 } ) ;
449459} ;
460+
450461export const generateInitialsImage = ( name ) => {
451462 const canvas = document . createElement ( 'canvas' ) ;
452463 const ctx = canvas . getContext ( '2d' ) ;
0 commit comments