The Background section of the View tab (collapsed by default on phones) controls the uploaded picture that sits behind the lensed image; the flat field colour behind it is set in the Field row of the Colors section. Neither is a physical object: they are not lensed, contribute nothing to any deflection, and appear in the plain lensed-image view only, since the quantity maps colour every pixel themselves and Line art paints its own opaque palette background. The picture has one further use: it is the input to the Delensed image view (below), which treats it as an observation and traces it back to the source plane.
Field color is the flat colour of the empty field, and applies whether or not a picture is loaded (it also fills the margins a Contain fit leaves over, and shows through where a picture is hidden). It is stored as its dark-theme value and handed to the shader as-is, exactly like a source’s own colour, so the light-theme canvas inversion flips it along with everything else; that is what keeps the default black meaning “the plain empty field”, which the inversion turns white in light theme as it always has. The picker shows the complement in light theme, so what you see there is what appears on the plot.
Its main use is with a pasted-image source. The rectangular boundary of a pasted image is visible only because it is a step between the picture and the field, so matching the two makes it disappear. Match image does this in one click: it averages the colour in a ring a few pixels deep around the border of the pasted image (weighted by alpha, so transparent padding is skipped) and sets the field to it. A ring rather than the single outermost row, since photographs often carry a compression fringe or a stray dark line right at the boundary. The source’s own Edge blend (§6) then feathers whatever step is left. Both the field and the pasted image reach the screen through the same as-is path, so equal values match in either theme.
The picture is uploaded to the GPU as a texture, sampled in the same fragment shader, and composited underneath the lensed light, using the source brightness itself as the backdrop’s coverage:
\[I_\text{out} = \operatorname{clamp}\Big(I_\text{src}' + \beta_\text{bg} I_\text{bg}\,(1 - C),\ 0,\ 1\Big), \qquad C = \max\big(\max_c I_{\text{src},c}',\ A_\text{max}\big), \qquad I_\text{src}' = f(I_\text{src}),\]where $I_\text{src}’$ is the stretched-and-recombined source light of §6, with $f$ applied before the composite so it shapes only the lensed light and leaves a supplied picture’s own tones intact, and $A_\text{max}$ is the largest pasted-image footprint at that pixel (§6). Coverage $C$ is therefore brightness for the analytic sources, whose light genuinely fades to nothing, and footprint for a pasted image, which is opaque wherever it exists. A fully lit pixel hides the backdrop completely and an empty one shows it in full, so Brightness ($\beta_\text{bg} \in [0,2]$) fades the picture between the empty field ($0$) and its supplied tones ($1$), or past them, and cannot alter the lensed image itself.
Why not a plain sum? Simply adding $\beta_\text{bg} I_\text{bg}$ to every pixel is the physically natural composite (light behind the field adds to light from the source), but it makes the Brightness slider shift every pixel by the backdrop’s value, and where the sum saturates it also un-clips arcs as the backdrop is dimmed, so the lensed image visibly changes with a control that is supposed to affect only the backdrop. Weighting by $1 - \max_c I’_{\text{src},c}$ keeps the source’s own contribution untouched at every brightness setting; the backdrop still shows through partially lit pixels, as anything behind a semi-transparent layer must.
Framing. The image panel is always square, so a rectangular picture has to be framed. Fit picks the starting frame, then Zoom and Offset X / Y adjust it by hand. Writing $a = w/h$ for the image’s aspect ratio, the fit sets how much of the texture the viewport spans on each axis:
| Fit | Span $(s_x, s_y)$ | Result |
|---|---|---|
| Cover (crop) | $\big(\min(1, 1/a),\ \min(1, a)\big)$ | Fills the square; the long axis is cropped. Default. |
| Contain (fit) | $\big(\max(1, 1/a),\ \max(1, a)\big)$ | Whole image visible; the remainder stays empty field. |
| Stretch | $(1, 1)$ | Exactly one image on each axis; distorts a non-square picture. |
| Tile (mirrored) | as Contain, wrapped | Whole image visible, mirror-repeated to fill the rest. |
Zoom $k$ divides the span, so a larger zoom shows less texture and magnifies the picture; the offsets pan by a fraction of the panel (a positive offset moves the image right or up). Sampling outside $[0,1]^2$, which a Contain fit or a zoom below the fit produces, falls back to the field colour rather than smearing the edge pixels, except under Tile, where the texture’s mirrored wrap fills the plane instead. The screen-space sample point is therefore
\[\mathbf{uv} = \Big(\mathbf{v} - \tfrac{1}{2} - \tfrac{1}{2}\boldsymbol{\delta}\Big) \odot \frac{\mathbf{s}}{k} \;+\; \frac{1}{2},\]with $\mathbf{v}$ the viewport UV, $\boldsymbol{\delta}$ the two offsets, and $\odot$ elementwise multiplication; the vertical component is then flipped, since a texture’s first data row is the image’s top.
Anchoring. By default the backdrop is fixed to the viewport: it fills the panel whatever the field of view, so zooming the lens does not zoom the sky behind it. Zoom with FOV anchors it to the sky instead, scaling the span by $\text{fov}/\text{fov}_0$ where $\text{fov}_0$ is the field of view frozen when the image was loaded (or when the option was switched on). The backdrop then magnifies along with the lensed image, at the cost of running out of pixels when zoomed far in.
Pixel scale. A picture can instead be given a pixel scale $p$ (arcseconds per pixel of the image as supplied, before any downscale on load), set in the Delensed image view. It then replaces all of the framing above: the picture is placed on the sky centred on the optical axis, spanning $W p \times H p$ arcsec for a $W \times H$ pixel image, so the viewport covers $\text{fov}/(Wp)$ and $\text{fov}/(Hp)$ of the texture on the two axes whatever the zoom. The Fit, Zoom, Offset and Zoom-with-FOV controls are disabled while it has one, and a note in the section says so, with a Use Fit framing button that drops the pixel scale and hands the picture back to them (not offered in the Delensed view itself, which needs one). The point of putting the scale on the backdrop rather than only on the delensing is consistency: the lens model you line up against the arcs in the lensed-image view is then exactly the one the Delensed view traces through. Uploading a new picture, removing it, or Reset framing clears the pixel scale, since it described the picture it was set for (in the Delensed view a fresh one is seeded straight away, as below).
Light theme. The image canvas is CSS-inverted in light theme so the lensed light reads dark on white, which would show a photograph as a negative. The shader therefore pre-inverts the picture, $I_\text{bg} \to 1 - I_\text{bg}$, cancelling the canvas inversion: the backdrop looks the same in either theme while every other plot element (arcs, overlay ink, source colours) still flips with the theme exactly as it did before. The inversion is applied before the brightness scale, so $\beta_\text{bg} = 0$ is the plain empty field in both themes (black in dark, white in light) rather than black in one and white in the other. PNG capture and video recording replicate the same inversion, so exports match the screen.
An eye button beside the upload and remove buttons takes the backdrop off the plot without unloading it, so a figure can be captured with and without it.
All eight controls round-trip through the config file (bgFit, bgZoom, bgOffX, bgOffY, bgBright, bgSkyLock, bgHidden, bgPixel, the last being $0$ for no pixel scale), each validated on load: bgFit against the allowlist and the rest clamped to the ranges above, so a hand-edited file cannot push them out of bounds, and a file predating the feature simply falls back to the defaults. Images larger than 4096 px on their longest side are downscaled on load, since a backdrop is never displayed above canvas resolution.
A picture you upload yourself is not saved in the config, exactly as for pasted-image sources (which have their own image: key for site-shipped pictures, §6); nor is the sky-lock reference $\text{fov}_0$, which is re-frozen at the current field of view whenever an image loads. A config can name one of the backdrops shipped alongside the presets, through the optional bgImage key, which is how the Image recreation preset (§11) arrives with its own photograph in place. Loading a config with no bgImage drops a preset-supplied image but leaves a hand-uploaded one alone, so switching presets neither discards your own picture nor leaves the previous preset’s behind.
The Delensed image view (key U) runs the lens backwards on the background picture. The picture is treated as an observation of the image plane, each of its pixels is traced through the current lens model to the source plane at the reference $z_s$ (the zs ref of §4), and its colour is painted where its light came from. With the right lens model the multiple images of a source fall on top of each other and reassemble into one source; with a wrong one they land as offset copies, which is what makes the view useful for fitting by eye. Lenses can be dragged and reshaped while watching the copies merge.
Pixel scale. Mapping image pixels to angles needs the image’s angular scale, set in the View tab’s Delensed Image section (which appears in this view only) as arcseconds per pixel. It is the same pixel scale that frames the backdrop in the lensed-image view (above). A picture that has none when the view opens is given the scale its framing already implied when it was loaded, $p = \text{fov}_0\,k / \sqrt{s_x W \, s_y H}$ with $(s_x, s_y)$ the fit span and $k$ the zoom (the two axes agree for every fit but Stretch), so nothing moves at that field of view and the Image recreation preset (§11) delenses correctly as soon as the view is opened. Offsets are not carried over, since a picture with a pixel scale is centred on the axis.
Method. A per-pixel inversion would have to solve the lens equation for every source-plane pixel, and a per-pixel scatter of image pixels to $\boldsymbol{\beta}$ leaves holes wherever the magnification is high. Instead the image-plane field of the picture is covered by a grid of $512$ nodes along its long axis (fewer along the short one, in proportion), drawn as a triangle mesh whose vertices sit at their traced $\boldsymbol{\beta}$ and whose texture coordinates are their $\boldsymbol{\theta}$. Rasterising the mesh lays the picture into the source plane with the triangles stretching to fill the gaps, and inside the caustics, where the mesh folds over itself, several sheets land on the same pixel and are combined:
\[I(\boldsymbol{\beta}) = \frac{\sum_i \lvert\mu_i\rvert\, I_\text{obs}(\boldsymbol{\theta}_i)}{\sum_i \lvert\mu_i\rvert},\]the sum running over the images $\boldsymbol{\theta}_i$ of $\boldsymbol{\beta}$ inside the picture. Weighting by $\lvert\mu\rvert$ makes this the average over all image pixels that land on a source pixel, which is what a scatter would compute: a more magnified image samples the source more finely, so it contributes more pixels. Surface brightness is conserved by lensing, so for a correct model every term agrees and the weights only decide how disagreements are settled.
Implementation. A first pass reuses the main fragment shader’s multiplane tracer to write, for every grid node, $\boldsymbol{\beta}$, $\lvert\mu\rvert$, and the short axis of the image patch a unit of the source plane maps onto ($1/\sigma_\text{max}$ of the Jacobian, by central differences with a step of a quarter node) into a float texture. The mesh pass draws two triangles per grid cell with no vertex buffer at all (each vertex reads its node from that texture by gl_VertexID) and accumulates $\lvert\mu\rvert I$ and $\lvert\mu\rvert$ into a half-float target by additive blending; a resolve pass divides them out. Both targets need WebGL2 float render targets (EXT_color_buffer_float, available almost everywhere), and the section says so on a GPU without them. The magnification is interpolated from the nodes rather than taken from each triangle’s screen-space derivatives, because the cells are often smaller than a pixel, most of all on a magnified arc, where the rasteriser’s sub-pixel vertex snapping makes those derivatives noisy by tens of percent. The texture’s mip level is set explicitly for the same reason, from the patch’s short axis: along an arc a source pixel covers a long thin strip of the image, every pixel of which comes from that same source pixel, so only the width across the arc decides how much detail is averaged away.
Two thresholds keep the mesh honest. Triangles with $\lvert\mu\rvert \lt 0.05$ are dropped: these are the most demagnified regions, chiefly around a singular lens centre, which map a handful of image pixels across a wide patch of the source plane. Their weight is already small, but where nothing else covers a source pixel they would fill it with a smear of whatever sits at the lens centre. The weight is capped at $\lvert\mu\rvert = 100$ so the thin, nearly degenerate triangles straddling a critical curve cannot swamp a pixel. Source-plane pixels that no part of the picture reaches show the empty field.
As a check, a Gaussian source rendered through an SIE and fed back in as the background is recovered to about $0.1\%$ rms of its peak, its centre within a pixel; the residual sits along the radial caustic, where the poorly resolved central image enters or leaves the average.
Display. The view is a source-plane map, like the image-number view (§4), so the overlay drops the image-plane features (critical curves, point-source image dots and their time delays, stretch whiskers) and keeps the caustics, lens and mask markers, and ruler. Unlike the image-number map it keeps the sources as well, since a source marker sits at its own source-plane position and can be laid over its reconstruction. The picture’s own tones are shown in either theme, scaled by the backdrop’s Brightness and clipped at white. The eye toggle does not apply here, where the picture is the data rather than decoration. With no picture loaded the image says where to add one.
Masks. A mask cuts an elliptical patch out of the picture before it is delensed, typically over the lens galaxy, whose own light would otherwise be traced back with everything else and spread over the source plane (mostly around the radial caustic), or over a foreground star. Masked pixels contribute nothing: a source-plane point whose images all fall under masks becomes a hole showing the empty field, and one with an unmasked image is rebuilt from that alone. A mask is a fourth object type, placed with the mask tool (key 4), which is offered while any tab shows this view (on a phone, while it is showing); it has a position, a Size (the semi-major axis $r$), an axis ratio $q$, and a position angle $\varphi$, and covers $x_r^2 + (y_r/q)^2 \le r^2$ in the same rotated frame as the analytic sources (§6). Its position is an angle on the observed picture, not in any source plane, so the plane it was placed on plays no part. Masks are drawn as gray circles in every view except line art, so one can be selected and edited from anywhere; in the lensed-image view the outline and a light fill also show the patch it covers, which is the place to line one up against the lens galaxy. Only the delensed view is affected by them. They are tested per pixel at the fragment’s image-plane position in the mesh pass, so the cut is exact whatever the mesh resolution, and up to 16 are applied (the section says if more are on). Like any object they can be hidden, dragged, copied, undone, animated, and saved in a config.
Heads up. Light from the lens galaxy itself is not removed unless a mask covers it: it is traced back like everything else and spread over the source plane, mostly around the radial caustic. The PSF is not undone either, so the reconstruction is as blurred as the observation, and differently so in each image, since each is magnified by a different amount.