diff --git a/examples/pi-heif/README.md b/examples/pi-heif/README.md
new file mode 100644
index 0000000..9e048b0
--- /dev/null
+++ b/examples/pi-heif/README.md
@@ -0,0 +1,18 @@
+# pi-heif Examples
+
+Each sub-directory contains a self-contained example. The order in
+which the examples are to appear is specified in `order.json` (an
+array of directory names in the expected order).
+
+In each example directory you'll find:
+
+* `config.toml` - must conform to the specification outlined here:
+ https://docs.pyscript.net/latest/user-guide/configuration/ This is
+ parsed and ultimately turned into a JSON representation as part of
+ the package's API object.
+* `setup.py` - Python code for contextual and environmental setup,
+ NOT SEEN BY THE END USER, but is run before the `code.py` code is
+ evaluated. Allows us to create useful (IPython) shims, avoid
+ repeating boilerplate and whatnot.
+* `code.py` - the actual code added to the editor which forms the
+ practical example of using the package.
diff --git a/examples/pi-heif/heif_hdr_and_thumbnails/code.py b/examples/pi-heif/heif_hdr_and_thumbnails/code.py
new file mode 100644
index 0000000..1d9e268
--- /dev/null
+++ b/examples/pi-heif/heif_hdr_and_thumbnails/code.py
@@ -0,0 +1,87 @@
+# ---------------------------------------------------------------------
+# Two pi-heif features that are easy to miss:
+# 1. `convert_hdr_to_8bit=False` keeps 10/12-bit HDR data intact, so
+# you can hand off 16-bit arrays to libraries like OpenCV.
+# 2. `bgr_mode=True` returns BGR-ordered channels, which is what
+# OpenCV expects natively.
+# We'll demonstrate the option flags and visualise the difference
+# between an 8-bit and a simulated 16-bit decode.
+# ---------------------------------------------------------------------
+import numpy as np
+import matplotlib.pyplot as plt
+from pi_heif import register_heif_opener
+
+register_heif_opener()
+
+
+heading("HDR decoding options")
+note(
+ "Calling open_heif with "
+ "convert_hdr_to_8bit=False preserves the original "
+ "bit depth (10 or 12 bits) as a uint16 array. Combined with "
+ "bgr_mode=True, this is the recommended path for "
+ "feeding HEIF directly into OpenCV's cv2.imwrite."
+)
+
+# A code snippet readers can adapt verbatim.
+example_call = """heif_file = pi_heif.open_heif(
+ "image.heic",
+ convert_hdr_to_8bit=False, # keep 10/12-bit HDR data
+ bgr_mode=True, # OpenCV-friendly channel order
+)
+np_array = np.asarray(heif_file) # uint16 if HDR, uint8 otherwise
+print(heif_file.mode, heif_file.bit_depth, np_array.dtype)"""
+display(HTML(f"
{example_call}"), append=True)
+
+# Simulate the visual difference between an 8-bit decode (clipped
+# highlights) and a 16-bit HDR decode (full dynamic range).
+height, width = 160, 320
+xx = np.linspace(0, 1, width)[None, :].repeat(height, axis=0)
+
+# Underlying scene with values that exceed the 8-bit range.
+hdr_scene = (xx ** 0.5) * 65535 # smooth gradient up to 16-bit max
+
+eight_bit = np.clip(hdr_scene / 256, 0, 255).astype(np.uint8)
+sixteen_bit = hdr_scene.astype(np.uint16)
+
+# Tone-map the 16-bit version for display, the way an HDR pipeline
+# would: a simple gamma curve preserves shadow detail.
+tone_mapped = (
+ (sixteen_bit / 65535) ** (1 / 2.2) * 255
+).astype(np.uint8)
+
+fig, axes = plt.subplots(1, 2, figsize=(10, 3))
+axes[0].imshow(np.dstack([eight_bit] * 3))
+axes[0].set_title("8-bit decode (convert_hdr_to_8bit=True)")
+axes[0].axis("off")
+
+axes[1].imshow(np.dstack([tone_mapped] * 3))
+axes[1].set_title("16-bit HDR decode, tone-mapped")
+axes[1].axis("off")
+fig.tight_layout()
+display(fig, append=True)
+
+heading("Thumbnails embedded in HEIF files")
+note(
+ "Many HEIF files carry one or more pre-baked thumbnails. "
+ "pi-heif exposes them via heif_file.thumbnails "
+ "(list of sizes) and heif_file.get_thumbnail(size). "
+ "This is much faster than decoding the full image when you only "
+ "need a preview."
+)
+
+thumbnail_snippet = """heif_file = pi_heif.open_heif("photo.heic")
+for size in heif_file.thumbnails:
+ thumb = heif_file.get_thumbnail(size)
+ Image.frombytes(thumb.mode, thumb.size, thumb.data).save(
+ f"thumb_{size}.png"
+ )"""
+display(HTML(f"{thumbnail_snippet}"), append=True)
+
+note(
+ "From here, common next steps are: applying Pillow filters to the "
+ "decoded image, saving as PNG/JPEG via Pillow, or piping the "
+ "NumPy array into OpenCV or scikit-image. See "
+ ""
+ "pillow-heif.readthedocs.io for the full reference."
+)
diff --git a/examples/pi-heif/heif_hdr_and_thumbnails/config.toml b/examples/pi-heif/heif_hdr_and_thumbnails/config.toml
new file mode 100644
index 0000000..ae283ae
--- /dev/null
+++ b/examples/pi-heif/heif_hdr_and_thumbnails/config.toml
@@ -0,0 +1 @@
+packages = ["pi-heif", "Pillow", "numpy", "matplotlib"]
diff --git a/examples/pi-heif/heif_hdr_and_thumbnails/setup.py b/examples/pi-heif/heif_hdr_and_thumbnails/setup.py
new file mode 100644
index 0000000..38a3e24
--- /dev/null
+++ b/examples/pi-heif/heif_hdr_and_thumbnails/setup.py
@@ -0,0 +1,20 @@
+"""Setup for the third cell: same names as before, no IPython shim."""
+import js
+from pyscript import window, HTML, display as _display
+
+js.alert = window.alert
+
+
+def display(*args, **kwargs):
+ return _display(
+ *args, **kwargs, target=__pyscript_display_target__,
+ )
+
+
+def heading(text, level=2):
+ display(HTML(f"{text}
"), append=True) + diff --git a/examples/pi-heif/heif_pillow_plugin/code.py b/examples/pi-heif/heif_pillow_plugin/code.py new file mode 100644 index 0000000..240b086 --- /dev/null +++ b/examples/pi-heif/heif_pillow_plugin/code.py @@ -0,0 +1,54 @@ +""" +Reading HEIF (High Efficiency Image Format) images with pi-heif. + +HEIF is the container format used by modern iPhones and many cameras +for photos. pi-heif provides a decoder so you can open these files +just like JPEG or PNG. + +We don't have a real .heic file lying around in the browser, so we'll +build a plain Pillow image as a stand-in and walk through the API +you'd use on a real photo. + +Docs: https://pillow-heif.readthedocs.io/ +""" +from IPython.core.display import display, HTML +# Example-specific imports below. +import io +from PIL import Image, ImageDraw +import pi_heif +from pi_heif import register_heif_opener + + +# Register pi-heif as a Pillow plugin. After this call, Image.open +# transparently understands .heic and .heif files. +register_heif_opener() + +heading("Opening a HEIF image with Pillow") +note( + "Onceregister_heif_opener() has been called, "
+ "Pillow's Image.open handles HEIF files exactly like "
+ "any other format. On a real photo you'd simply write "
+ "Image.open('photo.heic'); here we build a Pillow "
+ "image directly as a stand-in for the decoded result."
+)
+
+# Stand-in for a decoded HEIF photo. In real code this would be the
+# return value of Image.open("photo.heic").
+demo = Image.new("RGB", (240, 160), "lightsteelblue")
+draw = ImageDraw.Draw(demo)
+draw.rectangle((20, 20, 220, 140), outline="navy", width=3)
+draw.text((40, 70), "HEIF demo image", fill="navy")
+
+note(f"Pillow image mode: {demo.mode}, size: {demo.size}")
+display(demo, append=True)
+
+# Ask Pillow which extensions now map to the HEIF/HEIC formats. This
+# is the most reliable way to answer "will Pillow open my file?",
+# because it reflects what register_heif_opener() actually wired up.
+# (Avoid reaching into pi_heif.options for codec lists — those
+# attributes are internal and shift between versions.)
+heif_exts = sorted(
+ ext for ext, fmt in Image.registered_extensions().items()
+ if fmt in ("HEIF", "HEIC")
+)
+note(f"File extensions Pillow now routes to pi-heif: {heif_exts}")
\ No newline at end of file
diff --git a/examples/pi-heif/heif_pillow_plugin/config.toml b/examples/pi-heif/heif_pillow_plugin/config.toml
new file mode 100644
index 0000000..4a525bc
--- /dev/null
+++ b/examples/pi-heif/heif_pillow_plugin/config.toml
@@ -0,0 +1 @@
+packages = ["pi-heif", "Pillow"]
diff --git a/examples/pi-heif/heif_pillow_plugin/setup.py b/examples/pi-heif/heif_pillow_plugin/setup.py
new file mode 100644
index 0000000..0cc7a6f
--- /dev/null
+++ b/examples/pi-heif/heif_pillow_plugin/setup.py
@@ -0,0 +1,41 @@
+"""
+Shim IPython's display API onto PyScript so example code written in a
+Jupyter/IPython idiom runs unmodified in the browser.
+"""
+
+import sys
+import types
+import js
+from pyscript import window, HTML, display as _display
+
+js.alert = window.alert
+
+
+def display(*args, **kwargs):
+ return _display(
+ *args, **kwargs, target=__pyscript_display_target__,
+ )
+
+
+ipython = types.ModuleType("IPython")
+core = types.ModuleType("IPython.core")
+core_display = types.ModuleType("IPython.core.display")
+core_display.display = display
+core_display.HTML = HTML
+ipython.core = core
+core.display = core_display
+ipython.get_ipython = lambda: None
+ipython.display = core_display
+sys.modules["IPython"] = ipython
+sys.modules["IPython.core"] = core
+sys.modules["IPython.core.display"] = core_display
+sys.modules["IPython.display"] = core_display
+
+
+def heading(text, level=2):
+ display(HTML(f"{text}
"), append=True) + diff --git a/examples/pi-heif/heif_to_numpy/code.py b/examples/pi-heif/heif_to_numpy/code.py new file mode 100644 index 0000000..6fad9ee --- /dev/null +++ b/examples/pi-heif/heif_to_numpy/code.py @@ -0,0 +1,81 @@ +# --------------------------------------------------------------------- +# pi-heif exposes the buffer protocol, so a decoded HEIF image can be +# turned directly into a NumPy array. This is the idiomatic way to feed +# HEIF photos into OpenCV, scikit-image, or any other array-based +# pipeline. +# --------------------------------------------------------------------- +import numpy as np +import matplotlib.pyplot as plt +import pi_heif +from pi_heif import register_heif_opener + +register_heif_opener() + +heading("From HeifFile to NumPy array") +note( + "pi_heif.open_heif(...) returns a HeifFile "
+ "whose first image is exposed via the buffer protocol. Wrapping it "
+ "in np.asarray gives you a (height, width, channels) "
+ "array with no extra copy. We'll simulate this by creating an array "
+ "directly and inspecting it as if it had come from HEIF decoding."
+)
+
+# Synthetic 200x300 RGB image: a horizontal gradient with a circle.
+height, width = 200, 300
+yy, xx = np.mgrid[0:height, 0:width]
+red = (xx * 255 / width).astype(np.uint8)
+green = (yy * 255 / height).astype(np.uint8)
+blue = np.full_like(red, 80)
+
+# Add a brighter disc in the middle to give the eye something to track.
+cy, cx = height // 2, width // 2
+disc = (yy - cy) ** 2 + (xx - cx) ** 2 < 50 ** 2
+red[disc] = 255
+green[disc] = 240
+blue[disc] = 200
+
+decoded = np.dstack([red, green, blue])
+
+note(
+ f"Array shape: {decoded.shape}, "
+ f"dtype: {decoded.dtype}. "
+ "This is exactly the layout you would get from "
+ "np.asarray(pi_heif.open_heif('photo.heic'))."
+)
+
+# Show channel statistics, the kind of summary you'd compute right
+# after decoding.
+channel_names = ["R", "G", "B"]
+fig, axes = plt.subplots(1, 2, figsize=(10, 4))
+
+axes[0].imshow(decoded)
+axes[0].set_title("Decoded image")
+axes[0].axis("off")
+
+for i, name in enumerate(channel_names):
+ axes[1].hist(
+ decoded[..., i].ravel(),
+ bins=32,
+ alpha=0.5,
+ label=name,
+ color=name.lower().replace("r", "red").replace("g", "green").replace("b", "blue"),
+ )
+axes[1].set_title("Per-channel histogram")
+axes[1].set_xlabel("Pixel value")
+axes[1].set_ylabel("Count")
+axes[1].legend()
+fig.tight_layout()
+display(fig, append=True)
+
+# The HeifFile object also carries useful metadata. Show the kinds of
+# attributes you can read from it.
+note("Typical attributes available on a HeifFile:")
+display(HTML(
+ "heif_file.size — (width, height) tupleheif_file.mode — e.g. 'RGB', 'RGBA', 'BGR;16'heif_file.has_alpha — boolheif_file.bit_depth — 8, 10, or 12heif_file.info['exif'] — raw EXIF bytes if present{text}
"), append=True) + + diff --git a/examples/pi-heif/order.json b/examples/pi-heif/order.json new file mode 100644 index 0000000..f624463 --- /dev/null +++ b/examples/pi-heif/order.json @@ -0,0 +1,5 @@ +[ + "heif_pillow_plugin", + "heif_to_numpy", + "heif_hdr_and_thumbnails" +]