Browse Source

test: offscreen rendering background colors

Samuel Maddock 2 months ago
parent
commit
5b32118711
3 changed files with 71 additions and 7 deletions
  1. 42 2
      spec/api-browser-window-spec.ts
  2. 20 0
      spec/lib/image-helpers.ts
  3. 9 5
      spec/lib/screen-helpers.ts

+ 42 - 2
spec/api-browser-window-spec.ts

@@ -1,4 +1,4 @@
-import { nativeImage } from 'electron';
+import { NativeImage, nativeImage } from 'electron';
 import { app, BrowserWindow, BrowserView, dialog, ipcMain, OnBeforeSendHeadersListenerDetails, net, protocol, screen, webContents, webFrameMain, session, WebContents, WebFrameMain } from 'electron/main';
 
 import { expect } from 'chai';
@@ -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 { previewNativeImage } 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';
 
@@ -6439,6 +6440,9 @@ describe('BrowserWindow module', () => {
   });
 
   describe('offscreen rendering', () => {
+    // Toggle to display windows previewing OSR NativeImage results
+    const DEBUG_OFFSCREEN = false;
+
     let w: BrowserWindow;
     beforeEach(function () {
       w = new BrowserWindow({
@@ -6453,18 +6457,54 @@ describe('BrowserWindow module', () => {
     });
     afterEach(closeAllWindows);
 
+    const preview = async (image: NativeImage) => {
+      if (DEBUG_OFFSCREEN) {
+        await previewNativeImage(image);
+      }
+    };
+
     it('creates offscreen window with correct size', 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;
       expect(data.constructor.name).to.equal('NativeImage');
       expect(data.isEmpty()).to.be.false('data is empty');
+      await preview(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);
+      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 preview(data);
+      expect(getPixelColor(data, { x: 0, y: 0 }, true)).to.equal('#ff0000ff');
+    });
+
+    it('creates offscreen window with transparent background', async () => {
+      w.setBackgroundColor(HexColors.TRANSPARENT);
+      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 preview(data);
+      expect(getPixelColor(data, { x: 0, y: 0 }, true)).to.equal(HexColors.TRANSPARENT);
+    });
+
+    // Semi-transparent background is not supported
+    it.skip('creates offscreen window with semi-transparent background', async () => {
+      const bgColor = '#66ffffff'; // ARGB
+      w.setBackgroundColor(bgColor);
+      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 preview(data);
+      expect(getPixelColor(data, { x: 0, y: 0 }, true)).to.equal(bgColor);
+    });
+
     it('does not crash after navigation', () => {
       w.webContents.loadURL('about:blank');
       w.loadFile(path.join(fixtures, 'api', 'offscreen-rendering.html'));

+ 20 - 0
spec/lib/image-helpers.ts

@@ -0,0 +1,20 @@
+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.
+ */
+export async function previewNativeImage (image: NativeImage) {
+  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');
+};

+ 9 - 5
spec/lib/screen-helpers.ts

@@ -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