summaryrefslogtreecommitdiff
path: root/client/common
diff options
context:
space:
mode:
authorJules Laplace <julescarbon@gmail.com>2019-01-13 00:04:35 +0100
committerJules Laplace <julescarbon@gmail.com>2019-01-13 00:04:35 +0100
commitbb7efd0af0db8183b5b3f96ac0de1bfd9cd249ae (patch)
tree75549888c89cdb0bb1ee8d217f77b95db6e966dc /client/common
parent4060915f156dec87a449a10c96d166d474f2d628 (diff)
face analysis frontend scaffolding
Diffstat (limited to 'client/common')
-rw-r--r--client/common/index.js2
-rw-r--r--client/common/upload.helpers.js155
-rw-r--r--client/common/uploadImage.component.js46
3 files changed, 203 insertions, 0 deletions
diff --git a/client/common/index.js b/client/common/index.js
index cfb34b32..cbd3166e 100644
--- a/client/common/index.js
+++ b/client/common/index.js
@@ -3,6 +3,7 @@ import DetectionBoxes from './detectionBoxes.component'
import DetectionList from './detectionList.component'
// import Header from './header.component'
import Loader from './loader.component'
+import UploadImage from './uploadImage.component'
import Sidebar from './sidebar.component'
import Gate from './gate.component'
import Video from './video.component'
@@ -12,6 +13,7 @@ import './common.css'
export {
Sidebar,
Loader,
+ UploadImage,
Gate,
TableObject,
TableArray,
diff --git a/client/common/upload.helpers.js b/client/common/upload.helpers.js
new file mode 100644
index 00000000..5a041fd4
--- /dev/null
+++ b/client/common/upload.helpers.js
@@ -0,0 +1,155 @@
+import ExifReader from 'exifreader'
+
+export const MAX_SIDE = 300
+
+function base64ToUint8Array(string, start, finish) {
+ start = start || 0
+ finish = finish || string.length
+ // atob that shit
+ const binary = atob(string)
+ const buffer = new Uint8Array(binary.length)
+ for (let i = start; i < finish; i++) {
+ buffer[i] = binary.charCodeAt(i)
+ }
+ return buffer
+}
+
+function getOrientation(uri) {
+ const exif = new ExifReader()
+ // Split off the base64 data
+ const base64String = uri.split(',')[1]
+ // Read off first 128KB, which is all we need to
+ // get the EXIF data
+ const arr = base64ToUint8Array(base64String, 0, 2 ** 17)
+ try {
+ exif.load(arr.buffer)
+ return exif.getTagValue('Orientation')
+ } catch (err) {
+ return 1
+ }
+}
+
+function applyRotation(canvas, ctx, deg) {
+ const radians = deg * (Math.PI / 180)
+ if (deg === 90) {
+ ctx.translate(canvas.width, 0)
+ } else if (deg === 180) {
+ ctx.translate(canvas.width, canvas.height)
+ } else if (deg === 270) {
+ ctx.translate(0, canvas.height)
+ }
+ ctx.rotate(radians)
+}
+
+/**
+ * Mapping from EXIF orientation values to data
+ * regarding the rotation and mirroring necessary to
+ * render the canvas correctly
+ * Derived from:
+ * http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto/
+ */
+const orientationToTransform = {
+ 1: { rotation: 0, mirror: false },
+ 2: { rotation: 0, mirror: true },
+ 3: { rotation: 180, mirror: false },
+ 4: { rotation: 180, mirror: true },
+ 5: { rotation: 90, mirror: true },
+ 6: { rotation: 90, mirror: false },
+ 7: { rotation: 270, mirror: true },
+ 8: { rotation: 270, mirror: false }
+}
+
+function applyOrientationCorrection(canvas, ctx, uri) {
+ const orientation = getOrientation(uri)
+ // Only apply transform if there is some non-normal orientation
+ if (orientation && orientation !== 1) {
+ const transform = orientationToTransform[orientation]
+ const { rotation } = transform
+ const flipAspect = rotation === 90 || rotation === 270
+ if (flipAspect) {
+ // Fancy schmancy swap algo
+ canvas.width = canvas.height + canvas.width
+ canvas.height = canvas.width - canvas.height
+ canvas.width -= canvas.height
+ }
+ if (rotation > 0) {
+ applyRotation(canvas, ctx, rotation)
+ }
+ }
+}
+
+function getScale(width, height, viewportWidth, viewportHeight, fillViewport) {
+ function fitHorizontal() {
+ return viewportWidth / width
+ }
+ function fitVertical() {
+ return viewportHeight / height
+ }
+ fillViewport = !!fillViewport
+ const landscape = (width / height) > (viewportWidth / viewportHeight)
+ if (landscape) {
+ if (fillViewport) {
+ return fitVertical()
+ }
+ if (width > viewportWidth) {
+ return fitHorizontal()
+ }
+ } else {
+ if (fillViewport) {
+ return fitHorizontal()
+ }
+ if (height > viewportHeight) {
+ return fitVertical()
+ }
+ }
+ return 1
+}
+
+export function renderToCanvas(img, options) {
+ if (!img) return null
+ options = options || {}
+
+ // Canvas max size for any side
+ const maxSize = MAX_SIDE
+ const canvas = document.createElement('canvas')
+ const ctx = canvas.getContext('2d')
+ const initialScale = options.scale || 1
+ // Scale to needed to constrain canvas to max size
+ let scale = getScale(img.width * initialScale, img.height * initialScale, maxSize, maxSize, true)
+ // Still need to apply the user defined scale
+ scale *= initialScale
+ canvas.width = Math.round(img.width * scale)
+ canvas.height = Math.round(img.height * scale)
+ const { correctOrientation } = options
+ const jpeg = !!img.src.match(/data:image\/jpeg|\.jpeg$|\.jpg$/i)
+ const hasDataURI = !!img.src.match(/^data:/)
+
+ ctx.save()
+
+ // Can only correct orientation on JPEGs represented as dataURIs
+ // for the time being
+ if (correctOrientation && jpeg && hasDataURI) {
+ applyOrientationCorrection(canvas, ctx, img.src)
+ }
+ // Resize image if too large
+ if (scale !== 1) {
+ ctx.scale(scale, scale)
+ }
+
+ ctx.drawImage(img, 0, 0)
+ ctx.restore()
+
+ return canvas
+}
+
+export function renderThumbnail(img) {
+ const resized = renderToCanvas(img, { correctOrientation: true })
+ const canvas = document.createElement('canvas') // document.querySelector('#user_photo_canvas')
+ const ctx = canvas.getContext('2d')
+ ctx.fillStyle = 'black'
+ ctx.fillRect(0, 0, MAX_SIDE, MAX_SIDE)
+ const xOffset = (MAX_SIDE - resized.width) / 2
+ const yOffset = (MAX_SIDE - resized.height) / 2
+ ctx.drawImage(resized, xOffset, yOffset)
+ return canvas
+}
diff --git a/client/common/uploadImage.component.js b/client/common/uploadImage.component.js
new file mode 100644
index 00000000..eb8cc60f
--- /dev/null
+++ b/client/common/uploadImage.component.js
@@ -0,0 +1,46 @@
+import React, { Component } from 'react'
+
+import { renderThumbnail } from './upload.helpers'
+
+export default class UploadImageComponent extends Component {
+ upload(e) {
+ const files = e.dataTransfer ? e.dataTransfer.files : e.target.files
+ let i
+ let file
+ for (i = 0; i < files.length; i++) {
+ file = files[i]
+ if (file && file.type.match('image.*')) break
+ }
+ if (!file) return
+ const fr = new FileReader()
+ fr.onload = fileReaderEvent => {
+ fr.onload = null
+ const img = new Image()
+ img.onload = () => {
+ img.onload = null
+ this.resizeAndUpload(img)
+ }
+ img.src = fileReaderEvent.result
+ }
+ fr.readAsDataURL(files[0])
+ }
+
+ resizeAndUpload(img) {
+ const canvas = renderThumbnail(img)
+ canvas.toBlob(blob => {
+ this.props.onUpload(blob)
+ }, 'image/jpeg', 80)
+ }
+
+ render() {
+ return (
+ <input
+ type="file"
+ name="img"
+ accept="image/*"
+ onChange={this.upload.bind(this)}
+ required
+ />
+ )
+ }
+}