Can be fine for fine-tuning parameters and image selections.
Purpose is for debugging and testing matches. Should be easy to both extend.
Not the most beautiful code
![Laughing :lol:](./images/smilies/icon_lol.gif)
Here is an example of result after searching with needles form this site:
Code: Select all
[
{fn: "img/t-01-qmark.png", txt: "qmark"},
{fn: "img/t-02-lol.png", txt: "lol", opt: {
sm: "coc",
dpc: 2,
sex: 1,
conf: 87
}},
{fn: "img/t-03-preview.png", txt: "preview"},
{fn: "img/t-04-bell.png", txt: "bell"},
],
The targets are marked with rectangles and crosses. (Questionmark, lol smiley, preview button and bell.)
![Image](https://i.ibb.co/tZspv9F/x1.png)
Code: Select all
/* vim:set noexpandtab tabstop=4 nosmarttab noshiftround shiftwidth=4 softtabstop=0: */
/* eslint no-unused-vars: off */
/* global Actionaz Algorithms Color Console
* Notify Execution include Point ProcessHandle RawData Rect Script Size Stdio */
/* eslint-disable-next-line no-redeclare */
/* global Image Window */
const AA = Object.create(null);
// Simple logger for shell:
(function() {
"use strict";
const colors = {
black: 31,
red: 31,
green: 32,
yellow: 33,
blue: 34,
purple: 35,
cyan: 36,
white: 37
};
AA.print = function(str, color, bold) {
var s = str,
c = colors[color],
b = bold ? "1;" : ""
;
if (c)
s = "\x1b[" + b + c + "m" + str + "\x1b[0m";
else if (b)
s = "\x1b[1m" + str + "\x1b[0m";
Stdio.print(s);
};
AA.abort = function (msg, color) {
if (msg)
AA.print(msg + "\n", color || "purple", 1);
AA.print("\nAborting. Bye!!\n", "red", 1);
Execution.stop();
};
AA.dbg = function (str) {
if (AA.debug)
Stdio.print(";; " + str + "\n");
};
AA.note = function(title, txt, nosleep) {
var myObject = new Notify({
title: title,
text: txt,
timeout: 1000
});
myObject.show();
// Notification closes when script exit
if (!nosleep)
Execution.sleep(3000);
};
})();
// MAIN Routine:
(function(opt) {
"use strict";
// Locate window by title.
// Try "tries" times before giving up.
// If bool sleep is truth, sleep 1500ms between
// each try.
const get_window = function (title, tries, sleep) {
var ret = null;
--tries;
if (sleep)
Execution.sleep(1500);
AA.dbg("get window[" + tries + "]: " + title);
const win = Window.find({
title: title,
titleMode: Window.Wildcard
});
if (win.length > 1) {
if (tries > 0)
return get_window(title, tries, 1);
AA.print("Found more then one window matching: " + title + "\n\n", "red", 1);
for (var i = 0; i < win.length; ++i)
Stdio.print(win[i].title() + "\n");
} else if (win.length < 1) {
if (tries > 0)
return get_window(title, tries, 1);
AA.print("Found NO window matching: " + title + "\n\n", "red", 1);
} else if (win.length === 1) {
ret = win[0];
}
return ret;
};
// Screenshot of win.
// Save to fn_out
const shoot = function (win, fn_out) {
const shot = Image.takeScreenshot(win);
if (!shot) {
AA.print("Unable to shoot window\n", "red", 1);
return null;
}
if (fn_out) {
AA.dbg("Save shot as: " + fn_out);
shot.saveToFile(fn_out);
}
return {win: win, shot: shot};
};
// Try to find Window specified by option:
// opt.win_title
// on success proceed with shot.
const shoot_target = function (fn_out) {
const win = get_window(opt.win_title, opt.tries);
if (!win)
return null;
AA.dbg(win);
return shoot(win, fn_out);
};
// win Bring Window to foreground.
// tw Sleep tw milliseconds after request
const win_to_fg = function (win, tw) {
win.setForeground();
Execution.sleep(tw ? tw : 300);
};
/*
* hay Image image to search
* needle Image image to search for
* opt Obj search options:
* sm: search method. coc, crc or sd
* conf: minimum confidence,
* dpc : downPyramidCount
* sex : search expansion
* max : maximum matches
* */
const IMG_SEARCH_METHOD = {
coc: "CorrelationCoefficient",
crc: "CrossCorrelation",
sd : "SquaredDifference"
};
const find_subimages = function (hay, needle, opt) {
opt = opt || {};
var res = hay.findSubImages(needle, {
method: IMG_SEARCH_METHOD[opt.sm || "coc"]
, confidenceMinimum: opt.conf || 90
, downPyramidCount: opt.dpc || 1
, searchExpansion: opt.sex || 30
, maximumMatches: opt.max || 100
});
return res;
};
// Draw horizontal line
// img Image
// c Color
// x, y, w
// d Debug flag
const draw_line_h = function (img, c, x, y, w, d) {
const e = x + w;
if (!d)
AA.dbg("H: "
+ x.toFixed(1) + ' x ' + y.toFixed(1) + ' - '
+ e.toFixed(1) + ' x ' + y.toFixed(1) + ' '
+ w
);
for ( ; x < e; ++x)
img.setPixel(x, y, c);
}
// Draw vertical line
const draw_line_v = function (img, c, x, y, h, d) {
const e = y + h;
if (!d)
AA.dbg("V: "
+ x.toFixed(1) + ' x ' + y.toFixed(1) + ' - '
+ x.toFixed(1) + ' x ' + e.toFixed(1) + ' '
+ h
);
for ( ; y < e; ++y)
img.setPixel(x, y, c);
}
// Draw plus (+)
const draw_plus = function (img, c, pt, spread) {
spread = spread || 2;
const z = spread * 2 + 1;
draw_line_h (img, c, pt.sx - spread, pt.sy, z, 1);
draw_line_v(img, c, pt.sx, pt.sy - spread, z, 1);
}
// Draw rectangle
const draw_rect = function (img, c, r) {
draw_line_h(img, c, r.x, r.y, r.w);
draw_line_h(img, c, r.x, r.b, r.w);
draw_line_v(img, c, r.x, r.y, r.h);
draw_line_v(img, c, r.r, r.y, r.h);
}
// Combine various infoemation from match in sub image
// search into an object.
const match2rect = function (pos, sz, conf, f_info) {
const r = {
//x: Math.floor(pos.x - sz.width / 2),
//y: Math.floor(pos.y - sz.height / 2),
x: (pos.x - sz.width / 2),
y: (pos.y - sz.height / 2),
w: sz.width,
h: sz.height,
b: 0,
r: 0,
sx: pos.x,
sy: pos.y,
conf: conf,
fn: f_info.fn,
txt: f_info.txt
};
r.b = r.y + r.h;
r.r = r.x + r.w;
return r;
}
// Find sub-images in hay.
// f_info is typically from options executing the script.
const subimg2rect = function (hay, f_info) {
const needle = new Image(f_info.fn);
const match = find_subimages(hay, needle, f_info.opt);
const ret = [];
if (match) {
match.forEach(function(m) {
ret.push(match2rect(m.position, needle.size(), m.confidence, f_info));
});
return ret;
}
return null;
}
// Draw text: Require one to implement a methid for this
// in the source code.
/*
const draw_txt = function (img, x, y, txt, opt) {
img.setLabel(x, y, txt, opt);
};
*/
const run = function () {
// Locate and shoot target
const target = shoot_target(opt.img.shot_saveas);
if (!target)
AA.abort();
// the Image
const hay = target.shot;
// color for drawing
const c = new Color();
// matches
var result = [];
var tmp;
// Loop needle stack from options. If found append obj with
// data to result.
opt.img.img.forEach(function(f) {
if ((tmp = subimg2rect(hay, f)) !== null)
result = result.concat(tmp);
});
AA.dbg(result.length + "\n" + result);
// Loop and highlight finds.
result.forEach(function(r) {
// Rectangle by needle
AA.dbg('Rect for: ' + r.fn);
c.setNamedColor("#F00");
draw_rect(hay, c, r);
// A plus / ctross showing hit point
c.setNamedColor("#15F");
draw_plus(hay, c, r, Math.min(r.w, r.h) / 2);
/* Draw text. Require implementation of methid in source code
* of actiona.
draw_txt(hay, r.sx, r.sy, r.txt, {
fontName: "Arial",
fontSize: 18,
bold: 81,
italic: false,
color: "#00A"
});
*/
});
// Save image with drawings to file
hay.saveToFile(opt.img.dbg_saveas);
};
// More verbose logging to shell.
AA.debug = 1;
run();
})({
/* *****************************************************************
*
* O P T I O N S A N D I N P U T
*
* ******************************************************************
// Number of tries locating target window
tries: 3,
// Title of target window
win_title: "forum.jmgr.net - Post a new topic *",
img: {
// File OUT for original shot
shot_saveas: "example-shot.jpg",
// File OUT name for annotated image
dbg_saveas : "example-out.png",
// Array of images to search for.
/*
{
fn : file-path,
opt : {
sm : search method. coc, crc or sd
conf: minimum confidence,
dpc : downPyramidCount
sex : search expansion
max : maximum matches
},
txt : Text to write (label) matches with.
Require one to implement a method for this in the
actiona source code ...
}
*/
// ls -1 | sed 's/test-t\(.*\)-.*/{fn: "img\/test\/tuxes-\/\0", txt: "\1"},/'
img: [
{fn: "img/t-01-qmark.png", txt: "qmark"},
{fn: "img/t-02-lol.png", txt: "lol", opt: {
sm: "coc",
dpc: 2,
sex: 1,
conf: 87
}},
{fn: "img/t-03-preview.png", txt: "preview"},
{fn: "img/t-04-bell.png", txt: "bell"},
],
},
});