Skip to content

Commit db17529

Browse files
authored
Add several runtime optimisations relating to screen capture (#54)
- Add MouseOver.getText() utility to return all text in the MouseOverText zone regardless of colour. - Idler holds the colour variables for less runtime overhead. - Changed Minimap.getXP() to return Integer rather than String. - Drastically improve ScreenManager zone extraction speed. - Drastically improve Ocr speed through memory management and font map optimisation. - Add abstraction to extract text from a CU81 Mat object directly to reduce BufferedImage conversion overhead. - Add more attempts to MovingObject. - Add Morphological closing to default ColourContours behaviour to minimise attempts, reduce duplicate contours and to make contour extraction robust.
1 parent afef25d commit db17529

File tree

7 files changed

+273
-199
lines changed

7 files changed

+273
-199
lines changed

src/main/java/com/chromascape/utils/actions/Idler.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,31 @@
11
package com.chromascape.utils.actions;
22

33
import com.chromascape.base.BaseScript;
4-
import com.chromascape.utils.core.screen.colour.ColourInstances;
54
import com.chromascape.utils.core.screen.colour.ColourObj;
65
import com.chromascape.utils.domain.ocr.Ocr;
76
import java.awt.Rectangle;
87
import java.time.Duration;
98
import java.time.Instant;
109
import org.apache.logging.log4j.LogManager;
1110
import org.apache.logging.log4j.Logger;
11+
import org.bytedeco.opencv.opencv_core.Scalar;
1212

1313
/**
1414
* Utility class for handling idle behavior in scripts.
1515
*
1616
* <p>This class provides functionality to pause execution for a given amount of time, or until the
1717
* game client indicates the player has become idle again through a chat message.
18-
*
19-
* <p>You can add ChatRed directly to your colours/colours.json file by adding this. { "name" :
20-
* "ChatRed", "min" : [ 177, 229, 239, 0 ], "max" : [ 179, 240, 240, 0 ] }
2118
*/
2219
public class Idler {
2320

2421
private static final Logger logger = LogManager.getLogger(Idler.class);
2522
private static volatile String lastMessage = "";
2623

24+
private static final ColourObj black =
25+
new ColourObj("black", new Scalar(0, 0, 0, 0), new Scalar(0, 0, 0, 0));
26+
private static final ColourObj chatRed =
27+
new ColourObj("chatRed", new Scalar(177, 229, 239, 0), new Scalar(179, 240, 240, 0));
28+
2729
/**
2830
* Waits until either the specified timeout has elapsed or until the client chatbox reports that
2931
* the player is idle.
@@ -46,9 +48,7 @@ public static void waitUntilIdle(BaseScript base, int timeoutSeconds) {
4648
// Throttle wait to reduce lag, this is enough.
4749
BaseScript.waitMillis(300);
4850
Rectangle latestMessage = base.controller().zones().getChatTabs().get("Latest Message");
49-
ColourObj red = ColourInstances.getByName("ChatRed");
50-
ColourObj black = ColourInstances.getByName("Black");
51-
String idleText = Ocr.extractText(latestMessage, "Plain 12", red, true);
51+
String idleText = Ocr.extractText(latestMessage, "Plain 12", chatRed, true);
5252
String timeStamp = Ocr.extractText(latestMessage, "Plain 12", black, true);
5353
if ((idleText.contains("moving") || idleText.contains("idle"))
5454
&& !timeStamp.equals(lastMessage)) {

src/main/java/com/chromascape/utils/actions/Minimap.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,12 @@ public static int getSpec(BaseScript script) throws IOException {
8686
* https://github.com/StaticSweep/ChromaScape/wiki/Requirements
8787
*
8888
* @param script The current running script (typically pass {@code this})
89-
* @return the XP string, or empty if not found
89+
* @return the XP integer, or empty if not found
9090
* @throws IOException if the OCR failed to load the font
9191
*/
92-
public static String getXp(BaseScript script) throws IOException {
92+
public static int getXp(BaseScript script) throws IOException {
9393
Rectangle xpZone = script.controller().zones().getMinimap().get("totalXP");
94-
return Ocr.extractText(xpZone, "Plain 12", white, true);
94+
String xpText = Ocr.extractText(xpZone, "Plain 12", white, true);
95+
return Integer.parseInt(xpText.trim().replace(",", ""));
9596
}
9697
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package com.chromascape.utils.actions;
2+
3+
import static org.bytedeco.opencv.global.opencv_core.bitwise_or;
4+
import static org.bytedeco.opencv.global.opencv_core.inRange;
5+
import static org.bytedeco.opencv.global.opencv_imgproc.COLOR_BGR2HSV;
6+
import static org.bytedeco.opencv.global.opencv_imgproc.cvtColor;
7+
import static org.opencv.core.CvType.CV_8UC1;
8+
9+
import com.chromascape.base.BaseScript;
10+
import com.chromascape.utils.core.screen.colour.ColourObj;
11+
import com.chromascape.utils.core.screen.window.ScreenManager;
12+
import com.chromascape.utils.domain.ocr.Ocr;
13+
import java.awt.Rectangle;
14+
import java.awt.image.BufferedImage;
15+
import java.io.IOException;
16+
import java.util.ArrayList;
17+
import java.util.Arrays;
18+
import java.util.List;
19+
import org.bytedeco.javacv.Java2DFrameUtils;
20+
import org.bytedeco.opencv.opencv_core.Mat;
21+
import org.bytedeco.opencv.opencv_core.Scalar;
22+
23+
/**
24+
* An actions utility to provide a high level API for MouseOverText.
25+
*
26+
* <p>Uses OpenCV to iterate over a list of colours, and collates the resulting image into one
27+
* overall mask. This allows the user to get the text of the whole MouseOverText zone regardless of
28+
* colour.
29+
*
30+
* <p>Allows the user to grab the MouseOverText immediately as a string, excluding spaces.
31+
*/
32+
public class MouseOver {
33+
34+
/** Colours that exist within the MouseOverText zone. */
35+
private static final List<ColourObj> colours =
36+
new ArrayList<>(
37+
Arrays.asList(
38+
new ColourObj("TEXT_CYAN", new Scalar(80, 180, 200, 0), new Scalar(100, 255, 255, 0)),
39+
new ColourObj(
40+
"TEXT_OFF_WHITE", new Scalar(0, 0, 190, 0), new Scalar(180, 30, 255, 0)),
41+
new ColourObj("TEXT_ORANGE", new Scalar(8, 140, 180, 0), new Scalar(22, 220, 255, 0)),
42+
new ColourObj("TEXT_GREEN", new Scalar(50, 190, 100, 0), new Scalar(95, 255, 255, 0)),
43+
new ColourObj(
44+
"TEXT_YELLOW", new Scalar(25, 130, 190, 0), new Scalar(35, 255, 255, 0)),
45+
new ColourObj("TEXT_RED", new Scalar(0, 190, 190, 0), new Scalar(8, 255, 255, 0))));
46+
47+
/**
48+
* Captures the minimap to extract all possible colours. Layers the captures to create a mask
49+
* containing all text regardless of colour. Searches for text based on this.
50+
*
51+
* @param baseScript Your script instance, typically {@code this}.
52+
* @return The string found within the MouseOverText zone (No spaces).
53+
* @throws IOException If font loading failed within OCR.
54+
*/
55+
public static String getText(BaseScript baseScript) throws IOException {
56+
// Get image of MouseOverText
57+
Rectangle zone = baseScript.controller().zones().getMouseOver();
58+
BufferedImage capture = ScreenManager.captureZone(zone);
59+
60+
// Convert the captured BGR image to HSV once here,
61+
// so we don't have to do it inside the loop for every single colour
62+
Mat hsvMat = new Mat();
63+
try (Mat bgrMat = Java2DFrameUtils.toMat(capture)) {
64+
cvtColor(bgrMat, hsvMat, COLOR_BGR2HSV);
65+
}
66+
67+
// Accumulate all colour matches into a single binary mask
68+
try (Mat combinedMask =
69+
new Mat(capture.getHeight(), capture.getWidth(), CV_8UC1, new Scalar(0));
70+
Mat tempMask = new Mat()) { // Reusable mask for the loop using try with resources
71+
72+
for (ColourObj c : colours) {
73+
// In memory thresholding using the pre-converted HSV Mat
74+
try (Mat min = new Mat(c.hsvMin());
75+
Mat max = new Mat(c.hsvMax())) {
76+
inRange(hsvMat, min, max, tempMask);
77+
bitwise_or(combinedMask, tempMask, combinedMask);
78+
}
79+
}
80+
81+
// Cleanup
82+
hsvMat.release();
83+
84+
return Ocr.extractTextFromMask(combinedMask, "Bold 12", true);
85+
}
86+
}
87+
}

src/main/java/com/chromascape/utils/actions/MovingObject.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public static boolean clickMovingObjectByColourObjUntilRedClick(
7979

8080
// Initial Calculation and Click
8181
BufferedImage gameView = baseScript.controller().zones().getGameView();
82-
Point clickLocation = PointSelector.getRandomPointByColourObj(gameView, colour, 5);
82+
Point clickLocation = PointSelector.getRandomPointByColourObj(gameView, colour, 15);
8383

8484
if (clickLocation == null) {
8585
return false;

src/main/java/com/chromascape/utils/core/screen/topology/ColourContours.java

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,8 @@
22

33
import static org.bytedeco.opencv.global.opencv_core.CV_8UC1;
44
import static org.bytedeco.opencv.global.opencv_core.inRange;
5-
import static org.bytedeco.opencv.global.opencv_imgproc.CHAIN_APPROX_SIMPLE;
6-
import static org.bytedeco.opencv.global.opencv_imgproc.COLOR_BGR2HSV;
7-
import static org.bytedeco.opencv.global.opencv_imgproc.CV_RETR_LIST;
8-
import static org.bytedeco.opencv.global.opencv_imgproc.boundingRect;
9-
import static org.bytedeco.opencv.global.opencv_imgproc.cvtColor;
10-
import static org.bytedeco.opencv.global.opencv_imgproc.findContours;
11-
import static org.bytedeco.opencv.global.opencv_imgproc.pointPolygonTest;
5+
import static org.bytedeco.opencv.global.opencv_imgproc.*;
126

13-
import com.chromascape.utils.core.screen.DisplayImage;
147
import com.chromascape.utils.core.screen.colour.ColourObj;
158
import com.chromascape.utils.core.screen.viewport.ViewportManager;
169
import com.chromascape.utils.core.screen.window.ScreenManager;
@@ -22,10 +15,7 @@
2215
import java.util.ArrayList;
2316
import java.util.List;
2417
import org.bytedeco.javacv.Java2DFrameUtils;
25-
import org.bytedeco.opencv.opencv_core.Mat;
26-
import org.bytedeco.opencv.opencv_core.MatVector;
27-
import org.bytedeco.opencv.opencv_core.Point2f;
28-
import org.bytedeco.opencv.opencv_core.Rect;
18+
import org.bytedeco.opencv.opencv_core.*;
2919

3020
/**
3121
* Utility class for extracting and processing colour-based contours from images. Uses OpenCV to
@@ -34,7 +24,12 @@
3424
*/
3525
public class ColourContours {
3626

37-
public static boolean debug = false;
27+
private static final Mat DILATE_KERNEL = getStructuringElement(MORPH_ELLIPSE, new Size(25, 25));
28+
private static final Mat ERODE_KERNEL = getStructuringElement(MORPH_ELLIPSE, new Size(25, 25));
29+
private static final Scalar COLOUR_WHITE = new Scalar(255);
30+
private static final Mat EMPTY_HIERARCHY = new Mat();
31+
private static final org.bytedeco.opencv.opencv_core.Point OFFSET_ZERO =
32+
new org.bytedeco.opencv.opencv_core.Point(0, 0);
3833

3934
/**
4035
* Finds and returns a list of ChromaObj instances representing contours in the given image that
@@ -46,6 +41,7 @@ public class ColourContours {
4641
*/
4742
public static List<ChromaObj> getChromaObjsInColour(BufferedImage image, ColourObj colourObj) {
4843
Mat mask = extractColours(image, colourObj);
44+
morphClose(mask);
4945
MatVector contours = extractContours(mask);
5046
mask.release();
5147
return createChromaObjects(contours);
@@ -84,17 +80,43 @@ public static Mat extractColours(Mat inputMat, ColourObj colourObj) {
8480
hsvImage.release();
8581
hsvMin.release();
8682
hsvMax.release();
87-
88-
// if debugging, display the mask
89-
if (debug) {
90-
DisplayImage.display(Java2DFrameUtils.toBufferedImage(result));
91-
}
92-
9383
ViewportManager.getInstance().updateState(result);
9484

9585
return result;
9686
}
9787

88+
/**
89+
* Uses Morphological Closing via dilation and erosion, to ensure that no breaks appear in the
90+
* contour. Fills object's contours to ensure consistency and to reduce duplicate contours.
91+
* Mutates the given Mat object rather than assigning separate objects.
92+
*
93+
* @param result The 8UC1 {@link Mat} mask which to mutate.
94+
*/
95+
public static void morphClose(Mat result) {
96+
// Dilate the contour to fix breaks e.g., C should become O
97+
morphologyEx(result, result, MORPH_DILATE, DILATE_KERNEL);
98+
99+
// Completely fill internal space with white
100+
// For consistency and improved contour calculation
101+
try (MatVector contours = new MatVector()) {
102+
findContours(result, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
103+
// Using static constants for reused variables to reduce CPU allocation fatigue
104+
drawContours(
105+
result,
106+
contours,
107+
-1,
108+
COLOUR_WHITE,
109+
-1,
110+
LINE_8,
111+
EMPTY_HIERARCHY,
112+
Integer.MAX_VALUE,
113+
OFFSET_ZERO);
114+
}
115+
116+
// Restore original size through erosion whilst closing contour breaks
117+
morphologyEx(result, result, MORPH_ERODE, ERODE_KERNEL);
118+
}
119+
98120
/**
99121
* Finds contours in a binary mask image.
100122
*

0 commit comments

Comments
 (0)