Skip to content

Commit

Permalink
Add a screen-to-backlight module.
Browse files Browse the repository at this point in the history
This is a heavily optimised (~20ms -> ~4.4ms frame) version of PrestoLight.

Co-authored-by: Phil Howard <[email protected]>
  • Loading branch information
thirdr and Gadgetoid committed Nov 28, 2024
1 parent 94c4797 commit a6fccef
Showing 1 changed file with 75 additions and 0 deletions.
75 changes: 75 additions & 0 deletions modules/py_frozen/backlight.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import plasma
import micropython


class Zone(object):
SIZE = 80

# A section of the screen we'll use for sampling.
# We're using 80 x 80 sections and sampling 49 points within that space
def __init__(self, x, y, width=240):
self.colours = []

# Pre calculate the ranges to use later
# Calculates the coordinates in terms of RGB56 / two bytes per pixel
self.x_range = range(x * 2, (x + self.SIZE) * 2, 10 * 2)
self.y_range = range(y * 2, (y + self.SIZE) * 2, 10 * 2)

# Calculate the offsets for every pixel we need to sample
# A tuple is slightly less expensive than a list to iterate
self.pixels = tuple([y * width + x for y in self.y_range for x in self.x_range])
self.samples = len(self.pixels)

@micropython.native
def return_avg(self, mv):
# Running total of the RGB values for our average calculation later
r_value, g_value, b_value = 0, 0, 0

for offset in self.pixels:
# Grab the pixel colour value back from the framebuffer
# This will be in RGB565 - RRRRRGGGGGGBBBBB
rgb = (mv[offset] << 8) | mv[offset + 1]

# Now we need to sort and arrange those bits into their R G and B values
# We can't get back the exact value due to the destructive nature of converting to RGB565
# But it's close enough for displaying a colour on the backlight :)
# We're shifting the 5, 6 and 5 bits into the most significant places
r_value += (rgb >> 8) & 0b11111000
g_value += (rgb >> 3) & 0b11111100
b_value += (rgb << 3) & 0b11111000

# Now we finish the average calculation and return the value as an integer
return (
int(r_value / self.samples),
int(g_value / self.samples),
int(b_value / self.samples)
)


class Reactive(object):
def __init__(self, surface, width=240, height=240):
self.mv = surface if isinstance(surface, memoryview) else memoryview(surface)

MAX = width - Zone.SIZE
MID = width // 2 - Zone.SIZE // 2
MIN = 0
W = width

# The index for each section matches the index for the Backlight LED
# Zone 0 is in the bottom right corner LED as you're looking at the screen
# And the LEDs proceed around counter-clockwise
# A tuple is slightly less expensive than a list to iterate
self.zones = (Zone(MAX, MAX, W), Zone(MAX, MID, W), Zone(MAX, MIN, W), Zone(MID, MIN, W),
Zone(MIN, MIN, W), Zone(MIN, MID, W), Zone(MIN, MAX, W))

self.bl = plasma.WS2812(7, 0, 0, 33)
self.bl.start()

@micropython.native
def update(self):
mv = self.mv
i = 0
# For each section we get the average colour in that area and set the backlight LED to that colour
for zone in self.zones:
self.bl.set_rgb(i, *zone.return_avg(mv))
i += 1

0 comments on commit a6fccef

Please sign in to comment.