mirror of
https://github.com/vector-im/element-call.git
synced 2026-09-22 22:29:30 +00:00
Give a background something to cover with
- The stand-in backgrounds were the app's own gradients, which are overlay scrims: measured, not one pixel in either is opaque, and where they look dark they are transparent. As a background their dark half was simply absent, which is what showed on the slow path. - They are now laid on the canvas colour they are painted over and kept as JPEG, which cannot carry transparency at all. - The same hole was waiting for anyone's own picture: a canvas starts transparent, WebP keeps an alpha channel, and a file small enough to keep as it was never passed through either. Every picture now goes through an opaque ground, whatever its size. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
@@ -5,8 +5,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import desktopGradient from "../graphics/desktop-gradient.png?url";
|
import indoorStandIn from "../graphics/background-indoor-standin.jpg?url";
|
||||||
import mobileGradient from "../graphics/mobile-gradient.png?url";
|
import outdoorStandIn from "../graphics/background-outdoor-standin.jpg?url";
|
||||||
|
|
||||||
/** How much to blur, when the chosen effect is blur. */
|
/** How much to blur, when the chosen effect is blur. */
|
||||||
export const blurRadius = 15;
|
export const blurRadius = 15;
|
||||||
@@ -17,11 +17,18 @@ export interface ShippedBackground {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EXPLORATION SHORTCUT (S1): the two shipped backgrounds, one indoor and one
|
// EXPLORATION SHORTCUT (S1): the two shipped backgrounds, one indoor and one
|
||||||
// outdoor, do not exist yet. These stand-ins are existing gradient assets, so
|
// outdoor, do not exist yet. These stand-ins are the app's own gradients, laid
|
||||||
// the pipeline can be exercised and measured. Never port this.
|
// on the canvas colour they are painted over and kept as JPEG.
|
||||||
|
//
|
||||||
|
// Not a detail: those gradients are overlay scrims, and measured, not one
|
||||||
|
// pixel in either is opaque — where they look dark they are transparent. Used
|
||||||
|
// directly as a background their dark half was simply absent, which is what
|
||||||
|
// showed on the slow path. Flattening them is what makes them a picture rather
|
||||||
|
// than a veil, and JPEG cannot carry transparency at all, so it cannot come
|
||||||
|
// back. Never port this.
|
||||||
export const shippedBackgrounds: ShippedBackground[] = [
|
export const shippedBackgrounds: ShippedBackground[] = [
|
||||||
{ id: "indoor", imagePath: desktopGradient },
|
{ id: "indoor", imagePath: indoorStandIn },
|
||||||
{ id: "outdoor", imagePath: mobileGradient },
|
{ id: "outdoor", imagePath: outdoorStandIn },
|
||||||
];
|
];
|
||||||
|
|
||||||
export type BackgroundEffect =
|
export type BackgroundEffect =
|
||||||
|
|||||||
@@ -83,16 +83,24 @@ export async function prepareImage(file: Blob): Promise<Blob> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Every picture goes through the canvas, not only the oversized ones: a
|
||||||
|
// background covers what is behind it, so it has to be opaque, and a
|
||||||
|
// picture with transparency in it — a logo, a screenshot with rounded
|
||||||
|
// corners — would otherwise be stored with its holes and drawn with them.
|
||||||
|
// Passing small files straight through is what kept them.
|
||||||
const longest = Math.max(bitmap.width, bitmap.height);
|
const longest = Math.max(bitmap.width, bitmap.height);
|
||||||
if (longest <= maxStoredEdge) return file;
|
const scale = Math.min(1, maxStoredEdge / longest);
|
||||||
|
|
||||||
const scale = maxStoredEdge / longest;
|
|
||||||
const canvas = new OffscreenCanvas(
|
const canvas = new OffscreenCanvas(
|
||||||
Math.round(bitmap.width * scale),
|
Math.round(bitmap.width * scale),
|
||||||
Math.round(bitmap.height * scale),
|
Math.round(bitmap.height * scale),
|
||||||
);
|
);
|
||||||
const context = canvas.getContext("2d");
|
const context = canvas.getContext("2d");
|
||||||
if (!context) throw new UnusableImage("undecodable");
|
if (!context) throw new UnusableImage("undecodable");
|
||||||
|
// The ground the picture is laid on, so nothing it does not cover is a
|
||||||
|
// hole. Black rather than white: an unfilled corner reads as the frame's
|
||||||
|
// own edge rather than as a lamp.
|
||||||
|
context.fillStyle = "black";
|
||||||
|
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
context.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
|
context.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
|
||||||
return await canvas.convertToBlob({ type: "image/webp", quality: 0.9 });
|
return await canvas.convertToBlob({ type: "image/webp", quality: 0.9 });
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in New Issue
Block a user