USE the GPU
With the canvas 2D API you have limited access to the GPU which you can leverage to your advantage.
If the two colors are separate channels (as you have red and blue) and there are no other colors to mess things up, you can use the GPU to scale the image down to reduce the initial search.
Create a working canvas
const can = document.createElement("canvas");
var w = can.width = ctx.canvas.width;
var h = can.height = ctx.canvas.height;
const ctxW = can.getContext("2d");
Set smoothing on and blend mode copy
ctxW.imageSmoothingEnabled = true;
ctxW.globalCompositeOperation = "copy";
Reduce the original canvas in half steps
// first step move original to working canvas
w /= 2;
h /= 2;
ctxW.drawImage(ctx.canvas, 0, 0, w, h);
// Reduce again drawing onto its self
ctxW.drawImage(ctxW.canvas, 0, 0, w, h, 0, 0, w / 2, h / 2);
w /= 2;
h /= 2;
ctxW.drawImage(ctxW.canvas, 0, 0, w, h, 0, 0, w / 2, h / 2);
const imgData = ctxW.getImageData(0, 0, w / 2, h / 2); // 1/64th as many pixels
At this point the scaled down canvas is 1/8th the size, better yet it has 82 less pixels to crunch.
The scaling is done by the GPU.
You can then search these pixels via the brute force method, creating a list of candidates. Note that if red and blue are closer than 8 pixel they will now occupy the same pixel in some instances.
Then return to the original canvas and refine the proximity of the candidates found in the scaled down pixel data.
Not really an improvement on O(n2) but a massive performance gain as you exploit the power of parallel processing that the GPU provides.
Also note that when you reduce each step you have enough space on the working canvas to keep each reduction meaning you can work back up through the reductions as you refine candidate areas, saving even more time.