From 92a1e6a967db41b8ddf22aa9af67d5029b0ff042 Mon Sep 17 00:00:00 2001 From: Sven Hettwer Date: Fri, 4 Dec 2020 18:05:34 +0100 Subject: [PATCH] (#154) added "captureRegion" to Screen API --- CHANGELOG.md | 3 ++ lib/screen.class.spec.ts | 101 +++++++++++++++++++++++++++++++++++---- lib/screen.class.ts | 48 +++++++++++++++++-- 3 files changed, 140 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c039b54..7c8108b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. +## upcomming release +- Feature: Create screenshot from region [(#154)](https://github.com/nut-tree/nut.js/issues/154) + ## 1.5.0 - Enhancement: Window support [(#5)](https://github.com/nut-tree/nut.js/issues/5) diff --git a/lib/screen.class.spec.ts b/lib/screen.class.spec.ts index 12e6e30c..2c55bded 100644 --- a/lib/screen.class.spec.ts +++ b/lib/screen.class.spec.ts @@ -1,12 +1,14 @@ -import {join} from "path"; -import {cwd} from "process"; -import {VisionAdapter} from "./adapter/vision.adapter.class"; -import {Image} from "./image.class"; -import {LocationParameters} from "./locationparameters.class"; -import {MatchRequest} from "./match-request.class"; -import {MatchResult} from "./match-result.class"; -import {Region} from "./region.class"; -import {Screen} from "./screen.class"; +import { join } from "path"; +import { cwd } from "process"; +import { VisionAdapter } from "./adapter/vision.adapter.class"; +import { Image } from "./image.class"; +import { LocationParameters } from "./locationparameters.class"; +import { MatchRequest } from "./match-request.class"; +import { MatchResult } from "./match-result.class"; +import { Region } from "./region.class"; +import { Screen } from "./screen.class"; +import { mockPartial } from "sneer"; +import { FileType } from "./file-type.enum"; jest.mock("./adapter/native.adapter.class"); jest.mock("./adapter/vision.adapter.class"); @@ -269,4 +271,85 @@ describe("Screen.", () => { expect(matchRegion).toEqual(expectedMatchRegion); }) + describe("capture",() => { + it("should capture the whole screen and save image", async() => { + + // GIVEN + const screenshot = mockPartial({data: "pretty pretty image"}); + VisionAdapter.prototype.grabScreen = jest.fn(() => Promise.resolve(screenshot)); + VisionAdapter.prototype.saveImage = jest.fn(); + const visionAdapterMock = new VisionAdapter(); + const SUT = new Screen(visionAdapterMock); + const imageName = "foobar.png" + const expectedImagePath = join(cwd(), imageName) + + // WHEN + const imagePath = await SUT.capture(imageName) + + // THEN + expect(imagePath).toBe(expectedImagePath) + expect(VisionAdapter.prototype.grabScreen).toHaveBeenCalled() + expect(VisionAdapter.prototype.saveImage).toHaveBeenCalledWith(screenshot,expectedImagePath) + }) + + it("should consider output configuration", async () => { + + // GIVEN + const visionAdapterMock = new VisionAdapter(); + const SUT = new Screen(visionAdapterMock); + const imageName = "foobar" + const filePath = "/path/to/file" + const prefix = "answer_" + const postfix = "_42" + const expectedImagePath = join(filePath, `${prefix}${imageName}${postfix}${FileType.JPG.toString()}`) + + // WHEN + const imagePath = await SUT.capture(imageName, FileType.JPG, filePath, prefix, postfix) + + // THEN + expect(imagePath).toBe(expectedImagePath) + }) + }) + + describe("captureRegion", () => { + + it("should capture the specified region of the screen and save image", async () => { + // GIVEN + const screenshot = mockPartial({data: "pretty partial image"}); + const regionToCapture = mockPartial({top:42, left:9, height: 10, width: 3.14159265359}) + VisionAdapter.prototype.grabScreenRegion = jest.fn(() => Promise.resolve(screenshot)); + VisionAdapter.prototype.saveImage = jest.fn(); + const visionAdapterMock = new VisionAdapter(); + const SUT = new Screen(visionAdapterMock); + const imageName = "foobar.png" + const expectedImagePath = join(cwd(), imageName) + + // WHEN + const imagePath = await SUT.captureRegion(imageName, regionToCapture) + + // THEN + expect(imagePath).toBe(expectedImagePath) + expect(VisionAdapter.prototype.grabScreenRegion).toHaveBeenCalledWith(regionToCapture) + expect(VisionAdapter.prototype.saveImage).toHaveBeenCalledWith(screenshot,expectedImagePath) + }) + + it("should consider output configuration", async () => { + + // GIVEN + const regionToCapture = mockPartial({top:42, left:9, height: 10, width: 3.14159265359}) + const visionAdapterMock = new VisionAdapter(); + const SUT = new Screen(visionAdapterMock); + const imageName = "foobar" + const filePath = "/path/to/file" + const prefix = "answer_" + const postfix = "_42" + const expectedImagePath = join(filePath, `${prefix}${imageName}${postfix}${FileType.JPG.toString()}`) + + // WHEN + const imagePath = await SUT.captureRegion(imageName, regionToCapture, FileType.JPG, filePath, prefix, postfix) + + // THEN + expect(imagePath).toBe(expectedImagePath) + }) + }) }); diff --git a/lib/screen.class.ts b/lib/screen.class.ts index 79506b70..e2117813 100644 --- a/lib/screen.class.ts +++ b/lib/screen.class.ts @@ -8,6 +8,7 @@ import {MatchRequest} from "./match-request.class"; import {MatchResult} from "./match-result.class"; import {Region} from "./region.class"; import {timeout} from "./util/poll-action.function"; +import { Image } from "./image.class"; export type FindHookCallback = (target: MatchResult) => Promise; @@ -181,15 +182,56 @@ export class Screen { filePath: string = cwd(), fileNamePrefix: string = "", fileNamePostfix: string = ""): Promise { + const currentScreen = await this.vision.grabScreen(); + return this.saveImage( + currentScreen, + fileName, + fileFormat, + filePath, + fileNamePrefix, + fileNamePostfix); + } + + /** + * {@link captureRegion} captures a screenshot of a region on the systems main display + * @param fileName Basename for the generated screenshot + * @param regionToCapture The region of the screen to capture in the screenshot + * @param fileFormat The {@link FileType} for the generated screenshot + * @param filePath The output path for the generated screenshot (Default: {@link cwd}) + * @param fileNamePrefix Filename prefix for the generated screenshot (Default: empty) + * @param fileNamePostfix Filename postfix for the generated screenshot (Default: empty) + */ + public async captureRegion( + fileName: string, + regionToCapture: Region, + fileFormat: FileType = FileType.PNG, + filePath: string = cwd(), + fileNamePrefix: string = "", + fileNamePostfix: string = ""): Promise { + const regionImage = await this.vision.grabScreenRegion(regionToCapture); + return this.saveImage( + regionImage, + fileName, + fileFormat, + filePath, + fileNamePrefix, + fileNamePostfix); + } + + private async saveImage( + image: Image, + fileName: string, + fileFormat: FileType, + filePath: string, + fileNamePrefix: string , + fileNamePostfix: string){ const outputPath = generateOutputPath(fileName, { path: filePath, postfix: fileNamePostfix, prefix: fileNamePrefix, type: fileFormat, }); - - const currentScreen = await this.vision.grabScreen(); - await this.vision.saveImage(currentScreen, outputPath); + await this.vision.saveImage(image, outputPath); return outputPath; } }