Compare commits

...

3 Commits

Author SHA1 Message Date
Samuel Maddock
ea90dd1f2b maybe wait to see if a frame eventually matches? 2025-01-22 19:10:45 -05:00
Samuel Maddock
213328f00e refactor: use DEBUG_PREVIEW_IMAGE env var 2025-01-22 19:07:52 -05:00
Samuel Maddock
5b32118711 test: offscreen rendering background colors 2025-01-22 11:28:50 -05:00
3 changed files with 73 additions and 6 deletions

View File

@@ -16,7 +16,8 @@ import { setTimeout } from 'node:timers/promises';
import * as nodeUrl from 'node:url';
import { emittedUntil, emittedNTimes } from './lib/events-helpers';
import { HexColors, hasCapturableScreen, ScreenCapture } from './lib/screen-helpers';
import { debugPreviewImage } from './lib/image-helpers';
import { HexColors, hasCapturableScreen, ScreenCapture, getPixelColor } from './lib/screen-helpers';
import { ifit, ifdescribe, defer, listen, waitUntil } from './lib/spec-helpers';
import { closeWindow, closeAllWindows } from './lib/window-helpers';
@@ -6459,12 +6460,51 @@ describe('BrowserWindow module', () => {
const [, , data] = await paint;
expect(data.constructor.name).to.equal('NativeImage');
expect(data.isEmpty()).to.be.false('data is empty');
await debugPreviewImage(data);
const size = data.getSize();
const { scaleFactor } = screen.getPrimaryDisplay();
expect(size.width).to.be.closeTo(100 * scaleFactor, 2);
expect(size.height).to.be.closeTo(100 * scaleFactor, 2);
});
it('creates offscreen window with opaque background', async () => {
w.setBackgroundColor(HexColors.RED);
await waitUntil(async () => {
const paint = once(w.webContents, 'paint') as Promise<[any, Electron.Rectangle, Electron.NativeImage]>;
w.loadFile(path.join(fixtures, 'api', 'offscreen-rendering.html'));
const [, , data] = await paint;
await debugPreviewImage(data);
expect(getPixelColor(data, { x: 0, y: 0 }, true)).to.equal('#ff0000ff');
return true;
});
});
it('creates offscreen window with transparent background', async () => {
w.setBackgroundColor(HexColors.TRANSPARENT);
await waitUntil(async () => {
const paint = once(w.webContents, 'paint') as Promise<[any, Electron.Rectangle, Electron.NativeImage]>;
w.loadFile(path.join(fixtures, 'api', 'offscreen-rendering.html'));
const [, , data] = await paint;
await debugPreviewImage(data);
expect(getPixelColor(data, { x: 0, y: 0 }, true)).to.equal(HexColors.TRANSPARENT);
return true;
});
});
// Semi-transparent background is not supported
it.skip('creates offscreen window with semi-transparent background', async () => {
const bgColor = '#66ffffff'; // ARGB
w.setBackgroundColor(bgColor);
await waitUntil(async () => {
const paint = once(w.webContents, 'paint') as Promise<[any, Electron.Rectangle, Electron.NativeImage]>;
w.loadFile(path.join(fixtures, 'api', 'offscreen-rendering.html'));
const [, , data] = await paint;
await debugPreviewImage(data);
expect(getPixelColor(data, { x: 0, y: 0 }, true)).to.equal(bgColor);
return true;
});
});
it('does not crash after navigation', () => {
w.webContents.loadURL('about:blank');
w.loadFile(path.join(fixtures, 'api', 'offscreen-rendering.html'));

23
spec/lib/image-helpers.ts Normal file
View File

@@ -0,0 +1,23 @@
import { BaseWindow, NativeImage } from 'electron';
import { once } from 'node:events';
/**
* Opens a window to display a native image. Useful for quickly debugging tests
* rather than writing a file and opening manually.
*
* Set the `DEBUG_PREVIEW_IMAGE` environment variable to show previews.
*/
export async function debugPreviewImage (image: NativeImage) {
if (!process.env.DEBUG_PREVIEW_IMAGE) return;
const previewWindow = new BaseWindow({
title: 'NativeImage preview',
backgroundColor: '#444444'
});
const ImageView = (require('electron') as any).ImageView;
const imgView = new ImageView();
imgView.setImage(image);
previewWindow.contentView.addChildView(imgView);
imgView.setBounds({ x: 0, y: 0, ...image.getSize() });
await once(previewWindow, 'close');
};

View File

@@ -10,6 +10,7 @@ export enum HexColors {
RED = '#ff0000',
BLUE = '#0000ff',
WHITE = '#ffffff',
TRANSPARENT = '#00000000'
}
function hexToRgba (
@@ -35,9 +36,10 @@ function formatHexByte (val: number): string {
/**
* Get the hex color at the given pixel coordinate in an image.
*/
function getPixelColor (
export function getPixelColor (
image: Electron.NativeImage,
point: Electron.Point
point: Electron.Point,
includeAlpha: boolean = false
): string {
// image.crop crashes if point is fractional, so round to prevent that crash
const pixel = image.crop({
@@ -48,8 +50,10 @@ function getPixelColor (
});
// TODO(samuelmaddock): NativeImage.toBitmap() should return the raw pixel
// color, but it sometimes differs. Why is that?
const [b, g, r] = pixel.toBitmap();
return `#${formatHexByte(r)}${formatHexByte(g)}${formatHexByte(b)}`;
const [b, g, r, a] = pixel.toBitmap();
let hex = `#${formatHexByte(r)}${formatHexByte(g)}${formatHexByte(b)}`;
if (includeAlpha) hex += `${formatHexByte(a)}`;
return hex;
}
/** Calculate euclidean distance between colors. */
@@ -68,7 +72,7 @@ function colorDistance (hexColorA: string, hexColorB: string): number {
* Determine if colors are similar based on distance. This can be useful when
* comparing colors which may differ based on lossy compression.
*/
function areColorsSimilar (
export function areColorsSimilar (
hexColorA: string,
hexColorB: string,
distanceThreshold = 90