Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

image-comparator

Package Overview
Dependencies
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

image-comparator - npm Package Compare versions

Comparing version 1.1.0 to 1.2.0

.prettierignore

22

index.js

@@ -7,15 +7,25 @@ const {

extractBitmapAndChannelCount,
MIMES,
} = require("./lib");
module.exports = {
compare: async (buffer1, buffer2) =>
areBuffersEqual(
compare: async (buffer1, buffer2) => {
const isInterformatComparison =
buffer1[0] === MIMES.WEBP && buffer1[0] !== buffer2[0];
if (isInterformatComparison) {
throw new Error("Interformat webp comparison not supported");
}
return areBuffersEqual(
...(await Promise.all([compareImpl(buffer1), compareImpl(buffer2)])),
BYTE_COMPARERS[buffer1.at(0)],
BYTE_COMPARERS[buffer2.at(0)]
),
BYTE_COMPARERS[buffer1[0]],
BYTE_COMPARERS[buffer2[0]],
buffer1[0]
);
},
};
const compareImpl = async (buffer) => {
const { width, height } = getDimensions[buffer.at(0)](buffer);
const { width, height } = getDimensions[buffer[0]](buffer);

@@ -22,0 +32,0 @@ const { channels, data: bitmap } = await extractBitmapAndChannelCount(buffer);

@@ -1,2 +0,2 @@

const { HIT_THRESHOLD } = require("./enums");
const { HIT_THRESHOLD, MIMES } = require("./enums");

@@ -8,3 +8,4 @@ // https://stackoverflow.com/a/21554107

comparerOfBuffer1,
comparerOfBuffer2
comparerOfBuffer2,
mime
) => {

@@ -14,2 +15,18 @@ const view1 = new Int8Array(buffer1);

if (mime === MIMES.WEBP) {
const total = 16;
let hits = 0;
for (let i = 0; i < total; i++) {
if (
comparerOfBuffer1(view1[i], view2[i]) ||
comparerOfBuffer2(view1[i], view2[i])
) {
hits++;
}
}
return hits / total >= 0.7;
}
if (buffer1.byteLength != buffer2.byteLength) {

@@ -16,0 +33,0 @@ let hits = 0;

const MIMES = {
PNG: 0x89,
JPG: 0xff,
WEBP: 0x52,
};

@@ -9,2 +10,3 @@

[MIMES.JPG]: (byte1, byte2) => Math.abs(byte1 - byte2) < 30,
[MIMES.WEBP]: (byte1, byte2) => Math.abs(byte1 - byte2) < 30,
};

@@ -11,0 +13,0 @@

@@ -17,3 +17,9 @@ const {

const { toUTF8String, toHexString } = require("./formatters");
const { readUInt32BE, readUInt, readUInt16BE } = require("./readers");
const {
readUInt32BE,
readUInt,
readUInt16BE,
readInt16LE,
readUInt24LE,
} = require("./readers");

@@ -103,2 +109,27 @@ function isEXIF(input) {

function calculateExtended(input) {
return {
height: 1 + readUInt24LE(input, 7),
width: 1 + readUInt24LE(input, 4),
};
}
function calculateLossless(input) {
return {
height:
1 +
(((input[4] & 0xf) << 10) | (input[3] << 2) | ((input[2] & 0xc0) >> 6)),
width: 1 + (((input[2] & 0x3f) << 8) | input[1]),
};
}
function calculateLossy(input) {
// `& 0x3fff` returns the last 14 bits
// TO-DO: include webp scaling in the calculations
return {
height: readInt16LE(input, 8) & 0x3fff,
width: readInt16LE(input, 6) & 0x3fff,
};
}
const getDimensions = {

@@ -158,2 +189,33 @@ // https://github.com/image-size/image-size/blob/08389eb1d6804485a3ae5964dfe827df885b0c54/lib/types/png.ts#L25

},
// https://github.com/image-size/image-size/blob/08389eb1d6804485a3ae5964dfe827df885b0c54/lib/types/webp.ts#L38
[MIMES.WEBP]: (input) => {
const chunkHeader = toUTF8String(input, 12, 16);
input = input.slice(20, 30);
// Extended webp stream signature
if (chunkHeader === "VP8X") {
const extendedHeader = input[0];
const validStart = (extendedHeader & 0xc0) === 0;
const validEnd = (extendedHeader & 0x01) === 0;
if (validStart && validEnd) {
return calculateExtended(input);
} else {
// TODO: breaking change
throw new TypeError("Invalid WebP");
}
}
// Lossless webp stream signature
if (chunkHeader === "VP8 " && input[0] !== 0x2f) {
return calculateLossy(input);
}
// Lossy webp stream signature
const signature = toHexString(input, 3, 6);
if (chunkHeader === "VP8L" && signature !== "9d012a") {
return calculateLossless(input);
}
throw new TypeError("Invalid WebP");
},
};

@@ -160,0 +222,0 @@

const Resize = require("./Resize");
const { MIMES } = require("./enums");
const parseJpg = require("./jpgparse");
const { WebPDecoder, WebPRiffParser } = require("./webpparse");
const { parse: parsePng } = require("./pngparse");
const parseJpg = require("./jpgparse");
const parseMapper = {
[0x89]: (buffer) =>
[MIMES.PNG]: (buffer) =>
new Promise((resolve, reject) => {

@@ -16,7 +18,24 @@ parsePng(buffer, (error, bitmap) => {

}),
[0xff]: parseJpg,
[MIMES.JPG]: parseJpg,
[MIMES.WEBP]: async (buffer) => {
const {
frames: [{ src_off: srcOff, src_size: srcSize }],
} = WebPRiffParser(buffer, 0);
const decoder = new WebPDecoder();
const dimensions = { width: [], height: [] };
return {
data: decoder.WebPDecodeRGBA(
buffer,
srcOff,
srcSize,
dimensions.width,
dimensions.height
),
channels: 4,
};
},
};
const extractBitmapAndChannelCount = (buffer) =>
parseMapper[buffer.at(0)](buffer);
const extractBitmapAndChannelCount = (buffer) => parseMapper[buffer[0]](buffer);

@@ -23,0 +42,0 @@ module.exports = {

@@ -12,2 +12,7 @@ // github.com/image-size/image-size/blob/08389eb1d6804485a3ae5964dfe827df885b0c54/lib/types/utils.ts

const readInt16LE = (input, offset = 0) => {
const val = input[offset] + input[offset + 1] * 2 ** 8;
return val | ((val & (2 ** 15)) * 0x1fffe);
};
const readUInt = (input, bits, offset, isBigEndian) => {

@@ -20,6 +25,11 @@ offset = offset || 0;

const readUInt24LE = (input, offset = 0) =>
input[offset] + input[offset + 1] * 2 ** 8 + input[offset + 2] * 2 ** 16;
module.exports = {
readUInt,
readInt16LE,
readUInt24LE,
readUInt32BE,
readUInt16BE,
};
{
"name": "image-comparator",
"version": "1.1.0",
"version": "1.2.0",
"description": "Compares images by resizing without dependencies",

@@ -5,0 +5,0 @@ "main": "index.js",

@@ -62,2 +62,28 @@ const assert = require("node:assert");

describe("webp", () => {
const commonJpgImagesPath = [...commonImagesPath, "webp"];
it("should return false for different webp images", async () => {
const expected = false;
const commonPathSegments = [...commonJpgImagesPath, "different"];
const image1 = readFileSync(join(...commonPathSegments, "image1.webp"));
const image2 = readFileSync(join(...commonPathSegments, "image2.webp"));
const actual = await compare(image1, image2);
assert.strictEqual(actual, expected);
});
it("should return true for same webp images", async () => {
const expected = true;
const commonPathSegments = [...commonJpgImagesPath, "same"];
const image1 = readFileSync(join(...commonPathSegments, "image1.webp"));
const image2 = readFileSync(join(...commonPathSegments, "image2.webp"));
const actual = await compare(image1, image2);
assert.strictEqual(actual, expected);
});
});
describe("mixins", () => {

@@ -94,2 +120,16 @@ const commonPngImagesPath = [...commonImagesPath, "png"];

});
it("should throw on interformat webp comparison (not implemented)", async () => {
const expected = Error;
const jpgImage = readFileSync(
join(...commonImagesPath, "webp", "same", "image1.webp")
);
const pngImage = readFileSync(
join(...commonImagesPath, "mixins", "image.png")
);
const actual = () => compare(jpgImage, pngImage);
assert.rejects(actual, expected);
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc