@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.13",
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/logger": "1.1.15",
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.19",
84
+ "@applitools/driver": "^1.9.21",
84
85
  "@applitools/scripts": "^1.1.0",
85
- "@applitools/spec-driver-webdriverio": "^1.2.18",
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('./image')
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('./image')
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