一,GDI+(obs 编译说明:Install Instructions · obsproject/obs-studio Wiki · GitHub)
此插件主要用GDI+ 进行文字的绘制
GDI+是微软提供的新的图形设备接口,通过托管代码的类来展现。GDI+主要提供了三类服务:
- 二维矢量图形
- 图像处理
文字显示
GDI+比GDI的优越性主要展现在2个方面:扩展了新功能
- 变成更加简易灵活
二,插件逻辑
构造界面,想添加自定义控件,可在以下函数中则更加,其中T_ALIGN 类似的宏定义 为翻译文件中的对应的字符串
static obs_properties_t *get_properties(void *data)
{
TextSource *s = reinterpret_cast<TextSource *>(data);
string path;
obs_properties_t *props = obs_properties_create();
obs_property_t *p;
obs_properties_add_font(props, S_FONT, T_FONT);
p = obs_properties_add_bool(props, S_USE_FILE, T_USE_FILE);
obs_property_set_modified_callback(p, use_file_changed);
string filter;
filter += T_FILTER_TEXT_FILES;
filter += " (*.txt);;";
filter += T_FILTER_ALL_FILES;
filter += " (*.*)";
if (s && !s->file.empty()) {
const char *slash;
path = s->file;
replace(path.begin(), path.end(), '\\', '/');
slash = strrchr(path.c_str(), '/');
if (slash)
path.resize(slash - path.c_str() + 1);
}
obs_properties_add_text(props, S_TEXT, T_TEXT, OBS_TEXT_MULTILINE);
obs_properties_add_path(props, S_FILE, T_FILE, OBS_PATH_FILE,
filter.c_str(), path.c_str());
p = obs_properties_add_list(props, S_TRANSFORM, T_TRANSFORM,
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
obs_property_list_add_int(p, T_TRANSFORM_NONE, S_TRANSFORM_NONE);
obs_property_list_add_int(p, T_TRANSFORM_UPPERCASE,
S_TRANSFORM_UPPERCASE);
obs_property_list_add_int(p, T_TRANSFORM_LOWERCASE,
S_TRANSFORM_LOWERCASE);
obs_properties_add_bool(props, S_VERTICAL, T_VERTICAL);
obs_properties_add_color(props, S_COLOR, T_COLOR);
p = obs_properties_add_int_slider(props, S_OPACITY, T_OPACITY, 0, 100,
1);
obs_property_int_set_suffix(p, "%");
p = obs_properties_add_bool(props, S_GRADIENT, T_GRADIENT);
obs_property_set_modified_callback(p, gradient_changed);
obs_properties_add_color(props, S_GRADIENT_COLOR, T_GRADIENT_COLOR);
p = obs_properties_add_int_slider(props, S_GRADIENT_OPACITY,
T_GRADIENT_OPACITY, 0, 100, 1);
obs_property_int_set_suffix(p, "%");
obs_properties_add_float_slider(props, S_GRADIENT_DIR, T_GRADIENT_DIR,
0, 360, 0.1);
obs_properties_add_color(props, S_BKCOLOR, T_BKCOLOR);
p = obs_properties_add_int_slider(props, S_BKOPACITY, T_BKOPACITY, 0,
100, 1);
obs_property_int_set_suffix(p, "%");
p = obs_properties_add_list(props, S_ALIGN, T_ALIGN,
OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_STRING);
obs_property_list_add_string(p, T_ALIGN_LEFT, S_ALIGN_LEFT);
obs_property_list_add_string(p, T_ALIGN_CENTER, S_ALIGN_CENTER);
obs_property_list_add_string(p, T_ALIGN_RIGHT, S_ALIGN_RIGHT);
p = obs_properties_add_list(props, S_VALIGN, T_VALIGN,
OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_STRING);
obs_property_list_add_string(p, T_VALIGN_TOP, S_VALIGN_TOP);
obs_property_list_add_string(p, T_VALIGN_CENTER, S_VALIGN_CENTER);
obs_property_list_add_string(p, T_VALIGN_BOTTOM, S_VALIGN_BOTTOM);
p = obs_properties_add_bool(props, S_OUTLINE, T_OUTLINE);
obs_property_set_modified_callback(p, outline_changed);
obs_properties_add_int(props, S_OUTLINE_SIZE, T_OUTLINE_SIZE, 1, 20, 1);
obs_properties_add_color(props, S_OUTLINE_COLOR, T_OUTLINE_COLOR);
p = obs_properties_add_int_slider(props, S_OUTLINE_OPACITY,
T_OUTLINE_OPACITY, 0, 100, 1);
obs_property_int_set_suffix(p, "%");
p = obs_properties_add_bool(props, S_CHATLOG_MODE, T_CHATLOG_MODE);
obs_property_set_modified_callback(p, chatlog_mode_changed);
obs_properties_add_int(props, S_CHATLOG_LINES, T_CHATLOG_LINES, 1, 1000,
1);
p = obs_properties_add_bool(props, S_EXTENTS, T_EXTENTS);
obs_property_set_modified_callback(p, extents_modified);
obs_properties_add_int(props, S_EXTENTS_CX, T_EXTENTS_CX, 32, 8000, 1);
obs_properties_add_int(props, S_EXTENTS_CY, T_EXTENTS_CY, 32, 8000, 1);
obs_properties_add_bool(props, S_EXTENTS_WRAP, T_EXTENTS_WRAP);
return props;
}
模块初始化,并加载默认配置
bool obs_module_load(void)// 加载文本插件
{
obs_source_info si = {};
si.id = "text_gdiplus"; //插件唯一ID
si.type = OBS_SOURCE_TYPE_INPUT;
si.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW;
si.get_properties = get_properties;
si.get_name = [](void *) { return obs_module_text("Subtitle"); };
si.create = [](obs_data_t *settings, obs_source_t *source) {
return (void *)new TextSource(source, settings);
};
si.destroy = [](void *data) {
delete reinterpret_cast<TextSource *>(data);
};
si.get_width = [](void *data) {
return reinterpret_cast<TextSource *>(data)->cx;
};
si.get_height = [](void *data) {
return reinterpret_cast<TextSource *>(data)->cy;
};
si.get_defaults = [](obs_data_t *settings) {
obs_data_t *font_obj = obs_data_create();
obs_data_set_default_string(font_obj, "face", "Arial");
obs_data_set_default_int(font_obj, "size", 36);
//qxl
obs_data_set_default_string(settings, S_SOURCELANGUAGE, S_SOURCELANGUAGE_ENGLISH);
obs_data_set_default_string(settings, S_TARGETLANGUAGE, S_TARGETLANGUAGE_CHINESE);
//加载默认配置
obs_data_set_default_obj(settings, S_FONT, font_obj);
obs_data_set_default_string(settings, S_ALIGN, S_ALIGN_LEFT);
obs_data_set_default_string(settings, S_VALIGN, S_VALIGN_TOP);
obs_data_set_default_int(settings, S_COLOR, 0xFFFFFF);
obs_data_set_default_int(settings, S_OPACITY, 100);
obs_data_set_default_int(settings, S_GRADIENT_COLOR, 0xFFFFFF);
obs_data_set_default_int(settings, S_GRADIENT_OPACITY, 100);
obs_data_set_default_double(settings, S_GRADIENT_DIR, 90.0);
obs_data_set_default_int(settings, S_BKCOLOR, 0x000000);
obs_data_set_default_int(settings, S_BKOPACITY, 0);
obs_data_set_default_int(settings, S_OUTLINE_SIZE, 2);
obs_data_set_default_int(settings, S_OUTLINE_COLOR, 0xFFFFFF);
obs_data_set_default_int(settings, S_OUTLINE_OPACITY, 100);
obs_data_set_default_int(settings, S_CHATLOG_LINES, 6);
obs_data_set_default_bool(settings, S_EXTENTS_WRAP, true);
obs_data_set_default_int(settings, S_EXTENTS_CX, 100);
obs_data_set_default_int(settings, S_EXTENTS_CY, 100);
obs_data_set_default_int(settings, S_TRANSFORM,
S_TRANSFORM_NONE);
obs_data_release(font_obj);
};
si.update = [](void *data, obs_data_t *settings) {
reinterpret_cast<TextSource *>(data)->Update(settings);
};
si.video_tick = [](void *data, float seconds) {
reinterpret_cast<TextSource *>(data)->Tick(seconds);
};
si.video_render = [](void *data, gs_effect_t *) {
reinterpret_cast<TextSource *>(data)->Render();
};
obs_register_source(&si);
const GdiplusStartupInput gdip_input;
GdiplusStartup(&gdip_token, &gdip_input, nullptr);
return true;
}
属性改变时,调用 回调函数 进行动作的触发
//属性改变
obs_property_set_modified_callback(p, outline_changed);
//回调
static bool outline_changed(obs_properties_t *props, obs_property_t *p,
obs_data_t *s)
{
bool outline = obs_data_get_bool(s, S_OUTLINE);
set_vis(outline, S_OUTLINE_SIZE, true);
set_vis(outline, S_OUTLINE_COLOR, true);
set_vis(outline, S_OUTLINE_OPACITY, true);
return true;
}
renderText 进行绘制
void TextSource::RenderText()
{
StringFormat format(StringFormat::GenericTypographic());
Status stat;
RectF box;
SIZE size;
GetStringFormat(format);
CalculateTextSizes(format, box, size);
unique_ptr<uint8_t[]> bits(new uint8_t[size.cx * size.cy * 4]);
Bitmap bitmap(size.cx, size.cy, 4 * size.cx, PixelFormat32bppARGB,
bits.get());
Graphics graphics_bitmap(&bitmap);
LinearGradientBrush brush(RectF(0, 0, (float)size.cx, (float)size.cy),
Color(calc_color(color, opacity)),
Color(calc_color(color2, opacity2)),
gradient_dir, 1);
DWORD full_bk_color = bk_color & 0xFFFFFF;
if (!text.empty() || use_extents)
full_bk_color |= get_alpha_val(bk_opacity);
if ((size.cx > box.Width || size.cy > box.Height) && !use_extents) {
stat = graphics_bitmap.Clear(Color(0));
warn_stat("graphics_bitmap.Clear");
SolidBrush bk_brush = Color(full_bk_color);
stat = graphics_bitmap.FillRectangle(&bk_brush, box);
warn_stat("graphics_bitmap.FillRectangle");
} else {
stat = graphics_bitmap.Clear(Color(full_bk_color));
warn_stat("graphics_bitmap.Clear");
}
graphics_bitmap.SetTextRenderingHint(TextRenderingHintAntiAlias);
graphics_bitmap.SetCompositingMode(CompositingModeSourceOver);
graphics_bitmap.SetSmoothingMode(SmoothingModeAntiAlias);
if (!text.empty()) {
if (use_outline) {
box.Offset(outline_size / 2, outline_size / 2);
FontFamily family;
GraphicsPath path;
font->GetFamily(&family);
stat = path.AddString(text.c_str(), (int)text.size(),
&family, font->GetStyle(),
font->GetSize(), box, &format);
warn_stat("path.AddString");
RenderOutlineText(graphics_bitmap, path, brush);
} else {
stat = graphics_bitmap.DrawString(text.c_str(),
(int)text.size(),
font.get(), box,
&format, &brush);
warn_stat("graphics_bitmap.DrawString");
}
}
if (!tex || (LONG)cx != size.cx || (LONG)cy != size.cy) {
obs_enter_graphics();
if (tex)
gs_texture_destroy(tex);
const uint8_t *data = (uint8_t *)bits.get();
tex = gs_texture_create(size.cx, size.cy, GS_BGRA, 1, &data,
GS_DYNAMIC);
obs_leave_graphics();
cx = (uint32_t)size.cx;
cy = (uint32_t)size.cy;
} else if (tex) {
obs_enter_graphics(); //libobs 中的函数
gs_texture_set_image(tex, bits.get(), size.cx * 4, false);
obs_leave_graphics();
}
}