Merge pull request #6436 from jpark37/jxr-wic

libobs,image-source,UI: Add JXR support on Windows
This commit is contained in:
Jim 2022-05-12 16:09:53 -07:00 committed by GitHub
commit f2ea473373
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 348 additions and 38 deletions

View File

@ -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};

View File

@ -1,12 +1,20 @@
#include "graphics.h"
#include "half.h"
#include "srgb.h"
#include <obs-ffmpeg-compat.h>
#include <util/dstr.h>
#include <util/platform.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#include "../obs-ffmpeg-compat.h"
#include "srgb.h"
#ifdef _WIN32
#include <wincodec.h>
#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;
}

View File

@ -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)

View File

@ -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);
}

View File

@ -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

View File

@ -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;

View File

@ -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()