1 // Copyright 2008-present Contributors to the OpenImageIO project.
2 // SPDX-License-Identifier: BSD-3-Clause
3 // https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md
4
5
6 #include <iostream>
7
8 #include "imageviewer.h"
9 #include <OpenImageIO/strutil.h>
10
11
IvImage(const std::string & filename,const ImageSpec * input_config)12 IvImage::IvImage(const std::string& filename, const ImageSpec* input_config)
13 : ImageBuf(filename, 0, 0, nullptr, input_config)
14 , m_thumbnail(NULL)
15 , m_thumbnail_valid(false)
16 , m_gamma(1)
17 , m_exposure(0)
18 , m_file_dataformat(TypeDesc::UNKNOWN)
19 , m_image_valid(false)
20 , m_auto_subimage(false)
21 {
22 }
23
24
25
~IvImage()26 IvImage::~IvImage() { delete[] m_thumbnail; }
27
28
29
30 bool
init_spec_iv(const std::string & filename,int subimage,int miplevel)31 IvImage::init_spec_iv(const std::string& filename, int subimage, int miplevel)
32 {
33 // invalidate info strings
34 m_shortinfo.clear();
35 m_longinfo.clear();
36
37 // If we're changing mip levels or subimages, the pixels will no
38 // longer be valid.
39 if (subimage != this->subimage() || miplevel != this->miplevel())
40 m_image_valid = false;
41 bool ok = ImageBuf::init_spec(filename, subimage, miplevel);
42 if (ok && m_file_dataformat.basetype == TypeDesc::UNKNOWN) {
43 m_file_dataformat = spec().format;
44 }
45 string_view colorspace = spec().get_string_attribute("oiio:ColorSpace");
46 if (Strutil::istarts_with(colorspace, "GammaCorrected")) {
47 float g = Strutil::from_string<float>(colorspace.c_str() + 14);
48 if (g > 1.0 && g <= 3.0 /*sanity check*/) {
49 gamma(gamma() / g);
50 }
51 }
52 return ok;
53 }
54
55
56
57 bool
read_iv(int subimage,int miplevel,bool force,TypeDesc format,ProgressCallback progress_callback,void * progress_callback_data,bool secondary_data)58 IvImage::read_iv(int subimage, int miplevel, bool force, TypeDesc format,
59 ProgressCallback progress_callback,
60 void* progress_callback_data, bool secondary_data)
61 {
62 // Don't read if we already have it in memory, unless force is true.
63 // FIXME: should we also check the time on the file to see if it's
64 // been updated since we last loaded?
65 if (m_image_valid && !force && subimage == this->subimage()
66 && miplevel != this->miplevel())
67 return true;
68
69 m_image_valid = init_spec_iv(name(), subimage, miplevel);
70 if (m_image_valid)
71 m_image_valid = ImageBuf::read(subimage, miplevel, force, format,
72 progress_callback,
73 progress_callback_data);
74
75 if (m_image_valid && secondary_data && spec().format == TypeDesc::UINT8) {
76 m_corrected_image.reset("", ImageSpec(spec().width, spec().height,
77 std::min(spec().nchannels, 4),
78 spec().format));
79 } else {
80 m_corrected_image.clear();
81 }
82 return m_image_valid;
83 }
84
85
86
87 std::string
shortinfo() const88 IvImage::shortinfo() const
89 {
90 if (m_shortinfo.empty()) {
91 m_shortinfo = Strutil::sprintf("%d x %d", spec().width, spec().height);
92 if (spec().depth > 1)
93 m_shortinfo += Strutil::sprintf(" x %d", spec().depth);
94 m_shortinfo += Strutil::sprintf(" x %d channel %s (%.2f MB)",
95 spec().nchannels, m_file_dataformat,
96 (float)spec().image_bytes()
97 / (1024.0 * 1024.0));
98 }
99 return m_shortinfo;
100 }
101
102
103
104 // Format name/value pairs as HTML table entries.
105 std::string
html_table_row(const char * name,const std::string & value)106 html_table_row(const char* name, const std::string& value)
107 {
108 std::string line = Strutil::sprintf("<tr><td><i>%s</i> : </td>",
109 name);
110 line += Strutil::sprintf("<td>%s</td></tr>\n", value.c_str());
111 return line;
112 }
113
114
115 std::string
html_table_row(const char * name,int value)116 html_table_row(const char* name, int value)
117 {
118 return html_table_row(name, Strutil::sprintf("%d", value));
119 }
120
121
122 std::string
html_table_row(const char * name,float value)123 html_table_row(const char* name, float value)
124 {
125 return html_table_row(name, Strutil::sprintf("%g", value));
126 }
127
128
129
130 std::string
longinfo() const131 IvImage::longinfo() const
132 {
133 if (m_longinfo.empty()) {
134 const ImageSpec& m_spec(nativespec());
135 m_longinfo += "<table>";
136 // m_longinfo += html_table_row (Strutil::sprintf("<b>%s</b>", m_name.c_str()).c_str(),
137 // std::string());
138 if (m_spec.depth <= 1)
139 m_longinfo += html_table_row("Dimensions",
140 Strutil::sprintf("%d x %d pixels",
141 m_spec.width,
142 m_spec.height));
143 else
144 m_longinfo += html_table_row("Dimensions",
145 Strutil::sprintf("%d x %d x %d pixels",
146 m_spec.width,
147 m_spec.height,
148 m_spec.depth));
149 m_longinfo += html_table_row("Channels", m_spec.nchannels);
150 std::string chanlist;
151 for (int i = 0; i < m_spec.nchannels; ++i) {
152 chanlist += m_spec.channelnames[i].c_str();
153 if (i != m_spec.nchannels - 1)
154 chanlist += ", ";
155 }
156 m_longinfo += html_table_row("Channel list", chanlist);
157 m_longinfo += html_table_row("File format", file_format_name());
158 m_longinfo += html_table_row("Data format", m_file_dataformat.c_str());
159 m_longinfo += html_table_row(
160 "Data size", Strutil::sprintf("%.2f MB", (float)m_spec.image_bytes()
161 / (1024.0 * 1024.0)));
162 m_longinfo += html_table_row("Image origin",
163 Strutil::sprintf("%d, %d, %d", m_spec.x,
164 m_spec.y, m_spec.z));
165 m_longinfo += html_table_row("Full/display size",
166 Strutil::sprintf("%d x %d x %d",
167 m_spec.full_width,
168 m_spec.full_height,
169 m_spec.full_depth));
170 m_longinfo
171 += html_table_row("Full/display origin",
172 Strutil::sprintf("%d, %d, %d", m_spec.full_x,
173 m_spec.full_y, m_spec.full_z));
174 if (m_spec.tile_width)
175 m_longinfo += html_table_row("Scanline/tile",
176 Strutil::sprintf("tiled %d x %d x %d",
177 m_spec.tile_width,
178 m_spec.tile_height,
179 m_spec.tile_depth));
180 else
181 m_longinfo += html_table_row("Scanline/tile", "scanline");
182 if (m_spec.alpha_channel >= 0)
183 m_longinfo += html_table_row("Alpha channel", m_spec.alpha_channel);
184 if (m_spec.z_channel >= 0)
185 m_longinfo += html_table_row("Depth (z) channel", m_spec.z_channel);
186
187 // Sort the metadata alphabetically, case-insensitive, but making
188 // sure that all non-namespaced attribs appear before namespaced
189 // attribs.
190 ParamValueList attribs = m_spec.extra_attribs;
191 attribs.sort(false /* sort case-insensitively */);
192 for (auto&& p : attribs) {
193 std::string s = m_spec.metadata_val(p, true);
194 m_longinfo += html_table_row(p.name().c_str(), s);
195 }
196
197 m_longinfo += "</table>";
198 }
199 return m_longinfo;
200 }
201
202
203
204 // Used by pixel_transform to convert from UINT8 to float.
205 static EightBitConverter<float> converter;
206
207
208 /// Helper routine: compute (gain*value)^invgamma
209 ///
210
211 namespace {
212
213 inline float
calc_exposure(float value,float gain,float invgamma)214 calc_exposure(float value, float gain, float invgamma)
215 {
216 if (invgamma != 1 && value >= 0)
217 return powf(gain * value, invgamma);
218 // Simple case - skip the expensive pow; also fall back to this
219 // case for negative values, for which gamma makes no sense.
220 return gain * value;
221 }
222
223 } // namespace
224
225
226 void
pixel_transform(bool srgb_to_linear,int color_mode,int select_channel)227 IvImage::pixel_transform(bool srgb_to_linear, int color_mode,
228 int select_channel)
229 {
230 /// This table obeys the following function:
231 ///
232 /// unsigned char srgb2linear(unsigned char x)
233 /// {
234 /// float x_f = x/255.0;
235 /// float x_l = 0.0;
236 /// if (x_f <= 0.04045)
237 /// x_l = x_f/12.92;
238 /// else
239 /// x_l = powf((x_f+0.055)/1.055,2.4);
240 /// return (unsigned char)(x_l * 255 + 0.5)
241 /// }
242 ///
243 /// It's used to transform from sRGB color space to linear color space.
244 // clang-format off
245 static const unsigned char srgb_to_linear_lut[256] = {
246 0, 0, 0, 0, 0, 0, 0, 1,
247 1, 1, 1, 1, 1, 1, 1, 1,
248 1, 1, 2, 2, 2, 2, 2, 2,
249 2, 2, 3, 3, 3, 3, 3, 3,
250 4, 4, 4, 4, 4, 5, 5, 5,
251 5, 6, 6, 6, 6, 7, 7, 7,
252 8, 8, 8, 8, 9, 9, 9, 10,
253 10, 10, 11, 11, 12, 12, 12, 13,
254 13, 13, 14, 14, 15, 15, 16, 16,
255 17, 17, 17, 18, 18, 19, 19, 20,
256 20, 21, 22, 22, 23, 23, 24, 24,
257 25, 25, 26, 27, 27, 28, 29, 29,
258 30, 30, 31, 32, 32, 33, 34, 35,
259 35, 36, 37, 37, 38, 39, 40, 41,
260 41, 42, 43, 44, 45, 45, 46, 47,
261 48, 49, 50, 51, 51, 52, 53, 54,
262 55, 56, 57, 58, 59, 60, 61, 62,
263 63, 64, 65, 66, 67, 68, 69, 70,
264 71, 72, 73, 74, 76, 77, 78, 79,
265 80, 81, 82, 84, 85, 86, 87, 88,
266 90, 91, 92, 93, 95, 96, 97, 99,
267 100, 101, 103, 104, 105, 107, 108, 109,
268 111, 112, 114, 115, 116, 118, 119, 121,
269 122, 124, 125, 127, 128, 130, 131, 133,
270 134, 136, 138, 139, 141, 142, 144, 146,
271 147, 149, 151, 152, 154, 156, 157, 159,
272 161, 163, 164, 166, 168, 170, 171, 173,
273 175, 177, 179, 181, 183, 184, 186, 188,
274 190, 192, 194, 196, 198, 200, 202, 204,
275 206, 208, 210, 212, 214, 216, 218, 220,
276 222, 224, 226, 229, 231, 233, 235, 237,
277 239, 242, 244, 246, 248, 250, 253, 255
278 };
279 // clang-format on
280 unsigned char correction_table[256];
281 int total_channels = spec().nchannels;
282 int color_channels = spec().nchannels;
283 int max_channels = m_corrected_image.nchannels();
284
285 // FIXME: Now with the iterator and data proxy in place, it should be
286 // trivial to apply the transformations to any kind of data, not just
287 // UINT8.
288 if (spec().format != TypeDesc::UINT8 || !m_corrected_image.localpixels()) {
289 return;
290 }
291
292 if (color_channels > 3) {
293 color_channels = 3;
294 } else if (color_channels == 2) {
295 color_channels = 1;
296 }
297
298 // This image is Luminance or Luminance + Alpha, and we are asked to show
299 // luminance.
300 if (color_channels == 1 && color_mode == 3) {
301 color_mode = 0; // Just copy as usual.
302 }
303
304 // Happy path:
305 if (!srgb_to_linear && color_mode <= 1 && m_gamma == 1.0
306 && m_exposure == 0.0) {
307 ImageBuf::ConstIterator<unsigned char, unsigned char> src(*this);
308 ImageBuf::Iterator<unsigned char, unsigned char> dst(m_corrected_image);
309 for (; src.valid(); ++src) {
310 dst.pos(src.x(), src.y());
311 for (int i = 0; i < max_channels; i++)
312 dst[i] = src[i];
313 }
314 return;
315 }
316
317 // fill the correction_table
318 if (gamma() == 1.0 && exposure() == 0.0) {
319 for (int pixelvalue = 0; pixelvalue < 256; ++pixelvalue) {
320 correction_table[pixelvalue] = pixelvalue;
321 }
322 } else {
323 float inv_gamma = 1.0 / gamma();
324 float gain = powf(2.0f, exposure());
325 for (int pixelvalue = 0; pixelvalue < 256; ++pixelvalue) {
326 float pv_f = converter(pixelvalue);
327 pv_f = clamp(calc_exposure(pv_f, gain, inv_gamma), 0.0f, 1.0f);
328 correction_table[pixelvalue] = (unsigned char)(pv_f * 255 + 0.5);
329 }
330 }
331
332 ImageBuf::ConstIterator<unsigned char, unsigned char> src(*this);
333 ImageBuf::Iterator<unsigned char, unsigned char> dst(m_corrected_image);
334 for (; src.valid(); ++src) {
335 dst.pos(src.x(), src.y());
336 if (color_mode == 0 || color_mode == 1) {
337 // RGBA, RGB modes.
338 int ch = 0;
339 for (ch = 0; ch < color_channels; ch++) {
340 if (srgb_to_linear)
341 dst[ch] = correction_table[srgb_to_linear_lut[src[ch]]];
342 else
343 dst[ch] = correction_table[src[ch]];
344 }
345 for (; ch < max_channels; ch++) {
346 dst[ch] = src[ch];
347 }
348 } else if (color_mode == 3) {
349 // Convert RGB to luminance, (Rec. 709 luma coefficients).
350 float luminance;
351 if (srgb_to_linear) {
352 luminance = converter(srgb_to_linear_lut[src[0]]) * 0.2126f
353 + converter(srgb_to_linear_lut[src[1]]) * 0.7152f
354 + converter(srgb_to_linear_lut[src[2]]) * 0.0722f;
355 } else {
356 luminance = converter(src[0]) * 0.2126f
357 + converter(src[1]) * 0.7152f
358 + converter(src[2]) * 0.0722f;
359 }
360 unsigned char val
361 = (unsigned char)(clamp(luminance, 0.0f, 1.0f) * 255.0 + 0.5);
362 val = correction_table[val];
363 dst[0] = val;
364 dst[1] = val;
365 dst[2] = val;
366
367 // Handle the rest of the channels
368 for (int ch = 3; ch < total_channels; ++ch) {
369 dst[ch] = src[ch];
370 }
371 } else { // Single channel, heatmap.
372 unsigned char v = 0;
373 if (select_channel < color_channels) {
374 if (srgb_to_linear)
375 v = correction_table[srgb_to_linear_lut[src[select_channel]]];
376 else
377 v = correction_table[src[select_channel]];
378 } else if (select_channel < total_channels) {
379 v = src[select_channel];
380 }
381 int ch = 0;
382 for (; ch < color_channels; ++ch) {
383 dst[ch] = v;
384 }
385 for (; ch < max_channels; ++ch) {
386 dst[ch] = src[ch];
387 }
388 }
389 }
390 }
391
392
393
394 void
invalidate()395 IvImage::invalidate()
396 {
397 ustring filename(name());
398 reset(filename.string());
399 m_thumbnail_valid = false;
400 m_image_valid = false;
401 if (imagecache())
402 imagecache()->invalidate(filename);
403 }
404