Jelajahi Sumber

libobs: Add functions to crop individual scene items

Renders the scene item to a texture if crop is enabled; otherwise
renders directly.
jp9000 9 tahun lalu
induk
melakukan
eb1ee87f38
3 mengubah file dengan 177 tambahan dan 8 penghapusan
  1. 162 8
      libobs/obs-scene.c
  2. 3 0
      libobs/obs-scene.h
  3. 12 0
      libobs/obs.h

+ 162 - 8
libobs/obs-scene.c

@@ -281,12 +281,26 @@ static void calculate_bounds_data(struct obs_scene_item *item,
 			(int)-width_diff, (int)-height_diff);
 }
 
+static inline uint32_t calc_cx(const struct obs_scene_item *item,
+		uint32_t width)
+{
+	uint32_t crop_cx = item->crop.left + item->crop.right;
+	return (crop_cx > width) ? 2 : (width - crop_cx);
+}
+
+static inline uint32_t calc_cy(const struct obs_scene_item *item,
+		uint32_t height)
+{
+	uint32_t crop_cy = item->crop.top + item->crop.bottom;
+	return (crop_cy > height) ? 2 : (height - crop_cy);
+}
+
 static void update_item_transform(struct obs_scene_item *item)
 {
 	uint32_t        width         = obs_source_get_width(item->source);
 	uint32_t        height        = obs_source_get_height(item->source);
-	uint32_t        cx            = width;
-	uint32_t        cy            = height;
+	uint32_t        cx            = calc_cx(item, width);
+	uint32_t        cy            = calc_cy(item, height);
 	struct vec2     base_origin;
 	struct vec2     origin;
 	struct vec2     scale         = item->scale;
@@ -296,6 +310,9 @@ static void update_item_transform(struct obs_scene_item *item)
 	if (os_atomic_load_long(&item->defer_update) > 0)
 		return;
 
+	width = cx;
+	height = cy;
+
 	vec2_zero(&base_origin);
 	vec2_zero(&origin);
 
@@ -361,6 +378,63 @@ static inline bool source_size_changed(struct obs_scene_item *item)
 	return item->last_width != width || item->last_height != height;
 }
 
+static inline bool crop_enabled(const struct obs_sceneitem_crop *crop)
+{
+	return crop->left || crop->right || crop->top || crop->bottom;
+}
+
+static inline void render_item(struct obs_scene_item *item)
+{
+	if (item->crop_render) {
+		uint32_t width  = obs_source_get_width(item->source);
+		uint32_t height = obs_source_get_height(item->source);
+		uint32_t cx = calc_cx(item, width);
+		uint32_t cy = calc_cy(item, height);
+
+		if (cx && cy && gs_texrender_begin(item->crop_render, cx, cy)) {
+			float cx_scale = (float)width  / (float)cx;
+			float cy_scale = (float)height / (float)cy;
+			gs_matrix_scale3f(cx_scale, cy_scale, 1.0f);
+			gs_matrix_translate3f(
+					-(float)item->crop.left,
+					-(float)item->crop.top,
+					0.0f);
+
+			obs_source_video_render(item->source);
+			gs_texrender_end(item->crop_render);
+		}
+	}
+
+	gs_matrix_push();
+	gs_matrix_mul(&item->draw_transform);
+	if (item->crop_render) {
+		gs_texture_t *tex = gs_texrender_get_texture(item->crop_render);
+
+		while (gs_effect_loop(obs->video.default_effect, "Draw"))
+			obs_source_draw(tex, 0, 0, 0, 0, 0);
+	} else {
+		obs_source_video_render(item->source);
+	}
+	gs_matrix_pop();
+}
+
+static void scene_video_tick(void *data, float seconds)
+{
+	struct obs_scene *scene = data;
+	struct obs_scene_item *item;
+
+	video_lock(scene);
+	item = scene->first_item;
+	while (item) {
+		if (item->crop_render)
+			gs_texrender_reset(item->crop_render);
+		item = item->next;
+	}
+	video_unlock(scene);
+
+	UNUSED_PARAMETER(seconds);
+}
+
 static void scene_video_render(void *data, gs_effect_t *effect)
 {
 	DARRAY(struct obs_scene_item*) remove_items;
@@ -388,12 +462,8 @@ static void scene_video_render(void *data, gs_effect_t *effect)
 		if (source_size_changed(item))
 			update_item_transform(item);
 
-		if (item->user_visible) {
-			gs_matrix_push();
-			gs_matrix_mul(&item->draw_transform);
-			obs_source_video_render(item->source);
-			gs_matrix_pop();
-		}
+		if (item->user_visible)
+			render_item(item);
 
 		item = item->next;
 	}
@@ -471,6 +541,23 @@ static void scene_load_item(struct obs_scene *scene, obs_data_t *item_data)
 		(uint32_t)obs_data_get_int(item_data, "bounds_align");
 	obs_data_get_vec2(item_data, "bounds", &item->bounds);
 
+	item->crop.left   = (uint32_t)obs_data_get_int(item_data, "crop_left");
+	item->crop.top    = (uint32_t)obs_data_get_int(item_data, "crop_top");
+	item->crop.right  = (uint32_t)obs_data_get_int(item_data, "crop_right");
+	item->crop.bottom = (uint32_t)obs_data_get_int(item_data, "crop_bottom");
+
+	if (item->crop_render && !crop_enabled(&item->crop)) {
+		obs_enter_graphics();
+		gs_texrender_destroy(item->crop_render);
+		item->crop_render = NULL;
+		obs_leave_graphics();
+
+	} else if (!item->crop_render && crop_enabled(&item->crop)) {
+		obs_enter_graphics();
+		item->crop_render = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
+		obs_leave_graphics();
+	}
+
 	obs_source_release(source);
 
 	update_item_transform(item);
