node-native-win-utils 2.1.4 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,17 +1,6 @@
1
1
 
2
2
  [![License][license-src]][license-href]
3
- ![Node-API v8 Badge](assets/Node-API%20v8%20Badge.svg)
4
-
5
- #### USDT TRC20 - TYAJ3K3MZraJhWimxxeCKcJ2SYABkVsrzi
6
- #### USDT TON - UQDokuYZXr4OHvfslDqUoFYcp1_F8tcjQPk_TvqSSDk7SIa7
7
- #### BTC - 1A3mNKFdWKXZ7Bcnez8LbWbYHgck1g4GeV
8
- #### NOT - UQDokuYZXr4OHvfslDqUoFYcp1_F8tcjQPk_TvqSSDk7SIa7
9
-
10
-
11
-
12
- #### Will be very thankful for any support
13
-
14
-
3
+ ![Node-API v8 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v8%20Badge.svg)
15
4
 
16
5
  # Node Native Win Utils
17
6
 
package/dist/index.d.mts CHANGED
@@ -75,15 +75,28 @@ export type Color = [r: number, g: number, b: number];
75
75
  export type ROI = [x: number, y: number, width: number, height: number];
76
76
  export type Imread = (path: string) => ImageData;
77
77
  export type Imwrite = (image: ImageData) => Buffer;
78
+ export type StartMouseListener = (callback: (data: {
79
+ x: number;
80
+ y: number;
81
+ type: string;
82
+ }) => void) => void;
83
+ export type StopMouseListener = (error: {
84
+ success: boolean;
85
+ error?: string;
86
+ errorCode?: number;
87
+ }) => void;
78
88
  export type MatchTemplate = (image: ImageData, template: ImageData, method?: number | null, mask?: ImageData) => MatchData;
79
89
  export type Blur = (image: ImageData, sizeX: number, sizeY: number) => ImageData;
80
90
  export type BgrToGray = (image: ImageData) => ImageData;
81
91
  export type EqualizeHist = (image: ImageData) => ImageData;
92
+ export type ColorBound = [number, number, number];
93
+ export type DarkenColor = (image: ImageData, lowerBound: ColorBound, upperBound: ColorBound, darkenFactor: number) => ImageData;
82
94
  export type DrawRectangle = (image: ImageData, start: Point, end: Point, rgb: Color, thickness: number) => ImageData;
83
95
  export type GetRegion = (image: ImageData, region: ROI) => ImageData;
84
96
  export type TextRecognition = (trainedDataPath: string, dataLang: string, imagePath: string) => string;
85
97
  export type CaptureScreenAsync = () => Promise<Buffer>;
86
- declare const getWindowData: GetWindowData, captureWindowN: CaptureWindow, captureScreenAsync: CaptureScreenAsync, mouseMove: MouseMove, mouseClick: MouseClick, mouseDrag: MouseDrag, typeString: TypeString, textRecognition: TextRecognition;
98
+ export type BringWindowToFront = (windowName: string) => boolean;
99
+ declare const getWindowData: GetWindowData, captureWindowN: CaptureWindow, captureScreenAsync: CaptureScreenAsync, mouseMove: MouseMove, mouseClick: MouseClick, mouseDrag: MouseDrag, typeString: TypeString, textRecognition: TextRecognition, bringWindowToFront: BringWindowToFront, startMouseListener: StartMouseListener, stopMouseListener: StopMouseListener;
87
100
  declare const rawPressKey: PressKey;
88
101
  /**
89
102
  * Captures a window and saves it to a file.
@@ -250,6 +263,8 @@ declare class OpenCV {
250
263
  * @returns A new OpenCV instance with the equalized image data.
251
264
  */
252
265
  equalizeHist(): OpenCV;
