Masks ----- .. versionadded:: 1.5 The image-mask system is entirely new in 1.5. A mask lets an operation affect only part of an image, with a soft (feathered) boundary. Ground truth: :file:`src/core/masks.{c,h}`, the ``mask_t`` type and the mask members of ``struct ffit`` in :file:`src/core/siril.h`, ``blend_fits_with_mask`` in :file:`src/core/processing.c`, and the mask editor UI in :file:`src/gui-gtk4/masks_gui.c`. What a mask is ============== A mask is a single-channel alpha map the same width and height as the image it is attached to. The type is tiny (:file:`siril.h`):: typedef struct _mask_t { uint8_t bitpix; // 8, 16 or 32 void *data; // rx*ry samples: uint8_t, uint16_t or float } mask_t; ``bitpix`` selects the sample depth: **8-bit** (``uint8_t``, 0–255), **16-bit** (``uint16_t``, 0–65535) or **32-bit** (``float``, 0.0–1.0). The default depth is returned by ``get_default_mask_bitpix``. In all cases the value is an alpha weight: 0 = the operation has no effect at that pixel, full scale = the operation applies fully. How a mask attaches to a fits ============================= Two members of ``struct ffit`` carry the mask: - ``mask_t *mask`` — the mask itself, or NULL. - ``gboolean mask_active`` — whether the mask is currently in effect. Toggle it through ``set_mask_active`` (which also notifies the GUI), never by writing the field directly. The mask is owned by the fits: ``free_mask`` releases it, and the fits's ``rwlock`` covers it like the pixel data. Like the rest of a public fits, it is handled under the worker locking — you do not lock it yourself from a hook. How a mask modulates an operation ================================= When you submit a single-image operation with ``mask_aware = TRUE`` (see :ref:`genericworkers:generic_image_worker`) and the fits has an active mask, the worker blends the hook's output against the original pixels through the mask (``blend_fits_with_mask``). Per pixel:: out = original + alpha * (processed - original) where ``alpha`` is the normalised mask value. The blend is implemented for all three mask depths and both image types (``DATA_USHORT`` and ``DATA_FLOAT``). .. warning:: If your operation should respect masks but you leave ``mask_aware = FALSE``, the worker installs the processed pixels **without** blending — a silent wrong result. Conversely, marking an op ``mask_aware`` that has no sensible partial-application semantics is misleading; only set it for pixel operations where partial application is meaningful. Writing a mask-producing feature ================================ Mask creation and editing go through ``generic_mask_worker`` with a ``mask_hook`` (``int (*)(struct generic_mask_args *)``). The existing hooks in :file:`masks.h` are your templates — for example ``mask_from_stars_hook``, ``mask_from_lum_hook``, ``mask_from_color_hook``, ``mask_binarize_hook``, ``mask_blur_hook``, ``mask_feather_hook``, ``mask_invert_hook`` — each paired with a ``*_log`` function for the HISTORY/undo text and a small ``user`` struct (with a leading ``destructor``) such as ``mask_from_stars_data``. Set ``mask_creation = TRUE`` for a hook that *creates* a mask; the worker then calls ``set_mask_active`` on completion so the new mask takes effect. The lower-level builders (``mask_create_from_stars``, ``mask_create_from_luminance``, ``mask_create_from_channel``, ``mask_thresh``/``mask_binarize``, ``mask_feather``, ``mask_scale``, ``mask_change_bitpix`` …) do the actual work and can be reused. Masks are also reachable from commands (e.g. a ``mask_from_stars`` command in :file:`command.c`) and from user polygons via ``set_poly_in_mask``. Display ======= The mask is shown as a colour **tint** overlay on the viewports. Redraws go through ``gui_iface``: - ``gui_iface.queue_redraw_mask(remap_tints)`` / ``redraw_mask_idle`` — request a mask redraw from any thread. Pass ``remap_tints = TRUE`` when the mask **data** changed (so the tinted viewports must be recomputed); FALSE when only visibility toggled and the tints are already current. - ``gui_iface.on_mask_state_changed`` — called when a mask is removed or its active state changes (the mask worker's completion idle calls this). - ``gui_iface.update_mask_enable(state)`` — syncs the enable toggle button. Undo and geometry ================= A mask edit on ``gfit`` (non-command) saves an undo state before running, so the previous mask can be restored. **Geometry-changing operations must keep the mask dimensionally consistent with the image.** Resize, rotate, crop and binning in :file:`src/algos/geometry.c` transform the mask alongside the pixels (``resize_mask`` / ``bin_mask`` / region copy) and only free it (``free_mask`` + ``fit->mask = NULL`` + ``gui_iface.on_mask_state_changed``) if that transform fails. If you add a new geometry operation, follow the same contract: transform the mask to the new geometry, or drop it — never leave a mask whose dimensions no longer match its image.