diff --git a/renderdoc/api/replay/renderdoc_replay.h b/renderdoc/api/replay/renderdoc_replay.h index a7e79a923..47be61035 100644 --- a/renderdoc/api/replay/renderdoc_replay.h +++ b/renderdoc/api/replay/renderdoc_replay.h @@ -648,8 +648,9 @@ extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_LogText(const char *text); extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_LogMessage(LogMessageType type, const char *project, const char *file, unsigned int line, const char *text); -extern "C" RENDERDOC_API bool32 RENDERDOC_CC RENDERDOC_GetThumbnail(const char *filename, byte *buf, - uint32_t &len); +extern "C" RENDERDOC_API bool32 RENDERDOC_CC RENDERDOC_GetThumbnail(const char *filename, + FileType type, uint32_t maxsize, + rdctype::array *buf); extern "C" RENDERDOC_API const char *RENDERDOC_CC RENDERDOC_GetVersionString(); extern "C" RENDERDOC_API const char *RENDERDOC_CC RENDERDOC_GetCommitHash(); extern "C" RENDERDOC_API const char *RENDERDOC_CC RENDERDOC_GetConfigSetting(const char *name); diff --git a/renderdoc/core/target_control.cpp b/renderdoc/core/target_control.cpp index 7cd5947c1..afab12850 100644 --- a/renderdoc/core/target_control.cpp +++ b/renderdoc/core/target_control.cpp @@ -127,15 +127,12 @@ void RenderDoc::TargetControlClientThread(void *s) ser.Serialise("", captures.back().timestamp); ser.Serialise("", path); - uint32_t len = 0; - RENDERDOC_GetThumbnail(captures.back().path.c_str(), NULL, len); - byte *thumb = new byte[len]; - RENDERDOC_GetThumbnail(captures.back().path.c_str(), thumb, len); + rdctype::array buf; + RENDERDOC_GetThumbnail(captures.back().path.c_str(), eFileType_JPG, 0, &buf); - size_t l = len; - ser.Serialise("", len); - ser.SerialiseBuffer("", thumb, l); - delete[] thumb; + size_t sz = buf.size(); + ser.Serialise("", buf.count); + ser.SerialiseBuffer("", buf.elems, sz); } else if(childprocs.size() != children.size()) { @@ -596,7 +593,7 @@ public: msg->NewCapture.path = path; msg->NewCapture.local = m_Local; - uint32_t thumblen = 0; + int32_t thumblen = 0; ser->Serialise("", thumblen); create_array_uninit(msg->NewCapture.thumbnail, thumblen); diff --git a/renderdoc/replay/entry_points.cpp b/renderdoc/replay/entry_points.cpp index c9dfc3e4e..830fafff8 100644 --- a/renderdoc/replay/entry_points.cpp +++ b/renderdoc/replay/entry_points.cpp @@ -28,11 +28,15 @@ #include "common/common.h" #include "core/core.h" #include "data/version.h" +#include "jpeg-compressor/jpgd.h" +#include "jpeg-compressor/jpge.h" #include "maths/camera.h" #include "maths/formatpacking.h" #include "replay/replay_renderer.h" #include "serialise/serialiser.h" #include "serialise/string_utils.h" +#include "stb/stb_image_resize.h" +#include "stb/stb_image_write.h" // these entry points are for the replay/analysis side - not for the application. @@ -420,8 +424,17 @@ extern "C" RENDERDOC_API uint32_t RENDERDOC_CC RENDERDOC_InjectIntoProcess( waitForExit != 0); } -extern "C" RENDERDOC_API bool32 RENDERDOC_CC RENDERDOC_GetThumbnail(const char *filename, byte *buf, - uint32_t &len) +static void writeToByteVector(void *context, void *data, int size) +{ + std::vector *vec = (std::vector *)context; + byte *start = (byte *)data; + byte *end = start + size; + vec->insert(vec->end(), start, end); +} + +extern "C" RENDERDOC_API bool32 RENDERDOC_CC RENDERDOC_GetThumbnail(const char *filename, + FileType type, uint32_t maxsize, + rdctype::array *buf) { Serialiser ser(filename, Serialiser::READING, false); @@ -453,20 +466,97 @@ extern "C" RENDERDOC_API bool32 RENDERDOC_CC RENDERDOC_GetThumbnail(const char * if(jpgbuf == NULL) return false; - if(buf == NULL) + // if the desired output is jpg and either there's no max size or it's already satisfied, + // return the data directly + if(type == eFileType_JPG && (maxsize == 0 || (maxsize > thumbwidth && maxsize > thumbheight))) { - len = (uint32_t)thumblen; - delete[] jpgbuf; - return true; + create_array_init(*buf, thumblen, jpgbuf); } - - if(thumblen > len) + else { - delete[] jpgbuf; - return false; - } + // otherwise we need to decode, resample maybe, and re-encode - memcpy(buf, jpgbuf, thumblen); + int w = (int)thumbwidth; + int h = (int)thumbheight; + int comp = 3; + byte *thumbpixels = + jpgd::decompress_jpeg_image_from_memory(jpgbuf, (int)thumblen, &w, &h, &comp, 3); + + if(maxsize != 0) + { + uint32_t clampedWidth = RDCMIN(maxsize, thumbwidth); + uint32_t clampedHeight = RDCMIN(maxsize, thumbheight); + + if(clampedWidth != thumbwidth || clampedHeight != thumbheight) + { + // preserve aspect ratio, take the smallest scale factor and multiply both + float scaleX = float(clampedWidth) / float(thumbwidth); + float scaleY = float(clampedHeight) / float(thumbheight); + + if(scaleX < scaleY) + clampedHeight = uint32_t(scaleX * thumbheight); + else if(scaleY < scaleX) + clampedWidth = uint32_t(scaleY * thumbwidth); + + byte *resizedpixels = (byte *)malloc(3 * clampedWidth * clampedHeight); + + stbir_resize_uint8_srgb(thumbpixels, thumbwidth, thumbheight, 0, resizedpixels, + clampedWidth, clampedHeight, 0, 3, -1, 0); + + free(thumbpixels); + + thumbpixels = resizedpixels; + thumbwidth = clampedWidth; + thumbheight = clampedHeight; + } + } + + std::vector encodedBytes; + + switch(type) + { + case eFileType_JPG: + { + int len = thumbwidth * thumbheight * 3; + encodedBytes.resize(len); + jpge::params p; + p.m_quality = 90; + jpge::compress_image_to_jpeg_file_in_memory(&encodedBytes[0], len, (int)thumbwidth, + (int)thumbheight, 3, thumbpixels, p); + encodedBytes.resize(len); + break; + } + case eFileType_PNG: + { + stbi_write_png_to_func(&writeToByteVector, &encodedBytes, (int)thumbwidth, (int)thumbheight, + 3, thumbpixels, 0); + break; + } + case eFileType_TGA: + { + stbi_write_tga_to_func(&writeToByteVector, &encodedBytes, (int)thumbwidth, (int)thumbheight, + 3, thumbpixels); + break; + } + case eFileType_BMP: + { + stbi_write_bmp_to_func(&writeToByteVector, &encodedBytes, (int)thumbwidth, (int)thumbheight, + 3, thumbpixels); + break; + } + default: + { + RDCERR("Unsupported file type %d in thumbnail fetch", type); + free(thumbpixels); + delete[] jpgbuf; + return false; + } + } + + *buf = encodedBytes; + + free(thumbpixels); + } delete[] jpgbuf; diff --git a/renderdoccmd/renderdoccmd.cpp b/renderdoccmd/renderdoccmd.cpp index 9cfb358de..ce5bc5b51 100644 --- a/renderdoccmd/renderdoccmd.cpp +++ b/renderdoccmd/renderdoccmd.cpp @@ -197,7 +197,13 @@ struct ThumbCommand : public Command virtual void AddOptions(cmdline::parser &parser) { parser.set_footer(""); - parser.add("out", 'o', "The output filename to save the jpg to", false, "filename.jpg"); + parser.add("out", 'o', "The output filename to save the file to", true, "filename.jpg"); + parser.add("format", 'f', + "The format of the output file. If empty, detected from filename", false, "", + cmdline::oneof("jpg", "png", "bmp", "tga")); + parser.add( + "max-size", 's', + "The maximum dimension of the thumbnail. Default is 0, which is unlimited.", false, 0); } virtual const char *Description() { return "Saves a capture's embedded thumbnail to disk."; } virtual bool IsInternalOnly() { return false; } @@ -214,33 +220,42 @@ struct ThumbCommand : public Command string filename = parser.rest()[0]; - string jpgname; + string outfile = parser.get("out"); - if(parser.exist("out")) + string format = parser.get("format"); + + FileType type = eFileType_JPG; + + if(format == "png") { - jpgname = parser.get("out"); + type = eFileType_PNG; + } + else if(format == "tga") + { + type = eFileType_TGA; + } + else if(format == "bmp") + { + type = eFileType_BMP; } else { - jpgname = filename; + const char *dot = strrchr(outfile.c_str(), '.'); - if(jpgname[jpgname.length() - 4] == '.' && jpgname[jpgname.length() - 3] == 'r' && - jpgname[jpgname.length() - 2] == 'd' && jpgname[jpgname.length() - 1] == 'c') - { - jpgname.pop_back(); - jpgname.pop_back(); - jpgname.pop_back(); - - jpgname += "jpg"; - } + if(dot != NULL && strstr(dot, "png")) + type = eFileType_PNG; + else if(dot != NULL && strstr(dot, "tga")) + type = eFileType_TGA; + else if(dot != NULL && strstr(dot, "bmp")) + type = eFileType_BMP; else - { - jpgname += ".jpg"; - } + std::cerr << "Couldn't guess format from '" << outfile << "', defaulting to jpg." + << std::endl; } - uint32_t len = 0; - bool32 ret = RENDERDOC_GetThumbnail(filename.c_str(), NULL, len); + rdctype::array buf; + bool32 ret = + RENDERDOC_GetThumbnail(filename.c_str(), type, parser.get("max-size"), &buf); if(!ret) { @@ -248,24 +263,19 @@ struct ThumbCommand : public Command } else { - byte *jpgbuf = new byte[len]; - RENDERDOC_GetThumbnail(filename.c_str(), jpgbuf, len); - - FILE *f = fopen(jpgname.c_str(), "wb"); + FILE *f = fopen(outfile.c_str(), "wb"); if(!f) { - std::cerr << "Couldn't open destination file '" << jpgname << "'" << std::endl; + std::cerr << "Couldn't open destination file '" << outfile << "'" << std::endl; } else { - fwrite(jpgbuf, 1, len, f); + fwrite(buf.elems, 1, buf.count, f); fclose(f); - std::cout << "Wrote thumbnail from '" << filename << "' to '" << jpgname << "'." << std::endl; + std::cout << "Wrote thumbnail from '" << filename << "' to '" << outfile << "'." << std::endl; } - - delete[] jpgbuf; } return 0; diff --git a/renderdocui/Interop/StaticExports.cs b/renderdocui/Interop/StaticExports.cs index 9c3d36f84..05257a1df 100644 --- a/renderdocui/Interop/StaticExports.cs +++ b/renderdocui/Interop/StaticExports.cs @@ -98,7 +98,7 @@ namespace renderdoc private static extern void RENDERDOC_SetConfigSetting(IntPtr name, IntPtr value); [DllImport("renderdoc.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] - private static extern bool RENDERDOC_GetThumbnail(IntPtr filename, byte[] outmem, ref UInt32 len); + private static extern bool RENDERDOC_GetThumbnail(IntPtr filename, FileType type, UInt32 maxsize, IntPtr outmem); [DllImport("renderdoc.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr RENDERDOC_MakeEnvironmentModificationList(int numElems); @@ -362,13 +362,15 @@ namespace renderdoc CustomMarshal.Free(value_mem); } - public static byte[] GetThumbnail(string filename) + public static byte[] GetThumbnail(string filename, FileType type, UInt32 maxsize) { UInt32 len = 0; IntPtr filename_mem = CustomMarshal.MakeUTF8String(filename); - bool success = RENDERDOC_GetThumbnail(filename_mem, null, ref len); + IntPtr mem = CustomMarshal.Alloc(typeof(templated_array)); + + bool success = RENDERDOC_GetThumbnail(filename_mem, type, maxsize, mem); if (!success || len == 0) { @@ -376,14 +378,9 @@ namespace renderdoc return null; } - byte[] ret = new byte[len]; + byte[] ret = (byte[])CustomMarshal.GetTemplatedArray(mem, typeof(byte), true); - success = RENDERDOC_GetThumbnail(filename_mem, ret, ref len); - - CustomMarshal.Free(filename_mem); - - if (!success || len == 0) - return null; + CustomMarshal.Free(mem); return ret; }