1360 if (Julian_day < 1 || Julian_day > 366)
1366 const bool leap = (year % 4 == 0 && year % 100 != 0) ||
1369 if (!leap && Julian_day == 366)
1374 int month_lengths[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1376 month_lengths[1] = 29;
1379 int d_remaining = Julian_day;
1383 for (
int i = 0; i < 12; ++i) {
1384 if (d_remaining > month_lengths[i]) {
1385 d_remaining -= month_lengths[i];
1393 return make_Date(d_remaining, month, year);
1403 int month = date.
month;
1404 int year = date.
year;
1407 if (month < 1 || month > 12) {
1408 helios_runtime_error(
"ERROR (JulianDay): Month of year is out of range (month of " + std::to_string(month) +
" was given).");
1412 int daysInMonth[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1415 if (
bool isLeapYear = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
1416 daysInMonth[2] = 29;
1419 if (day < 1 || day > daysInMonth[month]) {
1420 helios_runtime_error(
"ERROR (JulianDay): Day of month is out of range (day of " + std::to_string(day) +
" was given for month " + std::to_string(month) +
").");
1428 int dayOfYear = day;
1429 for (
int m = 1; m < month; m++) {
1430 dayOfYear += daysInMonth[m];
1441 std::string fn(filename);
1442 auto dot_pos = fn.find_last_of(
'.');
1443 if (dot_pos == std::string::npos) {
1446 std::string ext = fn.substr(dot_pos + 1);
1447 if (ext !=
"png" && ext !=
"PNG") {
1452 auto fileCloser = [](FILE *f) {
1456 std::unique_ptr<FILE,
decltype(fileCloser)> fp(std::fopen(fn.c_str(),
"rb"), fileCloser);
1462 unsigned char header[8];
1463 if (std::fread(header, 1, 8, fp.get()) != 8 || png_sig_cmp(header, 0, 8)) {
1467 png_structp png_ptr =
nullptr;
1468 png_infop info_ptr =
nullptr;
1472 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
nullptr,
nullptr,
nullptr);
1474 throw std::runtime_error(
"png_create_read_struct failed.");
1476 info_ptr = png_create_info_struct(png_ptr);
1478 png_destroy_read_struct(&png_ptr,
nullptr,
nullptr);
1479 throw std::runtime_error(
"png_create_info_struct failed.");
1483 if (setjmp(png_jmpbuf(png_ptr))) {
1484 throw std::runtime_error(
"Error during PNG initialization.");
1488 png_init_io(png_ptr, fp.get());
1489 png_set_sig_bytes(png_ptr, 8);
1490 png_read_info(png_ptr, info_ptr);
1493 png_byte color_type = png_get_color_type(png_ptr, info_ptr);
1494 bool has_tRNS = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) != 0;
1497 bool has_alpha = ((color_type & PNG_COLOR_MASK_ALPHA) != 0) || has_tRNS;
1500 png_destroy_read_struct(&png_ptr, &info_ptr,
nullptr);
1503 }
catch (
const std::exception &e) {
1507 png_destroy_read_struct(&png_ptr, &info_ptr,
nullptr);
1509 png_destroy_read_struct(&png_ptr,
nullptr,
nullptr);
1519 const std::string &fn = filename;
1520 auto dot = fn.find_last_of(
'.');
1521 if (dot == std::string::npos) {
1524 std::string ext = fn.substr(dot + 1);
1525 std::transform(ext.begin(), ext.end(), ext.begin(), [](
unsigned char c) { return std::tolower(c); });
1530 std::vector<std::vector<bool>> mask;
1531 png_structp png_ptr =
nullptr;
1532 png_infop info_ptr =
nullptr;
1536 auto fileDeleter = [](FILE *f) {
1540 std::unique_ptr<FILE,
decltype(fileDeleter)> fp(fopen(filename.c_str(),
"rb"), fileDeleter);
1542 throw std::runtime_error(
"File " + filename +
" could not be opened for reading.");
1546 unsigned char header[8];
1547 if (fread(header, 1, 8, fp.get()) != 8) {
1548 throw std::runtime_error(
"Failed to read PNG header from " + filename);
1550 if (png_sig_cmp(header, 0, 8)) {
1551 throw std::runtime_error(
"File " + filename +
" is not a valid PNG.");
1555 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
nullptr,
nullptr,
nullptr);
1557 throw std::runtime_error(
"png_create_read_struct failed.");
1560 info_ptr = png_create_info_struct(png_ptr);
1562 png_destroy_read_struct(&png_ptr,
nullptr,
nullptr);
1563 throw std::runtime_error(
"png_create_info_struct failed.");
1567 if (setjmp(png_jmpbuf(png_ptr))) {
1568 throw std::runtime_error(
"Error during PNG initialization.");
1571 png_init_io(png_ptr, fp.get());
1572 png_set_sig_bytes(png_ptr, 8);
1573 png_read_info(png_ptr, info_ptr);
1575 uint width = png_get_image_width(png_ptr, info_ptr);
1576 uint height = png_get_image_height(png_ptr, info_ptr);
1577 png_byte color_type = png_get_color_type(png_ptr, info_ptr);
1578 png_byte bit_depth = png_get_bit_depth(png_ptr, info_ptr);
1579 bool has_alpha = (color_type & PNG_COLOR_MASK_ALPHA) != 0 || png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) != 0;
1581 mask.resize(height);
1582 for (
uint i = 0; i < height; i++) {
1583 mask.at(i).resize(width);
1587 for (
uint j = 0; j < height; ++j) {
1588 std::fill(mask.at(j).begin(), mask.at(j).end(),
true);
1590 png_destroy_read_struct(&png_ptr, &info_ptr,
nullptr);
1595 if (bit_depth == 16) {
1596 png_set_strip_16(png_ptr);
1598 if (color_type == PNG_COLOR_TYPE_PALETTE) {
1599 png_set_palette_to_rgb(png_ptr);
1601 if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
1602 png_set_expand_gray_1_2_4_to_8(png_ptr);
1604 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
1605 png_set_tRNS_to_alpha(png_ptr);
1608 if (!png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) && (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)) {
1609 png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER);
1611 if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
1612 png_set_gray_to_rgb(png_ptr);
1615 png_set_interlace_handling(png_ptr);
1616 png_read_update_info(png_ptr, info_ptr);
1619 size_t rowbytes = png_get_rowbytes(png_ptr, info_ptr);
1620 std::vector<std::vector<png_byte>> row_data(height, std::vector<png_byte>(rowbytes));
1621 std::vector<png_bytep> row_pointers(height);
1622 for (
uint y = 0; y < height; ++y) {
1623 row_pointers[y] = row_data[y].data();
1627 if (setjmp(png_jmpbuf(png_ptr))) {
1628 throw std::runtime_error(
"Error during PNG read.");
1630 png_read_image(png_ptr, row_pointers.data());
1633 for (
uint j = 0; j < height; j++) {
1634 png_byte *row = row_pointers[j];
1635 for (
uint i = 0; i < width; i++) {
1636 png_byte *ba = &(row[i * 4]);
1637 float alpha = ba[3];
1638 mask.at(j).at(i) = (alpha >= 250);
1642 png_destroy_read_struct(&png_ptr, &info_ptr,
nullptr);
1644 }
catch (
const std::exception &e) {
1646 png_destroy_read_struct(&png_ptr, info_ptr ? &info_ptr :
nullptr,
nullptr);
1656 auto ext_pos = filename.find_last_of(
'.');
1657 if (ext_pos == std::string::npos) {
1660 std::string ext = filename.substr(ext_pos + 1);
1661 std::transform(ext.begin(), ext.end(), ext.begin(), [](
unsigned char c) { return std::tolower(c); });
1666 png_structp png_ptr =
nullptr;
1667 png_infop info_ptr =
nullptr;
1673 auto fileDeleter = [](FILE *f) {
1677 std::unique_ptr<FILE,
decltype(fileDeleter)> fp(fopen(filename.c_str(),
"rb"), fileDeleter);
1679 throw std::runtime_error(
"File " + filename +
" could not be opened.");
1683 unsigned char header[8];
1684 if (fread(header, 1, 8, fp.get()) != 8) {
1685 throw std::runtime_error(
"Failed to read PNG header from " + filename);
1687 if (png_sig_cmp(header, 0, 8)) {
1688 throw std::runtime_error(
"File " + filename +
" is not a valid PNG.");
1692 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
nullptr,
nullptr,
nullptr);
1694 throw std::runtime_error(
"Failed to create PNG read struct.");
1696 info_ptr = png_create_info_struct(png_ptr);
1698 png_destroy_read_struct(&png_ptr,
nullptr,
nullptr);
1699 throw std::runtime_error(
"Failed to create PNG info struct.");
1703 if (setjmp(png_jmpbuf(png_ptr))) {
1704 throw std::runtime_error(
"Error during PNG initialization.");
1708 png_init_io(png_ptr, fp.get());
1709 png_set_sig_bytes(png_ptr, 8);
1710 png_read_info(png_ptr, info_ptr);
1713 png_byte bit_depth = png_get_bit_depth(png_ptr, info_ptr);
1714 png_byte color_type = png_get_color_type(png_ptr, info_ptr);
1716 if (bit_depth == 16) {
1717 png_set_strip_16(png_ptr);
1719 if (color_type == PNG_COLOR_TYPE_PALETTE) {
1720 png_set_palette_to_rgb(png_ptr);
1722 if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
1723 png_set_expand_gray_1_2_4_to_8(png_ptr);
1725 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
1726 png_set_tRNS_to_alpha(png_ptr);
1729 if (!png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) && (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)) {
1730 png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER);
1732 if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
1733 png_set_gray_to_rgb(png_ptr);
1737 png_set_interlace_handling(png_ptr);
1740 png_read_update_info(png_ptr, info_ptr);
1743 size_t w = png_get_image_width(png_ptr, info_ptr);
1744 size_t h = png_get_image_height(png_ptr, info_ptr);
1746 constexpr size_t max_pixels = (std::numeric_limits<size_t>::max)() /
sizeof(
helios::RGBAcolor);
1747 if (w == 0 || h == 0 || w > max_pixels / h) {
1748 throw std::runtime_error(
"Invalid image dimensions: " + std::to_string(w) +
"×" + std::to_string(h));
1750 width = scast<uint>(w);
1751 height = scast<uint>(h);
1754 size_t rowbytes = png_get_rowbytes(png_ptr, info_ptr);
1755 if (rowbytes < width * 4) {
1756 throw std::runtime_error(
"Unexpected row size: " + std::to_string(rowbytes));
1758 std::vector<std::vector<png_byte>> row_data(height, std::vector<png_byte>(rowbytes));
1759 std::vector<png_bytep> row_pointers(height);
1760 for (
uint y = 0; y < height; ++y) {
1761 row_pointers[y] = row_data[y].data();
1765 if (setjmp(png_jmpbuf(png_ptr))) {
1766 throw std::runtime_error(
"Error during PNG read.");
1768 png_read_image(png_ptr, row_pointers.data());
1771 texture.resize(scast<size_t>(width) * height);
1772 for (
uint y = 0; y < height; ++y) {
1773 png_bytep row = row_pointers[y];
1774 for (
uint x = 0; x < width; ++x) {
1775 png_bytep px = row + x * 4;
1776 auto &c = texture[y * width + x];
1777 c.r = px[0] / 255.0f;
1778 c.g = px[1] / 255.0f;
1779 c.b = px[2] / 255.0f;
1780 c.a = px[3] / 255.0f;
1783 }
catch (
const std::exception &e) {
1787 png_destroy_read_struct(&png_ptr, &info_ptr,
nullptr);
1789 png_destroy_read_struct(&png_ptr,
nullptr,
nullptr);
1797 png_destroy_read_struct(&png_ptr, &info_ptr,
nullptr);
1799 png_destroy_read_struct(&png_ptr,
nullptr,
nullptr);
1805 FILE *fp = fopen(filename.c_str(),
"wb");
1810 png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
nullptr,
nullptr,
nullptr);
1815 png_infop info = png_create_info_struct(png);
1820 if (setjmp(png_jmpbuf(png))) {
1824 png_init_io(png, fp);
1827 png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
1828 png_write_info(png, info);
1834 std::vector<unsigned char *> row_pointers;
1835 row_pointers.resize(height);
1837 std::vector<std::vector<unsigned char>> data;
1838 data.resize(height);
1840 for (
uint row = 0; row < height; row++) {
1841 data.at(row).resize(4 * width);
1842 for (
uint col = 0; col < width; col++) {
1843 data.at(row).at(4 * col) = (
unsigned char) round(
clamp(pixel_data.at(row * width + col).r, 0.f, 1.f) * 255.f);
1844 data.at(row).at(4 * col + 1) = (
unsigned char) round(
clamp(pixel_data.at(row * width + col).g, 0.f, 1.f) * 255.f);
1845 data.at(row).at(4 * col + 2) = (
unsigned char) round(
clamp(pixel_data.at(row * width + col).b, 0.f, 1.f) * 255.f);
1846 data.at(row).at(4 * col + 3) = (
unsigned char) round(
clamp(pixel_data.at(row * width + col).a, 0.f, 1.f) * 255.f);
1848 row_pointers.at(row) = &data.at(row).at(0);
1851 png_write_image(png, &row_pointers.at(0));
1852 png_write_end(png,
nullptr);
1856 png_destroy_write_struct(&png, &info);
1896 if (file_extension !=
".jpg" && file_extension !=
".JPG" && file_extension !=
".jpeg" && file_extension !=
".JPEG") {
1900 jpeg_decompress_struct cinfo{};
1902 jpeg_error_mgr jerr{};
1906 std::unique_ptr<FILE, int (*)(FILE *)> infile(fopen(filename.c_str(),
"rb"), fclose);
1908 helios_runtime_error(
"ERROR (Context::readJPEG): File " + filename +
" could not be opened. Check that the file exists and that you have permission to read it.");
1911 cinfo.err = jpeg_std_error(&jerr);
1912 jerr.error_exit = jpg_error_exit;
1915 jpeg_create_decompress(&cinfo);
1916 jpeg_stdio_src(&cinfo, infile.get());
1917 (void) jpeg_read_header(&cinfo, (
boolean) 1);
1919 (void) jpeg_start_decompress(&cinfo);
1921 row_stride = cinfo.output_width * cinfo.output_components;
1922 buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
1924 width = cinfo.output_width;
1925 height = cinfo.output_height;
1927 if (cinfo.output_components != 3) {
1929 }
else if (width == 0 || height == 0) {
1933 pixel_data.resize(width * height);
1937 while (cinfo.output_scanline < cinfo.output_height) {
1938 (void) jpeg_read_scanlines(&cinfo, buffer, 1);
1942 for (
int col = 0; col < row_stride; col += 3) {
1943 pixel_data.at(row * width + col / 3) =
make_RGBcolor(ba[col] / 255.f, ba[col + 1] / 255.f, ba[col + 2] / 255.f);
1949 (void) jpeg_finish_decompress(&cinfo);
1951 jpeg_destroy_decompress(&cinfo);
1953 jpeg_destroy_decompress(&cinfo);
1960 if (file_extension !=
".jpg" && file_extension !=
".JPG" && file_extension !=
".jpeg" && file_extension !=
".JPEG") {
1961 helios_runtime_error(
"ERROR (Context::getImageResolutionJPEG): File " + filename +
" is not JPEG format.");
1964 jpeg_decompress_struct cinfo{};
1966 jpeg_error_mgr jerr{};
1967 std::unique_ptr<FILE, int (*)(FILE *)> infile(fopen(filename.c_str(),
"rb"), fclose);
1969 helios_runtime_error(
"ERROR (Context::getImageResolutionJPEG): File " + filename +
" could not be opened. Check that the file exists and that you have permission to read it.");
1972 cinfo.err = jpeg_std_error(&jerr);
1973 jerr.error_exit = jpg_error_exit;
1976 jpeg_create_decompress(&cinfo);
1977 jpeg_stdio_src(&cinfo, infile.get());
1978 (void) jpeg_read_header(&cinfo, (
boolean) 1);
1979 (void) jpeg_start_decompress(&cinfo);
1981 jpeg_destroy_decompress(&cinfo);
1983 jpeg_destroy_decompress(&cinfo);
1987 return make_int2(cinfo.output_width, cinfo.output_height);
1992 std::string filename = a_filename;
1994 if (file_extension !=
".jpg" && file_extension !=
".JPG" && file_extension !=
".jpeg" && file_extension !=
".JPEG") {
1995 filename.append(
".jpeg");
1998 if (pixel_data.size() != width * height) {
1999 helios_runtime_error(
"ERROR (Context::writeJPEG): Pixel data does not have size of width*height.");
2002 const uint bsize = 3 * width * height;
2003 std::vector<unsigned char> screen_shot_trans(bsize);
2006 for (
size_t i = 0; i < width * height; i++) {
2007 screen_shot_trans.at(ii) = (
unsigned char) round(
clamp(pixel_data.at(i).r, 0.f, 1.f) * 255);
2008 screen_shot_trans.at(ii + 1) = (
unsigned char) round(
clamp(pixel_data.at(i).g, 0.f, 1.f) * 255);
2009 screen_shot_trans.at(ii + 2) = (
unsigned char) round(
clamp(pixel_data.at(i).b, 0.f, 1.f) * 255);
2013 struct jpeg_compress_struct cinfo{};
2015 struct jpeg_error_mgr jerr{};
2017 cinfo.err = jpeg_std_error(&jerr);
2018 jerr.error_exit = jpg_error_exit;
2020 JSAMPROW row_pointer;
2023 std::unique_ptr<FILE, int (*)(FILE *)> outfile(fopen(filename.c_str(),
"wb"), fclose);
2025 helios_runtime_error(
"ERROR (Context::writeJPEG): File " + filename +
" could not be opened. Check that the file path is correct you have permission to write to it.");
2028 jpeg_create_compress(&cinfo);
2029 jpeg_stdio_dest(&cinfo, outfile.get());
2031 cinfo.image_width = width;
2032 cinfo.image_height = height;
2033 cinfo.input_components = 3;
2034 cinfo.in_color_space = JCS_RGB;
2036 jpeg_set_defaults(&cinfo);
2038 jpeg_set_quality(&cinfo, 100, (
boolean) 1 );
2040 jpeg_start_compress(&cinfo, (
boolean) 1);
2043 row_stride = width * 3;
2045 while (cinfo.next_scanline < cinfo.image_height) {
2046 row_pointer = (JSAMPROW) &screen_shot_trans[(cinfo.image_height - cinfo.next_scanline - 1) * row_stride];
2047 (void) jpeg_write_scanlines(&cinfo, &row_pointer, 1);
2050 jpeg_finish_compress(&cinfo);
2051 jpeg_destroy_compress(&cinfo);
2053 jpeg_destroy_compress(&cinfo);
2084void helios::writeEXR(
const std::string &filename,
uint width,
uint height,
const std::vector<float> &pixel_data,
const std::string &channel_name) {
2086 if (pixel_data.size() != width * height) {
2087 helios_runtime_error(
"ERROR (writeEXR): pixel_data size (" + std::to_string(pixel_data.size()) +
") does not match width*height (" + std::to_string(width * height) +
").");
2091 InitEXRHeader(&header);
2094 InitEXRImage(&image);
2096 image.num_channels = 1;
2097 image.width = scast<int>(width);
2098 image.height = scast<int>(height);
2100 float *image_ptr[1];
2101 image_ptr[0] =
const_cast<float *
>(pixel_data.data());
2103 image.images =
reinterpret_cast<unsigned char **
>(image_ptr);
2105 header.num_channels = 1;
2106 header.channels = scast<EXRChannelInfo *>(malloc(
sizeof(EXRChannelInfo)));
2107 strncpy(header.channels[0].name, channel_name.c_str(), 255);
2108 header.channels[0].name[255] =
'\0';
2110 header.pixel_types = scast<int *>(malloc(
sizeof(
int)));
2111 header.requested_pixel_types = scast<int *>(malloc(
sizeof(
int)));
2112 header.pixel_types[0] = TINYEXR_PIXELTYPE_FLOAT;
2113 header.requested_pixel_types[0] = TINYEXR_PIXELTYPE_FLOAT;
2115 header.compression_type = TINYEXR_COMPRESSIONTYPE_ZIP;
2117 const char *err =
nullptr;
2118 int ret = SaveEXRImageToFile(&image, &header, filename.c_str(), &err);
2120 free(header.channels);
2121 free(header.pixel_types);
2122 free(header.requested_pixel_types);
2124 if (ret != TINYEXR_SUCCESS) {
2125 std::string error_msg =
"ERROR (writeEXR): Failed to write EXR file '" + filename +
"'";
2127 error_msg +=
": " + std::string(err);
2128 FreeEXRErrorMessage(err);
2134void helios::writeEXR(
const std::string &filename,
uint width,
uint height,
const std::vector<std::vector<float>> &channel_data,
const std::vector<std::string> &channel_names) {
2136 if (channel_data.size() != channel_names.size()) {
2137 helios_runtime_error(
"ERROR (writeEXR): channel_data size (" + std::to_string(channel_data.size()) +
") does not match channel_names size (" + std::to_string(channel_names.size()) +
").");
2139 if (channel_data.empty()) {
2142 for (
size_t c = 0; c < channel_data.size(); c++) {
2143 if (channel_data[c].size() != width * height) {
2144 helios_runtime_error(
"ERROR (writeEXR): channel_data[" + std::to_string(c) +
"] size (" + std::to_string(channel_data[c].size()) +
") does not match width*height (" + std::to_string(width * height) +
").");
2148 int num_channels = scast<int>(channel_data.size());
2152 auto mapChannelName = [](
const std::string &name) -> std::string {
2153 std::string lower = name;
2154 std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
2155 if (lower ==
"r" || lower ==
"red")
return "R";
2156 if (lower ==
"g" || lower ==
"green")
return "G";
2157 if (lower ==
"b" || lower ==
"blue")
return "B";
2158 if (lower ==
"a" || lower ==
"alpha")
return "A";
2162 std::vector<std::string> exr_channel_names(num_channels);
2163 for (
int c = 0; c < num_channels; c++) {
2164 exr_channel_names[c] = mapChannelName(channel_names[c]);
2168 std::vector<size_t> sort_indices(num_channels);
2169 for (
size_t i = 0; i < sort_indices.size(); i++) {
2170 sort_indices[i] = i;
2172 std::sort(sort_indices.begin(), sort_indices.end(), [&](
size_t a,
size_t b) {
2173 return exr_channel_names[a] < exr_channel_names[b];
2177 InitEXRHeader(&header);
2180 InitEXRImage(&image);
2182 image.num_channels = num_channels;
2183 image.width = scast<int>(width);
2184 image.height = scast<int>(height);
2186 std::vector<float *> image_ptrs(num_channels);
2187 for (
int c = 0; c < num_channels; c++) {
2188 image_ptrs[c] =
const_cast<float *
>(channel_data[sort_indices[c]].data());
2190 image.images =
reinterpret_cast<unsigned char **
>(image_ptrs.data());
2192 header.num_channels = num_channels;
2193 header.channels = scast<EXRChannelInfo *>(malloc(
sizeof(EXRChannelInfo) * num_channels));
2194 header.pixel_types = scast<int *>(malloc(
sizeof(
int) * num_channels));
2195 header.requested_pixel_types = scast<int *>(malloc(
sizeof(
int) * num_channels));
2197 for (
int c = 0; c < num_channels; c++) {
2198 strncpy(header.channels[c].name, exr_channel_names[sort_indices[c]].c_str(), 255);
2199 header.channels[c].name[255] =
'\0';
2200 header.pixel_types[c] = TINYEXR_PIXELTYPE_FLOAT;
2201 header.requested_pixel_types[c] = TINYEXR_PIXELTYPE_FLOAT;
2204 header.compression_type = TINYEXR_COMPRESSIONTYPE_ZIP;
2206 const char *err =
nullptr;
2207 int ret = SaveEXRImageToFile(&image, &header, filename.c_str(), &err);
2209 free(header.channels);
2210 free(header.pixel_types);
2211 free(header.requested_pixel_types);
2213 if (ret != TINYEXR_SUCCESS) {
2214 std::string error_msg =
"ERROR (writeEXR): Failed to write EXR file '" + filename +
"'";
2216 error_msg +=
": " + std::string(err);
2217 FreeEXRErrorMessage(err);
2604 if (points.empty()) {
2609 if (points.size() == 1) {
2615 constexpr float EPSILON = 1.0E-5f;
2616 bool is_likely_increasing = points.size() < 2 || points[1].x > points[0].x;
2618 if (is_likely_increasing) {
2620 bool is_valid_increasing =
true;
2621 for (
size_t i = 1; i < points.size() && is_valid_increasing; ++i) {
2622 float deltaX = points[i].x - points[i - 1].x;
2623 if (deltaX <= EPSILON) {
2624 is_valid_increasing =
false;
2628 if (is_valid_increasing) {
2630 if (x <= points.front().x) {
2631 return points.front().y;
2633 if (x >= points.back().x) {
2634 return points.back().y;
2638 auto it = std::lower_bound(points.begin(), points.end(), x, [](
const vec2 &point,
float value) { return point.x < value; });
2640 size_t upper_idx = std::distance(points.begin(), it);
2641 size_t lower_idx = upper_idx - 1;
2643 const vec2 &p1 = points[lower_idx];
2644 const vec2 &p2 = points[upper_idx];
2647 float t = (x - p1.
x) / (p2.
x - p1.
x);
2648 return p1.
y + t * (p2.
y - p1.
y);
2653 bool is_increasing =
true;
2654 bool is_decreasing =
true;
2656 for (
size_t i = 1; i < points.size(); ++i) {
2657 float deltaX = points[i].x - points[i - 1].x;
2659 if (std::abs(deltaX) < EPSILON) {
2664 is_decreasing =
false;
2666 is_increasing =
false;
2670 if (!is_increasing && !is_decreasing) {
2671 helios_runtime_error(
"ERROR (interp1): X points must be monotonic (either all increasing or all decreasing).");
2675 if (is_decreasing) {
2676 if (x >= points.front().x) {
2677 return points.front().y;
2679 if (x <= points.back().x) {
2680 return points.back().y;
2684 auto it = std::lower_bound(points.begin(), points.end(), x, [](
const vec2 &point,
float value) { return point.x > value; });
2686 size_t upper_idx = std::distance(points.begin(), it);
2689 size_t lower_idx = upper_idx - 1;
2691 const vec2 &p1 = points[lower_idx];
2692 const vec2 &p2 = points[upper_idx];
2695 float t = (x - p1.
x) / (p2.
x - p1.
x);
2696 return p1.
y + t * (p2.
y - p1.
y);
2733 if (output_path.empty()) {
2737 std::filesystem::path output_path_fs = output_path;
2739 std::string output_file = output_path_fs.filename().string();
2740 std::string output_file_ext = output_path_fs.extension().string();
2741 std::string output_dir = output_path_fs.parent_path().string();
2743 if (output_file.empty()) {
2746 if (output_dir.find_last_of(
'/') != output_dir.length() - 1) {
2752 if (output_path.back() !=
'/' && output_path.back() !=
'\\') {
2758 if (!output_dir.empty() && !std::filesystem::exists(output_dir)) {
2759 if (!std::filesystem::create_directory(output_dir)) {
2764 if (!output_file.empty() && !allowable_file_extensions.empty()) {
2766 bool valid_extension =
false;
2767 for (
const auto &ext: allowable_file_extensions) {
2768 if (output_file_ext == ext) {
2769 valid_extension =
true;
2773 if (!valid_extension) {