From 284311ae776ec616971f4f2a79ace73d54e35b42 Mon Sep 17 00:00:00 2001 From: rahix <rahix@rahix.de> Date: Fri, 12 Apr 2024 10:44:54 +0000 Subject: [PATCH] BIG flow3r * sim: Add configurable OLED scale * sim: Add configurable OLED aspect ratio This can be used to fix aspect ratio stretching of badly configured screens. * sim: Make OLED size configurable * sim: Implement full-screen mode * settings: Fix a type annotation --- python_payload/st3m/settings.py | 2 +- sim/fakes/_sim.py | 87 ++++++++++++++++++++++++--------- sim/run.py | 14 ++++++ 3 files changed, 78 insertions(+), 25 deletions(-) diff --git a/python_payload/st3m/settings.py b/python_payload/st3m/settings.py index 979a95b320..ad80d3a725 100644 --- a/python_payload/st3m/settings.py +++ b/python_payload/st3m/settings.py @@ -171,7 +171,7 @@ class NumberTunable(UnaryTunable): NumberTunable is a UnaryTunable that has a numeric value """ - def __init__(self, name: str, key: str, default: Optional[int | float]) -> None: + def __init__(self, name: str, key: str, default: Optional[float]) -> None: super().__init__(name, key, default) def press(self, vm: Optional["ViewManager"]) -> None: diff --git a/sim/fakes/_sim.py b/sim/fakes/_sim.py index 0881f3136b..2a3f19dcae 100644 --- a/sim/fakes/_sim.py +++ b/sim/fakes/_sim.py @@ -12,17 +12,25 @@ try: except ImportError: print("Info: No custom config.py found") +SCREENSHOT = False +SCREENSHOT_DELAY = 5 +FULL_SCREEN = os.environ.get("SIM_FULL_SCREEN", "0") == "1" + + pygame.init() -screen_w = 814 -screen_h = 854 -screen = pygame.display.set_mode(size=(screen_w, screen_h)) +if FULL_SCREEN: + screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN) +else: + screen = pygame.display.set_mode(size=(814, 854)) +screen_w = screen.get_width() +screen_h = screen.get_height() simpath = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) bgpath = os.path.join(simpath, "background.png") background = pygame.image.load(bgpath) - -SCREENSHOT = False -SCREENSHOT_DELAY = 5 +OLED_SIZE = int(os.environ["SIM_OLED_SIZE"]) +OLED_ASPECT = float(os.environ["SIM_OLED_ASPECT"]) +OLED_SCALE = float(os.environ["SIM_OLED_SCALE"]) def path_replace(p): @@ -395,7 +403,9 @@ class Simulation: ) self._petal_surface_dirty = True self._full_surface = pygame.Surface((screen_w, screen_h), flags=pygame.SRCALPHA) - self._oled_surface = pygame.Surface((240, 240), flags=pygame.SRCALPHA) + self._oled_surface = pygame.Surface( + (OLED_SIZE, OLED_SIZE), flags=pygame.SRCALPHA + ) # Calculate OLED per-row offset. # @@ -411,8 +421,12 @@ class Simulation: # that is True if the pixel corresponding to this mask's bit is part of # the OLED disc, and false otherwise. mask = [ - [math.sqrt((x - 120) ** 2 + (y - 120) ** 2) <= 120 for x in range(240)] - for y in range(240) + [ + math.sqrt((x - OLED_SIZE // 2) ** 2 + (y - OLED_SIZE // 2) ** 2) + <= OLED_SIZE // 2 + for x in range(OLED_SIZE) + ] + for y in range(OLED_SIZE) ] # Now, we iterate the mask row-by-row and find the first True bit in # it. The offset within that row is our per-row offset for the @@ -457,14 +471,14 @@ class Simulation: surface.fill((0, 0, 0, 0)) buf = surface.get_buffer() - fb = fb[: 240 * 240 * 4] - for y in range(240): + fb = fb[: OLED_SIZE * OLED_SIZE * 4] + for y in range(OLED_SIZE): # Use precalculated row offset to turn OLED disc into square # bounded plane. offset = self._oled_offset[y] - start_offs_bytes = y * 240 * 4 + start_offs_bytes = y * OLED_SIZE * 4 start_offs_bytes += offset * 4 - end_offs_bytes = (y + 1) * 240 * 4 + end_offs_bytes = (y + 1) * OLED_SIZE * 4 end_offs_bytes -= offset * 4 buf.write(bytes(fb[start_offs_bytes:end_offs_bytes]), start_offs_bytes) @@ -481,28 +495,40 @@ class Simulation: need_overlays = False if self._led_surface_dirty or self._petal_surface_dirty: full.fill((0, 0, 0, 255)) - full.blit(background, (0, 0)) + if not FULL_SCREEN: + full.blit(background, (0, 0)) need_overlays = True if self._led_surface_dirty: - self._render_leds(self._led_surface) + if not FULL_SCREEN: + self._render_leds(self._led_surface) self._led_surface_dirty = False if need_overlays: full.blit(self._led_surface, (0, 0), special_flags=pygame.BLEND_ADD) if self._petal_surface_dirty: - self._render_petal_markers(self._petal_surface) + if not FULL_SCREEN: + self._render_petal_markers(self._petal_surface) self._petal_surface_dirty = False if need_overlays: - full.blit(self._petal_surface, (0, 0)) + if not FULL_SCREEN: + full.blit(self._petal_surface, (0, 0)) # Always blit oled. Its' alpha blending is designed in a way that it # can be repeatedly applied to a dirty _full_surface without artifacts. - center_x = 408 - center_y = 426 - off_x = center_x - (240 // 2) - off_y = center_y - (240 // 2) - full.blit(self._oled_surface, (off_x, off_y)) + center_x = screen_w // 2 + center_y = screen_h // 2 + if FULL_SCREEN: + size_x = screen_h * OLED_ASPECT * OLED_SCALE + size_y = screen_h * OLED_SCALE + off_x = center_x - (size_x // 2) + off_y = center_y - (size_y // 2) + new = pygame.transform.scale(self._oled_surface, (size_x, size_y)) + full.blit(new, (off_x, off_y)) + else: + off_x = center_x - (OLED_SIZE // 2) + off_y = center_y - (OLED_SIZE // 2) + full.blit(self._oled_surface, (off_x, off_y)) screen.blit(full, (0, 0)) pygame.display.flip() @@ -554,8 +580,21 @@ class FramebufferManager: # (difference between ~10FPS and 500+FPS!). for _ in range(1): - fb, c = ctx._wasm.ctx_new_for_framebuffer(240, 240, 240 * 4, ctx.RGBA8) - ctx._wasm.ctx_apply_transform(c, 1, 0, 120, 0, 1, 120, 0, 0, 1) + fb, c = ctx._wasm.ctx_new_for_framebuffer( + OLED_SIZE, OLED_SIZE, OLED_SIZE * 4, ctx.RGBA8 + ) + ctx._wasm.ctx_apply_transform( + c, + OLED_SIZE / 240, + 0, + OLED_SIZE / 2, + 0, + OLED_SIZE / 240, + OLED_SIZE / 2, + 0, + 0, + 1, + ) self._free.append((fb, c)) self._overlay = ctx._wasm.ctx_new_for_framebuffer(240, 240, 240 * 4, ctx.RGBA8) diff --git a/sim/run.py b/sim/run.py index f2b7838655..d275f72a55 100755 --- a/sim/run.py +++ b/sim/run.py @@ -155,6 +155,16 @@ def sim_main(): default=False, help="Generate a flow3r.png screenshot.", ) + parser.add_argument( + "--full-screen", + dest="full_screen", + action="store_true", + default=False, + help="Run the simulator as full-screen OLED display.", + ) + parser.add_argument("--oled-size", dest="oled_size", default=240) + parser.add_argument("--oled-aspect", dest="oled_aspect", default=1) + parser.add_argument("--oled-scale", dest="oled_scale", default=1) parser.add_argument( "override_app", nargs="?", @@ -163,6 +173,10 @@ def sim_main(): ) args = parser.parse_args() + os.environ["SIM_FULL_SCREEN"] = "1" if args.full_screen else "0" + os.environ["SIM_OLED_SIZE"] = str(args.oled_size) + os.environ["SIM_OLED_ASPECT"] = str(args.oled_aspect) + os.environ["SIM_OLED_SCALE"] = str(args.oled_scale) import _sim _sim.SCREENSHOT = args.screenshot -- GitLab