Add option to copy or save subtitles with transparent background
This commit is contained in:
parent
1772dd17ae
commit
aadf6b32b9
7 changed files with 122 additions and 4 deletions
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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>());
|
||||
|
|
|
@ -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" }
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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" }
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -27,3 +27,4 @@ struct VideoFrame {
|
|||
};
|
||||
|
||||
wxImage GetImage(VideoFrame const& frame);
|
||||
wxImage GetImageWithAlpha(VideoFrame const& frame);
|
||||
|
|
Loading…
Reference in a new issue