shmuelzon / home-assistant-floor-plan

Home Assistant Floor Plan Generator Plugin For Sweet Home 3D
MIT License
264 stars 8 forks source link

RGB(W) Lights #22

Closed Zerogiven closed 3 weeks ago

Zerogiven commented 4 months ago

Hi,

One last nice thing which would be very nice is to get RGB(W) renderings for defined lights. I saw this at this tutorial (in german...) https://smarthomebastler.at/floorplan-dashboard-neu-in-2022/

no idea if this could be a way here too :)

      #-----------------------------------------------         
      #----------------------------------------------- 
      #  RGBW
      #-----------------------------------------------         
      #----------------------------------------------- 

  - type: custom:config-template-card
    variables:
      LEUCHTENSTATUS: states['light.<RGBW-ENTITY>'].state
      FARBMODUS: states['light.<RGBW-ENTITY>'].attributes.color_mode
      LICHTFARBE: states['light.<RGBW-ENTITY>'].attributes.hs_color
      HELLIGKEIT: states['light.<RGBW-ENTITY>'].attributes.brightness
    entities:
      - light.<RGBW-ENTITY>
    element:
      type: image
      image: /local/<DEIN-PFAD>/<DEIN-TRANSPARENTES-1x1-PIXEL-BILD>.png
      state_image:
        'on': >-
          ${FARBMODUS === 'color_temp' ?
          '/local/<DEIN-PFAD>/<DEIN-RGBW-ENTITÄTS-BILD-WEISS>.png' :
          '/local/<DEIN-PFAD>/<DEIN-RGBW-ENTITÄTS-BILD-ROT>.png' }
      entity: light.<RGBW-ENTITY>
    style:
      filter: >-
        ${ "hue-rotate(" + (LICHTFARBE ? LICHTFARBE[0] : 0) + "deg)"}
      opacity: >-
        ${LEUCHTENSTATUS === 'on' ? (HELLIGKEIT / 254) : '100'}
      mix-blend-mode: lighten
      pointer-events: none
      left: 50%
      top: 50%
      width: 100%
      #-----------------------------------------------         
      #----------------------------------------------- 
      #  RGB
      #-----------------------------------------------         
      #----------------------------------------------- 

  - type: custom:config-template-card
    variables:
      LEUCHTENSTATUS: states['light.<RGB-ENTITY>'].state
      FARBMODUS: states['light.<RGB-ENTITY>'].attributes.color_mode
      LICHTFARBE: states['light.<RGB-ENTITY>'].attributes.hs_color
      HELLIGKEIT: states['light.<RGB-ENTITY>'].attributes.brightness
    entities:
      - light.<RGB-ENTITY>
    element:
      type: image
      image: /local/<DEIN-PFAD>/<DEIN-TRANSPARENTES-1x1-PIXEL-BILD>.png
      state_image:
        'on': /local/local/<DEIN-PFAD>/<DEIN-RGB-ENTITÄTS-BILD-ROT>.png
      entity: light.<RGB-ENTITY>
    style:
      filter: >-
        ${ "hue-rotate(" + (LICHTFARBE ? LICHTFARBE[0] : 0) + "deg)"}
      opacity: >-
        ${LEUCHTENSTATUS === 'on' ? (HELLIGKEIT / 254) : '1'}
      mix-blend-mode: lighten
      pointer-events: none
      left: 50%
      top: 50%
      width: 100%
Zerogiven commented 4 months ago

One thing to say that the base image of the lights is with a red lighting

shmuelzon commented 4 months ago

Hey,

I'd need to try this out to see if/how it works but forcing the red color on the required lights in SH3D sounds awkward. Maybe if the plugin can change them automatically to a red tint somehow. In any case, it sounds like this could only work in the "room overlay" mode.

Zerogiven commented 4 months ago

Hmm ok, i understand :) the red light is, i think, cause doesn't shine as brightly as others(also not as far..) or maybe the filter hue-rotate works better with red ^^

But yeah, maybe only for room overlay mode possible, but i will try some configs getting for all modes done :)

however, this is a very very cool feature i think :D

Zerogiven commented 4 months ago

Just want to let you know that i've got this easily working with the CSS output (only at the moment) :) This is my yaml (not exactly what yours produced but maybe you like it too :))

The only thing i've had to do was to render all RGBW Lights two times, once with a red light and once with a white light (ok don't ask me why the red thing is important, however i did not try others at the moment)

