Lenticular weave¶
quiltwright.weave
¶
Weaving quilts into native pre-lensed frames on the CPU.
A Looking Glass panel is a passive lenticular optic over an ordinary LCD: it does not care who put the pixels behind it. Bridge normally runs the "lenticular" shader on the GPU every frame, interleaving quilt views per subpixel according to the device's factory calibration. This module runs the same shader once, in NumPy, and writes the result out as an ordinary image at the panel's exact native resolution.
Displayed 1:1 on the panel -- most usefully as the macOS desktop wallpaper of the Looking Glass display -- that image reconstructs as a static hologram with full parallax, no Bridge process required.
The math is a port of Bridge's Lenticular_RGBA_With_Aspect shader (via
the Unity plugin's HLSL conversion) together with LKG-Toolkit's
Calibration.ProcessPitch / ProcessSlope. It implements both
calibration generations:
- classic (Portrait-era): flat one-third-pixel RGB stride, and
- gen3 (configVersion 3.0): explicit per-channel
subpixelCellsoffsets, selected per pixel byCellPatternMode. These panels put R and G on one row and B on the other -- a 2-over-1 delta, ordered R, B, G left to right -- mirrored vertically on alternate columns.
The classic formula assumes R, G, B run left to right at 0, 1/3 and 2/3 of a pixel. Gen3 runs R, B, G at -0.31, 0.00 and +0.28, so applying the classic stride to a gen3 panel misplaces two channels of three. Measured on LKG-J00332, out of 48 views:
=========== ========== ============== =============== channel differs median offset worst offset =========== ========== ============== =============== R 100% 2 views 3 G 40% 0 views 1 B 100% 5 views 6 =========== ========== ============== ===============
Green happens to land near its assumed slot and survives; blue is assumed at 2/3 while it physically sits at the pixel centre, so it fares worst. The damage is therefore not a scrambled frame but a differential one -- a feature's red, green and blue arrive from viewing angles up to five views apart, which reads as colour fringing that worsens with parallax.
Two things break the effect completely and neither is detectable from code: the panel must run at its true native resolution (no HiDPI scaling -- one resample destroys the subpixel registration), and nothing may mix the RGB channels after weaving (Night Shift, True Tone, matrix ICC profiles), since each channel of each pixel carries a different view.
Typical usage::
from quiltwright import QUILT_PRESETS, Calibration, weave_quilt
from PIL import Image
import numpy as np
cal = Calibration.load("visual.json") # from the device / Bridge
spec = QUILT_PRESETS["16-landscape"]
quilt = np.asarray(Image.open("scene_qs8x6a1.77778.png").convert("RGB"))
native = weave_quilt(quilt, spec, cal)
Image.fromarray(native).save("scene_native.png")
Part of Quiltwright -- https://github.com/Flux-Frontiers/quiltwright Author: Eric G. Suchanek, PhD
Calibration(pitch, slope, center, dpi, screen_w, screen_h, flip_x=0.0, cell_pattern_mode=0, cells=tuple(), serial='')
dataclass
¶
Per-unit optical calibration of a Looking Glass panel.
Every panel is calibrated at the factory -- lens pitch, slant and phase
differ unit to unit, which is why a woven frame is registered to one
specific display. The values live in the device's visual.json
(readable via Looking Glass Bridge); :meth:load accepts that file
directly, {"value": ...} wrappers and all.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pitch
|
float
|
Raw lens pitch in lenticules per inch. |
required |
slope
|
float
|
Raw lens slant (run over rise, sign carries direction). |
required |
center
|
float
|
Phase offset of the lens array, in view-cycle units. |
required |
dpi
|
float
|
Panel pixel density. |
required |
screen_w
|
int
|
Native panel width in pixels. |
required |
screen_h
|
int
|
Native panel height in pixels. |
required |
flip_x
|
float
|
|
0.0
|
cell_pattern_mode
|
int
|
Subpixel-cell selection pattern (0-4). |
0
|
cells
|
tuple[SubpixelCell, ...]
|
Per-cell subpixel offsets; empty means the classic one-third-pixel RGB stripe layout. |
tuple()
|
serial
|
str
|
Device serial, kept for provenance in filenames/logs. |
''
|
processed_pitch
property
¶
Shader-space pitch: view cycles across the panel width.
LKG-Toolkit's Calibration.ProcessPitch -- the raw lenticules-
per-inch scaled to screen widths and foreshortened by the lens
slant.
processed_slope
property
¶
Shader-space tilt: view-phase change per unit of screen height.
LKG-Toolkit's Calibration.ProcessSlope, including the
flipImageX sign convention.
from_dict(raw)
classmethod
¶
Build a :class:Calibration from decoded visual.json data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw
|
dict
|
Parsed JSON dict, with or without |
required |
Returns:
| Type | Description |
|---|---|
Calibration
|
The calibration. |
Source code in src/quiltwright/weave.py
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | |
load(path)
classmethod
¶
Load a device visual.json calibration file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path
|
Path to the JSON file. |
required |
Returns:
| Type | Description |
|---|---|
Calibration
|
The calibration. |
Source code in src/quiltwright/weave.py
171 172 173 174 175 176 177 178 | |
SubpixelCell(r_offset_x, r_offset_y, g_offset_x, g_offset_y, b_offset_x, b_offset_y)
dataclass
¶
Physical offsets of one pixel's R, G and B emitters, in pixel units.
Gen3 panels do not lay their subpixels out in the classic vertical
R|G|B stripe: R and G sit on one row with B alone on the other, and the
arrangement mirrors vertically between neighbouring pixel columns. Each
cell describes one variant; CellPatternMode decides which cell a
given screen pixel uses.
weave_quilt(quilt, spec, cal, *, invert=False)
¶
Interleave a quilt into a native pre-lensed frame for one panel.
CPU port of Bridge's lenticular shader at its nearest-view setting
(filterMode 0). For every subpixel of the native frame the shader
phase (x + dx + (y + dy) * tilt) * pitch - center selects which
quilt view that subpixel physically radiates toward, and the value is
copied from that view. Nearest-neighbour everywhere: when the quilt
tile size is an integer multiple of the panel resolution (all official
presets are) sampling is exact, and no filtering ever mixes channels --
each channel of each output pixel carries a different view, so any
cross-channel blur is view crosstalk.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
quilt
|
ndarray
|
Quilt image |
required |
spec
|
QuiltSpec
|
Quilt tiling ( |
required |
cal
|
Calibration
|
The target panel's :class: |
required |
invert
|
bool
|
Reverse the view order. If the parallax fuses but reads inside-out (relief inverted, look-around backwards), the quilt's view sweep runs opposite to the panel's convention; this flips it. |
False
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Native frame |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the quilt does not divide evenly into the spec's tile grid. |
Source code in src/quiltwright/weave.py
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 | |