Skip to content

Commit 330d518

Browse files
authored
feat: improve calibrator to work correctly on ios simulators and camera cropping logic (#1239)
* feat: improve calibrator to work correctly on ios simulators and camera cropping logic * test: fix camera tests
1 parent 9eb8de4 commit 330d518

7 files changed

Lines changed: 284 additions & 145 deletions

File tree

src/browser/calibrator.ts

Lines changed: 87 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,21 @@ import looksSame from "looks-same";
44
import { CoreError } from "./core-error";
55
import { ExistingBrowser } from "./existing-browser";
66
import type { Image } from "../image";
7-
8-
const DIRECTION = { FORWARD: "forward", REVERSE: "reverse" } as const;
7+
import { Coord, Length, Rect, XBand, getHeight, getIntersection, getWidth } from "./isomorphic";
8+
import * as logger from "../utils/logger";
9+
import os from "node:os";
910

1011
interface BrowserFeatures {
1112
needsCompatLib: boolean;
1213
pixelRatio: number;
13-
innerWidth: number;
14+
innerWidth: Length<"css", "x">;
1415
}
1516

1617
export interface CalibrationResult extends BrowserFeatures {
17-
top: number;
18-
left: number;
18+
viewportArea: Rect<"image", "device">;
1919
usePixelRatio: boolean;
2020
}
2121

22-
interface ViewportStart {
23-
x: number;
24-
y: number;
25-
}
26-
27-
interface ImageAnalysisResult {
28-
viewportStart: ViewportStart;
29-
colorLength?: number;
30-
}
31-
3222
export class Calibrator {
3323
private _cache: Record<string, CalibrationResult>;
3424
private _script: string;
@@ -49,95 +39,130 @@ export class Calibrator {
4939

5040
const { innerWidth, pixelRatio } = features;
5141
const hasPixelRatio = Boolean(pixelRatio && pixelRatio > 1.0);
52-
const imageFeatures = await this._analyzeImage(image, { calculateColorLength: hasPixelRatio });
42+
const imageFeatures = await this._findMarkerAreaInImage(image);
5343

5444
if (!imageFeatures) {
45+
const screenshotPath = path.join(os.tmpdir(), "testplane-calibration-page.png");
46+
await image.save(screenshotPath);
47+
logger.error(
48+
"Could not calibrate, because marker area was not found. See calibration page screenshot for details: " +
49+
screenshotPath,
50+
);
51+
await image.save(screenshotPath);
5552
throw new CoreError(
5653
"Could not calibrate. This could be due to calibration page has failed to open properly",
5754
);
5855
}
5956

6057
const calibratedFeatures: CalibrationResult = {
6158
...features,
62-
top: imageFeatures.viewportStart.y,
63-
left: imageFeatures.viewportStart.x,
64-
usePixelRatio: hasPixelRatio && imageFeatures.colorLength! > innerWidth,
59+
viewportArea: imageFeatures,
60+
usePixelRatio: hasPixelRatio && imageFeatures.width > innerWidth,
6561
};
6662

6763
this._cache[browser.id] = calibratedFeatures;
6864
return calibratedFeatures;
6965
}
7066

71-
private async _analyzeImage(
72-
image: Image,
73-
params: { calculateColorLength?: boolean },
74-
): Promise<ImageAnalysisResult | null> {
67+
private async _findMarkerAreaInImage(image: Image): Promise<Rect<"image", "device"> | null> {
7568
const imageHeight = (await image.getSize()).height;
7669

77-
for (let y = 0; y < imageHeight; y++) {
78-
const result = await analyzeRow(y, image, params);
70+
let topPart: Rect<"image", "device"> | null = null;
71+
72+
for (let y = 0 as Coord<"image", "device", "y">; y < imageHeight; y++) {
73+
const result = await findMarkerXBandInRow(y, image);
7974
if (result) {
80-
return result;
75+
topPart = {
76+
top: y,
77+
left: result.left,
78+
width: result.width,
79+
height: getHeight(y, imageHeight as Coord<"image", "device", "y">),
80+
};
81+
break;
82+
}
83+
}
84+
85+
if (topPart === null) {
86+
return null;
87+
}
88+
89+
for (let y = (imageHeight - 1) as Coord<"image", "device", "y">; y >= 0; y--) {
90+
const result = await findMarkerXBandInRow(y, image);
91+
if (result) {
92+
const bottomPart = {
93+
top: 0,
94+
left: result.left,
95+
width: result.width,
96+
height: getHeight(0 as Coord<"image", "device", "y">, y),
97+
};
98+
99+
return getIntersection(topPart, bottomPart);
81100
}
82101
}
83102

84103
return null;
85104
}
86105
}
87106

88-
async function analyzeRow(
89-
row: number,
107+
async function findMarkerXBandInRow(
108+
row: Coord<"image", "device", "y">,
90109
image: Image,
91-
params: { calculateColorLength?: boolean } = {},
92-
): Promise<ImageAnalysisResult | null> {
93-
const markerStart = await findMarkerInRow(row, image, DIRECTION.FORWARD);
110+
): Promise<XBand<"image", "device"> | null> {
111+
const markerStart = await findMarkerStartInRow(row, image);
94112

95-
if (markerStart === -1) {
113+
if (markerStart === null) {
96114
return null;
97115
}
98116

99-
const result: ImageAnalysisResult = { viewportStart: { x: markerStart, y: row } };
117+
const markerEnd = await findMarkerEndInRow(row, image);
100118

101-
if (!params.calculateColorLength) {
102-
return result;
119+
if (markerEnd === null) {
120+
return null;
103121
}
104122

105-
const markerEnd = await findMarkerInRow(row, image, DIRECTION.REVERSE);
106-
const colorLength = markerEnd - markerStart + 1;
107-
108-
return { ...result, colorLength };
123+
return {
124+
left: markerStart,
125+
width: getWidth(markerStart, markerEnd),
126+
};
109127
}
110128

111-
async function findMarkerInRow(row: number, image: Image, searchDirection: "forward" | "reverse"): Promise<number> {
112-
const imageWidth = (await image.getSize()).width;
129+
async function isMarkerColorAtPoint(
130+
image: Image,
131+
x: Coord<"image", "device", "x">,
132+
y: Coord<"image", "device", "y">,
133+
): Promise<boolean> {
113134
const searchColor = { R: 148, G: 250, B: 0 };
135+
const color = await image.getRGB(x, y);
114136

115-
if (searchDirection === DIRECTION.REVERSE) {
116-
return searchReverse_();
117-
} else {
118-
return searchForward_();
119-
}
137+
return looksSame.colors(color, searchColor);
138+
}
120139

121-
async function searchForward_(): Promise<number> {
122-
for (let x = 0; x < imageWidth; x++) {
123-
if (await compare_(x)) {
124-
return x;
125-
}
140+
async function findMarkerStartInRow(
141+
row: Coord<"image", "device", "y">,
142+
image: Image,
143+
): Promise<Coord<"image", "device", "x"> | null> {
144+
const imageWidth = (await image.getSize()).width;
145+
146+
for (let x = 0 as Coord<"image", "device", "x">; x < imageWidth; x++) {
147+
if (await isMarkerColorAtPoint(image, x, row)) {
148+
return x;
126149
}
127-
return -1;
128150
}
129151

130-
async function searchReverse_(): Promise<number> {
131-
for (let x = imageWidth - 1; x >= 0; x--) {
132-
if (await compare_(x)) {
133-
return x;
134-
}
152+
return null;
153+
}
154+
155+
async function findMarkerEndInRow(
156+
row: Coord<"image", "device", "y">,
157+
image: Image,
158+
): Promise<Coord<"image", "device", "x"> | null> {
159+
const imageWidth = (await image.getSize()).width;
160+
161+
for (let x = (imageWidth - 1) as Coord<"image", "device", "x">; x >= 0; x--) {
162+
if (await isMarkerColorAtPoint(image, x, row)) {
163+
return x;
135164
}
136-
return -1;
137165
}
138166

139-
async function compare_(x: number): Promise<boolean> {
140-
const color = await image.getRGB(x, row);
141-
return looksSame.colors(color, searchColor);
142-
}
167+
return null;
143168
}

0 commit comments

Comments
 (0)