1329 if (Julian_day < 1 || Julian_day > 366)
1335 const bool leap = (year % 4 == 0 && year % 100 != 0) ||
1338 if (!leap && Julian_day == 366)
1343 int month_lengths[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1345 month_lengths[1] = 29;
1348 int d_remaining = Julian_day;
1352 for (
int i = 0; i < 12; ++i) {
1353 if (d_remaining > month_lengths[i]) {
1354 d_remaining -= month_lengths[i];
1362 return make_Date(d_remaining, month, year);
1372 int month = date.
month;
1373 int year = date.
year;
1376 if (month < 1 || month > 12) {
1377 helios_runtime_error(
"ERROR (JulianDay): Month of year is out of range (month of " + std::to_string(month) +
" was given).");
1381 int daysInMonth[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1384 if (
bool isLeapYear = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
1385 daysInMonth[2] = 29;
1388 if (day < 1 || day > daysInMonth[month]) {
1389 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) +
").");
1397 int dayOfYear = day;
1398 for (
int m = 1; m < month; m++) {
1399 dayOfYear += daysInMonth[m];
1410 std::string fn(filename);
1411 auto dot_pos = fn.find_last_of(
'.');
1412 if (dot_pos == std::string::npos) {
1415 std::string ext = fn.substr(dot_pos + 1);
1416 if (ext !=
"png" && ext !=
"PNG") {
1421 auto fileCloser = [](FILE *f) {
1425 std::unique_ptr<FILE,
decltype(fileCloser)> fp(std::fopen(fn.c_str(),
"rb"), fileCloser);
1431 unsigned char header[8];
1432 if (std::fread(header, 1, 8, fp.get()) != 8 || png_sig_cmp(header, 0, 8)) {
1436 png_structp png_ptr =
nullptr;
1437 png_infop info_ptr =
nullptr;
1441 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
nullptr,
nullptr,
nullptr);
1443 throw std::runtime_error(
"png_create_read_struct failed.");
1445 info_ptr = png_create_info_struct(png_ptr);
1447 png_destroy_read_struct(&png_ptr,
nullptr,
nullptr);
1448 throw std::runtime_error(
"png_create_info_struct failed.");
1452 if (setjmp(png_jmpbuf(png_ptr))) {
1453 throw std::runtime_error(
"Error during PNG initialization.");
1457 png_init_io(png_ptr, fp.get());
1458 png_set_sig_bytes(png_ptr, 8);
1459 png_read_info(png_ptr, info_ptr);
1462 png_byte color_type = png_get_color_type(png_ptr, info_ptr);
1463 bool has_tRNS = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) != 0;
1466 bool has_alpha = ((color_type & PNG_COLOR_MASK_ALPHA) != 0) || has_tRNS;
1469 png_destroy_read_struct(&png_ptr, &info_ptr,
nullptr);
1472 }
catch (
const std::exception &e) {
1476 png_destroy_read_struct(&png_ptr, &info_ptr,
nullptr);
1478 png_destroy_read_struct(&png_ptr,
nullptr,
nullptr);
1488 const std::string &fn = filename;
1489 auto dot = fn.find_last_of(
'.');
1490 if (dot == std::string::npos) {
1493 std::string ext = fn.substr(dot + 1);
1494 if (ext !=
"png" && ext !=
"PNG") {
1500 std::vector<std::vector<bool>> mask;
1502 png_structp png_ptr;
1508 FILE *fp = fopen(filename.c_str(),
"rb");
1510 helios_runtime_error(
"ERROR (readPNGAlpha): File " + std::string(filename) +
" could not be opened for reading.");
1512 size_t head = fread(header, 1, 8, fp);
1519 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
nullptr,
nullptr,
nullptr);
1525 info_ptr = png_create_info_struct(png_ptr);
1530 if (setjmp(png_jmpbuf(png_ptr))) {
1534 png_init_io(png_ptr, fp);
1535 png_set_sig_bytes(png_ptr, 8);
1537 png_read_info(png_ptr, info_ptr);
1539 uint width = png_get_image_width(png_ptr, info_ptr);
1540 uint height = png_get_image_height(png_ptr, info_ptr);
1541 png_byte color_type = png_get_color_type(png_ptr, info_ptr);
1542 bool has_alpha = (color_type & PNG_COLOR_MASK_ALPHA) != 0 || png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) != 0;
1544 mask.resize(height);
1545 for (
uint i = 0; i < height; i++) {
1546 mask.at(i).resize(width);
1550 for (
uint j = 0; j < height; ++j) {
1551 std::fill(mask.at(j).begin(), mask.at(j).end(),
true);
1554 png_destroy_read_struct(&png_ptr, &info_ptr,
nullptr);
1559 png_read_update_info(png_ptr, info_ptr);
1562 if (setjmp(png_jmpbuf(png_ptr))) {
1566 auto *row_pointers = (png_bytep *) malloc(
sizeof(png_bytep) * height);
1567 for (y = 0; y < height; y++)
1568 row_pointers[y] = (png_byte *) malloc(png_get_rowbytes(png_ptr, info_ptr));
1570 png_read_image(png_ptr, row_pointers);
1574 for (
uint j = 0; j < height; j++) {
1575 png_byte *row = row_pointers[j];
1576 for (
int i = 0; i < width; i++) {
1577 png_byte *ba = &(row[i * 4]);
1578 float alpha = ba[3];
1580 mask.at(j).at(i) =
false;
1582 mask.at(j).at(i) =
true;
1587 for (y = 0; y < height; y++)
1588 png_free(png_ptr, row_pointers[y]);
1589 png_free(png_ptr, row_pointers);
1590 png_destroy_read_struct(&png_ptr, &info_ptr,
nullptr);
1598 auto ext_pos = filename.find_last_of(
'.');
1599 if (ext_pos == std::string::npos) {
1602 std::string ext = filename.substr(ext_pos + 1);
1603 std::transform(ext.begin(), ext.end(), ext.begin(), [](
unsigned char c) { return std::tolower(c); });
1608 png_structp png_ptr =
nullptr;
1609 png_infop info_ptr =
nullptr;
1615 auto fileDeleter = [](FILE *f) {
1619 std::unique_ptr<FILE,
decltype(fileDeleter)> fp(fopen(filename.c_str(),
"rb"), fileDeleter);
1621 throw std::runtime_error(
"File " + filename +
" could not be opened.");
1625 unsigned char header[8];
1626 if (fread(header, 1, 8, fp.get()) != 8) {
1627 throw std::runtime_error(
"Failed to read PNG header from " + filename);
1629 if (png_sig_cmp(header, 0, 8)) {
1630 throw std::runtime_error(
"File " + filename +
" is not a valid PNG.");
1634 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
nullptr,
nullptr,
nullptr);
1636 throw std::runtime_error(
"Failed to create PNG read struct.");
1638 info_ptr = png_create_info_struct(png_ptr);
1640 png_destroy_read_struct(&png_ptr,
nullptr,
nullptr);
1641 throw std::runtime_error(
"Failed to create PNG info struct.");
1645 if (setjmp(png_jmpbuf(png_ptr))) {
1646 throw std::runtime_error(
"Error during PNG initialization.");
1650 png_init_io(png_ptr, fp.get());
1651 png_set_sig_bytes(png_ptr, 8);
1652 png_read_info(png_ptr, info_ptr);
1655 png_byte bit_depth = png_get_bit_depth(png_ptr, info_ptr);
1656 png_byte color_type = png_get_color_type(png_ptr, info_ptr);
1658 if (bit_depth == 16) {
1659 png_set_strip_16(png_ptr);
1661 if (color_type == PNG_COLOR_TYPE_PALETTE) {
1662 png_set_palette_to_rgb(png_ptr);
1664 if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
1665 png_set_expand_gray_1_2_4_to_8(png_ptr);
1667 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
1668 png_set_tRNS_to_alpha(png_ptr);
1671 if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) {
1672 png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER);
1674 if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
1675 png_set_gray_to_rgb(png_ptr);
1679 png_set_interlace_handling(png_ptr);
1682 png_read_update_info(png_ptr, info_ptr);
1685 size_t w = png_get_image_width(png_ptr, info_ptr);
1686 size_t h = png_get_image_height(png_ptr, info_ptr);
1688 constexpr size_t max_pixels = (std::numeric_limits<size_t>::max)() /
sizeof(
helios::RGBAcolor);
1689 if (w == 0 || h == 0 || w > max_pixels / h) {
1690 throw std::runtime_error(
"Invalid image dimensions: " + std::to_string(w) +
"×" + std::to_string(h));
1692 width = scast<uint>(w);
1693 height = scast<uint>(h);
1696 size_t rowbytes = png_get_rowbytes(png_ptr, info_ptr);
1697 if (rowbytes < width * 4) {
1698 throw std::runtime_error(
"Unexpected row size: " + std::to_string(rowbytes));
1700 std::vector<std::vector<png_byte>> row_data(height, std::vector<png_byte>(rowbytes));
1701 std::vector<png_bytep> row_pointers(height);
1702 for (
uint y = 0; y < height; ++y) {
1703 row_pointers[y] = row_data[y].data();
1707 if (setjmp(png_jmpbuf(png_ptr))) {
1708 throw std::runtime_error(
"Error during PNG read.");
1710 png_read_image(png_ptr, row_pointers.data());
1713 texture.resize(scast<size_t>(width) * height);
1714 for (
uint y = 0; y < height; ++y) {
1715 png_bytep row = row_pointers[y];
1716 for (
uint x = 0; x < width; ++x) {
1717 png_bytep px = row + x * 4;
1718 auto &c = texture[y * width + x];
1719 c.r = px[0] / 255.0f;
1720 c.g = px[1] / 255.0f;
1721 c.b = px[2] / 255.0f;
1722 c.a = px[3] / 255.0f;
1725 }
catch (
const std::exception &e) {
1729 png_destroy_read_struct(&png_ptr, &info_ptr,
nullptr);
1731 png_destroy_read_struct(&png_ptr,
nullptr,
nullptr);
1739 png_destroy_read_struct(&png_ptr, &info_ptr,
nullptr);
1741 png_destroy_read_struct(&png_ptr,
nullptr,
nullptr);
1747 FILE *fp = fopen(filename.c_str(),
"wb");
1752 png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
nullptr,
nullptr,
nullptr);
1757 png_infop info = png_create_info_struct(png);
1762 if (setjmp(png_jmpbuf(png))) {
1766 png_init_io(png, fp);
1769 png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
1770 png_write_info(png, info);
1776 std::vector<unsigned char *> row_pointers;
1777 row_pointers.resize(height);
1779 std::vector<std::vector<unsigned char>> data;
1780 data.resize(height);
1782 for (
uint row = 0; row < height; row++) {
1783 data.at(row).resize(4 * width);
1784 for (
uint col = 0; col < width; col++) {
1785 data.at(row).at(4 * col) = (
unsigned char) round(
clamp(pixel_data.at(row * width + col).r, 0.f, 1.f) * 255.f);
1786 data.at(row).at(4 * col + 1) = (
unsigned char) round(
clamp(pixel_data.at(row * width + col).g, 0.f, 1.f) * 255.f);
1787 data.at(row).at(4 * col + 2) = (
unsigned char) round(
clamp(pixel_data.at(row * width + col).b, 0.f, 1.f) * 255.f);
1788 data.at(row).at(4 * col + 3) = (
unsigned char) round(
clamp(pixel_data.at(row * width + col).a, 0.f, 1.f) * 255.f);
1790 row_pointers.at(row) = &data.at(row).at(0);
1793 png_write_image(png, &row_pointers.at(0));
1794 png_write_end(png,
nullptr);
1798 png_destroy_write_struct(&png, &info);
1838 if (file_extension !=
".jpg" && file_extension !=
".JPG" && file_extension !=
".jpeg" && file_extension !=
".JPEG") {
1842 jpeg_decompress_struct cinfo{};
1844 jpeg_error_mgr jerr{};
1848 std::unique_ptr<FILE, int (*)(FILE *)> infile(fopen(filename.c_str(),
"rb"), fclose);
1850 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.");
1853 cinfo.err = jpeg_std_error(&jerr);
1854 jerr.error_exit = jpg_error_exit;
1857 jpeg_create_decompress(&cinfo);
1858 jpeg_stdio_src(&cinfo, infile.get());
1859 (void) jpeg_read_header(&cinfo, (
boolean) 1);
1861 (void) jpeg_start_decompress(&cinfo);
1863 row_stride = cinfo.output_width * cinfo.output_components;
1864 buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
1866 width = cinfo.output_width;
1867 height = cinfo.output_height;
1869 if (cinfo.output_components != 3) {
1871 }
else if (width == 0 || height == 0) {
1875 pixel_data.resize(width * height);
1879 while (cinfo.output_scanline < cinfo.output_height) {
1880 (void) jpeg_read_scanlines(&cinfo, buffer, 1);
1884 for (
int col = 0; col < row_stride; col += 3) {
1885 pixel_data.at(row * width + col / 3) =
make_RGBcolor(ba[col] / 255.f, ba[col + 1] / 255.f, ba[col + 2] / 255.f);
1891 (void) jpeg_finish_decompress(&cinfo);
1893 jpeg_destroy_decompress(&cinfo);
1895 jpeg_destroy_decompress(&cinfo);
1902 if (file_extension !=
".jpg" && file_extension !=
".JPG" && file_extension !=
".jpeg" && file_extension !=
".JPEG") {
1903 helios_runtime_error(
"ERROR (Context::getImageResolutionJPEG): File " + filename +
" is not JPEG format.");
1906 jpeg_decompress_struct cinfo{};
1908 jpeg_error_mgr jerr{};
1909 std::unique_ptr<FILE, int (*)(FILE *)> infile(fopen(filename.c_str(),
"rb"), fclose);
1911 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.");
1914 cinfo.err = jpeg_std_error(&jerr);
1915 jerr.error_exit = jpg_error_exit;
1918 jpeg_create_decompress(&cinfo);
1919 jpeg_stdio_src(&cinfo, infile.get());
1920 (void) jpeg_read_header(&cinfo, (
boolean) 1);
1921 (void) jpeg_start_decompress(&cinfo);
1923 jpeg_destroy_decompress(&cinfo);
1925 jpeg_destroy_decompress(&cinfo);
1929 return make_int2(cinfo.output_width, cinfo.output_height);
1934 std::string filename = a_filename;
1936 if (file_extension !=
".jpg" && file_extension !=
".JPG" && file_extension !=
".jpeg" && file_extension !=
".JPEG") {
1937 filename.append(
".jpeg");
1940 if (pixel_data.size() != width * height) {
1941 helios_runtime_error(
"ERROR (Context::writeJPEG): Pixel data does not have size of width*height.");
1944 const uint bsize = 3 * width * height;
1945 std::vector<unsigned char> screen_shot_trans(bsize);
1948 for (
size_t i = 0; i < width * height; i++) {
1949 screen_shot_trans.at(ii) = (
unsigned char) round(
clamp(pixel_data.at(i).r, 0.f, 1.f) * 255);
1950 screen_shot_trans.at(ii + 1) = (
unsigned char) round(
clamp(pixel_data.at(i).g, 0.f, 1.f) * 255);
1951 screen_shot_trans.at(ii + 2) = (
unsigned char) round(
clamp(pixel_data.at(i).b, 0.f, 1.f) * 255);
1955 struct jpeg_compress_struct cinfo{};
1957 struct jpeg_error_mgr jerr{};
1959 cinfo.err = jpeg_std_error(&jerr);
1960 jerr.error_exit = jpg_error_exit;
1962 JSAMPROW row_pointer;
1965 std::unique_ptr<FILE, int (*)(FILE *)> outfile(fopen(filename.c_str(),
"wb"), fclose);
1967 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.");
1970 jpeg_create_compress(&cinfo);
1971 jpeg_stdio_dest(&cinfo, outfile.get());
1973 cinfo.image_width = width;
1974 cinfo.image_height = height;
1975 cinfo.input_components = 3;
1976 cinfo.in_color_space = JCS_RGB;
1978 jpeg_set_defaults(&cinfo);
1980 jpeg_set_quality(&cinfo, 100, (
boolean) 1 );
1982 jpeg_start_compress(&cinfo, (
boolean) 1);
1985 row_stride = width * 3;
1987 while (cinfo.next_scanline < cinfo.image_height) {
1988 row_pointer = (JSAMPROW) &screen_shot_trans[(cinfo.image_height - cinfo.next_scanline - 1) * row_stride];
1989 (void) jpeg_write_scanlines(&cinfo, &row_pointer, 1);
1992 jpeg_finish_compress(&cinfo);
1993 jpeg_destroy_compress(&cinfo);
1995 jpeg_destroy_compress(&cinfo);
2401 if (points.empty()) {
2406 if (points.size() == 1) {
2412 constexpr float EPSILON = 1.0E-5f;
2413 bool is_likely_increasing = points.size() < 2 || points[1].x > points[0].x;
2415 if (is_likely_increasing) {
2417 bool is_valid_increasing =
true;
2418 for (
size_t i = 1; i < points.size() && is_valid_increasing; ++i) {
2419 float deltaX = points[i].x - points[i - 1].x;
2420 if (deltaX <= EPSILON) {
2421 is_valid_increasing =
false;
2425 if (is_valid_increasing) {
2427 if (x <= points.front().x) {
2428 return points.front().y;
2430 if (x >= points.back().x) {
2431 return points.back().y;
2435 auto it = std::lower_bound(points.begin(), points.end(), x, [](
const vec2 &point,
float value) { return point.x < value; });
2437 size_t upper_idx = std::distance(points.begin(), it);
2438 size_t lower_idx = upper_idx - 1;
2440 const vec2 &p1 = points[lower_idx];
2441 const vec2 &p2 = points[upper_idx];
2444 float t = (x - p1.
x) / (p2.
x - p1.
x);
2445 return p1.
y + t * (p2.
y - p1.
y);
2450 bool is_increasing =
true;
2451 bool is_decreasing =
true;
2453 for (
size_t i = 1; i < points.size(); ++i) {
2454 float deltaX = points[i].x - points[i - 1].x;
2456 if (std::abs(deltaX) < EPSILON) {
2461 is_decreasing =
false;
2463 is_increasing =
false;
2467 if (!is_increasing && !is_decreasing) {
2468 helios_runtime_error(
"ERROR (interp1): X points must be monotonic (either all increasing or all decreasing).");
2472 if (is_decreasing) {
2473 if (x >= points.front().x) {
2474 return points.front().y;
2476 if (x <= points.back().x) {
2477 return points.back().y;
2481 auto it = std::lower_bound(points.begin(), points.end(), x, [](
const vec2 &point,
float value) { return point.x > value; });
2483 size_t upper_idx = std::distance(points.begin(), it);
2486 size_t lower_idx = upper_idx - 1;
2488 const vec2 &p1 = points[lower_idx];
2489 const vec2 &p2 = points[upper_idx];
2492 float t = (x - p1.
x) / (p2.
x - p1.
x);
2493 return p1.
y + t * (p2.
y - p1.
y);
2530 if (output_path.empty()) {
2534 std::filesystem::path output_path_fs = output_path;
2536 std::string output_file = output_path_fs.filename().string();
2537 std::string output_file_ext = output_path_fs.extension().string();
2538 std::string output_dir = output_path_fs.parent_path().string();
2540 if (output_file.empty()) {
2543 if (output_dir.find_last_of(
'/') != output_dir.length() - 1) {
2549 if (!output_dir.empty() && !std::filesystem::exists(output_dir)) {
2550 if (!std::filesystem::create_directory(output_dir)) {
2555 if (!output_file.empty() && !allowable_file_extensions.empty()) {
2557 bool valid_extension =
false;
2558 for (
const auto &ext: allowable_file_extensions) {
2559 if (output_file_ext == ext) {
2560 valid_extension =
true;
2564 if (!valid_extension) {
2614 std::filesystem::path filepath(filename);
2615 if (filepath.is_absolute()) {
2616 if (std::filesystem::exists(filepath)) {
2617 return std::filesystem::canonical(filepath);
2619 helios_runtime_error(
"ERROR (helios::resolveFilePath): Absolute file path " + filename +
" does not exist.");
2624 std::filesystem::path currentDirPath = std::filesystem::current_path() / filename;
2625 if (std::filesystem::exists(currentDirPath)) {
2626 return std::filesystem::canonical(currentDirPath);
2631 std::filesystem::path buildDirPath = std::filesystem::path(buildDir) / filename;
2633 if (std::filesystem::exists(buildDirPath)) {
2634 return std::filesystem::canonical(buildDirPath);
2638 helios_runtime_error(
"ERROR (helios::resolveFilePath): Could not locate asset file: " + filename +
" (checked: " + currentDirPath.string() +
" and " + buildDirPath.string() +
"). " +
2639 "Ensure file exists relative to current directory or HELIOS_BUILD path.");