Skip to content

Commit

Permalink
refactor: remove duplicate logic, add iFrameManipulator and ImageComp…
Browse files Browse the repository at this point in the history
…arison algo classes
  • Loading branch information
tiagofilipenunes committed Sep 21, 2024
1 parent cabf9e5 commit 00d8eef
Show file tree
Hide file tree
Showing 5 changed files with 330 additions and 298 deletions.
39 changes: 16 additions & 23 deletions src/content-scripts/image-diff.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,28 @@
import { addNewViewElement, loadImage, algos, findMainElement } from "../logic";
import { IFrameManipulator } from "../logic/utils";
import { ImageComparisonFactory, algoNames } from "../logic";

const processImages = async () => {
const mainElement = findMainElement();
const dataFileA = mainElement.getAttribute("data-file1");
const dataFileB = mainElement.getAttribute("data-file2");
if (!dataFileA || !dataFileB) throw Error("Couldn't get data URL");

try {
const [loadedImageA, loadedImageB] = await Promise.all([
loadImage(dataFileA),
loadImage(dataFileB),
]);
const iFrameManipulator = new IFrameManipulator();
const [loadedImageA, loadedImageB] = await iFrameManipulator.loadImages();
console.log("Finished loading images");

await Promise.all(
algos.map(async (algo) => {
const diffElement = addNewViewElement(mainElement, algo.name);
const isDiffSize =
!diffElement ||
loadedImageA.width !== loadedImageB.width ||
loadedImageA.height !== loadedImageB.height;
if (isDiffSize) return;

algo.func(diffElement, loadedImageA, loadedImageB);
algoNames.map(async (algoName) => {
const algo = ImageComparisonFactory.createAlgo(
algoName,
loadedImageA,
loadedImageB
);
if (!algo.isValidAlgo()) return;
const diffElement = iFrameManipulator.addNewViewElement(algoName);
await algo.createViewElement(diffElement);
})
);
console.log("Finished loading images");
console.log("Processing complete");
} catch (error) {
console.error("Failed to load image for reason: ", error);
}
};

processImages().then(() => {
console.log("Processing complete");
});
processImages();
281 changes: 146 additions & 135 deletions src/logic/algos.ts
Original file line number Diff line number Diff line change
@@ -1,159 +1,170 @@
import pixelmatch, { RGBTuple } from "pixelmatch";
import { createCanvasElement, getSettings, type Algo } from "../logic";
import pixelmatch, { PixelmatchOptions } from "pixelmatch";
import {
type AlgoName,
createCanvasElement,
getSettings,
hex2rgb,
} from "../logic";
import "./style.css";

const MAX_WIDTH = 414;

export const imgOverlay = async (
imgA: HTMLImageElement,
imgB: HTMLImageElement,
canvasDiff: HTMLCanvasElement
): Promise<number> => {
const ctxA = createCanvasElement(imgA);
const ctxB = createCanvasElement(imgB);
canvasDiff.width = imgA.width;
canvasDiff.height = imgA.height;
const diffCtx = canvasDiff.getContext("2d");
if (!diffCtx) throw Error("Couldn't get diff 2d context");
const diff = diffCtx.createImageData(imgA.width, imgA.height);
const settings = await getSettings();
const diffColor = hex2rgb(String(settings.diffColor));
const mismatchedPixels = pixelmatch(
ctxA.getImageData(0, 0, imgA.width, imgA.height).data,
ctxB.getImageData(0, 0, imgB.width, imgB.height).data,
diff.data,
imgA.width,
imgA.height,
{
threshold: 0,
alpha: 0.9,
diffColor,
aaColor: diffColor,
}
);
diffCtx.putImageData(diff, 0, 0);
return mismatchedPixels;
};
abstract class ImageComparisonAlgo {
constructor(
protected imgA: HTMLImageElement,
protected imgB: HTMLImageElement
) {}

const hex2rgb = (hex: string): RGBTuple => {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return [r, g, b];
};
abstract isValidAlgo(): boolean;
abstract createViewElement(viewElement: HTMLDivElement): void | Promise<void>;
}

class DifferenceAlgo extends ImageComparisonAlgo {
isValidAlgo = () => {
const isSameSize =
this.imgA.width === this.imgB.width &&
this.imgA.height === this.imgB.height;
return isSameSize;
};

export const imgDiff = async (
createViewElement = async (viewElement: HTMLDivElement) => {
const settings = await getSettings();
const diffColor = hex2rgb(String(settings.diffColor));
const canvasDiff = document.createElement("canvas");
const mismatchedPixels = createDiffOnCanvas(
this.imgA,
this.imgB,
canvasDiff,
{
threshold: 0,
diffMask: true,
diffColor,
aaColor: diffColor,
}
);
const newDiv = document.createElement("div");
newDiv.textContent = `Mismatched pixels: ${mismatchedPixels}`;
const diffWidth = Math.min(this.imgA.width, MAX_WIDTH);
const diffHeight =
this.imgA.width <= MAX_WIDTH
? this.imgA.height
: (this.imgA.height / this.imgA.width) * MAX_WIDTH;

// Style newDiv and viewElements
newDiv.style.setProperty("--diff-width", `${diffWidth}px`);
newDiv.style.setProperty("--diff-height", `${diffHeight}px`);
newDiv.classList.add("diff-frame");
newDiv.classList.add("diffView");
viewElement.style.setProperty("--diff-width", `${diffWidth}px`);
viewElement.style.setProperty("--diff-height", `${diffHeight}px`);
viewElement.classList.add("viewElement");

// Wrap canvas in img
const img = document.createElement("img");
img.src = canvasDiff.toDataURL();

// Set image style
img.style.setProperty("--diff-width", `${diffWidth}px`);
img.style.setProperty("--diff-height", `${diffHeight}px`);
img.classList.add("diffImage");

newDiv.appendChild(img);
viewElement.appendChild(newDiv);
};
}

class OverlayAlgo extends ImageComparisonAlgo {
isValidAlgo = () => {
const isSameSize =
this.imgA.width === this.imgB.width &&
this.imgA.height === this.imgB.height;
return isSameSize;
};

async createViewElement(viewElement: HTMLDivElement) {
const settings = await getSettings();
const diffColor = hex2rgb(String(settings.diffColor));
const canvasDiff = document.createElement("canvas");
const mismatchedPixels = createDiffOnCanvas(
this.imgA,
this.imgB,
canvasDiff,
{
threshold: 0,
alpha: 0.9,
diffColor,
aaColor: diffColor,
}
);
const newDiv = document.createElement("div");
newDiv.textContent = `Mismatched pixels: ${mismatchedPixels}`;
const diffWidth = Math.min(this.imgA.width, MAX_WIDTH);
const diffHeight =
this.imgA.width <= MAX_WIDTH
? this.imgA.height
: (this.imgA.height / this.imgA.width) * MAX_WIDTH;

// Style newDiv and viewElements
newDiv.style.setProperty("--diff-width", `${diffWidth}px`);
newDiv.style.setProperty("--diff-height", `${diffHeight}px`);
newDiv.classList.add("diff-frame");
newDiv.classList.add("diffView");
viewElement.style.setProperty("--diff-width", `${diffWidth}px`);
viewElement.style.setProperty("--diff-height", `${diffHeight}px`);
viewElement.classList.add("viewElement");

// Wrap canvas in img
const img = document.createElement("img");
img.src = canvasDiff.toDataURL();

// Set image style
img.style.setProperty("--diff-width", `${diffWidth}px`);
img.style.setProperty("--diff-height", `${diffHeight}px`);
img.classList.add("diffImage");

newDiv.appendChild(img);
viewElement.appendChild(newDiv);
}
}

export const createDiffOnCanvas = (
imgA: HTMLImageElement,
imgB: HTMLImageElement,
canvasDiff: HTMLCanvasElement
): Promise<number> => {
canvasElement: HTMLCanvasElement,
options: PixelmatchOptions
): number => {
const ctxA = createCanvasElement(imgA);
const ctxB = createCanvasElement(imgB);
canvasDiff.width = imgA.width;
canvasDiff.height = imgA.height;
const diffCtx = canvasDiff.getContext("2d");
canvasElement.width = imgA.width;
canvasElement.height = imgA.height;
const diffCtx = canvasElement.getContext("2d");
if (!diffCtx) throw Error("Couldn't get diff 2d context");
const diff = diffCtx.createImageData(imgA.width, imgA.height);
const settings = await getSettings();
const diffColor = hex2rgb(String(settings.diffColor));
const mismatchedPixels = pixelmatch(
ctxA.getImageData(0, 0, imgA.width, imgA.height).data,
ctxB.getImageData(0, 0, imgB.width, imgB.height).data,
diff.data,
imgA.width,
imgA.height,
{
threshold: 0,
diffMask: true,
diffColor,
aaColor: diffColor,
}
options
);
diffCtx.putImageData(diff, 0, 0);
return mismatchedPixels;
};

export const createDifferenceElement = async (
viewElement: HTMLDivElement,
imgA: HTMLImageElement,
imgB: HTMLImageElement
) => {
const canvasDiff = document.createElement("canvas");
const mismatchedPixels = await imgDiff(imgA, imgB, canvasDiff);
const newDiv = document.createElement("div");
newDiv.textContent = `Mismatched pixels: ${mismatchedPixels}`;
const diffWidth = Math.min(imgA.width, MAX_WIDTH);
const diffHeight =
imgA.width <= MAX_WIDTH
? imgA.height
: (imgA.height / imgA.width) * MAX_WIDTH;

// Style newDiv and viewElements
newDiv.style.setProperty("--diff-width", `${diffWidth}px`);
newDiv.style.setProperty("--diff-height", `${diffHeight}px`);
newDiv.classList.add("diff-frame");
newDiv.classList.add("diffView");
viewElement.style.setProperty("--diff-width", `${diffWidth}px`);
viewElement.style.setProperty("--diff-height", `${diffHeight}px`);
viewElement.classList.add("viewElement");

// Wrap canvas in img
const img = document.createElement("img");
img.src = canvasDiff.toDataURL();

// Set image style
img.style.setProperty("--diff-width", `${diffWidth}px`);
img.style.setProperty("--diff-height", `${diffHeight}px`);
img.classList.add("diffImage");

newDiv.appendChild(img);
viewElement.appendChild(newDiv);
};

export const createOverlayElement = async (
viewElement: HTMLDivElement,
imgA: HTMLImageElement,
imgB: HTMLImageElement
) => {
const canvasDiff = document.createElement("canvas");
const mismatchedPixels = await imgOverlay(imgA, imgB, canvasDiff);
const newDiv = document.createElement("div");
newDiv.textContent = `Mismatched pixels: ${mismatchedPixels}`;
const diffWidth = Math.min(imgA.width, MAX_WIDTH);
const diffHeight =
imgA.width <= MAX_WIDTH
? imgA.height
: (imgA.height / imgA.width) * MAX_WIDTH;

// Style newDiv and viewElements
newDiv.style.setProperty("--diff-width", `${diffWidth}px`);
newDiv.style.setProperty("--diff-height", `${diffHeight}px`);
newDiv.classList.add("diff-frame");
newDiv.classList.add("diffView");
viewElement.style.setProperty("--diff-width", `${diffWidth}px`);
viewElement.style.setProperty("--diff-height", `${diffHeight}px`);
viewElement.classList.add("viewElement");

// Wrap canvas in img
const img = document.createElement("img");
img.src = canvasDiff.toDataURL();

// Set image style
img.style.setProperty("--diff-width", `${diffWidth}px`);
img.style.setProperty("--diff-height", `${diffHeight}px`);
img.classList.add("diffImage");

newDiv.appendChild(img);
viewElement.appendChild(newDiv);
};
export class ImageComparisonFactory {
static createAlgo(
algoName: AlgoName,
imgA: HTMLImageElement,
imgB: HTMLImageElement
): ImageComparisonAlgo {
switch (algoName) {
case "difference":
return new DifferenceAlgo(imgA, imgB);
case "overlay":
return new OverlayAlgo(imgA, imgB);
}
}
}

export const algos: Algo[] = [
{
name: "Difference",
func: createDifferenceElement,
},
{
name: "Overlay",
func: createOverlayElement,
},
];
export const algoNames: AlgoName[] = ["difference", "overlay"];
9 changes: 1 addition & 8 deletions src/logic/types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
export type Algo = {
name: string;
func: (
viewElement: HTMLDivElement,
imgA: HTMLImageElement,
imgB: HTMLImageElement
) => void;
};
export type AlgoName = "difference" | "overlay";

export type RequestMessage = {
src: string;
Expand Down
Loading

0 comments on commit 00d8eef

Please sign in to comment.