Awesome
🏞 node-red-contrib-image-output
Simple image output node. Useful for previewing images (of face detecting, object recognition etc...) inside the Node-RED flow editor.
The expected input should be a jpg or png image, which need to be delivered in one of the following formats:
- A buffer
- A base64 encoded string
- A Jimp object
- A url to an image
Installation
Either use the Editor - Menu - Manage Palette - Install option, or run the following npm command in your Node-RED user directory (typically ~/.node-red
):
npm i node-red-contrib-image-output
Node usage
This node can be used to preview images inside the Node-RED flow editor. The following examples explain how the different kind of input image formats can be visualized. All these example flows are also available via the 'Import' menu in the Node-RED flow editor.
Buffer input image
Apply an image as a binary Buffer (i.e. bits and bytes ...):
[{"id":"46ffc819.b736f8","type":"http request","z":"30fb1577.8f556a","name":"","method":"GET","ret":"bin","paytoqs":false,"url":"https://dummyimage.com/200x150/000/fff&text={{{payload}}}","tls":"","persist":false,"proxy":"","authType":"","x":610,"y":640,"wires":[["d603e881.2fa1c8"]]},{"id":"25d928c1.708098","type":"inject","z":"30fb1577.8f556a","name":"Generate next image","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":200,"y":640,"wires":[["878f8ec1.effe4"]]},{"id":"878f8ec1.effe4","type":"function","z":"30fb1577.8f556a","name":"image counter","func":"var count = flow.get(\"count\")||0;\n\ncount++;\n\nnode.status({fill:\"blue\",shape:\"ring\",text:\"Image \" + count});\n\n// Save the new value back to context so it will be available next time\nflow.set('count',count);\n\n// Update the message payload and return - no need to create a new msg\nmsg.payload = \"Image \" + count;\nreturn msg;","outputs":1,"noerr":0,"x":420,"y":640,"wires":[["46ffc819.b736f8"]]},{"id":"d603e881.2fa1c8","type":"image","z":"30fb1577.8f556a","name":"","width":160,"data":"payload","dataType":"msg","thumbnail":true,"active":true,"x":800,"y":640,"wires":[]}]
Base64 input image
Apply an image as a base64 encoded string:
[{"id":"46ffc819.b736f8","type":"http request","z":"30fb1577.8f556a","name":"","method":"GET","ret":"bin","paytoqs":false,"url":"https://dummyimage.com/200x150/000/fff&text={{{payload}}}","tls":"","persist":false,"proxy":"","authType":"","x":610,"y":640,"wires":[["27a53fad.5c768"]]},{"id":"25d928c1.708098","type":"inject","z":"30fb1577.8f556a","name":"Generate next image","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":200,"y":640,"wires":[["878f8ec1.effe4"]]},{"id":"878f8ec1.effe4","type":"function","z":"30fb1577.8f556a","name":"image counter","func":"var count = flow.get(\"count\")||0;\n\ncount++;\n\nnode.status({fill:\"blue\",shape:\"ring\",text:\"Image \" + count});\n\n// Save the new value back to context so it will be available next time\nflow.set('count',count);\n\n// Update the message payload and return - no need to create a new msg\nmsg.payload = \"Image \" + count;\nreturn msg;","outputs":1,"noerr":0,"x":420,"y":640,"wires":[["46ffc819.b736f8"]]},{"id":"27a53fad.5c768","type":"base64","z":"30fb1577.8f556a","name":"","action":"str","property":"payload","x":780,"y":640,"wires":[["d603e881.2fa1c8"]]},{"id":"d603e881.2fa1c8","type":"image","z":"30fb1577.8f556a","name":"","width":160,"data":"payload","dataType":"msg","thumbnail":true,"active":true,"x":960,"y":640,"wires":[]}]
Jimp input image
Apply an image as a Jimp object. This node allows Jimp images to be previewed, which are send by other Jimp related nodes. The following example shows a Jimp image, generated by the node-red-contrib-image-tools nodes:
Note: the node-red-contrib-image-tools
node should be installed, prior to importing this example.
[{"id":"27719e17.2169f2","type":"jimp-image","z":"30fb1577.8f556a","name":"","data":"payload","dataType":"msg","ret":"img","parameter1":"","parameter1Type":"msg","parameter2":"","parameter2Type":"msg","parameter3":"","parameter3Type":"msg","parameter4":"","parameter4Type":"msg","parameter5":"","parameter5Type":"msg","parameter6":"","parameter6Type":"msg","parameter7":"","parameter7Type":"msg","parameter8":"","parameter8Type":"msg","parameterCount":0,"jimpFunction":"none","selectedJimpFunction":{"name":"none","fn":"none","description":"Just loads the image.","parameters":[]},"x":770,"y":640,"wires":[["d603e881.2fa1c8"]]},{"id":"46ffc819.b736f8","type":"http request","z":"30fb1577.8f556a","name":"","method":"GET","ret":"bin","paytoqs":false,"url":"https://dummyimage.com/200x150/000/fff&text={{{payload}}}","tls":"","persist":false,"proxy":"","authType":"","x":610,"y":640,"wires":[["27719e17.2169f2"]]},{"id":"25d928c1.708098","type":"inject","z":"30fb1577.8f556a","name":"Generate next image","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":200,"y":640,"wires":[["878f8ec1.effe4"]]},{"id":"878f8ec1.effe4","type":"function","z":"30fb1577.8f556a","name":"image counter","func":"var count = flow.get(\"count\")||0;\n\ncount++;\n\nnode.status({fill:\"blue\",shape:\"ring\",text:\"Image \" + count});\n\n// Save the new value back to context so it will be available next time\nflow.set('count',count);\n\n// Update the message payload and return - no need to create a new msg\nmsg.payload = \"Image \" + count;\nreturn msg;","outputs":1,"noerr":0,"x":420,"y":640,"wires":[["46ffc819.b736f8"]]},{"id":"d603e881.2fa1c8","type":"image","z":"30fb1577.8f556a","name":"","width":160,"data":"payload","dataType":"msg","thumbnail":true,"active":true,"x":960,"y":640,"wires":[]}]
Hide image
When the node is currently displaying an image, that image can be hidden via those values:
- A
null
image (e.g.msg.payload = null
). - No image (e.g.
delete msg.payload
). - An empty string image (e.g.
msg.payload = ""
).
The following demo hides the previewed image based on the msg.payload
:
[{"id":"acae4788.db10e8","type":"http request","z":"e2675d9d.6854e","name":"","method":"GET","ret":"bin","paytoqs":false,"url":"https://dummyimage.com/200x150/000/fff&text={{{payload}}}.jpg","tls":"","persist":false,"proxy":"","authType":"","x":570,"y":360,"wires":[["b548876a.e2f8b8"]]},{"id":"8ff5f74b.d2efb8","type":"inject","z":"e2675d9d.6854e","name":"Generate next image","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":200,"y":360,"wires":[["ea222193.08df9"]]},{"id":"ea222193.08df9","type":"function","z":"e2675d9d.6854e","name":"image counter","func":"var count = flow.get(\"count\")||0;\n\ncount++;\n\nnode.status({fill:\"blue\",shape:\"ring\",text:\"Image \" + count});\n\n// Save the new value back to context so it will be available next time\nflow.set('count',count);\n\n// Update the message payload and return - no need to create a new msg\nmsg.payload = \"Image \" + count;\nreturn msg;","outputs":1,"noerr":0,"x":400,"y":360,"wires":[["acae4788.db10e8"]]},{"id":"670b37d4.e67658","type":"inject","z":"e2675d9d.6854e","name":"Msg without payload","topic":"","payload":"","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":470,"y":260,"wires":[["6d9c5aa.2d5e6a4"]]},{"id":"b548876a.e2f8b8","type":"base64","z":"e2675d9d.6854e","name":"","action":"","property":"payload","x":720,"y":360,"wires":[["f8d56b3b.3bb1f8"]]},{"id":"6d9c5aa.2d5e6a4","type":"change","z":"e2675d9d.6854e","name":"","rules":[{"t":"delete","p":"payload","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":690,"y":260,"wires":[["f8d56b3b.3bb1f8"]]},{"id":"636d9da2.959fb4","type":"inject","z":"e2675d9d.6854e","name":"Msg with empty string","topic":"","payload":"","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":460,"y":300,"wires":[["6dce2d91.6158f4"]]},{"id":"c6c47147.1972a","type":"inject","z":"e2675d9d.6854e","name":"Msg with null value","topic":"","payload":"","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":470,"y":220,"wires":[["d21096f2.632ed8"]]},{"id":"6dce2d91.6158f4","type":"change","z":"e2675d9d.6854e","name":"msg.payload = \"\"","rules":[{"t":"set","p":"payload","pt":"msg","to":"","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":690,"y":300,"wires":[["f8d56b3b.3bb1f8"]]},{"id":"d21096f2.632ed8","type":"function","z":"e2675d9d.6854e","name":"msg.payload = null","func":"msg.payload = null;\nreturn msg;","outputs":1,"noerr":0,"x":690,"y":220,"wires":[["f8d56b3b.3bb1f8"]]},{"id":"b99d140c.ce4448","type":"comment","z":"e2675d9d.6854e","name":"Multiple ways to hide the preview image:","info":"","x":520,"y":180,"wires":[]},{"id":"f8d56b3b.3bb1f8","type":"image","z":"e2675d9d.6854e","name":"","width":160,"data":"payload","dataType":"msg","thumbnail":false,"active":true,"x":920,"y":360,"wires":[]}]
Control image stream
Via the button on the right side, the image stream can be stopped or (re)started again:
Remark: the server will stop sending images to the client, so there will be no more data traffic involved!
Node configuration
Width
The width (in pixels) that the image needs to be displayed in the flow. The height will be calculated automatically, with the same aspect ratio as the original image.
Property
Specify how the input image will be delivered to this node. By default the image needs to be delivered in the msg.payload
of the input message.
Resize images on server side
By transferring smaller images the bandwith can be reduced, i.e. the number of bytes that is being send across the network. When too much data is pushed (across the websocket), the flow editor can become unresponsive.
- When this option is activated, the images will be resized (to the specified width) on the server side. Then those small thumbnail images will be send to the browser, to reduce the bandwith.
- When this option is not activated, the (original) large images will be send to the browser. Once they arrive there, the browser will resize them to the specified width. As a result much more data needs to be transferred between the server and the browser.
Caution: resizing images on the server will require server-side CPU usage. So it has to be decided what is prefered: lower bandwidth or lower cpu usage on the server. This decision will depend on the use case and hardware...
Allow image passthrough
When selected this adds an output wire to the node in order to pass the original message through to a following node. This performs better than forking the wires, however it does remove the enable/disable button.