perspective tool: Support drawings

This will not work in more complex cases like lines containing both text
and drawings, but it's correct in simpler ones.
This commit is contained in:
arch1t3cht 2023-04-29 01:11:38 +02:00
parent 9c2d6169c6
commit 20cc0b8077
9 changed files with 104 additions and 48 deletions

View File

@ -41,17 +41,22 @@
#include <limits>
Spline::Spline(const VisualToolBase &tl)
Spline::Spline(const VisualToolBase *tl)
: coord_translator(tl)
{
}
Vector2D Spline::ToScript(Vector2D vec) const {
return coord_translator.ToScriptCoords(vec) * scale;
if (coord_translator)
vec = coord_translator->ToScriptCoords(vec);
return vec * scale;
}
Vector2D Spline::FromScript(Vector2D vec) const {
return coord_translator.FromScriptCoords(vec / scale);
vec = vec / scale;
if (coord_translator)
vec = coord_translator->FromScriptCoords(vec);
return vec;
}
void Spline::SetScale(int new_scale) {

View File

@ -36,7 +36,7 @@ class VisualToolBase;
class Spline final : private std::vector<SplineCurve> {
/// Visual tool to do the conversion between script and video pixels
const VisualToolBase &coord_translator;
const VisualToolBase *coord_translator = nullptr;
/// Spline scale
int scale = 0;
int raw_scale = 0;
@ -47,7 +47,8 @@ class Spline final : private std::vector<SplineCurve> {
/// Script coordinates -> Video coordinates
Vector2D FromScript(Vector2D vec) const;
public:
Spline(const VisualToolBase &scale);
Spline() {};
Spline(const VisualToolBase *scale);
/// Encode to an ASS vector drawing
std::string EncodeToAss() const;

View File

@ -115,6 +115,15 @@ Vector2D& SplineCurve::EndPoint() {
}
}
std::vector<Vector2D> SplineCurve::AnchorPoints() {
switch (type) {
case POINT: return std::vector<Vector2D>({p1});
case LINE: return std::vector<Vector2D>({p1, p2});
case BICUBIC: return std::vector<Vector2D>({p1, p2, p3, p4});
default: return std::vector<Vector2D>();
}
}
Vector2D SplineCurve::GetClosestPoint(Vector2D ref) const {
return GetPoint(GetClosestParam(ref));
}

View File

@ -73,6 +73,7 @@ public:
Vector2D GetPoint(float t) const;
Vector2D& EndPoint();
std::vector<Vector2D> AnchorPoints();
/// Get point on the curve closest to reference
Vector2D GetClosestPoint(Vector2D ref) const;
/// Get t value for the closest point to reference

View File

@ -40,6 +40,8 @@
#include <libaegisub/of_type_adaptor.h>
#include <libaegisub/split.h>
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <algorithm>
@ -536,11 +538,9 @@ int VisualToolBase::GetLineAlignment(AssDialogue *diag) {
return an;
}
void VisualToolBase::GetLineBaseExtents(AssDialogue *diag, double &width, double &height, double &descent, double &extlead) {
width = 0.;
height = 0.;
descent = 0.;
extlead = 0.;
std::pair<Vector2D, Vector2D> VisualToolBase::GetLineBaseExtents(AssDialogue *diag) {
double width = 0.;
double height = 0.;
AssStyle style;
if (AssStyle *basestyle = c->ass->GetStyle(diag->Style)) {
@ -550,25 +550,57 @@ void VisualToolBase::GetLineBaseExtents(AssDialogue *diag, double &width, double
}
auto blocks = diag->ParseTags();
if (param_vec tag = find_tag(blocks, "\\fs"))
style.fontsize = tag->front().Get(style.fontsize);
if (param_vec tag = find_tag(blocks, "\\fn"))
style.font = tag->front().Get(style.font);
param_vec ptag = find_tag(blocks, "\\p");
std::string text = diag->GetStrippedText();
std::vector<std::string> textlines;
boost::replace_all(text, "\\N", "\n");
agi::Split(textlines, text, '\n');
for (std::string line : textlines) {
double linewidth = 0;
double lineheight = 0;
if (!Automation4::CalculateTextExtents(&style, line, linewidth, lineheight, descent, extlead)) {
// meh... let's make some ballpark estimates
linewidth = style.fontsize * line.length();
lineheight = style.fontsize;
if (ptag && ptag->front().Get(0)) { // A drawing
Spline spline;
spline.SetScale(ptag->front().Get(1));
std::string drawing_text = join(blocks | agi::of_type<AssDialogueBlockDrawing>() | boost::adaptors::transformed([&](AssDialogueBlock *d) { return d->GetText(); }), "");
spline.DecodeFromAss(drawing_text);
if (!spline.size())
return std::make_pair(Vector2D(0, 0), Vector2D(0, 0));
float left = FLT_MAX;
float top = FLT_MAX;
float right = -FLT_MAX;
float bot = -FLT_MAX;
for (SplineCurve curve : spline) {
for (Vector2D pt : curve.AnchorPoints()) {
left = std::min(left, pt.X());
top = std::min(top, pt.Y());
right = std::max(right, pt.X());
bot = std::max(bot, pt.Y());
}
}
width = std::max(width, linewidth);
height += lineheight;
return std::make_pair(Vector2D(left, top), Vector2D(right, bot));
} else {
if (param_vec tag = find_tag(blocks, "\\fs"))
style.fontsize = tag->front().Get(style.fontsize);
if (param_vec tag = find_tag(blocks, "\\fn"))
style.font = tag->front().Get(style.font);
std::string text = diag->GetStrippedText();
std::vector<std::string> textlines;
boost::replace_all(text, "\\N", "\n");
agi::Split(textlines, text, '\n');
for (std::string line : textlines) {
double linewidth = 0;
double lineheight = 0;
double descent;
double extlead;
if (!Automation4::CalculateTextExtents(&style, line, linewidth, lineheight, descent, extlead)) {
// meh... let's make some ballpark estimates
linewidth = style.fontsize * line.length();
lineheight = style.fontsize;
}
width = std::max(width, linewidth);
height += lineheight;
}
return std::make_pair(Vector2D(0, 0), Vector2D(width, height));
}
}

View File

@ -132,11 +132,18 @@ protected:
float GetLineFontSize(AssDialogue *diag);
int GetLineAlignment(AssDialogue *diag);
/// @brief Compute text extents of the given line without any formatting
/// @param diag The dialogue line
/// @return The top left and bottom right corners of the line's bounding box respectively.
///
/// Formatting tags are stripped and \fs tags are respected, but \fscx and \fscy are kept as 100 even if
/// they are different in the style.
/// For text the top left corner of the bounding box will always be at the origin, but this needn't be
/// the case for drawings. The width and height of the bounding box are the shifts used for text alignment.
///
/// This function works for most common line formats, but can be inaccurate for more complex cases such as lines
/// containing both text and drawings.
/// Returns a rough estimate when getting the precise extents fails
void GetLineBaseExtents(AssDialogue *diag, double &width, double &height, double &descent, double &extlead);
std::pair<Vector2D, Vector2D> GetLineBaseExtents(AssDialogue *diag);
void GetLineClip(AssDialogue *diag, Vector2D &p1, Vector2D &p2, bool &inverse);
std::string GetLineVectorClip(AssDialogue *diag, int &scale, bool &inverse);

View File

@ -334,7 +334,7 @@ void VisualToolPerspective::Draw() {
gl.SetRotation(angle_x, angle_y, angle_z);
gl.SetScale(fsc);
gl.SetShear(fax, fay);
Vector2D glScale = textheight * Vector2D(1, 1) / spacing / 4;
Vector2D glScale = (bbox.second.Y() - bbox.first.Y()) * Vector2D(1, 1) / spacing / 4;
gl.SetScale(100 * glScale);
// Draw grid
@ -680,17 +680,18 @@ bool VisualToolPerspective::InnerToText() {
float quadwidth = ab.Len();
float quadheight = abs(ad.Y());
float scalex = quadwidth / textwidth;
float scaley = quadheight / textheight;
float scalex = quadwidth / std::max(bbox.second.X() - bbox.first.X(), 1.0f);
float scaley = quadheight / std::max(bbox.second.Y() - bbox.first.Y(), 1.0f);
Vector2D scale = Vector2D(scalex, scaley);
float shiftv = align <= 3 ? 1 : (align <= 6 ? 0.5 : 0);
float shifth = align % 3 == 0 ? 1 : (align % 3 == 2 ? 0.5 : 0);
pos = org + r[0].XY() + Vector2D(quadwidth * shifth, quadheight * shiftv);
pos = org + r[0].XY() - bbox.first * scale + Vector2D(quadwidth * shifth, quadheight * shiftv);
angle_x = rotx * rad2deg;
angle_y = -roty * rad2deg;
angle_z = -rotz * rad2deg;
Vector2D oldfsc = fsc;
fsc = 100 * Vector2D(scalex, scaley);
fsc = 100 * scale;
fax = rawfax * scaley / scalex;
fay = 0;
@ -781,40 +782,39 @@ void VisualToolPerspective::TextToPersp() {
align = GetLineAlignment(active_line);
double descend, extlead;
GetLineBaseExtents(active_line, textwidth, textheight, descend, extlead);
textwidth = std::max(textwidth, 1.);
textheight = std::max(textheight, 1.);
double textleft = 0., texttop = 0.;
bbox = GetLineBaseExtents(active_line);
float textwidth = std::max(bbox.second.X() - bbox.first.X(), 1.f);
float textheight = std::max(bbox.second.Y() - bbox.first.Y(), 1.f);
double shiftx = 0., shifty = 0.;
switch ((align - 1) % 3) {
case 1:
textleft = -textwidth / 2;
shiftx = -textwidth / 2;
break;
case 2:
textleft = -textwidth;
shiftx = -textwidth;
break;
default:
break;
}
switch ((align - 1) / 3) {
case 0:
texttop = -textheight;
shifty = -textheight;
break;
case 1:
texttop = -textheight / 2;
shifty = -textheight / 2;
break;
default:
break;
}
std::vector<Vector2D> textrect = MakeRect(Vector2D(0, 0), Vector2D(textwidth, textheight));
std::vector<Vector2D> textrect = MakeRect(bbox.first, bbox.second);
for (int i = 0; i < 4; i++) {
Vector2D p = textrect[i];
// Apply \fax and \fay
p = Vector2D(p.X() + p.Y() * fax, p.X() * fay + p.Y());
// Translate to alignment point
p = p + Vector2D(textleft, texttop);
p = p + Vector2D(shiftx, shifty);
// Apply scaling
p = Vector2D(p.X() * fsc.X() / 100., p.Y() * fsc.Y() / 100.);
// Translate relative to origin

View File

@ -52,7 +52,7 @@ public:
class VisualToolPerspective final : public VisualTool<VisualToolPerspectiveDraggableFeature> {
wxToolBar *toolBar = nullptr; /// The subtoolbar
int settings = 0;
int settings = 0;
agi::OptionValue* optOuter;
agi::OptionValue* optOuterLocked;
@ -69,8 +69,9 @@ class VisualToolPerspective final : public VisualTool<VisualToolPerspectiveDragg
int align = 0;
double textwidth = 0.f;
double textheight = 0.f;
// Corners of the bounding box of the event without any formatting.
// The top left corner is the zero vector for text but might not be for drawings.
std::pair<Vector2D, Vector2D> bbox;
Vector2D fsc;

View File

@ -34,7 +34,7 @@ int BUTTON_ID_BASE = 1300;
VisualToolVectorClip::VisualToolVectorClip(VideoDisplay *parent, agi::Context *context)
: VisualTool<VisualToolVectorClipDraggableFeature>(parent, context)
, spline(*this)
, spline(this)
{
}