Add option to copy or save subtitles with transparent background

This commit is contained in:
arch1t3cht 2022-08-19 01:45:22 +02:00
parent 1772dd17ae
commit aadf6b32b9
7 changed files with 122 additions and 4 deletions

View file

@ -25,6 +25,12 @@
#include <libaegisub/dispatch.h>
#if BOOST_VERSION >= 106900
#include <boost/gil.hpp>
#else
#include <boost/gil/gil_all.hpp>
#endif
enum {
NEW_SUBS_FILE = -1,
SUBS_FILE_ALREADY_LOADED = -2
@ -81,6 +87,55 @@ std::shared_ptr<VideoFrame> AsyncVideoProvider::ProcFrame(int frame_number, doub
return frame;
}
VideoFrame AsyncVideoProvider::GetBlankFrame(bool white) {
VideoFrame result;
result.width = GetWidth();
result.height = GetHeight();
result.pitch = result.width * 4;
result.flipped = false;
result.data.resize(result.pitch * result.height, white ? 255 : 0);
return result;
}
VideoFrame AsyncVideoProvider::GetSubtitles(double time) {
// We want to combine all transparent subtitle layers onto one layer.
// Instead of alpha blending them all together, which can be messy and cause
// rounding errors, we draw them once on a black frame and once on a white frame,
// and solve for the color and alpha. This has the benefit of being independent
// of the subtitle provider, as long as the provider works by alpha blending.
VideoFrame frame_black = GetBlankFrame(false);
if (!subs) return frame_black;
VideoFrame frame_white = GetBlankFrame(true);
subs_provider->LoadSubtitles(subs.get());
subs_provider->DrawSubtitles(frame_black, time / 1000.);
subs_provider->DrawSubtitles(frame_white, time / 1000.);
using namespace boost::gil;
auto blackview = interleaved_view(frame_black.width, frame_black.height, (bgra8_pixel_t*) frame_black.data.data(), frame_black.width * 4);
auto whiteview = interleaved_view(frame_white.width, frame_white.height, (bgra8_pixel_t*) frame_white.data.data(), frame_white.width * 4);
transform_pixels(blackview, whiteview, blackview, [=](const bgra8_pixel_t black, const bgra8_pixel_t white) -> bgra8_pixel_t {
int a = 255 - (white[0] - black[0]);
bgra8_pixel_t ret;
if (a == 0) {
ret[0] = 0;
ret[1] = 0;
ret[2] = 0;
ret[3] = 0;
} else {
ret[0] = black[0] / (a / 255.);
ret[1] = black[1] / (a / 255.);
ret[2] = black[2] / (a / 255.);
ret[3] = a;
}
return ret;
});
return frame_black;
}
static std::unique_ptr<SubtitlesProvider> get_subs_provider(wxEvtHandler *evt_handler, agi::BackgroundRunner *br) {
try {
return SubtitlesProviderFactory::GetProvider(br);

View file

@ -78,6 +78,9 @@ class AsyncVideoProvider {
std::vector<std::shared_ptr<VideoFrame>> buffers;
// Returns a monochromatic frame with the current dimensions
VideoFrame GetBlankFrame(bool white);
public:
/// @brief Load the passed subtitle file
/// @param subs File to load
@ -108,6 +111,15 @@ public:
/// @brief raw Get raw frame without subtitles
std::shared_ptr<VideoFrame> GetFrame(int frame, double time, bool raw = false);
/// @brief Synchronously get the subtitles with transparent background
/// @brief time Exact start time of the frame in seconds
///
/// This function is not used for drawing the subtitles on the screen and is not
/// guaranteed that drawing the resulting image on the current raw frame exactly
/// results in the current rendered frame with subtitles. This function is for
/// purposes like copying the current subtitles to the clipboard.
VideoFrame GetSubtitles(double time);
/// Ask the video provider to change YCbCr matricies
void SetColorSpace(std::string const& matrix);

View file

@ -287,9 +287,13 @@ struct video_focus_seek final : public validator_video_loaded {
}
};
wxImage get_image(agi::Context *c, bool raw) {
wxImage get_image(agi::Context *c, bool raw, bool subsonly = false) {
auto frame = c->videoController->GetFrameN();
return GetImage(*c->project->VideoProvider()->GetFrame(frame, c->project->Timecodes().TimeAtFrame(frame), raw));
if (subsonly) {
return GetImageWithAlpha(c->project->VideoProvider()->GetSubtitles(c->project->Timecodes().TimeAtFrame(frame)));
} else {
return GetImage(*c->project->VideoProvider()->GetFrame(frame, c->project->Timecodes().TimeAtFrame(frame), raw));
}
}
struct video_frame_copy final : public validator_video_loaded {
@ -314,6 +318,19 @@ struct video_frame_copy_raw final : public validator_video_loaded {
}
};
struct video_frame_copy_subs final : public validator_video_loaded {
CMD_NAME("video/frame/copy/subs")
STR_MENU("Copy image to Clipboard (only subtitles)")
STR_DISP("Copy image to Clipboard (only subtitles)")
STR_HELP("Copy the currently displayed subtitles to the clipboard, with transparent background")
void operator()(agi::Context *c) override {
wxBitmap img(get_image(c, false, true));
img.UseAlpha();
SetClipboard(img);
}
};
struct video_frame_next final : public validator_video_loaded {
CMD_NAME("video/frame/next")
STR_MENU("Next Frame")
@ -456,7 +473,7 @@ struct video_frame_prev_large final : public validator_video_loaded {
}
};
static void save_snapshot(agi::Context *c, bool raw) {
static void save_snapshot(agi::Context *c, bool raw, bool subsonly = false) {
auto option = OPT_GET("Path/Screenshot")->GetString();
agi::fs::path basepath;
@ -491,7 +508,7 @@ static void save_snapshot(agi::Context *c, bool raw) {
path = agi::format("%s_%03d_%d.png", basepath.string(), session_shot_count++, c->videoController->GetFrameN());
} while (agi::fs::FileExists(path));
get_image(c, raw).SaveFile(to_wx(path), wxBITMAP_TYPE_PNG);
get_image(c, raw, subsonly).SaveFile(to_wx(path), wxBITMAP_TYPE_PNG);
}
struct video_frame_save final : public validator_video_loaded {
@ -516,6 +533,17 @@ struct video_frame_save_raw final : public validator_video_loaded {
}
};
struct video_frame_save_subs final : public validator_video_loaded {
CMD_NAME("video/frame/save/subs")
STR_MENU("Save PNG snapshot (only subtitles)")
STR_DISP("Save PNG snapshot (only subtitles)")
STR_HELP("Save the currently displayed subtitles with transparent background to a PNG file in the video's directory")
void operator()(agi::Context *c) override {
save_snapshot(c, false, true);
}
};
struct video_jump final : public validator_video_loaded {
CMD_NAME("video/jump")
CMD_ICON(jumpto_button)
@ -751,6 +779,7 @@ namespace cmd {
reg(agi::make_unique<video_focus_seek>());
reg(agi::make_unique<video_frame_copy>());
reg(agi::make_unique<video_frame_copy_raw>());
reg(agi::make_unique<video_frame_copy_subs>());
reg(agi::make_unique<video_frame_next>());
reg(agi::make_unique<video_frame_next_boundary>());
reg(agi::make_unique<video_frame_next_keyframe>());
@ -761,6 +790,7 @@ namespace cmd {
reg(agi::make_unique<video_frame_prev_large>());
reg(agi::make_unique<video_frame_save>());
reg(agi::make_unique<video_frame_save_raw>());
reg(agi::make_unique<video_frame_save_subs>());
reg(agi::make_unique<video_jump>());
reg(agi::make_unique<video_jump_end>());
reg(agi::make_unique<video_jump_start>());

View file

@ -217,6 +217,9 @@
{ "command" : "video/frame/save/raw" },
{ "command" : "video/frame/copy/raw" },
{},
{ "command" : "video/frame/save/subs" },
{ "command" : "video/frame/copy/subs" },
{},
{ "command" : "video/copy_coordinates" }
]
}

View file

@ -227,6 +227,9 @@
{ "command" : "video/frame/save/raw" },
{ "command" : "video/frame/copy/raw" },
{},
{ "command" : "video/frame/save/subs" },
{ "command" : "video/frame/copy/subs" },
{},
{ "command" : "video/copy_coordinates" }
]
}

View file

@ -48,3 +48,17 @@ wxImage GetImage(VideoFrame const& frame) {
copy_and_convert_pixels(src, dst, color_converter());
return img;
}
wxImage GetImageWithAlpha(VideoFrame const &frame) {
wxImage img = GetImage(frame);
img.InitAlpha();
uint8_t *dst = img.GetAlpha();
const uint8_t *src = frame.data.data() + 3;
for (int y = 0; y < frame.height; y++) {
for (int x = 0; x < frame.width; x++) {
*(dst++) = *src;
src += 4;
}
}
return img;
}

View file

@ -27,3 +27,4 @@ struct VideoFrame {
};
wxImage GetImage(VideoFrame const& frame);
wxImage GetImageWithAlpha(VideoFrame const& frame);