mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-08-13 16:32:40 +01:00
Clamp blurhash aspect ratio
Co-authored-by: trevor-signal <131492920+trevor-signal@users.noreply.github.com>
This commit is contained in:
co-authored by
trevor-signal
parent
60929351aa
commit
5cdb0d6afd
@@ -0,0 +1,57 @@
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
import { computeBlurHashUrl } from '../../util/computeBlurHashUrl.std.ts';
|
||||
import { defaultBlurHash } from '../../util/Attachment.std.ts';
|
||||
|
||||
describe('computeBlurHashUrl', () => {
|
||||
const BLUR_HASH = defaultBlurHash();
|
||||
|
||||
async function loadImage(
|
||||
desiredWidth?: number,
|
||||
desiredHeight?: number
|
||||
): Promise<HTMLImageElement> {
|
||||
const image = document.createElement('img');
|
||||
image.src = computeBlurHashUrl(BLUR_HASH, desiredWidth, desiredHeight);
|
||||
await image.decode();
|
||||
return image;
|
||||
}
|
||||
|
||||
it('produces a square image by default', async () => {
|
||||
const image = await loadImage();
|
||||
assert.strictEqual(image.naturalWidth, 32);
|
||||
assert.strictEqual(image.naturalHeight, 32);
|
||||
});
|
||||
|
||||
it('respects the requested aspect ratio', async () => {
|
||||
const image = await loadImage(1600, 400);
|
||||
assert.strictEqual(image.naturalWidth / image.naturalHeight, 4);
|
||||
});
|
||||
|
||||
describe('bounds sender-provided dimensions', () => {
|
||||
const DEGENERATE: ReadonlyArray<[number, number]> = [
|
||||
[1, 25_000_000],
|
||||
[25_000_000, 1],
|
||||
[1, 0xffffffff],
|
||||
[0xffffffff, 1],
|
||||
[0, 0xffffffff],
|
||||
[0xffffffff, 0xffffffff],
|
||||
[-1, -25_000_000],
|
||||
[Number.MAX_SAFE_INTEGER, 1],
|
||||
[1, Number.MAX_SAFE_INTEGER],
|
||||
];
|
||||
|
||||
for (const [desiredWidth, desiredHeight] of DEGENERATE) {
|
||||
it(`stays small for ${desiredWidth}x${desiredHeight}`, async () => {
|
||||
const image = await loadImage(desiredWidth, desiredHeight);
|
||||
|
||||
assert.isAtLeast(image.naturalWidth, 4);
|
||||
assert.isAtLeast(image.naturalHeight, 4);
|
||||
assert.isAtMost(image.naturalWidth, 256);
|
||||
assert.isAtMost(image.naturalHeight, 256);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -46,6 +46,12 @@ const BITMAP_HEADER = new Uint8Array([
|
||||
]);
|
||||
|
||||
const PIXEL_COUNT = 32 * 32;
|
||||
const MIN_DIMENSION = 4;
|
||||
|
||||
// width * height = PIXEL_COUNT, so aspect_ratio = PIXEL_COUNT / (height * height), which is
|
||||
// maximized when height is small
|
||||
const MAX_ASPECT_RATIO = PIXEL_COUNT / (MIN_DIMENSION * MIN_DIMENSION); // 64
|
||||
const MIN_ASPECT_RATIO = 1 / MAX_ASPECT_RATIO;
|
||||
|
||||
function writeUInt32LE(
|
||||
bytes: Uint8Array<ArrayBuffer>,
|
||||
@@ -68,9 +74,14 @@ export function computeBlurHashUrl(
|
||||
desiredWidth = 1,
|
||||
desiredHeight = 1
|
||||
): string {
|
||||
const invAspect =
|
||||
const rawInvAspect =
|
||||
(Math.abs(desiredHeight) + 1e-23) / (Math.abs(desiredWidth) + 1e-23);
|
||||
|
||||
const invAspect = Math.min(
|
||||
MAX_ASPECT_RATIO,
|
||||
Math.max(MIN_ASPECT_RATIO, rawInvAspect)
|
||||
);
|
||||
|
||||
// Calculate width and height that roughly satisfy the desired PIXEL_COUNT
|
||||
//
|
||||
// height = invAspect * width
|
||||
@@ -85,14 +96,12 @@ export function computeBlurHashUrl(
|
||||
// oxlint-disable-next-line no-bitwise
|
||||
width <<= 2;
|
||||
|
||||
// Give at least two pixels of width to show gradients
|
||||
width = Math.max(2, width);
|
||||
width = Math.max(MIN_DIMENSION, width);
|
||||
|
||||
let height = width * invAspect;
|
||||
height = Math.round(height);
|
||||
|
||||
// Minimum two pixels of height for gradients
|
||||
height = Math.max(2, height);
|
||||
height = Math.max(MIN_DIMENSION, height);
|
||||
|
||||
const rgba = decode(blurHash, width, height);
|
||||
const bgrSize = (rgba.byteLength / 4) * 3;
|
||||
|
||||
Reference in New Issue
Block a user