266
+ rgbToHsv(rgb: ColorBound): number[][];
267
+ darkenColor(lowerBound: ColorBound, upperBound: ColorBound, darkenFactor: number): OpenCV;
253
268
  /**
254
269
  * Draws a rectangle on the image.
255
270
  * @param start - The starting point of the rectangle.
@@ -272,4 +287,4 @@ declare class OpenCV {
272
287
  imwrite(path: string): void;
273
288
  }
274
289
  declare function keyPress(keyCode: number, repeat?: number): Promise<boolean>;
275
- export { getWindowData, captureWindow, captureWindowN, mouseMove, mouseClick, mouseDrag, typeString, keyPress, rawPressKey, KeyCodeHelper, textRecognition, captureScreenToFile, captureScreenAsync, KeyboardListener, OpenCV };
290
+ export { getWindowData, bringWindowToFront, captureWindow, captureWindowN, mouseMove, mouseClick, mouseDrag, typeString, keyPress, rawPressKey, KeyCodeHelper, textRecognition, captureScreenToFile, captureScreenAsync, startMouseListener, stopMouseListener, KeyboardListener, OpenCV };
package/dist/index.mjs CHANGED
@@ -8,7 +8,7 @@ const nodeGypBuild = __require("node-gyp-build");
8
8
  import { keyCodes, KeyCodeHelper } from "./keyCodes.mjs";
9
9
  import { __dirnameLocal } from "./dirnameLocal.mjs";
10
10
  const bindings = nodeGypBuild(path.resolve(__dirnameLocal, ".."));
11
- const { setKeyDownCallback, setKeyUpCallback, unsetKeyDownCallback, unsetKeyUpCallback, getWindowData, captureWindowN, captureScreenAsync, mouseMove, mouseClick, mouseDrag, typeString, pressKey, imread, imwrite, matchTemplate, blur, bgrToGray, drawRectangle, getRegion, textRecognition, equalizeHist } = bindings;
11
+ const { setKeyDownCallback, setKeyUpCallback, unsetKeyDownCallback, unsetKeyUpCallback, getWindowData, captureWindowN, captureScreenAsync, mouseMove, mouseClick, mouseDrag, typeString, pressKey, imread, imwrite, matchTemplate, blur, bgrToGray, drawRectangle, getRegion, textRecognition, equalizeHist, darkenColor, bringWindowToFront, startMouseListener, stopMouseListener } = bindings;
12
12
  const rawPressKey = pressKey;
13
13
  /**
14
14
  * Captures a window and saves it to a file.
@@ -184,6 +184,38 @@ class OpenCV {
184
184
  equalizeHist() {
185
185
  return new OpenCV(equalizeHist(this.imageData));
186
186
  }
187
+ rgbToHsv(rgb) {
188
+ const r_norm = rgb[0] / 255;
189
+ const g_norm = rgb[1] / 255;
190
+ const b_norm = rgb[2] / 255;
191
+ const Cmax = Math.max(r_norm, g_norm, b_norm);
192
+ const Cmin = Math.min(r_norm, g_norm, b_norm);
193
+ const delta = Cmax - Cmin;
194
+ let Hue = 0;
195
+ if (delta !== 0) {
196
+ switch (Cmax) {
197
+ case r_norm:
198
+ Hue = 60 * (((g_norm - b_norm) / delta) % 6);
199
+ break;
200
+ case g_norm:
201
+ Hue = 60 * (((b_norm - r_norm) / delta) + 2);
202
+ break;
203
+ case b_norm:
204
+ Hue = 60 * (((r_norm - g_norm) / delta) + 4);
205
+ break;
206
+ }
207
+ }
208
+ let Saturation = 0.0 * 100;
209
+ if (Cmax !== 0)
210
+ Saturation = (delta / Cmax) * 100;
211
+ let Value = Cmax * 100;
212
+ return [
213
+ [Hue, Saturation, Value]
214
+ ];
215
+ }
216
+ darkenColor(lowerBound, upperBound, darkenFactor) {
217
+ return new OpenCV(darkenColor(this.imageData, lowerBound, upperBound, darkenFactor));
218
+ }
187
219
  /**
188
220
  * Draws a rectangle on the image.
189
221
  * @param start - The starting point of the rectangle.
@@ -230,4 +262,4 @@ function keyPress(keyCode, repeat) {
230
262
  return resolve(true);
231
263
  });
232
264
  }
233
- export { getWindowData, captureWindow, captureWindowN, mouseMove, mouseClick, mouseDrag, typeString, keyPress, rawPressKey, KeyCodeHelper, textRecognition, captureScreenToFile, captureScreenAsync, KeyboardListener, OpenCV };
265
+ export { getWindowData, bringWindowToFront, captureWindow, captureWindowN, mouseMove, mouseClick, mouseDrag, typeString, keyPress, rawPressKey, KeyCodeHelper, textRecognition, captureScreenToFile, captureScreenAsync, startMouseListener, stopMouseListener, KeyboardListener, OpenCV };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-native-win-utils",
3
- "version": "2.1.4",
3
+ "version": "2.2.0",
4
4
  "author": "Andrew K.",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/T-Rumibul/node-native-win-utils.git",
@@ -43,18 +43,13 @@
43
43
  "test": "tap run "
44
44
  },
45
45
  "dependencies": {
46
- "node-addon-api": "^8.3.1",
47
- "node-gyp": "^11.1.0",
46
+ "node-addon-api": "^8.6.0",
47
+ "node-gyp": "^12.2.0",
48
48
  "node-gyp-build": "^4.8.4"
49
49
  },
50
50
  "devDependencies": {
51
- "@types/node": "^20.2.5",
52
- "node-api-headers": "^1.5.0",
53
- "tap": "^21.1.0"
54
- },
55
- "tap": {
56
- "plugin": [
57
- "@tapjs/sinon"
58
- ]
51
+ "@types/node": "^25.3.5",
52
+ "node-api-headers": "^1.8.0",
53
+ "tap": "^21.6.2"
59
54
  }
60
55
  }
@@ -42,5 +42,78 @@ Napi::Value GetWindowData(const Napi::CallbackInfo &info)
42
42
  result.Set("x", Napi::Number::New(env, x));
43
43
  result.Set("y", Napi::Number::New(env, y));
44
44
 
45
+ return result;
46
+ }
47
+
48
+ Napi::Value BringWindowToFront(const Napi::CallbackInfo &info)
49
+ {
50
+ Napi::Env env = info.Env();
51
+ Napi::Object result = Napi::Object::New(env);
52
+
53
+ auto returnError = [&](const std::string& message, DWORD errorCode = 0) {
54
+ result.Set("success", false);
55
+ result.Set("error", message);
56
+ if (errorCode != 0) {
57
+ result.Set("errorCode", Napi::Number::New(env, errorCode));
58
+ }
59
+ return result;
60
+ };
61
+
62
+ if (info.Length() < 1 || !info[0].IsString())
63
+ {
64
+ return returnError("Window name must be provided as a string");
65
+ }
66
+
67
+ std::string windowName = info[0].As<Napi::String>().Utf8Value();
68
+
69
+ HWND hWnd = GetWindowByName(windowName.c_str());
70
+ if (!hWnd || !IsWindow(hWnd))
71
+ {
72
+ return returnError("Window not found or invalid: " + windowName);
73
+ }
74
+
75
+ // Restore if minimized
76
+ if (IsIconic(hWnd))
77
+ {
78
+ ShowWindow(hWnd, SW_RESTORE);
79
+ // Give the window a moment to restore before trying to focus it
80
+ Sleep(50);
81
+ }
82
+
83
+ HWND fg = GetForegroundWindow();
84
+
85
+ // Bug fix 1: must attach the TARGET window's thread, not just the foreground
86
+ // window's thread. We need a triangle: cur <-> fg thread <-> target thread.
87
+ DWORD fgThread = fg ? GetWindowThreadProcessId(fg, nullptr) : 0;
88
+ DWORD tgtThread = GetWindowThreadProcessId(hWnd, nullptr);
89
+ DWORD curThread = GetCurrentThreadId();
90
+
91
+ // Bug fix 2: attach all relevant thread pairs so input state is fully shared
92
+ bool attachedFg = (fgThread && fgThread != curThread) ? AttachThreadInput(curThread, fgThread, TRUE) != 0 : false;
93
+ bool attachedTgt = (tgtThread && tgtThread != curThread) ? AttachThreadInput(curThread, tgtThread, TRUE) != 0 : false;
94
+
95
+ // Bug fix 3: use AllowSetForegroundWindow to explicitly grant permission,
96
+ // which bypasses Windows' focus-stealing prevention for cross-process calls
97
+ AllowSetForegroundWindow(ASFW_ANY);
98
+
99
+ // Bring the window to front using the full sequence
100
+ SetForegroundWindow(hWnd);
101
+ SetActiveWindow(hWnd);
102
+ BringWindowToTop(hWnd);
103
+ // Bug fix 4: SetFocus only works reliably once input queues are attached
104
+ SetFocus(hWnd);
105
+
106
+ // Always detach — don't leave threads attached on any code path
107
+ if (attachedFg) AttachThreadInput(curThread, fgThread, FALSE);
108
+ if (attachedTgt) AttachThreadInput(curThread, tgtThread, FALSE);
109
+
110
+ // Verify it actually worked
111
+ HWND newFg = GetForegroundWindow();
112
+ if (newFg != hWnd)
113
+ {
114
+ return returnError("Window did not come to foreground (focus-stealing prevention may be active)");
115
+ }
116
+
117
+ result.Set("success", true);
45
118
  return result;
46
119
  }
package/src/cpp/main.cpp CHANGED
@@ -9,6 +9,7 @@
9
9
 
10
10
  Napi::Object Init(Napi::Env env, Napi::Object exports)
11
11
  {
12
+
12
13
  exports.Set("getWindowData", Napi::Function::New(env, GetWindowData));
13
14
  exports.Set("captureWindowN", Napi::Function::New(env, CaptureWindow));
14
15
  exports.Set("captureScreenAsync", Napi::Function::New(env, CaptureScreenAsync));
@@ -27,9 +28,13 @@ Napi::Object Init(Napi::Env env, Napi::Object exports)
27
28
  exports.Set("blur", Napi::Function::New(env, Blur));
28
29
  exports.Set("bgrToGray", Napi::Function::New(env, BgrToGray));
29
30
  exports.Set("equalizeHist", Napi::Function::New(env, EqualizeHist));
31
+ exports.Set("darkenColor", Napi::Function::New(env, DarkenColor));
30
32
  exports.Set("drawRectangle", Napi::Function::New(env, DrawRectangle));
31
33
  exports.Set("getRegion", Napi::Function::New(env, GetRegion));
32
34
  exports.Set("textRecognition", Napi::Function::New(env, TextRecognition));
35
+ exports.Set("bringWindowToFront", Napi::Function::New(env, BringWindowToFront));
36
+ exports.Set("startMouseListener", Napi::Function::New(env, StartMouseListener));
37
+ exports.Set("stopMouseListener", Napi::Function::New(env, StopMouseListener));
33
38
  static GdiPlusInitializer gdiplusInitializer;
34
39
  // Register cleanup hook
35
40
  env.AddCleanupHook(CleanupHook);
package/src/cpp/mouse.cpp CHANGED
@@ -1,6 +1,11 @@
1
1
  // patrially used code from https://github.com/octalmage/robotjs witch is under MIT License Copyright (c) 2014 Jason Stallings
2
2
  #include <napi.h>
3
3
  #include <windows.h>
4
+
5
+ HHOOK mouseHook;
6
+ Napi::ThreadSafeFunction tsfn;
7
+ static HANDLE hHookThread = NULL;
8
+ static DWORD hookThreadId = 0;
4
9
  /**
5
10
  * Move the mouse to a specific point.
6
11
  * @param point The coordinates to move the mouse to (x, y).
@@ -173,3 +178,150 @@ Napi::Value DragMouse(const Napi::CallbackInfo &info)
173
178
 
174
179
  return Napi::Boolean::New(env, true);
175
180
  }
181
+
182
+
183
+ LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
184
+ {
185
+ if (nCode >= 0)
186
+ {
187
+ MSLLHOOKSTRUCT *mouse = (MSLLHOOKSTRUCT *)lParam;
188
+
189
+ int x = mouse->pt.x;
190
+ int y = mouse->pt.y;
191
+
192
+ std::string type = "move";
193
+
194
+ switch (wParam)
195
+ {
196
+ case WM_LBUTTONDOWN:
197
+ type = "leftDown";
198
+ break;
199
+ case WM_LBUTTONUP:
200
+ type = "leftUp";
201
+ break;
202
+ case WM_RBUTTONDOWN:
203
+ type = "rightDown";
204
+ break;
205
+ case WM_RBUTTONUP:
206
+ type = "rightUp";
207
+ break;
208
+ case WM_MBUTTONDOWN:
209
+ type = "middleDown";
210
+ break;
211
+ case WM_MBUTTONUP:
212
+ type = "middleUp";
213
+ break;
214
+ case WM_MOUSEMOVE:
215
+ type = "move";
216
+ break;
217
+ }
218
+
219
+ auto callback = [x, y, type](Napi::Env env, Napi::Function jsCallback)
220
+ {
221
+ Napi::Object event = Napi::Object::New(env);
222
+ event.Set("x", x);
223
+ event.Set("y", y);
224
+ event.Set("type", type);
225
+
226
+ jsCallback.Call({event});
227
+ };
228
+
229
+ tsfn.BlockingCall(callback);
230
+ }
231
+
232
+ return CallNextHookEx(mouseHook, nCode, wParam, lParam);
233
+ }
234
+
235
+
236
+ DWORD WINAPI HookThread(LPVOID)
237
+ {
238
+ mouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseProc, NULL, 0);
239
+
240
+ MSG msg;
241
+ while (GetMessage(&msg, NULL, 0, 0))
242
+ {
243
+ TranslateMessage(&msg);
244
+ DispatchMessage(&msg);
245
+ }
246
+
247
+ UnhookWindowsHookEx(mouseHook);
248
+ mouseHook = NULL;
249
+ return 0;
250
+ }
251
+
252
+ Napi::Value StartMouseListener(const Napi::CallbackInfo &info)
253
+ {
254
+ Napi::Env env = info.Env();
255
+
256
+ if (!info[0].IsFunction())
257
+ {
258
+ Napi::TypeError::New(env, "Callback expected").ThrowAsJavaScriptException();
259
+ return env.Null();
260
+ }
261
+ // Bug fix: prevent double-start leaking a thread and hook
262
+ if (hHookThread != NULL)
263
+ {
264
+ Napi::TypeError::New(env, "Mouse listener already running").ThrowAsJavaScriptException();
265
+ return env.Null();
266
+ }
267
+ Napi::Function callback = info[0].As<Napi::Function>();
268
+
269
+ tsfn = Napi::ThreadSafeFunction::New(
270
+ env,
271
+ callback,
272
+ "MouseListener",
273
+ 0,
274
+ 1);
275
+
276
+ // Store handle and thread ID so StopMouseListener can signal and wait
277
+ hHookThread = CreateThread(NULL, 0, HookThread, NULL, 0, &hookThreadId);
278
+
279
+ return Napi::Boolean::New(env, true);
280
+ }
281
+
282
+
283
+ Napi::Value StopMouseListener(const Napi::CallbackInfo &info)
284
+ {
285
+ Napi::Env env = info.Env();
286
+ Napi::Object result = Napi::Object::New(env);
287
+
288
+ if (hHookThread == NULL)
289
+ {
290
+ result.Set("success", false);
291
+ result.Set("error", "No listener is running");
292
+ return result;
293
+ }
294
+
295
+ // PostThreadMessage with WM_QUIT breaks the GetMessage loop in HookThread,
296
+ // which then runs UnhookWindowsHookEx and exits
297
+ if (!PostThreadMessage(hookThreadId, WM_QUIT, 0, 0))
298
+ {
299
+ result.Set("success", false);
300
+ result.Set("error", "Failed to signal hook thread");
301
+ result.Set("errorCode", Napi::Number::New(env, GetLastError()));
302
+ return result;
303
+ }
304
+
305
+ // Wait for the thread to fully exit before releasing resources
306
+ DWORD waitResult = WaitForSingleObject(hHookThread, 3000);
307
+ if (waitResult != WAIT_OBJECT_0)
308
+ {
309
+ // Thread didn't exit in time — force it and warn
310
+ TerminateThread(hHookThread, 1);
311
+ result.Set("success", false);
312
+ result.Set("error", "Hook thread did not exit cleanly, was forcefully terminated");
313
+ }
314
+ else
315
+ {
316
+ result.Set("success", true);
317
+ }
318
+
319
+ // Release the TSFN so Node.js can garbage collect the JS callback
320
+ tsfn.Release();
321
+
322
+ CloseHandle(hHookThread);
323
+ hHookThread = NULL;
324
+ hookThreadId = 0;
325
+
326
+ return result;
327
+ }
@@ -234,6 +234,9 @@ Napi::Value BgrToGray(const Napi::CallbackInfo &info)
234
234
  return result;
235
235
  }
236
236
 
237
+
238
+
239
+
237
240
  Napi::Value EqualizeHist(const Napi::CallbackInfo &info)
238
241
  {
239
242
  Napi::Env env = info.Env();
@@ -453,20 +456,90 @@ Napi::Value GetRegion(const Napi::CallbackInfo &info)
453
456
  return result;
454
457
  }
455
458
 
456
- // Napi::Value ReadImage(const Napi::CallbackInfo &info)
457
- // {
458
- // Napi::Env env = info.Env();
459
- // std::string imagePath = info[0].As<Napi::String>();
460
459
 
461
- // cv::Mat image = cv::imread(imagePath, cv::IMREAD_COLOR);
462
- // if (image.empty())
463
- // {
464
- // Napi::TypeError::New(env, "Could not read the image").ThrowAsJavaScriptException();
465
- // return env.Null();
466
- // }
467
460
 
468
- // cv::Mat grayImage;
469
- // cv::cvtColor(image, grayImage, cv::COLOR_BGR2GRAY);
470
461
 
471
- // return Napi::Number::New(env, grayImage.rows * grayImage.cols);
472
- // }
462
+
463
+ Napi::Value DarkenColor(const Napi::CallbackInfo &info) {
464
+ Napi::Env env = info.Env();
465
+
466
+ // Validate arguments: (imageData: Object, lowerBound: Array, upperBound: Array, number darkFactor)
467
+ if (info.Length() < 4 ||
468
+ !info[0].IsObject() ||
469
+ !info[1].IsArray() ||
470
+ !info[2].IsArray() ||
471
+ !info[3].IsNumber()) {
472
+ Napi::TypeError::New(env, "Invalid arguments. Expected: (object, array, array, number)")
473
+ .ThrowAsJavaScriptException();
474
+ return env.Null();
475
+ }
476
+
477
+ Napi::Object imageData = info[0].As<Napi::Object>();
478
+ Napi::Array lowerArray = info[1].As<Napi::Array>();
479
+ Napi::Array upperArray = info[2].As<Napi::Array>();
480
+ double darkFactor = info[3].ToNumber().DoubleValue();
481
+
482
+ // Validate imageData object properties
483
+ if (!imageData.Has("width") || !imageData.Has("height") || !imageData.Has("data")) {
484
+ Napi::TypeError::New(env, "Invalid image data object. Expected properties: 'width', 'height', 'data'")
485
+ .ThrowAsJavaScriptException();
486
+ return env.Null();
487
+ }
488
+
489
+ // Validate bounds arrays length
490
+ if (lowerArray.Length() != 3 || upperArray.Length() != 3) {
491
+ Napi::TypeError::New(env, "Lower and upper color bounds must be arrays of length 3")
492
+ .ThrowAsJavaScriptException();
493
+ return env.Null();
494
+ }
495
+
496
+ int width = imageData.Get("width").ToNumber().Int32Value();
497
+ int height = imageData.Get("height").ToNumber().Int32Value();
498
+
499
+ if (!imageData.Get("data").IsTypedArray()) {
500
+ Napi::TypeError::New(env, "'data' property must be a TypedArray")
501
+ .ThrowAsJavaScriptException();
502
+ return env.Null();
503
+ }
504
+
505
+ Napi::Uint8Array uint8Array = imageData.Get("data").As<Napi::Uint8Array>();
506
+ // Account for potential byteOffset in the typed array.
507
+ uint8_t* dataPtr = reinterpret_cast<uint8_t*>(uint8Array.ArrayBuffer().Data()) + uint8Array.ByteOffset();
508
+
509
+ // Create a cv::Mat using the underlying data of the Uint8Array.
510
+ cv::Mat inputImage(height, width, CV_8UC3, dataPtr);
511
+ cv::Mat image;
512
+ cv::cvtColor(inputImage, image, cv::COLOR_BGR2RGB);
513
+ // Create lower and upper bounds from the provided arrays.
514
+ cv::Scalar lower_bound(
515
+ lowerArray.Get((uint32_t)0).ToNumber().DoubleValue(),
516
+ lowerArray.Get((uint32_t)1).ToNumber().DoubleValue(),
517
+ lowerArray.Get((uint32_t)2).ToNumber().DoubleValue()
518
+ );
519
+ cv::Scalar upper_bound(
520
+ upperArray.Get((uint32_t)0).ToNumber().DoubleValue(),
521
+ upperArray.Get((uint32_t)1).ToNumber().DoubleValue(),
522
+ upperArray.Get((uint32_t)2).ToNumber().DoubleValue()
523
+ );
524
+
525
+ // Loop through the image pixels and darken the ones within the color range
526
+ for (int y = 0; y < image.rows; y++) {
527
+ for (int x = 0; x < image.cols; x++) {
528
+ cv::Vec3b& color = image.at<cv::Vec3b>(y, x);
529
+
530
+ // Check if the pixel is within the color range
531
+ if (color[0] >= lower_bound[0] && color[0] <= upper_bound[0] &&
532
+ color[1] >= lower_bound[1] && color[1] <= upper_bound[1] &&
533
+ color[2] >= lower_bound[2] && color[2] <= upper_bound[2]) {
534
+
535
+ // Darken the pixel by scaling down its color values
536
+ color[0] = std::max(0, int(color[0] * darkFactor));
537
+ color[1] = std::max(0, int(color[1] * darkFactor));
538
+ color[2] = std::max(0, int(color[2] * darkFactor));
539
+ }
540
+ }
541
+ }
542
+ cv::cvtColor(image, inputImage, cv::COLOR_RGB2BGR);
543
+ // Return the modified imageData (which shares the same underlying data).
544
+ return imageData;
545
+ }
package/src/index.mts CHANGED
@@ -107,6 +107,8 @@ export type Imread = (path: string) => ImageData;
107
107
 
108
108
  export type Imwrite = (image: ImageData) => Buffer;
109
109
 
110
+ export type StartMouseListener = (callback: (data: {x: number, y: number, type: string}) => void) => void;
111
+ export type StopMouseListener = () => {success: boolean, error?: string, errorCode?: number};
110
112
  export type MatchTemplate = (
111
113
  image: ImageData,
112
114
  template: ImageData,
@@ -121,6 +123,9 @@ export type Blur = (
121
123
  ) => ImageData;
122
124
  export type BgrToGray = (image: ImageData) => ImageData;
123
125
  export type EqualizeHist = (image: ImageData) => ImageData;
126
+
127
+ export type ColorBound = [number, number, number]
128
+ export type DarkenColor = (image: ImageData, lowerBound: ColorBound, upperBound: ColorBound, darkenFactor: number) => ImageData;
124
129
  export type DrawRectangle = (
125
130
  image: ImageData,
126
131
  start: Point,
@@ -132,7 +137,7 @@ export type GetRegion = (image: ImageData, region: ROI) => ImageData;
132
137
 
133
138
  export type TextRecognition = (trainedDataPath: string, dataLang: string, imagePath: string) => string;
134
139
  export type CaptureScreenAsync = () => Promise<Buffer>;
135
-
140
+ export type BringWindowToFront = (windowName: string) => boolean;
136
141
  const {
137
142
  setKeyDownCallback,
138
143
  setKeyUpCallback,
@@ -154,7 +159,12 @@ const {
154
159
  drawRectangle,
155
160
  getRegion,
156
161
  textRecognition,
157
- equalizeHist
162
+ equalizeHist,
163
+ darkenColor,
164
+ bringWindowToFront,
165
+ startMouseListener,
166
+ stopMouseListener
167
+
158
168
  }: {
159
169
  setKeyDownCallback: SetKeyCallback;
160
170
  setKeyUpCallback: SetKeyCallback;
@@ -176,7 +186,11 @@ const {
176
186
  getRegion: GetRegion;
177
187
  textRecognition: TextRecognition;
178
188
  captureScreenAsync: CaptureScreenAsync;
179
- equalizeHist: EqualizeHist
189
+ equalizeHist: EqualizeHist;
190
+ darkenColor: DarkenColor;
191
+ bringWindowToFront: BringWindowToFront;
192
+ startMouseListener: StartMouseListener;
193
+ stopMouseListener: StopMouseListener
180
194
  } = bindings;
181
195
 
182
196
  const rawPressKey = pressKey;
@@ -434,7 +448,38 @@ class OpenCV {
434
448
  equalizeHist() {
435
449
  return new OpenCV(equalizeHist(this.imageData));
436
450
  }
437
-
451
+ rgbToHsv(rgb: ColorBound) {
452
+ const r_norm = rgb[0] / 255;
453
+ const g_norm = rgb[1] / 255;
454
+ const b_norm = rgb[2] / 255;
455
+ const Cmax = Math.max(r_norm, g_norm, b_norm);
456
+ const Cmin = Math.min(r_norm, g_norm, b_norm);
457
+ const delta = Cmax - Cmin;
458
+
459
+ let Hue = 0;
460
+ if (delta !== 0) {
461
+ switch (Cmax) {
462
+ case r_norm:
463
+ Hue = 60 * (((g_norm - b_norm) / delta) % 6);
464
+ break;
465
+ case g_norm:
466
+ Hue = 60 * (((b_norm - r_norm) / delta) + 2);
467
+ break;
468
+ case b_norm:
469
+ Hue = 60 * (((r_norm - g_norm) / delta) + 4);
470
+ break;
471
+ }
472
+ }
473
+ let Saturation = 0.0 * 100;
474
+ if(Cmax !== 0) Saturation = (delta / Cmax) * 100;
475
+ let Value = Cmax * 100;
476
+ return [
477
+ [Hue, Saturation, Value]
478
+ ];
479
+ }
480
+ darkenColor(lowerBound: ColorBound, upperBound: ColorBound, darkenFactor: number) {
481
+ return new OpenCV(darkenColor(this.imageData, lowerBound, upperBound, darkenFactor));
482
+ }
438
483
 
439
484
  /**
440
485
  * Draws a rectangle on the image.
@@ -489,6 +534,7 @@ function keyPress(keyCode: number, repeat?: number): Promise<boolean> {
489
534
 
490
535
  export {
491
536
  getWindowData,
537
+ bringWindowToFront,
492
538
  captureWindow,
493
539
  captureWindowN,
494
540
  mouseMove,
@@ -501,6 +547,8 @@ export {
501
547
  textRecognition,
502
548
  captureScreenToFile,
503
549
  captureScreenAsync,
550
+ startMouseListener,
551
+ stopMouseListener,
504
552
  KeyboardListener,
505
553
  OpenCV
506
554
  };