How can I load image file into the RGB array?

I am trying to play with 16x16 WS2812B LED Matix controlled by ESP8266 with WLED firmware. It allows controlling it with UDP packets which are simply the sequence of R,G,B values. For example first three pixels where first is red, second is green and third is blue are encoded like this: [0xff,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0xff]). The first challenge I faced is how to load image (16x16 png/jpg/bmp etc...) into the Node-Red as binary buffer array. I tried node-red-contrib-image-tools's image node, but it looks like it just reads image byte-by-byte without decoding it into RGB buffer. Are there any nodes for this? Also ideally, it should also be able to reverse each even line of the array (without reversing RGB values) - the matrix has snake topology.

I will appreciate any help/ideas/suggestions.

Hi, I wrote node-red-contrib-image-tools so I know a thing or two :wink:

It most definitely does decode the image & you can get access to the underlying RGB components.

[{"id":"a6f53bfd.0ec578","type":"jimp-image","z":"23406c97.6c35b4","name":"","data":"payload","dataType":"msg","ret":"img","parameter1":"img.bmp","parameter1Type":"str","parameter2":"","parameter2Type":"num","parameter3":"","parameter3Type":"num","parameter4":"","parameter4Type":"none","parameter5":"","parameter5Type":"none","parameter6":"","parameter6Type":"none","parameter7":"","parameter7Type":"none","parameter8":"","parameter8Type":"msg","parameterCount":0,"jimpFunction":"none","selectedJimpFunction":{"name":"none","fn":"none","description":"Just loads the image.","parameters":[]},"x":270,"y":900,"wires":[["7add4a7d.cbfb44","963690a7.e111"]]},{"id":"8c1ef7f6.812678","type":"inject","z":"23406c97.6c35b4","name":"16x16 pokeball","topic":"pokeball","payload":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACCSURBVDhPrZAJDoAwCAR5Ok/rz6pFFxekxmuTMS3HNFF6EREpqRKqPjihEvntajHDEjuNgtLAHSCx7yfBm2Uwdv8TVBLUZw+4gCUMelWfeqtt+xmPoJ2jkMODOdizjl8eYrsQqKrRWnPyMCcIRliSRRkWBjVeYxGDPife9mAwc07vCzBBNxZvGWrwAAAAAElFTkSuQmCC","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":120,"y":900,"wires":[["a6f53bfd.0ec578"]]},{"id":"36c268c9.66fb18","type":"debug","z":"23406c97.6c35b4","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":790,"y":960,"wires":[]},{"id":"7add4a7d.cbfb44","type":"function","z":"23406c97.6c35b4","name":"getRBG from Pixels (sync)","func":"let rgb = []\nlet image = msg.payload;\nfor(let x = 0; x < image.bitmap.width; x++){\n    for(let y = 0; y < image.bitmap.height; y++){    \n        let color = image.getPixelColor(x, y) ;\n        var r = (color >> 16) & 255; \n        var g = (color >> 8) & 255; \n        var b = color & 255; \n        rgb.push(r);\n        rgb.push(g);\n        rgb.push(b);\n    }\n}\n\nmsg.rgb = rgb;\nreturn msg;","outputs":1,"noerr":0,"x":480,"y":960,"wires":[["4e3ff0a0.8ccbc"]]},{"id":"4e3ff0a0.8ccbc","type":"image viewer","z":"23406c97.6c35b4","name":"","width":"64","data":"payload","dataType":"msg","x":670,"y":960,"wires":[["36c268c9.66fb18"]]},{"id":"963690a7.e111","type":"function","z":"23406c97.6c35b4","name":"getRBG from Pixels (async)","func":"\nvar image = msg.payload;\nvar rgb = [];\nimage.scan(0, 0, image.bitmap.width, image.bitmap.height, function(x, y, idx) {\n    // x, y is the position of this pixel on the image\n    // idx is the position start position of this rgba tuple in the bitmap Buffer\n    \n    var r = this.bitmap.data[idx + 0];\n    var g = this.bitmap.data[idx + 1];\n    var b = this.bitmap.data[idx + 2];\n    var a = this.bitmap.data[idx + 3];\n    rgb.push(r);\n    rgb.push(g);\n    rgb.push(b); \n    if (x >= image.bitmap.width - 1 && y >= image.bitmap.height - 1) {\n        // image scan finished, do your stuff\n        node.send({topic:\"rgb\",payload: rgb})\n    }\n});\n","outputs":1,"noerr":0,"x":480,"y":840,"wires":[["28ac1e3a.feadc2"]]},{"id":"28ac1e3a.feadc2","type":"debug","z":"23406c97.6c35b4","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":790,"y":840,"wires":[]},{"id":"f25e6bf2.8d2a78","type":"comment","z":"23406c97.6c35b4","name":"To load a file chage the inject to send filename","info":"","x":210,"y":780,"wires":[]},{"id":"c40b7bc3.139098","type":"comment","z":"23406c97.6c35b4","name":"RGB Data will be in msg.payload","info":"","x":870,"y":880,"wires":[]},{"id":"744fa2e9.617fbc","type":"comment","z":"23406c97.6c35b4","name":"RGB Data will be in msg.rgb","info":"","x":860,"y":1000,"wires":[]}]

EDIT...
Added this to flow library - https://flows.nodered.org/flow/1a867a5280c2cd5a2304ca8db567ec64

1 Like

@Steve-Mcl
I've been playing around with this (as I've been after something for a while to get at bitmaps) and I think there are a couple of issues with your sync function and I've produced this version

let rgb = []
let image = msg.payload;
for(let y = 0; y < image.bitmap.height; y++){    
    for(let x = 0; x < image.bitmap.width; x++){
        let color = image.getPixelColor(x, y) ;
        var r = (color >> 24) & 255; 
        var g = (color >> 16) & 255; 
        var b = (color >> 8) & 255; 
        rgb.push(r);
        rgb.push(g);
        rgb.push(b);
    }
}

msg.rgb = rgb;
return msg;

I swapped the for x/y around so that it does rows then columns
Also, I think the image.getPixelColor(x, y) is returning a 4 byte value with alpha as well as RGB so I've altered that bit as well

I've not looked at the async code as async JS just confuses me :slight_smile:

3 Likes

Yes it does but I left that out for the OP.

Good spot on swapping the X & Y around :+1:

I meant you returned the wrong bytes - I haven't added in the alpha value :slight_smile:

Haha another good spot. Cheers.

Off my game today.

I'm just pleased to have found out that the node returns the bitmap - like the OP, I'd looked into it in the past and didn't notice that it did.

Great work :slight_smile:

1 Like

Hi Steve, thank you very much for your very helpful reply! I will update here about the results in a few days. And thank you so much for the node - awesome job, man!

It works! I slightly modified code to reverse each even line (ugliest way of course, I am not coder at all):

let rgb = []
let image = msg.payload;
for(let y = 0; y < image.bitmap.height; y+=2){
    for(let x = image.bitmap.width; x > 0; x--){
        color = image.getPixelColor(x-1, y) ;
        r = (color >> 24) & 255; 
        g = (color >> 16) & 255; 
        b = (color >> 8) & 255; 
        rgb.push(r);
        rgb.push(g);
        rgb.push(b);
    }
    for(let x = 0; x < image.bitmap.width; x++){
        color = image.getPixelColor(x, y+1) ;
        r = (color >> 24) & 255; 
        g = (color >> 16) & 255; 
        b = (color >> 8) & 255; 
        rgb.push(r);
        rgb.push(g);
        rgb.push(b);
    }
}
msg.rgb = rgb;
msg.rgbBuffer = Buffer.from(rgb);
return msg;

Thank you both Steve-Mcl and cymplecy. Now I can directly control LED matrix from Node-Red by sending .rgbBuffer directly to the matrix's UDP port. .

2 Likes