@applitools/screenshoter
Advanced tools
Comparing version 3.0.6 to 3.0.7
@@ -7,3 +7,3 @@ { | ||
"version": "file:../dry-run.tgz", | ||
"integrity": "sha512-EVXuU3UF5h7CGPUZFqhSAnO99kIHiF1nu/KFbXtpdY7F06id3JFbj6pGqjPDLg7RZlstx4XzPUWCBhUbmf/Y/Q==", | ||
"integrity": "sha512-m+EtXKrNrh5j2eiVtOeinQXOBvb+lgHV0CM8Vvl9RKwaJNAA1ApkAhaGkZy7uowL+6FfGOrWW2zrhkcmL0BYGw==", | ||
"requires": { | ||
@@ -10,0 +10,0 @@ "@applitools/utils": "1.1.3", |
@@ -7,2 +7,7 @@ | ||
## 3.0.7 - 2021/5/13 | ||
fixed image cropping algorithm to not copy data into a heap | ||
optimized image rotation and image copping algorithms | ||
## 3.0.6 - 2021/5/11 | ||
@@ -9,0 +14,0 @@ |
{ | ||
"name": "@applitools/screenshoter", | ||
"version": "3.0.6", | ||
"version": "3.0.7", | ||
"description": "Applitools universal screenshoter for web and native applications", | ||
@@ -51,11 +51,11 @@ "keywords": [ | ||
"test": "yarn test:it && yarn test:coverage", | ||
"test:it": "mocha --no-timeouts './test/it/*.spec.js'", | ||
"test:coverage": "mocha --no-timeouts './test/coverage/*.spec.js'", | ||
"test:it": "mocha --no-timeouts './test/it/*.spec.js'", | ||
"preversion": "bongo preversion && yarn test", | ||
"version": "bongo version", | ||
"postversion": "bongo postversion --skip-release-notification", | ||
"deps": "bongo deps", | ||
"docker:setup": "node ../sdk-shared/src/generate-docker-compose-config.js && docker-compose up -d", | ||
"docker:teardown": "docker-compose down", | ||
"prepublish:setup": "yarn docker:setup" | ||
"setup": "yarn docker:setup", | ||
"deps": "bongo deps", | ||
"preversion": "bongo preversion", | ||
"version": "bongo version", | ||
"postversion": "bongo postversion --skip-release-notification" | ||
}, | ||
@@ -62,0 +62,0 @@ "license": "SEE LICENSE IN LICENSE", |
133
src/image.js
@@ -141,3 +141,3 @@ const fs = require('fs') | ||
async function crop(image, region) { | ||
if (utils.types.has(region, 'left')) { | ||
if (utils.types.has(region, ['left', 'right', 'top', 'bottom'])) { | ||
region = { | ||
@@ -151,23 +151,29 @@ x: region.left, | ||
// process the pixels - crop | ||
const croppedArray = [] | ||
const yStart = Math.max(0, Math.round(region.y)) | ||
const yEnd = Math.min(image.height, Math.round(region.y + region.height)) | ||
const xStart = Math.max(0, Math.round(region.x)) | ||
const xEnd = Math.min(image.width, Math.round(region.width + region.x)) | ||
const srcX = Math.max(0, Math.round(region.x)) | ||
const srcY = Math.max(0, Math.round(region.y)) | ||
const dstWidth = Math.round(Math.min(image.width - srcX, region.width)) | ||
const dstHeight = Math.round(Math.min(image.height - srcY, region.height)) | ||
let y, x, idx, i | ||
for (y = yStart; y < yEnd; y += 1) { | ||
for (x = xStart; x < xEnd; x += 1) { | ||
idx = (image.width * y + x) * 4 | ||
for (i = 0; i < 4; i += 1) { | ||
croppedArray.push(image.data[idx + i]) | ||
} | ||
} | ||
if (srcX === 0 && dstWidth === image.width) { | ||
const srcOffset = srcY * image.width * 4 | ||
const dstLength = dstWidth * dstHeight * 4 | ||
image.data = image.data.subarray(srcOffset, srcOffset + dstLength) | ||
image.width = dstWidth | ||
image.height = dstHeight | ||
return image | ||
} | ||
image.data = Buffer.from(croppedArray) | ||
image.width = xEnd - xStart | ||
image.height = yEnd - yStart | ||
const cropped = Buffer.alloc(dstWidth * dstHeight * 4) | ||
const chunkLength = dstWidth * 4 | ||
for (let chunk = 0; chunk < dstHeight; ++chunk) { | ||
const srcOffset = ((srcY + chunk) * image.width + srcX) * 4 | ||
cropped.set(image.data.subarray(srcOffset, srcOffset + chunkLength), chunk * chunkLength) | ||
} | ||
image.data = cropped | ||
image.width = dstWidth | ||
image.height = dstHeight | ||
return image | ||
@@ -177,28 +183,42 @@ } | ||
async function rotate(image, degrees) { | ||
let i = Math.round(degrees / 90) % 4 | ||
while (i < 0) { | ||
i += 4 | ||
degrees = (360 + degrees) % 360 | ||
const dstImage = { | ||
data: Buffer.alloc(image.data.length), | ||
} | ||
while (i > 0) { | ||
const dstBuffer = Buffer.alloc(image.data.length) | ||
let dstOffset = 0 | ||
for (let x = 0; x < image.width; x += 1) { | ||
for (let y = image.height - 1; y >= 0; y -= 1) { | ||
const srcOffset = (image.width * y + x) * 4 | ||
const data = image.data.readUInt32BE(srcOffset) | ||
dstBuffer.writeUInt32BE(data, dstOffset) | ||
dstOffset += 4 | ||
if (degrees === 90) { | ||
dstImage.width = image.height | ||
dstImage.height = image.width | ||
for (let srcY = 0, dstX = image.height - 1; srcY < image.height; ++srcY, --dstX) { | ||
for (let srcX = 0, dstY = 0; srcX < image.width; ++srcX, ++dstY) { | ||
const pixel = image.data.readUInt32BE((srcY * image.width + srcX) * 4) | ||
dstImage.data.writeUInt32BE(pixel, (dstY * dstImage.width + dstX) * 4) | ||
} | ||
} | ||
} else if (degrees === 180) { | ||
dstImage.width = image.width | ||
dstImage.height = image.height | ||
for (let srcY = 0, dstY = image.height - 1; srcY < image.height; ++srcY, --dstY) { | ||
for (let srcX = 0, dstX = image.width - 1; srcX < image.width; ++srcX, --dstX) { | ||
const pixel = image.data.readUInt32BE((srcY * image.width + srcX) * 4) | ||
dstImage.data.writeUInt32BE(pixel, (dstY * dstImage.width + dstX) * 4) | ||
} | ||
} | ||
} else if (degrees === 270) { | ||
dstImage.width = image.height | ||
dstImage.height = image.width | ||
for (let srcY = 0, dstX = 0; srcY < image.height; ++srcY, ++dstX) { | ||
for (let srcX = 0, dstY = image.width - 1; srcX < image.width; ++srcX, --dstY) { | ||
const pixel = image.data.readUInt32BE((srcY * image.width + srcX) * 4) | ||
dstImage.data.writeUInt32BE(pixel, (srcX * dstImage.width + dstY) * 4) | ||
} | ||
} | ||
} else { | ||
return image | ||
} | ||
image.data = Buffer.from(dstBuffer) | ||
const tmp = image.width | ||
image.data = dstImage.data | ||
image.width = dstImage.width | ||
image.height = dstImage.height | ||
image.width = image.height | ||
image.height = tmp | ||
i -= 1 | ||
} | ||
return image | ||
@@ -208,26 +228,19 @@ } | ||
async function copy(dstImage, srcImage, offset) { | ||
const offsetX = Math.round(offset.x) | ||
const offsetY = Math.round(offset.y) | ||
const dstX = Math.round(offset.x) | ||
const dstY = Math.round(offset.y) | ||
const srcWidth = Math.min(srcImage.width, dstImage.width - dstX) | ||
const srcHeight = Math.min(srcImage.height, dstImage.height - dstY) | ||
// Fix the problem when src image was out of dst image and pixels was copied to wrong position in dst image. | ||
const maxHeight = | ||
offsetY + srcImage.height <= dstImage.height ? srcImage.height : dstImage.height - offsetY | ||
const maxWidth = | ||
offsetX + srcImage.width <= dstImage.width ? srcImage.width : dstImage.width - offsetX | ||
if (dstX === 0 && srcWidth === dstImage.width && srcWidth === srcImage.width) { | ||
const dstOffset = dstY * dstImage.width * 4 | ||
dstImage.data.set(srcImage.data.subarray(0, srcWidth * srcHeight * 4), dstOffset) | ||
for (let srcY = 0; srcY < maxHeight; srcY += 1) { | ||
const dstY = offsetY + srcY | ||
return dstImage | ||
} | ||
for (let srcX = 0; srcX < maxWidth; srcX += 1) { | ||
const dstX = offsetX + srcX | ||
// Since each pixel is composed of 4 values (RGBA) we multiply each index by 4. | ||
const dstIndex = (dstY * dstImage.width + dstX) * 4 | ||
const srcIndex = (srcY * srcImage.width + srcX) * 4 | ||
dstImage.data[dstIndex] = srcImage.data[srcIndex] | ||
dstImage.data[dstIndex + 1] = srcImage.data[srcIndex + 1] | ||
dstImage.data[dstIndex + 2] = srcImage.data[srcIndex + 2] | ||
dstImage.data[dstIndex + 3] = srcImage.data[srcIndex + 3] | ||
} | ||
const chunkLength = srcWidth * 4 | ||
for (let chunk = 0; chunk < srcHeight; ++chunk) { | ||
const srcOffset = chunk * srcImage.width * 4 | ||
const dstOffset = ((dstY + chunk) * dstImage.width + dstX) * 4 | ||
dstImage.data.set(srcImage.data.subarray(srcOffset, srcOffset + chunkLength), dstOffset) | ||
} | ||
@@ -234,0 +247,0 @@ |
@@ -31,2 +31,10 @@ const assert = require('assert') | ||
it('should crop a big image without heap overflow', async () => { | ||
const actual = await makeImage({width: 1000, height: 50000}) | ||
.crop({x: 0, y: 0, width: 1000, height: 49500}) | ||
.then(image => image.toObject()) | ||
assert.strictEqual(actual.width, 1000) | ||
assert.strictEqual(actual.height, 49500) | ||
}) | ||
it('should scale', async () => { | ||
@@ -48,2 +56,10 @@ const actual = await makeImage('./test/fixtures/image/house.png') | ||
it('should rotate a big image without heap overflow', async () => { | ||
const actual = await makeImage({width: 1000, height: 50000}) | ||
.rotate(270) | ||
.then(image => image.toObject()) | ||
assert.strictEqual(actual.width, 50000) | ||
assert.strictEqual(actual.height, 1000) | ||
}) | ||
it('should copy one image to another', async () => { | ||
@@ -58,2 +74,11 @@ const image = await makeImage('./test/fixtures/image/house.png').toObject() | ||
}) | ||
it('should copy a big image without heap overflow', async () => { | ||
const source = await makeImage({width: 1000, height: 50000}).toObject() | ||
const actual = await makeImage({width: 1000, height: 50000}) | ||
.copy(source, {x: 100, y: 500}) | ||
.then(image => image.toObject()) | ||
assert.strictEqual(actual.width, 1000) | ||
assert.strictEqual(actual.height, 50000) | ||
}) | ||
}) |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
10459515
41
1648
2