Coverage Report

Created: 2025-12-31 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libvips/libvips/colour/uhdr2scRGB.c
Line
Count
Source
1
/* Turn uhdr (RGB plus a gainmap) to scRGB colourspace.
2
 *
3
 * 26/11/25
4
 *  - from XYZ2scRGB.c.c
5
 */
6
7
/*
8
9
  This file is part of VIPS.
10
11
  VIPS is free software; you can redistribute it and/or modify
12
  it under the terms of the GNU Lesser General Public License as published by
13
  the Free Software Foundation; either version 2 of the License, or
14
  (at your option) any later version.
15
16
  This program is distributed in the hope that it will be useful,
17
  but WITHOUT ANY WARRANTY; without even the implied warranty of
18
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
  GNU Lesser General Public License for more details.
20
21
  You should have received a copy of the GNU Lesser General Public License
22
  along with this program; if not, write to the Free Software
23
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24
  02110-1301  USA
25
26
 */
27
28
/*
29
30
  These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
31
32
 */
33
34
#ifdef HAVE_CONFIG_H
35
#include <config.h>
36
#endif /*HAVE_CONFIG_H*/
37
#include <glib/gi18n-lib.h>
38
39
#include <stdio.h>
40
#include <math.h>
41
42
#include <vips/vips.h>
43
44
#include "pcolour.h"
45
46
typedef struct _VipsUhdr2scRGB {
47
  VipsColour parent;
48
49
  VipsImage *in;
50
51
  /* Gainmap metadata.
52
   */
53
  float gamma[3];
54
  float min_content_boost[3];
55
  float max_content_boost[3];
56
  float offset_hdr[3];
57
  float offset_sdr[3];
58
59
  /* And the actual map.
60
   */
61
  VipsImage *gainmap;
62
63
} VipsUhdr2scRGB;
64
65
typedef VipsColourClass VipsUhdr2scRGBClass;
66
67
36
G_DEFINE_TYPE(VipsUhdr2scRGB, vips_uhdr2scRGB, VIPS_TYPE_COLOUR);
68
36
69
36
/* Derived from the apache-licensed applyGain() method of libuhdr.
70
36
 */
71
36
72
36
/* Monochrome gainmap, colour image. Probably the most common case.
73
36
 */
74
36
static void
75
36
vips_uhdr2scRGB_mono(VipsUhdr2scRGB *uhdr,
76
36
  VipsPel *out, VipsPel **in, int width)
77
36
{
78
0
  VipsPel *restrict p1 = in[0];
79
0
  VipsPel *restrict p2 = in[1];
80
0
  float *restrict q = (float *) out;
81
82
0
  for (int i = 0; i < width; i++) {
83
0
    float r = vips_v2Y_8[p1[0]];
84
0
    float g = vips_v2Y_8[p1[1]];
85
0
    float b = vips_v2Y_8[p1[2]];
86
0
    p1 += 3;
87
88
    // the gainmap is not gamma corrected in libultrahdr, confusingly
89
0
    float gg = p2[0] / 255.0;
90
0
    p2 += 1;
91
92
0
    if (uhdr->gamma[1] != 1.0f)
93
0
      gg = pow(gg, 1.0f / uhdr->gamma[1]);
94
95
0
    float boostg = log2(uhdr->min_content_boost[1]) * (1.0f - gg) +
96
0
      log2(uhdr->max_content_boost[1]) * gg;
97
98
0
    float gaing = exp2(boostg);
99
100
0
    q[0] = ((r + uhdr->offset_sdr[1]) * gaing) - uhdr->offset_hdr[1];
101
0
    q[1] = ((g + uhdr->offset_sdr[1]) * gaing) - uhdr->offset_hdr[1];
102
0
    q[2] = ((b + uhdr->offset_sdr[1]) * gaing) - uhdr->offset_hdr[1];
103
0
    q += 3;
104
0
  }
105
0
}
106
107
/* Colour image, colour gainmap. Allowed, but not common.
108
 */
