fix(13e6): pre-rotate LANDSCAPE diagram for CCW-to-landscape rotation

The user rotates the frame 90° CCW into landscape (not CW as the
previous comment block assumed), so the LANDSCAPE orientation diagram
needs to be pre-rotated the opposite direction to land upright.

- Previous: ribbon on bottom edge, LEFT arrow, label rotated 90° CCW on
  the diagram's left side (matched CW user rotation; rendered upside-
  down once the user actually rotates CCW into landscape).
- Now: ribbon on top edge, RIGHT arrow, label rotated 90° CW down the
  diagram's right side. After the user's 90° CCW rotation it lands as
  wide rect, ribbon-left, up-arrow — correct upright landscape.

Adds right_arrow() helper as the mirror of left_arrow(). Regenerated
all three setup-screen .bins.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-17 21:15:59 -04:00
parent a31a39fdc4
commit 22c9edb09e
5 changed files with 32 additions and 22 deletions
File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 KiB

After

Width:  |  Height:  |  Size: 248 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 KiB

After

Width:  |  Height:  |  Size: 248 KiB

+30 -20
View File
@@ -281,6 +281,15 @@ def left_arrow(draw, cx, cy, half_h=18, w=34, color=BK):
], fill=color)
def right_arrow(draw, cx, cy, half_h=18, w=34, color=BK):
"""Solid filled triangle pointing right, centered on (cx, cy)."""
draw.polygon([
(cx + w // 2, cy),
(cx - w // 2, cy - half_h),
(cx - w // 2, cy + half_h),
], fill=color)
def paste_rotated_text(img, text, font, fill, anchor_xy, ccw_degrees=90):
"""
Render `text` horizontally onto a transparent overlay, rotate ccw, and
@@ -304,17 +313,17 @@ def orientation_diagrams(img, cx, top_y, label_color=None, compact=False):
PORTRAIT = upright tall rect, ribbon along the bottom short edge,
up-arrow inside.
LANDSCAPE = pre-rotated 90° CCW from upright landscape. The frame
rotation portrait→landscape is 90° CW (ribbon moves
bottom→left as viewed by the user); the CCW pre-rotation
LANDSCAPE = pre-rotated 90° CW from upright landscape. The frame
rotation portrait→landscape is 90° CCW (ribbon moves
top→left as viewed by the user); the CW pre-rotation
cancels that, so when the user picks the frame up and
rotates it 90° CW into landscape the diagram lands
rotates it 90° CCW into landscape the diagram lands
upright (wide rect, ribbon-left, up-arrow).
In the portrait rendering that means: tall rect, ribbon
along bottom edge (was the LEFT edge upright), LEFT-
along TOP edge (was the LEFT edge upright), RIGHT-
pointing arrow (was UP upright), and the "LANDSCAPE"
label rotated 90° CCW so it runs up the long edge
reads horizontally once the frame is mounted landscape.
label rotated 90° CW so it runs DOWN the right long edge
reads horizontally once the frame is mounted landscape.
"""
if label_color is None:
label_color = BK
@@ -355,28 +364,29 @@ def orientation_diagrams(img, cx, top_y, label_color=None, compact=False):
pt_y + (diag_h - ribbon_thick) // 2)
text_center(draw, pt_x + diag_w // 2, diag_bottom + 14, "PORTRAIT", F_TINY, BK)
# LANDSCAPE — pre-rotated 90° CCW from upright.
# LANDSCAPE — pre-rotated 90° CW from upright (the user rotates 90° CCW
# into landscape; the CW pre-rotation cancels that so it lands upright).
# Upright landscape: wide rect, ribbon along LEFT long edge, UP arrow.
# After 90° CCW (in portrait rendering): tall rect, ribbon along BOTTOM
# short edge, LEFT-pointing arrow. Label runs up the LEFT long edge,
# rotated 90° CCW so it reads L→R once the frame is rotated to landscape.
# After 90° CW (in portrait rendering): tall rect, ribbon along TOP
# short edge, RIGHT-pointing arrow. Label runs DOWN the RIGHT long edge,
# rotated 90° CW so it reads L→R once the frame is rotated to landscape.
draw.rectangle([ls_x, ls_y, ls_x + diag_w - 1, ls_y + diag_h - 1],
outline=BK, width=3)
draw.rectangle([ls_x, ls_y + diag_h - ribbon_thick,
ls_x + diag_w - 1, ls_y + diag_h - 1], fill=BK)
left_arrow(draw, ls_x + diag_w // 2,
ls_y + (diag_h - ribbon_thick) // 2)
# Rotated label, anchored just left of the diagram's left long edge.
draw.rectangle([ls_x, ls_y,
ls_x + diag_w - 1, ls_y + ribbon_thick - 1], fill=BK)
right_arrow(draw, ls_x + diag_w // 2,
ls_y + ribbon_thick + (diag_h - ribbon_thick) // 2)
# Rotated label, anchored just right of the diagram's right long edge.
label_text = "LANDSCAPE"
bb = F_TINY.getbbox(label_text)
label_w = bb[2] - bb[0]
label_h = bb[3] - bb[1]
# Rotated label is `label_w` tall, `label_h` wide. Centred vertically
# against the rect, sitting just to its left.
rotated_x = ls_x - label_h - 16
# Rotated 90° CW: label is `label_w` tall, `label_h` wide. Centred
# vertically against the rect, sitting just to its right.
rotated_x = ls_x + diag_w + 16
rotated_y = ls_y + (diag_h - label_w) // 2
paste_rotated_text(img, label_text, F_TINY, BK, (rotated_x, rotated_y),
ccw_degrees=90)
ccw_degrees=-90)
# ═══════════════════════════════════════════════════════════════════════════════