@@ -359,14 +359,15 @@ async function resizeImageToDataURL(
359359 img : HTMLImageElement ,
360360 width : number ,
361361 height : number ,
362- mimeType = 'image/jpeg'
362+ mimeType = 'image/jpeg' ,
363+ quality ?: number
363364) : Promise < string > {
364365 const canvas = document . createElement ( 'canvas' ) ;
365366 canvas . width = width ;
366367 canvas . height = height ;
367368 canvas . getContext ( '2d' ) ?. drawImage ( img , 0 , 0 , width , height ) ;
368369
369- const toDataURL = ( ) => canvas . toDataURL ( mimeType ) ;
370+ const toDataURL = ( ) => canvas . toDataURL ( mimeType , quality ) ;
370371
371372 if (
372373 ! resizeImageWarmupDone &&
@@ -375,74 +376,84 @@ async function resizeImageToDataURL(
375376 ) {
376377 resizeImageWarmupDone = true ;
377378 return new Promise ( ( resolve ) => {
378- canvas . toBlob ( ( blob ) => {
379- if ( ! blob ) {
380- resolve ( toDataURL ( ) ) ;
381- return ;
382- }
383- const reader = new FileReader ( ) ;
384- reader . onload = ( ) => resolve ( String ( reader . result ) ) ;
385- reader . onerror = ( ) => resolve ( toDataURL ( ) ) ;
386- reader . readAsDataURL ( blob ) ;
387- } , mimeType ) ;
379+ canvas . toBlob (
380+ ( blob ) => {
381+ if ( ! blob ) {
382+ resolve ( toDataURL ( ) ) ;
383+ return ;
384+ }
385+ const reader = new FileReader ( ) ;
386+ reader . onload = ( ) => resolve ( String ( reader . result ) ) ;
387+ reader . onerror = ( ) => resolve ( toDataURL ( ) ) ;
388+ reader . readAsDataURL ( blob ) ;
389+ } ,
390+ mimeType ,
391+ quality
392+ ) ;
388393 } ) ;
389394 }
390395 return Promise . resolve ( toDataURL ( ) ) ;
391396}
392397
393- export const compressImage = async ( imageUrl , maxWidth , maxHeight ) => {
398+ /**
399+ * Calculate constrained dimensions preserving aspect ratio (pure function).
400+ */
401+ function calcResize (
402+ srcW : number ,
403+ srcH : number ,
404+ maxW ?: number | null ,
405+ maxH ?: number | null
406+ ) : { width : number ; height : number ; resized : boolean } {
407+ let w = srcW ,
408+ h = srcH ;
409+ if ( maxW && maxH ) {
410+ if ( w > maxW || h > maxH ) {
411+ if ( w / h > maxW / maxH ) {
412+ h = Math . round ( ( maxW * h ) / w ) ;
413+ w = maxW ;
414+ } else {
415+ w = Math . round ( ( maxH * w ) / h ) ;
416+ h = maxH ;
417+ }
418+ return { width : w , height : h , resized : true } ;
419+ }
420+ } else if ( maxW && w > maxW ) {
421+ h = Math . round ( ( maxW * h ) / w ) ;
422+ w = maxW ;
423+ return { width : w , height : h , resized : true } ;
424+ } else if ( maxH && h > maxH ) {
425+ w = Math . round ( ( maxH * w ) / h ) ;
426+ h = maxH ;
427+ return { width : w , height : h , resized : true } ;
428+ }
429+ return { width : w , height : h , resized : false } ;
430+ }
431+
432+ export const compressImage = async (
433+ imageUrl : string ,
434+ maxWidth ?: number | null ,
435+ maxHeight ?: number | null ,
436+ outputFormat ?: string ,
437+ quality ?: number
438+ ) => {
394439 return new Promise ( ( resolve , reject ) => {
395440 const img = new Image ( ) ;
396441 img . onload = async ( ) => {
397- let width = img . width ;
398- let height = img . height ;
442+ const { width, height, resized } = calcResize ( img . width , img . height , maxWidth , maxHeight ) ;
399443
400- // Maintain aspect ratio while resizing
401-
402- if ( maxWidth && maxHeight ) {
403- // Resize with both dimensions defined (preserves aspect ratio)
404-
405- if ( width <= maxWidth && height <= maxHeight ) {
406- resolve ( imageUrl ) ;
407- return ;
408- }
409-
410- if ( width / height > maxWidth / maxHeight ) {
411- height = Math . round ( ( maxWidth * height ) / width ) ;
412- width = maxWidth ;
413- } else {
414- width = Math . round ( ( maxHeight * width ) / height ) ;
415- height = maxHeight ;
416- }
417- } else if ( maxWidth ) {
418- // Only maxWidth defined
419-
420- if ( width <= maxWidth ) {
421- resolve ( imageUrl ) ;
422- return ;
423- }
424-
425- height = Math . round ( ( maxWidth * height ) / width ) ;
426- width = maxWidth ;
427- } else if ( maxHeight ) {
428- // Only maxHeight defined
429-
430- if ( height <= maxHeight ) {
431- resolve ( imageUrl ) ;
432- return ;
433- }
434-
435- width = Math . round ( ( maxHeight * width ) / height ) ;
436- height = maxHeight ;
444+ if ( ! resized && ! outputFormat ) {
445+ resolve ( imageUrl ) ;
446+ return ;
437447 }
438448
439- const mimeType = imageUrl . match ( / ^ d a t a : ( [ ^ ; ] + ) ; / ) ?. [ 1 ] ?? 'image/jpeg' ;
440- resolve ( await resizeImageToDataURL ( img , width , height , mimeType ) ) ;
449+ const mimeType = outputFormat || ( imageUrl . match ( / ^ d a t a : ( [ ^ ; ] + ) ; / ) ?. [ 1 ] ?? 'image/jpeg' ) ;
450+ resolve ( await resizeImageToDataURL ( img , width , height , mimeType , quality ) ) ;
441451 } ;
442452 img . onerror = ( error ) => reject ( error ) ;
443453 img . src = imageUrl ;
444454 } ) ;
445455} ;
456+
446457export const generateInitialsImage = ( name ) => {
447458 const canvas = document . createElement ( 'canvas' ) ;
448459 const ctx = canvas . getContext ( '2d' ) ;
0 commit comments