// This simple IO library works the Halide::Buffer type or any // other image type with the same API. #ifndef HALIDE_IMAGE_IO_H #define HALIDE_IMAGE_IO_H #include #include #include #include #include #include #include #include #include #include #include #ifndef HALIDE_NO_PNG #include "png.h" #endif #ifndef HALIDE_NO_JPEG #ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX #endif #include #endif #include "jpeglib.h" #endif #include "HalideRuntime.h" // for halide_type_t namespace Halide { namespace Tools { struct FormatInfo { halide_type_t type; int dimensions; bool operator<(const FormatInfo &other) const { if (type.code < other.type.code) { return true; } else if (type.code > other.type.code) { return false; } if (type.bits < other.type.bits) { return true; } else if (type.bits > other.type.bits) { return false; } if (type.lanes < other.type.lanes) { return true; } else if (type.lanes > other.type.lanes) { return false; } return (dimensions < other.dimensions); } }; namespace Internal { // Must be constexpr to allow use in case clauses. inline constexpr int halide_type_code(halide_type_code_t code, int bits) { return (((int)code) << 8) | bits; } typedef bool (*CheckFunc)(bool condition, const char *msg); inline bool CheckFail(bool condition, const char *msg) { if (!condition) { fprintf(stderr, "%s\n", msg); exit(-1); } return condition; } inline bool CheckReturn(bool condition, const char *msg) { return condition; } template To convert(const From &from); // Convert to bool template<> inline bool convert(const bool &in) { return in; } template<> inline bool convert(const uint8_t &in) { return in != 0; } template<> inline bool convert(const uint16_t &in) { return in != 0; } template<> inline bool convert(const uint32_t &in) { return in != 0; } template<> inline bool convert(const uint64_t &in) { return in != 0; } template<> inline bool convert(const int8_t &in) { return in != 0; } template<> inline bool convert(const int16_t &in) { return in != 0; } template<> inline bool convert(const int32_t &in) { return in != 0; } template<> inline bool convert(const int64_t &in) { return in != 0; } template<> inline bool convert(const float &in) { return in != 0; } template<> inline bool convert(const double &in) { return in != 0; } // Convert to u8 template<> inline uint8_t convert(const bool &in) { return in; } template<> inline uint8_t convert(const uint8_t &in) { return in; } template<> inline uint8_t convert(const uint16_t &in) { uint32_t tmp = (uint32_t)(in) + 0x80; // Fast approximation of div-by-257: see http://research.swtch.com/divmult return ((tmp * 255 + 255) >> 16); } template<> inline uint8_t convert(const uint32_t &in) { return (uint8_t)((((uint64_t)in) + 0x00808080) / 0x01010101); } // uint64 -> 8 just discards the lower 32 bits: if you were expecting more precision, well, sorry template<> inline uint8_t convert(const uint64_t &in) { return convert(uint32_t(in >> 32)); } template<> inline uint8_t convert(const int8_t &in) { return convert(in); } template<> inline uint8_t convert(const int16_t &in) { return convert(in); } template<> inline uint8_t convert(const int32_t &in) { return convert(in); } template<> inline uint8_t convert(const int64_t &in) { return convert(in); } template<> inline uint8_t convert(const float &in) { return (uint8_t)(in * 255.0f + 0.5f); } template<> inline uint8_t convert(const double &in) { return (uint8_t)(in * 255.0 + 0.5); } // Convert to u16 template<> inline uint16_t convert(const bool &in) { return in; } template<> inline uint16_t convert(const uint8_t &in) { return uint16_t(in) * 0x0101; } template<> inline uint16_t convert(const uint16_t &in) { return in; } template<> inline uint16_t convert(const uint32_t &in) { return in >> 16; } template<> inline uint16_t convert(const uint64_t &in) { return in >> 48; } template<> inline uint16_t convert(const int8_t &in) { return convert(in); } template<> inline uint16_t convert(const int16_t &in) { return convert(in); } template<> inline uint16_t convert(const int32_t &in) { return convert(in); } template<> inline uint16_t convert(const int64_t &in) { return convert(in); } template<> inline uint16_t convert(const float &in) { return (uint16_t)(in * 65535.0f + 0.5f); } template<> inline uint16_t convert(const double &in) { return (uint16_t)(in * 65535.0 + 0.5); } // Convert to u32 template<> inline uint32_t convert(const bool &in) { return in; } template<> inline uint32_t convert(const uint8_t &in) { return uint32_t(in) * 0x01010101; } template<> inline uint32_t convert(const uint16_t &in) { return uint32_t(in) * 0x00010001; } template<> inline uint32_t convert(const uint32_t &in) { return in; } template<> inline uint32_t convert(const uint64_t &in) { return (uint32_t)(in >> 32); } template<> inline uint32_t convert(const int8_t &in) { return convert(in); } template<> inline uint32_t convert(const int16_t &in) { return convert(in); } template<> inline uint32_t convert(const int32_t &in) { return convert(in); } template<> inline uint32_t convert(const int64_t &in) { return convert(in); } template<> inline uint32_t convert(const float &in) { return (uint32_t)(in * 4294967295.0 + 0.5); } template<> inline uint32_t convert(const double &in) { return (uint32_t)(in * 4294967295.0 + 0.5f); } // Convert to u64 template<> inline uint64_t convert(const bool &in) { return in; } template<> inline uint64_t convert(const uint8_t &in) { return uint64_t(in) * 0x0101010101010101LL; } template<> inline uint64_t convert(const uint16_t &in) { return uint64_t(in) * 0x0001000100010001LL; } template<> inline uint64_t convert(const uint32_t &in) { return uint64_t(in) * 0x0000000100000001LL; } template<> inline uint64_t convert(const uint64_t &in) { return in; } template<> inline uint64_t convert(const int8_t &in) { return convert(in); } template<> inline uint64_t convert(const int16_t &in) { return convert(in); } template<> inline uint64_t convert(const int32_t &in) { return convert(in); } template<> inline uint64_t convert(const int64_t &in) { return convert(in); } template<> inline uint64_t convert(const float &in) { return convert((uint32_t)(in * 4294967295.0 + 0.5)); } template<> inline uint64_t convert(const double &in) { return convert((uint32_t)(in * 4294967295.0 + 0.5)); } // Convert to i8 template<> inline int8_t convert(const bool &in) { return in; } template<> inline int8_t convert(const uint8_t &in) { return convert(in); } template<> inline int8_t convert(const uint16_t &in) { return convert(in); } template<> inline int8_t convert(const uint32_t &in) { return convert(in); } template<> inline int8_t convert(const uint64_t &in) { return convert(in); } template<> inline int8_t convert(const int8_t &in) { return convert(in); } template<> inline int8_t convert(const int16_t &in) { return convert(in); } template<> inline int8_t convert(const int32_t &in) { return convert(in); } template<> inline int8_t convert(const int64_t &in) { return convert(in); } template<> inline int8_t convert(const float &in) { return convert(in); } template<> inline int8_t convert(const double &in) { return convert(in); } // Convert to i16 template<> inline int16_t convert(const bool &in) { return in; } template<> inline int16_t convert(const uint8_t &in) { return convert(in); } template<> inline int16_t convert(const uint16_t &in) { return convert(in); } template<> inline int16_t convert(const uint32_t &in) { return convert(in); } template<> inline int16_t convert(const uint64_t &in) { return convert(in); } template<> inline int16_t convert(const int8_t &in) { return convert(in); } template<> inline int16_t convert(const int16_t &in) { return convert(in); } template<> inline int16_t convert(const int32_t &in) { return convert(in); } template<> inline int16_t convert(const int64_t &in) { return convert(in); } template<> inline int16_t convert(const float &in) { return convert(in); } template<> inline int16_t convert(const double &in) { return convert(in); } // Convert to i32 template<> inline int32_t convert(const bool &in) { return in; } template<> inline int32_t convert(const uint8_t &in) { return convert(in); } template<> inline int32_t convert(const uint16_t &in) { return convert(in); } template<> inline int32_t convert(const uint32_t &in) { return convert(in); } template<> inline int32_t convert(const uint64_t &in) { return convert(in); } template<> inline int32_t convert(const int8_t &in) { return convert(in); } template<> inline int32_t convert(const int16_t &in) { return convert(in); } template<> inline int32_t convert(const int32_t &in) { return convert(in); } template<> inline int32_t convert(const int64_t &in) { return convert(in); } template<> inline int32_t convert(const float &in) { return convert(in); } template<> inline int32_t convert(const double &in) { return convert(in); } // Convert to i64 template<> inline int64_t convert(const bool &in) { return in; } template<> inline int64_t convert(const uint8_t &in) { return convert(in); } template<> inline int64_t convert(const uint16_t &in) { return convert(in); } template<> inline int64_t convert(const uint32_t &in) { return convert(in); } template<> inline int64_t convert(const uint64_t &in) { return convert(in); } template<> inline int64_t convert(const int8_t &in) { return convert(in); } template<> inline int64_t convert(const int16_t &in) { return convert(in); } template<> inline int64_t convert(const int32_t &in) { return convert(in); } template<> inline int64_t convert(const int64_t &in) { return convert(in); } template<> inline int64_t convert(const float &in) { return convert(in); } template<> inline int64_t convert(const double &in) { return convert(in); } // Convert to f32 template<> inline float convert(const bool &in) { return in; } template<> inline float convert(const uint8_t &in) { return in / 255.0f; } template<> inline float convert(const uint16_t &in) { return in / 65535.0f; } template<> inline float convert(const uint32_t &in) { return (float)(in / 4294967295.0); } template<> inline float convert(const uint64_t &in) { return convert(uint32_t(in >> 32)); } template<> inline float convert(const int8_t &in) { return convert(in); } template<> inline float convert(const int16_t &in) { return convert(in); } template<> inline float convert(const int32_t &in) { return convert(in); } template<> inline float convert(const int64_t &in) { return convert(in); } template<> inline float convert(const float &in) { return in; } template<> inline float convert(const double &in) { return (float)in; } // Convert to f64 template<> inline double convert(const bool &in) { return in; } template<> inline double convert(const uint8_t &in) { return in / 255.0f; } template<> inline double convert(const uint16_t &in) { return in / 65535.0f; } template<> inline double convert(const uint32_t &in) { return (double)(in / 4294967295.0); } template<> inline double convert(const uint64_t &in) { return convert(uint32_t(in >> 32)); } template<> inline double convert(const int8_t &in) { return convert(in); } template<> inline double convert(const int16_t &in) { return convert(in); } template<> inline double convert(const int32_t &in) { return convert(in); } template<> inline double convert(const int64_t &in) { return convert(in); } template<> inline double convert(const float &in) { return (double)in; } template<> inline double convert(const double &in) { return in; } inline std::string to_lowercase(const std::string &s) { std::string r = s; std::transform(r.begin(), r.end(), r.begin(), ::tolower); return r; } inline std::string get_lowercase_extension(const std::string &path) { size_t last_dot = path.rfind('.'); if (last_dot == std::string::npos) { return ""; } return to_lowercase(path.substr(last_dot + 1)); } template ElemType read_big_endian(const uint8_t *src); template<> inline uint8_t read_big_endian(const uint8_t *src) { return *src; } template<> inline uint16_t read_big_endian(const uint8_t *src) { return (((uint16_t)src[0]) << 8) | ((uint16_t)src[1]); } template void write_big_endian(const ElemType &src, uint8_t *dst); template<> inline void write_big_endian(const uint8_t &src, uint8_t *dst) { *dst = src; } template<> inline void write_big_endian(const uint16_t &src, uint8_t *dst) { dst[0] = src >> 8; dst[1] = src & 0xff; } struct FileOpener { FileOpener(const std::string &filename, const char *mode) : f(fopen(filename.c_str(), mode)) { // nothing } ~FileOpener() { if (f != nullptr) { fclose(f); } } // read a line of data, skipping lines that begin with '#" char *read_line(char *buf, int maxlen) { char *status; do { status = fgets(buf, maxlen, f); } while (status && buf[0] == '#'); return (status); } // call read_line and to a sscanf() on it int scan_line(const char *fmt, ...) { char buf[1024]; if (!read_line(buf, 1024)) { return 0; } va_list args; va_start(args, fmt); int result = vsscanf(buf, fmt, args); va_end(args); return result; } bool read_bytes(void *data, size_t count) { return fread(data, 1, count, f) == count; } template bool read_array(T (&data)[N]) { return read_bytes(&data[0], sizeof(T) * N); } template bool read_vector(std::vector *v) { return read_bytes(v->data(), v->size() * sizeof(T)); } bool write_bytes(const void *data, size_t count) { return fwrite(data, 1, count, f) == count; } template bool write_vector(const std::vector &v) { return write_bytes(v.data(), v.size() * sizeof(T)); } template bool write_array(const T (&data)[N]) { return write_bytes(&data[0], sizeof(T) * N); } FILE *const f; }; // Read a row of ElemTypes from a byte buffer and copy them into a specific image row. // Multibyte elements are assumed to be big-endian. template void read_big_endian_row(const uint8_t *src, int y, ImageType *im) { auto im_typed = im->template as(); const int xmin = im_typed.dim(0).min(); const int xmax = im_typed.dim(0).max(); if (im_typed.dimensions() > 2) { const int cmin = im_typed.dim(2).min(); const int cmax = im_typed.dim(2).max(); for (int x = xmin; x <= xmax; x++) { for (int c = cmin; c <= cmax; c++) { im_typed(x, y, c + cmin) = read_big_endian(src); src += sizeof(ElemType); } } } else { for (int x = xmin; x <= xmax; x++) { im_typed(x, y) = read_big_endian(src); src += sizeof(ElemType); } } } // Copy a row from an image into a byte buffer. // Multibyte elements are written in big-endian layout. template void write_big_endian_row(const ImageType &im, int y, uint8_t *dst) { auto im_typed = im.template as::type>(); const int xmin = im_typed.dim(0).min(); const int xmax = im_typed.dim(0).max(); if (im_typed.dimensions() > 2) { const int cmin = im_typed.dim(2).min(); const int cmax = im_typed.dim(2).max(); for (int x = xmin; x <= xmax; x++) { for (int c = cmin; c <= cmax; c++) { write_big_endian(im_typed(x, y, c), dst); dst += sizeof(ElemType); } } } else { for (int x = xmin; x <= xmax; x++) { write_big_endian(im_typed(x, y), dst); dst += sizeof(ElemType); } } } #ifndef HALIDE_NO_PNG template bool load_png(const std::string &filename, ImageType *im) { static_assert(!ImageType::has_static_halide_type, ""); /* open file and test for it being a png */ Internal::FileOpener f(filename, "rb"); if (!check(f.f != nullptr, "File could not be opened for reading")) { return false; } png_byte header[8]; if (!check(f.read_array(header), "File ended before end of header")) { return false; } if (!check(!png_sig_cmp(header, 0, 8), "File is not recognized as a PNG file")) { return false; } /* initialize stuff */ png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!check(png_ptr != nullptr, "png_create_read_struct failed")) { return false; } png_infop info_ptr = png_create_info_struct(png_ptr); if (!check(info_ptr != nullptr, "png_create_info_struct failed")) { return false; } if (!check(!setjmp(png_jmpbuf(png_ptr)), "Error loading PNG")) { return false; } png_init_io(png_ptr, f.f); png_set_sig_bytes(png_ptr, 8); png_read_info(png_ptr, info_ptr); const int width = png_get_image_width(png_ptr, info_ptr); const int height = png_get_image_height(png_ptr, info_ptr); const int channels = png_get_channels(png_ptr, info_ptr); const int bit_depth = png_get_bit_depth(png_ptr, info_ptr); const halide_type_t im_type(halide_type_uint, bit_depth); std::vector im_dimensions = {width, height}; if (channels != 1) { im_dimensions.push_back(channels); } *im = ImageType(im_type, im_dimensions); png_read_update_info(png_ptr, info_ptr); auto copy_to_image = bit_depth == 8 ? Internal::read_big_endian_row : Internal::read_big_endian_row; std::vector row(png_get_rowbytes(png_ptr, info_ptr)); const int ymin = im->dim(1).min(); const int ymax = im->dim(1).max(); for (int y = ymin; y <= ymax; ++y) { png_read_row(png_ptr, row.data(), nullptr); copy_to_image(row.data(), y, im); } png_destroy_read_struct(&png_ptr, &info_ptr, NULL); return true; } inline const std::set &query_png() { static std::set info = { {halide_type_t(halide_type_uint, 8), 2}, {halide_type_t(halide_type_uint, 16), 2}, {halide_type_t(halide_type_uint, 8), 3}, {halide_type_t(halide_type_uint, 16), 3}}; return info; } // "im" is not const-ref because copy_to_host() is not const. template bool save_png(ImageType &im, const std::string &filename) { static_assert(!ImageType::has_static_halide_type, ""); im.copy_to_host(); const int width = im.width(); const int height = im.height(); const int channels = im.channels(); if (!check(channels >= 1 && channels <= 4, "Can't write PNG files that have other than 1, 2, 3, or 4 channels")) { return false; } const png_byte color_types[4] = { PNG_COLOR_TYPE_GRAY, PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_RGB, PNG_COLOR_TYPE_RGB_ALPHA}; png_byte color_type = color_types[channels - 1]; // open file Internal::FileOpener f(filename, "wb"); if (!check(f.f != nullptr, "[write_png_file] File could not be opened for writing")) { return false; } // initialize stuff png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!check(png_ptr != nullptr, "[write_png_file] png_create_write_struct failed")) { return false; } png_infop info_ptr = png_create_info_struct(png_ptr); if (!check(info_ptr != nullptr, "[write_png_file] png_create_info_struct failed")) { return false; } if (!check(!setjmp(png_jmpbuf(png_ptr)), "Error saving PNG")) { return false; } png_init_io(png_ptr, f.f); const halide_type_t im_type = im.type(); const int bit_depth = im_type.bits; png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); png_write_info(png_ptr, info_ptr); auto copy_from_image = bit_depth == 8 ? Internal::write_big_endian_row : Internal::write_big_endian_row; std::vector row(png_get_rowbytes(png_ptr, info_ptr)); const int ymin = im.dim(1).min(); const int ymax = im.dim(1).max(); for (int y = ymin; y <= ymax; ++y) { copy_from_image(im, y, row.data()); png_write_row(png_ptr, row.data()); } png_write_end(png_ptr, NULL); png_destroy_write_struct(&png_ptr, &info_ptr); return true; } #endif // not HALIDE_NO_PNG template bool read_pnm_header(Internal::FileOpener &f, const std::string &hdr_fmt, int *width, int *height, int *bit_depth) { if (!check(f.f != nullptr, "File could not be opened for reading")) { return false; } char header[256]; if (!check(f.scan_line("%255s", header) == 1, "Could not read header")) { return false; } if (!check(to_lowercase(hdr_fmt) == to_lowercase(header), "Unexpected file header")) { return false; } if (!check(f.scan_line("%d %d\n", width, height) == 2, "Could not read width and height")) { return false; } int maxval; if (!check(f.scan_line("%d", &maxval) == 1, "Could not read max value")) { return false; } if (maxval == 255) { *bit_depth = 8; } else if (maxval == 65535) { *bit_depth = 16; } else { *bit_depth = 0; return check(false, "Invalid bit depth"); } return true; } template bool load_pnm(const std::string &filename, int channels, ImageType *im) { static_assert(!ImageType::has_static_halide_type, ""); const char *hdr_fmt = channels == 3 ? "P6" : "P5"; Internal::FileOpener f(filename, "rb"); int width, height, bit_depth; if (!Internal::read_pnm_header(f, hdr_fmt, &width, &height, &bit_depth)) { return false; } const halide_type_t im_type(halide_type_uint, bit_depth); std::vector im_dimensions = {width, height}; if (channels > 1) { im_dimensions.push_back(channels); } *im = ImageType(im_type, im_dimensions); auto copy_to_image = bit_depth == 8 ? Internal::read_big_endian_row : Internal::read_big_endian_row; std::vector row(width * channels * (bit_depth / 8)); const int ymin = im->dim(1).min(); const int ymax = im->dim(1).max(); for (int y = ymin; y <= ymax; ++y) { if (!check(f.read_vector(&row), "Could not read data")) { return false; } copy_to_image(row.data(), y, im); } return true; } template bool save_pnm(ImageType &im, const int channels, const std::string &filename) { static_assert(!ImageType::has_static_halide_type, ""); if (!check(im.channels() == channels, "Wrong number of channels")) { return false; } im.copy_to_host(); const halide_type_t im_type = im.type(); const int width = im.width(); const int height = im.height(); const int bit_depth = im_type.bits; Internal::FileOpener f(filename, "wb"); if (!check(f.f != nullptr, "File could not be opened for writing")) { return false; } const char *hdr_fmt = channels == 3 ? "P6" : "P5"; fprintf(f.f, "%s\n%d %d\n%d\n", hdr_fmt, width, height, (1 << bit_depth) - 1); auto copy_from_image = bit_depth == 8 ? Internal::write_big_endian_row : Internal::write_big_endian_row; std::vector row(width * channels * (bit_depth / 8)); const int ymin = im.dim(1).min(); const int ymax = im.dim(1).max(); for (int y = ymin; y <= ymax; ++y) { copy_from_image(im, y, row.data()); if (!check(f.write_vector(row), "Could not write data")) { return false; } } return true; } template bool load_pgm(const std::string &filename, ImageType *im) { return Internal::load_pnm(filename, 1, im); } inline const std::set &query_pgm() { static std::set info = { {halide_type_t(halide_type_uint, 8), 2}, {halide_type_t(halide_type_uint, 16), 2}}; return info; } // "im" is not const-ref because copy_to_host() is not const. template bool save_pgm(ImageType &im, const std::string &filename) { return Internal::save_pnm(im, 1, filename); } template bool load_ppm(const std::string &filename, ImageType *im) { return Internal::load_pnm(filename, 3, im); } inline const std::set &query_ppm() { static std::set info = { {halide_type_t(halide_type_uint, 8), 3}, {halide_type_t(halide_type_uint, 16), 3}}; return info; } // "im" is not const-ref because copy_to_host() is not const. template bool save_ppm(ImageType &im, const std::string &filename) { return Internal::save_pnm(im, 3, filename); } #ifndef HALIDE_NO_JPEG template bool load_jpg(const std::string &filename, ImageType *im) { static_assert(!ImageType::has_static_halide_type, ""); Internal::FileOpener f(filename, "rb"); if (!check(f.f != nullptr, "File could not be opened for reading")) { return false; } struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; cinfo.err = jpeg_std_error(&jerr); jpeg_create_decompress(&cinfo); jpeg_stdio_src(&cinfo, f.f); jpeg_read_header(&cinfo, TRUE); jpeg_start_decompress(&cinfo); const int width = cinfo.output_width; const int height = cinfo.output_height; const int channels = cinfo.output_components; const halide_type_t im_type(halide_type_uint, 8); std::vector im_dimensions = {width, height}; if (channels > 1) { im_dimensions.push_back(channels); } *im = ImageType(im_type, im_dimensions); auto copy_to_image = Internal::read_big_endian_row; std::vector row(width * channels); const int ymin = im->dim(1).min(); const int ymax = im->dim(1).max(); for (int y = ymin; y <= ymax; ++y) { uint8_t *src = row.data(); jpeg_read_scanlines(&cinfo, &src, 1); copy_to_image(row.data(), y, im); } jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); return true; } inline const std::set &query_jpg() { static std::set info = { {halide_type_t(halide_type_uint, 8), 2}, {halide_type_t(halide_type_uint, 8), 3}, }; return info; } template bool save_jpg(ImageType &im, const std::string &filename) { static_assert(!ImageType::has_static_halide_type, ""); im.copy_to_host(); const int width = im.width(); const int height = im.height(); const int channels = im.channels(); if (!check(channels == 1 || channels == 3, "Wrong number of channels")) { return false; } Internal::FileOpener f(filename, "wb"); if (!check(f.f != nullptr, "File could not be opened for writing")) { return false; } // TODO: Make this an argument? constexpr int quality = 99; struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); jpeg_stdio_dest(&cinfo, f.f); cinfo.image_width = width; cinfo.image_height = height; cinfo.input_components = channels; cinfo.in_color_space = (channels == 3) ? JCS_RGB : JCS_GRAYSCALE; jpeg_set_defaults(&cinfo); jpeg_set_quality(&cinfo, quality, TRUE); jpeg_start_compress(&cinfo, TRUE); auto copy_from_image = Internal::write_big_endian_row; std::vector row(width * channels); const int ymin = im.dim(1).min(); const int ymax = im.dim(1).max(); for (int y = ymin; y <= ymax; ++y) { uint8_t *dst = row.data(); copy_from_image(im, y, dst); jpeg_write_scanlines(&cinfo, &dst, 1); } jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); return true; } #endif // not HALIDE_NO_JPEG constexpr int kNumTmpCodes = 10; inline const halide_type_t *tmp_code_to_halide_type() { static const halide_type_t tmp_code_to_halide_type_[kNumTmpCodes] = { {halide_type_float, 32}, {halide_type_float, 64}, {halide_type_uint, 8}, {halide_type_int, 8}, {halide_type_uint, 16}, {halide_type_int, 16}, {halide_type_uint, 32}, {halide_type_int, 32}, {halide_type_uint, 64}, {halide_type_int, 64}}; return tmp_code_to_halide_type_; } // return true iff the buffer storage has no padding between // any elements, and is in strictly planar order. template bool buffer_is_compact_planar(ImageType &im) { const halide_type_t im_type = im.type(); const size_t elem_size = (im_type.bits / 8); if (((const uint8_t *)im.begin() + (im.number_of_elements() * elem_size)) != (const uint8_t *)im.end()) { return false; } for (int d = 1; d < im.dimensions(); ++d) { if (im.dim(d - 1).stride() > im.dim(d).stride()) { return false; } // Strides can only match if the previous dimension has extent 1 // (this can happen when artificially adding dimension(s), e.g. // to write a .tmp file) if (im.dim(d - 1).stride() == im.dim(d).stride() && im.dim(d - 1).extent() != 1) { return false; } } return true; } // ".tmp" is a file format used by the ImageStack tool (see https://github.com/abadams/ImageStack) template bool load_tmp(const std::string &filename, ImageType *im) { static_assert(!ImageType::has_static_halide_type, ""); FileOpener f(filename, "rb"); if (!check(f.f != nullptr, "File could not be opened for reading")) { return false; } int32_t header[5]; if (!check(f.read_array(header), "Count not read .tmp header")) { return false; } if (!check(header[0] > 0 && header[1] > 0 && header[2] > 0 && header[3] > 0 && header[4] >= 0 && header[4] < kNumTmpCodes, "Bad header on .tmp file")) { return false; } const halide_type_t im_type = tmp_code_to_halide_type()[header[4]]; std::vector im_dimensions = {header[0], header[1], header[2], header[3]}; *im = ImageType(im_type, im_dimensions); // This should never fail unless the default Buffer<> constructor behavior changes. if (!check(buffer_is_compact_planar(*im), "load_tmp() requires compact planar images")) { return false; } if (!check(f.read_bytes(im->begin(), im->size_in_bytes()), "Count not read .tmp payload")) { return false; } im->set_host_dirty(); return true; } inline const std::set &query_tmp() { // TMP files require exactly 4 dimensions. static std::set info = { {halide_type_t(halide_type_float, 32), 4}, {halide_type_t(halide_type_float, 64), 4}, {halide_type_t(halide_type_uint, 8), 4}, {halide_type_t(halide_type_int, 8), 4}, {halide_type_t(halide_type_uint, 16), 4}, {halide_type_t(halide_type_int, 16), 4}, {halide_type_t(halide_type_uint, 32), 4}, {halide_type_t(halide_type_int, 32), 4}, {halide_type_t(halide_type_uint, 64), 4}, {halide_type_t(halide_type_int, 64), 4}, }; return info; } template bool write_planar_payload(ImageType &im, FileOpener &f) { if (im.dimensions() == 0 || buffer_is_compact_planar(im)) { // Contiguous buffer! Write it all in one swell foop. if (!check(f.write_bytes(im.begin(), im.size_in_bytes()), "Count not write .tmp payload")) { return false; } } else { // We have to do this the hard way. int d = im.dimensions() - 1; for (int i = im.dim(d).min(); i <= im.dim(d).max(); i++) { auto slice = im.sliced(d, i); if (!write_planar_payload(slice, f)) { return false; } } } return true; } // ".tmp" is a file format used by the ImageStack tool (see https://github.com/abadams/ImageStack) template bool save_tmp(ImageType &im, const std::string &filename) { static_assert(!ImageType::has_static_halide_type, ""); im.copy_to_host(); int32_t header[5] = {1, 1, 1, 1, -1}; for (int i = 0; i < im.dimensions(); ++i) { header[i] = im.dim(i).extent(); } auto *table = tmp_code_to_halide_type(); for (int i = 0; i < kNumTmpCodes; i++) { if (im.type() == table[i]) { header[4] = i; break; } } if (!check(header[4] >= 0, "Unsupported type for .tmp file")) { return false; } FileOpener f(filename, "wb"); if (!check(f.f != nullptr, "File could not be opened for writing")) { return false; } if (!check(f.write_array(header), "Could not write .tmp header")) { return false; } if (!write_planar_payload(im, f)) { return false; } return true; } // ".mat" is the matlab level 5 format documented here: // http://www.mathworks.com/help/pdf_doc/matlab/matfile_format.pdf enum MatlabTypeCode { miINT8 = 1, miUINT8 = 2, miINT16 = 3, miUINT16 = 4, miINT32 = 5, miUINT32 = 6, miSINGLE = 7, miDOUBLE = 9, miINT64 = 12, miUINT64 = 13, miMATRIX = 14, miCOMPRESSED = 15, miUTF8 = 16, miUTF16 = 17, miUTF32 = 18 }; enum MatlabClassCode { mxCHAR_CLASS = 3, mxDOUBLE_CLASS = 6, mxSINGLE_CLASS = 7, mxINT8_CLASS = 8, mxUINT8_CLASS = 9, mxINT16_CLASS = 10, mxUINT16_CLASS = 11, mxINT32_CLASS = 12, mxUINT32_CLASS = 13, mxINT64_CLASS = 14, mxUINT64_CLASS = 15 }; template bool load_mat(const std::string &filename, ImageType *im) { static_assert(!ImageType::has_static_halide_type, ""); FileOpener f(filename, "rb"); if (!check(f.f != nullptr, "File could not be opened for reading")) { return false; } uint8_t header[128]; if (!check(f.read_array(header), "Could not read .mat header\n")) { return false; } // Matrix header uint32_t matrix_header[2]; if (!check(f.read_array(matrix_header), "Could not read .mat header\n")) { return false; } if (!check(matrix_header[0] == miMATRIX, "Could not parse this .mat file: bad matrix header\n")) { return false; } // Array flags uint32_t flags[4]; if (!check(f.read_array(flags), "Could not read .mat header\n")) { return false; } if (!check(flags[0] == miUINT32 && flags[1] == 8, "Could not parse this .mat file: bad flags\n")) { return false; } // Shape uint32_t shape_header[2]; if (!check(f.read_array(shape_header), "Could not read .mat header\n")) { return false; } if (!check(shape_header[0] == miINT32, "Could not parse this .mat file: bad shape header\n")) { return false; } int dims = shape_header[1] / 4; std::vector extents(dims); if (!check(f.read_vector(&extents), "Could not read .mat header\n")) { return false; } if (dims & 1) { uint32_t padding; if (!check(f.read_bytes(&padding, 4), "Could not read .mat header\n")) { return false; } } // Skip over the name uint32_t name_header[2]; if (!check(f.read_array(name_header), "Could not read .mat header\n")) { return false; } if (name_header[0] >> 16) { // Name must be fewer than 4 chars, and so the whole name // field was stored packed into 8 bytes } else { if (!check(name_header[0] == miINT8, "Could not parse this .mat file: bad name header\n")) { return false; } std::vector scratch((name_header[1] + 7) / 8); if (!check(f.read_vector(&scratch), "Could not read .mat header\n")) { return false; } } // Payload header uint32_t payload_header[2]; if (!check(f.read_array(payload_header), "Could not read .mat header\n")) { return false; } halide_type_t type; switch (payload_header[0]) { case miINT8: type = halide_type_of(); break; case miINT16: type = halide_type_of(); break; case miINT32: type = halide_type_of(); break; case miINT64: type = halide_type_of(); break; case miUINT8: type = halide_type_of(); break; case miUINT16: type = halide_type_of(); break; case miUINT32: type = halide_type_of(); break; case miUINT64: type = halide_type_of(); break; case miSINGLE: type = halide_type_of(); break; case miDOUBLE: type = halide_type_of(); break; } *im = ImageType(type, extents); // This should never fail unless the default Buffer<> constructor behavior changes. if (!check(buffer_is_compact_planar(*im), "load_mat() requires compact planar images")) { return false; } if (!check(f.read_bytes(im->begin(), im->size_in_bytes()), "Could not read .tmp payload")) { return false; } im->set_host_dirty(); return true; } inline const std::set &query_mat() { // MAT files must have at least 2 dimensions, but there's no upper // bound. Our support arbitrarily stops at 16 dimensions. static std::set info = []() { std::set s; for (int i = 2; i < 16; i++) { s.insert({halide_type_t(halide_type_float, 32), i}); s.insert({halide_type_t(halide_type_float, 64), i}); s.insert({halide_type_t(halide_type_uint, 8), i}); s.insert({halide_type_t(halide_type_int, 8), i}); s.insert({halide_type_t(halide_type_uint, 16), i}); s.insert({halide_type_t(halide_type_int, 16), i}); s.insert({halide_type_t(halide_type_uint, 32), i}); s.insert({halide_type_t(halide_type_int, 32), i}); s.insert({halide_type_t(halide_type_uint, 64), i}); s.insert({halide_type_t(halide_type_int, 64), i}); } return s; }(); return info; } template bool save_mat(ImageType &im, const std::string &filename) { static_assert(!ImageType::has_static_halide_type, ""); im.copy_to_host(); uint32_t class_code = 0, type_code = 0; switch (im.raw_buffer()->type.code) { case halide_type_int: switch (im.raw_buffer()->type.bits) { case 8: class_code = mxINT8_CLASS; type_code = miINT8; break; case 16: class_code = mxINT16_CLASS; type_code = miINT16; break; case 32: class_code = mxINT32_CLASS; type_code = miINT32; break; case 64: class_code = mxINT64_CLASS; type_code = miINT64; break; default: check(false, "unreachable"); }; break; case halide_type_uint: switch (im.raw_buffer()->type.bits) { case 8: class_code = mxUINT8_CLASS; type_code = miUINT8; break; case 16: class_code = mxUINT16_CLASS; type_code = miUINT16; break; case 32: class_code = mxUINT32_CLASS; type_code = miUINT32; break; case 64: class_code = mxUINT64_CLASS; type_code = miUINT64; break; default: check(false, "unreachable"); }; break; case halide_type_float: switch (im.raw_buffer()->type.bits) { case 16: check(false, "float16 not supported by .mat"); break; case 32: class_code = mxSINGLE_CLASS; type_code = miSINGLE; break; case 64: class_code = mxDOUBLE_CLASS; type_code = miDOUBLE; break; default: check(false, "unreachable"); }; break; case halide_type_bfloat: check(false, "bfloat not supported by .mat"); break; default: check(false, "unreachable"); } FileOpener f(filename, "wb"); if (!check(f.f != nullptr, "File could not be opened for writing")) { return false; } // Pick a name for the array size_t idx = filename.rfind('.'); std::string name = filename.substr(0, idx); idx = filename.rfind('/'); if (idx != std::string::npos) { name = name.substr(idx + 1); } // Matlab variable names conform to similar rules as C if (name.empty() || !std::isalpha(name[0])) { name = "v" + name; } for (size_t i = 0; i < name.size(); i++) { if (!std::isalnum(name[i])) { name[i] = '_'; } } uint32_t name_size = (int)name.size(); while (name.size() & 0x7) name += '\0'; char header[128] = "MATLAB 5.0 MAT-file, produced by Halide"; int len = strlen(header); memset(header + len, ' ', sizeof(header) - len); // Version *((uint16_t *)(header + 124)) = 0x0100; // Endianness check header[126] = 'I'; header[127] = 'M'; uint64_t payload_bytes = im.size_in_bytes(); if (!check((payload_bytes >> 32) == 0, "Buffer too large to save as .mat")) { return false; } int dims = im.dimensions(); if (dims < 2) { dims = 2; } int padded_dims = dims + (dims & 1); uint32_t padding_bytes = 7 - ((payload_bytes - 1) & 7); // Matrix header uint32_t matrix_header[2] = { miMATRIX, 40 + padded_dims * 4 + (uint32_t)name.size() + (uint32_t)payload_bytes + padding_bytes}; // Array flags uint32_t flags[4] = { miUINT32, 8, class_code, 1}; // Shape int32_t shape[2] = { miINT32, im.dimensions() * 4, }; std::vector extents(im.dimensions()); for (int d = 0; d < im.dimensions(); d++) { extents[d] = im.dim(d).extent(); } while ((int)extents.size() < dims) { extents.push_back(1); } while ((int)extents.size() < padded_dims) { extents.push_back(0); } // Name uint32_t name_header[2] = { miINT8, name_size}; // Payload header uint32_t payload_header[2] = { type_code, (uint32_t)payload_bytes}; bool success = f.write_array(header) && f.write_array(matrix_header) && f.write_array(flags) && f.write_array(shape) && f.write_vector(extents) && f.write_array(name_header) && f.write_bytes(&name[0], name.size()) && f.write_array(payload_header); if (!check(success, "Could not write .mat header")) { return false; } if (!write_planar_payload(im, f)) { return false; } // Padding if (!check(padding_bytes < 8, "Too much padding!\n")) { return false; } uint64_t padding = 0; if (!f.write_bytes(&padding, padding_bytes)) { return false; } return true; } template bool load_tiff(const std::string &filename, ImageType *im) { static_assert(!ImageType::has_static_halide_type, ""); check(false, "Reading TIFF is not yet supported"); return false; } inline const std::set &query_tiff() { auto build_set = []() -> std::set { std::set s; for (halide_type_code_t code : {halide_type_int, halide_type_uint, halide_type_float}) { for (int bits : {8, 16, 32, 64}) { for (int dims : {1, 2, 3, 4}) { if (code == halide_type_float && bits < 32) { continue; } s.insert({halide_type_t(code, bits), dims}); } } } return s; }; static std::set info = build_set(); return info; } #pragma pack(push) #pragma pack(2) struct halide_tiff_tag { uint16_t tag_code; int16_t type_code; int32_t count; union { int8_t i8; int16_t i16; int32_t i32; } value; void assign16(uint16_t tag_code, int32_t count, int16_t value) { this->tag_code = tag_code; this->type_code = 3; // SHORT this->count = count; this->value.i16 = value; } void assign32(uint16_t tag_code, int32_t count, int32_t value) { this->tag_code = tag_code; this->type_code = 4; // LONG this->count = count; this->value.i32 = value; } void assign32(uint16_t tag_code, int16_t type_code, int32_t count, int32_t value) { this->tag_code = tag_code; this->type_code = type_code; this->count = count; this->value.i32 = value; } }; struct halide_tiff_header { int16_t byte_order_marker; int16_t version; int32_t ifd0_offset; int16_t entry_count; halide_tiff_tag entries[15]; int32_t ifd0_end; int32_t width_resolution[2]; int32_t height_resolution[2]; }; #pragma pack(pop) template struct ElemWriter { ElemWriter(FileOpener *f) : f(f), next(&buf[0]), ok(true) { } ~ElemWriter() { flush(); } void operator()(const ElemType &elem) { if (!ok) return; *next++ = elem; if (next == &buf[BUFFER_SIZE]) { flush(); } } void flush() { if (!ok) return; if (next > buf) { if (!f->write_bytes(buf, (next - buf) * sizeof(ElemType))) { ok = false; } next = buf; } } FileOpener *const f; ElemType buf[BUFFER_SIZE]; ElemType *next; bool ok; }; // Note that this is a fairly simpleminded TIFF writer that doesn't // do any compression. It would be desirable to (optionally) support using libtiff // here instead, which would also allow us to provide a useful implementation // for TIFF reading. template bool save_tiff(ImageType &im, const std::string &filename) { static_assert(!ImageType::has_static_halide_type, ""); im.copy_to_host(); if (!check(im.dimensions() <= 4, "Can only save TIFF files with <= 4 dimensions")) { return false; } FileOpener f(filename, "wb"); if (!check(f.f != nullptr, "File could not be opened for writing")) { return false; } const size_t elements = im.number_of_elements(); halide_dimension_t shape[4]; for (int i = 0; i < im.dimensions() && i < 4; i++) { const auto &d = im.dim(i); shape[i].min = d.min(); shape[i].extent = d.extent(); shape[i].stride = d.stride(); } for (int i = im.dimensions(); i < 4; i++) { shape[i].min = 0; shape[i].extent = 1; shape[i].stride = 0; } const halide_type_t im_type = im.type(); if (!check(im_type.code >= 0 && im_type.code < 3, "Unsupported image type")) { return false; } const int32_t bytes_per_element = im_type.bytes(); const int32_t width = shape[0].extent; const int32_t height = shape[1].extent; int32_t depth = shape[2].extent; int32_t channels = shape[3].extent; if ((channels == 0 || channels == 1) && (depth < 5)) { channels = depth; depth = 1; } // TIFF sample type values are: // 0 => Signed int // 1 => Unsigned int // 2 => Floating-point static const int16_t type_code_to_tiff_sample_type[] = { 2, 1, 3}; struct halide_tiff_header header; memset(&header, 0, sizeof(header)); const int32_t MMII = 0x4d4d4949; // Select the appropriate two bytes signaling byte order automatically const char *c = (const char *)&MMII; header.byte_order_marker = (c[0] << 8) | c[1]; header.version = 42; header.ifd0_offset = offsetof(halide_tiff_header, entry_count); header.entry_count = sizeof(header.entries) / sizeof(header.entries[0]); static_assert(sizeof(halide_tiff_tag) == 12, "Unexpected halide_tiff_tag packing"); halide_tiff_tag *tag = &header.entries[0]; tag++->assign32(256, 1, width); // ImageWidth tag++->assign32(257, 1, height); // ImageLength tag++->assign16(258, 1, int16_t(bytes_per_element * 8)); // BitsPerSample tag++->assign16(259, 1, 1); // Compression -- none tag++->assign16(262, 1, channels >= 3 ? 2 : 1); // PhotometricInterpretation -- black is zero or RGB tag++->assign32(273, channels, sizeof(header)); // StripOffsets tag++->assign16(277, 1, int16_t(channels)); // SamplesPerPixel tag++->assign32(278, 1, height); // RowsPerStrip tag++->assign32(279, channels, // StripByteCounts (channels == 1) ? elements * bytes_per_element : sizeof(header) + channels * sizeof(int32_t)); // for channels > 1, this is an offset tag++->assign32(282, 5, 1, offsetof(halide_tiff_header, width_resolution)); // XResolution tag++->assign32(283, 5, 1, offsetof(halide_tiff_header, height_resolution)); // YResolution tag++->assign16(284, 1, 2); // PlanarConfiguration -- planar tag++->assign16(296, 1, 1); // ResolutionUnit -- none tag++->assign16(339, 1, type_code_to_tiff_sample_type[im_type.code]); // SampleFormat tag++->assign32(32997, 1, depth); // Image depth // Verify we used exactly the number we declared assert(tag == &header.entries[header.entry_count]); header.ifd0_end = 0; header.width_resolution[0] = 1; header.width_resolution[1] = 1; header.height_resolution[0] = 1; header.height_resolution[1] = 1; if (!check(f.write_bytes(&header, sizeof(header)), "TIFF write failed")) { return false; } if (channels > 1) { // Fill in the values for StripOffsets int32_t offset = sizeof(header) + channels * sizeof(int32_t) * 2; for (int32_t i = 0; i < channels; i++) { if (!check(f.write_bytes(&offset, sizeof(offset)), "TIFF write failed")) { return false; } offset += width * height * depth * bytes_per_element; } // Fill in the values for StripByteCounts int32_t count = width * height * depth * bytes_per_element; for (int32_t i = 0; i < channels; i++) { if (!check(f.write_bytes(&count, sizeof(count)), "TIFF write failed")) { return false; } } } // If image is dense, we can write it in one fell swoop if (elements * bytes_per_element == im.size_in_bytes()) { if (!check(f.write_bytes(im.data(), im.size_in_bytes()), "TIFF write failed")) { return false; } return true; } // Otherwise, write it out via manual traversal. #define HANDLE_CASE(CODE, BITS, TYPE) \ case halide_type_code(CODE, BITS): { \ ElemWriter ew(&f); \ im.template as().for_each_value(ew); \ if (!check(ew.ok, "TIFF write failed")) { \ return false; \ } \ break; \ } switch (halide_type_code((halide_type_code_t)im_type.code, im_type.bits)) { HANDLE_CASE(halide_type_float, 32, float) HANDLE_CASE(halide_type_float, 64, double) HANDLE_CASE(halide_type_int, 8, int8_t) HANDLE_CASE(halide_type_int, 16, int16_t) HANDLE_CASE(halide_type_int, 32, int32_t) HANDLE_CASE(halide_type_int, 64, int64_t) HANDLE_CASE(halide_type_uint, 1, bool) HANDLE_CASE(halide_type_uint, 8, uint8_t) HANDLE_CASE(halide_type_uint, 16, uint16_t) HANDLE_CASE(halide_type_uint, 32, uint32_t) HANDLE_CASE(halide_type_uint, 64, uint64_t) // Note that we don't attempt to handle halide_type_handle here. default: assert(false && "Unsupported type"); return false; } #undef HANDLE_CASE return true; } // Given something like ImageType, produce typedef ImageType template struct ImageTypeWithElemType { using type = decltype(std::declval().template as()); }; // Given something like ImageType, produce typedef ImageType template struct ImageTypeWithConstElemType { using type = decltype(std::declval().template as::type>()); }; template struct ImageIO { using ConstImageType = typename ImageTypeWithConstElemType::type; std::function load; std::function save; std::function &()> query; }; template bool find_imageio(const std::string &filename, ImageIO *result) { static_assert(!ImageType::has_static_halide_type, ""); using ConstImageType = typename ImageTypeWithConstElemType::type; const std::map> m = { #ifndef HALIDE_NO_JPEG {"jpeg", {load_jpg, save_jpg, query_jpg}}, {"jpg", {load_jpg, save_jpg, query_jpg}}, #endif {"pgm", {load_pgm, save_pgm, query_pgm}}, #ifndef HALIDE_NO_PNG {"png", {load_png, save_png, query_png}}, #endif {"ppm", {load_ppm, save_ppm, query_ppm}}, {"tmp", {load_tmp, save_tmp, query_tmp}}, {"mat", {load_mat, save_mat, query_mat}}, {"tiff", {load_tiff, save_tiff, query_tiff}}, }; std::string ext = Internal::get_lowercase_extension(filename); auto it = m.find(ext); if (it != m.end()) { *result = it->second; return true; } std::string err = "unsupported file extension \"" + ext + "\", supported are:"; for (auto &it : m) { err += " " + it.first; } err += "\n"; return check(false, err.c_str()); } template FormatInfo best_save_format(const ImageType &im, const std::set &info) { // A bit ad hoc, but will do for now: // Perfect score is zero (exact match). // The larger the score, the worse the match. int best_score = 0x7fffffff; FormatInfo best{}; const halide_type_t im_type = im.type(); const int im_dimensions = im.dimensions(); for (auto &f : info) { int score = 0; // If format has too-few dimensions, that's very bad. score += std::max(0, im_dimensions - f.dimensions) * 1024; // If format has too-few bits, that's pretty bad. score += std::max(0, im_type.bits - f.type.bits) * 8; // If format has too-many bits, that's a little bad. score += std::max(0, f.type.bits - im_type.bits); // If format has different code, that's a little bad. score += (f.type.code != im_type.code) ? 1 : 0; if (score < best_score) { best_score = score; best = f; } } return best; } } // namespace Internal struct ImageTypeConversion { // Convert an Image from one ElemType to another, where the src and // dst types are statically known (e.g. Buffer -> Buffer). // Note that this does conversion with scaling -- intepreting integers // as fixed-point numbers between 0 and 1 -- not merely C-style casting. // // You'd normally call this with an explicit type for DstElemType and // allow ImageType to be inferred, e.g. // Buffer src = ...; // Buffer dst = convert_image(src); template::value>::type * = nullptr> static auto convert_image(const ImageType &src) -> typename Internal::ImageTypeWithElemType::type { // The enable_if ensures this will never fire; this is here primarily // as documentation and a backstop against breakage. static_assert(ImageType::has_static_halide_type, "This variant of convert_image() requires a statically-typed image"); using SrcImageType = ImageType; using SrcElemType = typename SrcImageType::ElemType; using DstImageType = typename Internal::ImageTypeWithElemType::type; DstImageType dst = DstImageType::make_with_shape_of(src); const auto converter = [](DstElemType &dst_elem, SrcElemType src_elem) { dst_elem = Internal::convert(src_elem); }; // TODO: do we need src.copy_to_host() here? dst.for_each_value(converter, src); dst.set_host_dirty(); return dst; } // Convert an Image from one ElemType to another, where the dst type is statically // known but the src type is not (e.g. Buffer<> -> Buffer). // You'd normally call this with an explicit type for DstElemType and // allow ImageType to be inferred, e.g. // Buffer src = ...; // Buffer dst = convert_image(src); template::value>::type * = nullptr> static auto convert_image(const ImageType &src) -> typename Internal::ImageTypeWithElemType::type { // The enable_if ensures this will never fire; this is here primarily // as documentation and a backstop against breakage. static_assert(!ImageType::has_static_halide_type, "This variant of convert_image() requires a dynamically-typed image"); const halide_type_t src_type = src.type(); switch (Internal::halide_type_code((halide_type_code_t)src_type.code, src_type.bits)) { case Internal::halide_type_code(halide_type_float, 32): return convert_image(src.template as()); case Internal::halide_type_code(halide_type_float, 64): return convert_image(src.template as()); case Internal::halide_type_code(halide_type_int, 8): return convert_image(src.template as()); case Internal::halide_type_code(halide_type_int, 16): return convert_image(src.template as()); case Internal::halide_type_code(halide_type_int, 32): return convert_image(src.template as()); case Internal::halide_type_code(halide_type_int, 64): return convert_image(src.template as()); case Internal::halide_type_code(halide_type_uint, 1): return convert_image(src.template as()); case Internal::halide_type_code(halide_type_uint, 8): return convert_image(src.template as()); case Internal::halide_type_code(halide_type_uint, 16): return convert_image(src.template as()); case Internal::halide_type_code(halide_type_uint, 32): return convert_image(src.template as()); case Internal::halide_type_code(halide_type_uint, 64): return convert_image(src.template as()); default: assert(false && "Unsupported type"); using DstImageType = typename Internal::ImageTypeWithElemType::type; return DstImageType(); } } // Convert an Image from one ElemType to another, where the src type // is statically known but the dst type is not // (e.g. Buffer -> Buffer<>(halide_type_t)). template::value>::type * = nullptr> static auto convert_image(const ImageType &src, const halide_type_t &dst_type) -> typename Internal::ImageTypeWithElemType::type { // The enable_if ensures this will never fire; this is here primarily // as documentation and a backstop against breakage. static_assert(ImageType::has_static_halide_type, "This variant of convert_image() requires a statically-typed image"); // Call the appropriate static-to-static conversion routine // based on the desired dst type. switch (Internal::halide_type_code((halide_type_code_t)dst_type.code, dst_type.bits)) { case Internal::halide_type_code(halide_type_float, 32): return convert_image(src); case Internal::halide_type_code(halide_type_float, 64): return convert_image(src); case Internal::halide_type_code(halide_type_int, 8): return convert_image(src); case Internal::halide_type_code(halide_type_int, 16): return convert_image(src); case Internal::halide_type_code(halide_type_int, 32): return convert_image(src); case Internal::halide_type_code(halide_type_int, 64): return convert_image(src); case Internal::halide_type_code(halide_type_uint, 1): return convert_image(src); case Internal::halide_type_code(halide_type_uint, 8): return convert_image(src); case Internal::halide_type_code(halide_type_uint, 16): return convert_image(src); case Internal::halide_type_code(halide_type_uint, 32): return convert_image(src); case Internal::halide_type_code(halide_type_uint, 64): return convert_image(src); default: assert(false && "Unsupported type"); return ImageType(); } } // Convert an Image from one ElemType to another, where neither src type // nor dst type are statically known // (e.g. Buffer<>(halide_type_t) -> Buffer<>(halide_type_t)). template::value>::type * = nullptr> static auto convert_image(const ImageType &src, const halide_type_t &dst_type) -> typename Internal::ImageTypeWithElemType::type { // The enable_if ensures this will never fire; this is here primarily // as documentation and a backstop against breakage. static_assert(!ImageType::has_static_halide_type, "This variant of convert_image() requires a dynamically-typed image"); // Sniff the runtime type of src, coerce it to that type using as<>(), // and call the static-to-dynamic variant of this method. (Note that // this forces instantiation of the complete any-to-any conversion // matrix of code.) const halide_type_t src_type = src.type(); switch (Internal::halide_type_code((halide_type_code_t)src_type.code, src_type.bits)) { case Internal::halide_type_code(halide_type_float, 32): return convert_image(src.template as(), dst_type); case Internal::halide_type_code(halide_type_float, 64): return convert_image(src.template as(), dst_type); case Internal::halide_type_code(halide_type_int, 8): return convert_image(src.template as(), dst_type); case Internal::halide_type_code(halide_type_int, 16): return convert_image(src.template as(), dst_type); case Internal::halide_type_code(halide_type_int, 32): return convert_image(src.template as(), dst_type); case Internal::halide_type_code(halide_type_int, 64): return convert_image(src.template as(), dst_type); case Internal::halide_type_code(halide_type_uint, 1): return convert_image(src.template as(), dst_type); case Internal::halide_type_code(halide_type_uint, 8): return convert_image(src.template as(), dst_type); case Internal::halide_type_code(halide_type_uint, 16): return convert_image(src.template as(), dst_type); case Internal::halide_type_code(halide_type_uint, 32): return convert_image(src.template as(), dst_type); case Internal::halide_type_code(halide_type_uint, 64): return convert_image(src.template as(), dst_type); default: assert(false && "Unsupported type"); return ImageType(); } } }; // Load the Image from the given file. // If output Image has a static type, and the loaded image cannot be stored // in such an image without losing data, fail. // Returns false upon failure. template bool load(const std::string &filename, ImageType *im) { using DynamicImageType = typename Internal::ImageTypeWithElemType::type; Internal::ImageIO imageio; if (!Internal::find_imageio(filename, &imageio)) { return false; } using DynamicImageType = typename Internal::ImageTypeWithElemType::type; DynamicImageType im_d; if (!imageio.load(filename, &im_d)) { return false; } // Allow statically-typed images to be passed as the out-param, but do // a runtime check to ensure if (ImageType::has_static_halide_type) { const halide_type_t expected_type = ImageType::static_halide_type(); if (!check(im_d.type() == expected_type, "Image loaded did not match the expected type")) { return false; } } *im = im_d.template as(); im->set_host_dirty(); return true; } // Save the Image in the format associated with the filename's extension. // If the format can't represent the Image without losing data, fail. // Returns false upon failure. template bool save(ImageType &im, const std::string &filename) { using DynamicImageType = typename Internal::ImageTypeWithElemType::type; Internal::ImageIO imageio; if (!Internal::find_imageio(filename, &imageio)) { return false; } if (!check(imageio.query().count({im.type(), im.dimensions()}) > 0, "Image cannot be saved in this format")) { return false; } // Allow statically-typed images to be passed in, but quietly pass them on // as dynamically-typed images. auto im_d = im.template as(); return imageio.save(im_d, filename); } // Return a set of FormatInfo structs that contain the legal type-and-dimensions // that can be saved in this format. Most applications won't ever need to use // this call. Returns false upon failure. template bool save_query(const std::string &filename, std::set *info) { using DynamicImageType = typename Internal::ImageTypeWithElemType::type; Internal::ImageIO imageio; if (!Internal::find_imageio(filename, &imageio)) { return false; } *info = imageio.query(); return true; } // Fancy wrapper to call load() with CheckFail, inferring the return type; // this allows you to simply use // // Image im = load_image("filename"); // // without bothering to check error results (all errors simply abort). // // Note that if the image being loaded doesn't match the static type and // dimensions of of the image on the LHS, a runtime error will occur. class load_image { public: load_image(const std::string &f) : filename(f) { } template operator ImageType() { using DynamicImageType = typename Internal::ImageTypeWithElemType::type; DynamicImageType im_d; (void)load(filename, &im_d); return im_d.template as(); } private: const std::string filename; }; // Like load_image, but quietly convert the loaded image to the type of the LHS // if necessary, discarding information if necessary. class load_and_convert_image { public: load_and_convert_image(const std::string &f) : filename(f) { } template inline operator ImageType() { using DynamicImageType = typename Internal::ImageTypeWithElemType::type; DynamicImageType im_d; (void)load(filename, &im_d); const halide_type_t expected_type = ImageType::static_halide_type(); if (im_d.type() == expected_type) { return im_d.template as(); } else { return ImageTypeConversion::convert_image(im_d); } } private: const std::string filename; }; // Fancy wrapper to call save() with CheckFail; this allows you to simply use // // save_image(im, "filename"); // // without bothering to check error results (all errors simply abort). // // If the specified image file format cannot represent the image without // losing data (e.g, a float32 or 4-dimensional image saved as a JPEG), // a runtime error will occur. template void save_image(ImageType &im, const std::string &filename) { (void)save(im, filename); } // Like save_image, but quietly convert the saved image to a type that the // specified image file format can hold, discarding information if necessary. // (Note that the input image is unaffected!) template void convert_and_save_image(ImageType &im, const std::string &filename) { std::set info; (void)save_query(filename, &info); const FormatInfo best = Internal::best_save_format(im, info); if (best.type == im.type() && best.dimensions == im.dimensions()) { // It's an exact match, we can save as-is. (void)save(im, filename); } else { using DynamicImageType = typename Internal::ImageTypeWithElemType::type; DynamicImageType im_converted = ImageTypeConversion::convert_image(im, best.type); while (im_converted.dimensions() < best.dimensions) { im_converted.add_dimension(); } (void)save(im_converted, filename); } } } // namespace Tools } // namespace Halide #endif // HALIDE_IMAGE_IO_H