type: picture-elements
image: /local/plan/night.png
elements:
  - type: custom:config-template-card
    variables:
      LIGHT_STATE: states['light.wohnzimmer_licht_couch'].state
      COLOR_MODE: states['light.wohnzimmer_licht_couch'].attributes.color_mode
      LIGHT_COLOR: states['light.wohnzimmer_licht_couch'].attributes.hs_color
      BRIGHTNESS: states['light.wohnzimmer_licht_couch'].attributes.brightness
    entities:
      - light.wohnzimmer_licht_couch
    element:
      type: image
      image: /local/plan/transparent.png
      state_image:
        'on': >-
          ${COLOR_MODE === 'color_temp' ?
          '/local/plan/light.wohnzimmer_licht_couch.png' :
          '/local/plan/light.wohnzimmer_licht_couch_red.png' }
      entity: light.wohnzimmer_licht_couch
    style:
      filter: '${ "hue-rotate(" + (LIGHT_COLOR ? LIGHT_COLOR[0] : 0) + "deg)"}'
      opacity: '${LIGHT_STATE === ''on'' ? (BRIGHTNESS / 254) : ''100''}'
      mix-blend-mode: lighten
      pointer-events: none
      left: 50%
      top: 50%
      width: 100%
  - type: state-icon
    entity: light.wohnzimmer_licht_couch
    title: null
    style:
      top: 79.31%
      left: 36.85%
      border-radius: 50%
      text-align: center
      background-color: rgba(255, 255, 255, 0.3)
    tap_action:
      action: toggle

This was my firs tryout so, nothing optimized but it works and looks very cool if using RGBW or CCT lights and a bit more "realistic" :smiley:

I shortened my code, i also made some conditional things to get a morning/day/evening/night rendering

Zerogiven commented 4 months ago

Oh i forgot two things... i rendered the light graphics with a different time than the "night.png"

night.png = 20:30 lights = 01:00

The "trick" is mix-blend-mode: lighten and a transparent 1x1 pixel png which is used when the light is off. So you can use a daylight rendering and see the RGB(W) effect too :)

Zerogiven commented 4 months ago

And for CCT lamps :) (with white light rendering, red is only for RGB needed)

type: picture-elements
image: /local/plan/night.png
elements:
  - type: custom:config-template-card
    variables:
      LIGHT_STATE: states['light.wohnzimmer_licht_couch'].state
      LIGHT_COLOR: states['light.wohnzimmer_licht_couch'].attributes.hs_color
      BRIGHTNESS: states['light.wohnzimmer_licht_couch'].attributes.brightness
    entities:
      - light.wohnzimmer_licht_couch
    element:
      type: image
      image: /local/plan/transparent.png
      state_image:
        'on': '/local/plan/light.wohnzimmer_licht_couch.png'
      entity: light.wohnzimmer_licht_couch
    style:
      filter: '${ "hue-rotate(" + (LIGHT_COLOR ? LIGHT_COLOR[0] : 0) + "deg)"}'
      opacity: '${LIGHT_STATE === ''on'' ? (BRIGHTNESS / 254) : ''100''}'
      mix-blend-mode: lighten
      pointer-events: none
      left: 50%
      top: 50%
      width: 100%
  - type: state-icon
    entity: light.wohnzimmer_licht_couch
    title: null
    style:
      top: 79.31%
      left: 36.85%
      border-radius: 50%
      text-align: center
      background-color: rgba(255, 255, 255, 0.3)
    tap_action:
      action: toggle
shmuelzon commented 4 months ago

Let's see if I understand all of this...

Assuming we want to use the CSS lighten mode, which I think is a good approach, the only way I can get away with applying a hue-rotate only where the light illuminates the furniture is if the rest of the floor plan is either transparent or totally black. Is that the case with your red light overlay image?

I didn't see a way to change, in SH3D, the light color. Did you just create a red light source from the get-go or do you have some way to change an existing light's color? While I'd prefer to have some way to change the light color before rendering, I'm also OK with documenting that RGB lights need to be red when designing the floor plan in SH3D.

As for why it needs to be red, I'm guessing that's just the assumption of the hue-rotate degrees. Maybe we can add some offset to the calculated degrees to make it work with a white base color but I'm not entirely sure that's how hue works in the first place since, I think, a white light is represented with brightness alone anyway.

