further improved gizmos

This commit is contained in:
itsmattkc
2020-11-22 18:21:24 +11:00
parent bd215b34bc
commit a6d382f4d4
17 changed files with 118 additions and 85 deletions
+1 -7
View File
@@ -107,7 +107,7 @@ void CropDistortNode::DrawGizmos(NodeValueDatabase &db, QPainter *p)
{
QVector2D resolution = db[QStringLiteral("global")].Get(NodeParam::kVec2, QStringLiteral("resolution")).value<QVector2D>();
const int handle_radius = GetGizmoHandleRadius();
const double handle_radius = GetGizmoHandleRadius(p->transform());
p->setPen(QPen(Qt::white, 0));
@@ -152,12 +152,6 @@ bool CropDistortNode::GizmoPress(NodeValueDatabase &db, const QPointF &p)
bool in_rect = found_handle ? false : gizmo_whole_rect_.contains(p);
qDebug() << "Active handles:";
for (int i=0; i<kGizmoScaleCount; i++) {
qDebug() << " " << i << gizmo_active[i];
}
qDebug() << " Rect:" << in_rect;
gizmo_drag_ = kGizmoNone;
// Drag handle
@@ -28,7 +28,7 @@ namespace olive {
TransformDistortNode::TransformDistortNode()
{
autoscale_input_ = new NodeInput(QStringLiteral("autoscale_in"), NodeParam::kBoolean, false);
autoscale_input_ = new NodeInput(QStringLiteral("autoscale_in"), NodeParam::kCombo, 0);
AddInput(autoscale_input_);
texture_input_ = new NodeInput(QStringLiteral("tex_in"), NodeParam::kTexture);
@@ -41,12 +41,14 @@ void TransformDistortNode::Retranslate()
autoscale_input_->set_name(tr("Auto-Scale"));
texture_input_->set_name(tr("Texture"));
autoscale_input_->set_combobox_strings({tr("None"), tr("Fit"), tr("Fill"), tr("Stretch")});
}
NodeValueTable TransformDistortNode::Value(NodeValueDatabase &value) const
{
// Generate matrix
QMatrix4x4 generated_matrix = GenerateMatrix(value, true, false, false);
QMatrix4x4 generated_matrix = GenerateMatrix(value, true, false, false, false);
// Pop texture
TexturePtr texture = value[texture_input_].Take(NodeParam::kTexture).value<TexturePtr>();
@@ -59,7 +61,7 @@ NodeValueTable TransformDistortNode::Value(NodeValueDatabase &value) const
// Adjust our matrix by the resolutions involved
QVector2D sequence_res = value[QStringLiteral("global")].Get(NodeParam::kVec2, QStringLiteral("resolution")).value<QVector2D>();
QVector2D texture_res(texture->params().width() * texture->pixel_aspect_ratio().toDouble(), texture->params().height());
bool autoscale = value[autoscale_input_].Get(NodeParam::kBoolean).toBool();
AutoScaleType autoscale = static_cast<AutoScaleType>(value[autoscale_input_].Get(NodeParam::kCombo).toInt());
QMatrix4x4 real_matrix = AdjustMatrixByResolutions(generated_matrix,
sequence_res,
@@ -142,6 +144,9 @@ bool TransformDistortNode::GizmoPress(NodeValueDatabase &db, const QPointF &p)
gizmo_scale_anchor_.setY(texture_sz.y() - gizmo_scale_anchor_.y());
}
// Store current matrix
gizmo_matrix_ = GenerateMatrix(db, false, true, true, true);
return true;
} else if (gizmo_anchor_pt_.contains(p)) {
@@ -152,7 +157,7 @@ bool TransformDistortNode::GizmoPress(NodeValueDatabase &db, const QPointF &p)
gizmo_drag_ = anchor_input();
// Store current matrix
gizmo_matrix_ = GenerateMatrix(db, false, true, true);
gizmo_matrix_ = GenerateMatrix(db, false, true, true, false);
return true;
@@ -235,24 +240,27 @@ void TransformDistortNode::GizmoMove(const QPointF &p, const rational &time)
}
}
QPointF mouse_relative = gizmo_matrix_.toTransform().inverted().map(QPointF(p - gizmo_anchor_pt_.center()));
double x_scaled_movement = qAbs(mouse_relative.x() / gizmo_scale_anchor_.x());
double y_scaled_movement = qAbs(mouse_relative.y() / gizmo_scale_anchor_.y());
switch (gizmo_scale_axes_) {
case kGizmoScaleXOnly:
gizmo_dragger_[0].Drag(qAbs((p.x() - gizmo_anchor_pt_.center().x()) / gizmo_scale_anchor_.x()));
gizmo_dragger_[0].Drag(x_scaled_movement);
break;
case kGizmoScaleYOnly:
gizmo_dragger_[0].Drag(qAbs((p.y() - gizmo_anchor_pt_.center().y()) / gizmo_scale_anchor_.y()));
gizmo_dragger_[0].Drag(y_scaled_movement);
break;
case kGizmoScaleBoth:
if (gizmo_scale_uniform_) {
double distance = std::hypot(p.x() - gizmo_anchor_pt_.center().x(), p.y() - gizmo_anchor_pt_.center().y());
double distance = std::hypot(mouse_relative.x(), mouse_relative.y());
double texture_diag = std::hypot(gizmo_scale_anchor_.x(), gizmo_scale_anchor_.y());
gizmo_dragger_[0].Drag(qAbs(distance / texture_diag));
} else {
gizmo_dragger_[0].Drag(qAbs((p.x() - gizmo_anchor_pt_.center().x()) / gizmo_scale_anchor_.x()));
gizmo_dragger_[1].Drag(qAbs((p.y() - gizmo_anchor_pt_.center().y()) / gizmo_scale_anchor_.y()));
gizmo_dragger_[0].Drag(x_scaled_movement);
gizmo_dragger_[1].Drag(y_scaled_movement);
}
break;
}
@@ -289,7 +297,7 @@ void TransformDistortNode::GizmoRelease()
gizmo_drag_ = nullptr;
}
QMatrix4x4 TransformDistortNode::AdjustMatrixByResolutions(const QMatrix4x4 &mat, const QVector2D &sequence_res, const QVector2D &texture_res, bool auto_sz)
QMatrix4x4 TransformDistortNode::AdjustMatrixByResolutions(const QMatrix4x4 &mat, const QVector2D &sequence_res, const QVector2D &texture_res, AutoScaleType autoscale_type)
{
// First, create an identity matrix
QMatrix4x4 adjusted_matrix;
@@ -304,21 +312,31 @@ QMatrix4x4 TransformDistortNode::AdjustMatrixByResolutions(const QMatrix4x4 &mat
adjusted_matrix.scale(texture_res.x() * 0.5, texture_res.y() * 0.5, 1.0);
// If auto-scale is enabled, fit the texture to the sequence (without cropping)
if (auto_sz) {
double footage_real_ar = texture_res.x() / texture_res.y();
double sequence_real_ar = sequence_res.x() / sequence_res.y();
double autoscale_val;
if (sequence_real_ar > footage_real_ar) {
// Sequence is wider than footage, scale by height
autoscale_val = sequence_res.y() / texture_res.y();
if (autoscale_type != kAutoScaleNone) {
if (autoscale_type == kAutoScaleStretch) {
adjusted_matrix.scale(sequence_res.x() / texture_res.x(),
sequence_res.y() / texture_res.y(),
1.0);
} else {
// Footage is wider than sequence, scale by width
autoscale_val = sequence_res.x() / texture_res.x();
}
double footage_real_ar = texture_res.x() / texture_res.y();
double sequence_real_ar = sequence_res.x() / sequence_res.y();
adjusted_matrix.scale(autoscale_val, autoscale_val, 1.0);
double scale_by_x = sequence_res.x() / texture_res.x();
double scale_by_y = sequence_res.y() / texture_res.y();
double autoscale_val;
if ((autoscale_type == kAutoScaleFit) == (sequence_real_ar > footage_real_ar)) {
// Scale by height. Either the sequence is wider than the footage or we're using fill and
// cutting off the sides
autoscale_val = scale_by_y;
} else {
// Scale by width. Either the footage is wider than the sequence or we're using fill and
// cutting off the top and bottom
autoscale_val = scale_by_x;
}
adjusted_matrix.scale(autoscale_val, autoscale_val, 1.0);
}
}
return adjusted_matrix;
@@ -343,12 +361,12 @@ void TransformDistortNode::DrawGizmos(NodeValueDatabase &db, QPainter *p)
QVector2D tex_sz = db[texture_input_].Get(NodeParam::kTexture).value<QVector2D>();
// Retrieve autoscale value
bool autoscale = db[autoscale_input_].Get(NodeParam::kBoolean).toBool();
AutoScaleType autoscale = static_cast<AutoScaleType>(db[autoscale_input_].Get(NodeParam::kCombo).toInt());
// Fold values into a matrix for the rectangle
QMatrix4x4 rectangle_matrix;
rectangle_matrix.scale(sequence_half_res);
rectangle_matrix *= AdjustMatrixByResolutions(GenerateMatrix(db, false, false, false),
rectangle_matrix *= AdjustMatrixByResolutions(GenerateMatrix(db, false, false, false, false),
sequence_res,
tex_sz,
autoscale);
@@ -366,15 +384,18 @@ void TransformDistortNode::DrawGizmos(NodeValueDatabase &db, QPainter *p)
// Draw rectangle
p->drawPolyline(gizmo_rect_);
// Get handle size (relative to screen space rather than buffer space)
const double resize_handle_rad = GetGizmoHandleRadius(p->transform());
// Draw anchor point
QMatrix4x4 anchor_matrix;
anchor_matrix.scale(sequence_half_res);
anchor_matrix *= AdjustMatrixByResolutions(GenerateMatrix(db, false, true, false),
anchor_matrix *= AdjustMatrixByResolutions(GenerateMatrix(db, false, true, false, false),
sequence_res,
tex_sz,
autoscale);
QPointF anchor_pt = anchor_matrix.toTransform().map(QPointF(0, 0)) + sequence_half_res_pt;
const int anchor_pt_radius = QFontMetrics(qApp->font()).height() / 2 * 3;
const double anchor_pt_radius = resize_handle_rad * 2;
gizmo_anchor_pt_ = QRectF(anchor_pt.x() - anchor_pt_radius,
anchor_pt.y() - anchor_pt_radius,
@@ -392,8 +413,6 @@ void TransformDistortNode::DrawGizmos(NodeValueDatabase &db, QPainter *p)
p->setPen(Qt::NoPen);
p->setBrush(Qt::white);
const int resize_handle_rad = GetGizmoHandleRadius();
gizmo_resize_handle_[kGizmoScaleTopLeft] = CreateGizmoHandleRect(CreateScalePoint(-1, -1, sequence_half_res_pt, rectangle_matrix), resize_handle_rad);
gizmo_resize_handle_[kGizmoScaleTopCenter] = CreateGizmoHandleRect(CreateScalePoint( 0, -1, sequence_half_res_pt, rectangle_matrix), resize_handle_rad);
gizmo_resize_handle_[kGizmoScaleTopRight] = CreateGizmoHandleRect(CreateScalePoint( 1, -1, sequence_half_res_pt, rectangle_matrix), resize_handle_rad);
@@ -83,10 +83,17 @@ public:
return texture_input_;
}
enum AutoScaleType {
kAutoScaleNone,
kAutoScaleFit,
kAutoScaleFill,
kAutoScaleStretch
};
static QMatrix4x4 AdjustMatrixByResolutions(const QMatrix4x4& mat,
const QVector2D& sequence_res,
const QVector2D& texture_res,
bool auto_sz);
AutoScaleType autoscale_type = kAutoScaleNone);
private:
static QPointF CreateScalePoint(double x, double y, const QPointF& half_res, const QMatrix4x4& mat);
+15 -4
View File
@@ -91,16 +91,17 @@ void MatrixGenerator::Retranslate()
NodeValueTable MatrixGenerator::Value(NodeValueDatabase &value) const
{
// Push matrix output
QMatrix4x4 mat = GenerateMatrix(value, true, false, false);
QMatrix4x4 mat = GenerateMatrix(value, true, false, false, false);
NodeValueTable output = value.Merge();
output.Push(NodeParam::kMatrix, mat, this);
return output;
}
QMatrix4x4 MatrixGenerator::GenerateMatrix(NodeValueDatabase &value, bool take, bool ignore_anchor, bool ignore_position) const
QMatrix4x4 MatrixGenerator::GenerateMatrix(NodeValueDatabase &value, bool take, bool ignore_anchor, bool ignore_position, bool ignore_scale) const
{
QVector2D anchor;
QVector2D position;
QVector2D scale;
if (!ignore_anchor) {
if (take) {
@@ -115,6 +116,16 @@ QMatrix4x4 MatrixGenerator::GenerateMatrix(NodeValueDatabase &value, bool take,
value[anchor_input_].Take(NodeParam::kVec2).value<QVector2D>();
}
if (!ignore_scale) {
if (take) {
scale = value[scale_input_].Take(NodeParam::kVec2).value<QVector2D>();
} else {
scale = value[scale_input_].Get(NodeParam::kVec2).value<QVector2D>();
}
} else if (take) {
value[scale_input_].Take(NodeParam::kVec2).value<QVector2D>();
}
if (!ignore_position) {
if (take) {
position = value[position_input_].Take(NodeParam::kVec2).value<QVector2D>();
@@ -128,13 +139,13 @@ QMatrix4x4 MatrixGenerator::GenerateMatrix(NodeValueDatabase &value, bool take,
if (take) {
return GenerateMatrix(position,
value[rotation_input_].Take(NodeParam::kFloat).toFloat(),
value[scale_input_].Take(NodeParam::kVec2).value<QVector2D>(),
scale,
value[uniform_scale_input_].Take(NodeParam::kBoolean).toBool(),
anchor);
} else {
return GenerateMatrix(position,
value[rotation_input_].Get(NodeParam::kFloat).toFloat(),
value[scale_input_].Get(NodeParam::kVec2).value<QVector2D>(),
scale,
value[uniform_scale_input_].Get(NodeParam::kBoolean).toBool(),
anchor);
+1 -1
View File
@@ -47,7 +47,7 @@ public:
virtual NodeValueTable Value(NodeValueDatabase& value) const override;
protected:
QMatrix4x4 GenerateMatrix(NodeValueDatabase &value, bool take, bool ignore_anchor, bool ignore_position) const;
QMatrix4x4 GenerateMatrix(NodeValueDatabase &value, bool take, bool ignore_anchor, bool ignore_position, bool ignore_scale) const;
static QMatrix4x4 GenerateMatrix(const QVector2D &pos,
const float &rot,
const QVector2D &scale,
+1 -2
View File
@@ -314,8 +314,7 @@ NodeValueTable MathNodeBase::ValueInternal(NodeValueDatabase &value, Operation o
QMatrix4x4 adjusted_matrix = TransformDistortNode::AdjustMatrixByResolutions(number_val.data().value<QMatrix4x4>(),
sequence_res,
texture_res,
false);
texture_res);
if (operation != kOpMultiply || adjusted_matrix.isIdentity()) {
operation_is_noop = true;
+7 -3
View File
@@ -941,12 +941,16 @@ QRectF Node::CreateGizmoHandleRect(const QPointF &pt, int radius)
return QRectF(pt.x() - radius,
pt.y() - radius,
2*radius,
2*radius);;
2*radius);
}
int Node::GetGizmoHandleRadius()
double Node::GetGizmoHandleRadius(const QTransform &transform)
{
return QFontMetrics(qApp->font()).height() / 2;
double raw_value = QFontMetrics(qApp->font()).height() * 0.25;
raw_value /= transform.m11();
return raw_value;
}
void Node::DrawAndExpandGizmoHandles(QPainter *p, int handle_radius, QRectF *rects, int count)
+1 -1
View File
@@ -458,7 +458,7 @@ protected:
static QRectF CreateGizmoHandleRect(const QPointF& pt, int radius);
static int GetGizmoHandleRadius();
static double GetGizmoHandleRadius(const QTransform& transform);
static void DrawAndExpandGizmoHandles(QPainter* p, int handle_radius, QRectF* rects, int count);
+5 -3
View File
@@ -342,7 +342,7 @@ struct TextureToBind {
Texture::Interpolation interpolation;
};
void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, VideoParams destination_params)
void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, VideoParams destination_params, bool clear_destination)
{
// If this node is iterative, we'll pick up which input here
QString iterative_name;
@@ -541,8 +541,10 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video
DetachTextureAsDestination();
}
// Clear the destination, whatever it is
ClearDestination();
// Clear the destination if the caller requested it
if (clear_destination) {
ClearDestination();
}
} else {
// Always draw to output_tex, which gets swapped with input_tex every iteration
AttachTextureAsDestination(output_tex.get());
+2 -1
View File
@@ -68,7 +68,8 @@ protected slots:
virtual void Blit(QVariant shader,
olive::ShaderJob job,
olive::Texture* destination,
olive::VideoParams destination_params) override;
olive::VideoParams destination_params,
bool clear_destination) override;
private:
static GLint GetInternalFormat(VideoParams::Format format, int channel_layout);
+7 -7
View File
@@ -56,14 +56,14 @@ TexturePtr Renderer::CreateTexture(const VideoParams &params, const void *data,
return CreateTexture(params, Texture::k2D, data, linesize);
}
void Renderer::BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, bool source_is_premultiplied, Texture *destination, const QMatrix4x4 &matrix)
void Renderer::BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, bool source_is_premultiplied, Texture *destination, bool clear_destination, const QMatrix4x4 &matrix)
{
BlitColorManagedInternal(color_processor, source, source_is_premultiplied, destination, destination->params(), matrix);
BlitColorManagedInternal(color_processor, source, source_is_premultiplied, destination, destination->params(), clear_destination, matrix);
}
void Renderer::BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, bool source_is_premultiplied, VideoParams params, const QMatrix4x4& matrix)
void Renderer::BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, bool source_is_premultiplied, VideoParams params, bool clear_destination, const QMatrix4x4& matrix)
{
BlitColorManagedInternal(color_processor, source, source_is_premultiplied, nullptr, params, matrix);
BlitColorManagedInternal(color_processor, source, source_is_premultiplied, nullptr, params, clear_destination, matrix);
}
void Renderer::Destroy()
@@ -225,7 +225,7 @@ bool Renderer::GetColorContext(ColorProcessorPtr color_processor, Renderer::Colo
void Renderer::BlitColorManagedInternal(ColorProcessorPtr color_processor, TexturePtr source,
bool source_is_premultiplied, Texture *destination,
VideoParams params, const QMatrix4x4& matrix)
VideoParams params, bool clear_destination, const QMatrix4x4& matrix)
{
ColorContext color_ctx;
if (!GetColorContext(color_processor, &color_ctx)) {
@@ -262,9 +262,9 @@ void Renderer::BlitColorManagedInternal(ColorProcessorPtr color_processor, Textu
}
if (destination) {
BlitToTexture(color_ctx.compiled_shader, job, destination);
BlitToTexture(color_ctx.compiled_shader, job, destination, clear_destination);
} else {
Blit(color_ctx.compiled_shader, job, params);
Blit(color_ctx.compiled_shader, job, params, clear_destination);
}
}
+12 -8
View File
@@ -48,20 +48,22 @@ public:
void BlitToTexture(QVariant shader,
olive::ShaderJob job,
olive::Texture* destination)
olive::Texture* destination,
bool clear_destination = true)
{
Blit(shader, job, destination, destination->params());
Blit(shader, job, destination, destination->params(), clear_destination);
}
void Blit(QVariant shader,
olive::ShaderJob job,
olive::VideoParams params)
olive::VideoParams params,
bool clear_destination = true)
{
Blit(shader, job, nullptr, params);
Blit(shader, job, nullptr, params, clear_destination);
}
void BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, bool source_is_premultiplied, Texture* destination, const QMatrix4x4& matrix = QMatrix4x4());
void BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, bool source_is_premultiplied, VideoParams params, const QMatrix4x4& matrix = QMatrix4x4());
void BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, bool source_is_premultiplied, Texture* destination, bool clear_destination = true, const QMatrix4x4& matrix = QMatrix4x4());
void BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, bool source_is_premultiplied, VideoParams params, bool clear_destination = true, const QMatrix4x4& matrix = QMatrix4x4());
void Destroy();
@@ -89,7 +91,8 @@ protected slots:
virtual void Blit(QVariant shader,
olive::ShaderJob job,
olive::Texture* destination,
olive::VideoParams destination_params) = 0;
olive::VideoParams destination_params,
bool clear_destination) = 0;
private:
struct ColorContext {
@@ -115,7 +118,8 @@ private:
void BlitColorManagedInternal(ColorProcessorPtr color_processor, TexturePtr source,
bool source_is_premultiplied,
Texture* destination, VideoParams params, const QMatrix4x4 &matrix);
Texture* destination, VideoParams params, bool clear_destination,
const QMatrix4x4 &matrix);
QHash<QString, ColorContext> color_cache_;
+3 -2
View File
@@ -148,13 +148,14 @@ void RendererThreadWrapper::DownloadFromTexture(Texture *texture, void *data, in
Q_ARG(int, linesize));
}
void RendererThreadWrapper::Blit(QVariant shader, ShaderJob job, Texture *destination, VideoParams destination_params)
void RendererThreadWrapper::Blit(QVariant shader, ShaderJob job, Texture *destination, VideoParams destination_params, bool clear_destination)
{
QMetaObject::invokeMethod(inner_, "Blit", Qt::BlockingQueuedConnection,
Q_ARG(QVariant, shader),
OLIVE_NS_ARG(ShaderJob, job),
OLIVE_NS_ARG(Texture*, destination),
OLIVE_NS_ARG(VideoParams, destination_params));
OLIVE_NS_ARG(VideoParams, destination_params),
Q_ARG(bool, clear_destination));
}
}
+2 -1
View File
@@ -64,7 +64,8 @@ protected slots:
virtual void Blit(QVariant shader,
olive::ShaderJob job,
olive::Texture* destination,
olive::VideoParams destination_params) override;
olive::VideoParams destination_params,
bool clear_destination) override;
private:
Renderer* inner_;
+1 -1
View File
@@ -104,7 +104,7 @@ void RenderProcessor::Run()
if (output_color_transform) {
// Yes color transform, blit color managed
render_ctx_->BlitColorManaged(output_color_transform, texture, true, blit_tex.get(), matrix);
render_ctx_->BlitColorManaged(output_color_transform, texture, true, blit_tex.get(), true, matrix);
} else {
// No color transform, just blit
ShaderJob job;
+4 -7
View File
@@ -280,15 +280,11 @@ void ViewerDisplayWidget::mouseReleaseEvent(QMouseEvent *event)
}
}
void ViewerDisplayWidget::OnInit()
{
ManagedDisplayWidget::OnInit();
}
void ViewerDisplayWidget::OnPaint()
{
// Clear background to empty
renderer()->ClearDestination();
QColor bg_color = palette().window().color();
renderer()->ClearDestination(bg_color.redF(), bg_color.greenF(), bg_color.blueF());
// We only draw if we have a pipeline
if (last_loaded_buffer_ && color_service()) {
@@ -317,7 +313,7 @@ void ViewerDisplayWidget::OnPaint()
VideoParams::Format device_format = static_cast<VideoParams::Format>(Config::Current()["OfflinePixelFormat"].toInt());
VideoParams device_params(device_width, device_height, device_format, VideoParams::kInternalChannelCount);
renderer()->BlitColorManaged(color_service(), texture_to_draw, true, device_params,
renderer()->BlitColorManaged(color_service(), texture_to_draw, true, device_params, false,
combined_matrix_flipped_);
}
@@ -444,6 +440,7 @@ QTransform ViewerDisplayWidget::GenerateGizmoTransform()
QVector2D viewer_scale(GetTexturePosition(size()));
QTransform gizmo_transform = GenerateWorldTransform();
gizmo_transform.scale(viewer_scale.x(), viewer_scale.y());
gizmo_transform.scale(gizmo_params_.pixel_aspect_ratio().flipped().toDouble(), 1);
return gizmo_transform;
}
-7
View File
@@ -168,13 +168,6 @@ protected:
virtual void mouseReleaseEvent(QMouseEvent* event) override;
protected:
/**
* @brief Initialize function to set up the OpenGL context upon its construction
*
* Currently primarily used to regenerate the pipeline shader used for drawing.
*/
virtual void OnInit() override;
/**
* @brief Paint function to display the texture (received in SetTexture()) on screen.
*