Skip to content

Commit

Permalink
typescript screenshot stitcher
Browse files Browse the repository at this point in the history
  • Loading branch information
stacey-gammon committed Jun 20, 2018
1 parent fbbd5c1 commit d02a24d
Show file tree
Hide file tree
Showing 13 changed files with 346 additions and 176 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -227,12 +227,14 @@
"@kbn/eslint-plugin-license-header": "link:packages/kbn-eslint-plugin-license-header",
"@kbn/plugin-generator": "link:packages/kbn-plugin-generator",
"@kbn/test": "link:packages/kbn-test",
"@types/bluebird": "^3.1.1",
"@types/eslint": "^4.16.2",
"@types/execa": "^0.9.0",
"@types/getopts": "^2.0.0",
"@types/glob": "^5.0.35",
"@types/listr": "^0.13.0",
"@types/minimatch": "^2.0.29",
"@types/pngjs": "^3.3.1",
"@types/react": "^16.3.14",
"@types/react-dom": "^16.0.5",
"angular-mocks": "1.4.7",
Expand Down
10 changes: 10 additions & 0 deletions packages/kbn-es/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,12 @@ readable-stream@^2.0.0, readable-stream@^2.0.5:
string_decoder "~1.0.3"
util-deprecate "~1.0.1"

rxjs@^6.1.0:
version "6.2.1"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.2.1.tgz#246cebec189a6cbc143a3ef9f62d6f4c91813ca1"
dependencies:
tslib "^1.9.0"

safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
Expand Down Expand Up @@ -322,6 +328,10 @@ tree-kill@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36"

tslib@^1.9.0:
version "1.9.2"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.2.tgz#8be0cc9a1f6dc7727c38deb16c2ebd1a2892988e"

util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export class HeadlessChromiumDriver {
scale: 1
}
});
this._logger.debug(`captured screenshot clip ${JSON.stringify(screenshotClip)}`);
this._logger.debug(`Captured screenshot clip ${JSON.stringify(screenshotClip)}`);
return data;
}, this._logger);
}
Expand All @@ -112,6 +112,7 @@ export class HeadlessChromiumDriver {
}

