#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, unsigned char* 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 = 0;
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)
if (fclose(file))//Non-zero indicates failure.
return false;
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, unsigned char* 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 = 0;
constexpr 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)
if (fclose(file))
return false;
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(unsigned char* 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 unsigned char* 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, unsigned char* 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;
}
}