0

I'm makking a Node.js app with Jimp. I have a function for selecting part of an image. It draws a rectangle in the area, writes the number of the selection in the middle and then draws a dashed border around the outside of the rectangle selection. The dashed border seems to be multiplying and I have no idea why. Here's the full code of the file:

imageManipulationUtil.js:

const Jimp = require("jimp");

module.exports = async (readPath, writePath, comments, callback) => {
    const originalImage = await Jimp.read(readPath);
    const font = await Jimp.loadFont(Jimp.FONT_SANS_32_BLACK);
    
    // Please ignore this part. Focus on the other part (the functions I mentioned above).
    const addedWidth = 500;

    const commentsHeight = comments.reduce((commentsHeight, { comment }, i) => {
        comments[i].comment = `${i + 1}. ${comment}`;
        const textWidth = Jimp.measureText(font, comment);
        const textHeight = Jimp.measureTextHeight(font, comment);
        const lines = Math.ceil(textWidth / addedWidth);
        const height = textHeight * lines;
        return commentsHeight + height;
    }, 0);
    const imageHeight = commentsHeight + 10;

    if (imageHeight > originalImage.getHeight())
        originalImage.resize(Jimp.AUTO, imageHeight);

    const newImage = new Jimp(
        originalImage.getWidth() + addedWidth,
        originalImage.getHeight(),
        0xffffffff
    );

    // Some other code for another purpose

    // !!! Important code START !!!
    drawSelectionRects(comments, font, newImage);

    async function drawSelectionRects(comments, font, image) {
        comments.forEach(({ dimensions }) => {
            image.scanQuiet(
                dimensions.x,
                dimensions.y,
                dimensions.width,
                dimensions.height,
                (x, y, idx) => {
                    const color = {
                        r: image.bitmap.data[idx + 0],
                        g: image.bitmap.data[idx + 1],
                        b: image.bitmap.data[idx + 2],
                        a: image.bitmap.data[idx + 3] / 255,
                    };

                    const selectionColor = {
                        r: 187,
                        g: 187,
                        b: 187,
                        a: 187,
                    };

                    const newColor = blendColors(color, selectionColor);

                    const hexColor = Jimp.rgbaToInt(
                        newColor.r,
                        newColor.g,
                        newColor.b,
                        255
                    );

                    image.setPixelColor(hexColor, x, y);
                }
            );

            dashedBorder(
                image,
                { lineDash: [20, 5], lineWidth: 3, color: 0x1a53ffbb },
                dimensions
            );
        });
        comments.forEach(({ dimensions }, i) => {
            const text = `${i + 1}`;

            let textX =
                dimensions.x + (dimensions.width - Jimp.measureText(font, text)) / 2;
            let textY =
                dimensions.y +
                (dimensions.height - Jimp.measureTextHeight(font, text)) / 2;

            image.print(font, textX, textY, text);
        });
    }

    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 };
    }

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

        color = Jimp.intToRGBA(color);

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

                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 - k
                    );
                }
            }

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

        drawing = true;

        // Right border
        for (let j = y; j < y + height; j++) {
            if (drawing) {
                const pixelColor = Jimp.intToRGBA(image.getPixelColor(x + width, 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 + k,
                        j
                    );
                }
            }

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

        drawing = true;

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

                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
                    );
                }
            }

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

        drawing = true;

        // Left border
        for (let j = y + height; j > y + lineWidth; j--) {
            if (drawing) {
                const pixelColor = Jimp.intToRGBA(image.getPixelColor(x, 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,
                        j
                    );
                }
            }

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

    newImage.write(writePath);

    callback();
};
// !!! Important code END !!!

The code exports a function that takes some parameters. The most important parameter is the comments parameter (specifically the dimensions of the comment). The comments param is an array of objects. The objects have a dimensions key which is the important one in this question.

The image uploaded: upload

Image recieved: recieved

The problem that makes me think the image is multiplying is that in some places the border is more transparent than in other parts. Originally I thought that was just because of the image I used but then I switched it and noticed it was a real problem.

New full code:

const Jimp = require("jimp");

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

    color = Jimp.intToRGBA(color);

    // Top border
    for (let i = x - outsideWidth; i < x + width + outsideWidth; i++) {
        if (drawing) {
            for (let k = 0; k < lineWidth; k++) {
                image.setPixelColor(
                    Jimp.rgbaToInt(color.r, color.g, color.b, 255),
                    i,
                    y - k
                );
            }
        }

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

    // Right border
    for (
        let j = y + lineWidth - outsideWidth;
        j < y + height - (lineWidth - outsideWidth);
        j++
    ) {
        if (drawing) {
            for (let k = 0; k < lineWidth; k++) {
                image.setPixelColor(
                    Jimp.rgbaToInt(color.r, color.g, color.b, 255),
                    x + width + k - 1,
                    j
                );
            }
        }

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

    // Bottom border
    for (let i = x + width + lineWidth - outsideWidth; i > x - lineWidth; i--) {
        if (drawing) {
            for (let k = 0; k < lineWidth; k++) {
                image.setPixelColor(
                    Jimp.rgbaToInt(color.r, color.g, color.b, 255),
                    i,
                    y + height + k - 1
                );
            }
        }

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

    // Left border
    for (let j = y + height - outsideWidth; j > y; j--) {
        if (drawing) {
            for (let k = 0; k < lineWidth; k++) {
                image.setPixelColor(
                    Jimp.rgbaToInt(color.r, color.g, color.b, 255),
                    x - k,
                    j
                );
            }
        }

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

(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");
})();

Image: image3

ProGamer2711
  • 451
  • 1
  • 5
  • 18
  • you use `blendColors`. your dashed lines *do* overlap in the corners. are you aware of both? – Christoph Rackwitz Jan 31 '22 at 11:49
  • Do they overlap still? I'm pretty sure I changed it so they don't overlap. Any idea how I can fix it if they're still overlapping? – ProGamer2711 Jan 31 '22 at 12:21
  • those loops definitely cause the overlapping. just move the start and stop of some of these loops. – Christoph Rackwitz Jan 31 '22 at 13:58
  • @ChristophRackwitz I think I did it. I stopped using blendColors for the border and instead just used the blue of the border as the color of the pixel. I moved the starts and ends of the loops. Could you please check the code and see if it looks right? I'm updating the post above. – ProGamer2711 Feb 01 '22 at 07:33

0 Answers0