Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
MR414N-ID
GitHub Repository: MR414N-ID/botku2
Path: blob/master/node_modules/@jimp/plugin-crop/src/index.js
1126 views
1
/* eslint-disable no-labels */
2
3
import { throwError, isNodePattern } from '@jimp/utils';
4
5
export default function pluginCrop(event) {
6
/**
7
* Crops the image at a given point to a give size
8
* @param {number} x the x coordinate to crop form
9
* @param {number} y the y coordinate to crop form
10
* @param w the width of the crop region
11
* @param h the height of the crop region
12
* @param {function(Error, Jimp)} cb (optional) a callback for when complete
13
* @returns {Jimp} this for chaining of methods
14
*/
15
event('crop', function(x, y, w, h, cb) {
16
if (typeof x !== 'number' || typeof y !== 'number')
17
return throwError.call(this, 'x and y must be numbers', cb);
18
if (typeof w !== 'number' || typeof h !== 'number')
19
return throwError.call(this, 'w and h must be numbers', cb);
20
21
// round input
22
x = Math.round(x);
23
y = Math.round(y);
24
w = Math.round(w);
25
h = Math.round(h);
26
27
if (x === 0 && w === this.bitmap.width) {
28
// shortcut
29
const start = (w * y + x) << 2;
30
const end = (start + h * w) << 2;
31
32
this.bitmap.data = this.bitmap.data.slice(start, end);
33
} else {
34
const bitmap = Buffer.allocUnsafe(w * h * 4);
35
let offset = 0;
36
37
this.scanQuiet(x, y, w, h, function(x, y, idx) {
38
const data = this.bitmap.data.readUInt32BE(idx, true);
39
bitmap.writeUInt32BE(data, offset, true);
40
offset += 4;
41
});
42
43
this.bitmap.data = bitmap;
44
}
45
46
this.bitmap.width = w;
47
this.bitmap.height = h;
48
49
if (isNodePattern(cb)) {
50
cb.call(this, null, this);
51
}
52
53
return this;
54
});
55
56
return {
57
class: {
58
/**
59
* Autocrop same color borders from this image
60
* @param {number} tolerance (optional): a percent value of tolerance for pixels color difference (default: 0.0002%)
61
* @param {boolean} cropOnlyFrames (optional): flag to crop only real frames: all 4 sides of the image must have some border (default: true)
62
* @param {function(Error, Jimp)} cb (optional): a callback for when complete (default: no callback)
63
* @returns {Jimp} this for chaining of methods
64
*/
65
autocrop(...args) {
66
const w = this.bitmap.width;
67
const h = this.bitmap.height;
68
const minPixelsPerSide = 1; // to avoid cropping completely the image, resulting in an invalid 0 sized image
69
70
let cb; // callback
71
let leaveBorder = 0; // Amount of pixels in border to leave
72
let tolerance = 0.0002; // percent of color difference tolerance (default value)
73
let cropOnlyFrames = true; // flag to force cropping only if the image has a real "frame"
74
// i.e. all 4 sides have some border (default value)
75
let cropSymmetric = false; // flag to force cropping top be symmetric.
76
// i.e. north and south / east and west are cropped by the same value
77
let ignoreSides = {
78
north: false,
79
south: false,
80
east: false,
81
west: false
82
};
83
84
// parse arguments
85
for (let a = 0, len = args.length; a < len; a++) {
86
if (typeof args[a] === 'number') {
87
// tolerance value passed
88
tolerance = args[a];
89
}
90
91
if (typeof args[a] === 'boolean') {
92
// cropOnlyFrames value passed
93
cropOnlyFrames = args[a];
94
}
95
96
if (typeof args[a] === 'function') {
97
// callback value passed
98
cb = args[a];
99
}
100
101
if (typeof args[a] === 'object') {
102
// config object passed
103
const config = args[a];
104
105
if (typeof config.tolerance !== 'undefined') {
106
({ tolerance } = config);
107
}
108
109
if (typeof config.cropOnlyFrames !== 'undefined') {
110
({ cropOnlyFrames } = config);
111
}
112
113
if (typeof config.cropSymmetric !== 'undefined') {
114
({ cropSymmetric } = config);
115
}
116
117
if (typeof config.leaveBorder !== 'undefined') {
118
({ leaveBorder } = config);
119
}
120
121
if (typeof config.ignoreSides !== 'undefined') {
122
({ ignoreSides } = config);
123
}
124
}
125
}
126
127
/**
128
* All borders must be of the same color as the top left pixel, to be cropped.
129
* It should be possible to crop borders each with a different color,
130
* but since there are many ways for corners to intersect, it would
131
* introduce unnecessary complexity to the algorithm.
132
*/
133
134
// scan each side for same color borders
135
let colorTarget = this.getPixelColor(0, 0); // top left pixel color is the target color
136
const rgba1 = this.constructor.intToRGBA(colorTarget);
137
138
// for north and east sides
139
let northPixelsToCrop = 0;
140
let eastPixelsToCrop = 0;
141
let southPixelsToCrop = 0;
142
let westPixelsToCrop = 0;
143
144
// north side (scan rows from north to south)
145
colorTarget = this.getPixelColor(0, 0);
146
if (!ignoreSides.north) {
147
north: for (let y = 0; y < h - minPixelsPerSide; y++) {
148
for (let x = 0; x < w; x++) {
149
const colorXY = this.getPixelColor(x, y);
150
const rgba2 = this.constructor.intToRGBA(colorXY);
151
152
if (this.constructor.colorDiff(rgba1, rgba2) > tolerance) {
153
// this pixel is too distant from the first one: abort this side scan
154
break north;
155
}
156
}
157
158
// this row contains all pixels with the same color: increment this side pixels to crop
159
northPixelsToCrop++;
160
}
161
}
162
163
// east side (scan columns from east to west)
164
colorTarget = this.getPixelColor(w, 0);
165
if (!ignoreSides.east) {
166
east: for (let x = 0; x < w - minPixelsPerSide; x++) {
167
for (let y = 0 + northPixelsToCrop; y < h; y++) {
168
const colorXY = this.getPixelColor(x, y);
169
const rgba2 = this.constructor.intToRGBA(colorXY);
170
171
if (this.constructor.colorDiff(rgba1, rgba2) > tolerance) {
172
// this pixel is too distant from the first one: abort this side scan
173
break east;
174
}
175
}
176
177
// this column contains all pixels with the same color: increment this side pixels to crop
178
eastPixelsToCrop++;
179
}
180
}
181
182
// south side (scan rows from south to north)
183
colorTarget = this.getPixelColor(0, h);
184
185
if (!ignoreSides.south) {
186
south: for (
187
let y = h - 1;
188
y >= northPixelsToCrop + minPixelsPerSide;
189
y--
190
) {
191
for (let x = w - eastPixelsToCrop - 1; x >= 0; x--) {
192
const colorXY = this.getPixelColor(x, y);
193
const rgba2 = this.constructor.intToRGBA(colorXY);
194
195
if (this.constructor.colorDiff(rgba1, rgba2) > tolerance) {
196
// this pixel is too distant from the first one: abort this side scan
197
break south;
198
}
199
}
200
201
// this row contains all pixels with the same color: increment this side pixels to crop
202
southPixelsToCrop++;
203
}
204
}
205
206
// west side (scan columns from west to east)
207
colorTarget = this.getPixelColor(w, h);
208
if (!ignoreSides.west) {
209
west: for (
210
let x = w - 1;
211
x >= 0 + eastPixelsToCrop + minPixelsPerSide;
212
x--
213
) {
214
for (let y = h - 1; y >= 0 + northPixelsToCrop; y--) {
215
const colorXY = this.getPixelColor(x, y);
216
const rgba2 = this.constructor.intToRGBA(colorXY);
217
218
if (this.constructor.colorDiff(rgba1, rgba2) > tolerance) {
219
// this pixel is too distant from the first one: abort this side scan
220
break west;
221
}
222
}
223
224
// this column contains all pixels with the same color: increment this side pixels to crop
225
westPixelsToCrop++;
226
}
227
}
228
229
// decide if a crop is needed
230
let doCrop = false;
231
232
// apply leaveBorder
233
westPixelsToCrop -= leaveBorder;
234
eastPixelsToCrop -= leaveBorder;
235
northPixelsToCrop -= leaveBorder;
236
southPixelsToCrop -= leaveBorder;
237
238
if (cropSymmetric) {
239
const horizontal = Math.min(eastPixelsToCrop, westPixelsToCrop);
240
const vertical = Math.min(northPixelsToCrop, southPixelsToCrop);
241
westPixelsToCrop = horizontal;
242
eastPixelsToCrop = horizontal;
243
northPixelsToCrop = vertical;
244
southPixelsToCrop = vertical;
245
}
246
247
// make sure that crops are >= 0
248
westPixelsToCrop = westPixelsToCrop >= 0 ? westPixelsToCrop : 0;
249
eastPixelsToCrop = eastPixelsToCrop >= 0 ? eastPixelsToCrop : 0;
250
northPixelsToCrop = northPixelsToCrop >= 0 ? northPixelsToCrop : 0;
251
southPixelsToCrop = southPixelsToCrop >= 0 ? southPixelsToCrop : 0;
252
253
// safety checks
254
const widthOfRemainingPixels =
255
w - (westPixelsToCrop + eastPixelsToCrop);
256
const heightOfRemainingPixels =
257
h - (southPixelsToCrop + northPixelsToCrop);
258
259
if (cropOnlyFrames) {
260
// crop image if all sides should be cropped
261
doCrop =
262
eastPixelsToCrop !== 0 &&
263
northPixelsToCrop !== 0 &&
264
westPixelsToCrop !== 0 &&
265
southPixelsToCrop !== 0;
266
} else {
267
// crop image if at least one side should be cropped
268
doCrop =
269
eastPixelsToCrop !== 0 ||
270
northPixelsToCrop !== 0 ||
271
westPixelsToCrop !== 0 ||
272
southPixelsToCrop !== 0;
273
}
274
275
if (doCrop) {
276
// do the real crop
277
this.crop(
278
eastPixelsToCrop,
279
northPixelsToCrop,
280
widthOfRemainingPixels,
281
heightOfRemainingPixels
282
);
283
}
284
285
if (isNodePattern(cb)) {
286
cb.call(this, null, this);
287
}
288
289
return this;
290
}
291
}
292
};
293
}
294
295