async setViewport({ width, height, zoom }) {
this._logger.debug(`Setting viewport to width: ${width}, height: ${height}, zoom: ${zoom}`);
const { Emulation } = this._client;

await Emulation.setDeviceMetricsOverride({
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

// No types found for this package. May want to investigate an alternative with types.
// @ts-ignore: implicit any for JS file
import $streamToObservable from '@samverschueren/stream-to-observable';
import { PNG } from 'pngjs';
import * as Rx from 'rxjs';
import { ObservableInput } from 'rxjs';
import { map, mergeMap, reduce, switchMap, tap, toArray } from 'rxjs/operators';
import { Logger, Screenshot, Size } from './types';

// if we're only given one screenshot, and it matches the output dimensions
// we're going to skip the combination and just use it
const canUseFirstScreenshot = (
screenshots: Screenshot[],
size: { width: number; height: number }
) => {
if (screenshots.length !== 1) {
return false;
}

const firstScreenshot = screenshots[0];
return (
firstScreenshot.dimensions.width === size.width &&
firstScreenshot.dimensions.height === size.height
);
};

/**
* Combines the screenshot clips into a single screenshot of size `outputDimensions`.
* @param screenshots - Array of screenshots to combine
* @param outputSize - Final output size that the screenshots should match up with
* @param logger - logger for extra debug output
*/
export function $combine(
screenshots: Screenshot[],
outputSize: Size,
logger: Logger
): Rx.Observable<string> {
logger.debug(
`Combining screenshot clips into final, scaled output dimension of ${JSON.stringify(
outputSize
)}`
);

if (screenshots.length === 0) {
return Rx.throwError('Unable to combine 0 screenshots');
}

if (canUseFirstScreenshot(screenshots, outputSize)) {
return Rx.of(screenshots[0].data);
}

// Turn the screenshot data into actual PNGs
const pngs$ = Rx.from(screenshots).pipe(
mergeMap(
(screenshot: Screenshot): ObservableInput<PNG> => {
const png = new PNG();
const buffer = Buffer.from(screenshot.data, 'base64');
const parseAsObservable = Rx.bindNodeCallback(png.parse.bind(png));
return parseAsObservable(buffer);
},
(screenshot: Screenshot, png: PNG) => ({ screenshot, png })
)
);

const output$ = pngs$.pipe(
reduce((output: PNG, input: { screenshot: Screenshot; png: PNG }) => {
const { png, screenshot } = input;
// Spitting out a lot of output to help debug https://github.com/elastic/kibana/issues/19563. Once that is
// fixed, this should probably get pared down.
logger.debug(`Output dimensions is ${JSON.stringify(outputSize)}`);
logger.debug(`Input png w: ${png.width} and h: ${png.height}`);
logger.debug(
`Creating output png with ${JSON.stringify(screenshot.dimensions)}`
);
const { dimensions } = screenshot;
png.bitblt(
output,
0,
0,
dimensions.width,
dimensions.height,
dimensions.x,
dimensions.y
);
return output;
}, new PNG({ width: outputSize.width, height: outputSize.height }))
);

return output$.pipe(
tap(png => png.pack()),
switchMap<PNG, Buffer>($streamToObservable),
toArray(),
map((chunks: Buffer[]) => Buffer.concat(chunks).toString('base64'))
);
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,26 @@

import { toArray } from 'rxjs/operators';
import { $getClips } from './get_clips';
import { Dimension } from './types';

function getClipsTest(description, { dimensions, max }, { clips: expectedClips }) {
function getClipsTest(
description: string,
input: { dimensions: Dimension; max: number },
expectedClips: { clips: Dimension[] }
) {
test(description, async () => {
const clips = await $getClips(dimensions, max).pipe(toArray()).toPromise();
expect(clips.length).toBe(expectedClips.length);
const clips = await $getClips(input.dimensions, input.max)
.pipe(toArray())
.toPromise();
expect(clips.length).toBe(expectedClips.clips.length);
for (let i = 0; i < clips.length; ++i) {
expect(clips[i]).toEqual(expectedClips[i]);
expect(clips[i]).toEqual(expectedClips.clips[i]);
}
});
}

getClipsTest(`creates one rect if 0, 0`,
getClipsTest(
`creates one rect if 0, 0`,
{
dimensions: { x: 0, y: 0, height: 0, width: 0 },
max: 100,
Expand All @@ -27,7 +35,8 @@ getClipsTest(`creates one rect if 0, 0`,
}
);

getClipsTest(`creates one rect if smaller than max`,
getClipsTest(
`creates one rect if smaller than max`,
{
dimensions: { x: 0, y: 0, height: 99, width: 99 },
max: 100,
Expand All @@ -37,7 +46,8 @@ getClipsTest(`creates one rect if smaller than max`,
}
);

getClipsTest(`create one rect if equal to max`,
getClipsTest(
`create one rect if equal to max`,
{
dimensions: { x: 0, y: 0, height: 100, width: 100 },
max: 100,
Expand All @@ -47,33 +57,36 @@ getClipsTest(`create one rect if equal to max`,
}
);

getClipsTest(`creates two rects if width is 1.5 * max`,
getClipsTest(
`creates two rects if width is 1.5 * max`,
{
dimensions: { x: 0, y: 0, height: 100, width: 150 },
max: 100,
},
{
clips: [
{ x: 0, y: 0, height: 100, width: 100 },
{ x: 100, y: 0, height: 100, width: 50 }
{ x: 100, y: 0, height: 100, width: 50 },
],
}
);

getClipsTest(`creates two rects if height is 1.5 * max`,
getClipsTest(
`creates two rects if height is 1.5 * max`,
{
dimensions: { x: 0, y: 0, height: 150, width: 100 },
max: 100,
},
{
clips: [
{ x: 0, y: 0, height: 100, width: 100 },
{ x: 0, y: 100, height: 50, width: 100 }
{ x: 0, y: 100, height: 50, width: 100 },
],
}
);

getClipsTest(`created four rects if height and width is 1.5 * max`,
getClipsTest(
`created four rects if height and width is 1.5 * max`,
{
dimensions: { x: 0, y: 0, height: 150, width: 150 },
max: 100,
Expand All @@ -88,14 +101,13 @@ getClipsTest(`created four rects if height and width is 1.5 * max`,
}
);

getClipsTest(`creates one rect if height and width is equal to max and theres a y equal to the max`,
getClipsTest(
`creates one rect if height and width is equal to max and theres a y equal to the max`,
{
dimensions: { x: 0, y: 100, height: 100, width: 100 },
max: 100,
},
{
clips: [
{ x: 0, y: 100, height: 100, width: 100 },
],
clips: [{ x: 0, y: 100, height: 100, width: 100 }],
}
);
Loading

0 comments on commit d02a24d

Please sign in to comment.