Picture a panorama too large for one texture being navigated
A 22 927 × 8966 Lightroom panorama of a glacier, opened in develop: the wheel in from fit to the peaks, a slow pan along the ridge at that zoom, back out, then 1:1 with a double-click and fit with another. The scene borrows the file from outside the demo library, copies it into a panorama folder, restarts so the scan finds it, and deletes it and the sidecar the app writes beside it before restarting again; it is registered last so no other scene sees it. The film is fine ice and rock in every frame and was 22 MB at the usual 960 px and ten frames a second, so a scene can now name its own GIF size (GIF_SIZE, read by record.sh through `scenes.py gif`); this one is 640 px at eight, 8.7 MB.
This commit is contained in:
@@ -359,6 +359,8 @@ It opens in develop all the same, on a reduced copy that is drawn from the
|
|||||||
full resolution wherever you zoom in, and it exports at full size. The same
|
full resolution wherever you zoom in, and it exports at full size. The same
|
||||||
goes for a panorama Lightroom stitched and saved as a DNG.
|
goes for a panorama Lightroom stitched and saved as a DNG.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -400,6 +400,7 @@ preview and in the composite.</p>
|
|||||||
It opens in develop all the same, on a reduced copy that is drawn from the
|
It opens in develop all the same, on a reduced copy that is drawn from the
|
||||||
full resolution wherever you zoom in, and it exports at full size. The same
|
full resolution wherever you zoom in, and it exports at full size. The same
|
||||||
goes for a panorama Lightroom stitched and saved as a DNG.</p>
|
goes for a panorama Lightroom stitched and saved as a DNG.</p>
|
||||||
|
<figure><img loading="lazy" src="media/giant-pano.gif" alt="A 22 927 × 8966 panorama of a glacier at dusk: zoomed from the whole frame into the peaks, panned along the ridge and back out, then 1:1 on the peaks with a double-click"><figcaption>A 22 927 × 8966 panorama of a glacier at dusk: zoomed from the whole frame into the peaks, panned along the ridge and back out, then 1:1 on the peaks with a double-click</figcaption></figure>
|
||||||
<figure><img loading="lazy" src="media/panorama.gif" alt="Twelve frames aligned, the first left out and brought back, the projections tried, and the border filled"><figcaption>Twelve frames aligned, the first left out and brought back, the projections tried, and the border filled</figcaption></figure>
|
<figure><img loading="lazy" src="media/panorama.gif" alt="Twelve frames aligned, the first left out and brought back, the projections tried, and the border filled"><figcaption>Twelve frames aligned, the first left out and brought back, the projections tried, and the border filled</figcaption></figure>
|
||||||
<figure><img loading="lazy" src="media/panorama-aligned.png" alt="The alignment on a cylinder, each frame outlined where it landed"><figcaption>The alignment on a cylinder, each frame outlined where it landed</figcaption></figure>
|
<figure><img loading="lazy" src="media/panorama-aligned.png" alt="The alignment on a cylinder, each frame outlined where it landed"><figcaption>The alignment on a cylinder, each frame outlined where it landed</figcaption></figure>
|
||||||
<figure><img loading="lazy" src="media/panorama-filled.png" alt="The same, with the ragged border filled by the model rather than cropped away"><figcaption>The same, with the ragged border filled by the model rather than cropped away</figcaption></figure>
|
<figure><img loading="lazy" src="media/panorama-filled.png" alt="The same, with the ragged border filled by the model rather than cropped away"><figcaption>The same, with the ragged border filled by the model rather than cropped away</figcaption></figure>
|
||||||
|
|||||||
Binary file not shown.
@@ -47,6 +47,12 @@ manual does not show; it reads two files and runs in CI. `--changed`
|
|||||||
re-records a scene when one of its sources, or its own code, has changed
|
re-records a scene when one of its sources, or its own code, has changed
|
||||||
since the commit that last touched its pictures.
|
since the commit that last touched its pictures.
|
||||||
|
|
||||||
|
GIFs are made at 960 px and ten frames a second. A scene whose film does
|
||||||
|
not compress at that size says otherwise in `GIF_SIZE`, which `record.sh`
|
||||||
|
reads through `scenes.py gif NAME`. `giant_pano` borrows a 500 MB panorama
|
||||||
|
from outside the library (`DR_GIANT_DNG`, by default
|
||||||
|
`/var/tmp/dr-dng/IMG_4181-Pano.dng`) and removes it again, so it runs last.
|
||||||
|
|
||||||
Two things the scripts know that are not obvious: a Slint `TouchArea` wants
|
Two things the scripts know that are not obvious: a Slint `TouchArea` wants
|
||||||
a held press, not xdotool's `click`; and the app is driven on Xvfb rather
|
a held press, not xdotool's `click`; and the app is driven on Xvfb rather
|
||||||
than the desktop because under XWayland at scale 2 a pointer warp lands at
|
than the desktop because under XWayland at scale 2 a pointer warp lands at
|
||||||
|
|||||||
@@ -150,7 +150,8 @@ python3 "$scenes" record "$media" "${@:-all}"
|
|||||||
# GIFs for the page; the MP4s are working files and are not kept.
|
# GIFs for the page; the MP4s are working files and are not kept.
|
||||||
for mp4 in "$media"/*.mp4; do
|
for mp4 in "$media"/*.mp4; do
|
||||||
[ -e "$mp4" ] || continue
|
[ -e "$mp4" ] || continue
|
||||||
"$here/gif.sh" "$mp4" "${mp4%.mp4}.gif" 960 10
|
read -r width fps < <(python3 "$scenes" gif "$(basename "${mp4%.mp4}")")
|
||||||
|
"$here/gif.sh" "$mp4" "${mp4%.mp4}.gif" "$width" "$fps"
|
||||||
rm "$mp4"
|
rm "$mp4"
|
||||||
done
|
done
|
||||||
find "$media" -name '*.png' -newer "$stamp" -exec optipng -quiet -o2 {} + || true
|
find "$media" -name '*.png' -newer "$stamp" -exec optipng -quiet -o2 {} + || true
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
scenes.py changed the scenes whose sources changed since
|
scenes.py changed the scenes whose sources changed since
|
||||||
their pictures were last committed
|
their pictures were last committed
|
||||||
scenes.py list every scene, its pictures and sources
|
scenes.py list every scene, its pictures and sources
|
||||||
|
scenes.py gif NAME the width and frame rate NAME.gif is
|
||||||
|
made at (960 10 unless a scene says)
|
||||||
|
|
||||||
Each scene drives the running app through `drive.py` and leaves the
|
Each scene drives the running app through `drive.py` and leaves the
|
||||||
pictures it names in OUT_DIR: PNGs, and MP4s that `record.sh` turns into
|
pictures it names in OUT_DIR: PNGs, and MP4s that `record.sh` turns into
|
||||||
@@ -1527,6 +1529,99 @@ def duplicates():
|
|||||||
pause(3)
|
pause(3)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# --- a photograph larger than one texture -------------------------------------
|
||||||
|
# Last on purpose: it adds a file to the library for its own length only, and
|
||||||
|
# no scene after it should find that file there.
|
||||||
|
GIANT_DNG = os.environ.get('DR_GIANT_DNG', '/var/tmp/dr-dng/IMG_4181-Pano.dng')
|
||||||
|
GIANT = 'IMG_4181-Pano' # a 22927 × 8966 Lightroom panorama of a glacier at dusk
|
||||||
|
|
||||||
|
# A GIF that is fine rock and ice in every frame does not compress; at the
|
||||||
|
# usual 960 px and ten frames a second this one was 22 MB, and 11 at 720.
|
||||||
|
GIF_SIZE = {'giant-pano': (640, 8)}
|
||||||
|
|
||||||
|
|
||||||
|
def restart_on_library():
|
||||||
|
dr.stop()
|
||||||
|
dr.launch([])
|
||||||
|
dr.wait_ready()
|
||||||
|
pause(5)
|
||||||
|
at_library() # it reopens on the photograph it closed on
|
||||||
|
pause(3)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_giant(folder, made_folder):
|
||||||
|
for f in os.listdir(folder) if os.path.isdir(folder) else []:
|
||||||
|
if f.startswith(f'{GIANT}.'):
|
||||||
|
os.remove(os.path.join(folder, f))
|
||||||
|
if made_folder and os.path.isdir(folder) and not os.listdir(folder):
|
||||||
|
os.rmdir(folder)
|
||||||
|
|
||||||
|
|
||||||
|
@scene(media=['giant-pano.gif'],
|
||||||
|
sources=DEVELOP_SRC + ['ui/dr-ui/src/develop/session.rs', 'ui/dr-ui/src/develop/render.rs',
|
||||||
|
'core/dr-gpu/src/demosaic.rs', 'core/dr-pipeline/src/tiles.rs'])
|
||||||
|
def giant_pano():
|
||||||
|
"""A linear DNG too large for one GPU texture, opened and navigated: in
|
||||||
|
from fit on the peaks at its left as far as the wheel goes (sixteen
|
||||||
|
times fit, short of 1:1 on a frame this wide), a slow pan along the
|
||||||
|
ridge, back out, then 1:1 on the peaks with a double-click and fit with
|
||||||
|
another. The file is not in the demo library (it is 500 MB): it is
|
||||||
|
copied into a `panorama` folder for this scene, the app restarted so the
|
||||||
|
scan catalogues it, and the copy and the sidecar the app writes beside
|
||||||
|
it deleted, and the app restarted again, afterwards. DR_GIANT_DNG names it."""
|
||||||
|
folder = os.path.join(DEMO_LIBRARY, 'panorama')
|
||||||
|
copy = os.path.join(folder, f'{GIANT}.dng')
|
||||||
|
made_folder = not os.path.isdir(folder)
|
||||||
|
try:
|
||||||
|
os.makedirs(folder, exist_ok=True)
|
||||||
|
shutil.copy2(GIANT_DNG, copy)
|
||||||
|
restart_on_library()
|
||||||
|
at_library()
|
||||||
|
grid_top()
|
||||||
|
for _ in range(60): # until the scan has catalogued it
|
||||||
|
if dr.present(f'{GIANT}@ListItem'):
|
||||||
|
break
|
||||||
|
pause(2)
|
||||||
|
dr.click(*cell(GIANT))
|
||||||
|
dr.wait_for(f'{GIANT}.dng', 120)
|
||||||
|
wait_canvas(300)
|
||||||
|
pause(8) # the reduced copy, drawn
|
||||||
|
a = dr.photo(0.14, 0.22) # the snow peaks at the left
|
||||||
|
rec('giant-pano')
|
||||||
|
pause(1.5)
|
||||||
|
for _ in range(10): # the wheel's limit, 16 times fit
|
||||||
|
wheel(-1, *a)
|
||||||
|
pause(0.7)
|
||||||
|
pause(1.5)
|
||||||
|
cx, cy = mid(dr.rect('id:canvas-image'))
|
||||||
|
for _ in range(3): # along the ridge, rightwards
|
||||||
|
dr.drag(cx + 350, cy, cx - 350, cy, 90)
|
||||||
|
pause(1.0)
|
||||||
|
for _ in range(10):
|
||||||
|
wheel(1, cx, cy)
|
||||||
|
pause(0.5)
|
||||||
|
pause(1.5)
|
||||||
|
# 1:1 is past the wheel's limit on a frame this wide: a double-click
|
||||||
|
# reaches it, on the peaks the film started from, and another fits.
|
||||||
|
b = dr.photo(0.2, 0.2)
|
||||||
|
dr.move(*b)
|
||||||
|
pause(0.3)
|
||||||
|
dr.x('click', '--repeat', 2, '--delay', 80, 1)
|
||||||
|
pause(3.0)
|
||||||
|
dr.x('click', '--repeat', 2, '--delay', 80, 1)
|
||||||
|
pause(2.0)
|
||||||
|
cut()
|
||||||
|
finally:
|
||||||
|
# Back to the grid first, or the next start reopens the photograph
|
||||||
|
# it closed on and writes its sidecar again.
|
||||||
|
at_library()
|
||||||
|
dr.stop()
|
||||||
|
remove_giant(folder, made_folder)
|
||||||
|
restart_on_library()
|
||||||
|
remove_giant(folder, made_folder)
|
||||||
|
|
||||||
|
|
||||||
# --- running them -----------------------------------------------------------
|
# --- running them -----------------------------------------------------------
|
||||||
def order(names):
|
def order(names):
|
||||||
"""`names` with their prerequisites, each once, prerequisites first."""
|
"""`names` with their prerequisites, each once, prerequisites first."""
|
||||||
@@ -1677,6 +1772,8 @@ def main():
|
|||||||
elif cmd == 'changed':
|
elif cmd == 'changed':
|
||||||
for name, why in changed():
|
for name, why in changed():
|
||||||
print(f'{name}\t{why}')
|
print(f'{name}\t{why}')
|
||||||
|
elif cmd == 'gif':
|
||||||
|
print(*GIF_SIZE.get(rest[0], (960, 10)))
|
||||||
elif cmd == 'list':
|
elif cmd == 'list':
|
||||||
for s in SCENES.values():
|
for s in SCENES.values():
|
||||||
after = f' (after {", ".join(s.after)})' if s.after else ''
|
after = f' (after {", ".join(s.after)})' if s.after else ''
|
||||||
|
|||||||
Reference in New Issue
Block a user