diff --git a/UI/window-basic-main-dropfiles.cpp b/UI/window-basic-main-dropfiles.cpp index f08356e76..2e28f1b88 100644 --- a/UI/window-basic-main-dropfiles.cpp +++ b/UI/window-basic-main-dropfiles.cpp @@ -17,8 +17,11 @@ using namespace std; static const char *textExtensions[] = {"txt", "log", nullptr}; -static const char *imageExtensions[] = {"bmp", "tga", "png", "jpg", - "jpeg", "gif", "webp", nullptr}; +static const char *imageExtensions[] = {"bmp", "gif", "jpeg", "jpg", +#ifdef _WIN32 + "jxr", +#endif + "png", "tga", "webp", nullptr}; static const char *htmlExtensions[] = {"htm", "html", nullptr}; diff --git a/libobs/graphics/graphics-ffmpeg.c b/libobs/graphics/graphics-ffmpeg.c index cd19ff3c5..25118d65b 100644 --- a/libobs/graphics/graphics-ffmpeg.c +++ b/libobs/graphics/graphics-ffmpeg.c @@ -1,12 +1,20 @@ #include "graphics.h" +#include "half.h" +#include "srgb.h" +#include +#include +#include + #include #include #include #include -#include "../obs-ffmpeg-compat.h" -#include "srgb.h" +#ifdef _WIN32 +#include +#pragma comment(lib, "windowscodecs.lib") +#endif struct ffmpeg_image { const char *file; @@ -644,10 +652,230 @@ uint8_t *gs_create_texture_file_data(const char *file, return data; } +#ifdef _WIN32 +static float pq_to_linear(float u) +{ + const float common = powf(u, 1.f / 78.84375f); + return powf(fabsf(max(common - 0.8359375f, 0.f) / + (18.8515625f - 18.6875f * common)), + 1.f / 0.1593017578f); +} + +static void convert_pq_to_cccs(const BYTE *intermediate, + const UINT intermediate_size, BYTE *bytes) +{ + const BYTE *src_cursor = intermediate; + const BYTE *src_cursor_end = src_cursor + intermediate_size; + BYTE *dst_cursor = bytes; + uint32_t rgb10; + struct half rgba16[4]; + rgba16[3].u = 0x3c00; + while (src_cursor < src_cursor_end) { + memcpy(&rgb10, src_cursor, sizeof(rgb10)); + const float blue = (float)(rgb10 & 0x3ff) / 1023.f; + const float green = (float)((rgb10 >> 10) & 0x3ff) / 1023.f; + const float red = (float)((rgb10 >> 20) & 0x3ff) / 1023.f; + const float red2020 = pq_to_linear(red); + const float green2020 = pq_to_linear(green); + const float blue2020 = pq_to_linear(blue); + const float red709 = 1.6604910f * red2020 - + 0.5876411f * green2020 - + 0.0728499f * blue2020; + const float green709 = -0.1245505f * red2020 + + 1.1328999f * green2020 - + 0.0083494f * blue2020; + const float blue709 = -0.0181508f * red2020 - + 0.1005789f * green2020 + + 1.1187297f * blue2020; + rgba16[0] = half_from_float(red709 * 125.f); + rgba16[1] = half_from_float(green709 * 125.f); + rgba16[2] = half_from_float(blue709 * 125.f); + memcpy(dst_cursor, &rgba16, sizeof(rgba16)); + src_cursor += 4; + dst_cursor += 8; + } +} + +static void *wic_image_init_internal(const char *file, + IWICBitmapFrameDecode *pFrame, + enum gs_color_format *format, + uint32_t *cx_out, uint32_t *cy_out, + enum gs_color_space *space) +{ + BYTE *bytes = NULL; + + WICPixelFormatGUID pixelFormat; + HRESULT hr = pFrame->lpVtbl->GetPixelFormat(pFrame, &pixelFormat); + if (SUCCEEDED(hr)) { + const bool scrgb = memcmp(&pixelFormat, + &GUID_WICPixelFormat64bppRGBAHalf, + sizeof(pixelFormat)) == 0; + const bool pq10 = memcmp(&pixelFormat, + &GUID_WICPixelFormat32bppBGR101010, + sizeof(pixelFormat)) == 0; + if (scrgb || pq10) { + UINT width, height; + hr = pFrame->lpVtbl->GetSize(pFrame, &width, &height); + if (SUCCEEDED(hr)) { + const UINT pitch = 8 * width; + const UINT size = pitch * height; + bytes = bmalloc(size); + if (bytes) { + bool success = false; + if (pq10) { + const UINT intermediate_pitch = + 4 * width; + const UINT intermediate_size = + intermediate_pitch * + height; + BYTE *intermediate = bmalloc( + intermediate_size); + if (intermediate) { + hr = pFrame->lpVtbl->CopyPixels( + pFrame, NULL, + intermediate_pitch, + intermediate_size, + intermediate); + success = SUCCEEDED(hr); + if (success) { + convert_pq_to_cccs( + intermediate, + intermediate_size, + bytes); + } else { + blog(LOG_WARNING, + "WIC: Failed to CopyPixels intermediate for file: %s", + file); + } + + bfree(intermediate); + } else { + blog(LOG_WARNING, + "WIC: Failed to allocate intermediate for file: %s", + file); + } + } else { + hr = pFrame->lpVtbl->CopyPixels( + pFrame, NULL, pitch, + size, bytes); + success = SUCCEEDED(hr); + if (!success) { + blog(LOG_WARNING, + "WIC: Failed to CopyPixels for file: %s", + file); + } + } + + if (success) { + *format = GS_RGBA16F; + *cx_out = width; + *cy_out = height; + *space = GS_CS_709_SCRGB; + } else { + bfree(bytes); + bytes = NULL; + } + } else { + blog(LOG_WARNING, + "WIC: Failed to allocate for file: %s", + file); + } + } else { + blog(LOG_WARNING, + "WIC: Failed to GetSize of frame for file: %s", + file); + } + } else { + blog(LOG_WARNING, + "WIC: Only handle GUID_WICPixelFormat32bppBGR101010 and GUID_WICPixelFormat64bppRGBAHalf for now"); + } + } else { + blog(LOG_WARNING, "WIC: Failed to GetPixelFormat for file: %s", + file); + } + + return bytes; +} + +static void *wic_image_init(const struct ffmpeg_image *info, const char *file, + enum gs_color_format *format, uint32_t *cx_out, + uint32_t *cy_out, enum gs_color_space *space) +{ + const size_t len = strlen(file); + if (len <= 4 && astrcmpi(file + len - 4, ".jxr") != 0) { + blog(LOG_WARNING, + "WIC: Only handle JXR for WIC images for now"); + return NULL; + } + + BYTE *bytes = NULL; + + wchar_t *file_w = NULL; + os_utf8_to_wcs_ptr(file, 0, &file_w); + if (file_w) { + IWICImagingFactory *pFactory = NULL; + HRESULT hr = CoCreateInstance(&CLSID_WICImagingFactory, NULL, + CLSCTX_INPROC_SERVER, + &IID_IWICImagingFactory, + &pFactory); + if (SUCCEEDED(hr)) { + IWICBitmapDecoder *pDecoder = NULL; + hr = pFactory->lpVtbl->CreateDecoderFromFilename( + pFactory, file_w, NULL, GENERIC_READ, + WICDecodeMetadataCacheOnDemand, &pDecoder); + if (SUCCEEDED(hr)) { + IWICBitmapFrameDecode *pFrame = NULL; + hr = pDecoder->lpVtbl->GetFrame(pDecoder, 0, + &pFrame); + if (SUCCEEDED(hr)) { + bytes = wic_image_init_internal( + file, pFrame, format, cx_out, + cy_out, space); + + pFrame->lpVtbl->Release(pFrame); + } else { + blog(LOG_WARNING, + "WIC: Failed to create IWICBitmapFrameDecode from file: %s", + file); + } + + pDecoder->lpVtbl->Release(pDecoder); + } else { + blog(LOG_WARNING, + "WIC: Failed to create IWICBitmapDecoder from file: %s", + file); + } + + pFactory->lpVtbl->Release(pFactory); + } else { + blog(LOG_WARNING, + "WIC: Failed to create IWICImagingFactory"); + } + + bfree(file_w); + } else { + blog(LOG_WARNING, "WIC: Failed to widen file name: %s", file); + } + + return bytes; +} +#endif + uint8_t *gs_create_texture_file_data2(const char *file, enum gs_image_alpha_mode alpha_mode, enum gs_color_format *format, uint32_t *cx_out, uint32_t *cy_out) +{ + enum gs_color_space unused; + return gs_create_texture_file_data3(file, alpha_mode, format, cx_out, + cy_out, &unused); +} + +uint8_t *gs_create_texture_file_data3(const char *file, + enum gs_image_alpha_mode alpha_mode, + enum gs_color_format *format, + uint32_t *cx_out, uint32_t *cy_out, + enum gs_color_space *space) { struct ffmpeg_image image; uint8_t *data = NULL; @@ -658,10 +886,18 @@ uint8_t *gs_create_texture_file_data2(const char *file, *format = convert_format(image.format); *cx_out = (uint32_t)image.cx; *cy_out = (uint32_t)image.cy; + *space = GS_CS_SRGB; } ffmpeg_image_free(&image); } +#ifdef _WIN32 + if (data == NULL) { + data = wic_image_init(&image, file, format, cx_out, cy_out, + space); + } +#endif + return data; } diff --git a/libobs/graphics/graphics.h b/libobs/graphics/graphics.h index 696a932f9..f8c045287 100644 --- a/libobs/graphics/graphics.h +++ b/libobs/graphics/graphics.h @@ -593,6 +593,11 @@ EXPORT uint8_t *gs_create_texture_file_data(const char *file, EXPORT uint8_t *gs_create_texture_file_data2( const char *file, enum gs_image_alpha_mode alpha_mode, enum gs_color_format *format, uint32_t *cx, uint32_t *cy); +EXPORT uint8_t * +gs_create_texture_file_data3(const char *file, + enum gs_image_alpha_mode alpha_mode, + enum gs_color_format *format, uint32_t *cx, + uint32_t *cy, enum gs_color_space *space); #define GS_FLIP_U (1 << 0) #define GS_FLIP_V (1 << 1) diff --git a/libobs/graphics/image-file.c b/libobs/graphics/image-file.c index 5babb1f70..d53a1c853 100644 --- a/libobs/graphics/image-file.c +++ b/libobs/graphics/image-file.c @@ -193,6 +193,7 @@ not_animated: static void gs_image_file_init_internal(gs_image_file_t *image, const char *file, uint64_t *mem_usage, + enum gs_color_space *space, enum gs_image_alpha_mode alpha_mode) { size_t len; @@ -213,8 +214,9 @@ static void gs_image_file_init_internal(gs_image_file_t *image, } } - image->texture_data = gs_create_texture_file_data2( - file, alpha_mode, &image->format, &image->cx, &image->cy); + image->texture_data = + gs_create_texture_file_data3(file, alpha_mode, &image->format, + &image->cx, &image->cy, space); if (mem_usage) { *mem_usage += image->cx * image->cy * @@ -230,7 +232,9 @@ static void gs_image_file_init_internal(gs_image_file_t *image, void gs_image_file_init(gs_image_file_t *image, const char *file) { - gs_image_file_init_internal(image, file, NULL, GS_IMAGE_ALPHA_STRAIGHT); + enum gs_color_space unused; + gs_image_file_init_internal(image, file, NULL, &unused, + GS_IMAGE_ALPHA_STRAIGHT); } void gs_image_file_free(gs_image_file_t *image) @@ -255,18 +259,30 @@ void gs_image_file_free(gs_image_file_t *image) void gs_image_file2_init(gs_image_file2_t *if2, const char *file) { - gs_image_file_init_internal(&if2->image, file, &if2->mem_usage, + enum gs_color_space unused; + gs_image_file_init_internal(&if2->image, file, &if2->mem_usage, &unused, GS_IMAGE_ALPHA_STRAIGHT); } void gs_image_file3_init(gs_image_file3_t *if3, const char *file, enum gs_image_alpha_mode alpha_mode) { + enum gs_color_space unused; gs_image_file_init_internal(&if3->image2.image, file, - &if3->image2.mem_usage, alpha_mode); + &if3->image2.mem_usage, &unused, + alpha_mode); if3->alpha_mode = alpha_mode; } +void gs_image_file4_init(gs_image_file4_t *if4, const char *file, + enum gs_image_alpha_mode alpha_mode) +{ + gs_image_file_init_internal(&if4->image3.image2.image, file, + &if4->image3.image2.mem_usage, &if4->space, + alpha_mode); + if4->image3.alpha_mode = alpha_mode; +} + void gs_image_file_init_texture(gs_image_file_t *image) { if (!image->loaded) @@ -404,6 +420,13 @@ bool gs_image_file3_tick(gs_image_file3_t *if3, uint64_t elapsed_time_ns) if3->alpha_mode); } +bool gs_image_file4_tick(gs_image_file4_t *if4, uint64_t elapsed_time_ns) +{ + return gs_image_file_tick_internal(&if4->image3.image2.image, + elapsed_time_ns, + if4->image3.alpha_mode); +} + static void gs_image_file_update_texture_internal(gs_image_file_t *image, enum gs_image_alpha_mode alpha_mode) @@ -434,3 +457,9 @@ void gs_image_file3_update_texture(gs_image_file3_t *if3) gs_image_file_update_texture_internal(&if3->image2.image, if3->alpha_mode); } + +void gs_image_file4_update_texture(gs_image_file4_t *if4) +{ + gs_image_file_update_texture_internal(&if4->image3.image2.image, + if4->image3.alpha_mode); +} diff --git a/libobs/graphics/image-file.h b/libobs/graphics/image-file.h index 1fb62e968..df1e597c2 100644 --- a/libobs/graphics/image-file.h +++ b/libobs/graphics/image-file.h @@ -56,9 +56,15 @@ struct gs_image_file3 { enum gs_image_alpha_mode alpha_mode; }; +struct gs_image_file4 { + struct gs_image_file3 image3; + enum gs_color_space space; +}; + typedef struct gs_image_file gs_image_file_t; typedef struct gs_image_file2 gs_image_file2_t; typedef struct gs_image_file3 gs_image_file3_t; +typedef struct gs_image_file4 gs_image_file4_t; EXPORT void gs_image_file_init(gs_image_file_t *image, const char *file); EXPORT void gs_image_file_free(gs_image_file_t *image); @@ -81,6 +87,13 @@ EXPORT bool gs_image_file3_tick(gs_image_file3_t *if3, uint64_t elapsed_time_ns); EXPORT void gs_image_file3_update_texture(gs_image_file3_t *if3); +EXPORT void gs_image_file4_init(gs_image_file4_t *if4, const char *file, + enum gs_image_alpha_mode alpha_mode); + +EXPORT bool gs_image_file4_tick(gs_image_file4_t *if4, + uint64_t elapsed_time_ns); +EXPORT void gs_image_file4_update_texture(gs_image_file4_t *if4); + static void gs_image_file2_free(gs_image_file2_t *if2) { gs_image_file_free(&if2->image); @@ -102,6 +115,16 @@ static void gs_image_file3_init_texture(gs_image_file3_t *if3) gs_image_file2_init_texture(&if3->image2); } +static void gs_image_file4_free(gs_image_file4_t *if4) +{ + gs_image_file3_free(&if4->image3); +} + +static void gs_image_file4_init_texture(gs_image_file4_t *if4) +{ + gs_image_file3_init_texture(&if4->image3); +} + #ifdef __cplusplus } #endif diff --git a/libobs/obs-source-transition.c b/libobs/obs-source-transition.c index 17e696788..45869bc47 100644 --- a/libobs/obs-source-transition.c +++ b/libobs/obs-source-transition.c @@ -832,12 +832,8 @@ void obs_transition_video_render2( static enum gs_color_space mix_spaces(enum gs_color_space a, enum gs_color_space b) { - assert((a == GS_CS_SRGB) || (a == GS_CS_SRGB_16F) || - (a == GS_CS_709_EXTENDED)); - assert((b == GS_CS_SRGB) || (b == GS_CS_SRGB_16F) || - (b == GS_CS_709_EXTENDED)); - - if ((a == GS_CS_709_EXTENDED) || (b == GS_CS_709_EXTENDED)) + if ((a == GS_CS_709_EXTENDED) || (a == GS_CS_709_SCRGB) || + (b == GS_CS_709_EXTENDED) || (b == GS_CS_709_SCRGB)) return GS_CS_709_EXTENDED; if ((a == GS_CS_SRGB_16F) || (b == GS_CS_SRGB_16F)) return GS_CS_SRGB_16F; diff --git a/plugins/image-source/image-source.c b/plugins/image-source/image-source.c index dbd50e3ae..0ae3c6df5 100644 --- a/plugins/image-source/image-source.c +++ b/plugins/image-source/image-source.c @@ -24,7 +24,7 @@ struct image_source { bool active; bool restart_gif; - gs_image_file3_t if3; + gs_image_file4_t if4; }; static time_t get_modified_timestamp(const char *filename) @@ -46,23 +46,23 @@ static void image_source_load(struct image_source *context) char *file = context->file; obs_enter_graphics(); - gs_image_file3_free(&context->if3); + gs_image_file4_free(&context->if4); obs_leave_graphics(); if (file && *file) { debug("loading texture '%s'", file); context->file_timestamp = get_modified_timestamp(file); - gs_image_file3_init(&context->if3, file, + gs_image_file4_init(&context->if4, file, context->linear_alpha ? GS_IMAGE_ALPHA_PREMULTIPLY_SRGB : GS_IMAGE_ALPHA_PREMULTIPLY); context->update_time_elapsed = 0; obs_enter_graphics(); - gs_image_file3_init_texture(&context->if3); + gs_image_file4_init_texture(&context->if4); obs_leave_graphics(); - if (!context->if3.image2.image.loaded) + if (!context->if4.image3.image2.image.loaded) warn("failed to load texture '%s'", file); } } @@ -70,7 +70,7 @@ static void image_source_load(struct image_source *context) static void image_source_unload(struct image_source *context) { obs_enter_graphics(); - gs_image_file3_free(&context->if3); + gs_image_file4_free(&context->if4); obs_leave_graphics(); } @@ -120,13 +120,13 @@ static void restart_gif(void *data) { struct image_source *context = data; - if (context->if3.image2.image.is_animated_gif) { - context->if3.image2.image.cur_frame = 0; - context->if3.image2.image.cur_loop = 0; - context->if3.image2.image.cur_time = 0; + if (context->if4.image3.image2.image.is_animated_gif) { + context->if4.image3.image2.image.cur_frame = 0; + context->if4.image3.image2.image.cur_loop = 0; + context->if4.image3.image2.image.cur_time = 0; obs_enter_graphics(); - gs_image_file3_update_texture(&context->if3); + gs_image_file4_update_texture(&context->if4); obs_leave_graphics(); context->restart_gif = false; @@ -162,20 +162,22 @@ static void image_source_destroy(void *data) static uint32_t image_source_getwidth(void *data) { struct image_source *context = data; - return context->if3.image2.image.cx; + return context->if4.image3.image2.image.cx; } static uint32_t image_source_getheight(void *data) { struct image_source *context = data; - return context->if3.image2.image.cy; + return context->if4.image3.image2.image.cy; } static void image_source_render(void *data, gs_effect_t *effect) { struct image_source *context = data; - if (!context->if3.image2.image.texture) + struct gs_image_file *const image = &context->if4.image3.image2.image; + gs_texture_t *const texture = image->texture; + if (!texture) return; const bool previous = gs_framebuffer_srgb_enabled(); @@ -185,11 +187,9 @@ static void image_source_render(void *data, gs_effect_t *effect) gs_blend_function(GS_BLEND_ONE, GS_BLEND_INVSRCALPHA); gs_eparam_t *const param = gs_effect_get_param_by_name(effect, "image"); - gs_effect_set_texture_srgb(param, context->if3.image2.image.texture); + gs_effect_set_texture_srgb(param, texture); - gs_draw_sprite(context->if3.image2.image.texture, 0, - context->if3.image2.image.cx, - context->if3.image2.image.cy); + gs_draw_sprite(texture, 0, image->cx, image->cy); gs_blend_state_pop(); @@ -216,7 +216,7 @@ static void image_source_tick(void *data, float seconds) if (obs_source_showing(context->source)) { if (!context->active) { - if (context->if3.image2.image.is_animated_gif) + if (context->if4.image3.image2.image.is_animated_gif) context->last_time = frame_time; context->active = true; } @@ -233,13 +233,14 @@ static void image_source_tick(void *data, float seconds) return; } - if (context->last_time && context->if3.image2.image.is_animated_gif) { + if (context->last_time && + context->if4.image3.image2.image.is_animated_gif) { uint64_t elapsed = frame_time - context->last_time; - bool updated = gs_image_file3_tick(&context->if3, elapsed); + bool updated = gs_image_file4_tick(&context->if4, elapsed); if (updated) { obs_enter_graphics(); - gs_image_file3_update_texture(&context->if3); + gs_image_file4_update_texture(&context->if4); obs_leave_graphics(); } } @@ -248,11 +249,18 @@ static void image_source_tick(void *data, float seconds) } static const char *image_filter = +#ifdef _WIN32 + "All formats (*.bmp *.tga *.png *.jpeg *.jpg *.jxr *.gif *.psd *.webp);;" +#else "All formats (*.bmp *.tga *.png *.jpeg *.jpg *.gif *.psd *.webp);;" +#endif "BMP Files (*.bmp);;" "Targa Files (*.tga);;" "PNG Files (*.png);;" "JPEG Files (*.jpeg *.jpg);;" +#ifdef _WIN32 + "JXR Files (*.jxr);;" +#endif "GIF Files (*.gif);;" "PSD Files (*.psd);;" "WebP Files (*.webp);;" @@ -289,7 +297,7 @@ static obs_properties_t *image_source_properties(void *data) uint64_t image_source_get_memory_usage(void *data) { struct image_source *s = data; - return s->if3.image2.mem_usage; + return s->if4.image3.image2.mem_usage; } static void missing_file_callback(void *src, const char *new_path, void *data) @@ -323,6 +331,15 @@ static obs_missing_files_t *image_source_missingfiles(void *data) return files; } +static enum gs_color_space +image_source_get_color_space(void *data, size_t count, + const enum gs_color_space *preferred_spaces) +{ + struct image_source *const s = data; + gs_image_file4_t *const if4 = &s->if4; + return if4->image3.image2.image.texture ? if4->space : GS_CS_SRGB; +} + static struct obs_source_info image_source_info = { .id = "image_source", .type = OBS_SOURCE_TYPE_INPUT, @@ -342,6 +359,7 @@ static struct obs_source_info image_source_info = { .get_properties = image_source_properties, .icon_type = OBS_ICON_TYPE_IMAGE, .activate = image_source_activate, + .video_get_color_space = image_source_get_color_space, }; OBS_DECLARE_MODULE()