1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
|
function [img, info, errmsg] = HDRRead(imgfilename, continueOnError, flipit)
% [img, info, errmsg] = HDRRead(imgfilename [, continueOnError=0][, flipit=0])
% Read a high dynamic range image file and return it as double() matrix,
% suitable for use with Screen('MakeTexture') and friends.
%
% Input arguments:
%
% 'imgfilename' - Filename of the HDR image file to load.
%
% 'continueOnError' Optional flag. If set to 1, HDRRead won't abort on
% error, but simply return an empty 'img' matrix and 'info' and a error
% message in 'errmsg'. Useful for probing if a specific file type is
% supported. If set to 0 or omitted, then HDRRead will abort with an error
% on any problem or unsupported image file types.
%
% 'flipit' Optional flag: If set to 1, the loaded image is flipped upside
% down.
%
% Return arguments:
%
% Returns 'img' - A double precision matrix of size h x w x c where h is
% the height of the input image, w is the width and c is the number of
% color channels: 1 for luminance images, 2 for luminance images with alpha
% channel, 3 for true-color RGB images, 4 for RGB images with alpha
% channel. If 'imgfilename' is not a supported file type or some error happens,
% then 'img' will be returned as empty [] matrix.
%
% Returns 'info' - A struct with info about the image. On error, 'info'
% will be returned as empty [] matrix. On success, 'info' has at least the
% following fields, which may be computed from information in the image file,
% or may be "made up" from internal hard-coded defaults, if the specific image
% file or the general image file format do not provide the information:
%
% info.format - A string describing the format of the image file, e.g.,
% 'rgbe' or 'openexr'. See below for supported formats and their id's.
%
% info.dataWindow = Data window [xmin, ymin, xmax, ymax] as defined by OpenEXR.
%
% info.displayWindow = Display window [xmin, ymin, xmax, ymax] as defined by OpenEXR.
%
% info.pixelAspectRatio = Pixel aspect ratio as defined by OpenEXR.
%
% info.screenWindowWidth = Window width as defined by OpenEXR.
%
% info.screenWindowCenter = [cx, cy] as defined by OpenEXR.
%
% info.lineOrder = Line order (0) = downwards/increasing (1) = upwards/decreasing
% as defined by OpenEXR.
%
% info.compression = Compression type id, as defined by OpenEXR: 0 = None,
% 1 = RLE, 2 = ZIPS, 3 = ZIP, 4 = PIZ, 5 = PXR24, 6 = B44,
% 7 = B44A, 128 = ZFP.
%
% info.GamutFromFile = 0 if the file did not provide color gamut information,
% = 1 if the file did provide color gamut information,
% = -1 if the file may or may not contain gamut information,
% but HDRRead() can not read it.
%
% info.ColorGamut = 2-by-4 matrix with the CIE 1931 2D chromaticity
% coordinates of the red, green, and blue primaries
% (column 1 - 3) and of the white-point (4th column),
% iow. the definition of the color gamut of the color
% space in which the image is represented.
%
% If info.GamutFromFile is 1, then this matrix is parsed
% from the image file. Otherwise, the file did not
% provide the info and a hard-coded default is returned,
% which is defined in the spec for the file format, e.g.,
% Rec-709 color space for OpenEXR images, and something
% similar for .hdr Radiance images.
%
% info.sampleToNits = Either a conversion factor from sample units to nits,
% ie. the value by which each color component needs to
% be multiplied to convert it into nits. Or the value
% zero, to mark the conversion factor as "unknown" if
% the file does not provide the conversion factor.
%
% Depending on the file format and the specific file, there may be more
% optional info.subfields available, with file format specific meaning. Not
% all image attributes can be parsed by HDRRead().
%
% Returns 'errmsg' - An empty string on success, but on failure may contain
% a useful error message for the user.
%
%
% HDRRead is a dispatcher for a collection of reading routines for
% different HDR image file formats. Currently supported are:
%
% * Radiance run length encoded RGBE format, read via read_rle_rgbe().
% File extension is ".hdr" or ".pic". Returns a RGB image.
%
% info.format is 'rgbe', color values are supposed to be in units of
% radiance. The Radiance file format is specified here:
%
% https://floyd.lbl.gov/radiance/refer/filefmts.pdf
%
% The specification suggests that a pixel (r,g,b) color value of (1,1,1)
% corresponds to a total energy of 1 watt/steradian/sq.meter over the
% visible spectrum. It proposes the following formula for conversion to
% luminance for the standard Radiance RGB primaries:
%
% luminance = 179 * (0.265*R + 0.670*G + 0.065*B)
%
% So (r,g,b) = (1,1,1) corresponds to white light of 179 nits luminance.
% The value of 179 lumens/watt is the standard luminous efficacy of
% equal-energy white light that is defined and used by Radiance
% specifically for this conversion.
%
% * OpenEXR files, file extension is ".exr". info.format is 'openexr'.
% By default, Screen()'s builtin Screen('ReadHDRImage') function is used,
% which uses the builtin tinyexr open-source library from:
%
% https://github.com/syoyo/tinyexr
%
% This method is fast and can handle the most common OpenEXR format
% encoding, single-part RGB(A) images, but will not be able to cope with
% some more unusual encodings, e.g., YUV images, multipart images or deep
% images, or additional integer channels for pixel ids. See the feature
% table at tinyexr's GitHub page for features and limitations. Some of
% these limitations apply due to Screen()'s current use of tinyexr, e.g.,
% tinyexr can handle multi-part images and some deep images, but Screen
% currently does not implement the needed interfaces.
%
% A technical high level spec for OpenEXR files can be found at:
%
% https://www.openexr.com/documentation/TechnicalIntroduction.pdf
%
% In case of YUV images, HDRRead() will try to use the MIT licensed
% exrread() command from the following 3rd package/webpage, if the
% openexr-matlab package has been installed by the user:
%
% https://github.com/skycaptain/openexr-matlab
%
% That package uses the OpenEXR libraries for image reading, so those
% libraries must be installed on your system as well by yourself. At
% least GNU/Linux usually comes with libopenexr preinstalled or
% installable from the distribution package archives.
%
% The downside of using exrread() is that it won't provide color gamut
% meta information, but always return fixed gamut info for a Rec-709
% color space. For properly color-managed image reading you might
% therefore be better off using a 3rd party OpenEXR converter application
% to convert YUV images to RGB images, so Screen()'s internal .exr
% reading function can be used.
%
% The reader routines are contributed code or open source / free software /
% public domain code downloaded from various locations under different, but
% MIT compatible licenses. See the help for the respective loaders for
% copyright and author information.
%
% History:
% 14-Dec-2006 mk Written.
% 21-Jul-2020 mk Updated for use with Psychtoolbox new HDR support.
psychlasterror('reset');
if nargin < 1 || isempty(imgfilename) || ~ischar(imgfilename)
error('Missing HDR image filename.');
end
if nargin < 2 || isempty(continueOnError)
continueOnError = 0;
end
if nargin < 3 || isempty(flipit)
flipit = 0;
end
% Format dispatcher:
dispatched = 0;
img = [];
info = [];
if exist(imgfilename, 'file') ~= 2
errmsg = ['HDR file ' imgfilename ' does not exist.'];
if continueOnError
warning(errmsg);
return;
else
error(errmsg);
end
end
[~, ~, fext] = fileparts(imgfilename);
if strcmpi(fext, '.hdr') || strcmpi(fext, '.pic')
% Load a RLE encoded RGBE high dynamic range file:
dispatched = 1;
try
% Use our own routine:
[inimg, info] = read_rle_rgbe(imgfilename);
% Primary and white-point CIE coordinates defined?
if isfield(info, 'primaries')
% Yes. Parse them into our standard ColorGamut format:
info.GamutFromFile = 1;
gamut = sscanf(info.primaries, '%f');
info.ColorGamut = reshape(gamut, 2, 4);
else
% No. Assign default values per RADIANCE file format spec:
info.GamutFromFile = 0;
% These are almost like BT-709, but green x and white-point are
% different:
gamut = sscanf('0.640 0.330 0.2900.600 0.150 0.060 0.333 0.333', '%f');
info.ColorGamut = reshape(gamut, 2, 4);
end
% Pixel aspect ratio:
if isfield(info, 'pixaspect')
info.pixelAspectRatio = sscanf(info.pixaspect, '%f');
else
info.pixelAspectRatio = 1.0;
end
% window width:
info.screenWindowWidth = 1;
% window center:
info.screenWindowCenter = [0, 0];
% dataWindow and displayWindow just represent width x height
info.dataWindow = [0, 0, (info.width - 1), (info.height - 1)];
info.displayWindow = info.dataWindow;
% Line order equivalent:
if info.Ysign == '-'
info.lineOrder = 0;
else
info.lineOrder = 1;
end
% RLE compressed:
info.compression = 1;
% sampleToNits is unknown, so set it to zero:
info.sampleToNits = 0;
catch
if continueOnError
warning(['HDR file ' imgfilename ' failed to load.']);
msg = psychlasterror;
disp(msg.message);
errmsg = ['Unknown error. Maybe: ' msg.message];
return;
else
psychrethrow(psychlasterror);
end
end
info.radianceformat = info.format;
info.format = 'rgbe';
end
if strcmpi(fext, '.exr')
% Load an OpenEXR high dynamic range file:
dispatched = 1;
try
% Use our own Screen() implementation, which provides useful meta
% data about color gamut and value to Nits mapping, but can neither
% handle YUV images, nor multi-part images or deep images or some
% other more exotic stuff:
[inimg, format, err, info] = Screen('ReadHDRImage', imgfilename);
if ~isempty(inimg)
% Success. Assemble final info struct:
info.format = format;
elseif ~strcmp(err, 'R channel not found')
% 'ReadHDRImage' failed for a reason other than that this is not
% a RGB(A) image, but an YUV image. The optionally installed
% exrread() command would not do better, as its only advantage
% at the moment is that it can deal with single-part YUV
% images. So this is a fail case:
if continueOnError
warning(['HDR file ' imgfilename ' failed to load: ' err]);
msg = psychlasterror;
disp(msg.message);
errmsg = err;
return;
else
psychrethrow(psychlasterror);
end
else
% Failed because Red channel not found. Could be because it is
% a YUV image, which our own implementation can not handle
% atm., but exrread() can. Does (potentially optimized)
% exrread() function exist?
if exist('exrread', 'file')
% Yes. Use it and give it another try:
inimg = double(exrread(imgfilename));
% If we made it to here without exception, then reading
% worked, and we can try to also get some 'auxInfo':
info = exrinfo(imgfilename);
info.format = 'openexr';
% First add optional keys, so we can override them below:
for keyname = info.attributes.keys
ckey = char(keyname);
info = setfield(info, ckey, info.attributes(ckey)); %#ok<SFLD>
end
% Add the fields we promise will always be there for OpenEXR:
t = info.attributes('dataWindow');
info.dataWindow = double([t.min(1), t.min(2), t.max(1), t.max(2)]);
t = info.attributes('displayWindow');
info.displayWindow = double([t.min(1), t.min(2), t.max(1), t.max(2)]);
t = info.attributes('screenWindowCenter');
info.screenWindowCenter = double(t);
info.pixelAspectRatio = double(info.attributes('pixelAspectRatio'));
info.screenWindowWidth = double(info.attributes('screenWindowWidth'));
if strcmpi(info.attributes('lineOrder'), 'increasing_y')
info.lineOrder = 0;
elseif strcmpi(info.attributes('lineOrder'), 'decreasing_y')
info.lineOrder = 1;
else
info.lineOrder = 2;
end
info.compression = info.attributes('compression');
switch info.attributes('compression')
case 'none'
info.compression = 0;
case 'rle'
info.compression = 1;
case 'zips'
info.compression = 2;
case 'zip'
info.compression = 3;
case 'piz'
info.compression = 4;
case 'pxr24'
info.compression = 5;
case 'b44'
info.compression = 6;
case 'b44a'
info.compression = 7;
end
% exrinfo() can not provide gamut info, so pretend it
% wasn't there in the file and assign the fallback default
% of Rec-709. Is this a good idea? I don't know, but it is
% the best we can do atm.:
info.GamutFromFile = -1;
info.ColorGamut = [0.6400, 0.3000, 0.1500, 0.3127 ; 0.3300, 0.6000, 0.0600, 0.3290];
% If 'sampToNits' attribute exists, also return it as
% sampleToNits, otherwise mark sampleToNits as "invalid":
if ismember('sampToNits', info.attributes.keys)
info.sampleToNits = double(info.attributes('sampToNits'));
else
info.sampleToNits = 0;
end
else
% No. Maybe clue the user in on exrread() by abusing the
% exception handling (catch) below?
error('Could not read image, probably because it is a YUV image and the optional exrread() command is not installed, which could possibly handle it.');
end
end
catch
if continueOnError
warning(['HDR file ' imgfilename ' failed to load.']);
msg = psychlasterror;
disp(msg.message);
errmsg = ['Unknown error. Maybe: ' msg.message];
return;
else
psychrethrow(psychlasterror);
end
end
info.format = 'openexr';
end
if dispatched == 0
if ~continueOnError
error(['Potential HDR file ' imgfilename ' is of unknown type, or no loader available for this type.']);
else
warning(['Potential HDR file ' imgfilename ' is of unknown type, or no loader available for this type']);
errmsg = 'No loader available for this type.';
return;
end
end
if flipit
img(:,:,1) = flipud(inimg(:,:,1));
img(:,:,2) = flipud(inimg(:,:,2));
img(:,:,3) = flipud(inimg(:,:,3));
else
img = inimg;
end
errmsg = '';
% Done.
return;
|