-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlorebook.ts
More file actions
194 lines (177 loc) · 8.17 KB
/
lorebook.ts
File metadata and controls
194 lines (177 loc) · 8.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
import * as a1lib from 'alt1/base';
import { ImgRef } from 'alt1/base';
import * as OCR from 'alt1/ocr';
import { font, sourceimg, fontheavy } from './assets';
// Lorebook detection settings
/** The x-coordinate offset for the lore book detection. */
const OFFSET_X: number = 192;
/** The y-coordinate offset for the lore book detection. */
const OFFSET_Y: number = 290;
/** The width of the lore book detection area. */
const WIDTH: number = 450;
/** The height of the lore book detection area. */
const HEIGHT: number = 320;
/** The y-coordinate (base) position of the page numbers. */
const PAGE_NUM_POS: number = 313;
/** The x-coordinate position of the left page number. */
const PAGE_LEFT: number = 20;
/** The x-coordinate position of the right page number. */
const PAGE_RIGHT: number = 416;
/** The height of the title capture strip at the top of the lore book. */
const TITLE_HEIGHT: number = 25;
/** The y-coordinate within the title strip used for small-caps OCR. */
const TITLE_Y: number = 15;
/**
* Represents a lore book with its title, page numbers, and lines of text.
*/
export type Book = {
/** The title of the book. */
title: string;
/** The left page number. */
pageLeft: string;
/** The right page number. */
pageRight: string;
/** An array of lines of text in a book. */
lines: string[];
/** The hash value of the book's title for unique identification. */
titleHash: number;
};
/**
* Rework of the lore book reader to use the latest Alt1 library.
* Based on the original lore book reader at https://runeapps.org//imagelibs/lorebook.js
*/
export default class LoreBookReader {
pos: a1lib.RectLike | null = null;
bookimg: ImageData = sourceimg;
/**
* Finds the lore book in the provided image.
* @param {ImgRef} img - The image to search for the lore book. If not provided, a screenshot will be captured.
* @returns The position of the lore book if found, otherwise null.
*/
find(img?: ImgRef) {
// If no image is provided, capture the full RuneScape screen
if (!img) { img = a1lib.captureHoldFullRs(); }
// Search for the lore book image within the provided/captured image
const pos = img.findSubimage(this.bookimg);
// If no matches are found, return null indicating the lore book is not present
if (pos.length === 0) { return null; }
// Warn if more than one match is found, which could indicate multiple lore books or false positives
if (pos.length > 1) { console.warn('More than one possible lore book found'); }
// Adjust the found position by predefined offsets to pinpoint the exact location of the lore book
// and set the dimensions (width and height) of the detected area
this.pos = { x: pos[0].x - OFFSET_X, y: pos[0].y - OFFSET_Y, width: WIDTH, height: HEIGHT };
// Return the adjusted position of the lore book
return this.pos;
}
/**
* Reads a line of text from the provided image buffer.
* @param {ImageData} imageBuffer - alt1.capture ImageData based on position to read from.
* @param {number} x - The x-coordinate of the starting pixel.
* @param {number} y - The y-coordinate of the starting pixel.
* @returns The text of the line if found, otherwise an empty string.
*/
readLines(imageBuffer: ImageData, x: number, y: number) {
let detectedColor: any = null;
// Define the acceptable ranges for each color component
const colorRanges = [
[169, 248], // Red range
[117, 224], // Green range
[62, 177] // Blue range
];
// Loop through a set of pixels horizontally to find a color match
for (let pixelOffset = 0; pixelOffset < 40; pixelOffset++) {
// Calculate the index for the current pixel in the image data array
const pixelIndex = (x + pixelOffset) * 4 + (y - 2) * 4 * imageBuffer.width;
// Extract the color components (R, G, B) of the current pixel
const colorComponents = [
imageBuffer.data[pixelIndex], // Red component
imageBuffer.data[pixelIndex + 1],// Green component
imageBuffer.data[pixelIndex + 2] // Blue component
];
// Check if any color component is out of its acceptable range
const isOutOfRange = colorComponents.some((component, index) => {
const range = colorRanges[index];
return component < range[0] || component > range[1];
});
// If a color component is out of range, store the color and exit the loop
if (isOutOfRange) {
detectedColor = colorComponents;
break;
}
}
// If no color within the specified ranges was detected, return an empty string
if (!detectedColor) {
return '';
}
// Use OCR to read the line at the detected color's position, returning the text if found
const line = OCR.readLine(imageBuffer, font, detectedColor, x, y, true, false);
return line ? line.text : '';
}
/**
* Reads the lore book title from the top title strip of the currently detected book.
* @returns {string} The detected title text, or an empty string if OCR did not find a title.
*/
readTitle(): string {
// Capture the title area of the lore book using the defined position and dimensions
const buf: ImageData = a1lib.capture(this.pos.x, this.pos.y, WIDTH, TITLE_HEIGHT);
// Use OCR to read the title text from the captured image buffer
const title = OCR.readSmallCapsBackwards(buf, fontheavy, [[240, 190, 121]], 0, TITLE_Y, WIDTH, 1);
return title.text ?? '';
}
/**
* Reads the content of the lore book.
* @returns {Book} An object containing the title, page numbers, and lines of text from the lore book.
* @throws An error if no lore book is found.
*/
read(): Book {
if (!this.pos) {
throw new Error('No lore book found');
}
const imageBuffer: ImageData = a1lib.capture(this.pos);
// DEBUG: Show overlay of the captured image
// imageBuffer.show();
if (!imageBuffer) {
throw new Error('Failed to capture lore book image');
}
// Compares the captured image (imageBuffer) with a partial book image (sourceimg)
// at specified offsets (OFFSET_X, OFFSET_Y) to ensure the correct match.
const comparison: number = a1lib.simpleCompare(imageBuffer, sourceimg, OFFSET_X, OFFSET_Y);
if (comparison !== 0) {
if (comparison === Infinity) { // No match found
return;
}
console.warn('The lore book match is not accurate. Please check if the lore book is fully visible or adjust the image capture settings.');
// DEBUG: Show the difference between the captured image and the lore book image
// console.debug(a1lib.simpleCompare(imageBuffer, sourceimg, OFFSET_X, OFFSET_Y));
// sourceimg.show();
}
// Initializes an object to store text data extracted from the lore book.
const textData: Book = {
title: '',
pageLeft: '',
pageRight: '',
lines: [],
titleHash: 0
};
// Calculates a hash for the title area of the image to uniquely identify it.
textData.titleHash = imageBuffer.getPixelHash(new a1lib.Rect(110, 8, 200, 8));
textData.title = this.readTitle();
// Reads the page numbers from the lore book image.
// The last two booleans in the function calls indicate reading direction.
// First: forward (left to right), Second: backward (right to left).
const leftPage = OCR.readLine(imageBuffer, font, [0, 0, 0], PAGE_LEFT, PAGE_NUM_POS, true, false);
const rightPage = OCR.readLine(imageBuffer, font, [0, 0, 0], PAGE_RIGHT, PAGE_NUM_POS, false, true);
textData.pageLeft = leftPage.text ?? '?';
textData.pageRight = rightPage.text ?? '?';
textData.lines = [];
for (let lineIndex = 0; lineIndex < 15; lineIndex++) {
textData.lines.push(this.readLines(imageBuffer, 0, 58 + 16 * lineIndex));
}
for (let lineIndex = 0; lineIndex < 15; lineIndex++) {
textData.lines.push(this.readLines(imageBuffer, 238, 58 + 16 * lineIndex));
}
// DEBUG: Log the content of the lore book
//console.debug('Lore book content:', textData)
return textData;
}
}