diff --git a/src/lib/blob-helpers.js b/src/lib/blob-helpers.js index 2639478c..1ebf34c4 100644 --- a/src/lib/blob-helpers.js +++ b/src/lib/blob-helpers.js @@ -66,10 +66,58 @@ export function fileNameForMime(fname, mime) { return fname + '.' + ext; } +// Scale uploaded image to fit under certain dimensions or byte size, optionally constraining to a square. +export function imageFileScaled(file, maxWidth, maxHeight, maxSize, forceSquare, onSuccess, onError) { + const img = new Image(); + img.crossOrigin = 'Anonymous'; + img.onerror = function(err) { + onError("Image format unrecognized"); + } + img.onload = function() { + // Once the image is loaded, the URL is no longer needed. + URL.revokeObjectURL(img.src); + + // Calculate the desired image dimensions. + const dim = fitImageSize(this.width, this.height, maxWidth, maxHeight, forceSquare); + if (!dim) { + onError("Invalid image"); + return; + } + let canvas = document.createElement('canvas'); + canvas.width = dim.dstWidth; + canvas.height = dim.dstHeight; + const ctx = canvas.getContext('2d'); + ctx.imageSmoothingEnabled = true; + ctx.drawImage(this, dim.xoffset, dim.yoffset, dim.srcWidth, dim.srcHeight, + 0, 0, dim.dstWidth, dim.dstHeight); + + let mime = SUPPORTED_IMAGE_FORMATS.indexOf(file.type) < 0 ? 'image/jpeg' : file.type; + let imageBits = canvas.toDataURL(mime); + const parts = imageBits.split(','); + // Get actual image type: 'data:image/png;base64,' + mime = getMimeType(parts[0]); + if (!mime) { + onError("Unsupported image format"); + return; + } + // Ensure the image is not too large. Shrink the image keeping the aspect ratio. + while (base64DecodedLen(imageBits.length) > maxSize && quality > 0.45) { + imageBits = canvas.toDataURL(mime, quality); + } + + canvas.toBlob((blob) => { + onSuccess(blob, mime, dim.dstWidth, dim.dstHeight, fileNameForMime(file.name, mime)); + } + ); + canvas = null; + }; + img.src = URL.createObjectURL(file); +} + // Convert uploaded image into a base64-encoded string possibly scaling // linear dimensions or constraining to a square. export function imageFileScaledToBase64(file, width, height, forceSquare, onSuccess, onError) { - var img = new Image(); + const img = new Image(); img.crossOrigin = 'Anonymous'; img.onerror = function(err) { onError("Image format unrecognized"); @@ -100,7 +148,7 @@ export function imageFileScaledToBase64(file, width, height, forceSquare, onSucc return; } // Ensure the image is not too large - var quality = 0.78; + let quality = 0.78; if (base64DecodedLen(imageBits.length) > MAX_INBAND_ATTACHMENT_SIZE) { mime = 'image/jpeg'; } diff --git a/src/views/messages-view.jsx b/src/views/messages-view.jsx index f1628c1f..2df2963c 100644 --- a/src/views/messages-view.jsx +++ b/src/views/messages-view.jsx @@ -624,6 +624,9 @@ class MessagesView extends React.Component { } } + // Send attachment as Drafty message: + // - if file is too large, upload it and send a s link. + // - if file is small enough, just send it in-band. sendFileAttachment(file) { if (file.size > MAX_INBAND_ATTACHMENT_SIZE) { // Too large to send inband - uploading out of band and sending as a link. @@ -647,6 +650,7 @@ class MessagesView extends React.Component { } } + // handleAttachFile method is called when [Attach file] button is clicked. handleAttachFile(file) { if (file.size > MAX_EXTERN_ATTACHMENT_SIZE) { // Too large. @@ -662,16 +666,9 @@ class MessagesView extends React.Component { } } - sendImageAttachment(caption, mime, bits, width, height, fname) { - let msg = Drafty.insertImage(null, 0, mime, bits, width, height, fname); - if (caption) { - msg = Drafty.appendLineBreak(msg); - msg = Drafty.append(msg, Drafty.init(caption)); - } - this.props.sendMessage(msg); - } - - handleAttachImage(file) { + // sendImageAttachment sends the image bits inband as Drafty message. + sendImageAttachment(objectUrl, caption, mime, width, height, fname) { + // Upload or scale the image. // Check if the uploaded file is indeed an image and if it isn't too large. if (file.size > MAX_INBAND_ATTACHMENT_SIZE || SUPPORTED_IMAGE_FORMATS.indexOf(file.type) < 0) { // Convert image for size or format. @@ -680,7 +677,6 @@ class MessagesView extends React.Component { (bits, mime, width, height, fname) => { this.setState({imagePreview: { url: URL.createObjectURL(file), - bits: bits, filename: fname, width: width, height: height, @@ -713,6 +709,31 @@ class MessagesView extends React.Component { } ); } + + let msg = Drafty.insertImage(null, 0, mime, bits, width, height, fname); + if (caption) { + msg = Drafty.appendLineBreak(msg); + msg = Drafty.append(msg, Drafty.init(caption)); + } + this.props.sendMessage(msg); + } + + // handleAttachImage method is called when [Attach image] button is clicked. + handleAttachImage(file) { + let size = file.size; + // If image is too big scale it down before previewing. + if (size > MAX_EXTERN_ATTACHMENT_SIZE) { + } + + // Launch image preview screen. + this.setState({imagePreview: { + url: URL.createObjectURL(file), + filename: fname, + width: width, // FIXME: width is not defined. + height: height, // FIXME: height is not defined. + size: size, + type: mime // FIXME: mime type is not defined. + }}); } render() { diff --git a/src/widgets/image-preview.jsx b/src/widgets/image-preview.jsx index 8232fc72..aa16a861 100644 --- a/src/widgets/image-preview.jsx +++ b/src/widgets/image-preview.jsx @@ -30,7 +30,7 @@ export default class ImagePreview extends React.PureComponent { handleSendImage(caption) { this.props.onClose(); - this.props.onSendMessage(caption, this.props.content.type, this.props.content.bits, + this.props.onSendMessage(caption, this.props.content.type, this.props.content.width, this.props.content.height, this.props.content.filename); } @@ -47,7 +47,7 @@ export default class ImagePreview extends React.PureComponent { size.maxHeight = '100%'; let filename = this.props.content.filename; - // Averate font aspect ratio is ~0.5; File name takes 1/3 of the viewport width. + // Average font aspect ratio is ~0.5; File name takes 1/3 of the viewport width. const maxlength = Math.max(((this.state.width / REM_SIZE / 1.5) | 0) - 2, 12); if (filename.length > maxlength) { filename = filename.slice(0, maxlength/2 - 1) + '…' + filename.slice(1 - maxlength/2);