diff --git a/index.html b/index.html
index d4a8a6d1..6fbdc353 100644
--- a/index.html
+++ b/index.html
@@ -50,6 +50,9 @@
// Size of css 'rem' unit in pixels. Default 1rem = 10pt = 13px.
var REM_SIZE = 13;
+ // Size of the avatar image
+ var AVATAR_SIZE = 128;
+
// Number of chat messages to fetch in one call.
var MESSAGES_PAGE = 24;
@@ -61,6 +64,12 @@
// to adjust it to 1600 or 2400 for production.
var MAX_IMAGE_SIZE = 768;
+ // Supported image MIME types and corresponding file extensions.
+ var SUPPORTED_FORMATS = ['image/jpeg', 'image/gif', 'image/png', 'image/x-icon',
+ 'image/vnd.microsoft.icon', 'image/svg', 'image/svg+xml'];
+ var MIME_EXTENSIONS = ['jpg', 'gif', 'png', 'ico',
+ 'ico', 'svg', 'svg'];
+
// Short representation of time in the past.
function shortDateFormat(then) {
var locale = window.navigator.userLanguage || window.navigator.language;
@@ -83,8 +92,8 @@
'data:image/' + photo.type + ';base64,' + photo.data : null;
}
- // Scale liner dimensions to fit uder certain size
- function imageScaleDown(width, height, maxWidth, maxHeight) {
+ // Scale liner dimensions down to fit uder certain size.
+ function fitImageSize(width, height, maxWidth, maxHeight) {
if (!width || !height || !maxWidth || !maxHeight) {
return null;
}
@@ -93,28 +102,121 @@
Math.min(height, maxHeight) / height
);
- return {width: (width * scale) | 0, height: (height * scale) | 0};
+ var size = {width: (width * scale) | 0, height: (height * scale) | 0};
+ if (maxWidth == maxHeight) {
+ // Also calculate parameters for making the image square.
+ size.square = Math.min(width, height);
+ size.xoffset = ((width - size.square) / 2) | 0;
+ size.yoffset = ((height - size.square) / 2) | 0;
+ }
+ return size;
}
- // Convert uploaded image into a base64-encoded string.
- function imageFileToBase64(file, callback) {
+ // Ensure file's extension matches mime content type
+ function fileNameForMime(fname, mime) {
+ var idx = SUPPORTED_FORMATS.indexOf(mime);
+ var ext = MIME_EXTENSIONS[idx];
+
+ var at = fname.lastIndexOf('.');
+ if (at >= 0) {
+ fname = fname.substring(0, at);
+ }
+ return fname + '.' + ext;
+ }
+
+ // Get mime type from data URL header.
+ function getMimeType(header) {
+ var mime = /^data:(image\/[-+a-z0-9.]+);base64/.exec(header);
+ return (mime && mime.length > 1) ? mime[1] : null;
+ }
+
+ // Convert uploaded image into a base64-encoded string possibly scaling
+ // linear dimensions.
+ function imageFileScaledToBase64(file, width, height, forceSquare, onSuccess, onError) {
var img = new Image();
img.crossOrigin = 'Anonymous';
+ img.onerror = function(err) {
+ onError("Image format unrecognized");
+ }
img.onload = function() {
- var size = imageScaleDown(this.width, this.height, MAX_IMAGE_SIZE, MAX_IMAGE_SIZE);
+ var size = fitImageSize(this.width, this.height, width, height);
+ if (!size) {
+ onError("Invalid image");
+ return;
+ }
var canvas = document.createElement('CANVAS');
- canvas.width = size.width;
- canvas.height = size.height;
+ if (forceSquare) {
+ canvas.width = canvas.height = width;
+ } else {
+ canvas.width = size.width;
+ canvas.height = size.height;
+ }
var ctx = canvas.getContext('2d');
ctx.imageSmoothingEnabled = true;
- ctx.drawImage(this, 0, 0, this.width, this.height, 0, 0, canvas.width, canvas.height);
- var imageBits = canvas.toDataURL("image/jpeg");
+ if (forceSquare) {
+ ctx.drawImage(this, size.xoffset, size.yoffset, size.square, size.square,
+ 0, 0, canvas.width, canvas.height);
+ } else {
+ ctx.drawImage(this, 0, 0, this.width, this.height,
+ 0, 0, canvas.width, canvas.height);
+ }
+ var mime = (forceSquare || SUPPORTED_FORMATS.indexOf(file.type) < 0) ? "image/jpeg" : file.type;
+ var imageBits = canvas.toDataURL(mime);
+ var 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
+ var quality = 0.78;
+ while (mime == "image/jpeg" && imageBits.length * 0.75 > MAX_ATTACHMENT_SIZE && quality > 0.15) {
+ imageBits = canvas.toDataURL(mime, quality);
+ quality *= 0.84;
+ }
+ if (imageBits.length * 0.75 > MAX_ATTACHMENT_SIZE) {
+ onError("The image size " + bytesToHumanSize(imageBits.length * 0.75) +
+ " exceeds the " + bytesToHumanSize(MAX_ATTACHMENT_SIZE) + " limit.", "err");
+ return;
+ }
canvas = null;
- callback(imageBits, size.width, size.height);
+ onSuccess(imageBits.split(',')[1], mime, size.width, size.height, fileNameForMime(file.name, mime));
};
img.src = URL.createObjectURL(file);
}
+ // Convert uploaded image file to base64-encoded string without scaling/converting the image
+ function imageFileToBase64(file, onSuccess, onError) {
+ if (file.size > MAX_ATTACHMENT_SIZE) {
+ onError("The file size " + bytesToHumanSize(file.size) +
+ " exceeds the " + bytesToHumanSize(MAX_ATTACHMENT_SIZE) + " limit.", "err");
+ return;
+ }
+
+ var reader = new FileReader();
+ reader.addEventListener("load", function() {
+ var parts = reader.result.split(',');
+ var mime = getMimeType(parts[0]);
+ if (!mime) {
+ onError("Failed to process image file");
+ return;
+ }
+
+ // Get image size.
+ var img = new Image();
+ img.crossOrigin = 'Anonymous';
+ img.onload = function() {
+ onSuccess(parts[1], mime, this.width, this.height, fileNameForMime(file.name, mime));
+ }
+ img.onerror = function(err) {
+ onError("Image format unrecognized");
+ }
+ img.src = URL.createObjectURL(file);
+ }, false);
+ reader.readAsDataURL(file);
+ }
+
// Make shortcut icon appear with a green dot.
function updateFavicon(on) {
var oldIcon = document.getElementById("shortcut-icon");
@@ -901,9 +1003,11 @@
-