@@ -7,6 +7,7 @@ import React, { useState } from 'react';
77import * as Yup from 'yup' ;
88import ToolContent from '@components/ToolContent' ;
99import { ToolComponentProps } from '@tools/defineTool' ;
10+ import heic2any from 'heic2any' ;
1011
1112const initialValues = {
1213 quality : 85 ,
@@ -18,6 +19,13 @@ const validationSchema = Yup.object({
1819 backgroundColor : Yup . string ( ) . required ( 'Background color is required' )
1920} ) ;
2021
22+ function isHeicLike ( file : File ) {
23+ if ( [ 'heic' , 'heif' ] . includes ( file . type ) ) return true ;
24+
25+ const name = file . name . toLowerCase ( ) ;
26+ return name . endsWith ( '.heic' ) || name . endsWith ( '.heif' ) ;
27+ }
28+
2129export default function ConvertToJpg ( { title } : ToolComponentProps ) {
2230 const [ input , setInput ] = useState < File | null > ( null ) ;
2331 const [ result , setResult ] = useState < File | null > ( null ) ;
@@ -33,14 +41,39 @@ export default function ConvertToJpg({ title }: ToolComponentProps) {
3341 quality : number ,
3442 backgroundColor : string
3543 ) => {
36- const canvas = document . createElement ( 'canvas' ) ;
37- const ctx = canvas . getContext ( '2d' ) ;
38- if ( ctx == null ) return ;
44+ try {
45+ let workingBlob : Blob = file ;
46+ let workingName = file . name ;
3947
40- const img = new Image ( ) ;
41- img . src = URL . createObjectURL ( file ) ;
48+ if ( isHeicLike ( file ) ) {
49+ try {
50+ const converted = await heic2any ( {
51+ blob : file ,
52+ toType : 'image/png' ,
53+ quality : 1
54+ } ) ;
55+ const blobOut = Array . isArray ( converted ) ? converted [ 0 ] : converted ;
56+
57+ workingBlob = blobOut as Blob ;
58+ workingName = file . name . replace ( / \. [ ^ / . ] + $ / , '' ) + '.png' ;
59+ const pngFile = new File ( [ workingBlob ] , workingName , {
60+ type : 'image/png'
61+ } ) ;
62+ setInput ( pngFile ) ;
63+ } catch ( e ) {
64+ console . error ( 'heic2any conversion failed:' , e ) ;
65+ throw e ;
66+ }
67+ }
68+
69+ const canvas = document . createElement ( 'canvas' ) ;
70+ const ctx = canvas . getContext ( '2d' ) ;
71+ if ( ! ctx ) return ;
72+
73+ const objectUrl = URL . createObjectURL ( workingBlob ) ;
74+ const img = new Image ( ) ;
75+ img . src = objectUrl ;
4276
43- try {
4477 await img . decode ( ) ;
4578
4679 canvas . width = img . width ;
@@ -51,34 +84,35 @@ export default function ConvertToJpg({ title }: ToolComponentProps) {
5184 try {
5285 //@ts -ignore
5386 bgColor = Color ( backgroundColor ) . rgb ( ) . array ( ) ;
54- } catch ( err ) {
87+ } catch {
5588 bgColor = [ 255 , 255 , 255 ] ; // Default to white
5689 }
57-
5890 ctx . fillStyle = `rgb(${ bgColor [ 0 ] } , ${ bgColor [ 1 ] } , ${ bgColor [ 2 ] } )` ;
5991 ctx . fillRect ( 0 , 0 , canvas . width , canvas . height ) ;
6092
61- // Draw the image on top
6293 ctx . drawImage ( img , 0 , 0 ) ;
6394
64- // Convert to JPG with specified quality
65- canvas . toBlob (
66- ( blob ) => {
67- if ( blob ) {
68- const fileName = file . name . replace ( / \. [ ^ / . ] + $ / , '' ) + '.jpg' ;
69- const newFile = new File ( [ blob ] , fileName , {
70- type : 'image/jpeg'
71- } ) ;
72- setResult ( newFile ) ;
73- }
74- } ,
75- 'image/jpeg' ,
76- quality / 100
77- ) ;
95+ await new Promise < void > ( ( resolve ) => {
96+ canvas . toBlob (
97+ ( blob ) => {
98+ if ( blob ) {
99+ const baseName = workingName . replace ( / \. [ ^ / . ] + $ / , '' ) ;
100+ const outName = baseName + '.jpg' ;
101+ const newFile = new File ( [ blob ] , outName , {
102+ type : 'image/jpeg'
103+ } ) ;
104+ setResult ( newFile ) ;
105+ }
106+ resolve ( ) ;
107+ } ,
108+ 'image/jpeg' ,
109+ quality / 100
110+ ) ;
111+ } ) ;
78112 } catch ( error ) {
79113 console . error ( 'Error processing image:' , error ) ;
80114 } finally {
81- URL . revokeObjectURL ( img . src ) ;
115+ URL . revokeObjectURL ( objectUrl ) ;
82116 }
83117 } ;
84118
0 commit comments