I am wondering, though, how the CSS lighten mix-mode will work if I have a base image that's not pitch black, i.e., the floorplan is somewhat illuminated by the sun, with a colored light on it and the brightness level of both lights is similar. Some pixels might be taken from the base image while others from the colored light and I'm wondering how it'll look. If it looks OK, then I might just end up making all of the overlay images in black (set the time to midnight and disable ceiling lights).

Regarding the CCT lights, do they really need to be handled differently? Can't I use the same YAML as RGB with a default color or something? I don't have any in my HA so I'm not sure how they're represented.

I'd also appreciate it if you could share samples of the images in your floorplan to along with the YAML you posted so I can better understand what kind of images I need to prepare

boroborob commented 1 month ago

Hi!

First of all, great project, thanks for all the work! Tried to implement Zerogiven's code and it was easier than expected. Used GIMP to subtract the difference for each lamp ON image from the base.png and added transparency to them with an alpha channel. Also created a red version from these transparent images with the Colorize function. With the help of ChatGPT I was able to create a GIMP pythonfu script that works ok, although needs some work for sure. :D

Here's an example of the transparent and red images: light_subtracted light_red

The GIMP script: (has to be placed in \appdata\Roaming\GIMP\\\plug-ins)

from gimpfu import *
import os

def subtract_images_in_folder(base_image_path, folder_path):
    # Load the base image
    base_image = pdb.gimp_file_load(base_image_path, base_image_path)
    base_layer = pdb.gimp_image_get_active_layer(base_image)

    # Ensure the base image has an alpha channel
    if not pdb.gimp_drawable_has_alpha(base_layer):
        pdb.gimp_layer_add_alpha(base_layer)

    # List all files in the folder
    for filename in os.listdir(folder_path):
        if filename.endswith(".png") and filename != "base.png":
            image_path = os.path.join(folder_path, filename)

            # Load the current image
            current_image = pdb.gimp_file_load(image_path, image_path)
            current_layer = pdb.gimp_image_get_active_layer(current_image)

            # Duplicate the base layer into the current image
            base_copy = pdb.gimp_layer_new_from_drawable(base_layer, current_image)
            pdb.gimp_image_insert_layer(current_image, base_copy, None, 0)

            # Set the blending mode to 'Difference' for subtraction
            pdb.gimp_layer_set_mode(base_copy, DIFFERENCE_MODE)

            # Merge the layers to apply the difference
            merged_layer = pdb.gimp_image_merge_down(current_image, base_copy, CLIP_TO_BOTTOM_LAYER)

            # Select the black color to make it transparent
            pdb.gimp_image_select_color(current_image, CHANNEL_OP_REPLACE, merged_layer, (0, 0, 0))
            pdb.gimp_edit_clear(merged_layer)
            pdb.gimp_selection_none(current_image)

            # Save the result with the "_subtracted" suffix
            output_path = os.path.join(folder_path, filename.replace(".png", "_subtracted.png"))
            pdb.file_png_save_defaults(current_image, merged_layer, output_path, output_path)

            # Create a red version by colorizing
            red_layer = pdb.gimp_image_get_active_layer(current_image)

            # Apply the colorize effect (Hue: 1, Saturation: 0.75, Lightness: 0)
            pdb.gimp_colorize(red_layer, 360, 75, 0)

            # Save the red version with '_red' suffix
            output_red_path = os.path.join(folder_path, filename.replace(".png", "_red.png"))
            pdb.file_png_save_defaults(current_image, red_layer, output_red_path, output_red_path)

            # Clean up
            pdb.gimp_image_delete(current_image)

    # Clean up the base image
    pdb.gimp_image_delete(base_image)

register(
    "python_fu_subtract_images_in_folder",
    "Subtract multiple images from a base image in a folder and create transparent results",
    "Subtract all PNG images in a folder from a base image and output the subtracted images.",
    "Your Name", "Your Name", "2024",
    "<Toolbox>/Python-Fu/Subtract Images from Folder...",
    "",
    [
        (PF_FILE, "base_image_path", "Path to the base image (base.png)", ""),
        (PF_DIRNAME, "folder_path", "Path to the folder containing the images", ""),
    ],
    [],
    subtract_images_in_folder)

main()

