mirror of https://github.com/odrling/Aegisub
Add option to copy or save subtitles with transparent background
This commit is contained in:
parent
1772dd17ae
commit
aadf6b32b9
|
@ -25,6 +25,12 @@
|
||||||
|
|
||||||
#include <libaegisub/dispatch.h>
|
#include <libaegisub/dispatch.h>
|
||||||
|
|
||||||
|
#if BOOST_VERSION >= 106900
|
||||||
|
#include <boost/gil.hpp>
|
||||||
|
#else
|
||||||
|
#include <boost/gil/gil_all.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
NEW_SUBS_FILE = -1,
|
NEW_SUBS_FILE = -1,
|
||||||
SUBS_FILE_ALREADY_LOADED = -2
|
SUBS_FILE_ALREADY_LOADED = -2
|
||||||
|
@ -81,6 +87,55 @@ std::shared_ptr<VideoFrame> AsyncVideoProvider::ProcFrame(int frame_number, doub
|
||||||
return frame;
|
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) {
|
static std::unique_ptr<SubtitlesProvider> get_subs_provider(wxEvtHandler *evt_handler, agi::BackgroundRunner *br) {
|
||||||
try {
|
try {
|
||||||
return SubtitlesProviderFactory::GetProvider(br);
|
return SubtitlesProviderFactory::GetProvider(br);
|
||||||
|
|
|
@ -78,6 +78,9 @@ class AsyncVideoProvider {
|
||||||
|
|
||||||
std::vector<std::shared_ptr<VideoFrame>> buffers;
|
std::vector<std::shared_ptr<VideoFrame>> buffers;
|
||||||
|
|
||||||
|
// Returns a monochromatic frame with the current dimensions
|
||||||
|
VideoFrame GetBlankFrame(bool white);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// @brief Load the passed subtitle file
|
/// @brief Load the passed subtitle file
|
||||||
/// @param subs File to load
|
/// @param subs File to load
|
||||||
|
@ -108,6 +111,15 @@ public:
|
||||||
/// @brief raw Get raw frame without subtitles
|
/// @brief raw Get raw frame without subtitles
|
||||||
std::shared_ptr<VideoFrame> GetFrame(int frame, double time, bool raw = false);
|
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
|
/// Ask the video provider to change YCbCr matricies
|
||||||
void SetColorSpace(std::string const& matrix);
|
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();
|
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 {
|
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 {
|
struct video_frame_next final : public validator_video_loaded {
|
||||||
CMD_NAME("video/frame/next")
|
CMD_NAME("video/frame/next")
|
||||||
STR_MENU("Next Frame")
|
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();
|
auto option = OPT_GET("Path/Screenshot")->GetString();
|
||||||
agi::fs::path basepath;
|
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());
|
path = agi::format("%s_%03d_%d.png", basepath.string(), session_shot_count++, c->videoController->GetFrameN());
|
||||||
} while (agi::fs::FileExists(path));
|
} 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 {
|
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 {
|
struct video_jump final : public validator_video_loaded {
|
||||||
CMD_NAME("video/jump")
|
CMD_NAME("video/jump")
|
||||||
CMD_ICON(jumpto_button)
|
CMD_ICON(jumpto_button)
|
||||||
|
@ -751,6 +779,7 @@ namespace cmd {
|
||||||
reg(agi::make_unique<video_focus_seek>());
|
reg(agi::make_unique<video_focus_seek>());
|
||||||
reg(agi::make_unique<video_frame_copy>());
|
reg(agi::make_unique<video_frame_copy>());
|
||||||
reg(agi::make_unique<video_frame_copy_raw>());
|
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>());
|
||||||
reg(agi::make_unique<video_frame_next_boundary>());
|
reg(agi::make_unique<video_frame_next_boundary>());
|
||||||
reg(agi::make_unique<video_frame_next_keyframe>());
|
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_prev_large>());
|
||||||
reg(agi::make_unique<video_frame_save>());
|
reg(agi::make_unique<video_frame_save>());
|
||||||
reg(agi::make_unique<video_frame_save_raw>());
|
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>());
|
||||||
reg(agi::make_unique<video_jump_end>());
|
reg(agi::make_unique<video_jump_end>());
|
||||||
reg(agi::make_unique<video_jump_start>());
|
reg(agi::make_unique<video_jump_start>());
|
||||||
|
|
|
@ -217,6 +217,9 @@
|
||||||
{ "command" : "video/frame/save/raw" },
|
{ "command" : "video/frame/save/raw" },
|
||||||
{ "command" : "video/frame/copy/raw" },
|
{ "command" : "video/frame/copy/raw" },
|
||||||
{},
|
{},
|
||||||
|
{ "command" : "video/frame/save/subs" },
|
||||||
|
{ "command" : "video/frame/copy/subs" },
|
||||||
|
{},
|
||||||
{ "command" : "video/copy_coordinates" }
|
{ "command" : "video/copy_coordinates" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -227,6 +227,9 @@
|
||||||
{ "command" : "video/frame/save/raw" },
|
{ "command" : "video/frame/save/raw" },
|
||||||
{ "command" : "video/frame/copy/raw" },
|
{ "command" : "video/frame/copy/raw" },
|
||||||
{},
|
{},
|
||||||
|
{ "command" : "video/frame/save/subs" },
|
||||||
|
{ "command" : "video/frame/copy/subs" },
|
||||||
|
{},
|
||||||
{ "command" : "video/copy_coordinates" }
|
{ "command" : "video/copy_coordinates" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,3 +48,17 @@ wxImage GetImage(VideoFrame const& frame) {
|
||||||
copy_and_convert_pixels(src, dst, color_converter());
|
copy_and_convert_pixels(src, dst, color_converter());
|
||||||
return img;
|
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 GetImage(VideoFrame const& frame);
|
||||||
|
wxImage GetImageWithAlpha(VideoFrame const& frame);
|
||||||
|
|
Loading…
Reference in New Issue