-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: remove duplicate logic, add iFrameManipulator and ImageComp…
…arison algo classes
- Loading branch information
1 parent
cabf9e5
commit 00d8eef
Showing
5 changed files
with
330 additions
and
298 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.