diff --git a/src/graphics/background-indoor-standin.jpg b/src/graphics/background-indoor-standin.jpg new file mode 100644 index 000000000..7d709f819 Binary files /dev/null and b/src/graphics/background-indoor-standin.jpg differ diff --git a/src/graphics/background-outdoor-standin.jpg b/src/graphics/background-outdoor-standin.jpg new file mode 100644 index 000000000..91baebc55 Binary files /dev/null and b/src/graphics/background-outdoor-standin.jpg differ diff --git a/src/livekit/backgroundEffects.ts b/src/livekit/backgroundEffects.ts index e43dc9837..e3dcb3153 100644 --- a/src/livekit/backgroundEffects.ts +++ b/src/livekit/backgroundEffects.ts @@ -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. */ -import desktopGradient from "../graphics/desktop-gradient.png?url"; -import mobileGradient from "../graphics/mobile-gradient.png?url"; +import indoorStandIn from "../graphics/background-indoor-standin.jpg?url"; +import outdoorStandIn from "../graphics/background-outdoor-standin.jpg?url"; /** How much to blur, when the chosen effect is blur. */ export const blurRadius = 15; @@ -17,11 +17,18 @@ export interface ShippedBackground { } // EXPLORATION SHORTCUT (S1): the two shipped backgrounds, one indoor and one -// outdoor, do not exist yet. These stand-ins are existing gradient assets, so -// the pipeline can be exercised and measured. Never port this. +// outdoor, do not exist yet. These stand-ins are the app's own gradients, laid +// 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[] = [ - { id: "indoor", imagePath: desktopGradient }, - { id: "outdoor", imagePath: mobileGradient }, + { id: "indoor", imagePath: indoorStandIn }, + { id: "outdoor", imagePath: outdoorStandIn }, ]; export type BackgroundEffect = diff --git a/src/livekit/backgroundImages.ts b/src/livekit/backgroundImages.ts index f8554aaab..3ab2a1c5d 100644 --- a/src/livekit/backgroundImages.ts +++ b/src/livekit/backgroundImages.ts @@ -83,16 +83,24 @@ export async function prepareImage(file: Blob): Promise { } 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); - if (longest <= maxStoredEdge) return file; - - const scale = maxStoredEdge / longest; + const scale = Math.min(1, maxStoredEdge / longest); const canvas = new OffscreenCanvas( Math.round(bitmap.width * scale), Math.round(bitmap.height * scale), ); const context = canvas.getContext("2d"); 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); return await canvas.convertToBlob({ type: "image/webp", quality: 0.9 }); } finally {