@applitools/screenshoter 3.4.13 → 3.4.16
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applitools/screenshoter",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.16",
|
|
4
4
|
"description": "Applitools universal screenshoter for web and native applications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"applitools",
|
|
@@ -72,7 +72,8 @@
|
|
|
72
72
|
}
|
|
73
73
|
},
|
|
74
74
|
"dependencies": {
|
|
75
|
-
"@applitools/
|
|
75
|
+
"@applitools/image": "1.0.1",
|
|
76
|
+
"@applitools/logger": "1.1.16",
|
|
76
77
|
"@applitools/snippets": "2.4.5",
|
|
77
78
|
"@applitools/utils": "1.3.10",
|
|
78
79
|
"jpeg-js": "0.4.4",
|
|
@@ -80,9 +81,9 @@
|
|
|
80
81
|
},
|
|
81
82
|
"devDependencies": {
|
|
82
83
|
"@applitools/bongo": "^2.1.6",
|
|
83
|
-
"@applitools/driver": "^1.9.
|
|
84
|
+
"@applitools/driver": "^1.9.21",
|
|
84
85
|
"@applitools/scripts": "^1.1.0",
|
|
85
|
-
"@applitools/spec-driver-webdriverio": "^1.2.
|
|
86
|
+
"@applitools/spec-driver-webdriverio": "^1.2.19",
|
|
86
87
|
"@applitools/test-utils": "^1.4.3",
|
|
87
88
|
"appium": "^1.22.3",
|
|
88
89
|
"chromedriver": "^101.0.0",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const utils = require('@applitools/utils')
|
|
2
|
-
const makeImage = require('
|
|
2
|
+
const {makeImage} = require('@applitools/image')
|
|
3
3
|
const makeTakeViewportScreenshot = require('./take-viewport-screenshot')
|
|
4
4
|
const calculateScreenshotRegion = require('./calculate-screenshot-region')
|
|
5
5
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const utils = require('@applitools/utils')
|
|
2
2
|
const snippets = require('@applitools/snippets')
|
|
3
3
|
const findImagePattern = require('./find-image-pattern')
|
|
4
|
-
const makeImage = require('
|
|
4
|
+
const {makeImage} = require('@applitools/image')
|
|
5
5
|
|
|
6
6
|
function makeTakeViewportScreenshot(options) {
|
|
7
7
|
const {driver} = options
|
package/src/image.js
DELETED
|
@@ -1,667 +0,0 @@
|
|
|
1
|
-
const fs = require('fs')
|
|
2
|
-
const stream = require('stream')
|
|
3
|
-
const path = require('path')
|
|
4
|
-
const png = require('png-async')
|
|
5
|
-
const jpeg = require('jpeg-js')
|
|
6
|
-
const utils = require('@applitools/utils')
|
|
7
|
-
|
|
8
|
-
function makeImage(data) {
|
|
9
|
-
let image, size
|
|
10
|
-
let transforms = {rotate: 0, scale: 1, crop: null, modifiers: []}
|
|
11
|
-
|
|
12
|
-
if (utils.types.isBase64(data)) {
|
|
13
|
-
return makeImage(Buffer.from(data, 'base64'))
|
|
14
|
-
} else if (utils.types.isString(data)) {
|
|
15
|
-
return makeImage(fs.readFileSync(data))
|
|
16
|
-
} else if (Buffer.isBuffer(data)) {
|
|
17
|
-
if (isPngBuffer(data)) {
|
|
18
|
-
image = fromPngBuffer(data)
|
|
19
|
-
size = extractPngSize(data)
|
|
20
|
-
} else if (isJpegBuffer(data)) {
|
|
21
|
-
image = fromJpegBuffer(data)
|
|
22
|
-
size = extractJpegSize(data)
|
|
23
|
-
} else {
|
|
24
|
-
throw new Error('Unable to create an image abstraction from buffer with unknown data')
|
|
25
|
-
}
|
|
26
|
-
} else if (data.isImage) {
|
|
27
|
-
transforms = data.transforms
|
|
28
|
-
image = data.toRaw()
|
|
29
|
-
size = data.rawSize
|
|
30
|
-
} else if (utils.types.has(data, ['width', 'height'])) {
|
|
31
|
-
image = fromSize(data)
|
|
32
|
-
if (data.data) image.data = data.data
|
|
33
|
-
size = {width: data.width, height: data.height}
|
|
34
|
-
} else if (data.auto) {
|
|
35
|
-
size = {width: -1, height: -1}
|
|
36
|
-
} else {
|
|
37
|
-
throw new Error('Unable to create an image abstraction from unknown data')
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
get isImage() {
|
|
42
|
-
return true
|
|
43
|
-
},
|
|
44
|
-
get size() {
|
|
45
|
-
const croppedSize = utils.geometry.size(transforms.crop || size)
|
|
46
|
-
const scaledSize = utils.geometry.scale(croppedSize, transforms.scale)
|
|
47
|
-
const rotatedSize = utils.geometry.rotate(scaledSize, transforms.rotate)
|
|
48
|
-
return utils.geometry.ceil(rotatedSize)
|
|
49
|
-
},
|
|
50
|
-
get unscaledSize() {
|
|
51
|
-
const croppedSize = utils.geometry.size(transforms.crop || size)
|
|
52
|
-
const rotatedSize = utils.geometry.rotate(croppedSize, transforms.rotate)
|
|
53
|
-
return utils.geometry.ceil(rotatedSize)
|
|
54
|
-
},
|
|
55
|
-
get rawSize() {
|
|
56
|
-
return size
|
|
57
|
-
},
|
|
58
|
-
get transforms() {
|
|
59
|
-
return {...transforms}
|
|
60
|
-
},
|
|
61
|
-
get width() {
|
|
62
|
-
return this.size.width
|
|
63
|
-
},
|
|
64
|
-
get height() {
|
|
65
|
-
return this.size.height
|
|
66
|
-
},
|
|
67
|
-
scale(ratio) {
|
|
68
|
-
transforms.scale *= ratio
|
|
69
|
-
return this
|
|
70
|
-
},
|
|
71
|
-
rotate(degrees) {
|
|
72
|
-
transforms.rotate = (transforms.rotate + degrees) % 360
|
|
73
|
-
return this
|
|
74
|
-
},
|
|
75
|
-
crop(region) {
|
|
76
|
-
if (utils.types.has(region, ['left', 'right', 'top', 'bottom'])) {
|
|
77
|
-
region = {
|
|
78
|
-
x: region.left / transforms.scale,
|
|
79
|
-
y: region.top / transforms.scale,
|
|
80
|
-
width: size.width - (region.left + region.right) / transforms.scale,
|
|
81
|
-
height: size.height - (region.top + region.bottom) / transforms.scale,
|
|
82
|
-
}
|
|
83
|
-
} else {
|
|
84
|
-
region = utils.geometry.scale(region, 1 / transforms.scale)
|
|
85
|
-
}
|
|
86
|
-
region = utils.geometry.rotate(region, -transforms.rotate, utils.geometry.rotate(size, transforms.rotate))
|
|
87
|
-
region = transforms.crop
|
|
88
|
-
? utils.geometry.intersect(transforms.crop, utils.geometry.offset(region, transforms.crop))
|
|
89
|
-
: utils.geometry.intersect({x: 0, y: 0, ...size}, region)
|
|
90
|
-
transforms.crop = region
|
|
91
|
-
size = utils.geometry.size(transforms.crop)
|
|
92
|
-
return this
|
|
93
|
-
},
|
|
94
|
-
copy(srcImage, offset) {
|
|
95
|
-
// if "auto" image and this is first chunk
|
|
96
|
-
if (!image && size.width === -1 && size.height === -1) transforms.scale = srcImage.transforms.scale
|
|
97
|
-
|
|
98
|
-
const unscaledOffset = utils.geometry.scale(offset, 1 / transforms.scale)
|
|
99
|
-
|
|
100
|
-
if (!image) {
|
|
101
|
-
size = {
|
|
102
|
-
width: Math.max(Math.floor(unscaledOffset.x) + srcImage.unscaledSize.width, size.width),
|
|
103
|
-
height: Math.max(Math.floor(unscaledOffset.y) + srcImage.unscaledSize.height, size.height),
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const unscale =
|
|
108
|
-
srcImage.transforms.scale === transforms.scale
|
|
109
|
-
? 1 / srcImage.transforms.scale
|
|
110
|
-
: srcImage.transforms.scale / transforms.scale
|
|
111
|
-
|
|
112
|
-
transforms.modifiers.push({type: 'copy', image: srcImage.scale(unscale).toObject(), offset: unscaledOffset})
|
|
113
|
-
|
|
114
|
-
return this
|
|
115
|
-
},
|
|
116
|
-
frame(topImage, bottomImage, region) {
|
|
117
|
-
const prevSize = size
|
|
118
|
-
const unscaledRegion = utils.geometry.scale(region, 1 / topImage.transforms.scale)
|
|
119
|
-
size = {
|
|
120
|
-
width: Math.floor(topImage.unscaledSize.width + Math.max(size.width - unscaledRegion.width, 0)),
|
|
121
|
-
height: Math.floor(topImage.unscaledSize.height + Math.max(size.height - unscaledRegion.height, 0)),
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const unscale =
|
|
125
|
-
topImage.transforms.scale === transforms.scale
|
|
126
|
-
? 1 / topImage.transforms.scale
|
|
127
|
-
: topImage.transforms.scale / transforms.scale
|
|
128
|
-
transforms.modifiers.push({
|
|
129
|
-
type: 'frame',
|
|
130
|
-
top: topImage.scale(unscale).toObject(),
|
|
131
|
-
bottom: bottomImage.scale(unscale).toObject(),
|
|
132
|
-
region: unscaledRegion,
|
|
133
|
-
})
|
|
134
|
-
transforms.added = {width: size.width - prevSize.width, height: size.height - prevSize.height}
|
|
135
|
-
return this
|
|
136
|
-
},
|
|
137
|
-
async toRaw() {
|
|
138
|
-
return image
|
|
139
|
-
},
|
|
140
|
-
async toBuffer() {
|
|
141
|
-
const image = await this.toObject()
|
|
142
|
-
return image.data
|
|
143
|
-
},
|
|
144
|
-
async toPng() {
|
|
145
|
-
return toPng(await this.toObject())
|
|
146
|
-
},
|
|
147
|
-
async toFile(path) {
|
|
148
|
-
return toFile(await image, path)
|
|
149
|
-
},
|
|
150
|
-
async toObject() {
|
|
151
|
-
image = await transform(image ? await image : size, transforms)
|
|
152
|
-
transforms = {crop: null, scale: 1, rotate: 0, modifiers: []}
|
|
153
|
-
return image
|
|
154
|
-
},
|
|
155
|
-
async debug(debug) {
|
|
156
|
-
if (!debug || !debug.path) return
|
|
157
|
-
const timestamp = new Date().toISOString().replace(/[-T:.]/g, '_')
|
|
158
|
-
const filename = ['screenshot', timestamp, debug.name, debug.suffix].filter(part => part).join('_') + '.png'
|
|
159
|
-
const transformedImage = await transform(image ? await image : size, transforms)
|
|
160
|
-
return toFile(transformedImage, path.join(debug.path, filename)).catch(() => null)
|
|
161
|
-
},
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function isPngBuffer(buffer) {
|
|
166
|
-
return buffer.slice(12, 16).toString('ascii') === 'IHDR'
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function isJpegBuffer(buffer) {
|
|
170
|
-
return buffer.slice(6, 10).toString('ascii') === 'JFIF'
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function extractPngSize(buffer) {
|
|
174
|
-
return {width: buffer.readUInt32BE(16), height: buffer.readUInt32BE(20)}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function extractJpegSize(buffer) {
|
|
178
|
-
// skip file signature
|
|
179
|
-
let offset = 4
|
|
180
|
-
while (buffer.length > offset) {
|
|
181
|
-
// extract length of the block
|
|
182
|
-
offset += buffer.readUInt16BE(offset)
|
|
183
|
-
// if next segment is SOF extract size
|
|
184
|
-
if (buffer[offset + 1] === 0xc0) {
|
|
185
|
-
return {width: buffer.readUInt16BE(offset + 7), height: buffer.readUInt16BE(offset + 5)}
|
|
186
|
-
} else {
|
|
187
|
-
// skip block signature
|
|
188
|
-
offset += 2
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function fromSize(size) {
|
|
194
|
-
return new png.Image({width: size.width, height: size.height})
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
async function fromPngBuffer(buffer) {
|
|
198
|
-
return new Promise((resolve, reject) => {
|
|
199
|
-
const image = new png.Image()
|
|
200
|
-
|
|
201
|
-
image.parse(buffer, (err, image) => {
|
|
202
|
-
if (err) return reject(err)
|
|
203
|
-
resolve(image)
|
|
204
|
-
})
|
|
205
|
-
})
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
async function fromJpegBuffer(buffer) {
|
|
209
|
-
return jpeg.decode(buffer, {tolerantDecoding: true, formatAsRGBA: true})
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
async function toPng(image) {
|
|
213
|
-
return new Promise((resolve, reject) => {
|
|
214
|
-
let buffer = Buffer.alloc(0)
|
|
215
|
-
|
|
216
|
-
const writable = new stream.Writable({
|
|
217
|
-
write(chunk, _encoding, next) {
|
|
218
|
-
buffer = Buffer.concat([buffer, chunk])
|
|
219
|
-
next()
|
|
220
|
-
},
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
image
|
|
224
|
-
.pack()
|
|
225
|
-
.pipe(writable)
|
|
226
|
-
.on('finish', () => resolve(buffer))
|
|
227
|
-
.on('error', err => reject(err))
|
|
228
|
-
})
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
async function toFile(image, filepath) {
|
|
232
|
-
const buffer = await toPng(image)
|
|
233
|
-
return new Promise((resolve, reject) => {
|
|
234
|
-
fs.mkdirSync(path.dirname(filepath), {recursive: true})
|
|
235
|
-
fs.writeFile(filepath, buffer, err => (err ? reject(err) : resolve()))
|
|
236
|
-
})
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
async function transform(image, transforms) {
|
|
240
|
-
if (!image.data) {
|
|
241
|
-
const size = transforms.added
|
|
242
|
-
? {width: image.width - transforms.added.width, height: image.height - transforms.added.height}
|
|
243
|
-
: image
|
|
244
|
-
image = new png.Image(size)
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
image = await transforms.modifiers.reduce(async (image, modifier) => {
|
|
248
|
-
if (modifier.type === 'copy') {
|
|
249
|
-
return copy(await image, await modifier.image, modifier.offset)
|
|
250
|
-
} else if (modifier.type === 'frame') {
|
|
251
|
-
return frame(await modifier.top, await modifier.bottom, await image, modifier.region)
|
|
252
|
-
} else {
|
|
253
|
-
return image
|
|
254
|
-
}
|
|
255
|
-
}, image)
|
|
256
|
-
|
|
257
|
-
image = transforms.crop ? await extract(image, transforms.crop) : image
|
|
258
|
-
image = transforms.scale !== 1 ? await scale(image, transforms.scale) : image
|
|
259
|
-
image = transforms.rotate !== 0 ? await rotate(image, transforms.rotate) : image
|
|
260
|
-
return image
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
async function scale(image, scaleRatio) {
|
|
264
|
-
if (scaleRatio === 1) return image
|
|
265
|
-
|
|
266
|
-
const ratio = image.height / image.width
|
|
267
|
-
const scaledWidth = Math.ceil(image.width * scaleRatio)
|
|
268
|
-
const scaledHeight = Math.ceil(scaledWidth * ratio)
|
|
269
|
-
return resize(image, {width: scaledWidth, height: scaledHeight})
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
async function resize(image, size) {
|
|
273
|
-
const dst = {
|
|
274
|
-
data: Buffer.alloc(size.height * size.width * 4),
|
|
275
|
-
width: size.width,
|
|
276
|
-
height: size.height,
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (dst.width > image.width || dst.height > image.height) {
|
|
280
|
-
_doBicubicInterpolation(image, dst)
|
|
281
|
-
} else {
|
|
282
|
-
_scaleImageIncrementally(image, dst)
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
image.data = dst.data
|
|
286
|
-
image.width = dst.width
|
|
287
|
-
image.height = dst.height
|
|
288
|
-
|
|
289
|
-
return image
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
async function extract(image, region) {
|
|
293
|
-
const srcX = Math.max(0, Math.round(region.x))
|
|
294
|
-
const srcY = Math.max(0, Math.round(region.y))
|
|
295
|
-
const dstWidth = Math.round(Math.min(image.width - srcX, region.width))
|
|
296
|
-
const dstHeight = Math.round(Math.min(image.height - srcY, region.height))
|
|
297
|
-
const dstSize = {width: dstWidth, height: dstHeight}
|
|
298
|
-
|
|
299
|
-
if (utils.geometry.isEmpty(dstSize)) {
|
|
300
|
-
throw new Error(`Cannot extract empty region (${srcX};${srcY})${dstWidth}x${dstHeight} from image`)
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const extracted = new png.Image(dstSize)
|
|
304
|
-
|
|
305
|
-
if (srcX === 0 && dstWidth === image.width) {
|
|
306
|
-
const srcOffset = srcY * image.width * 4
|
|
307
|
-
const dstLength = dstWidth * dstHeight * 4
|
|
308
|
-
extracted.data.set(image.data.subarray(srcOffset, srcOffset + dstLength))
|
|
309
|
-
} else {
|
|
310
|
-
const chunkLength = dstWidth * 4
|
|
311
|
-
for (let chunk = 0; chunk < dstHeight; ++chunk) {
|
|
312
|
-
const srcOffset = ((srcY + chunk) * image.width + srcX) * 4
|
|
313
|
-
extracted.data.set(image.data.subarray(srcOffset, srcOffset + chunkLength), chunk * chunkLength)
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
return extracted
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
async function rotate(image, degrees) {
|
|
321
|
-
degrees = (360 + degrees) % 360
|
|
322
|
-
|
|
323
|
-
const dstImage = new png.Image({width: image.width, height: image.height})
|
|
324
|
-
|
|
325
|
-
if (degrees === 90) {
|
|
326
|
-
dstImage.width = image.height
|
|
327
|
-
dstImage.height = image.width
|
|
328
|
-
for (let srcY = 0, dstX = image.height - 1; srcY < image.height; ++srcY, --dstX) {
|
|
329
|
-
for (let srcX = 0, dstY = 0; srcX < image.width; ++srcX, ++dstY) {
|
|
330
|
-
const pixel = image.data.readUInt32BE((srcY * image.width + srcX) * 4)
|
|
331
|
-
dstImage.data.writeUInt32BE(pixel, (dstY * dstImage.width + dstX) * 4)
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
} else if (degrees === 180) {
|
|
335
|
-
dstImage.width = image.width
|
|
336
|
-
dstImage.height = image.height
|
|
337
|
-
for (let srcY = 0, dstY = image.height - 1; srcY < image.height; ++srcY, --dstY) {
|
|
338
|
-
for (let srcX = 0, dstX = image.width - 1; srcX < image.width; ++srcX, --dstX) {
|
|
339
|
-
const pixel = image.data.readUInt32BE((srcY * image.width + srcX) * 4)
|
|
340
|
-
dstImage.data.writeUInt32BE(pixel, (dstY * dstImage.width + dstX) * 4)
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
} else if (degrees === 270) {
|
|
344
|
-
dstImage.width = image.height
|
|
345
|
-
dstImage.height = image.width
|
|
346
|
-
for (let srcY = 0, dstX = 0; srcY < image.height; ++srcY, ++dstX) {
|
|
347
|
-
for (let srcX = 0, dstY = image.width - 1; srcX < image.width; ++srcX, --dstY) {
|
|
348
|
-
const pixel = image.data.readUInt32BE((srcY * image.width + srcX) * 4)
|
|
349
|
-
dstImage.data.writeUInt32BE(pixel, (dstY * dstImage.width + dstX) * 4)
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
} else {
|
|
353
|
-
return dstImage.data.set(image.data)
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
return dstImage
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
async function copy(dstImage, srcImage, offset) {
|
|
360
|
-
const dstX = Math.round(offset.x)
|
|
361
|
-
const dstY = Math.round(offset.y)
|
|
362
|
-
const srcWidth = Math.min(srcImage.width, dstImage.width - dstX)
|
|
363
|
-
const srcHeight = Math.min(srcImage.height, dstImage.height - dstY)
|
|
364
|
-
|
|
365
|
-
if (dstX === 0 && srcWidth === dstImage.width && srcWidth === srcImage.width) {
|
|
366
|
-
const dstOffset = dstY * dstImage.width * 4
|
|
367
|
-
dstImage.data.set(srcImage.data.subarray(0, srcWidth * srcHeight * 4), dstOffset)
|
|
368
|
-
return dstImage
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
const chunkLength = srcWidth * 4
|
|
372
|
-
for (let chunk = 0; chunk < srcHeight; ++chunk) {
|
|
373
|
-
const srcOffset = chunk * srcImage.width * 4
|
|
374
|
-
const dstOffset = ((dstY + chunk) * dstImage.width + dstX) * 4
|
|
375
|
-
if (dstOffset >= 0) {
|
|
376
|
-
dstImage.data.set(srcImage.data.subarray(srcOffset, srcOffset + chunkLength), dstOffset)
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
return dstImage
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
async function frame(topImage, bottomImage, srcImage, region) {
|
|
384
|
-
region = utils.geometry.intersect(
|
|
385
|
-
{x: 0, y: 0, width: topImage.width, height: topImage.height},
|
|
386
|
-
utils.geometry.round(region),
|
|
387
|
-
)
|
|
388
|
-
|
|
389
|
-
if (region.x === 0 && region.y === 0 && region.width >= topImage.width && region.height >= topImage.height) {
|
|
390
|
-
return srcImage
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
if (region.width >= srcImage.width && region.height >= srcImage.height) {
|
|
394
|
-
await copy(topImage, srcImage, {x: region.x, y: region.y})
|
|
395
|
-
return topImage
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
const dstImage = new png.Image({
|
|
399
|
-
width: topImage.width + Math.max(srcImage.width - region.width, 0),
|
|
400
|
-
height: topImage.height + Math.max(srcImage.height - region.height, 0),
|
|
401
|
-
})
|
|
402
|
-
|
|
403
|
-
if (region.width === srcImage.width) {
|
|
404
|
-
const topExtImage = await extract(topImage, {
|
|
405
|
-
x: 0,
|
|
406
|
-
y: 0,
|
|
407
|
-
width: topImage.width,
|
|
408
|
-
height: region.y + region.height,
|
|
409
|
-
})
|
|
410
|
-
await copy(dstImage, topExtImage, {x: 0, y: 0})
|
|
411
|
-
} else if (region.height === srcImage.height) {
|
|
412
|
-
const leftExtImage = await extract(topImage, {
|
|
413
|
-
x: 0,
|
|
414
|
-
y: 0,
|
|
415
|
-
width: region.x + region.width,
|
|
416
|
-
height: topImage.height,
|
|
417
|
-
})
|
|
418
|
-
await copy(dstImage, leftExtImage, {x: 0, y: 0})
|
|
419
|
-
} else {
|
|
420
|
-
const topLeftExtImage = await extract(topImage, {
|
|
421
|
-
x: 0,
|
|
422
|
-
y: 0,
|
|
423
|
-
width: region.x + region.width,
|
|
424
|
-
height: region.y + region.height,
|
|
425
|
-
})
|
|
426
|
-
await copy(dstImage, topLeftExtImage, {x: 0, y: 0})
|
|
427
|
-
|
|
428
|
-
const rightExtImage = await extract(topImage, {
|
|
429
|
-
x: region.x + region.width,
|
|
430
|
-
y: 0,
|
|
431
|
-
width: topImage.width - (region.x + region.width),
|
|
432
|
-
height: region.y,
|
|
433
|
-
})
|
|
434
|
-
await copy(dstImage, rightExtImage, {x: region.x + region.width, y: 0})
|
|
435
|
-
|
|
436
|
-
const bottomExtImage = await extract(topImage, {
|
|
437
|
-
x: 0,
|
|
438
|
-
y: region.y + region.height,
|
|
439
|
-
width: region.x,
|
|
440
|
-
height: topImage.height - (region.y + region.height),
|
|
441
|
-
})
|
|
442
|
-
await copy(dstImage, bottomExtImage, {x: 0, y: region.y + region.height})
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
if (bottomImage.height > region.y + region.height || bottomImage.width > region.x + region.width) {
|
|
446
|
-
// first image might be higher
|
|
447
|
-
const yDiff = topImage.height - bottomImage.height
|
|
448
|
-
if (region.width === srcImage.width) {
|
|
449
|
-
const bottomExtImage = await extract(bottomImage, {
|
|
450
|
-
x: 0,
|
|
451
|
-
y: region.y - yDiff + region.height,
|
|
452
|
-
width: bottomImage.width,
|
|
453
|
-
height: bottomImage.height - (region.y - yDiff + region.height),
|
|
454
|
-
})
|
|
455
|
-
await copy(dstImage, bottomExtImage, {x: 0, y: region.y + Math.max(srcImage.height, region.height)})
|
|
456
|
-
} else if (region.height === srcImage.height) {
|
|
457
|
-
const rightExtImage = await extract(bottomImage, {
|
|
458
|
-
x: region.x + region.width,
|
|
459
|
-
y: 0,
|
|
460
|
-
width: bottomImage.width - (region.x + region.width),
|
|
461
|
-
height: bottomImage.height,
|
|
462
|
-
})
|
|
463
|
-
await copy(dstImage, rightExtImage, {x: region.x + Math.max(srcImage.width, region.width), y: 0})
|
|
464
|
-
} else {
|
|
465
|
-
const bottomRightExtImage = await extract(bottomImage, {
|
|
466
|
-
x: region.x,
|
|
467
|
-
y: region.y - yDiff,
|
|
468
|
-
width: bottomImage.width - region.x,
|
|
469
|
-
height: bottomImage.height - (region.y - yDiff),
|
|
470
|
-
})
|
|
471
|
-
|
|
472
|
-
await copy(dstImage, bottomRightExtImage, {
|
|
473
|
-
x: region.x + Math.max(srcImage.width - region.width, 0),
|
|
474
|
-
y: region.y + Math.max(srcImage.height - region.height, 0),
|
|
475
|
-
})
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
await copy(dstImage, srcImage, {x: region.x, y: region.y})
|
|
480
|
-
|
|
481
|
-
return dstImage
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
function _interpolateCubic(x0, x1, x2, x3, t) {
|
|
485
|
-
const a0 = x3 - x2 - x0 + x1
|
|
486
|
-
const a1 = x0 - x1 - a0
|
|
487
|
-
const a2 = x2 - x0
|
|
488
|
-
|
|
489
|
-
return Math.ceil(Math.max(0, Math.min(255, a0 * (t * t * t) + a1 * (t * t) + (a2 * t + x1))))
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
function _interpolateRows(bufSrc, wSrc, hSrc, wDst) {
|
|
493
|
-
const buf = Buffer.alloc(wDst * hSrc * 4)
|
|
494
|
-
for (let i = 0; i < hSrc; i += 1) {
|
|
495
|
-
for (let j = 0; j < wDst; j += 1) {
|
|
496
|
-
const x = (j * (wSrc - 1)) / wDst
|
|
497
|
-
const xPos = Math.floor(x)
|
|
498
|
-
const t = x - xPos
|
|
499
|
-
const srcPos = (i * wSrc + xPos) * 4
|
|
500
|
-
const buf1Pos = (i * wDst + j) * 4
|
|
501
|
-
for (let k = 0; k < 4; k += 1) {
|
|
502
|
-
const kPos = srcPos + k
|
|
503
|
-
const x0 = xPos > 0 ? bufSrc[kPos - 4] : 2 * bufSrc[kPos] - bufSrc[kPos + 4]
|
|
504
|
-
const x1 = bufSrc[kPos]
|
|
505
|
-
const x2 = bufSrc[kPos + 4]
|
|
506
|
-
const x3 = xPos < wSrc - 2 ? bufSrc[kPos + 8] : 2 * bufSrc[kPos + 4] - bufSrc[kPos]
|
|
507
|
-
buf[buf1Pos + k] = _interpolateCubic(x0, x1, x2, x3, t)
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
return buf
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
function _interpolateColumns(bufSrc, hSrc, wDst, hDst) {
|
|
516
|
-
const buf = Buffer.alloc(wDst * hDst * 4)
|
|
517
|
-
for (let i = 0; i < hDst; i += 1) {
|
|
518
|
-
for (let j = 0; j < wDst; j += 1) {
|
|
519
|
-
const y = (i * (hSrc - 1)) / hDst
|
|
520
|
-
|
|
521
|
-
const yPos = Math.floor(y)
|
|
522
|
-
const t = y - yPos
|
|
523
|
-
const buf1Pos = (yPos * wDst + j) * 4
|
|
524
|
-
const buf2Pos = (i * wDst + j) * 4
|
|
525
|
-
for (let k = 0; k < 4; k += 1) {
|
|
526
|
-
const kPos = buf1Pos + k
|
|
527
|
-
const y0 = yPos > 0 ? bufSrc[kPos - wDst * 4] : 2 * bufSrc[kPos] - bufSrc[kPos + wDst * 4]
|
|
528
|
-
const y1 = bufSrc[kPos]
|
|
529
|
-
const y2 = bufSrc[kPos + wDst * 4]
|
|
530
|
-
const y3 = yPos < hSrc - 2 ? bufSrc[kPos + wDst * 8] : 2 * bufSrc[kPos + wDst * 4] - bufSrc[kPos]
|
|
531
|
-
|
|
532
|
-
buf[buf2Pos + k] = _interpolateCubic(y0, y1, y2, y3, t)
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
return buf
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
function _interpolateScale(bufColumns, wDst, hDst, wDst2, m, wM, hM) {
|
|
541
|
-
const buf = Buffer.alloc(wDst * hDst * 4)
|
|
542
|
-
for (let i = 0; i < hDst; i += 1) {
|
|
543
|
-
for (let j = 0; j < wDst; j += 1) {
|
|
544
|
-
let r = 0
|
|
545
|
-
let g = 0
|
|
546
|
-
let b = 0
|
|
547
|
-
let a = 0
|
|
548
|
-
let realColors = 0
|
|
549
|
-
for (let y = 0; y < hM; y += 1) {
|
|
550
|
-
const yPos = i * hM + y
|
|
551
|
-
for (let x = 0; x < wM; x += 1) {
|
|
552
|
-
const xPos = j * wM + x
|
|
553
|
-
const xyPos = (yPos * wDst2 + xPos) * 4
|
|
554
|
-
const pixelAlpha = bufColumns[xyPos + 3]
|
|
555
|
-
if (pixelAlpha) {
|
|
556
|
-
r += bufColumns[xyPos]
|
|
557
|
-
g += bufColumns[xyPos + 1]
|
|
558
|
-
b += bufColumns[xyPos + 2]
|
|
559
|
-
realColors += 1
|
|
560
|
-
}
|
|
561
|
-
a += pixelAlpha
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
const pos = (i * wDst + j) * 4
|
|
566
|
-
buf[pos] = realColors ? Math.round(r / realColors) : 0
|
|
567
|
-
buf[pos + 1] = realColors ? Math.round(g / realColors) : 0
|
|
568
|
-
buf[pos + 2] = realColors ? Math.round(b / realColors) : 0
|
|
569
|
-
buf[pos + 3] = Math.round(a / m)
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
return buf
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
function _doBicubicInterpolation(src, dst) {
|
|
577
|
-
// The implementation was taken from
|
|
578
|
-
// https://github.com/oliver-moran/jimp/blob/master/resize2.js
|
|
579
|
-
|
|
580
|
-
// when dst smaller than src/2, interpolate first to a multiple between 0.5 and 1.0 src, then sum squares
|
|
581
|
-
const wM = Math.max(1, Math.floor(src.width / dst.width))
|
|
582
|
-
const wDst2 = dst.width * wM
|
|
583
|
-
const hM = Math.max(1, Math.floor(src.height / dst.height))
|
|
584
|
-
const hDst2 = dst.height * hM
|
|
585
|
-
|
|
586
|
-
// Pass 1 - interpolate rows
|
|
587
|
-
// bufRows has width of dst2 and height of src
|
|
588
|
-
const bufRows = _interpolateRows(src.data, src.width, src.height, wDst2)
|
|
589
|
-
|
|
590
|
-
// Pass 2 - interpolate columns
|
|
591
|
-
// bufColumns has width and height of dst2
|
|
592
|
-
const bufColumns = _interpolateColumns(bufRows, src.height, wDst2, hDst2)
|
|
593
|
-
|
|
594
|
-
// Pass 3 - scale to dst
|
|
595
|
-
const m = wM * hM
|
|
596
|
-
if (m > 1) {
|
|
597
|
-
dst.data = _interpolateScale(bufColumns, dst.width, dst.height, wDst2, m, wM, hM)
|
|
598
|
-
} else {
|
|
599
|
-
dst.data = bufColumns
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
return dst
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
function _scaleImageIncrementally(src, dst) {
|
|
606
|
-
let currentWidth = src.width
|
|
607
|
-
let currentHeight = src.height
|
|
608
|
-
const targetWidth = dst.width
|
|
609
|
-
const targetHeight = dst.height
|
|
610
|
-
|
|
611
|
-
dst.data = src.data
|
|
612
|
-
dst.width = src.width
|
|
613
|
-
dst.height = src.height
|
|
614
|
-
|
|
615
|
-
// For ultra quality should use 7
|
|
616
|
-
const fraction = 2
|
|
617
|
-
|
|
618
|
-
do {
|
|
619
|
-
const prevCurrentWidth = currentWidth
|
|
620
|
-
const prevCurrentHeight = currentHeight
|
|
621
|
-
|
|
622
|
-
// If the current width is bigger than our target, cut it in half and sample again.
|
|
623
|
-
if (currentWidth > targetWidth) {
|
|
624
|
-
currentWidth -= Math.floor(currentWidth / fraction)
|
|
625
|
-
|
|
626
|
-
// If we cut the width too far it means we are on our last iteration. Just set it to the target width
|
|
627
|
-
// and finish up.
|
|
628
|
-
if (currentWidth < targetWidth) {
|
|
629
|
-
currentWidth = targetWidth
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
// If the current height is bigger than our target, cut it in half and sample again.
|
|
634
|
-
if (currentHeight > targetHeight) {
|
|
635
|
-
currentHeight -= Math.floor(currentHeight / fraction)
|
|
636
|
-
|
|
637
|
-
// If we cut the height too far it means we are on our last iteration. Just set it to the target height
|
|
638
|
-
// and finish up.
|
|
639
|
-
if (currentHeight < targetHeight) {
|
|
640
|
-
currentHeight = targetHeight
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
// Stop when we cannot incrementally step down anymore.
|
|
645
|
-
if (prevCurrentWidth === currentWidth && prevCurrentHeight === currentHeight) {
|
|
646
|
-
return dst
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
// Render the incremental scaled image.
|
|
650
|
-
const incrementalImage = {
|
|
651
|
-
data: Buffer.alloc(currentWidth * currentHeight * 4),
|
|
652
|
-
width: currentWidth,
|
|
653
|
-
height: currentHeight,
|
|
654
|
-
}
|
|
655
|
-
_doBicubicInterpolation(dst, incrementalImage)
|
|
656
|
-
|
|
657
|
-
// Now treat our incremental partially scaled image as the src image
|
|
658
|
-
// and cycle through our loop again to do another incremental scaling of it (if necessary).
|
|
659
|
-
dst.data = incrementalImage.data
|
|
660
|
-
dst.width = incrementalImage.width
|
|
661
|
-
dst.height = incrementalImage.height
|
|
662
|
-
} while (currentWidth !== targetWidth || currentHeight !== targetHeight)
|
|
663
|
-
|
|
664
|
-
return dst
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
module.exports = makeImage
|