Card:

    type: picture-elements
    image: /local/HA_Floorplan_0.6/base.png
    elements:
      - type: custom:config-template-card
        variables:
          LIGHT_STATE: states['light.<entity>'].state
          COLOR_MODE: states['light.<entity>'].attributes.color_mode
          LIGHT_COLOR: states['light.<entity>'].attributes.hs_color
          BRIGHTNESS: states['light.<entity>'].attributes.brightness
        entities:
          - light.<entity>
        element:
          type: image
          image: /local/transparent.png
          state_image:
            'on': >-
              ${COLOR_MODE === 'color_temp' ?
              '/local/HA_Floorplan_0.6/light.<entity>.png' :
              '/local/HA_Floorplan_0.6/light.<entity>_red.png' }
          entity: light.<entity>
        style:
          filter: '${ "hue-rotate(" + (LIGHT_COLOR ? LIGHT_COLOR[0] : 0) + "deg)"}'
          opacity: '${LIGHT_STATE === ''on'' ? (BRIGHTNESS / 255) : ''100''}'
          mix-blend-mode: lighten
          pointer-events: none
          left: 50%
          top: 50%
          width: 100%

.
.
.

End result: 9-03-2024 (18-47-13)

Used transparent light sources as 'ceilings' for each room with 0.5 percent power to avoid having completely black rooms with night renders (thanks for the always on feature!).

Hope this helps!

shmuelzon commented 4 weeks ago

This is very interesting, thanks! So, from what I understand, the plugin needs to implement the following:

I don't see, though, how this could be done with room-overlay nor complete renders though. For room-overlay we'll have images with all the light combinations in the same room so I can't colorize all of them in red. I'm not sure how popular the complete renders mode is to begin with so we can ignore it for now. We will need to, at least, add a warning or something when any light is configured for RGB and the mode isn't CSS overlay.

If anyone can provide a link or something to the required algorithms for finding the differences and colorize an image, I can try to implement something like this.

shmuelzon commented 4 weeks ago

Hey, while I couldn't use the GIMP script directly, the documentation for the colorizing function was enough for me to understand how to tint the overlay image :) I have a pull request ready but, before I merge it, anyone care to try it out: https://github.com/shmuelzon/home-assistant-floor-plan/actions/runs/10798997216/artifacts/1916013143

The only issue I'm aware of right now (and I guess it's the same when done manually as above) is when selecting an RGB light that's close to white/any shade of gray, the image is still red-tinted because the hue of grays is 0 (along with 0 saturation), so we don't change the hue of the tinted image keeping it as white. The YAML does support RGBW lights and then the "regular" image is used as a base and only brightness affects it but I think it'll still be red-ish when in RGB mode.

I still want/need to improve the generation of the overlay image, any ideas there are welcome!

andyxpert commented 3 weeks ago

Great, thanks for the build. One thing you could add is saturation through opacity ( :D ). Hear me out pls:

In my tests it works rather ok and doesn't show the bugs with saturation mentioned on other sites and also works like a charm with temp-only settings (Kelvin-based, only warm-cold, no RGB/HSL)

Here's a sample of my config that works fairly OK with saturation:

type: picture-elements
image: /local/floorplan/base.png?version=0CDE5A95DAC7881B7EDEA89880558DE9
elements:
  - type: custom:config-template-card
    variables:
      LIGHT_STATE: states['light.top_left'].state
      BRIGHTNESS: states['light.top_left'].attributes.brightness
    entities:
      - light.top_left
    element:
      type: image
      image: >-
        /local/floorplan/transparent.png?version=56ABE4CBC175363DA0810882244B34FF
      state_image:
        'on': >-
          /local/floorplan/light.top_left.png?version=6C03BB36204335FFE67E68B1D8328664
      entity: light.top_left
    style:
      opacity: '${LIGHT_STATE === ''on'' ? (BRIGHTNESS / 255) : ''100''}'
      pointer-events: none
      left: 50%
      top: 50%
      width: 100%
  - type: custom:config-template-card
    variables:
      LIGHT_COLOR: states['light.top_left'].attributes.hs_color
    entities:
      - light.top_left
    element:
      type: image
      image: >-
        /local/floorplan/transparent.png?version=56ABE4CBC175363DA0810882244B34FF
      state_image:
        'on': '/local/floorplan/light.top_left.red.png?version=AFA91B481FB27E832407893D6A55FA60'
      entity: light.top_left
    style:
      filter: '${ "hue-rotate(" + (LIGHT_COLOR ? LIGHT_COLOR[0] : 0) + "deg)"}'
      opacity: '${ (LIGHT_COLOR ? LIGHT_COLOR[1] : 100) + ''%''}'
      pointer-events: none
      left: 50%
      top: 50%
      width: 100%
