Aegisub/src/visual_tool_vector_clip.cpp
2014-04-06 07:48:20 -07:00

446 lines
13 KiB
C++

// Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Aegisub Project http://www.aegisub.org/
/// @file visual_tool_vector_clip.cpp
/// @brief Vector clipping visual typesetting tool
/// @ingroup visual_ts
#include "config.h"
#include "visual_tool_vector_clip.h"
#include "ass_dialogue.h"
#include "include/aegisub/context.h"
#include "libresrc/libresrc.h"
#include "options.h"
#include "selection_controller.h"
#include "utils.h"
#include <libaegisub/util.h>
#include <algorithm>
#include <boost/range/algorithm/copy.hpp>
#include <boost/range/algorithm/set_algorithm.hpp>
#include <wx/toolbar.h>
/// Button IDs
enum {
BUTTON_DRAG = 1300,
BUTTON_LINE,
BUTTON_BICUBIC,
BUTTON_CONVERT,
BUTTON_INSERT,
BUTTON_REMOVE,
BUTTON_FREEHAND,
BUTTON_FREEHAND_SMOOTH,
BUTTON_LAST // Leave this at the end and don't use it
};
VisualToolVectorClip::VisualToolVectorClip(VideoDisplay *parent, agi::Context *context)
: VisualTool<VisualToolVectorClipDraggableFeature>(parent, context)
, spline(*this)
{
}
void VisualToolVectorClip::SetToolbar(wxToolBar *toolBar) {
this->toolBar = toolBar;
int icon_size = OPT_GET("App/Toolbar Icon Size")->GetInt();
#define ICON(name) icon_size == 16 ? GETIMAGE(name ## _16) : GETIMAGE(name ## _24)
toolBar->AddTool(BUTTON_DRAG, _("Drag"), ICON(visual_vector_clip_drag), _("Drag control points"), wxITEM_CHECK);
toolBar->AddTool(BUTTON_LINE, _("Line"), ICON(visual_vector_clip_line), _("Appends a line"), wxITEM_CHECK);
toolBar->AddTool(BUTTON_BICUBIC, _("Bicubic"), ICON(visual_vector_clip_bicubic), _("Appends a bezier bicubic curve"), wxITEM_CHECK);
toolBar->AddSeparator();
toolBar->AddTool(BUTTON_CONVERT, _("Convert"), ICON(visual_vector_clip_convert), _("Converts a segment between line and bicubic"), wxITEM_CHECK);
toolBar->AddTool(BUTTON_INSERT, _("Insert"), ICON(visual_vector_clip_insert), _("Inserts a control point"), wxITEM_CHECK);
toolBar->AddTool(BUTTON_REMOVE, _("Remove"), ICON(visual_vector_clip_remove), _("Removes a control point"), wxITEM_CHECK);
toolBar->AddSeparator();
toolBar->AddTool(BUTTON_FREEHAND, _("Freehand"), ICON(visual_vector_clip_freehand), _("Draws a freehand shape"), wxITEM_CHECK);
toolBar->AddTool(BUTTON_FREEHAND_SMOOTH, _("Freehand smooth"), ICON(visual_vector_clip_freehand_smooth), _("Draws a smoothed freehand shape"), wxITEM_CHECK);
toolBar->ToggleTool(BUTTON_DRAG, true);
toolBar->Realize();
toolBar->Show(true);
toolBar->Bind(wxEVT_TOOL, [=](wxCommandEvent& e) { SetMode(e.GetId() - BUTTON_DRAG); });
SetMode(features.empty());
#undef ICON
}
void VisualToolVectorClip::SetMode(int new_mode) {
// Manually enforce radio behavior as we want one selection in the bar
// rather than one per group
for (int i = BUTTON_DRAG; i < BUTTON_LAST; i++)
toolBar->ToggleTool(i, i == new_mode + BUTTON_DRAG);
mode = new_mode;
}
void VisualToolVectorClip::Draw() {
if (!active_line) return;
if (spline.empty()) return;
// Parse vector
std::vector<float> points;
std::vector<int> start;
std::vector<int> count;
spline.GetPointList(points, start, count);
assert(!start.empty());
assert(!count.empty());
gl.SetLineColour(colour[3], 1.f, 2);
gl.SetFillColour(wxColour(0, 0, 0), 0.5f);
// draw the shade over clipped out areas and line showing the clip
gl.DrawMultiPolygon(points, start, count, video_pos, video_res, !inverse);
if (mode == 0 && holding && drag_start && mouse_pos) {
// Draw drag-select box
Vector2D top_left = drag_start.Min(mouse_pos);
Vector2D bottom_right = drag_start.Max(mouse_pos);
gl.DrawDashedLine(top_left, Vector2D(top_left.X(), bottom_right.Y()), 6);
gl.DrawDashedLine(Vector2D(top_left.X(), bottom_right.Y()), bottom_right, 6);
gl.DrawDashedLine(bottom_right, Vector2D(bottom_right.X(), top_left.Y()), 6);
gl.DrawDashedLine(Vector2D(bottom_right.X(), top_left.Y()), top_left, 6);
}
Vector2D pt;
float t;
Spline::iterator highlighted_curve;
spline.GetClosestParametricPoint(mouse_pos, highlighted_curve, t, pt);
// Draw highlighted line
if ((mode == 3 || mode == 4) && !active_feature && points.size() > 2) {
std::vector<float> highlighted_points;
spline.GetPointList(highlighted_points, highlighted_curve);
if (!highlighted_points.empty()) {
gl.SetLineColour(colour[2], 1.f, 2);
gl.DrawLineStrip(2, highlighted_points);
}
}
// Draw lines connecting the bicubic features
gl.SetLineColour(colour[3], 0.9f, 1);
for (auto const& curve : spline) {
if (curve.type == SplineCurve::BICUBIC) {
gl.DrawDashedLine(curve.p1, curve.p2, 6);
gl.DrawDashedLine(curve.p3, curve.p4, 6);
}
}
DrawAllFeatures();
// Draw preview of inserted line
if (mode == 1 || mode == 2) {
if (spline.size() && mouse_pos) {
auto c0 = std::find_if(spline.rbegin(), spline.rend(),
[](SplineCurve const& s) { return s.type == SplineCurve::POINT; });
SplineCurve *c1 = &spline.back();
gl.DrawDashedLine(mouse_pos, c0->p1, 6);
gl.DrawDashedLine(mouse_pos, c1->EndPoint(), 6);
}
}
// Draw preview of insert point
if (mode == 4)
gl.DrawCircle(pt, 4);
}
void VisualToolVectorClip::MakeFeature(Spline::iterator cur) {
auto feat = agi::util::make_unique<Feature>();
feat->curve = cur;
if (cur->type == SplineCurve::POINT) {
feat->pos = cur->p1;
feat->type = DRAG_SMALL_CIRCLE;
feat->point = 0;
}
else if (cur->type == SplineCurve::LINE) {
feat->pos = cur->p2;
feat->type = DRAG_SMALL_CIRCLE;
feat->point = 1;
}
else if (cur->type == SplineCurve::BICUBIC) {
// Control points
feat->pos = cur->p2;
feat->point = 1;
feat->type = DRAG_SMALL_SQUARE;
features.push_back(*feat.release());
feat = agi::util::make_unique<Feature>();
feat->curve = cur;
feat->pos = cur->p3;
feat->point = 2;
feat->type = DRAG_SMALL_SQUARE;
features.push_back(*feat.release());
// End point
feat = agi::util::make_unique<Feature>();
feat->curve = cur;
feat->pos = cur->p4;
feat->point = 3;
feat->type = DRAG_SMALL_CIRCLE;
}
features.push_back(*feat.release());
}
void VisualToolVectorClip::MakeFeatures() {
sel_features.clear();
features.clear();
active_feature = nullptr;
for (auto it = spline.begin(); it != spline.end(); ++it)
MakeFeature(it);
}
void VisualToolVectorClip::Save() {
std::string value = "(";
if (spline.GetScale() != 1)
value += std::to_string(spline.GetScale()) + ",";
value += spline.EncodeToAss() + ")";
for (auto line : c->selectionController->GetSelectedSet()) {
// This check is technically not correct as it could be outside of an
// override block... but that's rather unlikely
bool has_iclip = line->Text.get().find("\\iclip") != std::string::npos;
SetOverride(line, has_iclip ? "\\iclip" : "\\clip", value);
}
}
void VisualToolVectorClip::Commit(wxString message) {
Save();
VisualToolBase::Commit(message);
}
void VisualToolVectorClip::UpdateDrag(Feature *feature) {
spline.MovePoint(feature->curve, feature->point, feature->pos);
}
bool VisualToolVectorClip::InitializeDrag(Feature *feature) {
if (mode != 5) return true;
if (feature->curve->type == SplineCurve::BICUBIC && (feature->point == 1 || feature->point == 2)) {
// Deleting bicubic curve handles, so convert to line
feature->curve->type = SplineCurve::LINE;
feature->curve->p2 = feature->curve->p4;
}
else {
auto next = std::next(feature->curve);
if (next != spline.end()) {
if (feature->curve->type == SplineCurve::POINT) {
next->p1 = next->EndPoint();
next->type = SplineCurve::POINT;
}
else {
next->p1 = feature->curve->p1;
}
}
spline.erase(feature->curve);
}
active_feature = nullptr;
MakeFeatures();
Commit(_("delete control point"));
return false;
}
bool VisualToolVectorClip::InitializeHold() {
// Box selection
if (mode == 0) {
box_added.clear();
return true;
}
// Insert line/bicubic
if (mode == 1 || mode == 2) {
SplineCurve curve;
// New spline beginning at the clicked point
if (spline.empty()) {
curve.p1 = mouse_pos;
curve.type = SplineCurve::POINT;
}
else {
// Continue from the spline in progress
// Don't bother setting p2 as UpdateHold will handle that
curve.p1 = spline.back().EndPoint();
curve.type = mode == 1 ? SplineCurve::LINE : SplineCurve::BICUBIC;
}
spline.push_back(curve);
sel_features.clear();
MakeFeature(--spline.end());
UpdateHold();
return true;
}
// Convert and insert
if (mode == 3 || mode == 4) {
// Get closest point
Vector2D pt;
Spline::iterator curve;
float t;
spline.GetClosestParametricPoint(mouse_pos, curve, t, pt);
// Convert line <-> bicubic
if (mode == 3) {
if (curve != spline.end()) {
if (curve->type == SplineCurve::LINE) {
curve->type = SplineCurve::BICUBIC;
curve->p4 = curve->p2;
curve->p2 = curve->p1 * 0.75 + curve->p4 * 0.25;
curve->p3 = curve->p1 * 0.25 + curve->p4 * 0.75;
}
else if (curve->type == SplineCurve::BICUBIC) {
curve->type = SplineCurve::LINE;
curve->p2 = curve->p4;
}
}
}
// Insert
else {
if (spline.empty()) return false;
// Split the curve
if (curve == spline.end()) {
SplineCurve ct(spline.back().EndPoint(), spline.front().p1);
ct.p2 = ct.p1 * (1 - t) + ct.p2 * t;
spline.push_back(ct);
}
else {
std::pair<SplineCurve, SplineCurve> split = curve->Split(t);
*curve = split.first;
spline.insert(++curve, split.second);
}
}
MakeFeatures();
Commit();
return false;
}
// Freehand spline draw
if (mode == 6 || mode == 7) {
sel_features.clear();
features.clear();
active_feature = nullptr;
spline.clear();
spline.emplace_back(mouse_pos);
return true;
}
// Nothing to do for mode 5 (remove)
return false;
}
static bool in_box(Vector2D top_left, Vector2D bottom_right, Vector2D p) {
return p.X() >= top_left.X()
&& p.X() <= bottom_right.X()
&& p.Y() >= top_left.Y()
&& p.Y() <= bottom_right.Y();
}
void VisualToolVectorClip::UpdateHold() {
// Box selection
if (mode == 0) {
std::set<Feature *> boxed_features;
Vector2D p1 = drag_start.Min(mouse_pos);
Vector2D p2 = drag_start.Max(mouse_pos);
for (auto& feature : features) {
if (in_box(p1, p2, feature.pos))
boxed_features.insert(&feature);
}
// Keep track of which features were selected by the box selection so
// that only those are deselected if the user is holding ctrl
boost::set_difference(boxed_features, sel_features,
std::inserter(box_added, end(box_added)));
boost::copy(boxed_features, std::inserter(sel_features, end(sel_features)));
std::vector<Feature *> to_deselect;
boost::set_difference(box_added, boxed_features, std::back_inserter(to_deselect));
for (auto feature : to_deselect)
sel_features.erase(feature);
return;
}
if (mode == 1) {
spline.back().EndPoint() = mouse_pos;
features.back().pos = mouse_pos;
}
// Insert bicubic
else if (mode == 2) {
SplineCurve &curve = spline.back();
curve.EndPoint() = mouse_pos;
// Control points
if (spline.size() > 1) {
SplineCurve &c0 = spline.back();
float len = (curve.p4 - curve.p1).Len();
curve.p2 = (c0.type == SplineCurve::LINE ? c0.p2 - c0.p1 : c0.p4 - c0.p3).Unit() * (0.25f * len) + curve.p1;
}
else
curve.p2 = curve.p1 * 0.75 + curve.p4 * 0.25;
curve.p3 = curve.p1 * 0.25 + curve.p4 * 0.75;
MakeFeatures();
}
// Freehand
else if (mode == 6 || mode == 7) {
// See if distance is enough
Vector2D const& last = spline.back().EndPoint();
float len = (last - mouse_pos).SquareLen();
if ((mode == 6 && len >= 900) || (mode == 7 && len >= 3600)) {
spline.emplace_back(last, mouse_pos);
MakeFeature(--spline.end());
}
}
if (mode == 3 || mode == 4) return;
// Smooth spline
if (!holding && mode == 7)
spline.Smooth();
// End freedraw
if (!holding && (mode == 6 || mode == 7)) {
SetMode(0);
MakeFeatures();
}
}
void VisualToolVectorClip::DoRefresh() {
if (!active_line) return;
int scale;
std::string vect = GetLineVectorClip(active_line, scale, inverse);
spline.SetScale(scale);
spline.DecodeFromAss(vect);
MakeFeatures();
SelectAll();
}
void VisualToolVectorClip::SelectAll() {
sel_features.clear();
for (auto& feature : features)
sel_features.insert(&feature);
}