Changeset View
Changeset View
Standalone View
Standalone View
web/media/image-utils.js
// @flow | // @flow | ||||
import EXIF from 'exif-js'; | import EXIF from 'exif-js'; | ||||
import { rgbaToThumbHash } from 'thumbhash'; | |||||
import type { GetOrientationMediaMissionStep } from 'lib/types/media-types.js'; | import { hexToUintArray } from 'lib/media/data-utils.js'; | ||||
import type { | |||||
GetOrientationMediaMissionStep, | |||||
MediaMissionFailure, | |||||
MediaMissionStep, | |||||
} from 'lib/types/media-types.js'; | |||||
import { getMessageForException } from 'lib/utils/errors.js'; | import { getMessageForException } from 'lib/utils/errors.js'; | ||||
import * as AES from './aes-crypto-utils.js'; | |||||
import { preloadImage } from './media-utils.js'; | |||||
import { base64EncodeBuffer } from '../utils/base64-utils.js'; | |||||
function getEXIFOrientation(file: File): Promise<?number> { | function getEXIFOrientation(file: File): Promise<?number> { | ||||
return new Promise(resolve => { | return new Promise(resolve => { | ||||
EXIF.getData(file, function () { | EXIF.getData(file, function () { | ||||
resolve(EXIF.getTag(this, 'Orientation')); | resolve(EXIF.getTag(this, 'Orientation')); | ||||
}); | }); | ||||
}); | }); | ||||
} | } | ||||
Show All 14 Lines | return { | ||||
step: 'exif_fetch', | step: 'exif_fetch', | ||||
success, | success, | ||||
exceptionMessage, | exceptionMessage, | ||||
time: Date.now() - start, | time: Date.now() - start, | ||||
orientation, | orientation, | ||||
}; | }; | ||||
} | } | ||||
export { getOrientation }; | type GenerateThumbhashResult = { | ||||
+success: true, | |||||
+thumbHash: string, | |||||
}; | |||||
/** | |||||
* Generate a thumbhash for a given image file. If `encryptionKey` is provided, | |||||
* the thumbhash string will be encrypted with it. | |||||
*/ | |||||
async function generateThumbHash( | |||||
file: File, | |||||
encryptionKey: ?string = null, | |||||
): Promise<{ | |||||
+steps: $ReadOnlyArray<MediaMissionStep>, | |||||
+result: GenerateThumbhashResult | MediaMissionFailure, | |||||
}> { | |||||
const steps = []; | |||||
const initialURI = URL.createObjectURL(file); | |||||
const { steps: preloadSteps, result: image } = await preloadImage(initialURI); | |||||
steps.push(...preloadSteps); | |||||
if (!image) { | |||||
return { | |||||
steps, | |||||
result: { success: false, reason: 'preload_image_failed' }, | |||||
}; | |||||
} | |||||
let binaryThumbHash, thumbHashString, exceptionMessage; | |||||
try { | |||||
const canvas = document.createElement('canvas'); | |||||
const context = canvas.getContext('2d'); | |||||
// rescale to 100px max as thumbhash doesn't need more | |||||
const scale = 100 / Math.max(image.width, image.height); | |||||
canvas.width = Math.round(image.width * scale); | |||||
canvas.height = Math.round(image.height * scale); | |||||
context.drawImage(image, 0, 0, canvas.width, canvas.height); | |||||
const pixels = context.getImageData(0, 0, canvas.width, canvas.height); | |||||
binaryThumbHash = rgbaToThumbHash(pixels.width, pixels.height, pixels.data); | |||||
thumbHashString = base64EncodeBuffer(binaryThumbHash); | |||||
} catch (e) { | |||||
exceptionMessage = getMessageForException(e); | |||||
} finally { | |||||
URL.revokeObjectURL(initialURI); | |||||
} | |||||
steps.push({ | |||||
step: 'generate_thumbhash', | |||||
success: !!thumbHashString && !exceptionMessage, | |||||
exceptionMessage, | |||||
thumbHash: thumbHashString, | |||||
}); | |||||
if (!binaryThumbHash || !thumbHashString || exceptionMessage) { | |||||
return { steps, result: { success: false, reason: 'thumbhash_failed' } }; | |||||
} | |||||
if (encryptionKey) { | |||||
try { | |||||
const encryptedThumbHash = await AES.encrypt( | |||||
hexToUintArray(encryptionKey), | |||||
binaryThumbHash, | |||||
); | |||||
thumbHashString = base64EncodeBuffer(encryptedThumbHash); | |||||
} catch { | |||||
return { steps, result: { success: false, reason: 'encryption_failed' } }; | |||||
} | |||||
} | |||||
return { steps, result: { success: true, thumbHash: thumbHashString } }; | |||||
} | |||||
export { getOrientation, generateThumbHash }; |