Tutorials

Image Search

nut.js allows you to locate template images on your screen, a key capability for automation.

ImageFinder Providers


To do so, we will have to install an additional package, providing the actual implementation to perform image comparison. Otherwise, all functions relying on image matching will throw an error like Error: No ImageFinder registered.

nut.js provides a ready-to-use image finder implementation: @nut-tree/nl-matcher.

Attention: @nut-tree/nl-matcher is a nut.js premium package which requires an active subscription. Every subscription comes with a 14 day trial period, so you can try it out without any risk. See the registry access tutorial to learn how to subscribe and access the private registry.

npm i @nut-tree/nl-matcher

To use this provider package, simply require it in your code, e.g. index.js:

const {screen, imageResource} = require("@nut-tree/nut-js");
require("@nut-tree/nl-matcher"); // THIS IS NEW

(async () => {
    try {
        await screen.find(imageResource("img.png"));
    } catch (e) {
        console.error(e);
    }
})();

Working with template images


In order to search for an image on your screen, we have to provide a template Image.

These images can either be loaded via their full path and loadImage, or relative to a configurable resource directory.

Resource Directory

When working with a resource directory, you can reference template images by filename, omitting the full path. However, when loading a template image, these filenames are relative to screen.config.resourceDirectory.

screen.config.resourceDirectory = "/path/to/my/template/images"

If not configured explicitly, screen.config.resourceDirectory is set to the current working directory.

Loading Images from Resource Directory

Instead of using loadImage, so called image resources are loaded via imageResource.

Fetch Images from a Remote Host

fetchFromUrl allows you to pass in a URL to an image located on a remote host that will be fetched and returned as nut.js Image.

find


Template images are Images either directly loaded using their full path, relative to a configurable resource directory or from a remote host via fetchFromUrl.

finding images

Let's dissect how screen.find works with images by looking at a sample:

const {screen, imageResource} = require("@nut-tree/nut-js");
require("@nut-tree/nl-matcher");

(async () => {
    screen.config.resourceDirectory = "/resouce/path";
    try {
        const region = await screen.find(imageResource("mouse.png"));
        console.log(region);
    } catch (e) {
        console.error(e);
    }
})();

First things first, we're setting up our imports on line 1 and 2.

Line 5 sets our resourceDirectory, although the most interesting thing happens in line 7: actually searching the image.

screen.find will scan your main screen for the provided template image and if it finds a match, it'll return the Region it located the template image in. Images are matched on a per-pixel basis. The amount of matching pixels is configurable via the confidence property on the config object. confidence is expected to be a value between 0 and 1, it defaults to 0.99 (which corresponds to a 99% match).

!> nut.js currently does not support multi-monitor setups

The Cross-Platform Trick

The resource directory might seem confusing at first, but it actually has a really nice side effect. Imagine writing a cross-platform automation script where we're dealing with different UIs and therefore different template images.

Using the resource directory, we can configure our directory depending on our current platform:

screen.config.resourceDirectory = `/path/to/the/project/${process.platform}`;

This way, we can keep all our platform-specific template images in separate folders, but we don't have to actually care in our code.

By using the platform dependent resource directory, we don't have to deal with platform specific filenames. The same filename will load the correct template image for the current platform, no further action required! 💪

Troubleshooting

In case we screwed up, nut.js will let us know by rejecting.

Wrong resource directory

Searching for mouse.png failed. Reason: 'Error: Failed to load /foo/bar/mouse.png. Reason: 'Failed to load image from '/foo/bar/mouse.png''.'

No match

Searching for mouse.png failed. Reason: 'Error: No match with required confidence 0.99. Best match: 0 at (0, 0, 477, 328)'

Summary

  • nut.js provides a screen instance to search for template images on your screen.
  • The directory where to load your template images from is configurable via the config object.
  • It will search your main screen for the template image and if it finds a match, it'll return the Region it located the template image in.
  • The amount of matching pixels is configurable via the confidence property on the config object.

findAll


findAll is used very similarly to find. The major difference between the two is the fact that findAll will return a list of all detected matches on your main screen.

Everything else mentioned forfind applies to findAll as well.

waitFor


Being able to locate images on our screen is a huge benefit when automating things, but in reality, we have to deal with timing. waitFor is here to help by allowing us to specify a timeout in which we expect our template image to appear on screen!

Template images are Images either directly loaded using their full path, relative to a configurable resource directory or from a remote host via fetchFromUrl.

Waiting for images

Let's tweak the snippet used in the find example just a little:

const {screen, imageResource} = require("@nut-tree/nut-js");
require("@nut-tree/nl-matcher");

(async () => {
    screen.config.resourceDirectory = "/resouce/path";
    try {
        const region = await screen.waitFor(imageResource("mouse.png"));
        console.log(region);
    } catch (e) {
        console.error(e);
    }
})();

waitFor basically does the exact same as find, but multiple times over a specified period of time.

It'll scan your main screen for the given template image, but if it fails to find it, it'll simply give it another shot. The interval in which these retries happen is configurable as well.

const {screen, imageResource} = require("@nut-tree/nut-js");
require("@nut-tree/nl-matcher");

(async () => {
    screen.config.resourceDirectory = "/resouce/path";
    try {
        const region = await screen.waitFor(imageResource("mouse.png"), 5000, 1000);
        console.log(region);
    } catch (e) {
        console.error(e);
    }
})();

In the above snippet, we tell waitFor to look for our template image for at most five seconds, retrying every second.

If it still couldn't locate the image after the configured timeout in milliseconds, it'll reject. Otherwise, it'll return the Region it located the image in, just like find.

