#pragma once #include "EmberCommonPch.h" #define PNG_COMMENT_MAX 8 static std::recursive_mutex fileCs; /// /// Write a JPEG file. /// /// The full path and name of the file /// Pointer to the image data to write /// Width of the image in pixels /// Height of the image in pixels /// The quality to use /// True to embed comments, else false /// The comment string to embed /// Id of the author /// Url of the author /// Nickname of the author /// True if success, else false static bool WriteJpeg(const char* filename, byte* image, size_t width, size_t height, int quality, bool enableComments, const EmberImageComments& comments, const string& id, const string& url, const string& nick) { bool b = false; FILE* file = nullptr; errno_t fileResult; //Just to be extra safe. try { rlg l(fileCs); fileResult = fopen_s(&file, filename, "wb"); } catch (std::exception) { return false; } if (fileResult == 0) { size_t i; jpeg_error_mgr jerr; jpeg_compress_struct info; string nickString, urlString, idString; string bvString, niString, rtString; string genomeString, verString; //Create the mandatory comment strings. ostringstream os; os << "genome: " << comments.m_Genome; genomeString = os.str(); os.str(""); os << "error_rate: " << comments.m_Badvals; bvString = os.str(); os.str(""); os << "samples: " << comments.m_NumIters; niString = os.str(); os.str(""); os << "time: " << comments.m_Runtime; rtString = os.str(); os.str(""); os << "version: " << EmberVersion(); verString = os.str(); os.str(""); info.err = jpeg_std_error(&jerr); jpeg_create_compress(&info); jpeg_stdio_dest(&info, file); info.in_color_space = JCS_RGB; info.input_components = 3; info.image_width = static_cast(width); info.image_height = static_cast(height); jpeg_set_defaults(&info); #if defined(_WIN32) || defined(__APPLE__) jpeg_set_quality(&info, quality, static_cast(TRUE)); jpeg_start_compress(&info, static_cast(TRUE)); //Win32:TRUE is defined in MSVC2013\Windows Kits\8.1\Include\shared\minwindef.h:"#define TRUE 1" //cast from int to boolean in External/libjpeg/jmorecfg.h:"typedef enum { FALSE = 0, TRUE =1 } boolean;" #else jpeg_set_quality(&info, quality, TRUE); jpeg_start_compress(&info, TRUE); #endif //Write comments to jpeg. if (enableComments) { string s; jpeg_write_marker(&info, JPEG_COM, reinterpret_cast(verString.c_str()), static_cast(verString.size())); if (nick != "") { os.str(""); os << "nickname: " << nick; s = os.str(); jpeg_write_marker(&info, JPEG_COM, reinterpret_cast(s.c_str()), static_cast(s.size())); } if (url != "") { os.str(""); os << "url: " << url; s = os.str(); jpeg_write_marker(&info, JPEG_COM, reinterpret_cast(s.c_str()), static_cast(s.size())); } if (id != "") { os.str(""); os << "id: " << id; s = os.str(); jpeg_write_marker(&info, JPEG_COM, reinterpret_cast(s.c_str()), static_cast(s.size())); } jpeg_write_marker(&info, JPEG_COM, reinterpret_cast(bvString.c_str()), static_cast(bvString.size())); jpeg_write_marker(&info, JPEG_COM, reinterpret_cast(niString.c_str()), static_cast(niString.size())); jpeg_write_marker(&info, JPEG_COM, reinterpret_cast(rtString.c_str()), static_cast(rtString.size())); jpeg_write_marker(&info, JPEG_COM, reinterpret_cast(genomeString.c_str()), static_cast(genomeString.size())); } for (i = 0; i < height; i++) { JSAMPROW row_pointer[1]; row_pointer[0] = image + (3 * width * i); jpeg_write_scanlines(&info, row_pointer, 1); } jpeg_finish_compress(&info); jpeg_destroy_compress(&info); if (file != nullptr) fclose(file); b = true; } return b; } /// /// Write a PNG file. /// /// The full path and name of the file /// Pointer to the image data to write /// Width of the image in pixels /// Height of the image in pixels /// Bytes per channel, 1 or 2. /// True to embed comments, else false /// The comment string to embed /// Id of the author /// Url of the author /// Nickname of the author /// True if success, else false static bool WritePng(const char* filename, byte* image, size_t width, size_t height, size_t bytesPerChannel, bool enableComments, const EmberImageComments& comments, const string& id, const string& url, const string& nick) { bool b = false; FILE* file = nullptr; errno_t fileResult; //Just to be extra safe. try { rlg l(fileCs); fileResult = fopen_s(&file, filename, "wb"); } catch (std::exception) { return false; } if (fileResult == 0) { png_structp png_ptr; png_infop info_ptr; png_text text[PNG_COMMENT_MAX]; size_t i; glm::uint16 testbe = 1; vector rows(height); text[0].compression = PNG_TEXT_COMPRESSION_NONE; text[0].key = const_cast("ember_version"); text[0].text = const_cast(EmberVersion()); text[1].compression = PNG_TEXT_COMPRESSION_NONE; text[1].key = const_cast("ember_nickname"); text[1].text = const_cast(nick.c_str()); text[2].compression = PNG_TEXT_COMPRESSION_NONE; text[2].key = const_cast("ember_url"); text[2].text = const_cast(url.c_str()); text[3].compression = PNG_TEXT_COMPRESSION_NONE; text[3].key = const_cast("ember_id"); text[3].text = const_cast(id.c_str()); text[4].compression = PNG_TEXT_COMPRESSION_NONE; text[4].key = const_cast("ember_error_rate"); text[4].text = const_cast(comments.m_Badvals.c_str()); text[5].compression = PNG_TEXT_COMPRESSION_NONE; text[5].key = const_cast("ember_samples"); text[5].text = const_cast(comments.m_NumIters.c_str()); text[6].compression = PNG_TEXT_COMPRESSION_NONE; text[6].key = const_cast("ember_time"); text[6].text = const_cast(comments.m_Runtime.c_str()); text[7].compression = PNG_TEXT_COMPRESSION_zTXt; text[7].key = const_cast("ember_genome"); text[7].text = const_cast(comments.m_Genome.c_str()); for (i = 0; i < height; i++) rows[i] = image + i * width * 4 * bytesPerChannel; png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); info_ptr = png_create_info_struct(png_ptr); if (setjmp(png_jmpbuf(png_ptr))) { fclose(file); png_destroy_write_struct(&png_ptr, &info_ptr); perror("writing file"); return false; } png_init_io(png_ptr, file); png_set_IHDR(png_ptr, info_ptr, static_cast(width), static_cast(height), 8 * static_cast(bytesPerChannel), PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); if (enableComments == 1) png_set_text(png_ptr, info_ptr, text, PNG_COMMENT_MAX); png_write_info(png_ptr, info_ptr); //Must set this after png_write_info(). if (bytesPerChannel == 2 && testbe != htons(testbe)) { png_set_swap(png_ptr); } png_write_image(png_ptr, rows.data()); png_write_end(png_ptr, info_ptr); png_destroy_write_struct(&png_ptr, &info_ptr); if (file != nullptr) fclose(file); b = true; } return b; } /// /// Convert an RGB buffer to BGR for usage with BMP. /// /// The buffer to convert /// The width. /// The height. /// The size of the new buffer created /// The converted buffer if successful, else NULL. static vector ConvertRGBToBMPBuffer(byte* buffer, size_t width, size_t height, size_t& newSize) { if (buffer == nullptr || width == 0 || height == 0) return vector(); size_t padding = 0; const auto scanlinebytes = width * 3; while ((scanlinebytes + padding ) % 4 != 0) padding++; const auto psw = scanlinebytes + padding; newSize = height * psw; vector newBuf(newSize); size_t bufpos = 0; size_t newpos = 0; for (size_t y = 0; y < height; y++) { for (size_t x = 0; x < 3 * width; x += 3) { bufpos = y * 3 * width + x; // position in original buffer newpos = (height - y - 1) * psw + x; // position in padded buffer newBuf[newpos] = buffer[bufpos + 2]; // swap r and b newBuf[newpos + 1] = buffer[bufpos + 1]; // g stays newBuf[newpos + 2] = buffer[bufpos]; // swap b and r //No swap. //newBuf[newpos] = buffer[bufpos]; //newBuf[newpos + 1] = buffer[bufpos + 1]; //newBuf[newpos + 2] = buffer[bufpos + 2]; } } return newBuf; } /// /// Save a Bmp file. /// /// The full path and name of the file /// Pointer to the image data to write /// Width of the image in pixels /// Height of the image in pixels /// Padded size, greater than or equal to total image size. /// True if success, else false static bool SaveBmp(const char* filename, const byte* image, size_t width, size_t height, size_t paddedSize) { #ifdef _WIN32 BITMAPFILEHEADER bmfh; BITMAPINFOHEADER info; DWORD bwritten; HANDLE file = nullptr; memset (&bmfh, 0, sizeof (BITMAPFILEHEADER)); memset (&info, 0, sizeof (BITMAPINFOHEADER)); bmfh.bfType = 0x4d42; // 0x4d42 = 'BM' bmfh.bfReserved1 = 0; bmfh.bfReserved2 = 0; bmfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + static_cast(paddedSize); bmfh.bfOffBits = 0x36; info.biSize = sizeof(BITMAPINFOHEADER); info.biWidth = static_cast(width); info.biHeight = static_cast(height); info.biPlanes = 1; info.biBitCount = 24; info.biCompression = BI_RGB; info.biSizeImage = 0; info.biXPelsPerMeter = 0x0ec4; info.biYPelsPerMeter = 0x0ec4; info.biClrUsed = 0; info.biClrImportant = 0; //Just to be extra safe. try { rlg l(fileCs); if ((file = CreateFileA(filename, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)) == NULL) { if (file != 0) CloseHandle(file); return false; } } catch (std::exception) { CloseHandle(file); return false; } if (WriteFile(file, &bmfh, sizeof (BITMAPFILEHEADER), &bwritten, NULL) == false) { CloseHandle(file); return false; } if (WriteFile(file, &info, sizeof(BITMAPINFOHEADER), &bwritten, NULL) == false) { CloseHandle(file); return false; } if (WriteFile(file, image, static_cast(paddedSize), &bwritten, NULL) == false) { CloseHandle(file); return false; } CloseHandle(file); #endif return true; } /// /// Convert a buffer from RGB to BGR and write a Bmp file. /// /// The full path and name of the file /// Pointer to the image data to write /// Width of the image in pixels /// Height of the image in pixels /// True if success, else false static bool WriteBmp(const char* filename, byte* image, size_t width, size_t height) { bool b = false; size_t newSize; auto bgrBuf = ConvertRGBToBMPBuffer(image, width, height, newSize); b = SaveBmp(filename, bgrBuf.data(), width, height, newSize); return b; } /// /// Write an EXR file which will use the 16 bit half float format for each pixel channel. /// This is used for high color precision because it uses /// HALFS for each color channel. /// /// The full path and name of the file /// Pointer to the image data to write /// Width of the image in pixels /// Height of the image in pixels /// True to embed comments, else false /// The comment string to embed /// Id of the author /// Url of the author /// Nickname of the author /// True if success, else false static bool WriteExr16(const char* filename, Rgba* image, size_t width, size_t height, bool enableComments, const EmberImageComments& comments, const string& id, const string& url, const string& nick) { try { const auto iw = static_cast(width); const auto ih = static_cast(height); std::unique_ptr file; try { rlg l(fileCs); file = std::make_unique(filename, iw, ih, RgbaChannels::WRITE_RGBA); } catch (std::exception) { return false; } if (enableComments) { auto& header = const_cast(file->header()); header.insert("ember_version", StringAttribute(EmberVersion())); header.insert("ember_nickname", StringAttribute(nick)); header.insert("ember_url", StringAttribute(url)); header.insert("ember_id", StringAttribute(id)); header.insert("ember_error_rate", StringAttribute(comments.m_Badvals)); header.insert("ember_samples", StringAttribute(comments.m_NumIters)); header.insert("ember_time", StringAttribute(comments.m_Runtime)); header.insert("ember_genome", StringAttribute(comments.m_Genome)); } file->setFrameBuffer(image, 1, iw); file->writePixels(ih); return true; } catch (std::exception e) { cout << e.what() << endl; return false; } } /// /// Write an EXR file which will use the 32 bit half float format for each pixel channel. /// This is used for extreme color precision because it uses /// floats for each color channel. /// /// The full path and name of the file /// Pointer to the red image data to write /// Pointer to the green image data to write /// Pointer to the blue image data to write /// Pointer to the alpha image data to write /// Width of the image in pixels /// Height of the image in pixels /// True to embed comments, else false /// The comment string to embed /// Id of the author /// Url of the author /// Nickname of the author /// True if success, else false static bool WriteExr32(const char* filename, float* r, float* g, float* b, float* a, size_t width, size_t height, bool enableComments, const EmberImageComments& comments, const string& id, const string& url, const string& nick) { try { const auto iw = static_cast(width); const auto ih = static_cast(height); std::unique_ptr file; try { rlg l(fileCs); Header header(iw, ih); header.channels().insert("R", Channel(PixelType::FLOAT)); header.channels().insert("G", Channel(PixelType::FLOAT)); header.channels().insert("B", Channel(PixelType::FLOAT)); header.channels().insert("A", Channel(PixelType::FLOAT)); file = std::make_unique(filename, header); } catch (std::exception) { return false; } if (enableComments) { auto& header = const_cast(file->header()); header.insert("ember_version", StringAttribute(EmberVersion())); header.insert("ember_nickname", StringAttribute(nick)); header.insert("ember_url", StringAttribute(url)); header.insert("ember_id", StringAttribute(id)); header.insert("ember_error_rate", StringAttribute(comments.m_Badvals)); header.insert("ember_samples", StringAttribute(comments.m_NumIters)); header.insert("ember_time", StringAttribute(comments.m_Runtime)); header.insert("ember_genome", StringAttribute(comments.m_Genome)); } FrameBuffer frameBuffer; frameBuffer.insert("R", Slice(PixelType::FLOAT, (char*)r, sizeof(*r) * 1, sizeof(*r) * width)); frameBuffer.insert("G", Slice(PixelType::FLOAT, (char*)g, sizeof(*g) * 1, sizeof(*g) * width)); frameBuffer.insert("B", Slice(PixelType::FLOAT, (char*)b, sizeof(*b) * 1, sizeof(*b) * width)); frameBuffer.insert("A", Slice(PixelType::FLOAT, (char*)a, sizeof(*a) * 1, sizeof(*a) * width)); file->setFrameBuffer(frameBuffer); file->writePixels(ih); return true; } catch (std::exception e) { cout << e.what() << endl; return false; } }