Last active
March 12, 2024 17:27
-
-
Save deviationist/bc5f190922b03e35535ddc68a70877ea to your computer and use it in GitHub Desktop.
A simple HEIC to JPEG-implementation for Node using libheif-js (https://www.npmjs.com/package/libheif-js) and canvas (https://www.npmjs.com/package/canvas). Not type safe, should be refined. Based of catdad-experiments/heic-convert.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import libheif from 'libheif-js'; | |
import { ImageData, Canvas, createCanvas } from 'canvas' | |
type Props = { | |
buffer: Buffer; | |
quality: number; | |
}; | |
const processSingleImage = (image: any): Promise<any> => { | |
return new Promise((resolve, reject) => { | |
const w = image.get_width(); | |
const h = image.get_height(); | |
const whiteImage: any = new ImageData(w, h); | |
for (let i = 0; i < w * h; i++) { | |
whiteImage.data[i * 4 + 3] = 255; | |
} | |
image.display(whiteImage, (imageData: any) => { | |
if (!imageData) { | |
return reject( | |
"ERR_LIBHEIF Error while processing single image and generating image data, could not ensure image" | |
); | |
} | |
resolve(imageData); | |
}); | |
}); | |
}; | |
const decodeBuffer = (buffer: Buffer): Promise<any> => { | |
return new Promise(async (resolve, reject) => { | |
const decoder = new libheif.HeifDecoder(); | |
let imagesArr = decoder.decode(buffer); | |
imagesArr = imagesArr.filter((x: any) => { | |
let valid = true; | |
try { | |
/* | |
sometimes the heic container is valid | |
yet the images themselves are corrupt | |
*/ | |
x.get_height(); | |
} catch (e) { | |
valid = false; | |
} | |
return valid; | |
}); | |
if (!imagesArr.length) { | |
reject('No valid images found'); | |
} | |
imagesArr = await Promise.all(imagesArr.map((image: libheif.DecodeResult) => processSingleImage(image))); | |
resolve(imagesArr); | |
}); | |
} | |
const imageDataToBlob = ({ | |
imageData, | |
quality = 0.92, | |
}: { | |
imageData: any; | |
toType?: string; | |
quality?: number; | |
}): Promise<Blob> => { | |
// normalize quality | |
if (quality > 1 || quality < 0) { | |
quality = 0.92; | |
} | |
return new Promise((resolve, reject) => { | |
let canvas: Canvas | null = null; | |
try { | |
canvas = createCanvas(imageData.width, imageData.height); | |
} catch (e) {} | |
if (!canvas) { | |
return reject( | |
"ERR_CANVAS Error on converting imagedata to blob: Could not create canvas element" | |
); | |
} | |
const ctx = canvas.getContext("2d"); | |
if (!ctx) { | |
return reject( | |
"ERR_CANVAS Error on converting imagedata to blob: Could not get canvas context" | |
); | |
} | |
ctx.putImageData(imageData as ImageData, 0, 0); | |
// Convert canvas to buffer, then to blob, then resolve | |
const buf = canvas.toBuffer('image/jpeg', { quality: quality }); | |
resolve(new Blob([buf], { type: 'image/jpeg' })); | |
}); | |
}; | |
export const heic2jpeg = ({ buffer, quality }: Props): Promise<Blob[]> => { | |
return new Promise(async (resolve, reject) => { | |
const imagesArr = await decodeBuffer(buffer); | |
const files = await Promise.all( | |
imagesArr.map((imageData: any) => | |
imageDataToBlob({ | |
imageData, | |
quality, | |
}) | |
) | |
); | |
resolve(files); | |
}); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment