/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 | } |