From 1ad8af34d193096e6fdf5df59b4ddff798e5f165 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 12 Nov 2020 13:50:06 +0100 Subject: [PATCH] add thumbnailing code --- src/platform/web/Platform.js | 11 +++- src/platform/web/dom/BlobHandle.js | 4 +- src/platform/web/dom/ImageHandle.js | 96 +++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 src/platform/web/dom/ImageHandle.js diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 6a981c64..f99fd13c 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -28,6 +28,7 @@ import {Crypto} from "./dom/Crypto.js"; import {estimateStorageUsage} from "./dom/StorageEstimate.js"; import {WorkerPool} from "./dom/WorkerPool.js"; import {BlobHandle} from "./dom/BlobHandle.js"; +import {hasReadPixelPermission, ImageHandle} from "./dom/ImageHandle.js"; import {downloadInIframe} from "./dom/download.js"; function addScript(src) { @@ -156,7 +157,7 @@ export class Platform { const file = input.files[0]; this._container.removeChild(input); if (file) { - resolve({name: file.name, blob: BlobHandle.fromFile(file)}); + resolve({name: file.name, blob: BlobHandle.fromBlob(file)}); } else { reject(new Error("No file selected")); } @@ -168,4 +169,12 @@ export class Platform { input.click(); return promise; } + + async loadImage(blob) { + return ImageHandle.fromBlob(blob); + } + + hasReadPixelPermission() { + return hasReadPixelPermission(); + } } diff --git a/src/platform/web/dom/BlobHandle.js b/src/platform/web/dom/BlobHandle.js index 00098de1..fb1b150a 100644 --- a/src/platform/web/dom/BlobHandle.js +++ b/src/platform/web/dom/BlobHandle.js @@ -84,9 +84,9 @@ export class BlobHandle { return new BlobHandle(new Blob([buffer], {type: mimetype}), buffer); } - static fromFile(file) { + static fromBlob(blob) { // ok to not filter mimetypes as these are local files - return new BlobHandle(file); + return new BlobHandle(blob); } get nativeBlob() { diff --git a/src/platform/web/dom/ImageHandle.js b/src/platform/web/dom/ImageHandle.js new file mode 100644 index 00000000..691ead22 --- /dev/null +++ b/src/platform/web/dom/ImageHandle.js @@ -0,0 +1,96 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import {BlobHandle} from "./BlobHandle.js"; + +export class ImageHandle { + static async fromBlob(blob) { + const img = await loadImgFromBlob(blob); + const {width, height} = img; + return new ImageHandle(blob, width, height, img); + } + + constructor(blob, width, height, imgElement) { + this.blob = blob; + this.width = width; + this.height = height; + this._imgElement = imgElement; + } + + async _getImgElement() { + if (!this._imgElement) { + this._imgElement = await loadImgFromBlob(this.blob); + } + return this._imgElement; + } + + async scale(maxDimension) { + const aspectRatio = this.width / this.height; + const scaleFactor = Math.min(1, maxDimension / (aspectRatio >= 1 ? this.width : this.height)); + const scaledWidth = this.width * scaleFactor; + const scaledHeight = this.height * scaleFactor; + + const canvas = document.createElement("canvas"); + canvas.width = scaledWidth; + canvas.height = scaledHeight; + const ctx = canvas.getContext("2d"); + const img = await this._getImgElement(); + ctx.drawImage(img, 0, 0, scaledWidth, scaledHeight); + const mimeType = this.blob.mimeType === "image/jpeg" ? "image/jpeg" : "image/png"; + const nativeBlob = await new Promise(resolve => { + canvas.toBlob(resolve, mimeType); + }); + const blob = BlobHandle.fromBlob(nativeBlob); + return new ImageHandle(blob, scaledWidth, scaledHeight, null); + } + + dispose() { + this.blob.dispose(); + } +} + +export function hasReadPixelPermission() { + const canvas = document.createElement("canvas"); + canvas.width = 1; + canvas.height = 1; + const ctx = canvas.getContext("2d"); + const rgb = [ + Math.round(Math.random() * 255), + Math.round(Math.random() * 255), + Math.round(Math.random() * 255), + ] + ctx.fillStyle = `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`; + ctx.fillRect(0, 0, 1, 1); + const data = ctx.getImageData(0, 0, 1, 1).data; + return data[0] === rgb[0] && data[1] === rgb[1] && data[2] === rgb[2]; +} + +async function loadImgFromBlob(blob) { + const img = document.createElement("img"); + let detach; + const loadPromise = new Promise((resolve, reject) => { + detach = () => { + img.removeEventListener("load", resolve); + img.removeEventListener("error", reject); + }; + img.addEventListener("load", resolve); + img.addEventListener("error", reject); + }); + img.src = blob.url; + await loadPromise; + detach(); + return img; +}