Troubleshooting

Everything mentioned on find applies to waitFor as well.

Timeout

Action timed out after 5000 ms

Summary

  • waitFor will repeatedly search your main screen for the template image and if it finds a match, it'll return the Region it located the template image in.
  • If it can't locate the image, it'll retry the search in configurable intervals until it hits the configured timeout in milliseconds.

Cancelling waitFor


As we learned earlier, waitFor will repeatedly search our screen for a given template image.

This great flexibility does not come for free, so we might not want to wait for the timeout to fire before we can cancel the ongoing search. nut.js follows the same approach to cancellation as the browser fetch API, using an AbortController.

Before we can actually look at a sample, we will have to install an additional package to our project:

npm i node-abort-controller

Now, let's take a look at a (rather artificial) example:

const {screen, Region, imageResource} = require("@nut-tree/nut-js");
require("@nut-tree/nl-matcher");
const {AbortController} = require("node-abort-controller");

(async () => {
    const controller = new AbortController();
    screen.waitFor(imageResource("test.png"), 5000, 1000, {abort: controller.signal});
    setTimeout(() => controller.abort(), 2000);
})();

We instantiate our AbortController in line 6 and pass its signal as an OptionalSearchParameter to waitFor.

waitFor has a timeout of 5000 milliseconds configured, retrying after 1000 milliseconds, but after 2000 milliseconds, we call abort() on our AbortController, which will cancel the ongoing search:

Action aborted by signal

Summary

highlight


Especially during development, we might want to visually track what happens when executing our script. When it comes to image search, it's one thing to see in e.g. the log that we found a match, but a visual indicator would be even better.

highlight is exactly this!

Configuration

highlight works by overlaying a Region of interest with an opaque highlight window.

Highlight duration and opacity are once again configurable properties on the screen.config object.

Highlighting regions

highlight receives a Region specifying the area to highlight. It will then overlay the given region with an opaque highlight window.

const {screen, Region} = require("@nut-tree/nut-js");

(async () => {
    screen.config.highlightDurationMs = 3000;
    const highlightRegion = new Region(50, 100, 200, 300);
    await screen.highlight(highlightRegion);
})();

Auto Highlighting

The way the API is structured, it's really easy to highlight regions located by e.g. find:

const {screen, imageResource} = require("@nut-tree/nut-js");
require("@nut-tree/nl-matcher");

(async () => {
    screen.config.resourceDirectory = "/resouce/path";
    screen.config.highlightDurationMs = 3000;
    await screen.highlight(screen.find(imageResource("image.png")));
})();

However, manually adding highlights is not only cumbersome, but also requires additional effort in case we want to remove it again before running our script in production.

Therefore, nut.js provides an auto-highlight mechanism which is toggleable via the config property. Highlight during development, disable it in production!

const {screen, imageResource} = require("@nut-tree/nut-js");
require("@nut-tree/nl-matcher");

(async () => {
    screen.config.resourceDirectory = "/resouce/path";
    screen.config.autoHighlight = true;
    screen.config.highlightDurationMs = 1500;
    await screen.find(imageResource("test.png"));
})();

With auto highlight turned on, we no longer have to care about manually highlighting find results. Once find returns a valid Region, it will be highlighted. And since waitFor reuses find, auto-highlight works there as well!

Summary

  • nut.js provides a way to visually debug image search results.
  • Both the highlight duration and the highlight window opacity are configurable via the config object.
  • Auto highlight will automatically highlight results returned from find.

find, findAll and waitFor accept OptionalSearchParameters to fine-tune the search.

This allows to e.g. limit the search space to a certain portion of your screen:

const {screen, Region, OptionalSearchParameters, imageResource} = require("@nut-tree/nut-js");
require("@nut-tree/nl-matcher");
const {AbortController} = require("node-abort-controller");

(async () => {
    // Configure the postion and size of the area you wish Nut to search
    const searchRegion = new Region(10, 10, 500, 500);

    // Configure the confidence you wish Nut to have before finding a match
    const confidence = 0.88;

    // Configure an Abort controller so that you can cancel the find operation at any time
    const controller = new AbortController();
    const {signal} = controller;

    // Feed your parameters into the OptionalSearchParameters constructor to make sure they fit the spec
    const fullSearchOptionsConfiguration = new OptionalSearchParameters(searchRegion, confidence, signal);

    // .find() will return the Region where it found a match based on your search parameters and provided Image data
    const matchRegion = await screen.find(imageResource("image.png"), fullSearchOptionsConfiguration);

    const cancelFindTimeout = setTimeout(() => {
        controller.abort();
    }, 5000);


})();

Multi-scale image search gives you resilience when switching between multiple screen resolutions, but also comes with a price. Compared to searching on a single scale, it might take substantially longer when searching through multiple scales.

Depending on your task at hand you might not need this additional flexibility, but instead want to benefit of a faster execution. See this benchmark for an example:

hyperfine --warmup 3 'node multi-scale.js' 'node single-scale.js' --show-output
Benchmark 1: node multi-scale.js
  Time (mean ± σ):     933.5 ms ±  10.4 ms    [User: 1647.4 ms, System: 433.8 ms]
  Range (min … max):   920.9 ms … 948.4 ms    10 runs

Benchmark 2: node single-scale.js
  Time (mean ± σ):     526.8 ms ±   9.3 ms    [User: 400.2 ms, System: 108.4 ms]
  Range (min … max):   514.3 ms … 544.4 ms    10 runs

Summary
  'node single-scale.js' ran
    1.77 ± 0.04 times faster than 'node multi-scale.js'
Previous
On Screen Search