From 7a4bbcf33a0c57eb9d5643e695ab435f41042e70 Mon Sep 17 00:00:00 2001 From: = <=> Date: Thu, 7 Sep 2023 02:37:08 +0200 Subject: [PATCH] Added cropping feature to color detection (only covered brightness until now). Changed "crop_offset_left" to "crop_left". Changed "crop_offset_top" to "crop_top". --- CHANGELOG.md | 9 +++-- README.md | 16 ++++---- .../ambient_extractor/__init__.py | 38 ++++++++++++------- custom_components/ambient_extractor/const.py | 4 +- .../ambient_extractor/manifest.json | 2 +- .../ambient_extractor/services.yaml | 4 +- 6 files changed, 44 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdbe38b..cfca87d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,14 @@ +# v0.5.2 +Expanded crop functionality to color detection. +Changed "crop_offset_left" to "offset_left". +Changed "crop_offset_top" to "offset_top". + # v0.5.1 Fixed services.yaml # v0.5 New features: -- Limit detection to an area of the source image. +- Limit brightness detection to an area of the source image. Configuration: - crop_offset_left = [0-100] in % width; default = 0 @@ -11,8 +16,6 @@ New features: - crop_width = [0-100] in % width; default = 0 == full image - crop_height = [ß-100] in % height; default = 0 == full image -Poor man's lamp positioning? Yes. Very poor. - # v0.4 Released 2023-02-20 diff --git a/README.md b/README.md index b2d96e5..ef6cdca 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ Additionally, overall brightness can be calculated and applied within adjustable | brightness_mode | Yes | mean rms natural dominant | mean | Brightness calculation method. `mean` and `rms` use a grayscale image, `natural` uses perceived brightness, `dominant` the same color as for RGB (fastest). | brightness_min | Yes | Int: 0 to 255 | 2 | Minimal brightness. `< 2` means off for most devices. | brightness_max | Yes | Int: 0 to 255 | 70 | Maximal brightness, should be `> brightness_min`. -| crop_offset_left | Yes | Int: 0 to 99 | 0 | Crop area: Left offset in % of image width. Default: 0 -| crop_offset_top | Yes | Int: 0 to 99 | 0 | Crop area: Top offset in % of image height. Default: 0 +| crop_left | Yes | Int: 0 to 99 | 0 | Crop area: Left offset in % of image width. Default: 0 +| crop_top | Yes | Int: 0 to 99 | 0 | Crop area: Top offset in % of image height. Default: 0 | crop_width | Yes | Int: 0 to 100 | 0 | Crop area: Width. Default: 0 (= no cropping) | crop_height | Yes | Int: 0 to 100 | 0 | Crop area: Height. Default: 0 (= no cropping) @@ -167,8 +167,8 @@ action: - light.living_room_tv_left transition: 0.6 brightness_auto: true - crop_offset_left: 0 - crop_offset_top: 0 + crop_left: 0 + crop_top: 0 crop_width: 50 crop_height: 100 - delay: @@ -183,8 +183,8 @@ action: - light.living_room_tv_right transition: 0.6 brightness_auto: true - crop_offset_left: 50 - crop_offset_top: 0 + crop_left: 50 + crop_top: 0 crop_width: 50 crop_height: 100 - delay: @@ -199,8 +199,8 @@ action: - light.living_room_ceiling transition: 0.6 brightness_auto: true - crop_offset_left: 0 - crop_offset_top: 0 + crop_left: 0 + crop_top: 0 crop_width: 100 crop_height: 35 mode: single diff --git a/custom_components/ambient_extractor/__init__.py b/custom_components/ambient_extractor/__init__.py index 545c59a..3328b8c 100644 --- a/custom_components/ambient_extractor/__init__.py +++ b/custom_components/ambient_extractor/__init__.py @@ -9,6 +9,7 @@ from colorthief import ColorThief import voluptuous as vol import math +from tempfile import TemporaryFile from homeassistant.components.light import ( ATTR_RGB_COLOR, @@ -28,6 +29,7 @@ ATTR_URL, DOMAIN, SERVICE_TURN_ON, + ATTR_BRIGHTNESS_AUTO, ATTR_BRIGHTNESS_MODE, ATTR_BRIGHTNESS_MIN, @@ -72,7 +74,13 @@ def _get_file(file_path): return file_path -def _get_color(file_handler) -> tuple: +def _get_color_from_image(im) -> tuple: + file_handler = TemporaryFile() + im.save(file_handler, "PNG") + return _get_color_from_file(file_handler) + + +def _get_color_from_file(file_handler) -> tuple: """Given an image file, extract the predominant color from it.""" color_thief = ColorThief(file_handler) @@ -82,13 +90,7 @@ def _get_color(file_handler) -> tuple: return color -def _get_brightness(file_handler, br_mode, color, crop_area): - - # No crop support for "dominant" - if br_mode == "dominant": - r, g, b = color - return (r + g + b) / 3 - +def _get_cropped_image(file_handler, crop_area): im = Image.open(file_handler) if crop_area['active']: im_width, im_height = im.size @@ -98,6 +100,13 @@ def _get_brightness(file_handler, br_mode, color, crop_area): math.floor(im_width / 100 * (crop_area['x'] + crop_area['w'])), math.floor(im_width / 100 * (crop_area['y'] + crop_area['h'])), )) + return im + + +def _get_brightness(im, br_mode, color): + if br_mode == "dominant": + r, g, b = color + return (r + g + b) / 3 if br_mode == "natural": stat = ImageStat.Stat(im) @@ -238,10 +247,12 @@ async def async_extract_color_from_url(url, check_brightness, br_mode, crop_area _file.name = "ambient_extractor.jpg" _file.seek(0) - color = _get_color(_file) + im = _get_cropped_image(_file, crop_area) + + color = _get_color_from_image(im) if crop_area['active'] else _get_color_from_file(_file) brightness = 0 if check_brightness: - brightness = _get_brightness(_file, br_mode, color, crop_area) + brightness = _get_brightness(im, br_mode, color) return { "color": color, @@ -258,12 +269,13 @@ def extract_color_from_path(file_path, check_brightness, br_mode, crop_area): return None _LOGGER.debug("Getting predominant RGB from file path '%s'", file_path) - _file = _get_file(file_path) - color = _get_color(_file) + im = _get_cropped_image(_file, crop_area) + color = _get_color_from_image(im) if crop_area['active'] else _get_color_from_file(_file) + brightness = 0 if check_brightness: - brightness = _get_brightness(_file, br_mode, color, crop_area) + brightness = _get_brightness(im, br_mode, color) return { "color": color, diff --git a/custom_components/ambient_extractor/const.py b/custom_components/ambient_extractor/const.py index c20207a..17b2cd3 100644 --- a/custom_components/ambient_extractor/const.py +++ b/custom_components/ambient_extractor/const.py @@ -10,9 +10,9 @@ # Image cropping # left offset in % of source width -ATTR_CROP_X = "crop_offset_left" +ATTR_CROP_X = "crop_left" # right offset in % of source height -ATTR_CROP_Y = "crop_offset_top" +ATTR_CROP_Y = "crop_top" # width in % of source width, 0 means full image ATTR_CROP_W = "crop_width" # height in % of source height, 0 means full image diff --git a/custom_components/ambient_extractor/manifest.json b/custom_components/ambient_extractor/manifest.json index 05d6f13..0cbb7ba 100644 --- a/custom_components/ambient_extractor/manifest.json +++ b/custom_components/ambient_extractor/manifest.json @@ -1,5 +1,5 @@ { - "version": "0.5.1", + "version": "0.5.2", "domain": "ambient_extractor", "name": "Ambient Extractor", "config_flow": false, diff --git a/custom_components/ambient_extractor/services.yaml b/custom_components/ambient_extractor/services.yaml index 239323c..097407a 100644 --- a/custom_components/ambient_extractor/services.yaml +++ b/custom_components/ambient_extractor/services.yaml @@ -60,7 +60,7 @@ turn_on: - natural - dominant - crop_offset_left: + crop_left: name: Crop image, left offset description: In % of the original width example: 0 @@ -68,7 +68,7 @@ turn_on: number: min: 0 max: 99 - crop_offset_top: + crop_top: name: Crop image, top offset description: In % of the original height example: 0