Ctrl K
ComunidadGuías…

src/iop/scalepixels.c

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/*
    This file is part of darktable,
    Copyright (C) 2014-2026 darktable developers.

    darktable is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    darktable is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with darktable.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "bauhaus/bauhaus.h"
#include "common/interpolation.h"
#include "common/opencl.h"
#include "develop/imageop.h"
#include "develop/imageop_gui.h"
#include "develop/tiling.h"
#include "gui/accelerators.h"
#include "gui/gtk.h"
#include "iop/iop_api.h"

#include <gtk/gtk.h>
#include <stdlib.h>

DT_MODULE_INTROSPECTION(1, dt_iop_scalepixels_params_t)

typedef struct dt_iop_scalepixels_params_t
{
  // Aspect ratio of the pixels, usually 1 but some cameras need scaling
  // <1 means the image needs to be stretched vertically, (0.5 means 2x)
  // >1 means the image needs to be stretched horizontally (2 mean 2x)
  float pixel_aspect_ratio; // $MIN: 0.5f $MAX: 2.0f $DEFAULT: 1.0f $DESCRIPTION: "pixel aspect ratio"
} dt_iop_scalepixels_params_t;

typedef struct dt_iop_scalepixels_gui_data_t
{
} dt_iop_scalepixels_gui_data_t;

typedef struct dt_iop_scalepixels_data_t {
  float pixel_aspect_ratio;
  float x_scale;
  float y_scale;
} dt_iop_scalepixels_data_t;

typedef struct dt_iop_scalepixels_global_data_t
{
  int kernel_scalepixels;
} dt_iop_scalepixels_global_data_t;

const char *name()
{
  return C_("modulename", "scale pixels");
}

int flags()
{
  return IOP_FLAGS_ALLOW_TILING | IOP_FLAGS_TILING_FULL_ROI | IOP_FLAGS_ONE_INSTANCE
    | IOP_FLAGS_UNSAFE_COPY;
}

int default_group()
{
  return IOP_GROUP_CORRECT | IOP_GROUP_TECHNICAL;
}

int operation_tags()
{
  return IOP_TAG_DISTORT;
}

dt_iop_colorspace_type_t default_colorspace(dt_iop_module_t *self,
                                            dt_dev_pixelpipe_t *pipe,
                                            dt_dev_pixelpipe_iop_t *piece)
{
  return IOP_CS_RGB;
}

const char **description(dt_iop_module_t *self)
{
  return dt_iop_set_description(self,
                                _("module for setting pixel aspect ratio\n\n"
                                "useful for certain sensor types\n"
                                "and anamorphic desqueeze"),
                                _("corrective"),
                                _("linear, RGB, scene-referred"),
                                _("linear, RGB"),
                                _("linear, RGB, scene-referred"));
}

static void _transform(const dt_dev_pixelpipe_iop_t *const piece, float *p)
{
  dt_iop_scalepixels_data_t *d = piece->data;

  if(d->pixel_aspect_ratio < 1.0f)
  {
    p[1] /= d->pixel_aspect_ratio;
  }
  else
  {
    p[0] *= d->pixel_aspect_ratio;
  }
}

static void _backtransform(const dt_dev_pixelpipe_iop_t *const piece, float *p)
{
  dt_iop_scalepixels_data_t *d = piece->data;

  if(d->pixel_aspect_ratio < 1.0f)
  {
    p[1] *= d->pixel_aspect_ratio;
  }
  else
  {
    p[0] /= d->pixel_aspect_ratio;
  }
}

// map like modify_roi_out(): d->x_scale and d->y_scale are relative
// to the upscaled input requested by modify_roi_in()
gboolean distort_transform(dt_iop_module_t *self,
                           dt_dev_pixelpipe_iop_t *piece,
                           float *points,
                           size_t points_count)
{
  for(size_t i = 0; i < points_count * 2; i += 2)
    _transform(piece, points + i);

  return TRUE;
}

gboolean distort_backtransform(dt_iop_module_t *self,
                               dt_dev_pixelpipe_iop_t *piece,
                               float *points,
                               size_t points_count)
{
  for(size_t i = 0; i < points_count * 2; i += 2)
    _backtransform(piece, points + i);

  return TRUE;
}

void distort_mask(dt_iop_module_t *self,
                  dt_dev_pixelpipe_iop_t *piece,
                  const float *const in,
                  float *const out,
                  const dt_iop_roi_t *const roi_in,
                  const dt_iop_roi_t *const roi_out)
{
  const dt_interpolation_t *iter = dt_interpolation_new(DT_INTERPOLATION_USERPREF_WARP);
  const int iw = roi_in->width;
  const int ih = roi_in->height;
  const size_t ow = roi_out->width;

  // Compute scale factors locally from the actual roi_in/roi_out to ensure
  // consistency, rather than relying on d->x_scale/d->y_scale which may have
  // been overwritten by modify_roi_in() with different ROI dimensions.
  const float x_scale = (roi_in->width * 1.0f) / (roi_out->width * 1.0f);
  const float y_scale = (roi_in->height * 1.0f) / (roi_out->height * 1.0f);

  DT_OMP_FOR(collapse(2))
  for(int row = 0; row < roi_out->height; row++)
  {
    for(int col = 0; col < roi_out->width; col++)
    {
      const float x = col * x_scale;
      const float y = row * y_scale;
      out[ow * row + col] = CLIP(dt_interpolation_compute_sample(iter, in, x, y, iw, ih, 1, iw));
    }
  }
}

void modify_roi_out(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_out,
                    const dt_iop_roi_t *const roi_in)
{
  *roi_out = *roi_in;

  float xy[2] = { roi_out->x, roi_out->y };
  float wh[2] = { roi_out->width, roi_out->height };

  _transform(piece, xy);
  _transform(piece, wh);

  roi_out->x = (int)floorf(xy[0]);
  roi_out->y = (int)floorf(xy[1]);
  roi_out->width = (int)ceilf(wh[0]);
  roi_out->height = (int)ceilf(wh[1]);

  // sanity check.
  if(roi_out->x < 0) roi_out->x = 0;
  if(roi_out->y < 0) roi_out->y = 0;
  if(roi_out->width < 1) roi_out->width = 1;
  if(roi_out->height < 1) roi_out->height = 1;
}

void modify_roi_in(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *const roi_out,
                   dt_iop_roi_t *roi_in)
{
  *roi_in = *roi_out;

  // If possible try to get an image that's strictly larger than what we want to output
  float hw[2] = {roi_out->height, roi_out->width};
  _transform(piece, hw); // _transform() is used reversed here intentionally
  roi_in->height = hw[0];
  roi_in->width = hw[1];

  dt_iop_scalepixels_data_t *d = piece->data;
  d->x_scale = (roi_in->width * 1.0f) / (roi_out->width * 1.0f);
  d->y_scale = (roi_in->height * 1.0f) / (roi_out->height * 1.0f);

  roi_in->scale = roi_out->scale * MAX(d->x_scale, d->y_scale);
  roi_in->x = roi_out->x * d->x_scale;
  roi_in->y = roi_out->y * d->y_scale;

  // a tile at the far edge of the whole roi gets the input up to its far
  // edge: the rounding above can leave it one input row or column short, and
  // the last output row or column would then mirror the border too early
  const dt_iop_roi_t *const full_in = &piece->processed_roi_in;
  const dt_iop_roi_t *const full_out = &piece->processed_roi_out;
  if(piece->pipe->tiling && full_out->width > 0 && full_in->width > 0)
  {
    if(roi_out->x + roi_out->width >= full_out->x + full_out->width)
      roi_in->width = MAX(roi_in->width, full_in->x + full_in->width - roi_in->x);
    if(roi_out->y + roi_out->height >= full_out->y + full_out->height)
      roi_in->height = MAX(roi_in->height, full_in->y + full_in->height - roi_in->y);
  }
}

// output pixel (i, j) reads the input at ((i, j) + shift) * scale - offset, on
// the grid of the whole roi: a tile's own rois have slightly different ratios,
// which would shift each tile. Untiled, shift and offset are 0
static void _sampling_grid(const dt_dev_pixelpipe_iop_t *const piece,
                           const dt_iop_roi_t *const roi_in,
                           const dt_iop_roi_t *const roi_out,
                           float scale[2],
                           int shift[2],
                           float offset[2])
{
  const gboolean whole = piece->processed_roi_out.width > 0 && piece->processed_roi_in.width > 0;
  const dt_iop_roi_t *const full_in = whole ? &piece->processed_roi_in : roi_in;
  const dt_iop_roi_t *const full_out = whole ? &piece->processed_roi_out : roi_out;

  scale[0] = (full_in->width * 1.0f) / (full_out->width * 1.0f);
  scale[1] = (full_in->height * 1.0f) / (full_out->height * 1.0f);
  shift[0] = roi_out->x - full_out->x;
  shift[1] = roi_out->y - full_out->y;
  offset[0] = roi_in->x - full_in->x;
  offset[1] = roi_in->y - full_in->y;
}

void process(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid,
             const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
{
  const int ch = piece->colors;
  const int ch_width = ch * roi_in->width;
  const dt_interpolation_t *interpolation = dt_interpolation_new(DT_INTERPOLATION_USERPREF);

  float scale[2], offset[2];
  int shift[2];
  _sampling_grid(piece, roi_in, roi_out, scale, shift, offset);

  DT_OMP_FOR()
  // (slow) point-by-point transformation.
  // TODO: optimize with scanlines and linear steps between?
  for(int j = 0; j < roi_out->height; j++)
  {
    float *out = ((float *)ovoid) + (size_t)4 * j * roi_out->width;
    for(int i = 0; i < roi_out->width; i++, out += 4)
    {
      const float x = (i + shift[0]) * scale[0] - offset[0];
      const float y = (j + shift[1]) * scale[1] - offset[1];
      dt_interpolation_compute_pixel4c(interpolation, (float *)ivoid, out, x, y, roi_in->width,
                                       roi_in->height, ch_width);
    }
  }
}

#ifdef HAVE_OPENCL
int process_cl(dt_iop_module_t *self,
               dt_dev_pixelpipe_iop_t *piece,
               cl_mem dev_in,
               cl_mem dev_out,
               const dt_iop_roi_t *const roi_in,
               const dt_iop_roi_t *const roi_out)
{
  const dt_iop_scalepixels_global_data_t *gd = self->global_data;
  const int devid = piece->pipe->devid;
  const dt_interpolation_t *interpolation = dt_interpolation_new(DT_INTERPOLATION_USERPREF);

  float scale[2], offset[2];
  int shift[2];
  _sampling_grid(piece, roi_in, roi_out, scale, shift, offset);
  const int itype = interpolation->id;
  const int iwidth = interpolation->width;

  return dt_opencl_enqueue_kernel_2d_args(devid, gd->kernel_scalepixels, roi_out->width, roi_out->height,
                                          CLARG(dev_in), CLARG(dev_out),
                                          CLARG(roi_out->width), CLARG(roi_out->height),
                                          CLARG(roi_in->width), CLARG(roi_in->height),
                                          CLARG(scale), CLARG(shift), CLARG(offset),
                                          CLARG(itype), CLARG(iwidth));
}
#endif

void init_global(dt_iop_module_so_t *self)
{
  const int program = 2; // basic.cl, from programs.conf
  dt_iop_scalepixels_global_data_t *gd = malloc(sizeof(dt_iop_scalepixels_global_data_t));
  self->data = gd;
  gd->kernel_scalepixels = dt_opencl_create_kernel(program, "scalepixels");
}

void cleanup_global(dt_iop_module_so_t *self)
{
  dt_iop_scalepixels_global_data_t *gd = self->data;
  dt_opencl_free_kernel(gd->kernel_scalepixels);
  free(self->data);
  self->data = NULL;
}

void commit_params(dt_iop_module_t *self, dt_iop_params_t *params, dt_dev_pixelpipe_t *pipe,
                   dt_dev_pixelpipe_iop_t *piece)
{
  const dt_iop_scalepixels_params_t *p = params;
  dt_iop_scalepixels_data_t *d = piece->data;

  d->pixel_aspect_ratio = p->pixel_aspect_ratio;
  d->x_scale = 1.0f;
  d->y_scale = 1.0f;

  if(dt_isnan(p->pixel_aspect_ratio) || p->pixel_aspect_ratio <= 0.0f || p->pixel_aspect_ratio == 1.0f)
    piece->enabled = FALSE;
}

void init_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
{
  piece->data = calloc(1, sizeof(dt_iop_scalepixels_data_t));
}

void cleanup_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
{
  free(piece->data);
  piece->data = NULL;
}

void gui_init(dt_iop_module_t *self)
{
  GtkWidget *w = dt_bauhaus_slider_from_params(self, "pixel_aspect_ratio");
  gtk_widget_set_tooltip_text(w, _("adjust pixel aspect ratio"));
}

// clang-format off
// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
// vim: shiftwidth=2 expandtab tabstop=2 cindent
// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
// clang-format on