fractorium/Source/EmberCommon/JpegUtils.h
mfeemster 018ba26b5f --User changes
-Add support for multiple GPU devices.
  --These options are present in the command line and in Fractorium.
 -Change scheme of specifying devices from platform,device to just total device index.
  --Single number on the command line.
  --Change from combo boxes for device selection to a table of all devices in Fractorium.
 -Temporal samples defaults to 100 instead of 1000 which was needless overkill.

--Bug fixes
 -EmberAnimate, EmberRender, FractoriumSettings, FinalRenderDialog: Fix wrong order of arguments to Clamp() when assigning thread priority.
 -VariationsDC.h: Fix NVidia OpenCL compilation error in DCTriangleVariation.
 -FractoriumXformsColor.cpp: Checking for null pixmap pointer is not enough, must also check if the underlying buffer is null via call to QPixmap::isNull().

--Code changes
 -Ember.h: Add case for FLAME_MOTION_NONE and default in ApplyFlameMotion().
 -EmberMotion.h: Call base constructor.
 -EmberPch.h: #pragma once only on Windows.
 -EmberToXml.h:
  --Handle different types of exceptions.
  --Add default cases to ToString().
 -Isaac.h: Remove unused variable in constructor.
 -Point.h: Call base constructor in Color().
 -Renderer.h/cpp:
  --Add bool to Alloc() to only allocate memory for the histogram. Needed for multi-GPU.
  --Make CoordMap() return a const ref, not a pointer.
 -SheepTools.h:
  --Use 64-bit types like the rest of the code already does.
  --Fix some comment misspellings.
 -Timing.h: Make BeginTime(), EndTime(), ElapsedTime() and Format() be const functions.
 -Utils.h:
  --Add new functions Equal() and Split().
  --Handle more exception types in ReadFile().
  --Get rid of most legacy blending of C and C++ argument parsing.
 -XmlToEmber.h:
  --Get rid of most legacy blending of C and C++ code from flam3.
  --Remove some unused variables.
 -EmberAnimate:
  --Support multi-GPU processing that alternates full frames between devices.
  --Use OpenCLInfo instead of OpenCLWrapper for --openclinfo option.
  --Remove bucketT template parameter, and hard code float in its place.
  --If a render fails, exit since there is no point in continuing an animation with a missing frame.
  --Pass variables to threaded save better, which most likely fixes a very subtle bug that existed before.
  --Remove some unused variables.
 -EmberGenome, EmberRender:
  --Support multi-GPU processing that alternates full frames between devices.
  --Use OpenCLInfo instead of OpenCLWrapper for --openclinfo option.
  --Remove bucketT template parameter, and hard code float in its place.
 -EmberRender:
  --Support multi-GPU processing that alternates full frames between devices.
  --Use OpenCLInfo instead of OpenCLWrapper for --openclinfo option.
  --Remove bucketT template parameter, and hard code float in its place.
  --Only print values when not rendering with OpenCL, since they're always 0 in that case.
 -EmberCLPch.h:
  --#pragma once only on Windows.
  --#include <atomic>.
 -IterOpenCLKernelCreator.h: Add new kernel for summing two histograms. This is needed for multi-GPU.
 -OpenCLWrapper.h:
  --Move all OpenCL info related code into its own class OpenCLInfo.
  --Add members to cache the values of global memory size and max allocation size.
 -RendererCL.h/cpp:
  --Redesign to accomodate multi-GPU.
  --Constructor now takes a vector of devices.
  --Remove DumpErrorReport() function, it's handled in the base.
  --ClearBuffer(), ReadPoints(), WritePoints(), ReadHist() and WriteHist() now optionally take a device index as a parameter.
  --MakeDmap() override and m_DmapCL member removed because it no longer applies since the histogram is always float since the last commit.
  --Add new function SumDeviceHist() to sum histograms from two devices by first copying to a temporary on the host, then a temporary on the device, then summing.
  --m_Calls member removed, as it's now per-device.
  --OpenCLWrapper removed.
  --m_Seeds member is now a vector of vector of seeds, to accomodate a separate and different array of seeds for each device.
  --Added member m_Devices, a vector of unique_ptr of RendererCLDevice.
 -EmberCommon.h
  --Added Devices() function to convert from a vector of device indices to a vector of platform,device indices.
  --Changed CreateRenderer() to accept a vector of devices to create a single RendererCL which will split work across multiple devices.
  --Added CreateRenderers() function to accept a vector of devices to create multiple RendererCL, each which will render on a single device.
  --Add more comments to some existing functions.
 -EmberCommonPch.h: #pragma once only on Windows.
 -EmberOptions.h:
  --Remove --platform option, it's just sequential device number now with the --device option.
  --Make --out be OPT_USE_RENDER instead of OPT_RENDER_ANIM since it's an error condition when animating. It makes no sense to write all frames to a single image.
  --Add Devices() function to parse comma separated --device option string and return a vector of device indices.
  --Make int and uint types be 64-bit, so intmax_t and size_t.
  --Make better use of macros.
 -JpegUtils.h: Make string parameters to WriteJpeg() and WritePng() be const ref.
 -All project files: Turn off buffer security check option in Visual Studio (/Gs-)
 -deployment.pri: Remove the line OTHER_FILES +=, it's pointless and was causing problems.
 -Ember.pro, EmberCL.pro: Add CONFIG += plugin, otherwise it wouldn't link.
 -EmberCL.pro: Add new files for multi-GPU support.
 -build_all.sh: use -j4 and QMAKE=${QMAKE:/usr/bin/qmake}
 -shared_settings.pri:
  -Add version string.
  -Remove old DESTDIR definitions.
  -Add the following lines or else nothing would build:
   CONFIG(release, debug|release) {
    CONFIG += warn_off
    DESTDIR = ../../../Bin/release
   }

   CONFIG(debug, debug|release) {
    DESTDIR = ../../../Bin/debug
   }

   QMAKE_POST_LINK += $$quote(cp --update ../../../Data/flam3-palettes.xml $${DESTDIR}$$escape_expand(\n\t))
   LIBS += -L/usr/lib -lpthread
 -AboutDialog.ui: Another futile attempt to make it look correct on Linux.
 -FinalRenderDialog.h/cpp:
  --Add support for multi-GPU.
  --Change from combo boxes for device selection to a table of all devices.
  --Ensure device selection makes sense.
  --Remove "FinalRender" prefix of various function names, it's implied given the context.
 -FinalRenderEmberController.h/cpp:
  --Add support for multi-GPU.
  --Change m_FinishedImageCount to be atomic.
  --Move CancelRender() from the base to FinalRenderEmberController<T>.
  --Refactor RenderComplete() to omit any progress related functionality or image saving since it can be potentially ran in a thread.
  --Consolidate setting various renderer fields into SyncGuiToRenderer().
 -Fractorium.cpp: Allow for resizing of the options dialog to show the entire device table.
 -FractoriumCommon.h: Add various functions to handle a table showing the available OpenCL devices on the system.
 -FractoriumEmberController.h/cpp: Remove m_FinalImageIndex, it's no longer needed.
 -FractoriumRender.cpp: Scale the interactive sub batch count and quality by the number of devices used.
 -FractoriumSettings.h/cpp:
  --Temporal samples defaults to 100 instead of 1000 which was needless overkill.
  --Add multi-GPU support, remove old device,platform pair.
 -FractoriumToolbar.cpp: Disable OpenCL toolbar button if there are no devices present on the system.
 -FractoriumOptionsDialog.h/cpp:
  --Add support for multi-GPU.
  --Consolidate more assignments in DataToGui().
  --Enable/disable CPU/OpenCL items in response to OpenCL checkbox event.
 -Misc: Convert almost everything to size_t for unsigned, intmax_t for signed.
