1339 if (Julian_day < 1 || Julian_day > 366)
1345 const bool leap = (year % 4 == 0 && year % 100 != 0) ||
1348 if (!leap && Julian_day == 366)
1353 int month_lengths[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1355 month_lengths[1] = 29;
1358 int d_remaining = Julian_day;
1362 for (
int i = 0; i < 12; ++i) {
1363 if (d_remaining > month_lengths[i]) {
1364 d_remaining -= month_lengths[i];
1372 return make_Date(d_remaining, month, year);
1382 int month = date.
month;
1383 int year = date.
year;
1386 if (month < 1 || month > 12) {
1387 helios_runtime_error(
"ERROR (JulianDay): Month of year is out of range (month of " + std::to_string(month) +
" was given).");
1391 int daysInMonth[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1394 if (
bool isLeapYear = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
1395 daysInMonth[2] = 29;
1398 if (day < 1 || day > daysInMonth[month]) {
1399 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) +
").");
1407 int dayOfYear = day;
1408 for (
int m = 1; m < month; m++) {
1409 dayOfYear += daysInMonth[m];
1420 std::string fn(filename);
1421 auto dot_pos = fn.find_last_of(
'.');
1422 if (dot_pos == std::string::npos) {
1425 std::string ext = fn.substr(dot_pos + 1);
1426 if (ext !=
"png" && ext !=
"PNG") {
1431 auto fileCloser = [](FILE *f) {
1435 std::unique_ptr<FILE,
decltype(fileCloser)> fp(std::fopen(fn.c_str(),
"rb"), fileCloser);
1441 unsigned char header[8];
1442 if (std::fread(header, 1, 8, fp.get()) != 8 || png_sig_cmp(header, 0, 8)) {
1446 png_structp png_ptr =
nullptr;
1447 png_infop info_ptr =
nullptr;
1451 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
nullptr,
nullptr,
nullptr);
1453 throw std::runtime_error(
"png_create_read_struct failed.");
1455 info_ptr = png_create_info_struct(png_ptr);
1457 png_destroy_read_struct(&png_ptr,
nullptr,
nullptr);
1458 throw std::runtime_error(
"png_create_info_struct failed.");
1462 if (setjmp(png_jmpbuf(png_ptr))) {
1463 throw std::runtime_error(
"Error during PNG initialization.");
1467 png_init_io(png_ptr, fp.get());
1468 png_set_sig_bytes(png_ptr, 8);
1469 png_read_info(png_ptr, info_ptr);
1472 png_byte color_type = png_get_color_type(png_ptr, info_ptr);
1473 bool has_tRNS = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) != 0;
1476 bool has_alpha = ((color_type & PNG_COLOR_MASK_ALPHA) != 0) || has_tRNS;
1479 png_destroy_read_struct(&png_ptr, &info_ptr,
nullptr);
1482 }
catch (
const std::exception &e) {
1486 png_destroy_read_struct(&png_ptr, &info_ptr,
nullptr);
1488 png_destroy_read_struct(&png_ptr,
nullptr,
nullptr);
1498 const std::string &fn = filename;
1499 auto dot = fn.find_last_of(
'.');
1500 if (dot == std::string::npos) {
1503 std::string ext = fn.substr(dot + 1);
1504 std::transform(ext.begin(), ext.end(), ext.begin(), [](
unsigned char c) { return std::tolower(c); });
1509 std::vector<std::vector<bool>> mask;
1510 png_structp png_ptr =
nullptr;
1511 png_infop info_ptr =
nullptr;
1515 auto fileDeleter = [](FILE *f) {
1519 std::unique_ptr<FILE,
decltype(fileDeleter)> fp(fopen(filename.c_str(),
"rb"), fileDeleter);
1521 throw std::runtime_error(
"File " + filename +
" could not be opened for reading.");
1525 unsigned char header[8];
1526 if (fread(header, 1, 8, fp.get()) != 8) {
1527 throw std::runtime_error(
"Failed to read PNG header from " + filename);
1529 if (png_sig_cmp(header, 0, 8)) {
1530 throw std::runtime_error(
"File " + filename +
" is not a valid PNG.");
1534 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
nullptr,
nullptr,
nullptr);
1536 throw std::runtime_error(
"png_create_read_struct failed.");
1539 info_ptr = png_create_info_struct(png_ptr);
1541 png_destroy_read_struct(&png_ptr,
nullptr,
nullptr);
1542 throw std::runtime_error(
"png_create_info_struct failed.");
1546 if (setjmp(png_jmpbuf(png_ptr))) {
1547 throw std::runtime_error(
"Error during PNG initialization.");
1550 png_init_io(png_ptr, fp.get());
1551 png_set_sig_bytes(png_ptr, 8);
1552 png_read_info(png_ptr, info_ptr);
1554 uint width = png_get_image_width(png_ptr, info_ptr);
1555 uint height = png_get_image_height(png_ptr, info_ptr);
1556 png_byte color_type = png_get_color_type(png_ptr, info_ptr);
1557 png_byte bit_depth = png_get_bit_depth(png_ptr, info_ptr);
1558 bool has_alpha = (color_type & PNG_COLOR_MASK_ALPHA) != 0 || png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) != 0;
1560 mask.resize(height);
1561 for (
uint i = 0; i < height; i++) {
1562 mask.at(i).resize(width);
1566 for (
uint j = 0; j < height; ++j) {
1567 std::fill(mask.at(j).begin(), mask.at(j).end(),
true);
1569 png_destroy_read_struct(&png_ptr, &info_ptr,
nullptr);
1574 if (bit_depth == 16) {
1575 png_set_strip_16(png_ptr);
1577 if (color_type == PNG_COLOR_TYPE_PALETTE) {
1578 png_set_palette_to_rgb(png_ptr);
1580 if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
1581 png_set_expand_gray_1_2_4_to_8(png_ptr);
1583 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
1584 png_set_tRNS_to_alpha(png_ptr);
1587 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)) {
1588 png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER);
1590 if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
1591 png_set_gray_to_rgb(png_ptr);
1594 png_set_interlace_handling(png_ptr);
1595 png_read_update_info(png_ptr, info_ptr);
1598 size_t rowbytes = png_get_rowbytes(png_ptr, info_ptr);
1599 std::vector<std::vector<png_byte>> row_data(height, std::vector<png_byte>(rowbytes));
1600 std::vector<png_bytep> row_pointers(height);
1601 for (
uint y = 0; y < height; ++y) {
1602 row_pointers[y] = row_data[y].data();
1606 if (setjmp(png_jmpbuf(png_ptr))) {
1607 throw std::runtime_error(
"Error during PNG read.");
1609 png_read_image(png_ptr, row_pointers.data());
1612 for (
uint j = 0; j < height; j++) {
1613 png_byte *row = row_pointers[j];
1614 for (
uint i = 0; i < width; i++) {
1615 png_byte *ba = &(row[i * 4]);
1616 float alpha = ba[3];
1617 mask.at(j).at(i) = (alpha >= 250);
1621 png_destroy_read_struct(&png_ptr, &info_ptr,
nullptr);
1623 }
catch (
const std::exception &e) {
1625 png_destroy_read_struct(&png_ptr, info_ptr ? &info_ptr :
nullptr,
nullptr);
1635 auto ext_pos = filename.find_last_of(
'.');
1636 if (ext_pos == std::string::npos) {
1639 std::string ext = filename.substr(ext_pos + 1);
1640 std::transform(ext.begin(), ext.end(), ext.begin(), [](
unsigned char c) { return std::tolower(c); });
1645 png_structp png_ptr =
nullptr;
1646 png_infop info_ptr =
nullptr;
1652 auto fileDeleter = [](FILE *f) {
1656 std::unique_ptr<FILE,
decltype(fileDeleter)> fp(fopen(filename.c_str(),
"rb"), fileDeleter);
1658 throw std::runtime_error(
"File " + filename +
" could not be opened.");
1662 unsigned char header[8];
1663 if (fread(header, 1, 8, fp.get()) != 8) {
1664 throw std::runtime_error(
"Failed to read PNG header from " + filename);
1666 if (png_sig_cmp(header, 0, 8)) {
1667 throw std::runtime_error(
"File " + filename +
" is not a valid PNG.");
1671 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
nullptr,
nullptr,
nullptr);
1673 throw std::runtime_error(
"Failed to create PNG read struct.");
1675 info_ptr = png_create_info_struct(png_ptr);
1677 png_destroy_read_struct(&png_ptr,
nullptr,
nullptr);
1678 throw std::runtime_error(
"Failed to create PNG info struct.");
1682 if (setjmp(png_jmpbuf(png_ptr))) {
1683 throw std::runtime_error(
"Error during PNG initialization.");
1687 png_init_io(png_ptr, fp.get());
1688 png_set_sig_bytes(png_ptr, 8);
1689 png_read_info(png_ptr, info_ptr);
1692 png_byte bit_depth = png_get_bit_depth(png_ptr, info_ptr);
1693 png_byte color_type = png_get_color_type(png_ptr, info_ptr);
1695 if (bit_depth == 16) {
1696 png_set_strip_16(png_ptr);
1698 if (color_type == PNG_COLOR_TYPE_PALETTE) {
1699 png_set_palette_to_rgb(png_ptr);
1701 if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
1702 png_set_expand_gray_1_2_4_to_8(png_ptr);
1704 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
1705 png_set_tRNS_to_alpha(png_ptr);
1708 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)) {
1709 png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER);
1711 if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
1712 png_set_gray_to_rgb(png_ptr);
1716 png_set_interlace_handling(png_ptr);
1719 png_read_update_info(png_ptr, info_ptr);
1722 size_t w = png_get_image_width(png_ptr, info_ptr);
1723 size_t h = png_get_image_height(png_ptr, info_ptr);
1725 constexpr size_t max_pixels = (std::numeric_limits<size_t>::max)() /
sizeof(
helios::RGBAcolor);
1726 if (w == 0 || h == 0 || w > max_pixels / h) {
1727 throw std::runtime_error(
"Invalid image dimensions: " + std::to_string(w) +
"×" + std::to_string(h));
1729 width = scast<uint>(w);
1730 height = scast<uint>(h);
1733 size_t rowbytes = png_get_rowbytes(png_ptr, info_ptr);
1734 if (rowbytes < width * 4) {
1735 throw std::runtime_error(
"Unexpected row size: " + std::to_string(rowbytes));
1737 std::vector<std::vector<png_byte>> row_data(height, std::vector<png_byte>(rowbytes));
1738 std::vector<png_bytep> row_pointers(height);
1739 for (
uint y = 0; y < height; ++y) {
1740 row_pointers[y] = row_data[y].data();
1744 if (setjmp(png_jmpbuf(png_ptr))) {
1745 throw std::runtime_error(
"Error during PNG read.");
1747 png_read_image(png_ptr, row_pointers.data());
1750 texture.resize(scast<size_t>(width) * height);
1751 for (
uint y = 0; y < height; ++y) {
1752 png_bytep row = row_pointers[y];
1753 for (
uint x = 0; x < width; ++x) {
1754 png_bytep px = row + x * 4;
1755 auto &c = texture[y * width + x];
1756 c.r = px[0] / 255.0f;
1757 c.g = px[1] / 255.0f;
1758 c.b = px[2] / 255.0f;
1759 c.a = px[3] / 255.0f;
1762 }
catch (
const std::exception &e) {
1766 png_destroy_read_struct(&png_ptr, &info_ptr,
nullptr);
1768 png_destroy_read_struct(&png_ptr,
nullptr,
nullptr);
1776 png_destroy_read_struct(&png_ptr, &info_ptr,
nullptr);
1778 png_destroy_read_struct(&png_ptr,
nullptr,
nullptr);
1784 FILE *fp = fopen(filename.c_str(),
"wb");
1789 png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
nullptr,
nullptr,
nullptr);
1794 png_infop info = png_create_info_struct(png);
1799 if (setjmp(png_jmpbuf(png))) {
1803 png_init_io(png, fp);
1806 png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
1807 png_write_info(png, info);
1813 std::vector<unsigned char *> row_pointers;
1814 row_pointers.resize(height);
1816 std::vector<std::vector<unsigned char>> data;
1817 data.resize(height);
1819 for (
uint row = 0; row < height; row++) {
1820 data.at(row).resize(4 * width);
1821 for (
uint col = 0; col < width; col++) {
1822 data.at(row).at(4 * col) = (
unsigned char) round(
clamp(pixel_data.at(row * width + col).r, 0.f, 1.f) * 255.f);
1823 data.at(row).at(4 * col + 1) = (
unsigned char) round(
clamp(pixel_data.at(row * width + col).g, 0.f, 1.f) * 255.f);
1824 data.at(row).at(4 * col + 2) = (
unsigned char) round(
clamp(pixel_data.at(row * width + col).b, 0.f, 1.f) * 255.f);
1825 data.at(row).at(4 * col + 3) = (
unsigned char) round(
clamp(pixel_data.at(row * width + col).a, 0.f, 1.f) * 255.f);
1827 row_pointers.at(row) = &data.at(row).at(0);
1830 png_write_image(png, &row_pointers.at(0));
1831 png_write_end(png,
nullptr);
1835 png_destroy_write_struct(&png, &info);
1875 if (file_extension !=
".jpg" && file_extension !=
".JPG" && file_extension !=
".jpeg" && file_extension !=
".JPEG") {
1879 jpeg_decompress_struct cinfo{};
1881 jpeg_error_mgr jerr{};
1885 std::unique_ptr<FILE, int (*)(FILE *)> infile(fopen(filename.c_str(),
"rb"), fclose);
1887 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.");
1890 cinfo.err = jpeg_std_error(&jerr);
1891 jerr.error_exit = jpg_error_exit;
1894 jpeg_create_decompress(&cinfo);
1895 jpeg_stdio_src(&cinfo, infile.get());
1896 (void) jpeg_read_header(&cinfo, (
boolean) 1);
1898 (void) jpeg_start_decompress(&cinfo);
1900 row_stride = cinfo.output_width * cinfo.output_components;
1901 buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
1903 width = cinfo.output_width;
1904 height = cinfo.output_height;
1906 if (cinfo.output_components != 3) {
1908 }
else if (width == 0 || height == 0) {
1912 pixel_data.resize(width * height);
1916 while (cinfo.output_scanline < cinfo.output_height) {
1917 (void) jpeg_read_scanlines(&cinfo, buffer, 1);
1921 for (
int col = 0; col < row_stride; col += 3) {
1922 pixel_data.at(row * width + col / 3) =
make_RGBcolor(ba[col] / 255.f, ba[col + 1] / 255.f, ba[col + 2] / 255.f);
1928 (void) jpeg_finish_decompress(&cinfo);
1930 jpeg_destroy_decompress(&cinfo);
1932 jpeg_destroy_decompress(&cinfo);
1939 if (file_extension !=
".jpg" && file_extension !=
".JPG" && file_extension !=
".jpeg" && file_extension !=
".JPEG") {
1940 helios_runtime_error(
"ERROR (Context::getImageResolutionJPEG): File " + filename +
" is not JPEG format.");
1943 jpeg_decompress_struct cinfo{};
1945 jpeg_error_mgr jerr{};
1946 std::unique_ptr<FILE, int (*)(FILE *)> infile(fopen(filename.c_str(),
"rb"), fclose);
1948 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.");
1951 cinfo.err = jpeg_std_error(&jerr);
1952 jerr.error_exit = jpg_error_exit;
1955 jpeg_create_decompress(&cinfo);
1956 jpeg_stdio_src(&cinfo, infile.get());
1957 (void) jpeg_read_header(&cinfo, (
boolean) 1);
1958 (void) jpeg_start_decompress(&cinfo);
1960 jpeg_destroy_decompress(&cinfo);
1962 jpeg_destroy_decompress(&cinfo);
1966 return make_int2(cinfo.output_width, cinfo.output_height);
1971 std::string filename = a_filename;
1973 if (file_extension !=
".jpg" && file_extension !=
".JPG" && file_extension !=
".jpeg" && file_extension !=
".JPEG") {
1974 filename.append(
".jpeg");
1977 if (pixel_data.size() != width * height) {
1978 helios_runtime_error(
"ERROR (Context::writeJPEG): Pixel data does not have size of width*height.");
1981 const uint bsize = 3 * width * height;
1982 std::vector<unsigned char> screen_shot_trans(bsize);
1985 for (
size_t i = 0; i < width * height; i++) {
1986 screen_shot_trans.at(ii) = (
unsigned char) round(
clamp(pixel_data.at(i).r, 0.f, 1.f) * 255);
1987 screen_shot_trans.at(ii + 1) = (
unsigned char) round(
clamp(pixel_data.at(i).g, 0.f, 1.f) * 255);
1988 screen_shot_trans.at(ii + 2) = (
unsigned char) round(
clamp(pixel_data.at(i).b, 0.f, 1.f) * 255);
1992 struct jpeg_compress_struct cinfo{};
1994 struct jpeg_error_mgr jerr{};
1996 cinfo.err = jpeg_std_error(&jerr);
1997 jerr.error_exit = jpg_error_exit;
1999 JSAMPROW row_pointer;
2002 std::unique_ptr<FILE, int (*)(FILE *)> outfile(fopen(filename.c_str(),
"wb"), fclose);
2004 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.");
2007 jpeg_create_compress(&cinfo);
2008 jpeg_stdio_dest(&cinfo, outfile.get());
2010 cinfo.image_width = width;
2011 cinfo.image_height = height;
2012 cinfo.input_components = 3;
2013 cinfo.in_color_space = JCS_RGB;
2015 jpeg_set_defaults(&cinfo);
2017 jpeg_set_quality(&cinfo, 100, (
boolean) 1 );
2019 jpeg_start_compress(&cinfo, (
boolean) 1);
2022 row_stride = width * 3;
2024 while (cinfo.next_scanline < cinfo.image_height) {
2025 row_pointer = (JSAMPROW) &screen_shot_trans[(cinfo.image_height - cinfo.next_scanline - 1) * row_stride];
2026 (void) jpeg_write_scanlines(&cinfo, &row_pointer, 1);
2029 jpeg_finish_compress(&cinfo);
2030 jpeg_destroy_compress(&cinfo);
2032 jpeg_destroy_compress(&cinfo);
2444 if (points.empty()) {
2449 if (points.size() == 1) {
2455 constexpr float EPSILON = 1.0E-5f;
2456 bool is_likely_increasing = points.size() < 2 || points[1].x > points[0].x;
2458 if (is_likely_increasing) {
2460 bool is_valid_increasing =
true;
2461 for (
size_t i = 1; i < points.size() && is_valid_increasing; ++i) {
2462 float deltaX = points[i].x - points[i - 1].x;
2463 if (deltaX <= EPSILON) {
2464 is_valid_increasing =
false;
2468 if (is_valid_increasing) {
2470 if (x <= points.front().x) {
2471 return points.front().y;
2473 if (x >= points.back().x) {
2474 return points.back().y;
2478 auto it = std::lower_bound(points.begin(), points.end(), x, [](
const vec2 &point,
float value) { return point.x < value; });
2480 size_t upper_idx = std::distance(points.begin(), it);
2481 size_t lower_idx = upper_idx - 1;
2483 const vec2 &p1 = points[lower_idx];
2484 const vec2 &p2 = points[upper_idx];
2487 float t = (x - p1.
x) / (p2.
x - p1.
x);
2488 return p1.
y + t * (p2.
y - p1.
y);
2493 bool is_increasing =
true;
2494 bool is_decreasing =
true;
2496 for (
size_t i = 1; i < points.size(); ++i) {
2497 float deltaX = points[i].x - points[i - 1].x;
2499 if (std::abs(deltaX) < EPSILON) {
2504 is_decreasing =
false;
2506 is_increasing =
false;
2510 if (!is_increasing && !is_decreasing) {
2511 helios_runtime_error(
"ERROR (interp1): X points must be monotonic (either all increasing or all decreasing).");
2515 if (is_decreasing) {
2516 if (x >= points.front().x) {
2517 return points.front().y;
2519 if (x <= points.back().x) {
2520 return points.back().y;
2524 auto it = std::lower_bound(points.begin(), points.end(), x, [](
const vec2 &point,
float value) { return point.x > value; });
2526 size_t upper_idx = std::distance(points.begin(), it);
2529 size_t lower_idx = upper_idx - 1;
2531 const vec2 &p1 = points[lower_idx];
2532 const vec2 &p2 = points[upper_idx];
2535 float t = (x - p1.
x) / (p2.
x - p1.
x);
2536 return p1.
y + t * (p2.
y - p1.
y);
2573 if (output_path.empty()) {
2577 std::filesystem::path output_path_fs = output_path;
2579 std::string output_file = output_path_fs.filename().string();
2580 std::string output_file_ext = output_path_fs.extension().string();
2581 std::string output_dir = output_path_fs.parent_path().string();
2583 if (output_file.empty()) {
2586 if (output_dir.find_last_of(
'/') != output_dir.length() - 1) {
2592 if (output_path.back() !=
'/' && output_path.back() !=
'\\') {
2598 if (!output_dir.empty() && !std::filesystem::exists(output_dir)) {
2599 if (!std::filesystem::create_directory(output_dir)) {
2604 if (!output_file.empty() && !allowable_file_extensions.empty()) {
2606 bool valid_extension =
false;
2607 for (
const auto &ext: allowable_file_extensions) {
2608 if (output_file_ext == ext) {
2609 valid_extension =
true;
2613 if (!valid_extension) {