shmuelzon commented 3 weeks ago

Since I understand very little of the HSV colorspace, can you please explain why this is better and, perhaps, add screenshots to compare the two YAMLs' results?

andyxpert commented 3 weeks ago

It's problematic... I am testing with 5 rgb lights in one room and overlapping layers isn't going too well... I will look through the backups as I did a similar setup 2 years ago though there was almost no overlapping of layers...

Coming from a photography background I honestly miss Photoshop blending precision on layers TBH :)

I'll post my findings in a few days

andyxpert commented 3 weeks ago

Since I understand very little of the HSV colorspace, can you please explain why this is better and, perhaps, add screenshots to compare the two YAMLs' results?

In theory there are a few things to consider:

Now, these concepts in bitmaps are seen as:

The normal approach to coloring or recoloring a bitmap is to handle the luminosity and color separately. A base dark layer + a luminosity layer + a color layer The luminosity layer controls brightness through opacity The color layer controls color through opacity and hue-rotation

These layers should have some blending modes set (lighten for luminosity layer and color for the color layer)

The problem appears when you overlap multiple such layers and colors start to blend erratically... https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode - this helps with blending modes - as a reference

andyxpert commented 3 weeks ago

Ok, played a little and here's what I came up with... this is the best thing I could do with the given images generated by the tool:

What I still need to do for a better looking layout:

Here is how it renders: image

Here is the configuration:

      - type: picture-elements
        image: /local/floorplan/base.png
        elements:
#LIVING LIGHTS
          - type: custom:config-template-card
            variables:
              LIGHT_STATE: states['light.top_light'].state
              BRIGHTNESS: states['light.top_light'].attributes.brightness
            entities:
              - light.top_light
            element:
              type: image
              image: /local/floorplan/transparent.png
              state_image:
                'on': /local/floorplan/light.top_light.png
              entity: light.top_light
            style:
              opacity: '${LIGHT_STATE === "on" ? (BRIGHTNESS / 255) : "0"}'
              mix-blend-mode: lighten
              pointer-events: none
              left: 50%
              top: 50%
              width: 100%
          - type: custom:config-template-card
            variables:
              LIGHT_STATE: states['light.table_light'].state
              BRIGHTNESS: states['light.table_light'].attributes.brightness
            entities:
              - light.table_light
            element:
              type: image
              image: /local/floorplan/transparent.png
              state_image:
                'on': /local/floorplan/light.table_light.png
              entity: light.table_light
            style:
              opacity: '${LIGHT_STATE === "on" ? (BRIGHTNESS / 255) : "0"}'
              mix-blend-mode: lighten
              pointer-events: none
              left: 50%
              top: 50%
              width: 100%
          - type: custom:config-template-card
            variables:
              LIGHT_STATE: states['light.strip'].state
              BRIGHTNESS: states['light.strip'].attributes.brightness
            entities:
              - light.strip
            element:
              type: image
              image: /local/floorplan/transparent.png
              state_image:
                'on': /local/floorplan/light.strip.png
              entity: light.strip
            style:
              opacity: '${LIGHT_STATE === "on" ? (BRIGHTNESS / 255) : "0"}'
              mix-blend-mode: lighten
              pointer-events: none
              left: 50%
              top: 50%
              width: 100%
          - type: custom:config-template-card
            variables:
              LIGHT_STATE: states['light.top_lamp'].state
              BRIGHTNESS: states['light.top_lamp'].attributes.brightness
            entities:
              - light.top_lamp
            element:
              type: image
              image: /local/floorplan/transparent.png
              state_image:
                'on': /local/floorplan/light.top_lamp.png
              entity: light.top_lamp
            style:
              opacity: '${LIGHT_STATE === "on" ? (BRIGHTNESS / 255) : "0"}'
              mix-blend-mode: lighten
              pointer-events: none
              left: 50%
              top: 50%
              width: 100%
          - type: custom:config-template-card
            variables:
              LIGHT_STATE: states['light.top_left'].state
              BRIGHTNESS: states['light.top_left'].attributes.brightness
            entities:
              - light.top_left
            element:
              type: image
              image: /local/floorplan/transparent.png
              state_image:
                'on': /local/floorplan/light.top_left.png
              entity: light.top_left
            style:
              opacity: '${LIGHT_STATE === "on" ? (BRIGHTNESS / 255) : "0"}'
              mix-blend-mode: lighten
              pointer-events: none
              left: 50%
              top: 50%
              width: 100%
          - type: custom:config-template-card
            variables:
              LIGHT_STATE: states['light.lamp'].state
              BRIGHTNESS: states['light.lamp'].attributes.brightness
            entities:
              - light.lamp
            element:
              type: image
              image: /local/floorplan/transparent.png
              state_image:
                'on': /local/floorplan/light.lamp.png
              entity: light.lamp
            style:
              opacity: '${LIGHT_STATE === "on" ? (BRIGHTNESS / 255) : "0"}'
              mix-blend-mode: lighten
              pointer-events: none
              left: 50%
              top: 50%
              width: 100%
