@applitools/screenshoter 3.2.8 → 3.3.2

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.
Files changed (80) hide show
  1. package/.bongo/dry-run/package-lock.json +49 -6
  2. package/.bongo/dry-run/package.json +5 -0
  3. package/.bongo/dry-run.tgz +0 -0
  4. package/CHANGELOG.md +19 -0
  5. package/index.js +2 -1
  6. package/package.json +7 -4
  7. package/src/find-image-pattern.js +11 -38
  8. package/src/image.js +107 -64
  9. package/src/take-screenshot.js +136 -160
  10. package/src/take-simple-screenshot.js +25 -0
  11. package/src/take-stitched-screenshot.js +34 -40
  12. package/src/take-viewport-screenshot.js +179 -16
  13. package/test/e2e/android.spec.js +120 -13
  14. package/test/e2e/external.spec.js +155 -0
  15. package/test/e2e/ios.spec.js +142 -12
  16. package/test/e2e/web-ios.spec.js +19 -6
  17. package/test/e2e/web.spec.js +33 -21
  18. package/test/fixtures/android/app-fully-non-scrollable.png +0 -0
  19. package/test/fixtures/android/app-fully-recycler.png +0 -0
  20. package/test/fixtures/android/app-fully-scroll-statusbar.png +0 -0
  21. package/test/fixtures/android/app-fully-scroll.png +0 -0
  22. package/test/fixtures/android/app-statusbar.png +0 -0
  23. package/test/fixtures/android/x-app-fully-collapsing.png +0 -0
  24. package/test/fixtures/android/x-app-fully-recycler.png +0 -0
  25. package/test/fixtures/android/x-element-fully.png +0 -0
  26. package/test/fixtures/external/agl.png +0 -0
  27. package/test/fixtures/image/{house.combined-higher-wider.png → house.framed-higher-wider.png} +0 -0
  28. package/test/fixtures/image/{house.combined-higher.png → house.framed-higher.png} +0 -0
  29. package/test/fixtures/image/house.framed-shorter-thinner.png +0 -0
  30. package/test/fixtures/image/{house.combined-wider.png → house.framed-wider.png} +0 -0
  31. package/test/fixtures/ios/app-fully-collapsing.png +0 -0
  32. package/test/fixtures/ios/app-fully-collection.png +0 -0
  33. package/test/fixtures/ios/app-fully-overlapped-statusbar.png +0 -0
  34. package/test/fixtures/ios/app-fully-overlapped.png +0 -0
  35. package/test/fixtures/ios/app-fully-scroll-statusbar.png +0 -0
  36. package/test/fixtures/ios/app-fully-scroll.png +0 -0
  37. package/test/fixtures/ios/app-fully-superview.png +0 -0
  38. package/test/fixtures/ios/app-fully-table.png +0 -0
  39. package/test/fixtures/ios/app-statusbar.png +0 -0
  40. package/test/fixtures/ios/app.png +0 -0
  41. package/test/fixtures/ios/element-fully.png +0 -0
  42. package/test/fixtures/ios/element.png +0 -0
  43. package/test/fixtures/ios/region.png +0 -0
  44. package/test/fixtures/ios/webview-fully.png +0 -0
  45. package/test/fixtures/ios/webview.png +0 -0
  46. package/test/fixtures/pattern/iPad_5th_landscape.png +0 -0
  47. package/test/fixtures/pattern/iPad_5th_portrait.png +0 -0
  48. package/test/fixtures/pattern/iPad_9th_landscape.png +0 -0
  49. package/test/fixtures/pattern/iPad_9th_portrait.png +0 -0
  50. package/test/fixtures/pattern/iPhone_11_landscape.png +0 -0
  51. package/test/fixtures/pattern/iPhone_11_portrait.png +0 -0
  52. package/test/fixtures/pattern/iPhone_13_landscape.png +0 -0
  53. package/test/fixtures/pattern/iPhone_13_portrait.png +0 -0
  54. package/test/fixtures/pattern/iPhone_SE_landscape.png +0 -0
  55. package/test/fixtures/pattern/iPhone_SE_portrait.png +0 -0
  56. package/test/fixtures/pattern/iPhone_XS_portrait_noviewport.png +0 -0
  57. package/test/fixtures/web/frame-fully.png +0 -0
  58. package/test/fixtures/web/frame.png +0 -0
  59. package/test/fixtures/web/inner-element-fully.png +0 -0
  60. package/test/fixtures/web/inner-element.png +0 -0
  61. package/test/fixtures/web/inner-region-fully.png +0 -0
  62. package/test/fixtures/web/inner-region.png +0 -0
  63. package/test/fixtures/web/page-fully.png +0 -0
  64. package/test/fixtures/web/page.png +0 -0
  65. package/test/fixtures/web/region-fully.png +0 -0
  66. package/test/fixtures/web/region.png +0 -0
  67. package/test/fixtures/web-ios/page-fully.png +0 -0
  68. package/test/it/find-pattern.spec.js +16 -11
  69. package/test/it/image.spec.js +42 -15
  70. package/docker-compose.yaml +0 -29
  71. package/src/calculate-screenshot-regions.js +0 -31
  72. package/src/screenshoter.js +0 -158
  73. package/test/fixtures/pattern/iPad_Air_portrait.png +0 -0
  74. package/test/fixtures/pattern/iPhone_5S_landscape.png +0 -0
  75. package/test/fixtures/pattern/iPhone_XR_perfecto_landscape.png +0 -0
  76. package/test/fixtures/pattern/iPhone_XS_Max_perfecto_landscape.png +0 -0
  77. package/test/fixtures/pattern/iPhone_XS_landscape.png +0 -0
  78. package/test/fixtures/pattern/iPhone_XS_portrait.png +0 -0
  79. package/test/fixtures/pattern/iPhone_X_perfecto_portrait.png +0 -0
  80. package/test/util/spec-driver.js +0 -288
