0

I'm making a Node.js app with Jimp for image processing. I want to make a dashed border on rectangles based on parameters for the rectangles themselves (x, y, width and height). I want the border to be as close to a web browser dashed border as possible. What I mean by that is that it goes top, right, bottom and finally left. I also need an offset variable to make it go a little bit outward but I think I did that (If you think there's a way to improve the offset, please tell me). The problem is that I use scanQuiet from Jimp and I'm unable to go backward (ie. -width or -height). For the bottom dashed line I need to go backwards the width (-width) and for the left - backwards the height (-height). How do I do that? Can I make scanQuiet go backwards? Is there any better way to do the dashed border?

Note: I have a funtion called blendColors which I'm going to include in the code below.

Code:

function dashedBorder(
    image,
    { lineDash, lineWidth, color },
    { x, y, width, height }
) {
    let drawing = true,
        passed = 0,
        offset = lineWidth - 1;

    color = Jimp.intToRGBA(color);

    // Top border
    image.scanQuiet(x - offset, y - offset, width, 1, (x, y) => {
        if (drawing) {
            const pixelColor = Jimp.intToRGBA(image.getPixelColor(x, y));

            const newColor = blendColors(pixelColor, color);

            for (let i = 0; i < lineWidth; i++) {
                image.setPixelColor(
                    Jimp.rgbaToInt(newColor.r, newColor.g, newColor.b, 255),
                    x,
                    y + i
                );
            }
        }

        passed++;
        if (
            (passed >= lineDash[0] && drawing) ||
            (passed >= lineDash[1] && !drawing)
        ) {
            drawing = !drawing;
            passed = 0;
        }
    });

    drawing = true;

    // Right border
    image.scanQuiet(
        x + width + offset,
        y + lineWidth + offset,
        1,
        height - lineWidth * 2,
        (x, y) => {
            if (drawing) {
                const pixelColor = Jimp.intToRGBA(image.getPixelColor(x, y));

                const newColor = blendColors(pixelColor, color);

                for (let i = 0; i < lineWidth; i++) {
                    image.setPixelColor(
                        Jimp.rgbaToInt(newColor.r, newColor.g, newColor.b, 255),
                        x - i,
                        y
                    );
                }
            }

            passed++;
            if (
                (passed >= lineDash[0] && drawing) ||
                (passed >= lineDash[1] && !drawing)
            ) {
                drawing = !drawing;
                passed = 0;
            }
        }
    );

    drawing = true;

    // Bottom border
    image.scanQuiet(
        x + width + offset,
        y + height + offset,
        -width,
        -1,
        (x, y) => {
            if (drawing) {
                const pixelColor = Jimp.intToRGBA(image.getPixelColor(x, y));

                const newColor = blendColors(pixelColor, color);

                for (let i = 0; i < lineWidth; i++) {
                    image.setPixelColor(
                        Jimp.rgbaToInt(newColor.r, newColor.g, newColor.b, 255),
                        x,
                        y - i
                    );
                }
            }

            passed++;
            if (
                (passed >= lineDash[0] && drawing) ||
                (passed >= lineDash[1] && !drawing)
            ) {
                drawing = !drawing;
                passed = 0;
            }
        }
    );

    drawing = true;

    // Left border
    image.scanQuiet(
        x - offset,
        y + height - lineWidth - offset,
        1,
        -height + lineWidth * 2,
        (x, y) => {
            if (drawing) {
                const pixelColor = Jimp.intToRGBA(image.getPixelColor(x, y));

                const newColor = blendColors(pixelColor, color);

                for (let i = 0; i < lineWidth; i++) {
                    image.setPixelColor(
                        Jimp.rgbaToInt(newColor.r, newColor.g, newColor.b, 255),
                        x + i,
                        y
                    );
                }
            }

            passed++;
            if (
                (passed >= lineDash[0] && drawing) ||
                (passed >= lineDash[1] && !drawing)
            ) {
                drawing = !drawing;
                passed = 0;
            }
        }
    );
}

function blendColors(c1, c2) {
    const stepPoint = c2.a / 255;
    const r = c1.r + stepPoint * (c2.r - c1.r);
    const g = c1.g + stepPoint * (c2.g - c1.g);
    const b = c1.b + stepPoint * (c2.b - c1.b);
    return { r, g, b };
}

(async () => {
    let image = await Jimp.read("./test.png");

    dashedBorder(
        image,
        { lineWidth: 3, lineDash: [20, 5], color: 0x1a53ffbb },
        { x: 0, y: 0, width: image.bitmap.width, height: image.bitmap.height }
    );

    image.write("./test-border.png");
})();

How the image should be:correct selection

How the image currently is:incorrect selection

Note: Ignore the number in the middle of the second image.

Update: New code

const Jimp = require("jimp");