#LIVING COLOR LIGHTS
          - type: custom:config-template-card
            variables:
              LIGHT_STATE: states['light.table_light'].state
              LIGHT_COLOR: states['light.table_light'].attributes.hs_color
              BRIGHTNESS: states['light.table_light'].attributes.brightness
            entities:
              - light.table_light
            element:
              type: image
              image: /local/floorplan/transparent.png
              state_image:
                'on': /local/floorplan/light.table_light.red.png
              entity: light.table_light
            style:
              filter: >-
                ${ "saturate(" + (LIGHT_COLOR ? LIGHT_COLOR[1] : 100) + "%)
                hue-rotate(" + (LIGHT_COLOR ? LIGHT_COLOR[0] : 0) + "deg)"}
              opacity: '${LIGHT_STATE === "on" ? (BRIGHTNESS / 255) : "0"}'
              mix-blend-mode: color
              pointer-events: none
              left: 50%
              top: 50%
              width: 100%
          - type: custom:config-template-card
            variables:
              LIGHT_STATE: states['light.strip'].state
              LIGHT_COLOR: states['light.strip'].attributes.hs_color
              BRIGHTNESS: states['light.strip'].attributes.brightness
            entities:
              - light.strip
            element:
              type: image
              image: /local/floorplan/transparent.png
              state_image:
                'on': /local/floorplan/light.strip.red.png
              entity: light.strip
            style:
              filter: >-
                ${ "saturate(" + (LIGHT_COLOR ? LIGHT_COLOR[1] : 100) + "%)
                hue-rotate(" + (LIGHT_COLOR ? LIGHT_COLOR[0] : 0) + "deg)"}
              opacity: '${LIGHT_STATE === "on" ? (BRIGHTNESS / 255) : "0"}'
              mix-blend-mode: color
              pointer-events: none
              left: 50%
              top: 50%
              width: 100%
          - type: custom:config-template-card
            variables:
              LIGHT_STATE: states['light.top_lamp'].state
              LIGHT_COLOR: states['light.top_lamp'].attributes.hs_color
              BRIGHTNESS: states['light.top_lamp'].attributes.brightness
            entities:
              - light.top_lamp
            element:
              type: image
              image: /local/floorplan/transparent.png
              state_image:
                'on': /local/floorplan/light.top_lamp.red.png
              entity: light.top_lamp
            style:
              filter: >-
                ${ "saturate(" + (LIGHT_COLOR ? LIGHT_COLOR[1] : 100) + "%)
                hue-rotate(" + (LIGHT_COLOR ? LIGHT_COLOR[0] : 0) + "deg)"}
              opacity: '${LIGHT_STATE === "on" ? (BRIGHTNESS / 255) : "0"}'
              mix-blend-mode: color
              pointer-events: none
              left: 50%
              top: 50%
              width: 100%
          - type: custom:config-template-card
            variables:
              LIGHT_STATE: states['light.top_left'].state
              LIGHT_COLOR: states['light.top_left'].attributes.hs_color
              BRIGHTNESS: states['light.top_left'].attributes.brightness
            entities:
              - light.top_left
            element:
              type: image
              image: /local/floorplan/transparent.png
              state_image:
                'on': /local/floorplan/light.top_left.red.png
              entity: light.top_left
            style:
              filter: >-
                ${ "saturate(" + (LIGHT_COLOR ? LIGHT_COLOR[1] : 100) + "%)
                hue-rotate(" + (LIGHT_COLOR ? LIGHT_COLOR[0] : 0) + "deg)"}
              opacity: '${LIGHT_STATE === "on" ? (BRIGHTNESS / 255) : "0"}'
              mix-blend-mode: color
              pointer-events: none
              left: 50%
              top: 50%
              width: 100%
          - type: custom:config-template-card
            variables:
              LIGHT_STATE: states['light.lamp'].state
              LIGHT_COLOR: states['light.lamp'].attributes.hs_color
              BRIGHTNESS: states['light.lamp'].attributes.brightness
            entities:
              - light.lamp
            element:
              type: image
              image: /local/floorplan/transparent.png
              state_image:
                'on': /local/floorplan/light.lamp.red.png
              entity: light.lamp
            style:
              filter: >-
                ${ "saturate(" + (LIGHT_COLOR ? LIGHT_COLOR[1] : 100) + "%)
                hue-rotate(" + (LIGHT_COLOR ? LIGHT_COLOR[0] : 0) + "deg)"}
              opacity: '${LIGHT_STATE === "on" ? (BRIGHTNESS / 255) : "0"}'
              mix-blend-mode: color
              pointer-events: none
              left: 50%
              top: 50%
              width: 100%
andyxpert commented 3 weeks ago

Hmmm one idea, when you do the masking of the red images you have a hard pass, meaning that what falls below the threshold you cut the image suddenly.

Light has falloff, what if you could add a shaded transparency ? You apply on top of the cut image a radius based transparency gradient. That would get rid of the hard differences when overlaying multiple lights. I'll try something and get back to you.

LE: this solves the falloff problem from the picture-elements directly: mask-image: radial-gradient(farthest-side at 10% 10%, rgba(0,0,0,1) 40%,transparent 70%) I highly recommend this to make things look more natural play around with percentages per each light

updated render (with added radial-gradient IMG_1671

shmuelzon commented 3 weeks ago

@andyxpert So, what you're suggesting is that for RGB lights, I add two YAML sections? The first, something like the following that handles on/off and brightness with blend mode lighten?

          - type: custom:config-template-card
            variables:
              LIGHT_STATE: states['light.table_light'].state
              BRIGHTNESS: states['light.table_light'].attributes.brightness
            entities:
              - light.table_light
            element:
              type: image
              image: /local/floorplan/transparent.png
              state_image:
                'on': /local/floorplan/light.table_light.png
              entity: light.table_light
            style:
              opacity: '${LIGHT_STATE === "on" ? (BRIGHTNESS / 255) : "0"}'
              mix-blend-mode: lighten
              pointer-events: none
              left: 50%
              top: 50%
              width: 100%

Then, at the end of all the "regular" lights and the above for RGB lights, I add another YAML section to handle the color of the RGB lights, like so:

          - type: custom:config-template-card
            variables:
              LIGHT_STATE: states['light.table_light'].state
              LIGHT_COLOR: states['light.table_light'].attributes.hs_color
              BRIGHTNESS: states['light.table_light'].attributes.brightness
            entities:
              - light.table_light
            element:
              type: image
              image: /local/floorplan/transparent.png
              state_image:
                'on': /local/floorplan/light.table_light.red.png
              entity: light.table_light
            style:
              filter: >-
                ${ "saturate(" + (LIGHT_COLOR ? LIGHT_COLOR[1] : 100) + "%)
                hue-rotate(" + (LIGHT_COLOR ? LIGHT_COLOR[0] : 0) + "deg)"}
              opacity: '${LIGHT_STATE === "on" ? (BRIGHTNESS / 255) : "0"}'
              mix-blend-mode: color
              mask-image: radial-gradient(farthest-side at 10% 10%, rgba(0,0,0,1) 40%,transparent 70%)
              pointer-events: none
              left: 50%
              top: 50%
              width: 100%

Did I get it right?

shmuelzon commented 3 weeks ago

I have another commit I have lined up that needs what I currently have for RGB as a prerequisite so I'm going to merge the current implementation. We can then take on improving it with the above or any other suggestion that could work.

andyxpert commented 3 weeks ago

Great, let's leave the color light implementation as is and I can open a new discussion on the improvement of color lights render.

The above suggestion is not perfect as an almost-white color would make the underlying layers render in grayscale... Still playing with the yaml in my spare time...

andyxpert commented 3 weeks ago

I think I managed to make it render correctly.

filter: >-
                ${ "opacity(" + (LIGHT_COLOR ? LIGHT_COLOR[1] : 100) + "%)
                hue-rotate(" + (LIGHT_COLOR ? LIGHT_COLOR[0] : 0) + "deg)"}
              opacity: '${LIGHT_STATE === "on" ? (BRIGHTNESS / 255) : "0"}'
              mix-blend-mode: normal

blend mode set to normal for color lights, style opacity set to light brightness and filter opacity set to light saturation. this fixes the saturation-going-gray issue mentioned above.

I'll start a discussion with a proper explanation and examples, screenshots and maybe that will be a starting point for how you aggregate the yaml file. Maybe you could add it as a toggle in the plugin (color lights V1/V2) so both yaml styles are available for people to choose which they prefer.

shmuelzon commented 3 weeks ago

I'll start a discussion with a proper explanation and examples, screenshots and maybe that will be a starting point for how you aggregate the yaml file.

That would be great, thanks!

Maybe you could add it as a toggle in the plugin (color lights V1/V2) so both yaml styles are available for people to choose which they prefer.

I would rather just stick with one if it provides better results as more options raise more questions. I was actually considering also removing the option for different rendering modes for the same reason but, we'll see...

andyxpert commented 5 days ago

I'll start a discussion with a proper explanation and examples, screenshots and maybe that will be a starting point for how you aggregate the yaml file.

That would be great, thanks!

@shmuelzon Hi, since the discussions tab is not enabled, I'm writing here with the final config I have for the YAML to display color lights properly:

          - type: custom:config-template-card
            variables:
              LIGHT_STATE: states['light.top_left'].state
              BRIGHTNESS: states['light.top_left'].attributes.brightness
            entities:
              - light.top_left
            element:
              type: image
              image: /local/floorplan/transparent.png
              state_image:
                'on': /local/floorplan/light.top_left.png
              entity: light.top_left
            style:
              mask-image: >-
                radial-gradient(farthest-side at 30% 70%, rgba(0,0,0,1) 20%,
                transparent 40%)
              opacity: '${LIGHT_STATE === "on" ? (BRIGHTNESS / 255)*0.8+0.2 : "0"}'
              mix-blend-mode: lighten
              pointer-events: none
              left: 50%
              top: 50%
              width: 100%
          - type: custom:config-template-card
            variables:
              LIGHT_STATE: states['light.top_left'].state
              LIGHT_COLOR: states['light.top_left'].attributes.hs_color
              BRIGHTNESS: states['light.top_left'].attributes.brightness
            entities:
              - light.top_left
            element:
              type: image
              image: /local/floorplan/transparent.png
              state_image:
                'on': /local/floorplan/light.top_left.red.png
              entity: light.top_left
            style:
              mask-image: >-
                radial-gradient(farthest-side at 20% 70%, rgba(0,0,0,1)
                20%,transparent 40%)
              filter: >-
                ${ "opacity(" + (LIGHT_COLOR ? LIGHT_COLOR[1] : 100) + "%)
                hue-rotate(" + (LIGHT_COLOR ? LIGHT_COLOR[0] : 0) + "deg)"}
              opacity: '${LIGHT_STATE === "on" ? (BRIGHTNESS / 255)*0.8+0.2 : "0"}'
              mix-blend-mode: normal
              pointer-events: none
              left: 50%
              top: 50%
              width: 100%

So, first I add the color light as a "lighten" layer to display the luminance layer (opacity style for brightness and "lighten" as blend mode). Then I added the same color light (after, so it's on top) as a "color" layer to display its color (opacity style for brightness, hue-rotate for color and blend mode set to normal - so it leaves the color intact) I also added a "mask-image" with radial-gradient for both of them to avoid spilling the light too far away (light follows an inverse-square rule and its intensity should fall-off fairly quickly - the percentages should be modified for each light based on how the lights are placed in the room/house layout... trial & error for each of them to show best results - these percentages could also be linked to light brightness so it spreads more when the light is brighter... )

For me this resulted in almost perfect light display, blend of light color and as far as I can see it's almost perfect so I'm pleased with the result.

You could alter how the YAML is generated to take into account the duplicated layers and add the brightness/color configs by default with the 2 blend modes. the radial-gradient in mask-image could be "commented" by default and each user can decide whether they want it or not per each light...

Hope this helps

shmuelzon commented 5 days ago

@andyxpert thanks for that! A couple of question, if I may:

andyxpert commented 5 days ago

Hi,