109
static void
110
vips_uhdr2scRGB_rgb(VipsUhdr2scRGB *uhdr, VipsPel *out, VipsPel **in, int width)
111
0
{
112
0
  VipsPel *restrict p1 = in[0];
113
0
  VipsPel *restrict p2 = in[1];
114
0
  float *restrict q = (float *) out;
115
116
0
  for (int i = 0; i < width; i++) {
117
0
    float r = vips_v2Y_8[p1[0]];
118
0
    float g = vips_v2Y_8[p1[1]];
119
0
    float b = vips_v2Y_8[p1[2]];
120
0
    p1 += 3;
121
122
0
    float gr = vips_v2Y_8[p2[0]];
123
0
    float gg = vips_v2Y_8[p2[1]];
124
0
    float gb = vips_v2Y_8[p2[2]];
125
0
    p2 += 3;
126
127
0
    if (uhdr->gamma[0] != 1.0f)
128
0
      gr = pow(gr, 1.0f / uhdr->gamma[0]);
129
0
    if (uhdr->gamma[1] != 1.0f)
130
0
      gg = pow(gg, 1.0f / uhdr->gamma[1]);
131
0
    if (uhdr->gamma[2] != 1.0f)
132
0
      gb = pow(gb, 1.0f / uhdr->gamma[2]);
133
134
0
    float boostr = log2(uhdr->min_content_boost[0]) * (1.0f - gr) +
135
0
      log2(uhdr->max_content_boost[0]) * gr;
136
0
    float boostg = log2(uhdr->min_content_boost[1]) * (1.0f - gg) +
137
0
      log2(uhdr->max_content_boost[1]) * gg;
138
0
    float boostb = log2(uhdr->min_content_boost[2]) * (1.0f - gb) +
139
0
      log2(uhdr->max_content_boost[2]) * gb;
140
141
0
    float gainr = exp2(boostr);
142
0
    float gaing = exp2(boostg);
143
0
    float gainb = exp2(boostb);
144
145
0
    q[0] = ((r + uhdr->offset_sdr[0]) * gainr) - uhdr->offset_hdr[0];
146
0
    q[1] = ((g + uhdr->offset_sdr[1]) * gaing) - uhdr->offset_hdr[1];
147
0
    q[2] = ((b + uhdr->offset_sdr[2]) * gainb) - uhdr->offset_hdr[2];
148
0
    q += 3;
149
0
  }
150
0
}
151
152
static void
153
vips_uhdr2scRGB_line(VipsColour *colour, VipsPel *out, VipsPel **in, int width)
154
0
{
155
0
  VipsUhdr2scRGB *uhdr = (VipsUhdr2scRGB *) colour;
156
157
0
  g_assert(colour->in[0]->Xsize == colour->in[1]->Xsize);
158
0
  g_assert(colour->in[0]->Ysize == colour->in[1]->Ysize);
159
160
0
  if (uhdr->gainmap->Bands == 1)
161
0
    vips_uhdr2scRGB_mono(uhdr, out, in, width);
162
0
  else
163
0
    vips_uhdr2scRGB_rgb(uhdr, out, in, width);
164
0
}
165
166
// pass in the array to fill, size must match
167
static int
168
image_get_array_float(VipsImage *image, const char *name,
169
  float *out, int n_out)
170
0
{
171
0
  double *d;
172
0
  int n;
173
0
  if (vips_image_get_array_double(image, name, &d, &n))
174
0
    return -1;
175
0
  if (n != n_out) {
176
0
    vips_error("image_get_array_float", _("bad size"));
177
0
    return -1;
178
0
  }
179
180
0
  for (int i = 0; i < n; i++)
181
0
    out[i] = d[i];
182
183
0
  return 0;
184
0
}
185
186
static int
187
vips_uhdr2scRGB_build(VipsObject *object)
188
0
{
189
0
  VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object);
190
0
  VipsColour *colour = VIPS_COLOUR(object);
191
0
  VipsUhdr2scRGB *uhdr = (VipsUhdr2scRGB *) object;
192
193
  /* We allow one-band gainmap plus 3 band main image, so we have to turn
194
   * off the automatic detach-attach alpha processing.
195
   */
196
0
  colour->input_bands = 0;
197
198
0
  colour->n = 2;
199
0
  colour->in = (VipsImage **) vips_object_local_array(object, 2);
200
201
  /* Get the gainmap image metadata.
202
   */