@@ -1,20 +1,63 @@
1
1
  {
2
+ "name": "dry-run",
3
+ "lockfileVersion": 2,
2
4
  "requires": true,
3
- "lockfileVersion": 1,
5
+ "packages": {
6
+ "": {
7
+ "dependencies": {
8
+ "@applitools/screenshoter": "file:../dry-run.tgz"
9
+ }
10
+ },
11
+ "node_modules/@applitools/screenshoter": {
12
+ "version": "3.3.1",
13
+ "resolved": "file:../dry-run.tgz",
14
+ "integrity": "sha512-Nn4m2tV2g1+ECbNzbJ4NJzvpx02lYAZ6MM4HXuT16ShCZPXi2fCuvnJEZX3D5j/EkZjEB6yFZWXrKT70m2Djew==",
15
+ "license": "SEE LICENSE IN LICENSE",
16
+ "dependencies": {
17
+ "@applitools/snippets": "2.1.10",
18
+ "@applitools/utils": "1.2.4",
19
+ "png-async": "0.9.4"
20
+ },
21
+ "engines": {
22
+ "node": ">= 8.9.0"
23
+ }
24
+ },
25
+ "node_modules/@applitools/snippets": {
26
+ "version": "2.1.10",
27
+ "resolved": "https://registry.npmjs.org/@applitools/snippets/-/snippets-2.1.10.tgz",
28
+ "integrity": "sha512-LGjtd8IZOwbhETqKRDX9CX85+BskkFUkuMuLvriSDEEQiSm0MpFbbcJVMjYaVL2qobaJbY+3WvK0g7+gkZuuqA==",
29
+ "engines": {
30
+ "node": ">=8.9.0"
31
+ }
32
+ },
33
+ "node_modules/@applitools/utils": {
34
+ "version": "1.2.4",
35
+ "resolved": "https://registry.npmjs.org/@applitools/utils/-/utils-1.2.4.tgz",
36
+ "integrity": "sha512-w7ma6FFGyqhdP6LEcuHFWOcH7EzBjnoAX3UfbFWcTHA3QXnXPX37Y2ENYRodfwkorP1cUKyUHwNXJB/BMIj/hg==",
37
+ "engines": {
38
+ "node": ">= 8.9.0"
39
+ }
40
+ },
41
+ "node_modules/png-async": {
42
+ "version": "0.9.4",
43
+ "resolved": "https://registry.npmjs.org/png-async/-/png-async-0.9.4.tgz",
44
+ "integrity": "sha512-B//AXX9TkneKfgtOpT1mdUnnhk2BImGD+a98vImsMU8uo1dBeHyW/kM2erWZ/CsYteTPU/xKG+t6T62heHkC3A=="
45
+ }
46
+ },
4
47
  "dependencies": {
5
48
  "@applitools/screenshoter": {
6
49
  "version": "file:../dry-run.tgz",
7
- "integrity": "sha512-J9JOZ+7hf0GMgaDZtNE5oVcRcOwtvDD/gXDP/eEY0PFIPFpXeEZU9abkVH4E6VeT6PwRCZz42EIb8Mvf2nsEkQ==",
50
+ "integrity": "sha512-Nn4m2tV2g1+ECbNzbJ4NJzvpx02lYAZ6MM4HXuT16ShCZPXi2fCuvnJEZX3D5j/EkZjEB6yFZWXrKT70m2Djew==",
8
51
  "requires": {
9
- "@applitools/snippets": "2.1.7",
52
+ "@applitools/snippets": "2.1.10",
10
53
  "@applitools/utils": "1.2.4",
11
54
  "png-async": "0.9.4"
12
55
  }
13
56
  },
14
57
  "@applitools/snippets": {
15
- "version": "2.1.7",
16
- "resolved": "https://registry.npmjs.org/@applitools/snippets/-/snippets-2.1.7.tgz",
17
- "integrity": "sha512-Tr4Gj7Qov/oPy+8WI4oVmmubxqpOzr8P3Wjzpl6rA57xKLg6/TiIg5oZNb4+jEmO2ShjNYLaEwRWHl7kPgb4fw=="
58
+ "version": "2.1.10",
59
+ "resolved": "https://registry.npmjs.org/@applitools/snippets/-/snippets-2.1.10.tgz",
60
+ "integrity": "sha512-LGjtd8IZOwbhETqKRDX9CX85+BskkFUkuMuLvriSDEEQiSm0MpFbbcJVMjYaVL2qobaJbY+3WvK0g7+gkZuuqA=="
18
61
  },
19
62
  "@applitools/utils": {
20
63
  "version": "1.2.4",
@@ -0,0 +1,5 @@
1
+ {
2
+ "dependencies": {
3
+ "@applitools/screenshoter": "file:../dry-run.tgz"
4
+ }
5
+ }
Binary file
package/CHANGELOG.md CHANGED
@@ -4,6 +4,25 @@
4
4
  ## Unreleased
5
5
 
6
6
 
7
+ ## 3.3.2 - 2021/12/20
8
+
9
+ - add basic support of webview screenshots on ios
10
+ - updated to @applitools/snippets@2.1.10 (from 2.1.8)
11
+
12
+ ## 3.3.1 - 2021/12/17
13
+
14
+ - no changes
15
+
16
+ ## 3.3.0 - 2021/12/16
17
+
18
+ - fix ios web screenshots on pages without viewport meta tag
19
+ - improve native apps support
20
+ - updated to @applitools/snippets@2.1.8 (from 2.1.7)
21
+
22
+ ## 3.2.9 - 2021/11/14
23
+
24
+ - add support of scrollable elements that change its size during scrolling
25
+
7
26
  ## 3.2.8 - 2021/10/30
8
27
 
9
28
  - updated to @applitools/utils@1.2.4 (from 1.2.3)
package/index.js CHANGED
@@ -1 +1,2 @@
1
- module.exports = require('./src/screenshoter')
1
+ module.exports = require('./src/take-screenshot')
2
+ exports.takeScreenshot = require('./src/take-screenshot')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applitools/screenshoter",
3
- "version": "3.2.8",
3
+ "version": "3.3.2",
4
4
  "description": "Applitools universal screenshoter for web and native applications",
5
5
  "keywords": [
6
6
  "applitools",
@@ -49,13 +49,16 @@
49
49
  }
50
50
  },
51
51
  "dependencies": {
52
- "@applitools/snippets": "2.1.7",
52
+ "@applitools/snippets": "2.1.10",
53
53
  "@applitools/utils": "1.2.4",
54
54
  "png-async": "0.9.4"
55
55
  },
56
56
  "devDependencies": {
57
- "@applitools/driver": "1.2.7",
57
+ "@applitools/driver": "1.4.5",
58
58
  "@applitools/sdk-release-kit": "0.13.4",
59
+ "@applitools/spec-driver-webdriverio": "1.2.2",
60
+ "@applitools/test-utils": "1.0.10",
61
+ "chromedriver": "^95.0.0",
59
62
  "eslint": "^7.9.0",
60
63
  "eslint-plugin-mocha-no-only": "^1.1.1",
61
64
  "eslint-plugin-node": "^11.1.0",
@@ -64,7 +67,7 @@
64
67
  "mocha": "^8.2.1",
65
68
  "pixelmatch": "^5.2.1",
66
69
  "prettier": "1.19.0",
67
- "webdriverio": "^6.10.5"
70
+ "webdriverio": "^7.16.7"
68
71
  },
69
72
  "engines": {
70
73
  "node": ">= 8.9.0"
@@ -1,60 +1,33 @@
1
1
  function findImagePattern(image, pattern) {
2
2
  for (let pixel = 0; pixel < image.width * image.height; ++pixel) {
3
3
  if (isPattern(image, pixel, pattern)) {
4
- return {
5
- x: (pixel % image.width) - pattern.offset,
6
- y: Math.floor(pixel / image.width) - pattern.offset,
7
- }
4
+ const patterOffset = pattern.offset * pattern.pixelRatio
5
+ return {x: (pixel % image.width) - patterOffset, y: Math.floor(pixel / image.width) - patterOffset}
8
6
  }
9
7
  }
10
8
  return null
11
9
  }
12
10
 
13
11
  function isPattern(image, index, pattern) {
14
- const channels = 4
15
- const roundNumber = pattern.size - Math.floor(pattern.size / 2)
16
- for (const [chunkIndex, chunkColor] of pattern.mask.entries()) {
17
- const pixelOffset = index + image.width * pattern.size * chunkIndex
18
- for (let round = 0; round < roundNumber; ++round) {
19
- const sideLength = pattern.size - round * 2
20
- const stepsNumber = sideLength * channels - channels
21
- const threshold = Math.min((roundNumber - round) * 10 + 10, 100)
22
- for (let step = 0; step < stepsNumber; ++step) {
23
- let pixelIndex = pixelOffset + round + round * image.width
24
-
25
- if (step < sideLength) {
26
- pixelIndex += step
27
- } else if (step < sideLength * 2 - 1) {
28
- pixelIndex += sideLength - 1 + ((step % sideLength) + 1) * image.width
29
- } else if (step < sideLength * 3 - 2) {
30
- pixelIndex += (sideLength - 1) * image.width + (sideLength - (step % sideLength) - 1)
31
- } else {
32
- pixelIndex += (step % sideLength) * image.width
33
- }
34
-
35
- const pixelColor = pixelColorAt(image, pixelIndex, threshold)
36
- if (pixelColor !== chunkColor) {
37
- return false
38
- }
39
- }
12
+ const itemLength = pattern.size * pattern.pixelRatio
13
+ for (const [itemIndex, itemColor] of pattern.mask.entries()) {
14
+ for (let partOffset = itemIndex * itemLength; partOffset < (itemIndex + 1) * itemLength; ++partOffset) {
15
+ const pixelColor = pixelColorAt(image, index + partOffset)
16
+ if (pixelColor !== itemColor) return false
40
17
  }
41
18
  }
42
19
  return true
43
20
  }
44
21
 
45
- function pixelColorAt(image, index, threshold = 0) {
22
+ function pixelColorAt(image, index) {
46
23
  const channels = 4
47
24
  const r = image.data[index * channels]
48
25
  const g = image.data[index * channels + 1]
49
26
  const b = image.data[index * channels + 2]
50
- const rgb = [r, g, b]
51
27
 
52
- // WHITE
53
- if (rgb.every(sub => sub >= 255 - threshold)) return 1
54
- // BLACK
55
- else if (rgb.every(sub => sub <= threshold)) return 0
56
- // OTHER
57
- else return -1
28
+ const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b
29
+
30
+ return luminance < 128 ? /* black */ 1 : /* white */ 0
58
31
  }
59
32
 
60
33
  module.exports = findImagePattern
package/src/image.js CHANGED
@@ -6,7 +6,7 @@ const utils = require('@applitools/utils')
6
6
 
7
7
  function makeImage(data) {
8
8
  let image, size
9
- let transforms = {rotate: 0, scale: 1, crop: null}
9
+ let transforms = {rotate: 0, scale: 1, crop: null, modifiers: []}
10
10
 
11
11
  if (utils.types.isBase64(data)) {
12
12
  const buffer = Buffer.from(data, 'base64')
@@ -20,21 +20,19 @@ function makeImage(data) {
20
20
  image = fromBuffer(data)
21
21
  size = extractPngSize(data)
22
22
  } else if (data.isImage) {
23
- image = data.toRaw()
24
- size = data.size
25
23
  transforms = data.transforms
24
+ image = data.toRaw()
25
+ size = utils.geometry.scale(data.size, 1 / transforms.scale)
26
26
  } else if (utils.types.has(data, ['width', 'height'])) {
27
27
  image = fromSize(data)
28
28
  if (data.data) image.data = data.data
29
29
  size = {width: data.width, height: data.height}
30
+ } else if (data.auto) {
31
+ size = {width: -1, height: -1}
30
32
  } else {
31
33
  throw new Error('Unable to create an image abstraction from unknown data')
32
34
  }
33
35
 
34
- if (!transforms.crop) {
35
- transforms.crop = utils.geometry.region({x: 0, y: 0}, size)
36
- }
37
-
38
36
  return {
39
37
  get isImage() {
40
38
  return true
@@ -46,10 +44,10 @@ function makeImage(data) {
46
44
  return {...transforms}
47
45
  },
48
46
  get width() {
49
- return size.width
47
+ return this.size.width
50
48
  },
51
49
  get height() {
52
- return size.height
50
+ return this.size.height
53
51
  },
54
52
  scale(ratio) {
55
53
  transforms.scale *= ratio
@@ -67,7 +65,9 @@ function makeImage(data) {
67
65
  region = utils.geometry.scale(region, 1 / transforms.scale)
68
66
  }
69
67
  region = utils.geometry.rotate(region, transforms.rotate)
70
- transforms.crop = utils.geometry.intersect(transforms.crop, utils.geometry.offset(region, transforms.crop))
68
+ transforms.crop = transforms.crop
69
+ ? utils.geometry.intersect(transforms.crop, utils.geometry.offset(region, transforms.crop))
70
+ : utils.geometry.intersect({x: 0, y: 0, ...size}, region)
71
71
 
72
72
  size = utils.geometry.round(utils.geometry.size(transforms.crop))
73
73
 
@@ -77,18 +77,43 @@ function makeImage(data) {
77
77
  transforms.rotate = (transforms.rotate + degree) % 360
78
78
  return this
79
79
  },
80
- async copy(srcImage, offset) {
81
- const [dst, src] = await Promise.all([this.toObject(), srcImage.toObject()])
82
- image = await copy(dst, src, offset)
80
+ copy(srcImage, offset) {
81
+ const scale = srcImage.transforms.scale
82
+ if (!image) {
83
+ size = {
84
+ width: Math.max(Math.floor((offset.x + srcImage.width) / scale), size.width),
85
+ height: Math.max(Math.floor((offset.y + srcImage.height) / scale), size.height),
86
+ }
87
+ transforms.scale = Math.min(scale, transforms.scale)
88
+ }
89
+ transforms.modifiers.push({
90
+ type: 'copy',
91
+ image: srcImage.scale(scale === transforms.scale ? 1 / scale : scale / transforms.scale).toObject(),
92
+ offset: utils.geometry.scale(offset, 1 / transforms.scale),
93
+ })
94
+
83
95
  return this
84
96
  },
85
- async combine(firstImage, lastImage, region) {
86
- const [first, last, src] = await Promise.all([firstImage.toObject(), lastImage.toObject(), this.toObject()])
87
- image = await combine(first, last, src, region)
88
- size = {width: image.width, height: image.height}
89
- transforms.crop = utils.geometry.region({x: 0, y: 0}, size)
97
+ frame(topImage, bottomImage, region) {
98
+ const scale = topImage.transforms.scale
99
+ const prevSize = size
100
+ region = utils.geometry.scale(region, 1 / scale)
101
+ size = {
102
+ width: Math.floor(topImage.width / scale + Math.max(size.width - region.width, 0)),
103
+ height: Math.floor(topImage.height / scale + Math.max(size.height - region.height, 0)),
104
+ }
105
+ transforms.modifiers.push({
106
+ type: 'frame',
107
+ top: topImage.scale(scale === transforms.scale ? 1 / scale : scale / transforms.scale).toObject(),
108
+ bottom: bottomImage.scale(scale === transforms.scale ? 1 / scale : scale / transforms.scale).toObject(),
109
+ region,
110
+ })
111
+ transforms.added = {width: size.width - prevSize.width, height: size.height - prevSize.height}
90
112
  return this
91
113
  },
114
+ async toRaw() {
115
+ return image
116
+ },
92
117
  async toBuffer() {
93
118
  const image = await this.toObject()
94
119
  return image.data
@@ -99,19 +124,17 @@ function makeImage(data) {
99
124
  async toFile(path) {
100
125
  return toFile(await image, path)
101
126
  },
102
- async toRaw() {
103
- return image
104
- },
105
127
  async toObject() {
106
- image = await transform(await image, transforms)
107
- transforms = {rotate: 0, scale: 1, crop: utils.geometry.region({x: 0, y: 0}, size)}
128
+ image = await transform(image ? await image : size, transforms)
129
+ transforms = {crop: null, scale: 1, rotate: 0, modifiers: []}
108
130
  return image
109
131
  },
110
132
  async debug(debug) {
111
133
  if (!debug || !debug.path) return
112
134
  const timestamp = new Date().toISOString().replace(/[-T:.]/g, '_')
113
135
  const filename = ['screenshot', timestamp, debug.name, debug.suffix].filter(part => part).join('_') + '.png'
114
- return toFile(await transform(await image, transforms), path.join(debug.path, filename)).catch(() => null)
136
+ const transformedImage = await transform(image ? await image : size, transforms)
137
+ return toFile(transformedImage, path.join(debug.path, filename)).catch(() => null)
115
138
  },
116
139
  }
117
140
  }
@@ -164,10 +187,27 @@ async function toFile(image, path) {
164
187
  }
165
188
 
166
189
  async function transform(image, transforms) {
167
- const croppedImage = transforms.crop ? await extract(image, transforms.crop) : image
168
- const scaledImage = transforms.scale !== 1 ? await scale(croppedImage, transforms.scale) : croppedImage
169
- const rotatedImage = transforms.rotate > 0 ? await rotate(scaledImage, transforms.rotate) : scaledImage
170
- return rotatedImage
190
+ if (!image.data) {
191
+ const size = transforms.added
192
+ ? {width: image.width - transforms.added.width, height: image.height - transforms.added.height}
193
+ : image
194
+ image = new png.Image(size)
195
+ }
196
+
197
+ image = await transforms.modifiers.reduce(async (image, modifier) => {
198
+ if (modifier.type === 'copy') {
199
+ return copy(await image, await modifier.image, modifier.offset)
200
+ } else if (modifier.type === 'frame') {
201
+ return frame(await modifier.top, await modifier.bottom, await image, modifier.region)
202
+ } else {
203
+ return image
204
+ }
205
+ }, image)
206
+
207
+ image = transforms.rotate > 0 ? await rotate(image, transforms.rotate) : image
208
+ image = transforms.crop ? await extract(image, transforms.crop) : image
209
+ image = transforms.scale !== 1 ? await scale(image, transforms.scale) : image
210
+ return image
171
211
  }
172
212
 
173
213
  async function scale(image, scaleRatio) {
@@ -288,95 +328,98 @@ async function copy(dstImage, srcImage, offset) {
288
328
  return dstImage
289
329
  }
290
330
 
291
- async function combine(firstImage, lastImage, srcImage, region) {
292
- region = utils.geometry.intersect({x: 0, y: 0, width: firstImage.width, height: firstImage.height}, region)
331
+ async function frame(topImage, bottomImage, srcImage, region) {
332
+ region = utils.geometry.intersect(
333
+ {x: 0, y: 0, width: topImage.width, height: topImage.height},
334
+ utils.geometry.round(region),
335
+ )
293
336
 
294
- if (region.x === 0 && region.y === 0 && region.width >= firstImage.width && region.height >= firstImage.height) {
337
+ if (region.x === 0 && region.y === 0 && region.width >= topImage.width && region.height >= topImage.height) {
295
338
  return srcImage
296
339
  }
297
340
 
298
- if (region.width === srcImage.width && region.height === srcImage.height) {
299
- await copy(firstImage, srcImage, {x: region.x, y: region.y})
300
- return firstImage
341
+ if (region.width >= srcImage.width && region.height >= srcImage.height) {
342
+ await copy(topImage, srcImage, {x: region.x, y: region.y})
343
+ return topImage
301
344
  }
302
345
 
303
346
  const dstImage = new png.Image({
304
- width: firstImage.width - region.width + srcImage.width,
305
- height: firstImage.height - region.height + srcImage.height,
347
+ width: topImage.width + Math.max(srcImage.width - region.width, 0),
348
+ height: topImage.height + Math.max(srcImage.height - region.height, 0),
306
349
  })
307
350
 
308
351
  if (region.width === srcImage.width) {
309
- const topImage = await extract(firstImage, {
352
+ const topExtImage = await extract(topImage, {
310
353
  x: 0,
311
354
  y: 0,
312
- width: firstImage.width,
355
+ width: topImage.width,
313
356
  height: region.y + region.height,
314
357
  })
315
- await copy(dstImage, topImage, {x: 0, y: 0})
358
+ await copy(dstImage, topExtImage, {x: 0, y: 0})
316
359
  } else if (region.height === srcImage.height) {
317
- const leftImage = await extract(firstImage, {
360
+ const leftExtImage = await extract(topImage, {
318
361
  x: 0,
319
362
  y: 0,
320
363
  width: region.x + region.width,
321
- height: firstImage.height,
364
+ height: topImage.height,
322
365
  })
323
- await copy(dstImage, leftImage, {x: 0, y: 0})
366
+ await copy(dstImage, leftExtImage, {x: 0, y: 0})
324
367
  } else {
325
- const topLeftImage = await extract(firstImage, {
368
+ const topLeftExtImage = await extract(topImage, {
326
369
  x: 0,
327
370
  y: 0,
328
371
  width: region.x + region.width,
329
372
  height: region.y + region.height,
330
373
  })
331
- await copy(dstImage, topLeftImage, {x: 0, y: 0})
374
+ await copy(dstImage, topLeftExtImage, {x: 0, y: 0})
332
375
 
333
- const rightExtImage = await extract(firstImage, {
376
+ const rightExtImage = await extract(topImage, {
334
377
  x: region.x + region.width,
335
378
  y: 0,
336
- width: firstImage.width - (region.x + region.width),
379
+ width: topImage.width - (region.x + region.width),
337
380
  height: region.y,
338
381
  })
339
382
  await copy(dstImage, rightExtImage, {x: region.x + region.width, y: 0})
340
383
 
341
- const bottomExtImage = await extract(firstImage, {
384
+ const bottomExtImage = await extract(topImage, {
342
385
  x: 0,
343
386
  y: region.y + region.height,
344
387
  width: region.x,
345
- height: firstImage.height - (region.y + region.height),
388
+ height: topImage.height - (region.y + region.height),
346
389
  })
347
390
  await copy(dstImage, bottomExtImage, {x: 0, y: region.y + region.height})
348
391
  }
349
392
 
350
- if (lastImage.height > region.y + region.height || lastImage.width > region.x + region.width) {
393
+ if (bottomImage.height > region.y + region.height || bottomImage.width > region.x + region.width) {
351
394
  // first image might be higher
352
- const yDiff = firstImage.height - lastImage.height
395
+ const yDiff = topImage.height - bottomImage.height
353
396
  if (region.width === srcImage.width) {
354
- const bottomImage = await extract(lastImage, {
397
+ const bottomExtImage = await extract(bottomImage, {
355
398
  x: 0,
356
399
  y: region.y - yDiff + region.height,
357
- width: lastImage.width,
358
- height: lastImage.height - (region.y - yDiff + region.height),
400
+ width: bottomImage.width,
401
+ height: bottomImage.height - (region.y - yDiff + region.height),
359
402
  })
360
- await copy(dstImage, bottomImage, {x: 0, y: region.y + srcImage.height})
403
+ await copy(dstImage, bottomExtImage, {x: 0, y: region.y + Math.max(srcImage.height, region.height)})
361
404
  } else if (region.height === srcImage.height) {
362
- const rightImage = await extract(lastImage, {
405
+ const rightExtImage = await extract(bottomImage, {
363
406
  x: region.x + region.width,
364
407
  y: 0,
365
- width: lastImage.width - (region.x + region.width),
366
- height: lastImage.height,
408
+ width: bottomImage.width - (region.x + region.width),
409
+ height: bottomImage.height,
367
410
  })
368
- await copy(dstImage, rightImage, {x: region.x + srcImage.width, y: 0})
411
+ await copy(dstImage, rightExtImage, {x: region.x + Math.max(srcImage.width, region.width), y: 0})
369
412
  } else {
370
- const bottomRightImage = await extract(lastImage, {
413
+ const bottomRightExtImage = await extract(bottomImage, {
371
414
  x: region.x,
372
415
  y: region.y - yDiff,
373
- width: lastImage.width - region.x,
374
- height: lastImage.height - (region.y - yDiff),
416
+ width: bottomImage.width - region.x,
417
+ height: bottomImage.height - (region.y - yDiff),
375
418
  })
376
419
 
377
- await copy(dstImage, bottomRightImage, {
378
- x: region.x + srcImage.width - region.width,
379
- y: region.y + srcImage.height - region.height,
420
+ await copy(dstImage, bottomRightExtImage, {
421
+ x: region.x + Math.max(srcImage.width - region.width, 0),
422
+ y: region.y + Math.max(srcImage.height - region.height, 0),
380
423
  })
381
424
  }
382
425
  }