2015-09-12 18:33:45 -07:00

365 lines
12 KiB
C

#pragma once
#include "EmberCommonPch.h"
#define PNG_COMMENT_MAX 8
/// <summary>
/// Write a PPM file.
/// </summary>
/// <param name="filename">The full path and name of the file</param>
/// <param name="image">Pointer to the image data to write</param>
/// <param name="width">Width of the image in pixels</param>
/// <param name="height">Height of the image in pixels</param>
/// <returns>True if success, else false</returns>
static bool WritePpm(const char* filename, byte* image, size_t width, size_t height)
{
bool b = false;
size_t size = width * height * 3;
FILE* file;
if (fopen_s(&file, filename, "wb") == 0)
{
fprintf_s(file, "P6\n");
fprintf_s(file, "%lu %lu\n255\n", width, height);
b = (size == fwrite(image, 1, size, file));
fclose(file);
}
return b;
}
/// <summary>
/// Write a JPEG file.
/// </summary>
/// <param name="filename">The full path and name of the file</param>
/// <param name="image">Pointer to the image data to write</param>
/// <param name="width">Width of the image in pixels</param>
/// <param name="height">Height of the image in pixels</param>
/// <param name="quality">The quality to use</param>
/// <param name="enableComments">True to embed comments, else false</param>
/// <param name="comments">The comment string to embed</param>
/// <param name="id">Id of the author</param>
/// <param name="url">Url of the author</param>
/// <param name="nick">Nickname of the author</param>
/// <returns>True if success, else false</returns>
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;
if (fopen_s(&file, filename, "wb") == 0)
{
size_t i;
jpeg_error_mgr jerr;
jpeg_compress_struct info;
char nickString[64], urlString[128], idString[128];
char bvString[64], niString[64], rtString[64];
char genomeString[65536], verString[64];
//Create the mandatory comment strings.
snprintf_s(genomeString, 65536, "flam3_genome: %s", comments.m_Genome.c_str());
snprintf_s(bvString, 64, "flam3_error_rate: %s", comments.m_Badvals.c_str());
snprintf_s(niString, 64, "flam3_samples: %s", comments.m_NumIters.c_str());
snprintf_s(rtString, 64, "flam3_time: %s", comments.m_Runtime.c_str());
snprintf_s(verString, 64, "flam3_version: %s", EmberVersion());
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 = JDIMENSION(width);
info.image_height = JDIMENSION(height);
jpeg_set_defaults(&info);
jpeg_set_quality(&info, quality, TRUE);
jpeg_start_compress(&info, TRUE);
//Write comments to jpeg.
if (enableComments)
{
jpeg_write_marker(&info, JPEG_COM, reinterpret_cast<byte*>(verString), uint(strlen(verString)));
if (nick != "")
{
snprintf_s(nickString, 64, "flam3_nickname: %s", nick.c_str());
jpeg_write_marker(&info, JPEG_COM, reinterpret_cast<byte*>(nickString), uint(strlen(nickString)));
}
if (url != "")
{
snprintf_s(urlString, 128, "flam3_url: %s", url.c_str());
jpeg_write_marker(&info, JPEG_COM, reinterpret_cast<byte*>(urlString), uint(strlen(urlString)));
}
if (id != "")
{
snprintf_s(idString, 128, "flam3_id: %s", id.c_str());
jpeg_write_marker(&info, JPEG_COM, reinterpret_cast<byte*>(idString), uint(strlen(idString)));
}
jpeg_write_marker(&info, JPEG_COM, reinterpret_cast<byte*>(bvString), uint(strlen(bvString)));
jpeg_write_marker(&info, JPEG_COM, reinterpret_cast<byte*>(niString), uint(strlen(niString)));
jpeg_write_marker(&info, JPEG_COM, reinterpret_cast<byte*>(rtString), uint(strlen(rtString)));
jpeg_write_marker(&info, JPEG_COM, reinterpret_cast<byte*>(genomeString), uint(strlen(genomeString)));
}
for (i = 0; i < height; i++)
{
JSAMPROW row_pointer[1];
row_pointer[0] = reinterpret_cast<byte*>(image) + (3 * width * i);
jpeg_write_scanlines(&info, row_pointer, 1);
}
jpeg_finish_compress(&info);
jpeg_destroy_compress(&info);
fclose(file);
b = true;
}
return b;
}
/// <summary>
/// Write a PNG file.
/// </summary>
/// <param name="filename">The full path and name of the file</param>
/// <param name="image">Pointer to the image data to write</param>
/// <param name="width">Width of the image in pixels</param>
/// <param name="height">Height of the image in pixels</param>
/// <param name="bytesPerChannel">Bytes per channel, 1 or 2.</param>
/// <param name="enableComments">True to embed comments, else false</param>
/// <param name="comments">The comment string to embed</param>
/// <param name="id">Id of the author</param>
/// <param name="url">Url of the author</param>
/// <param name="nick">Nickname of the author</param>
/// <returns>True if success, else false</returns>
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;
if (fopen_s(&file, filename, "wb") == 0)
{
png_structp png_ptr;
png_infop info_ptr;
png_text text[PNG_COMMENT_MAX];
size_t i;
glm::uint16 testbe = 1;
vector<byte*> rows(height);
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
text[0].key = const_cast<png_charp>("flam3_version");
text[0].text = const_cast<png_charp>(EmberVersion());
text[1].compression = PNG_TEXT_COMPRESSION_NONE;
text[1].key = const_cast<png_charp>("flam3_nickname");
text[1].text = const_cast<png_charp>(nick.c_str());
text[2].compression = PNG_TEXT_COMPRESSION_NONE;
text[2].key = const_cast<png_charp>("flam3_url");
text[2].text = const_cast<png_charp>(url.c_str());
text[3].compression = PNG_TEXT_COMPRESSION_NONE;
text[3].key = const_cast<png_charp>("flam3_id");
text[3].text = const_cast<png_charp>(id.c_str());
text[4].compression = PNG_TEXT_COMPRESSION_NONE;
text[4].key = const_cast<png_charp>("flam3_error_rate");
text[4].text = const_cast<png_charp>(comments.m_Badvals.c_str());
text[5].compression = PNG_TEXT_COMPRESSION_NONE;
text[5].key = const_cast<png_charp>("flam3_samples");
text[5].text = const_cast<png_charp>(comments.m_NumIters.c_str());
text[6].compression = PNG_TEXT_COMPRESSION_NONE;
text[6].key = const_cast<png_charp>("flam3_time");
text[6].text = const_cast<png_charp>(comments.m_Runtime.c_str());
text[7].compression = PNG_TEXT_COMPRESSION_zTXt;
text[7].key = const_cast<png_charp>("flam3_genome");
text[7].text = const_cast<png_charp>(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, png_uint_32(width), png_uint_32(height), 8 * png_uint_32(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, const_cast<png_bytepp>(rows.data()));
png_write_end(png_ptr, info_ptr);
png_destroy_write_struct(&png_ptr, &info_ptr);
fclose(file);
b = true;
}
return b;
}
/// <summary>
/// Convert an RGB buffer to BGR for usage with BMP.
/// </summary>
/// <param name="buffer">The buffer to convert</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="newSize">The size of the new buffer created</param>
/// <returns>The converted buffer if successful, else NULL.</returns>
static byte* ConvertRGBToBMPBuffer(byte* buffer, size_t width, size_t height, size_t& newSize)
{
if (buffer == nullptr || width == 0 || height == 0)
return nullptr;
size_t padding = 0;
size_t scanlinebytes = width * 3;
while ((scanlinebytes + padding ) % 4 != 0)
padding++;
size_t psw = scanlinebytes + padding;
newSize = height * psw;
byte* newBuf = new byte[newSize];
if (newBuf)
{
memset (newBuf, 0, 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;
}
return nullptr;
}
/// <summary>
/// Save a Bmp file.
/// </summary>
/// <param name="filename">The full path and name of the file</param>
/// <param name="image">Pointer to the image data to write</param>
/// <param name="width">Width of the image in pixels</param>
/// <param name="height">Height of the image in pixels</param>
/// <param name="paddedSize">Padded size, greater than or equal to total image size.</param>
/// <returns>True if success, else false</returns>
static bool SaveBmp(const char* filename, byte* image, size_t width, size_t height, size_t paddedSize)
{
#ifdef WIN32
BITMAPFILEHEADER bmfh;
BITMAPINFOHEADER info;
DWORD bwritten;
HANDLE file;
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) + (DWORD)paddedSize;
bmfh.bfOffBits = 0x36;
info.biSize = sizeof(BITMAPINFOHEADER);
info.biWidth = (LONG)width;
info.biHeight = (LONG)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;
if ((file = CreateFileA(filename, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)) == NULL)
{
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, (DWORD)paddedSize, &bwritten, NULL) == false)
{
CloseHandle(file);
return false;
}
CloseHandle(file);
#endif
return true;
}
/// <summary>
/// Convert a buffer from RGB to BGR and write a Bmp file.
/// </summary>
/// <param name="filename">The full path and name of the file</param>
/// <param name="image">Pointer to the image data to write</param>
/// <param name="width">Width of the image in pixels</param>
/// <param name="height">Height of the image in pixels</param>
/// <returns>True if success, else false</returns>
static bool WriteBmp(const char* filename, byte* image, size_t width, size_t height)
{
bool b = false;
size_t newSize;
unique_ptr<byte> bgrBuf(ConvertRGBToBMPBuffer(image, width, height, newSize));
if (bgrBuf.get())
b = SaveBmp(filename, bgrBuf.get(), width, height, newSize);
return b;
}