203
0
  if (uhdr->in) {
204
0
    if (vips_check_bands(class->nickname, uhdr->in, 3))
205
0
      return -1;
206
0
    if (uhdr->in->BandFmt != VIPS_FORMAT_UCHAR) {
207
0
      vips_error(class->nickname, "%s", _("image must be uchar"));
208
0
      return -1;
209
0
    }
210
211
    /* Need this for fast srgb -> scRGB.
212
     */
213
0
    vips_col_make_tables_RGB_8();
214
215
0
    if (image_get_array_float(uhdr->in,
216
0
        "gainmap-max-content-boost", &uhdr->max_content_boost[0], 3) ||
217
0
      image_get_array_float(uhdr->in,
218
0
        "gainmap-min-content-boost", &uhdr->min_content_boost[0], 3) ||
219
0
      image_get_array_float(uhdr->in,
220
0
        "gainmap-gamma", &uhdr->gamma[0], 3) ||
221
0
      image_get_array_float(uhdr->in,
222
0
        "gainmap-offset-sdr", &uhdr->offset_sdr[0], 3) ||
223
0
      image_get_array_float(uhdr->in,
224
0
        "gainmap-offset-hdr", &uhdr->offset_hdr[0], 3))
225
0
      return -1;
226
227
0
    VipsImage *gainmap;
228
0
    if (!(gainmap = vips_image_get_gainmap(uhdr->in)))
229
0
      return -1;
230
0
    if (vips_check_bands_1or3(class->nickname, gainmap))
231
0
      return -1;
232
233
    /* Scale the gainmap image to match the main image 1:1.
234
     */
235
0
    if (vips_resize(gainmap, &uhdr->gainmap,
236
0
        (double) uhdr->in->Xsize / gainmap->Xsize,
237
0
        "vscale", (double) uhdr->in->Ysize / gainmap->Ysize,
238
0
        "kernel", VIPS_KERNEL_LINEAR,
239
0
        NULL))
240
0
      return -1;
241
0
    g_object_unref(gainmap);
242
243
0
    colour->in[0] = uhdr->in;
244
0
    g_object_ref(uhdr->in);
245
0
    colour->in[1] = uhdr->gainmap;
246
0
  }
247
248
0
  if (VIPS_OBJECT_CLASS(vips_uhdr2scRGB_parent_class)->build(object))
249
0
    return -1;
250
251
0
  return 0;
252
0
}
253
254
static void
255
vips_uhdr2scRGB_class_init(VipsUhdr2scRGBClass *class)
256
18
{
257
18
  GObjectClass *gobject_class = G_OBJECT_CLASS(class);
258
18
  VipsObjectClass *object_class = (VipsObjectClass *) class;
259
18
  VipsColourClass *colour_class = VIPS_COLOUR_CLASS(class);
260
261
18
  gobject_class->set_property = vips_object_set_property;
262
18
  gobject_class->get_property = vips_object_get_property;
263
264
18
  object_class->nickname = "uhdr2scRGB";
265
18
  object_class->description = _("transform uhdr to scRGB");
266
18
  object_class->build = vips_uhdr2scRGB_build;
267
268
18
  colour_class->process_line = vips_uhdr2scRGB_line;
269
270
18
  VIPS_ARG_IMAGE(class, "in", 1,
271
18
    _("Input"),
272
18
    _("Input image"),
273
18
    VIPS_ARGUMENT_REQUIRED_INPUT,
274
18
    G_STRUCT_OFFSET(VipsColourTransform, in));
275
18
}
276
277
static void
278
vips_uhdr2scRGB_init(VipsUhdr2scRGB *uhdr)
279
0
{
280
0
  VipsColour *colour = VIPS_COLOUR(uhdr);
281
282
0
  colour->interpretation = VIPS_INTERPRETATION_scRGB;
283
0
  colour->format = VIPS_FORMAT_FLOAT;
284
0
  colour->bands = 3;
285
0
}
286
287
/**
288
 * vips_uhdr2scRGB: (method)
289
 * @in: input image
290
 * @out: (out): output image
291
 * @...: `NULL`-terminated list of optional named arguments
292
 *
293
 * Transform a uhdr image (three band sRGB with an attached gainmap) to
294
 * scRGB.
295
 *
296
 * Returns: 0 on success, -1 on error
297
 */
298
int
299
vips_uhdr2scRGB(VipsImage *in, VipsImage **out, ...)
300
0
{
301
0
  va_list ap;
302
0
  int result;
303
304
0
  va_start(ap, out);
305
0
  result = vips_call_split("uhdr2scRGB", ap, in, out);
306
0
  va_end(ap);
307
308
0
  return result;
309
0
}