From 577c411a397e2b14c061e05958cb7370255f5ed9 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 14 Jan 2019 17:10:20 +0000 Subject: [PATCH] experimental fix for https://github.com/vector-im/riot-web/issues/2985 needs server to support 1600x1200 thumbnails for retina large ones. ideally need to cap maximum thumbnail size to 800x600 rather than expand to arbitrary widths. need to check that luke's funky timeline code doesn't get confused between naturalWidth and infoWidth etc. also need to consider whether to encode a resolution metric in the event rather than lying about resolution. --- package.json | 1 + src/ContentMessages.js | 34 +++++++++++++++++++-- src/components/views/messages/MImageBody.js | 10 ++++-- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 7b55a09948..82246534a2 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "matrix-js-sdk": "0.14.2", "optimist": "^0.6.1", "pako": "^1.0.5", + "png-chunks-extract": "^1.0.0", "prop-types": "^15.5.8", "qrcode-react": "^0.1.16", "querystring": "^0.2.0", diff --git a/src/ContentMessages.js b/src/ContentMessages.js index f2bbdfafe5..e4c62b5de1 100644 --- a/src/ContentMessages.js +++ b/src/ContentMessages.js @@ -25,6 +25,7 @@ import { _t } from './languageHandler'; const Modal = require('./Modal'); const encrypt = require("browser-encrypt-attachment"); +const png_chunks_extract = require("png-chunks-extract"); // Polyfill for Canvas.toBlob API using Canvas.toDataURL require("blueimp-canvas-to-blob"); @@ -32,6 +33,9 @@ require("blueimp-canvas-to-blob"); const MAX_WIDTH = 800; const MAX_HEIGHT = 600; +// scraped out of a macOS hidpi (5660ppm) screenshot png +// 5669 px (x-axis) , 5669 px (y-axis) , per metre +const PHYS_HIDPI = [0x00, 0x00, 0x16, 0x25, 0x00, 0x00, 0x16, 0x25, 0x01]; /** * Create a thumbnail for a image DOM element. @@ -102,10 +106,34 @@ function loadImageElement(imageFile) { const objectUrl = URL.createObjectURL(imageFile); img.src = objectUrl; + // check for hi-dpi PNGs and fudge display resolution as needed. + // this is mainly needed for macOS screencaps + let hidpi = false; + if (imageFile.type === "image/png") { + // in practice macOS happens to order the chunks so they fall in + // the first 0x1000 bytes (thanks to a massive ICC header). + // Thus we could slice the file down to only sniff the first 0x1000 + // bytes (but this makes png_chunks_extract choke on the corrupt file) + const headers = imageFile; //.slice(0, 0x1000); + readFileAsArrayBuffer(headers).then(arrayBuffer=>{ + const buffer = new Uint8Array(arrayBuffer); + const chunks = png_chunks_extract(buffer); + for (const chunk of chunks) { + if (chunk.name === 'pHYs') { + if (chunk.data.byteLength !== PHYS_HIDPI.length) return; + hidpi = chunk.data.every((val, i) => val === PHYS_HIDPI[i]); + return; + } + } + }); + } + // Once ready, create a thumbnail img.onload = function() { URL.revokeObjectURL(objectUrl); - deferred.resolve(img); + let width = hidpi ? (img.width >> 1) : img.width; + let height = hidpi ? (img.height >> 1) : img.height; + deferred.resolve({ img, width, height }); }; img.onerror = function(e) { deferred.reject(e); @@ -129,8 +157,8 @@ function infoForImageFile(matrixClient, roomId, imageFile) { } let imageInfo; - return loadImageElement(imageFile).then(function(img) { - return createThumbnail(img, img.width, img.height, thumbnailType); + return loadImageElement(imageFile).then(function(r) { + return createThumbnail(r.img, r.width, r.height, thumbnailType); }).then(function(result) { imageInfo = result.info; return uploadFile(matrixClient, roomId, result.thumbnail); diff --git a/src/components/views/messages/MImageBody.js b/src/components/views/messages/MImageBody.js index dc891b86ff..c0e751431f 100644 --- a/src/components/views/messages/MImageBody.js +++ b/src/components/views/messages/MImageBody.js @@ -179,10 +179,16 @@ export default class MImageBody extends React.Component { // given we deliberately don't thumbnail them serverside to prevent // billion lol attacks and similar return this.context.matrixClient.mxcUrlToHttp( - content.info.thumbnail_url, 800, 600, + content.info.thumbnail_url, + 800 * window.devicePixelRatio, + 600 * window.devicePixelRatio, ); } else { - return this.context.matrixClient.mxcUrlToHttp(content.url, 800, 600); + return this.context.matrixClient.mxcUrlToHttp( + content.url, + 800 * window.devicePixelRatio, + 600 * window.devicePixelRatio + ); } }