From 37033903a07951a77a3d17a6303f928d84399ba3 Mon Sep 17 00:00:00 2001 From: Scott Date: Sat, 24 Dec 2016 09:05:32 -0800 Subject: [PATCH] More work to windows desktop dup Not working correct yet, the coords for capturing are not correct. --- .vscode/.cmaketools.json | 8 +- .vscode/settings.json | 3 +- Example/CMakeLists.txt | 4 +- Example/Screen_Capture_Example.cpp | 32 +- Example/tiny_jpeg.h | 1279 ++++++++++++++++++++++++++ include/InterruptableSleeper.h | 53 -- include/ScreenTypes.h | 12 +- include/windows/CommonTypes.h | 3 + include/windows/DXFrameProcessor.h | 2 + include/windows/ScreenCaptureDX.h | 3 +- lib/CMakeLists.txt | 1 - src/ScreenCaptureManager.cpp | 6 +- src/windows/DXDuplicationManager.cpp | 22 +- src/windows/DXFrameProcessor.cpp | 109 ++- src/windows/DXThreadManager.cpp | 76 +- src/windows/ScreenCaptureDX.cpp | 1 - src/windows/ScreenCaptureGDI.cpp | 4 +- 17 files changed, 1462 insertions(+), 156 deletions(-) create mode 100644 Example/tiny_jpeg.h delete mode 100644 include/InterruptableSleeper.h diff --git a/.vscode/.cmaketools.json b/.vscode/.cmaketools.json index 75c10aec..073700f5 100644 --- a/.vscode/.cmaketools.json +++ b/.vscode/.cmaketools.json @@ -1,12 +1,12 @@ { "variant": { - "label": "Release", + "label": "Debug", "keywordSettings": { - "buildType": "release" + "buildType": "debug" }, - "description": "Enable optimizations, omit debug info" + "description": "Emit debug information without performing optimizations" }, "activeEnvironments": [ - "Visual C++ 14.0 - amd64" + "Visual C++ 14.0 - x86" ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 72d8de34..d4a03775 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ // Place your settings in this file to overwrite default and user settings. { - "C_Cpp.clang_format_style": "WebKit" + "C_Cpp.clang_format_style": "WebKit", + } \ No newline at end of file diff --git a/Example/CMakeLists.txt b/Example/CMakeLists.txt index 22258fc5..eaad04a1 100644 --- a/Example/CMakeLists.txt +++ b/Example/CMakeLists.txt @@ -1,7 +1,9 @@ project(Screen_Capture_Example) include_directories(${Screen_Capture_SOURCE_DIR}/include) -add_executable(${PROJECT_NAME} Screen_Capture_Example.cpp) + + +add_executable(${PROJECT_NAME} tiny_jpeg.h Screen_Capture_Example.cpp) target_link_libraries(${PROJECT_NAME} Screen_Capturelib) install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) \ No newline at end of file diff --git a/Example/Screen_Capture_Example.cpp b/Example/Screen_Capture_Example.cpp index b9e68e2a..9138cde2 100644 --- a/Example/Screen_Capture_Example.cpp +++ b/Example/Screen_Capture_Example.cpp @@ -1,23 +1,39 @@ #include "windows\ScreenCaptureDX.h" #include #include +#include + +#include +#define TJE_IMPLEMENTATION +#include "tiny_jpeg.h" int main() { + + std::atomic realcounter; + realcounter = 0; SL::Screen_Capture::ScreenCaptureDX dx; - SL::Screen_Capture::ImageCallback func = [](const SL::Screen_Capture::Image& img, SL::Screen_Capture::Captured_Image type) { - //std::cout << "Got here" << std::endl; + SL::Screen_Capture::ImageCallback func = [&](const SL::Screen_Capture::CapturedImage& img) { + if (img.ScreenIndex != 1) return; + std::cout << "Height " << img.Height << ", Width " << img.Width << ", Top " << img.AbsoluteTop << ", Left " << img.AbsoluteLeft << std::endl; + auto r = realcounter.fetch_add(1); + std::string s("screen"); + s += std::to_string(img.AbsoluteTop) + std::string(",") + std::to_string(img.AbsoluteLeft) +std::string(" ") + std::to_string(r) + std::string(".jpg"); + //savetodisk(img, s); + + if (!tje_encode_to_file(s.c_str(), img.Width, img.Height, 4, (const unsigned char*)img.Data.get())) { + std::cout << "Could not write JPEG\n"; + } }; dx.StartProcessing(func); - while (true) { - - - - std::this_thread::sleep_for(std::chrono::milliseconds(50)); + while (true) { + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } + dx.StopProcessing(); int k = 0; std::cin >> k; return 0; -} \ No newline at end of file +} + diff --git a/Example/tiny_jpeg.h b/Example/tiny_jpeg.h new file mode 100644 index 00000000..e8f9a928 --- /dev/null +++ b/Example/tiny_jpeg.h @@ -0,0 +1,1279 @@ +/** + * tiny_jpeg.h + * + * Tiny JPEG Encoder + * - Sergio Gonzalez + * + * This is a readable and simple single-header JPEG encoder. + * + * Features + * - Implements Baseline DCT JPEG compression. + * - No dynamic allocations. + * + * This library is coded in the spirit of the stb libraries and mostly follows + * the stb guidelines. + * + * It is written in C99. And depends on the C standard library. + * Works with C++11 + * + * + * ==== Thanks ==== + * + * AssociationSirius (Bug reports) + * Bernard van Gastel (Thread-safe defaults, BSD compilation) + * + * + * ==== License ==== + * + * This software is in the public domain. Where that dedication is not + * recognized, you are granted a perpetual, irrevocable license to copy and + * modify this file as you see fit. + * + */ + +// ============================================================ +// Usage +// ============================================================ +// Include "tiny_jpeg.h" to and use the public interface defined below. +// +// You *must* do: +// +// #define TJE_IMPLEMENTATION +// #include "tiny_jpeg.h" +// +// in exactly one of your C files to actually compile the implementation. + + +// Here is an example program that loads a bmp with stb_image and writes it +// with Tiny JPEG + +/* + +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + + +#define TJE_IMPLEMENTATION +#include "tiny_jpeg.h" + + +int main() +{ + int width, height, num_components; + unsigned char* data = stbi_load("in.bmp", &width, &height, &num_components, 0); + if ( !data ) { + puts("Could not find file"); + return EXIT_FAILURE; + } + + if ( !tje_encode_to_file("out.jpg", width, height, num_components, data) ) { + fprintf(stderr, "Could not write JPEG\n"); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +*/ + + + +#ifdef __cplusplus +extern "C" +{ +#endif + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" // We use {0}, which will zero-out the struct. +#pragma GCC diagnostic ignored "-Wmissing-braces" +#pragma GCC diagnostic ignored "-Wpadded" +#endif + +// ============================================================ +// Public interface: +// ============================================================ + +#ifndef TJE_HEADER_GUARD +#define TJE_HEADER_GUARD + +// - tje_encode_to_file - +// +// Usage: +// Takes bitmap data and writes a JPEG-encoded image to disk. +// +// PARAMETERS +// dest_path: filename to which we will write. e.g. "out.jpg" +// width, height: image size in pixels +// num_components: 3 is RGB. 4 is RGBA. Those are the only supported values +// src_data: pointer to the pixel data. +// +// RETURN: +// 0 on error. 1 on success. + +int tje_encode_to_file(const char* dest_path, + const int width, + const int height, + const int num_components, + const unsigned char* src_data); + +// - tje_encode_to_file_at_quality - +// +// Usage: +// Takes bitmap data and writes a JPEG-encoded image to disk. +// +// PARAMETERS +// dest_path: filename to which we will write. e.g. "out.jpg" +// quality: 3: Highest. Compression varies wildly (between 1/3 and 1/20). +// 2: Very good quality. About 1/2 the size of 3. +// 1: Noticeable. About 1/6 the size of 3, or 1/3 the size of 2. +// width, height: image size in pixels +// num_components: 3 is RGB. 4 is RGBA. Those are the only supported values +// src_data: pointer to the pixel data. +// +// RETURN: +// 0 on error. 1 on success. + +int tje_encode_to_file_at_quality(const char* dest_path, + const int quality, + const int width, + const int height, + const int num_components, + const unsigned char* src_data); + +// - tje_encode_with_func - +// +// Usage +// Same as tje_encode_to_file_at_quality, but it takes a callback that knows +// how to handle (or ignore) `context`. The callback receives an array `data` +// of `size` bytes, which can be written directly to a file. There is no need +// to free the data. + +typedef void tje_write_func(void* context, void* data, int size); + +int tje_encode_with_func(tje_write_func* func, + void* context, + const int quality, + const int width, + const int height, + const int num_components, + const unsigned char* src_data); + +#endif // TJE_HEADER_GUARD + + + +// Implementation: In exactly one of the source files of your application, +// define TJE_IMPLEMENTATION and include tiny_jpeg.h + +// ============================================================ +// Internal +// ============================================================ +#ifdef TJE_IMPLEMENTATION + + +#define tjei_min(a, b) ((a) < b) ? (a) : (b); +#define tjei_max(a, b) ((a) < b) ? (b) : (a); + + +#if defined(_MSC_VER) +#define TJEI_FORCE_INLINE __forceinline +// #define TJEI_FORCE_INLINE __declspec(noinline) // For profiling +#else +#define TJEI_FORCE_INLINE static // TODO: equivalent for gcc & clang +#endif + +// Only use zero for debugging and/or inspection. +#define TJE_USE_FAST_DCT 1 + +// C std lib +#include +#include +#include // floorf, ceilf +#include // FILE, puts +#include // memcpy + + +#define TJEI_BUFFER_SIZE 1024 + +#ifdef _WIN32 + +#include +#ifndef snprintf +#define snprintf sprintf_s +#endif +// Not quite the same but it works for us. If I am not mistaken, it differs +// only in the return value. + +#endif + +#ifndef NDEBUG + +#ifdef _WIN32 +#define tje_log(msg) OutputDebugStringA(msg) +#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) +#define tje_log(msg) puts(msg) +#else +#warning "need a tje_log definition for your platform for debugging purposes (not needed if compiling with NDEBUG)" +#endif + +#else // NDEBUG +#define tje_log(msg) +#endif // NDEBUG + + +typedef struct +{ + void* context; + tje_write_func* func; +} TJEWriteContext; + +typedef struct +{ + // Huffman data. + uint8_t ehuffsize[4][257]; + uint16_t ehuffcode[4][256]; + uint8_t const * ht_bits[4]; + uint8_t const * ht_vals[4]; + + // Cuantization tables. + uint8_t qt_luma[64]; + uint8_t qt_chroma[64]; + + // fwrite by default. User-defined when using tje_encode_with_func. + TJEWriteContext write_context; + + // Buffered output. Big performance win when using the usual stdlib implementations. + size_t output_buffer_count; + uint8_t output_buffer[TJEI_BUFFER_SIZE]; +} TJEState; + +// ============================================================ +// Table definitions. +// +// The spec defines tjei_default reasonably good quantization matrices and huffman +// specification tables. +// +// +// Instead of hard-coding the final huffman table, we only hard-code the table +// spec suggested by the specification, and then derive the full table from +// there. This is only for didactic purposes but it might be useful if there +// ever is the case that we need to swap huffman tables from various sources. +// ============================================================ + + +// K.1 - suggested luminance QT +static const uint8_t tjei_default_qt_luma_from_spec[] = +{ + 16,11,10,16, 24, 40, 51, 61, + 12,12,14,19, 26, 58, 60, 55, + 14,13,16,24, 40, 57, 69, 56, + 14,17,22,29, 51, 87, 80, 62, + 18,22,37,56, 68,109,103, 77, + 24,35,55,64, 81,104,113, 92, + 49,64,78,87,103,121,120,101, + 72,92,95,98,112,100,103, 99, +}; + +// Unused +#if 0 +static const uint8_t tjei_default_qt_chroma_from_spec[] = +{ + // K.1 - suggested chrominance QT + 17,18,24,47,99,99,99,99, + 18,21,26,66,99,99,99,99, + 24,26,56,99,99,99,99,99, + 47,66,99,99,99,99,99,99, + 99,99,99,99,99,99,99,99, + 99,99,99,99,99,99,99,99, + 99,99,99,99,99,99,99,99, + 99,99,99,99,99,99,99,99, +}; +#endif + +static const uint8_t tjei_default_qt_chroma_from_paper[] = +{ + // Example QT from JPEG paper + 16, 12, 14, 14, 18, 24, 49, 72, + 11, 10, 16, 24, 40, 51, 61, 12, + 13, 17, 22, 35, 64, 92, 14, 16, + 22, 37, 55, 78, 95, 19, 24, 29, + 56, 64, 87, 98, 26, 40, 51, 68, + 81, 103, 112, 58, 57, 87, 109, 104, + 121,100, 60, 69, 80, 103, 113, 120, + 103, 55, 56, 62, 77, 92, 101, 99, +}; + +// == Procedure to 'deflate' the huffman tree: JPEG spec, C.2 + +// Number of 16 bit values for every code length. (K.3.3.1) +static const uint8_t tjei_default_ht_luma_dc_len[16] = +{ + 0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0 +}; +// values +static const uint8_t tjei_default_ht_luma_dc[12] = +{ + 0,1,2,3,4,5,6,7,8,9,10,11 +}; + +// Number of 16 bit values for every code length. (K.3.3.1) +static const uint8_t tjei_default_ht_chroma_dc_len[16] = +{ + 0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0 +}; +// values +static const uint8_t tjei_default_ht_chroma_dc[12] = +{ + 0,1,2,3,4,5,6,7,8,9,10,11 +}; + +// Same as above, but AC coefficients. +static const uint8_t tjei_default_ht_luma_ac_len[16] = +{ + 0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d +}; +static const uint8_t tjei_default_ht_luma_ac[] = +{ + 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, + 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0, + 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, + 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, + 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2, + 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, + 0xF9, 0xFA +}; + +static const uint8_t tjei_default_ht_chroma_ac_len[16] = +{ + 0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77 +}; +static const uint8_t tjei_default_ht_chroma_ac[] = +{ + 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, + 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33, 0x52, 0xF0, + 0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, 0x24, 0x34, 0xE1, 0x25, 0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26, + 0x27, 0x28, 0x29, 0x2A, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, + 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, + 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, + 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, + 0xF9, 0xFA +}; + + +// ============================================================ +// Code +// ============================================================ + +// Zig-zag order: +static const uint8_t tjei_zig_zag[64] = +{ + 0, 1, 5, 6, 14, 15, 27, 28, + 2, 4, 7, 13, 16, 26, 29, 42, + 3, 8, 12, 17, 25, 30, 41, 43, + 9, 11, 18, 24, 31, 40, 44, 53, + 10, 19, 23, 32, 39, 45, 52, 54, + 20, 22, 33, 38, 46, 51, 55, 60, + 21, 34, 37, 47, 50, 56, 59, 61, + 35, 36, 48, 49, 57, 58, 62, 63, +}; + +// Memory order as big endian. 0xhilo -> 0xlohi which looks as 0xhilo in memory. +static uint16_t tjei_be_word(const uint16_t le_word) +{ + uint16_t lo = (le_word & 0x00ff); + uint16_t hi = ((le_word & 0xff00) >> 8); + return (uint16_t)((lo << 8) | hi); +} + +// ============================================================ +// The following structs exist only for code clarity, debugability, and +// readability. They are used when writing to disk, but it is useful to have +// 1-packed-structs to document how the format works, and to inspect memory +// while developing. +// ============================================================ + +static const uint8_t tjeik_jfif_id[] = "JFIF"; +static const uint8_t tjeik_com_str[] = "Created by Tiny JPEG Encoder"; + +// TODO: Get rid of packed structs! +#pragma pack(push) +#pragma pack(1) +typedef struct +{ + uint16_t SOI; + // JFIF header. + uint16_t APP0; + uint16_t jfif_len; + uint8_t jfif_id[5]; + uint16_t version; + uint8_t units; + uint16_t x_density; + uint16_t y_density; + uint8_t x_thumb; + uint8_t y_thumb; +} TJEJPEGHeader; + +typedef struct +{ + uint16_t com; + uint16_t com_len; + char com_str[sizeof(tjeik_com_str) - 1]; +} TJEJPEGComment; + +// Helper struct for TJEFrameHeader (below). +typedef struct +{ + uint8_t component_id; + uint8_t sampling_factors; // most significant 4 bits: horizontal. 4 LSB: vertical (A.1.1) + uint8_t qt; // Quantization table selector. +} TJEComponentSpec; + +typedef struct +{ + uint16_t SOF; + uint16_t len; // 8 + 3 * frame.num_components + uint8_t precision; // Sample precision (bits per sample). + uint16_t height; + uint16_t width; + uint8_t num_components; // For this implementation, will be equal to 3. + TJEComponentSpec component_spec[3]; +} TJEFrameHeader; + +typedef struct +{ + uint8_t component_id; // Just as with TJEComponentSpec + uint8_t dc_ac; // (dc|ac) +} TJEFrameComponentSpec; + +typedef struct +{ + uint16_t SOS; + uint16_t len; + uint8_t num_components; // 3. + TJEFrameComponentSpec component_spec[3]; + uint8_t first; // 0 + uint8_t last; // 63 + uint8_t ah_al; // o +} TJEScanHeader; +#pragma pack(pop) + + +static void tjei_write(TJEState* state, const void* data, size_t num_bytes, size_t num_elements) +{ + size_t to_write = num_bytes * num_elements; + + // Cap to the buffer available size and copy memory. + size_t capped_count = tjei_min(to_write, TJEI_BUFFER_SIZE - 1 - state->output_buffer_count); + + memcpy(state->output_buffer + state->output_buffer_count, data, capped_count); + state->output_buffer_count += capped_count; + + assert (state->output_buffer_count <= TJEI_BUFFER_SIZE - 1); + + // Flush the buffer. + if ( state->output_buffer_count == TJEI_BUFFER_SIZE - 1 ) { + state->write_context.func(state->write_context.context, state->output_buffer, (int)state->output_buffer_count); + state->output_buffer_count = 0; + } + + // Recursively calling ourselves with the rest of the buffer. + if (capped_count < to_write) { + tjei_write(state, (uint8_t*)data+capped_count, to_write - capped_count, 1); + } +} + +static void tjei_write_DQT(TJEState* state, const uint8_t* matrix, uint8_t id) +{ + uint16_t DQT = tjei_be_word(0xffdb); + tjei_write(state, &DQT, sizeof(uint16_t), 1); + uint16_t len = tjei_be_word(0x0043); // 2(len) + 1(id) + 64(matrix) = 67 = 0x43 + tjei_write(state, &len, sizeof(uint16_t), 1); + assert(id < 4); + uint8_t precision_and_id = id; // 0x0000 8 bits | 0x00id + tjei_write(state, &precision_and_id, sizeof(uint8_t), 1); + // Write matrix + tjei_write(state, matrix, 64*sizeof(uint8_t), 1); +} + +typedef enum +{ + TJEI_DC = 0, + TJEI_AC = 1 +} TJEHuffmanTableClass; + +static void tjei_write_DHT(TJEState* state, + uint8_t const * matrix_len, + uint8_t const * matrix_val, + TJEHuffmanTableClass ht_class, + uint8_t id) +{ + int num_values = 0; + for ( int i = 0; i < 16; ++i ) { + num_values += matrix_len[i]; + } + assert(num_values <= 0xffff); + + uint16_t DHT = tjei_be_word(0xffc4); + // 2(len) + 1(Tc|th) + 16 (num lengths) + ?? (num values) + uint16_t len = tjei_be_word(2 + 1 + 16 + (uint16_t)num_values); + assert(id < 4); + uint8_t tc_th = (uint8_t)((((uint8_t)ht_class) << 4) | id); + + tjei_write(state, &DHT, sizeof(uint16_t), 1); + tjei_write(state, &len, sizeof(uint16_t), 1); + tjei_write(state, &tc_th, sizeof(uint8_t), 1); + tjei_write(state, matrix_len, sizeof(uint8_t), 16); + tjei_write(state, matrix_val, sizeof(uint8_t), (size_t)num_values); +} +// ============================================================ +// Huffman deflation code. +// ============================================================ + +// Returns all code sizes from the BITS specification (JPEG C.3) +static uint8_t* tjei_huff_get_code_lengths(uint8_t huffsize[/*256*/], uint8_t const * bits) +{ + int k = 0; + for ( int i = 0; i < 16; ++i ) { + for ( int j = 0; j < bits[i]; ++j ) { + huffsize[k++] = (uint8_t)(i + 1); + } + huffsize[k] = 0; + } + return huffsize; +} + +// Fills out the prefixes for each code. +static uint16_t* tjei_huff_get_codes(uint16_t codes[], uint8_t* huffsize, int64_t count) +{ + uint16_t code = 0; + int k = 0; + uint8_t sz = huffsize[0]; + for(;;) { + do { + assert(k < count); + codes[k++] = code++; + } while (huffsize[k] == sz); + if (huffsize[k] == 0) { + return codes; + } + do { + code = (uint16_t)(code << 1); + ++sz; + } while( huffsize[k] != sz ); + } +} + +static void tjei_huff_get_extended(uint8_t* out_ehuffsize, + uint16_t* out_ehuffcode, + uint8_t const * huffval, + uint8_t* huffsize, + uint16_t* huffcode, int64_t count) +{ + int k = 0; + do { + uint8_t val = huffval[k]; + out_ehuffcode[val] = huffcode[k]; + out_ehuffsize[val] = huffsize[k]; + k++; + } while ( k < count ); +} +// ============================================================ + +// Returns: +// out[1] : number of bits +// out[0] : bits +TJEI_FORCE_INLINE void tjei_calculate_variable_length_int(int value, uint16_t out[2]) +{ + int abs_val = value; + if ( value < 0 ) { + abs_val = -abs_val; + --value; + } + out[1] = 1; + while( abs_val >>= 1 ) { + ++out[1]; + } + out[0] = (uint16_t)(value & ((1 << out[1]) - 1)); +} + +// Write bits to file. +TJEI_FORCE_INLINE void tjei_write_bits(TJEState* state, + uint32_t* bitbuffer, uint32_t* location, + uint16_t num_bits, uint16_t bits) +{ + // v-- location + // [ ] <-- bit buffer + // 32 0 + // + // This call pushes to the bitbuffer and saves the location. Data is pushed + // from most significant to less significant. + // When we can write a full byte, we write a byte and shift. + + // Push the stack. + uint32_t nloc = *location + num_bits; + *bitbuffer |= (uint32_t)(bits << (32 - nloc)); + *location = nloc; + while ( *location >= 8 ) { + // Grab the most significant byte. + uint8_t c = (uint8_t)((*bitbuffer) >> 24); + // Write it to file. + tjei_write(state, &c, 1, 1); + if ( c == 0xff ) { + // Special case: tell JPEG this is not a marker. + char z = 0; + tjei_write(state, &z, 1, 1); + } + // Pop the stack. + *bitbuffer <<= 8; + *location -= 8; + } +} + +// DCT implementation by Thomas G. Lane. +// Obtained through NVIDIA +// http://developer.download.nvidia.com/SDK/9.5/Samples/vidimaging_samples.html#gpgpu_dct +// +// QUOTE: +// This implementation is based on Arai, Agui, and Nakajima's algorithm for +// scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in +// Japanese, but the algorithm is described in the Pennebaker & Mitchell +// JPEG textbook (see REFERENCES section in file README). The following code +// is based directly on figure 4-8 in P&M. +// +static void tjei_fdct (float * data) +{ + float tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + float tmp10, tmp11, tmp12, tmp13; + float z1, z2, z3, z4, z5, z11, z13; + float *dataptr; + int ctr; + + /* Pass 1: process rows. */ + + dataptr = data; + for ( ctr = 7; ctr >= 0; ctr-- ) { + tmp0 = dataptr[0] + dataptr[7]; + tmp7 = dataptr[0] - dataptr[7]; + tmp1 = dataptr[1] + dataptr[6]; + tmp6 = dataptr[1] - dataptr[6]; + tmp2 = dataptr[2] + dataptr[5]; + tmp5 = dataptr[2] - dataptr[5]; + tmp3 = dataptr[3] + dataptr[4]; + tmp4 = dataptr[3] - dataptr[4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[0] = tmp10 + tmp11; /* phase 3 */ + dataptr[4] = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * ((float) 0.707106781); /* c4 */ + dataptr[2] = tmp13 + z1; /* phase 5 */ + dataptr[6] = tmp13 - z1; + + /* Odd part */ + + tmp10 = tmp4 + tmp5; /* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + z5 = (tmp10 - tmp12) * ((float) 0.382683433); /* c6 */ + z2 = ((float) 0.541196100) * tmp10 + z5; /* c2-c6 */ + z4 = ((float) 1.306562965) * tmp12 + z5; /* c2+c6 */ + z3 = tmp11 * ((float) 0.707106781); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[5] = z13 + z2; /* phase 6 */ + dataptr[3] = z13 - z2; + dataptr[1] = z11 + z4; + dataptr[7] = z11 - z4; + + dataptr += 8; /* advance pointer to next row */ + } + + /* Pass 2: process columns. */ + + dataptr = data; + for ( ctr = 8-1; ctr >= 0; ctr-- ) { + tmp0 = dataptr[8*0] + dataptr[8*7]; + tmp7 = dataptr[8*0] - dataptr[8*7]; + tmp1 = dataptr[8*1] + dataptr[8*6]; + tmp6 = dataptr[8*1] - dataptr[8*6]; + tmp2 = dataptr[8*2] + dataptr[8*5]; + tmp5 = dataptr[8*2] - dataptr[8*5]; + tmp3 = dataptr[8*3] + dataptr[8*4]; + tmp4 = dataptr[8*3] - dataptr[8*4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[8*0] = tmp10 + tmp11; /* phase 3 */ + dataptr[8*4] = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * ((float) 0.707106781); /* c4 */ + dataptr[8*2] = tmp13 + z1; /* phase 5 */ + dataptr[8*6] = tmp13 - z1; + + /* Odd part */ + + tmp10 = tmp4 + tmp5; /* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + z5 = (tmp10 - tmp12) * ((float) 0.382683433); /* c6 */ + z2 = ((float) 0.541196100) * tmp10 + z5; /* c2-c6 */ + z4 = ((float) 1.306562965) * tmp12 + z5; /* c2+c6 */ + z3 = tmp11 * ((float) 0.707106781); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[8*5] = z13 + z2; /* phase 6 */ + dataptr[8*3] = z13 - z2; + dataptr[8*1] = z11 + z4; + dataptr[8*7] = z11 - z4; + + dataptr++; /* advance pointer to next column */ + } +} +#if !TJE_USE_FAST_DCT +static float slow_fdct(int u, int v, float* data) +{ +#define kPI 3.14159265f + float res = 0.0f; + float cu = (u == 0) ? 0.70710678118654f : 1; + float cv = (v == 0) ? 0.70710678118654f : 1; + for ( int y = 0; y < 8; ++y ) { + for ( int x = 0; x < 8; ++x ) { + res += (data[y * 8 + x]) * + cosf(((2.0f * x + 1.0f) * u * kPI) / 16.0f) * + cosf(((2.0f * y + 1.0f) * v * kPI) / 16.0f); + } + } + res *= 0.25f * cu * cv; + return res; +#undef kPI +} +#endif + +#define ABS(x) ((x) < 0 ? -(x) : (x)) + +static void tjei_encode_and_write_MCU(TJEState* state, + float* mcu, +#if TJE_USE_FAST_DCT + float* qt, // Pre-processed quantization matrix. +#else + uint8_t* qt, +#endif + uint8_t* huff_dc_len, uint16_t* huff_dc_code, // Huffman tables + uint8_t* huff_ac_len, uint16_t* huff_ac_code, + int* pred, // Previous DC coefficient + uint32_t* bitbuffer, // Bitstack. + uint32_t* location) +{ + int du[64]; // Data unit in zig-zag order + + float dct_mcu[64]; + memcpy(dct_mcu, mcu, 64 * sizeof(float)); + +#if TJE_USE_FAST_DCT + tjei_fdct(dct_mcu); + for ( int i = 0; i < 64; ++i ) { + float fval = dct_mcu[i]; + fval *= qt[i]; +#if 0 + fval = (fval > 0) ? floorf(fval + 0.5f) : ceilf(fval - 0.5f); +#else + fval = floorf(fval + 1024 + 0.5f); + fval -= 1024; +#endif + int val = (int)fval; + du[tjei_zig_zag[i]] = val; + } +#else + for ( int v = 0; v < 8; ++v ) { + for ( int u = 0; u < 8; ++u ) { + dct_mcu[v * 8 + u] = slow_fdct(u, v, mcu); + } + } + for ( int i = 0; i < 64; ++i ) { + float fval = dct_mcu[i] / (qt[i]); + int val = (int)((fval > 0) ? floorf(fval + 0.5f) : ceilf(fval - 0.5f)); + du[tjei_zig_zag[i]] = val; + } +#endif + + uint16_t vli[2]; + + // Encode DC coefficient. + int diff = du[0] - *pred; + *pred = du[0]; + if ( diff != 0 ) { + tjei_calculate_variable_length_int(diff, vli); + // Write number of bits with Huffman coding + tjei_write_bits(state, bitbuffer, location, huff_dc_len[vli[1]], huff_dc_code[vli[1]]); + // Write the bits. + tjei_write_bits(state, bitbuffer, location, vli[1], vli[0]); + } else { + tjei_write_bits(state, bitbuffer, location, huff_dc_len[0], huff_dc_code[0]); + } + + // ==== Encode AC coefficients ==== + + int last_non_zero_i = 0; + // Find the last non-zero element. + for ( int i = 63; i > 0; --i ) { + if (du[i] != 0) { + last_non_zero_i = i; + break; + } + } + + for ( int i = 1; i <= last_non_zero_i; ++i ) { + // If zero, increase count. If >=15, encode (FF,00) + int zero_count = 0; + while ( du[i] == 0 ) { + ++zero_count; + ++i; + if (zero_count == 16) { + // encode (ff,00) == 0xf0 + tjei_write_bits(state, bitbuffer, location, huff_ac_len[0xf0], huff_ac_code[0xf0]); + zero_count = 0; + } + } + tjei_calculate_variable_length_int(du[i], vli); + + assert(zero_count < 0x10); + assert(vli[1] <= 10); + + uint16_t sym1 = (uint16_t)((uint16_t)zero_count << 4) | vli[1]; + + assert(huff_ac_len[sym1] != 0); + + // Write symbol 1 --- (RUNLENGTH, SIZE) + tjei_write_bits(state, bitbuffer, location, huff_ac_len[sym1], huff_ac_code[sym1]); + // Write symbol 2 --- (AMPLITUDE) + tjei_write_bits(state, bitbuffer, location, vli[1], vli[0]); + } + + if (last_non_zero_i != 63) { + // write EOB HUFF(00,00) + tjei_write_bits(state, bitbuffer, location, huff_ac_len[0], huff_ac_code[0]); + } + return; +} + +enum { + TJEI_LUMA_DC, + TJEI_LUMA_AC, + TJEI_CHROMA_DC, + TJEI_CHROMA_AC, +}; + +#if TJE_USE_FAST_DCT +struct TJEProcessedQT +{ + float chroma[64]; + float luma[64]; +}; +#endif + +// Set up huffman tables in state. +static void tjei_huff_expand(TJEState* state) +{ + assert(state); + + state->ht_bits[TJEI_LUMA_DC] = tjei_default_ht_luma_dc_len; + state->ht_bits[TJEI_LUMA_AC] = tjei_default_ht_luma_ac_len; + state->ht_bits[TJEI_CHROMA_DC] = tjei_default_ht_chroma_dc_len; + state->ht_bits[TJEI_CHROMA_AC] = tjei_default_ht_chroma_ac_len; + + state->ht_vals[TJEI_LUMA_DC] = tjei_default_ht_luma_dc; + state->ht_vals[TJEI_LUMA_AC] = tjei_default_ht_luma_ac; + state->ht_vals[TJEI_CHROMA_DC] = tjei_default_ht_chroma_dc; + state->ht_vals[TJEI_CHROMA_AC] = tjei_default_ht_chroma_ac; + + // How many codes in total for each of LUMA_(DC|AC) and CHROMA_(DC|AC) + int32_t spec_tables_len[4] = { 0 }; + + for ( int i = 0; i < 4; ++i ) { + for ( int k = 0; k < 16; ++k ) { + spec_tables_len[i] += state->ht_bits[i][k]; + } + } + + // Fill out the extended tables.. + uint8_t huffsize[4][257]; + uint16_t huffcode[4][256]; + for ( int i = 0; i < 4; ++i ) { + assert (256 >= spec_tables_len[i]); + tjei_huff_get_code_lengths(huffsize[i], state->ht_bits[i]); + tjei_huff_get_codes(huffcode[i], huffsize[i], spec_tables_len[i]); + } + for ( int i = 0; i < 4; ++i ) { + int64_t count = spec_tables_len[i]; + tjei_huff_get_extended(state->ehuffsize[i], + state->ehuffcode[i], + state->ht_vals[i], + &huffsize[i][0], + &huffcode[i][0], count); + } +} + +static int tjei_encode_main(TJEState* state, + const unsigned char* src_data, + const int width, + const int height, + const int src_num_components) +{ + if (src_num_components != 3 && src_num_components != 4) { + return 0; + } + + if (width > 0xffff || height > 0xffff) { + return 0; + } + +#if TJE_USE_FAST_DCT + struct TJEProcessedQT pqt; + // Again, taken from classic japanese implementation. + // + /* For float AA&N IDCT method, divisors are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * We apply a further scale factor of 8. + * What's actually stored is 1/divisor so that the inner loop can + * use a multiplication rather than a division. + */ + static const float aan_scales[] = { + 1.0f, 1.387039845f, 1.306562965f, 1.175875602f, + 1.0f, 0.785694958f, 0.541196100f, 0.275899379f + }; + + // build (de)quantization tables + for(int y=0; y<8; y++) { + for(int x=0; x<8; x++) { + int i = y*8 + x; + pqt.luma[y*8+x] = 1.0f / (8 * aan_scales[x] * aan_scales[y] * state->qt_luma[tjei_zig_zag[i]]); + pqt.chroma[y*8+x] = 1.0f / (8 * aan_scales[x] * aan_scales[y] * state->qt_chroma[tjei_zig_zag[i]]); + } + } +#endif + + { // Write header + TJEJPEGHeader header; + // JFIF header. + header.SOI = tjei_be_word(0xffd8); // Sequential DCT + header.APP0 = tjei_be_word(0xffe0); + + uint16_t jfif_len = sizeof(TJEJPEGHeader) - 4 /*SOI & APP0 markers*/; + header.jfif_len = tjei_be_word(jfif_len); + memcpy(header.jfif_id, (void*)tjeik_jfif_id, 5); + header.version = tjei_be_word(0x0102); + header.units = 0x01; // Dots-per-inch + header.x_density = tjei_be_word(0x0060); // 96 DPI + header.y_density = tjei_be_word(0x0060); // 96 DPI + header.x_thumb = 0; + header.y_thumb = 0; + tjei_write(state, &header, sizeof(TJEJPEGHeader), 1); + } + { // Write comment + TJEJPEGComment com; + uint16_t com_len = 2 + sizeof(tjeik_com_str) - 1; + // Comment + com.com = tjei_be_word(0xfffe); + com.com_len = tjei_be_word(com_len); + memcpy(com.com_str, (void*)tjeik_com_str, sizeof(tjeik_com_str)-1); + tjei_write(state, &com, sizeof(TJEJPEGComment), 1); + } + + // Write quantization tables. + tjei_write_DQT(state, state->qt_luma, 0x00); + tjei_write_DQT(state, state->qt_chroma, 0x01); + + { // Write the frame marker. + TJEFrameHeader header; + header.SOF = tjei_be_word(0xffc0); + header.len = tjei_be_word(8 + 3 * 3); + header.precision = 8; + assert(width <= 0xffff); + assert(height <= 0xffff); + header.width = tjei_be_word((uint16_t)width); + header.height = tjei_be_word((uint16_t)height); + header.num_components = 3; + uint8_t tables[3] = { + 0, // Luma component gets luma table (see tjei_write_DQT call above.) + 1, // Chroma component gets chroma table + 1, // Chroma component gets chroma table + }; + for (int i = 0; i < 3; ++i) { + TJEComponentSpec spec; + spec.component_id = (uint8_t)(i + 1); // No particular reason. Just 1, 2, 3. + spec.sampling_factors = (uint8_t)0x11; + spec.qt = tables[i]; + + header.component_spec[i] = spec; + } + // Write to file. + tjei_write(state, &header, sizeof(TJEFrameHeader), 1); + } + + tjei_write_DHT(state, state->ht_bits[TJEI_LUMA_DC], state->ht_vals[TJEI_LUMA_DC], TJEI_DC, 0); + tjei_write_DHT(state, state->ht_bits[TJEI_LUMA_AC], state->ht_vals[TJEI_LUMA_AC], TJEI_AC, 0); + tjei_write_DHT(state, state->ht_bits[TJEI_CHROMA_DC], state->ht_vals[TJEI_CHROMA_DC], TJEI_DC, 1); + tjei_write_DHT(state, state->ht_bits[TJEI_CHROMA_AC], state->ht_vals[TJEI_CHROMA_AC], TJEI_AC, 1); + + // Write start of scan + { + TJEScanHeader header; + header.SOS = tjei_be_word(0xffda); + header.len = tjei_be_word((uint16_t)(6 + (sizeof(TJEFrameComponentSpec) * 3))); + header.num_components = 3; + + uint8_t tables[3] = { + 0x00, + 0x11, + 0x11, + }; + for (int i = 0; i < 3; ++i) { + TJEFrameComponentSpec cs; + // Must be equal to component_id from frame header above. + cs.component_id = (uint8_t)(i + 1); + cs.dc_ac = (uint8_t)tables[i]; + + header.component_spec[i] = cs; + } + header.first = 0; + header.last = 63; + header.ah_al = 0; + tjei_write(state, &header, sizeof(TJEScanHeader), 1); + + } + // Write compressed data. + + float du_y[64]; + float du_b[64]; + float du_r[64]; + + // Set diff to 0. + int pred_y = 0; + int pred_b = 0; + int pred_r = 0; + + // Bit stack + uint32_t bitbuffer = 0; + uint32_t location = 0; + + + for ( int y = 0; y < height; y += 8 ) { + for ( int x = 0; x < width; x += 8 ) { + // Block loop: ==== + for ( int off_y = 0; off_y < 8; ++off_y ) { + for ( int off_x = 0; off_x < 8; ++off_x ) { + int block_index = (off_y * 8 + off_x); + + int src_index = (((y + off_y) * width) + (x + off_x)) * src_num_components; + + int col = x + off_x; + int row = y + off_y; + + if(row >= height) { + src_index -= (width * (row - height + 1)) * src_num_components; + } + if(col >= width) { + src_index -= (col - width + 1) * src_num_components; + } + assert(src_index < width * height * src_num_components); + + uint8_t r = src_data[src_index + 0]; + uint8_t g = src_data[src_index + 1]; + uint8_t b = src_data[src_index + 2]; + + float luma = 0.299f * r + 0.587f * g + 0.114f * b - 128; + float cb = -0.1687f * r - 0.3313f * g + 0.5f * b; + float cr = 0.5f * r - 0.4187f * g - 0.0813f * b; + + du_y[block_index] = luma; + du_b[block_index] = cb; + du_r[block_index] = cr; + } + } + + tjei_encode_and_write_MCU(state, du_y, +#if TJE_USE_FAST_DCT + pqt.luma, +#else + state->qt_luma, +#endif + state->ehuffsize[TJEI_LUMA_DC], state->ehuffcode[TJEI_LUMA_DC], + state->ehuffsize[TJEI_LUMA_AC], state->ehuffcode[TJEI_LUMA_AC], + &pred_y, &bitbuffer, &location); + tjei_encode_and_write_MCU(state, du_b, +#if TJE_USE_FAST_DCT + pqt.chroma, +#else + state->qt_chroma, +#endif + state->ehuffsize[TJEI_CHROMA_DC], state->ehuffcode[TJEI_CHROMA_DC], + state->ehuffsize[TJEI_CHROMA_AC], state->ehuffcode[TJEI_CHROMA_AC], + &pred_b, &bitbuffer, &location); + tjei_encode_and_write_MCU(state, du_r, +#if TJE_USE_FAST_DCT + pqt.chroma, +#else + state->qt_chroma, +#endif + state->ehuffsize[TJEI_CHROMA_DC], state->ehuffcode[TJEI_CHROMA_DC], + state->ehuffsize[TJEI_CHROMA_AC], state->ehuffcode[TJEI_CHROMA_AC], + &pred_r, &bitbuffer, &location); + + + } + } + + // Finish the image. + { // Flush + if (location > 0 && location < 8) { + tjei_write_bits(state, &bitbuffer, &location, (uint16_t)(8 - location), 0); + } + } + uint16_t EOI = tjei_be_word(0xffd9); + tjei_write(state, &EOI, sizeof(uint16_t), 1); + + if (state->output_buffer_count) { + state->write_context.func(state->write_context.context, state->output_buffer, (int)state->output_buffer_count); + state->output_buffer_count = 0; + } + + return 1; +} + +int tje_encode_to_file(const char* dest_path, + const int width, + const int height, + const int num_components, + const unsigned char* src_data) +{ + int res = tje_encode_to_file_at_quality(dest_path, 3, width, height, num_components, src_data); + return res; +} + +static void tjei_stdlib_func(void* context, void* data, int size) +{ + FILE* fd = (FILE*)context; + fwrite(data, size, 1, fd); +} + +// Define public interface. +int tje_encode_to_file_at_quality(const char* dest_path, + const int quality, + const int width, + const int height, + const int num_components, + const unsigned char* src_data) +{ + FILE* fd = fopen(dest_path, "wb"); + if (!fd) { + tje_log("Could not open file for writing."); + return 0; + } + + int result = tje_encode_with_func(tjei_stdlib_func, fd, + quality, width, height, num_components, src_data); + + result |= 0 == fclose(fd); + + return result; +} + +int tje_encode_with_func(tje_write_func* func, + void* context, + const int quality, + const int width, + const int height, + const int num_components, + const unsigned char* src_data) +{ + if (quality < 1 || quality > 3) { + tje_log("[ERROR] -- Valid 'quality' values are 1 (lowest), 2, or 3 (highest)\n"); + return 0; + } + + TJEState state = { 0 }; + + uint8_t qt_factor = 1; + switch(quality) { + case 3: + for ( int i = 0; i < 64; ++i ) { + state.qt_luma[i] = 1; + state.qt_chroma[i] = 1; + } + break; + case 2: + qt_factor = 10; + // don't break. fall through. + case 1: + for ( int i = 0; i < 64; ++i ) { + state.qt_luma[i] = tjei_default_qt_luma_from_spec[i] / qt_factor; + if (state.qt_luma[i] == 0) { + state.qt_luma[i] = 1; + } + state.qt_chroma[i] = tjei_default_qt_chroma_from_paper[i] / qt_factor; + if (state.qt_chroma[i] == 0) { + state.qt_chroma[i] = 1; + } + } + break; + default: + assert(!"invalid code path"); + break; + } + + TJEWriteContext wc = { 0 }; + + wc.context = context; + wc.func = func; + + state.write_context = wc; + + + tjei_huff_expand(&state); + + int result = tjei_encode_main(&state, src_data, width, height, num_components); + + return result; +} +// ============================================================ +#endif // TJE_IMPLEMENTATION +// ============================================================ +// +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + + +#ifdef __cplusplus +} // extern C +#endif + diff --git a/include/InterruptableSleeper.h b/include/InterruptableSleeper.h deleted file mode 100644 index 1688dcfa..00000000 --- a/include/InterruptableSleeper.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace SL { - namespace Screen_Capture { - - class InterruptableSleeper { - std::timed_mutex mut_; - std::atomic_bool lockedByCreator_; // track whether the mutex is locked - void lock() { // lock mutex - mut_.lock(); - lockedByCreator_ = true; - } - void unlock() { // unlock mutex - lockedByCreator_ = false; - mut_.unlock(); - } - public: - // lock on creation - InterruptableSleeper() { - lock(); - } - // unlock on destruction, if wake was never called - ~InterruptableSleeper() { - if (lockedByCreator_) { - unlock(); - } - } - // called by any thread except the creator - // waits until wake is called or the specified time passes - template< class Rep, class Period > - void sleepFor(const std::chrono::duration& timeout_duration) { - if (mut_.try_lock_for(timeout_duration)) { - // if successfully locked, - // remove the lock - mut_.unlock(); - } - } - // unblock any waiting threads, handling a situation - // where wake has already been called. - // should only be called by the creating thread - void wake() { - if (lockedByCreator_) { - unlock(); - } - } - }; - - } -} diff --git a/include/ScreenTypes.h b/include/ScreenTypes.h index 7479897d..abfc8b36 100644 --- a/include/ScreenTypes.h +++ b/include/ScreenTypes.h @@ -4,16 +4,18 @@ namespace SL { namespace Screen_Capture { - struct Image { + struct CapturedImage { std::shared_ptr Data; int Height = 0; int Width = 0; - int Top = 0; - int Left = 0; + int RelativeTop = 0; + int RelativeLeft = 0; + int AbsoluteTop = 0; + int AbsoluteLeft = 0; + int ScreenIndex; const int PixelStride = 4;//in bytes }; - enum Captured_Image{ IMAGE_NEW, IMAGE_DIF, IMAGE_PERMISSION_NEEDED, IMAGE_ERROR }; - typedef std::function ImageCallback; + typedef std::function ImageCallback; } } \ No newline at end of file diff --git a/include/windows/CommonTypes.h b/include/windows/CommonTypes.h index c28e2f4a..caeb53c9 100644 --- a/include/windows/CommonTypes.h +++ b/include/windows/CommonTypes.h @@ -8,6 +8,7 @@ #pragma once #include #include +#include #define NOMINMAX #define WIN32_LEAN_AND_MEAN @@ -63,6 +64,7 @@ namespace SL { std::shared_ptr MetaData; UINT DirtyCount = 0; UINT MoveCount = 0; + UINT SrcreenIndex = 0; }; struct DX_RESOURCES { @@ -82,6 +84,7 @@ namespace SL { // Used by WinProc to signal to threads to exit std::shared_ptr TerminateThreadsEvent; + std::shared_ptr GlobalLock; UINT Output; ImageCallback CallBack; std::shared_ptr PtrInfo; diff --git a/include/windows/DXFrameProcessor.h b/include/windows/DXFrameProcessor.h index e2e2f579..bf43d451 100644 --- a/include/windows/DXFrameProcessor.h +++ b/include/windows/DXFrameProcessor.h @@ -24,11 +24,13 @@ namespace SL { Microsoft::WRL::ComPtr m_DeviceContext; Microsoft::WRL::ComPtr m_MoveSurf; Microsoft::WRL::ComPtr m_CopySurf; + Microsoft::WRL::ComPtr m_StagingSurf; Microsoft::WRL::ComPtr m_VertexShader; Microsoft::WRL::ComPtr m_PixelShader; Microsoft::WRL::ComPtr m_InputLayout; Microsoft::WRL::ComPtr m_RTV; Microsoft::WRL::ComPtr m_SamplerLinear; + ImageCallback CallBack; std::unique_ptr m_DirtyVertexBufferAlloc; diff --git a/include/windows/ScreenCaptureDX.h b/include/windows/ScreenCaptureDX.h index a08bc92f..b2931e65 100644 --- a/include/windows/ScreenCaptureDX.h +++ b/include/windows/ScreenCaptureDX.h @@ -1,7 +1,7 @@ #pragma once #include "ScreenTypes.h" -#include "InterruptableSleeper.h" +#include namespace SL { namespace Screen_Capture { @@ -9,7 +9,6 @@ namespace SL { class ScreenCaptureDX { std::thread _Thread; - InterruptableSleeper _InterruptableSleeper; bool _KeepRunning; public: diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index c94bf75f..cd90291e 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -27,7 +27,6 @@ endif() set(SCREEN_CAPTURE_COMMON_SRC ${Screen_Capture_SOURCE_DIR}/include/ScreenTypes.h ${Screen_Capture_SOURCE_DIR}/include/ScreenCaptureManager.h - ${Screen_Capture_SOURCE_DIR}/include/InterruptableSleeper.h ${Screen_Capture_SOURCE_DIR}/src/ScreenCaptureManager.cpp ) set(SCREEN_CAPTURE_COMMON_INC diff --git a/src/ScreenCaptureManager.cpp b/src/ScreenCaptureManager.cpp index 856c6a11..77e491ef 100644 --- a/src/ScreenCaptureManager.cpp +++ b/src/ScreenCaptureManager.cpp @@ -1,5 +1,4 @@ #include "ScreenCaptureManager.h" -#include "InterruptableSleeper.h" #include #include #include @@ -17,13 +16,12 @@ namespace SL { while (keeprunning) { - sleeper.sleepFor(std::chrono::milliseconds{ sleeptime }); } } public: std::thread thread; - InterruptableSleeper sleeper; + int sleeptime = 100;//in ms ImageCallback callback; bool keeprunning = true; @@ -40,7 +38,7 @@ namespace SL { } void stop() { if (thread.joinable()) { - sleeper.wake(); + thread.join(); } } diff --git a/src/windows/DXDuplicationManager.cpp b/src/windows/DXDuplicationManager.cpp index aa55b13d..1f63ca23 100644 --- a/src/windows/DXDuplicationManager.cpp +++ b/src/windows/DXDuplicationManager.cpp @@ -41,18 +41,14 @@ namespace SL { { return ProcessFailure(m_Device.Get(), L"Failed to acquire next frame in DUPLICATIONMANAGER", L"Error", hr, FrameInfoExpectedErrors); } - - // If still holding old frame, destroy it m_AcquiredDesktopImage = nullptr; - // QI for IDXGIResource hr = DesktopResource.Get()->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast(m_AcquiredDesktopImage.GetAddressOf())); - DesktopResource = nullptr; if (FAILED(hr)) { return ProcessFailure(nullptr, L"Failed to QI for ID3D11Texture2D from acquired IDXGIResource in DUPLICATIONMANAGER", L"Error", hr); } - + // Get metadata if (FrameInfo.TotalMetadataBufferSize) { @@ -98,7 +94,6 @@ namespace SL { Data->MetaData = m_MetaDataBuffer; } - Data->Frame = m_AcquiredDesktopImage; Data->FrameInfo = FrameInfo; @@ -114,7 +109,6 @@ namespace SL { return ProcessFailure(m_Device.Get(), L"Failed to release frame in DUPLICATIONMANAGER", L"Error", hr, FrameInfoExpectedErrors); } m_AcquiredDesktopImage = nullptr; - return DUPL_RETURN_SUCCESS; } @@ -124,18 +118,16 @@ namespace SL { m_Device = Device; // Get DXGI device - IDXGIDevice* DxgiDevice = nullptr; - HRESULT hr = Device->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast(&DxgiDevice)); + Microsoft::WRL::ComPtr DxgiDevice; + HRESULT hr = Device->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast(DxgiDevice.GetAddressOf())); if (FAILED(hr)) { return ProcessFailure(nullptr, L"Failed to QI for DXGI Device", L"Error", hr); } // Get DXGI adapter - IDXGIAdapter* DxgiAdapter = nullptr; - hr = DxgiDevice->GetParent(__uuidof(IDXGIAdapter), reinterpret_cast(&DxgiAdapter)); - DxgiDevice->Release(); - DxgiDevice = nullptr; + Microsoft::WRL::ComPtr DxgiAdapter; + hr = DxgiDevice->GetParent(__uuidof(IDXGIAdapter), reinterpret_cast(DxgiAdapter.GetAddressOf())); if (FAILED(hr)) { return ProcessFailure(m_Device.Get(), L"Failed to get parent DXGI Adapter", L"Error", hr, SystemTransitionsExpectedErrors); @@ -154,14 +146,14 @@ namespace SL { // QI for Output 1 Microsoft::WRL::ComPtr DxgiOutput1; - hr = DxgiOutput.Get()->QueryInterface(__uuidof(DxgiOutput1), reinterpret_cast(DxgiOutput1.GetAddressOf())); + hr = DxgiOutput.Get()->QueryInterface(__uuidof(IDXGIOutput1), reinterpret_cast(DxgiOutput1.GetAddressOf())); if (FAILED(hr)) { return ProcessFailure(nullptr, L"Failed to QI for DxgiOutput1 in DUPLICATIONMANAGER", L"Error", hr); } // Create desktop duplication - hr = DxgiOutput1->DuplicateOutput(Device, &m_DeskDupl); + hr = DxgiOutput1->DuplicateOutput(Device, m_DeskDupl.GetAddressOf()); if (FAILED(hr)) { return ProcessFailure(m_Device.Get(), L"Failed to get duplicate output in DUPLICATIONMANAGER", L"Error", hr, CreateDuplicationExpectedErrors); diff --git a/src/windows/DXFrameProcessor.cpp b/src/windows/DXFrameProcessor.cpp index 4800f865..309a7f86 100644 --- a/src/windows/DXFrameProcessor.cpp +++ b/src/windows/DXFrameProcessor.cpp @@ -1,4 +1,5 @@ #include "DXFrameProcessor.h" +#include namespace SL { namespace Screen_Capture { @@ -27,13 +28,13 @@ namespace SL { m_Device = nullptr; m_MoveSurf = nullptr; m_CopySurf = nullptr; - + m_VertexShader = nullptr; m_PixelShader = nullptr; m_InputLayout = nullptr; m_SamplerLinear = nullptr; m_RTV = nullptr; - CallBack = [](const Image& img, Captured_Image type) {}; + CallBack = [](const CapturedImage& img) {}; } @@ -58,9 +59,75 @@ namespace SL { if (Data->DirtyCount) { - Image img; - CallBack(img, SL::Screen_Capture::Captured_Image::IMAGE_NEW); - Ret = CopyDirty(Data->Frame.Get(), reinterpret_cast(Data->MetaData.get() + (Data->MoveCount * sizeof(DXGI_OUTDUPL_MOVE_RECT))), Data->DirtyCount, DeskDesc); + auto dirtyrects = reinterpret_cast(Data->MetaData.get() + (Data->MoveCount * sizeof(DXGI_OUTDUPL_MOVE_RECT))); + + Ret = CopyDirty(Data->Frame.Get(), dirtyrects, Data->DirtyCount, DeskDesc); + + D3D11_TEXTURE2D_DESC ThisDesc; + Data->Frame->GetDesc(&ThisDesc); + HRESULT hr; + if (!m_StagingSurf) + { + D3D11_TEXTURE2D_DESC StagingDesc; + StagingDesc = ThisDesc; + StagingDesc.BindFlags = 0; + StagingDesc.Usage = D3D11_USAGE_STAGING; + StagingDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + StagingDesc.MiscFlags = 0; + hr = m_Device->CreateTexture2D(&StagingDesc, nullptr, m_StagingSurf.GetAddressOf()); + if (FAILED(hr)) + { + return ProcessFailure(m_Device.Get(), L"Failed to create staging texture for move rects", L"Error", hr, SystemTransitionsExpectedErrors); + } + } + + for (UINT i = 0; i < Data->DirtyCount; ++i) + { + D3D11_BOX Box; + Box.left = dirtyrects[i].left; + Box.top = dirtyrects[i].top; + Box.front = 0; + Box.right = dirtyrects[i].right; + Box.bottom = dirtyrects[i].bottom; + Box.back = 1; + //m_DeviceContext->CopySubresourceRegion(m_StagingSurf.Get(), 0, dirtyrects[i].left, dirtyrects[i].top, 0, m_CopySurf.Get(), 0, &Box); + m_DeviceContext->CopyResource(m_StagingSurf.Get(), m_CopySurf.Get()); + D3D11_MAPPED_SUBRESOURCE MappingDesc; + hr = m_DeviceContext->Map(m_StagingSurf.Get(), 0, D3D11_MAP_READ, 0, &MappingDesc); + + // Get the data + if (MappingDesc.pData == NULL) { + return ProcessFailure(m_Device.Get(), L"DrawSurface_GetPixelColor: Could not read the pixel color because the mapped subresource returned NULL", L"Error", hr, SystemTransitionsExpectedErrors); + } + else { + CapturedImage img; + img.ScreenIndex = Data->SrcreenIndex; + img.Height = (Box.bottom - Box.top); + img.Width = (Box.right - Box.left); + img.RelativeTop = Box.top; + img.RelativeLeft = Box.left; + img.AbsoluteLeft = DeskDesc->DesktopCoordinates.left + Box.left; + img.AbsoluteTop = DeskDesc->DesktopCoordinates.top + Box.top; + auto sizeneeded = 4 * img.Height *img.Width; + img.Data = std::shared_ptr(new char[sizeneeded], [](char* ptr) { if (ptr) delete[] ptr; }); + auto startsrc = img.Data.get(); + auto dststart = (char*)MappingDesc.pData + (Box.left * img.PixelStride); + + for (auto t = Box.top; t < img.Height; t++) { + memcpy(startsrc, dststart, img.Width * img.PixelStride); + startsrc += img.Width * img.PixelStride; + dststart += MappingDesc.RowPitch; + } + + CallBack(img); + } + // Unlock the memory + m_DeviceContext->Unmap(m_StagingSurf.Get(), 0); + + } + + + } } @@ -138,14 +205,14 @@ namespace SL { // DUPL_RETURN DXFrameProcessor::CopyMove(ID3D11Texture2D* SrcSurface, DXGI_OUTDUPL_MOVE_RECT* MoveBuffer, UINT MoveCount, DXGI_OUTPUT_DESC* DeskDesc) { - + D3D11_TEXTURE2D_DESC ThisDesc; SrcSurface->GetDesc(&ThisDesc); // Make new intermediate surface to copy into for moving if (!m_MoveSurf) { - + D3D11_TEXTURE2D_DESC MoveDesc; MoveDesc = ThisDesc; MoveDesc.Width = DeskDesc->DesktopCoordinates.right - DeskDesc->DesktopCoordinates.left; @@ -168,7 +235,7 @@ namespace SL { // Copy rect out of shared surface D3D11_BOX Box; - Box.left = SrcRect.left + DeskDesc->DesktopCoordinates.left ; + Box.left = SrcRect.left + DeskDesc->DesktopCoordinates.left; Box.top = SrcRect.top + DeskDesc->DesktopCoordinates.top; Box.front = 0; Box.right = SrcRect.right + DeskDesc->DesktopCoordinates.left; @@ -261,19 +328,19 @@ namespace SL { } // Set positions - Vertices[0].Pos = XMFLOAT3((DestDirty.left + DeskDesc->DesktopCoordinates.left - CenterX) / static_cast(CenterX), - -1 * (DestDirty.bottom + DeskDesc->DesktopCoordinates.top - CenterY) / static_cast(CenterY), + Vertices[0].Pos = XMFLOAT3((DestDirty.left - CenterX) / static_cast(CenterX), + -1 * (DestDirty.bottom - CenterY) / static_cast(CenterY), 0.0f); - Vertices[1].Pos = XMFLOAT3((DestDirty.left + DeskDesc->DesktopCoordinates.left - CenterX) / static_cast(CenterX), - -1 * (DestDirty.top + DeskDesc->DesktopCoordinates.top - CenterY) / static_cast(CenterY), + Vertices[1].Pos = XMFLOAT3((DestDirty.left - CenterX) / static_cast(CenterX), + -1 * (DestDirty.top - CenterY) / static_cast(CenterY), 0.0f); - Vertices[2].Pos = XMFLOAT3((DestDirty.right + DeskDesc->DesktopCoordinates.left - CenterX) / static_cast(CenterX), - -1 * (DestDirty.bottom + DeskDesc->DesktopCoordinates.top - CenterY) / static_cast(CenterY), + Vertices[2].Pos = XMFLOAT3((DestDirty.right - CenterX) / static_cast(CenterX), + -1 * (DestDirty.bottom - CenterY) / static_cast(CenterY), 0.0f); Vertices[3].Pos = Vertices[2].Pos; Vertices[4].Pos = Vertices[1].Pos; - Vertices[5].Pos = XMFLOAT3((DestDirty.right + DeskDesc->DesktopCoordinates.left - CenterX) / static_cast(CenterX), - -1 * (DestDirty.top + DeskDesc->DesktopCoordinates.top - CenterY) / static_cast(CenterY), + Vertices[5].Pos = XMFLOAT3((DestDirty.right - CenterX) / static_cast(CenterX), + -1 * (DestDirty.top - CenterY) / static_cast(CenterY), 0.0f); Vertices[3].TexCoord = Vertices[2].TexCoord; @@ -283,6 +350,8 @@ namespace SL { // // Copies dirty rectangles // + + DUPL_RETURN DXFrameProcessor::CopyDirty(ID3D11Texture2D* SrcSurface, RECT* DirtyBuffer, UINT DirtyCount, DXGI_OUTPUT_DESC* DeskDesc) { HRESULT hr; @@ -304,7 +373,7 @@ namespace SL { if (!m_RTV) { - hr = m_Device->CreateRenderTargetView(m_CopySurf.Get(), nullptr, &m_RTV); + hr = m_Device->CreateRenderTargetView(m_CopySurf.Get(), nullptr, m_RTV.GetAddressOf()); if (FAILED(hr)) { return ProcessFailure(m_Device.Get(), L"Failed to create render target view for dirty rects", L"Error", hr, SystemTransitionsExpectedErrors); @@ -319,7 +388,9 @@ namespace SL { // Create new shader resource view Microsoft::WRL::ComPtr ShaderResource; + hr = m_Device->CreateShaderResourceView(SrcSurface, &ShaderDesc, ShaderResource.GetAddressOf()); + if (FAILED(hr)) { return ProcessFailure(m_Device.Get(), L"Failed to create shader resource view for dirty rects", L"Error", hr, SystemTransitionsExpectedErrors); @@ -350,6 +421,9 @@ namespace SL { // Fill them in VERTEX* DirtyVertex = reinterpret_cast(m_DirtyVertexBufferAlloc.get()); + auto str = std::to_string(DirtyCount); + OutputDebugStringA(str.c_str()); + for (UINT i = 0; i < DirtyCount; ++i, DirtyVertex += 6) { SetDirtyVert(DirtyVertex, &(DirtyBuffer[i]), DeskDesc, &ThisDesc); @@ -387,6 +461,7 @@ namespace SL { m_DeviceContext->Draw(6 * DirtyCount, 0); + return DUPL_RETURN_SUCCESS; } } diff --git a/src/windows/DXThreadManager.cpp b/src/windows/DXThreadManager.cpp index 26939357..d5d49016 100644 --- a/src/windows/DXThreadManager.cpp +++ b/src/windows/DXThreadManager.cpp @@ -66,7 +66,7 @@ namespace SL { } - + // Figure out right dimensions for full size desktop texture and # of outputs to duplicate UINT OutputCount = 0; @@ -146,65 +146,56 @@ namespace SL { RtlZeroMemory(&DesktopDesc, sizeof(DXGI_OUTPUT_DESC)); DuplMgr.GetOutputDesc(&DesktopDesc); - // Main duplication loop - bool WaitToProcessCurrentFrame = false; - FRAME_DATA CurrentData; + + FRAME_DATA CurrentData; + CurrentData.SrcreenIndex = TData->Output; while (!*TData->TerminateThreadsEvent) { auto start = std::chrono::high_resolution_clock::now(); - if (!WaitToProcessCurrentFrame) - { + bool TimeOut; + // Get new frame from desktop duplication - bool TimeOut; - Ret = DuplMgr.GetFrame(&CurrentData, &TimeOut); - if (Ret != DUPL_RETURN_SUCCESS) - { - // An error occurred getting the next frame drop out of loop which - // will check if it was expected or not - break; - } - - // Check for timeout - if (TimeOut) - { - // No new frame at the moment - continue; - } - } - - - // We can now process the current frame - WaitToProcessCurrentFrame = false; - - // Get mouse info - //Ret = DuplMgr.GetMouse(TData->PtrInfo.get(), &(CurrentData.FrameInfo), &DesktopDesc); + Ret = DuplMgr.GetFrame(&CurrentData, &TimeOut); if (Ret != DUPL_RETURN_SUCCESS) { - DuplMgr.DoneWithFrame(); + // An error occurred getting the next frame drop out of loop which + // will check if it was expected or not break; } - // Process new frame - Ret = DispMgr.ProcessFrame(&CurrentData, &DesktopDesc); - if (Ret != DUPL_RETURN_SUCCESS) + // Check for timeout + if (TimeOut) { - DuplMgr.DoneWithFrame(); - break; + // No new frame at the moment + continue; } - // Release frame back to desktop duplication - Ret = DuplMgr.DoneWithFrame(); - if (Ret != DUPL_RETURN_SUCCESS) { - break; + + std::lock_guard lock(*TData->GlobalLock); + // Process new frame + DispMgr.ProcessFrame(&CurrentData, &DesktopDesc); + // Release frame back to desktop duplication + Ret = DuplMgr.DoneWithFrame(); + if (Ret != DUPL_RETURN_SUCCESS) + { + break; + } } + auto mspassed = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start).count(); std::string msg = "took "; - msg += std::to_string(std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start).count()) + "ms\n"; + msg += std::to_string(mspassed) + "ms for output "; + msg += std::to_string(TData->Output) + "\n"; OutputDebugStringA(msg.c_str()); + auto timetowait = 100 - mspassed; + if (timetowait > 0) { - + std::this_thread::sleep_for(std::chrono::milliseconds(timetowait)); + } } + OutputDebugStringA("Exiting Thread\n"); + return 0; @@ -235,7 +226,7 @@ namespace SL { m_ThreadCount = GetMonitorCount(); m_ThreadHandles.resize(m_ThreadCount); m_ThreadData.resize(m_ThreadCount); - + auto globallock = std::make_shared(); // Create appropriate # of threads for duplication DUPL_RETURN Ret = DUPL_RETURN_SUCCESS; for (int i = 0; i < m_ThreadCount; ++i) @@ -247,6 +238,7 @@ namespace SL { m_ThreadData[i]->Output = i; m_ThreadData[i]->CallBack = cb; m_ThreadData[i]->PtrInfo = m_PtrInfo; + m_ThreadData[i]->GlobalLock = globallock; Ret = InitializeDx(&m_ThreadData[i]->DxRes); if (Ret != DUPL_RETURN_SUCCESS) diff --git a/src/windows/ScreenCaptureDX.cpp b/src/windows/ScreenCaptureDX.cpp index 4998ff50..e808a334 100644 --- a/src/windows/ScreenCaptureDX.cpp +++ b/src/windows/ScreenCaptureDX.cpp @@ -21,7 +21,6 @@ namespace SL { void ScreenCaptureDX::StopProcessing() { _KeepRunning = false; - _InterruptableSleeper.wake(); if (_Thread.joinable()) { _Thread.join(); } diff --git a/src/windows/ScreenCaptureGDI.cpp b/src/windows/ScreenCaptureGDI.cpp index 020cd2f5..0ed05147 100644 --- a/src/windows/ScreenCaptureGDI.cpp +++ b/src/windows/ScreenCaptureGDI.cpp @@ -29,11 +29,11 @@ void Reorder(std::vector& screens) { #define RAIIHBITMAP(handle) std::unique_ptr::type, decltype(&::DeleteObject)>(handle, &::DeleteObject) #define RAIIHANDLE(handle) std::unique_ptr::type, decltype(&::CloseHandle)>(handle, &::CloseHandle) -SL::Screen_Capture::Image CaptureDesktopImage(const std::vector& screens) +SL::Screen_Capture::CapturedImage CaptureDesktopImage(const std::vector& screens) { auto start = std::chrono::high_resolution_clock::now(); - SL::Screen_Capture::Image ret; + SL::Screen_Capture::CapturedImage ret; int left(0), top(0); for (const auto& mon : screens) { ret.Width += mon.Width;