Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 54 additions & 27 deletions src/views/view.c
Original file line number Diff line number Diff line change
Expand Up @@ -1946,36 +1946,58 @@ void dt_view_paint_surface(cairo_t *cr,
const int maxw = MIN(port->width, backbuf_scale * processed_width * (1<<closeup) / ppd);
const int maxh = MIN(port->height, backbuf_scale * processed_height * (1<<closeup) / ppd);

// Position the clip (and assessment frame) from the viewport centre (zoom) and
// the IMAGE's own geometry, NOT from the backbuf ROI centre (offset). The ROI
// centre is clamped to the image, and while the viewport is panned outside the
// picture to reach off-image mask handles it lags the live zoom in discrete
// re-render steps. Driving the clip from it made the clip sawtooth back and
// forth on every re-render, even though the image content -- scaled from the
// backbuf to the live zoom by back_scale below -- stays put. img_w/img_h are
// the full image in screen pixels; on screen the image spans
// img_w/img_h are the full image in screen pixels; on screen the image spans
// [(-0.5 - zoom) * img .. (0.5 - zoom) * img] about the viewport centre.
const double img_w = processed_width * backbuf_scale * (1 << closeup) / ppd;
const double img_h = processed_height * backbuf_scale * (1 << closeup) / ppd;

// Visible image rectangle: the image, panned by zoom_x/zoom_y, intersected
// with the viewport. Used for the color-assessment frame (always) and, when
// panned off-image, for the clip.
const double vis_x0 = fmax(-0.5 * port->width, (-0.5 - zoom_x) * img_w);
const double vis_x1 = fmin(0.5 * port->width, (0.5 - zoom_x) * img_w);
const double vis_y0 = fmax(-0.5 * port->height, (-0.5 - zoom_y) * img_h);
const double vis_y1 = fmin(0.5 * port->height, (0.5 - zoom_y) * img_h);

// Whether the viewport is panned beyond the image, i.e. off-image content is
// deliberately in view. In normal use the pan clamp keeps the viewport inside
// the picture, so this is FALSE; it only becomes TRUE while editing a mask,
// where the clamp is relaxed to reach handles outside the image (see
// _clamp_zoom_to_mask). We keep darktable's original clip and coverage
// behaviour in the normal case so a stale backbuf lagging a drag can't leave a
// grey seam at the border (#21606) or a growing frame over the grey
// background (#21594); the viewport-centre-driven geometry is used only for
// the off-image mask-editing case it was introduced for.
const float halfw_img = img_w > 0 ? 0.5f * port->width / (float)img_w : 0.5f;
const float halfh_img = img_h > 0 ? 0.5f * port->height / (float)img_h : 0.5f;
const gboolean off_image =
fabsf(zoom_x) > fmaxf(0.0f, 0.5f - halfw_img) + 1e-4f
|| fabsf(zoom_y) > fmaxf(0.0f, 0.5f - halfh_img) + 1e-4f;

if(port->color_assessment
&& window != DT_WINDOW_SLIDESHOW)
{
// draw the white frame around picture
// draw the white frame hugging the visible image edge. Sized from the
// visible rectangle (image intersected with viewport) so it can't grow out
// across the grey background as you zoom in (#21594).
const double ratio = dt_conf_get_float("darkroom/ui/color_assessment_border_white_ratio");
const double borw = img_w + 2.0 * tb * ratio;
const double borh = img_h + 2.0 * tb * ratio;
cairo_rectangle(cr, -0.5 * borw - zoom_x * img_w, -0.5 * borh - zoom_y * img_h, borw, borh);
const double borw = fmax(0.0, vis_x1 - vis_x0) + 2.0 * tb * ratio;
const double borh = fmax(0.0, vis_y1 - vis_y0) + 2.0 * tb * ratio;
cairo_rectangle(cr, vis_x0 - tb * ratio, vis_y0 - tb * ratio, borw, borh);
dt_gui_gtk_set_source_rgb(cr, DT_GUI_COLOR_COLOR_ASSESSMENT_FG);
cairo_fill(cr);
}

// clip to the image rectangle intersected with the viewport
const double clip_x0 = fmax(-0.5 * port->width, (-0.5 - zoom_x) * img_w);
const double clip_x1 = fmin(0.5 * port->width, (0.5 - zoom_x) * img_w);
const double clip_y0 = fmax(-0.5 * port->height, (-0.5 - zoom_y) * img_h);
const double clip_y1 = fmin(0.5 * port->height, (0.5 - zoom_y) * img_h);
cairo_rectangle(cr, clip_x0, clip_y0, fmax(0.0, clip_x1 - clip_x0), fmax(0.0, clip_y1 - clip_y0));
if(off_image)
// Editing a mask with the viewport panned past the picture: clip to the
// image's true border (intersected with the viewport) so the off-image
// background shows where the handles are being placed.
cairo_rectangle(cr, vis_x0, vis_y0, fmax(0.0, vis_x1 - vis_x0), fmax(0.0, vis_y1 - vis_y0));
else
// Original darktable clip: MIN(viewport, image) centred on the viewport. It
// never sits exactly on the image border while zoomed in, so a backbuf that
// lags a drag can't leave a grey seam at the border (#21606).
cairo_rectangle(cr, -0.5 * maxw, -0.5 * maxh, maxw, maxh);
cairo_clip(cr);
const double back_scale = (buf_scale == 0 ? 1.0 : backbuf_scale / buf_scale) * (1<<closeup) / ppd;
const double trans_x = (offset_x - zoom_x) * processed_width * buf_scale - 0.5 * buf_width;
Expand All @@ -1987,15 +2009,20 @@ void dt_view_paint_surface(cairo_t *cr,
// handles, the backbuf centre (offset) can get no closer to the viewport centre
// (zoom) than the image edge. Measuring against the raw zoom centre would keep
// reporting the off-image (background) margin as "not covered" and re-trigger
// the pipe every frame, making the clip jitter. So measure against reach_*, the
// closest centre the renderer can actually reach: when offset already sits there
// a re-render cannot improve coverage, and the residual is harmless background.
const float rhx = 0.5f * buf_width / fmaxf(1.0f, (float)processed_width * buf_scale);
const float rhy = 0.5f * buf_height / fmaxf(1.0f, (float)processed_height * buf_scale);
const float reach_x = rhx >= 0.5f ? 0.0f : CLAMP(zoom_x, -(0.5f - rhx), 0.5f - rhx);
const float reach_y = rhy >= 0.5f ? 0.0f : CLAMP(zoom_y, -(0.5f - rhy), 0.5f - rhy);
const double cov_trans_x = (offset_x - reach_x) * processed_width * buf_scale - 0.5 * buf_width;
const double cov_trans_y = (offset_y - reach_y) * processed_height * buf_scale - 0.5 * buf_height;
// the pipe every frame. So in that case measure against the closest centre the
// renderer can actually reach. In normal use we measure against the real zoom
// centre, so a drag that brings the border into view still re-renders to fill
// it instead of leaving it grey.
float cov_zoom_x = zoom_x, cov_zoom_y = zoom_y;
if(off_image)
{
const float rhx = 0.5f * buf_width / fmaxf(1.0f, (float)processed_width * buf_scale);
const float rhy = 0.5f * buf_height / fmaxf(1.0f, (float)processed_height * buf_scale);
cov_zoom_x = rhx >= 0.5f ? 0.0f : CLAMP(zoom_x, -(0.5f - rhx), 0.5f - rhx);
cov_zoom_y = rhy >= 0.5f ? 0.0f : CLAMP(zoom_y, -(0.5f - rhy), 0.5f - rhy);
}
const double cov_trans_x = (offset_x - cov_zoom_x) * processed_width * buf_scale - 0.5 * buf_width;
const double cov_trans_y = (offset_y - cov_zoom_y) * processed_height * buf_scale - 0.5 * buf_height;

// Check if we should use the preview pipe for fallback rendering
// This is only valid for the main develop (not for pinned images which have dev != darktable.develop)
Expand Down
Loading