function dashedBorder(
    image,
    { lineDash, lineWidth, color },
    { x, y, width, height }
) {
    let drawing = true,
        passed = 0,
        offset = lineWidth - 1;

    color = Jimp.intToRGBA(color);

    // Top border
    for (let i = x; i < x + width; i++) {
        if (drawing) {
            const pixelColor = Jimp.intToRGBA(image.getPixelColor(x, y - offset));

            const newColor = blendColors(pixelColor, color);

            for (let k = 0; k < lineWidth; k++) {
                image.setPixelColor(
                    Jimp.rgbaToInt(newColor.r, newColor.g, newColor.b, 255),
                    i,
                    y - offset + k
                );
            }
        }

        passed++;
        if (
            (passed >= lineDash[0] && drawing) ||
            (passed >= lineDash[1] && !drawing)
        ) {
            drawing = !drawing;
            passed = 0;
        }
    }

    // Right border
    for (let j = y; j < y + height; j++) {
        if (drawing) {
            const pixelColor = Jimp.intToRGBA(
                image.getPixelColor(x + width + offset, y)
            );

            const newColor = blendColors(pixelColor, color);

            for (let k = 0; k < lineWidth; k++) {
                image.setPixelColor(
                    Jimp.rgbaToInt(newColor.r, newColor.g, newColor.b, 255),
                    x + width + offset - k,
                    j
                );
            }
        }

        passed++;
        if (
            (passed >= lineDash[0] && drawing) ||
            (passed >= lineDash[1] && !drawing)
        ) {
            drawing = !drawing;
            passed = 0;
        }
    }

    // Bottom border
    for (let i = x + width - (lineWidth - offset); i > x; i--) {
        if (drawing) {
            const pixelColor = Jimp.intToRGBA(
                image.getPixelColor(i, y + height + offset)
            );

            const newColor = blendColors(pixelColor, color);

            for (let k = 0; k < lineWidth; k++) {
                image.setPixelColor(
                    Jimp.rgbaToInt(newColor.r, newColor.g, newColor.b, 255),
                    i,
                    y + height - k + offset
                );
            }
        }

        passed++;
        if (
            (passed >= lineDash[0] && drawing) ||
            (passed >= lineDash[1] && !drawing)
        ) {
            drawing = !drawing;
            passed = 0;
        }
    }

    // Left border
    for (let j = y + height - (lineWidth - offset); j > y; j--) {
        if (drawing) {
            const pixelColor = Jimp.intToRGBA(image.getPixelColor(x - offset, j));

            const newColor = blendColors(pixelColor, color);

            for (let k = 0; k < lineWidth; k++) {
                image.setPixelColor(
                    Jimp.rgbaToInt(newColor.r, newColor.g, newColor.b, 255),
                    x + k - offset,
                    j
                );
            }
        }

        passed++;
        if (
            (passed >= lineDash[0] && drawing) ||
            (passed >= lineDash[1] && !drawing)
        ) {
            drawing = !drawing;
            passed = 0;
        }
    }
}

function blendColors(c1, c2) {
    const stepPoint = c2.a / 255;
    const r = c1.r + stepPoint * (c2.r - c1.r);
    const g = c1.g + stepPoint * (c2.g - c1.g);
    const b = c1.b + stepPoint * (c2.b - c1.b);
    return { r, g, b };
}

(async () => {
    let image = await Jimp.read("./test.png");

    dashedBorder(
        image,
        { lineWidth: 3, lineDash: [20, 5], color: 0x1a53ffbb },
        { x: 0, y: 0, width: image.bitmap.width, height: image.bitmap.height }
    );

    image.write("./test-border.png");
})();
ProGamer2711
  • 451
  • 1
  • 5
  • 18
  • question in a series: https://stackoverflow.com/questions/70803655/is-there-any-way-to-have-a-custom-dashed-border-on-a-rect-in-jimp https://stackoverflow.com/questions/70875182/why-does-border-on-image-multiply https://stackoverflow.com/questions/70903214/how-to-make-a-dashed-border-on-a-rectangular-area-in-jimp – Christoph Rackwitz Jan 30 '22 at 18:16
  • yeah. the `scanQuiet` does nothing but count up some indices. it probably implements Bresenham algorithm. you only need straight lines, so that's even simpler. just use a for-loop... – Christoph Rackwitz Jan 30 '22 at 18:46
  • @ChristophRackwitz What do I loop over? – ProGamer2711 Jan 30 '22 at 18:48
  • x coordinate, for the horizontal lines. increasing to go right, decreasing to go left. come on, you can figure out out! play around. – Christoph Rackwitz Jan 30 '22 at 20:09
  • @ChristophRackwitz I'm now using `for` loops. Thank you so much for that. I just have one problem - when I get a space between two dashes that starts on the last pixel of the right line and I transfer it to the bottom line, I somehow miss `1px`. I mean instead of getting `5px` I get `4px`. Any idea why that is? I updated my question with the new code. Please give it a check because I can let it go but I don't want to. Later it might cause a problem and also I'll just know that there is a single space that is `4px` instead of `5px` – ProGamer2711 Jan 30 '22 at 21:25

0 Answers0