diff --git a/src/components/views/messages/MImageBody.js b/src/components/views/messages/MImageBody.js index c0e751431f..14c8c94f30 100644 --- a/src/components/views/messages/MImageBody.js +++ b/src/components/views/messages/MImageBody.js @@ -151,6 +151,18 @@ export default class MImageBody extends React.Component { if (this.refs.image) { const { naturalWidth, naturalHeight } = this.refs.image; + // XXX: if this is a retina image, the naturalWidth/Height + // are going to be off by a factor of 2. However, we don't know + // this at this point - so until we include a resolution param + // in info, this might cause scroll jumps for retina images? + // + // Something like: + // + // if (content.info && content.info.devicePixelRatio) { + // naturalWidth /= content.info.devicePixelRatio; + // naturalHeight /= content.info.devicePixelRatio; + // } + loadedImageDimensions = { naturalWidth, naturalHeight }; } @@ -167,6 +179,11 @@ export default class MImageBody extends React.Component { } _getThumbUrl() { + // FIXME: the dharma skin lets images grow as wide as you like, rather than capped to 800x600. + // So either we need to support custom timeline widths here, or reimpose the cap, otherwise the + // thumbnail resolution will be unnecessarily reduced. + // custom timeline widths seems preferable. + const content = this.props.mxEvent.getContent(); if (content.file !== undefined) { // Don't use the thumbnail for clients wishing to autoplay gifs. @@ -175,7 +192,7 @@ export default class MImageBody extends React.Component { } return this.state.decryptedUrl; } else if (content.info && content.info.mimetype === "image/svg+xml" && content.info.thumbnail_url) { - // special case to return client-generated thumbnails for SVGs, if any, + // special case to return clientside sender-generated thumbnails for SVGs, if any, // given we deliberately don't thumbnail them serverside to prevent // billion lol attacks and similar return this.context.matrixClient.mxcUrlToHttp( @@ -184,11 +201,49 @@ export default class MImageBody extends React.Component { 600 * window.devicePixelRatio, ); } else { - return this.context.matrixClient.mxcUrlToHttp( - content.url, - 800 * window.devicePixelRatio, - 600 * window.devicePixelRatio - ); + if (window.devicePixelRatio === 1.0 || + (!content.info || !content.info.w || + !content.info.h || !content.info.size)) + { + // always thumbnail. it may look a bit worse, but it'll save bandwidth. + // which is probably desirable on a lo-dpi device anyway. + return this.context.matrixClient.mxcUrlToHttp( + content.url, + 800 * window.devicePixelRatio, + 600 * window.devicePixelRatio + ); + } + else { + // we should only request thumbnails if the image is bigger than 800x600 + // (or 1600x1200 on retina) otherwise the image in the timeline will just + // end up resampled and de-retina'd for no good reason. + // Ideally the server would pregen 1600x1200 thumbnails in order to provide retina + // thumbnails, but we don't do this currently in synapse for fear of disk space. + // As a compromise, let's switch to non-retina thumbnails only if the original + // image is both physically too large and going to be massive to load in the + // timeline (e.g. >1MB). + + if ((content.info.w > 800 * window.devicePixelRatio || + content.info.h > 600 * window.devicePixelRatio) && + content.info.size > 1*1024*1024) + { + // image is too large physically and bytewise to clutter our timeline so + // we ask for a thumbnail, despite knowing that it will be max 800x600 + // despite us being retina (as synapse doesn't do 1600x1200 thumbs yet). + + return this.context.matrixClient.mxcUrlToHttp( + content.url, + 800 * window.devicePixelRatio, + 600 * window.devicePixelRatio + ); + } + else { + // no width/height means we want the original image. + return this.context.matrixClient.mxcUrlToHttp( + content.url, + }; + } + } } }