Skip to content

Instantly share code, notes, and snippets.

@kevinrodriguez-io
Last active January 20, 2020 04:26
Show Gist options
  • Save kevinrodriguez-io/ee4985be770e8deb89c2dfa82a8f4550 to your computer and use it in GitHub Desktop.
Save kevinrodriguez-io/ee4985be770e8deb89c2dfa82a8f4550 to your computer and use it in GitHub Desktop.
You can use the following hook to upload images, encode them and correct their orientation.
yarn add blueimp-load-image
import LoadImage from 'blueimp-load-image'
/**
* @param {File} file
*/
export const parseMetadata = file => {
return new Promise(resolve => {
LoadImage.parseMetaData(file, data => {
resolve(data)
})
})
}
/**
* @param {File} file
* @param {import('blueimp-load-image').LoadImageOptions} settings
* @returns {Promise<HTMLCanvasElement>}
*/
export const loadImage = (file, settings) => {
return new Promise(resolve => {
LoadImage(
file,
/** @param {HTMLCanvasElement} canvas */
canvas => resolve(canvas),
{
...settings,
},
)
})
}
/**
* @param {HTMLCanvasElement} canvas
* @param {string} type
* @param {number} quality
* @returns {Promise<Blob>}
* */
export const canvasToBlob = (canvas, type, quality) => {
return new Promise(resolve => {
canvas.toBlob(blob => resolve(blob), type, quality)
})
}
/**
* @param {Blob} blob
* @returns {Promise<{dataUrl: string | ArrayBuffer, base64: string}>}
*/
export const loadImageSrc = blob => {
return new Promise(resolve => {
const previewReader = new FileReader()
previewReader.onload = e => {
const dataUrl = e.target.result
// @ts-ignore
const [, base64] = dataUrl.split(',')
resolve({
dataUrl,
base64,
})
}
previewReader.readAsDataURL(blob)
})
}
import { useState, useCallback } from 'react'
import {
parseMetadata,
loadImage,
canvasToBlob,
loadImageSrc,
} from './promises'
/**
* The useImageSelector hook allows for the creation and parsing of blob images selected from
*
* @param {Object} settings - Hook settings
* @param {string} [settings.imageMimeType] - Mime Type to be used
* @param {boolean} [settings.fixOrientation] - Fixes the orientation automatically from camera pictures
* @param {number} [settings.quality] - Image quality
* @param {import('blueimp-load-image').LoadImageOptions} [settings.loadImageOptions] - Image loader options
* @returns {[boolean, any, Blob, String | ArrayBuffer, string, ({ target }: React.ChangeEvent<HTMLInputElement>) => Promise<void>]}
*/
const useImageSelector = ({
imageMimeType = 'image/jpeg',
quality = 70,
fixOrientation = true,
loadImageOptions = {
canvas: true,
maxWidth: 500,
},
} = {}) => {
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState(null)
const [blob, setBlob] = useState(null)
const [base64Contents, setBase64Contents] = useState(null)
const [imageSrc, setImageSrc] = useState(null)
const onImageFileSelected = useCallback(
/***
* @param {React.ChangeEvent<HTMLInputElement>} event
* */
async ({ target }) => {
setIsLoading(true)
try {
const file = target.files.item(0)
const meta = await parseMetadata(file)
const options = fixOrientation
? {
orientation: meta.exif ? meta.exif.get('Orientation') : 0,
...loadImageOptions,
}
: loadImageOptions
const canvas = await loadImage(file, options)
const blob = await canvasToBlob(canvas, imageMimeType, quality)
const { dataUrl, base64 } = await loadImageSrc(blob)
setBlob(blob)
setImageSrc(dataUrl)
setBase64Contents(base64)
} catch (error) {
setError(error)
} finally {
setIsLoading(false)
}
},
[fixOrientation, imageMimeType, loadImageOptions, quality],
)
return [isLoading, error, blob, base64Contents, imageSrc, onImageFileSelected]
}
export default useImageSelector
import React from 'react'
import useImageSelector from 'useImageSelector'
const IMAGE_SELECTOR_SETTINGS = Object.freeze({
imageMimeType: 'image/png',
quality: 70,
fixOrientation: true,
loadImageOptions: Object.freeze({
canvas: true,
maxWidth: 500,
}),
})
const MyComponent = () => {
const [
isLoadingImage,
imageError,
blob, // Binary Format
base64Contents, // Raw Base64 contents
imageSrc, // Image source to be used in HTML
onImageFileSelected, // Callback to be used by a type="file" input
] = useImageSelector(IMAGE_SELECTOR_SETTINGS)
return (
<div>
{imageSrc && (
<section className="previewer-container">
<img className="previewer-container__image" src={imageSrc} />
</section>
)}
{imageError && <pre>{JSON.stringify({ imageError }, null, 2)}</pre>}
<input
style={{
position: 'absolute',
display: 'none',
opacity: 0;
}}
id="inputImage"
type="file"
accept="image/*"
onChange={onImageFileSelected}
/>
<label htmlFor="inputImage">
Upload an Image
</label>
</div>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment