Detecting text collisions

I am creating a web application that draws a set of letters in different fonts in HTML 5 Canvas

using fillText

. The user will click somewhere on that canvas and I need to check which letter they clicked on (or if they clicked on a letter at all).

I think I need:

  • Get the vector path for each letter (I don't know how to do this).
  • Check if the click point is inside the letter path using a simple collision detection algorithm.

Is there some lightweight feature for this that I am missing? Or maybe a library for such things? If there are no libraries available, how do I get the path for a letter in a specific font to test myself?

I need to use the actual shape of the letter, not just its bounding box, as I don't want the user to be able to click in the middle O

and it registers as a hit.

Any hints in this direction would be appreciated.

+2


source to share


2 answers


Logics

You cannot handle individual letters on the canvas without creating custom logic for it. Everything drawn on the canvas merges with the pixel soup.

And unfortunately you cannot add text as a pure path, so you have to check the pixel values. Otherwise, you can just add text to the new path and use the method isPointInPath

for each letter.

One approach

We may not offer complete solutions here on SO, but here is a framework you can hopefully build from above to provide the basic logic to click individual letters on the canvas:

  • Each letter is stored as an object incl. its position, size, font and char, but also with the hitting area of ​​the rectangle (see below).
  • Define an array with these objects and then pass them to the render function
  • When you register a click to iterate over the array and check the hit rectangle and if inside check the pixel (*)

*) To distinguish between overlapping letters, you need to check the priority. You can also render this char on a separate canvas to get the pixels of just that char. I am not showing this in the demo, but you get the idea.

Demo

var ltrs = []; /// stores the letter objects

/// Create some random objects

for(;i < 20; i++) {

    /// build the object
    var o = {char: alpha[((alpha.length - 1) * Math.random())|0],
             x:    ((w - 20) * Math.random())|0,
             y:    ((h - 20) * Math.random())|0,
             size: (50 * Math.random() + 16)|0,
             font: fonts[((fonts.length - 1) * Math.random())|0]};

             /// store other things such as color etc.

    /// store it in array
    ltrs.push(o);
}

      

Then we have some function to render those symbols (see demo).

When we process the clicks, we iterate through the array of objects and first check along the border to check which letter we are in (selecting just a pixel here will not allow us to identify the letter):



demo.onclick = function(e) {

    /// adjust mouse position to be relative to canvas
    var rect = demo.getBoundingClientRect(),
        x = e.clientX - rect.left,
        y = e.clientY - rect.top,
        i = 0, o;

    /// iterate
    for(;o = ltrs[i]; i++) {

        /// is in rectangle? "Older" letters has higher priority here...
        if (x > o.x && x < (o.x + o.rect[2]) &&
            y > o.y && y < (o.y + o.rect[3])) {

            /// it is, check if we actually clicked a letter
            /// This is what you would adopt to be on a separate canvas...    
            if (checkPixel(x, y) === true) {
                setLetterObject(o, '#f00')
                return;
            }
        }
    }
}

      

Pixel checking is straightforward, it picks one pixel at the x / y position and checks its alpha value (or color if you are using solid backgrounds):

function checkPixel(x, y) {
    var data = ctx.getImageData(x, y, 1, 1).data;
    return (data[3] !== 0);
}

      

CLICK HERE FOR ONLINE DEMO

Updated check pixel function :

This updated check is able to check letters even if they overlap in the same area.

We create a separate canvas for drawing the letter. This isolates the letter, and when we select a pixel, we can only get a pixel from that particular letter. It also doesn't matter what background color is, as we, our splash screens, only set the pixels for the letter, not the background during validation. The overhead is minimal.

function checkPixel(o, x, y) {

    /// create off-screen canvas        
    var oc = document.createElement('canvas'),
        octx = oc.getContext('2d'),
        data,
        oldX = o.x,
        oldY = o.y;

    /// default canvas is 300x150, adjust if letter size is larger *)
    //oc.width = oc.height = 200;

    /// this can be refactored to something better but for demo...
    o.x = 0;
    o.y = 0;

    setLetterObject(octx, o, '#000');

    o.x = oldX;
    o.y = oldY;

    data = octx.getImageData(x - oldX, y - oldY, 1, 1).data;
    return (data[3] !== 0);
}

      

*) When we create the canvas, the default size is 300x150. To avoid reallocating the new bitmap, we just leave it as it is, since memory is already allocated for it and we only need to select one pixel. If the letters are at a larger pixel size than the default, we of course have to reallocate to make the letter fit.

In this demo, we are temporarily overriding the x and y positions. For production, you have to include a method setLetterObject

to override this somehow, as that would be more elegant. But I'll leave that as it is here in the demo, since the most important thing is to understand the principle.

+4


source


I would say the best option is to actually use pixels, which by the way is the most accurate thing you can do (remember, the user sees pixels when they click, nothing more).

Since you can't just use color directly (because there might be many text objects with the same color (and there might be other primitives with the same color as well), you can use a separate canvas for selection instead.

Basically, when you paint your objects on the main canvas in the redraw function, you also paint them in another hidden canvas with the same size, but you paint them using a unique color for each object. This way, you can have up to 16 million entities (24 bits) on the canvas and know immediately which one was clicked, keeping the map between the color code and the entity itself. By the way, such a map is often used in CAD applications to speed up the selection.



The only annoying part is that there is no portable way to turn off anti-aliasing when painting on the canvas, so it is possible that the color you return from the selection canvas is not one of the colors you used, or worse, it is possible that by clicking on the border of an object, another unrelated object is selected.

This should be a very rare event if your display is truly crowded and the choice is random anyway.

0


source







All Articles