Skip to content

Commit 8534a8b

Browse files
committed
add heic/heif support on convert-to-jpg
1 parent d22b7e5 commit 8534a8b

File tree

1 file changed

+58
-24
lines changed
  • src/pages/tools/image/generic/convert-to-jpg

1 file changed

+58
-24
lines changed

src/pages/tools/image/generic/convert-to-jpg/index.tsx

Lines changed: 58 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import React, { useState } from 'react';
77
import * as Yup from 'yup';
88
import ToolContent from '@components/ToolContent';
99
import { ToolComponentProps } from '@tools/defineTool';
10+
import heic2any from 'heic2any';
1011

1112
const 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+
2129
export 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

Comments
 (0)