Skip to content

Instantly share code, notes, and snippets.

@Trafalgar63me
Last active August 28, 2024 15:04
Show Gist options
  • Save Trafalgar63me/89b86900399b32974e579cd845668d35 to your computer and use it in GitHub Desktop.
Save Trafalgar63me/89b86900399b32974e579cd845668d35 to your computer and use it in GitHub Desktop.
compare Images And Highlight
// ==UserScript==
// @name Azure DevOps - PR Image Comparison
// @version 2024-08-20
// @description Display pixel differences when clicking on a modified image in Azure Devops pull-requests view. Uses pixelmatch library.
// @author Mwangi Gakiba
// @match https://dev.azure.com/*
// @icon https://cdn.vsassets.io/content/icons/favicon.ico
// @grant none
// ==/UserScript==
const DEFAULT_MATCH_THRESHOLD = 0.1;
const DEFAULT_ADDED_PIXELS_COLOR = [0, 255, 0];
const DEFAULT_REMOVED_PIXELS_COLOR = [255, 0, 0];
const AZURE_IMAGE_SECTION_SELECTOR = '.bolt-card-content';
const AZURE_IMAGE_SELECTOR = '.bolt-card-content img';
(function () {
'use strict';
const observableElement = document.body;
const observer = new MutationObserver(() => handleDiffableImages(observableElement));
observer.observe(observableElement, { childList: true, subtree: true });
})();
function handleDiffableImages(element) {
const images = element.querySelectorAll(AZURE_IMAGE_SELECTOR);
images.forEach((img) => {
if (!img.onload) {
img.onload = () => attachOnClickHandlerToImages();
}
});
}
function attachOnClickHandlerToImages() {
const processedAttributeName = 'data-pixelmatch-processed';
const sections = document.querySelectorAll(AZURE_IMAGE_SECTION_SELECTOR);
sections.forEach((section) => {
const pngImages = section.querySelectorAll(
`.repos-editor-image:not([${processedAttributeName}])`
);
if (pngImages.length % 2 !== 0) {
return;
}
waitForImagesToLoad(pngImages, () => {
pngImages.forEach((img, index) => {
let overlay = null;
img.style.cursor = 'pointer';
img.setAttribute(processedAttributeName, 'true');
const handleImageClick = function () {
if (!overlay) {
const nextImg = pngImages[index + (index % 2 === 0 ? 1 : -1)];
overlay = compareImagesAndHighlight(img, nextImg);
overlay.style.position = 'absolute';
overlay.style.left = img.offsetLeft + 'px';
overlay.style.top = img.offsetTop + 'px';
overlay.style.pointerEvents = 'none';
img.parentElement.prepend(overlay);
} else {
overlay.remove();
overlay = null;
}
};
img.onclick = handleImageClick;
});
});
});
}
function compareImagesAndHighlight(img1, img2) {
const { ctx: ctx1 } = createCanvasAndContext(img1.width, img1.height);
const { ctx: ctx2 } = createCanvasAndContext(img2.width, img2.height);
const imgData1 = drawImageToCanvas(ctx1, img1);
const imgData2 = drawImageToCanvas(ctx2, img2);
const { ctx: diffCtx, canvas: diffCanvas } = createCanvasAndContext(img1.width, img1.height);
const diffImageData = diffC// ==UserScript==
// @name Azure DevOps - PR Image Comparison
// @version 2024-08-20
// @description Display pixel differences when clicking on a modified image in Azure Devops pull-requests view. Uses pixelmatch library.
// @author Marius Eismantas
// @match https://dev.azure.com/*
// @icon https://cdn.vsassets.io/content/icons/favicon.ico
// @grant none
// ==/UserScript==
const DEFAULT_MATCH_THRESHOLD = 0.1;
const DEFAULT_ADDED_PIXELS_COLOR = [0, 255, 0];
const DEFAULT_REMOVED_PIXELS_COLOR = [255, 0, 0];
const AZURE_IMAGE_SECTION_SELECTOR = '.bolt-card-content';
const AZURE_IMAGE_SELECTOR = '.bolt-card-content img';
(function () {
'use strict';
const observableElement = document.body;
const observer = new MutationObserver(() => handleDiffableImages(observableElement));
observer.observe(observableElement, { childList: true, subtree: true });
})();
function handleDiffableImages(element) {
const images = element.querySelectorAll(AZURE_IMAGE_SELECTOR);
images.forEach((img) => {
if (!img.onload) {
img.onload = () => attachOnClickHandlerToImages();
}
});
}
function attachOnClickHandlerToImages() {
const processedAttributeName = 'data-pixelmatch-processed';
const sections = document.querySelectorAll(AZURE_IMAGE_SECTION_SELECTOR);
sections.forEach((section) => {
const pngImages = section.querySelectorAll(
`.repos-editor-image:not([${processedAttributeName}])`
);
if (pngImages.length % 2 !== 0) {
return;
}
waitForImagesToLoad(pngImages, () => {
pngImages.forEach((img, index) => {
let overlay = null;
img.style.cursor = 'pointer';
img.setAttribute(processedAttributeName, 'true');
const handleImageClick = function () {
if (!overlay) {
const nextImg = pngImages[index + (index % 2 === 0 ? 1 : -1)];
overlay = compareImagesAndHighlight(img, nextImg);
overlay.style.position = 'absolute';
overlay.style.left = img.offsetLeft + 'px';
overlay.style.top = img.offsetTop + 'px';
overlay.style.pointerEvents = 'none';
img.parentElement.prepend(overlay);
} else {
overlay.remove();
overlay = null;
}
};
img.onclick = handleImageClick;
});
});
});
}
function compareImagesAndHighlight(img1, img2) {
const { ctx: ctx1 } = createCanvasAndContext(img1.width, img1.height);
const { ctx: ctx2 } = createCanvasAndContext(img2.width, img2.height);
const imgData1 = drawImageToCanvas(ctx1, img1);
const imgData2 = drawImageToCanvas(ctx2, img2);
const { ctx: diffCtx, canvas: diffCanvas } = createCanvasAndContext(img1.width, img1.height);
const diffImageData = diffC
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment