Browse Source

use the sharty's slider algorithm

master 0.58
coomdev 9 months ago
parent
commit
fec1b4a896
  1. 178
      src/main.ts

178
src/main.ts

@ -124,8 +124,143 @@ function calculateDisorder(imgdata: ImageData) {
return res / (total === 0 ? 1 : total);
}
/*
* decide if a pixel is closer to black than to white.
* return 0 for white, 1 for black
*/
function pxlBlackOrWhite(r: number, g: number, b: number) {
return (r + g + b > 384) ? 0 : 1;
}
/*
* Get bordering pixels of transparent areas (the outline of the circles)
* and return their coordinates with the neighboring color.
*/
function getBoundries(imgdata: ImageData) {
const data = imgdata.data;
const width = imgdata.width;
let i = data.length - 1;
let cl = 0;
let cr = 0;
const chkArray = [];
let opq = true;
while (i > 0) {
// alpha channel above 128 is assumed opaque
const a = data[i] > 128;
if (a !== opq) {
if ((data[i - 4] > 128) === opq) {
// ignore just 1-width areas
i -= 4;
continue;
}
if (a) {
/* transparent pixel to its right */
/*
// set to color blue (for debugging)
data[i + 4] = 255;
data[i + 3] = 255;
data[i + 2] = 0;
data[i + 1] = 0;
*/
const pos = (i + 1) / 4;
const x = pos % width;
const y = (pos - x) / width;
// 1: black, 0: white
const clr = pxlBlackOrWhite(data[i - 1], data[i - 2], data[i - 3]);
chkArray.push([x, y, clr]);
cr += 1;
} else {
/* opaque pixel to its right */
/*
// set to color red (for debugging)
data[i] = 255;
data[i - 1] = 0;
data[i - 2] = 0;
data[i - 3] = 255;
*/
const pos = (i - 3) / 4;
const x = pos % width;
const y = (pos - x) / width;
// 1: black, 0: white
const clr = pxlBlackOrWhite(data[i + 1], data[i + 2], data[i + 3]);
chkArray.push([x, y, clr]);
cl += 1;
}
opq = a;
}
i -= 4;
}
return chkArray;
}
/*
* slide the background image and compare the colors of the border pixels in
* chkArray, the position with the most matches wins
* Return in slider-percentage.
*/
function getBestPos(bgdata: ImageData, chkArray: number[][], slideWidth: number) {
const data = bgdata.data;
const width = bgdata.width;
let bestSimilarity = 0;
let bestPos = 0;
for (let s = 0; s <= slideWidth; s += 1) {
let similarity = 0;
const amount = chkArray.length;
for (let p = 0; p < amount; p += 1) {
const chk = chkArray[p];
const x = chk[0] + s;
const y = chk[1];
const clr = chk[2];
const off = (y * width + x) * 4;
const bgclr = pxlBlackOrWhite(data[off], data[off + 1], data[off + 2]);
if (bgclr === clr) {
similarity += 1;
}
}
if (similarity > bestSimilarity) {
bestSimilarity = similarity;
bestPos = s;
}
}
return bestPos / slideWidth * 100;
}
async function getImageDataFromURI(uri: string) {
const image = await imageFromUri(uri);
if (!image)
throw new Error("No image");
const canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
const ctx = canvas.getContext('2d')!;
ctx.drawImage(image, 0, 0);
return ctx.getImageData(0, 0, canvas.width, canvas.height);
}
async function slideCaptcha(tfgElement: HTMLElement, tbgElement: HTMLElement, sliderElement: HTMLInputElement) {
// get data uris for captcha back- and foreground
const tbgUri = tbgElement.style.backgroundImage.slice(5, -2);
const tfgUri = tfgElement.style.backgroundImage.slice(5, -2);
// load foreground (image with holes)
const igd = await getImageDataFromURI(tfgUri);
// get array with pixels of foreground
// that we compare to background
const chkArray = getBoundries(igd);
// load background (image that gets slid)
const sigd = await getImageDataFromURI(tbgUri);
const slideWidth = sigd.width - igd.width;
// slide, compare and get best matching position
const sliderPos = getBestPos(sigd, chkArray, slideWidth);
// slide in the UI
sliderElement.value = '' + sliderPos;
(sliderElement as any).dispatchEvent(new Event('input'), { bubbles: true });
return 0 - (sliderPos / 2);
}
// returns ImageData from captcha's background image, foreground image, and offset (ranging from 0 to -50)
function imageFromCanvas(img: HTMLImageElement, bg: HTMLImageElement, off: number) {
async function imageFromCanvas(img: HTMLImageElement, bg: HTMLImageElement, off: number | null) {
const h = img.height;
const w = img.width;
const th = 80;
@ -163,44 +298,11 @@ function imageFromCanvas(img: HTMLImageElement, bg: HTMLImageElement, off: numbe
ctx.drawImage(img, -w / 2, -h / 2, w, h);
};
// if off is not specified and background image is present, try to figure out
// the best offset automatically; select the offset that has smallest value of
// calculateDisorder for the resulting image
if (bg && off == null) {
let bestDisorder = 999;
let bestImagedata: ImageData | null = null;
let bestOff = -1;
for (let off = 0; off >= -50; off--) {
draw(off);
let imgdata = ctx.getImageData(0, 0, canvas.width, canvas.height);
const disorder = calculateDisorder(imgdata);
if (disorder < bestDisorder) {
bestDisorder = disorder;
draw(off);
imgdata = ctx.getImageData(0, 0, canvas.width, canvas.height);
bestImagedata = imgdata;
bestOff = off;
}
}
// not the best idea to do this here
setTimeout(function () {
const bg = document.getElementById('t-bg');
const slider = document.getElementById('t-slider') as HTMLInputElement;
if (!bg || !slider) return;
slider.value = '' + (-bestOff * 2);
bg.style.backgroundPositionX = bestOff + 'px';
}, 1);
draw(bestOff);
return bestImagedata;
} else {
draw(off);
return ctx.getImageData(0, 0, canvas.width, canvas.height);
off = await slideCaptcha(document.getElementById('t-fg')!, document.getElementById('t-bg')!, document.getElementById('t-slider') as HTMLInputElement);
}
draw(off || 0);
return ctx.getImageData(0, 0, canvas.width, canvas.height);
}
// for debugging purposes
@ -306,7 +408,7 @@ async function predict(img: HTMLImageElement, bg: HTMLImageElement, off: number)
if (!model) {
model = await load();
}
const image = imageFromCanvas(img, bg, off);
const image = await imageFromCanvas(img, bg, off);
if (!image)
throw new Error("Failed to gen image");
const mono = toMonochrome(image.data);

Loading…
Cancel
Save