@@ -511,6 +598,10 @@ static void scene_save_item(obs_data_array_t *array,
 	obs_data_set_int   (item_data, "bounds_type",  (int)item->bounds_type);
 	obs_data_set_int   (item_data, "bounds_align", (int)item->bounds_align);
 	obs_data_set_vec2 (item_data, "bounds",       &item->bounds);
+	obs_data_set_int  (item_data, "crop_left",    (int)item->crop.left);
+	obs_data_set_int  (item_data, "crop_top",     (int)item->crop.top);
+	obs_data_set_int  (item_data, "crop_right",   (int)item->crop.right);
+	obs_data_set_int  (item_data, "crop_bottom",  (int)item->crop.bottom);
 
 	obs_data_array_push_back(array, item_data);
 	obs_data_release(item_data);
@@ -745,6 +836,7 @@ const struct obs_source_info scene_info =
 	.get_name      = scene_getname,
 	.create        = scene_create,
 	.destroy       = scene_destroy,
+	.video_tick    = scene_video_tick,
 	.video_render  = scene_video_render,
 	.audio_render  = scene_audio_render,
 	.get_width     = scene_getwidth,
@@ -872,6 +964,8 @@ obs_scene_t *obs_scene_duplicate(obs_scene_t *scene, const char *name,
 			new_item->bounds_align = item->bounds_align;
 			new_item->bounds = item->bounds;
 
+			obs_sceneitem_set_crop(new_item, &item->crop);
+
 			obs_source_release(source);
 		}
 	}
@@ -1126,6 +1220,11 @@ obs_sceneitem_t *obs_scene_add(obs_scene_t *scene, obs_source_t *source)
 static void obs_sceneitem_destroy(obs_sceneitem_t *item)
 {
 	if (item) {
+		if (item->crop_render) {
+			obs_enter_graphics();
+			gs_texrender_destroy(item->crop_render);
+			obs_leave_graphics();
+		}
 		obs_hotkey_pair_unregister(item->toggle_visibility);
 		pthread_mutex_destroy(&item->actions_mutex);
 		if (item->source)
@@ -1577,6 +1676,61 @@ void obs_scene_atomic_update(obs_scene_t *scene,
 	obs_scene_release(scene);
 }
 
+static inline bool crop_equal(const struct obs_sceneitem_crop *crop1,
+		const struct obs_sceneitem_crop *crop2)
+{
+	return crop1->left   == crop2->left  &&
+	       crop1->right  == crop2->right &&
+	       crop1->top    == crop2->top   &&
+	       crop1->bottom == crop2->bottom;
+}
+
+void obs_sceneitem_set_crop(obs_sceneitem_t *item,
+		const struct obs_sceneitem_crop *crop)
+{
+	bool now_enabled;
+
+	if (!obs_ptr_valid(item, "obs_sceneitem_set_crop"))
+		return;
+	if (!obs_ptr_valid(crop, "obs_sceneitem_set_crop"))
+		return;
+	if (crop_equal(crop, &item->crop))
+		return;
+
+	now_enabled = crop_enabled(crop);
+
+	obs_enter_graphics();
+
+	if (!now_enabled) {
+		gs_texrender_destroy(item->crop_render);
+		item->crop_render = NULL;
+
+	} else if (!item->crop_render) {
+		item->crop_render = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
+	}
+
+	memcpy(&item->crop, crop, sizeof(*crop));
+
+	if (item->crop.left < 0) item->crop.left = 0;
+	if (item->crop.right < 0) item->crop.right = 0;
+	if (item->crop.top < 0) item->crop.top = 0;
+	if (item->crop.bottom < 0) item->crop.bottom = 0;
+	obs_leave_graphics();
+
+	update_item_transform(item);
+}
+
+void obs_sceneitem_get_crop(const obs_sceneitem_t *item,
+		struct obs_sceneitem_crop *crop)
+{
+	if (!obs_ptr_valid(item, "obs_sceneitem_get_crop"))
+		return;
+	if (!obs_ptr_valid(crop, "obs_sceneitem_get_crop"))
+		return;
+
+	memcpy(crop, &item->crop, sizeof(*crop));
+}
+
 void obs_sceneitem_defer_update_begin(obs_sceneitem_t *item)
 {
 	if (!obs_ptr_valid(item, "obs_sceneitem_defer_update_begin"))

+ 3 - 0
libobs/obs-scene.h

@@ -40,6 +40,9 @@ struct obs_scene_item {
 	bool                  visible;
 	bool                  selected;
 
+	gs_texrender_t        *crop_render;
+	struct obs_sceneitem_crop crop;
+
 	struct vec2           pos;
 	struct vec2           scale;
 	float                 rot;

+ 12 - 0
libobs/obs.h

@@ -1237,6 +1237,18 @@ EXPORT void obs_sceneitem_get_box_transform(const obs_sceneitem_t *item,
 EXPORT bool obs_sceneitem_visible(const obs_sceneitem_t *item);
 EXPORT bool obs_sceneitem_set_visible(obs_sceneitem_t *item, bool visible);
 
+struct obs_sceneitem_crop {
+	int left;
+	int top;
+	int right;
+	int bottom;
+};
+
+EXPORT void obs_sceneitem_set_crop(obs_sceneitem_t *item,
+		const struct obs_sceneitem_crop *crop);
+EXPORT void obs_sceneitem_get_crop(const obs_sceneitem_t *item,
+		struct obs_sceneitem_crop *crop);
+
 EXPORT void obs_sceneitem_defer_update_begin(obs_sceneitem_t *item);
 EXPORT void obs_sceneitem_defer_update_end(obs_sceneitem_t *item);