/*** Olive - Non-Linear Video Editor Copyright (C) 2019 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "viewerdisplay.h" #include #include #include #include #include #include #include #include #include "common/define.h" #include "common/functiontimer.h" #include "gizmotraverser.h" #include "render/pixelformat.h" #include "core.h" OLIVE_NAMESPACE_ENTER ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent) : ManagedDisplayWidget(parent), signal_cursor_color_(false), gizmos_(nullptr), gizmo_click_(false), last_loaded_buffer_(nullptr), hand_dragging_(false), deinterlace_(false) { connect(Core::instance(), &Core::ToolChanged, this, &ViewerDisplayWidget::UpdateCursor); // Initializes cursor based on tool UpdateCursor(); } ViewerDisplayWidget::~ViewerDisplayWidget() { OnDestroy(); } void ViewerDisplayWidget::SetMatrixTranslate(const QMatrix4x4 &mat) { translate_matrix_ = mat; UpdateMatrix(); } void ViewerDisplayWidget::SetMatrixZoom(const QMatrix4x4 &mat) { scale_matrix_ = mat; UpdateMatrix(); } void ViewerDisplayWidget::UpdateCursor() { if (Core::instance()->tool() == Tool::kHand) { setCursor(Qt::OpenHandCursor); } else { unsetCursor(); } } QMatrix4x4 ViewerDisplayWidget::GetCompleteMatrixFlippedYTranslation() { QMatrix4x4 mat = combined_matrix_; mat.scale(1, -1, 1); return mat; } void ViewerDisplayWidget::SetSignalCursorColorEnabled(bool e) { signal_cursor_color_ = e; setMouseTracking(e); } void ViewerDisplayWidget::SetImage(FramePtr in_buffer) { last_loaded_buffer_ = in_buffer; if (last_loaded_buffer_) { makeCurrent(); if (!texture_ || texture_->width() != in_buffer->width() || texture_->height() != in_buffer->height() || texture_->format() != in_buffer->format()) { texture_ = renderer()->CreateTexture(in_buffer->video_params(), in_buffer->data(), in_buffer->linesize_pixels()); } else { texture_->Upload(in_buffer->data(), in_buffer->linesize_pixels()); } doneCurrent(); } update(); } void ViewerDisplayWidget::SetDeinterlacing(bool e) { deinterlace_ = e; update(); } const ViewerSafeMarginInfo &ViewerDisplayWidget::GetSafeMargin() const { return safe_margin_; } void ViewerDisplayWidget::SetSafeMargins(const ViewerSafeMarginInfo &safe_margin) { if (safe_margin_ != safe_margin) { safe_margin_ = safe_margin; update(); } } void ViewerDisplayWidget::SetGizmos(Node *node) { if (gizmos_ != node) { gizmos_ = node; update(); } } void ViewerDisplayWidget::SetVideoParams(const VideoParams ¶ms) { gizmo_params_ = params; if (gizmos_) { update(); } } void ViewerDisplayWidget::SetTime(const rational &time) { time_ = time; if (gizmos_) { update(); } } FramePtr ViewerDisplayWidget::last_loaded_buffer() const { return last_loaded_buffer_; } QPoint ViewerDisplayWidget::TransformViewerSpaceToBufferSpace(QPoint pos) { /* * Inversion will only fail if the viewer has been scaled by 0 in any direction * which I think should never happen. */ return pos * GenerateWorldTransform().inverted(); } void ViewerDisplayWidget::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton && gizmos_ && gizmos_->GizmoPress(gizmo_db_, TransformViewerSpaceToBufferSpace(event->pos()), QVector2D(GetTexturePosition(size())), size())) { // Handle gizmo click gizmo_click_ = true; gizmo_drag_time_ = GetGizmoTime(); } else if (IsHandDrag(event)) { // Handle hand drag hand_last_drag_pos_ = event->pos(); hand_dragging_ = true; emit HandDragStarted(); setCursor(Qt::ClosedHandCursor); } else { if (event->button() == Qt::LeftButton) { // Handle standard drag emit DragStarted(); } ManagedDisplayWidget::mousePressEvent(event); } } void ViewerDisplayWidget::mouseMoveEvent(QMouseEvent *event) { // Do this no matter what, emits signal to any pixel samplers if (signal_cursor_color_) { Color reference, display; if (last_loaded_buffer_) { QVector3D pixel_pos(static_cast(event->x()) / static_cast(width()) * 2.0f - 1.0f, static_cast(event->y()) / static_cast(height()) * 2.0f - 1.0f, 0); pixel_pos = GenerateWorldTransform().inverted() * pixel_pos; int frame_x = qRound((pixel_pos.x() + 1.0f) * 0.5f * last_loaded_buffer_->width()); int frame_y = qRound((pixel_pos.y() + 1.0f) * 0.5f * last_loaded_buffer_->height()); reference = last_loaded_buffer_->get_pixel(frame_x, frame_y); display = color_service()->ConvertColor(reference); } emit CursorColor(reference, display); } // Handle hand dragging if (hand_dragging_) { // Emit movement emit HandDragMoved(event->x() - hand_last_drag_pos_.x(), event->y() - hand_last_drag_pos_.y()); hand_last_drag_pos_ = event->pos(); } else if (gizmo_click_) { // Signal movement gizmos_->GizmoMove(TransformViewerSpaceToBufferSpace(event->pos()), QVector2D(GetTexturePosition(size())), gizmo_drag_time_); update(); } else { // Default behavior ManagedDisplayWidget::mouseMoveEvent(event); } } void ViewerDisplayWidget::mouseReleaseEvent(QMouseEvent *event) { if (hand_dragging_) { // Handle hand drag emit HandDragEnded(); hand_dragging_ = false; UpdateCursor(); } else if (gizmo_click_) { // Handle gizmo gizmos_->GizmoRelease(); gizmo_click_ = false; } else { // Default behavior ManagedDisplayWidget::mouseReleaseEvent(event); } } void ViewerDisplayWidget::OnInit() { ManagedDisplayWidget::OnInit(); } void ViewerDisplayWidget::OnPaint() { // Clear background to empty renderer()->ClearDestination(); // We only draw if we have a pipeline if (last_loaded_buffer_ && color_service()) { if (deinterlace_) { qDebug() << "FIXME: Deinterlacing is currently broken, we're working on this..."; //color_service()->pipeline()->setUniformValue("ove_resolution", texture_.width(), texture_.height()); //color_service()->pipeline()->setUniformValue("ove_deinterlace", deinterlace_); } // Draw texture through color transform renderer()->BlitColorManaged(color_service(), texture_, VideoParams(width(), height(), PixelFormat::PIX_FMT_RGBA16F), GetCompleteMatrixFlippedYTranslation()); } QTransform world_transform = GenerateWorldTransform(); // Draw gizmos if we have any if (gizmos_) { GizmoTraverser gt(QSize(gizmo_params_.width(), gizmo_params_.height())); rational node_time = GetGizmoTime(); gizmo_db_ = gt.GenerateDatabase(gizmos_, TimeRange(node_time, node_time)); QPainter p(inner_widget()); p.setWorldTransform(world_transform); gizmos_->DrawGizmos(gizmo_db_, &p, QVector2D(GetTexturePosition(size())), size()); } // Draw action/title safe areas if (safe_margin_.is_enabled()) { QPainter p(inner_widget()); p.setWorldTransform(world_transform); p.setPen(Qt::lightGray); p.setBrush(Qt::NoBrush); int x = 0, y = 0, w = width(), h = height(); if (safe_margin_.custom_ratio()) { double widget_ar = static_cast(width()) / static_cast(height()); if (widget_ar > safe_margin_.ratio()) { // Widget is wider than margins w = h * safe_margin_.ratio(); x = width() / 2 - w / 2; } else { h = w / safe_margin_.ratio(); y = height() / 2 - h / 2; } } p.drawRect(w / 20 + x, h / 20 + y, w / 10 * 9, h / 10 * 9); p.drawRect(w / 10 + x, h / 10 + y, w / 10 * 8, h / 10 * 8); int cross = qMin(w, h) / 32; QLine lines[] = {QLine(rect().center().x() - cross, rect().center().y(),rect().center().x() + cross, rect().center().y()), QLine(rect().center().x(), rect().center().y() - cross, rect().center().x(), rect().center().y() + cross)}; p.drawLines(lines, 2); } } void ViewerDisplayWidget::OnDestroy() { ManagedDisplayWidget::OnDestroy(); texture_ = nullptr; } QMatrix4x4 ViewerDisplayWidget::GetMatrixTranslate() { return translate_matrix_; } QPointF ViewerDisplayWidget::GetTexturePosition(const QPoint &screen_pos) { return GetTexturePosition(screen_pos.x(), screen_pos.y()); } QPointF ViewerDisplayWidget::GetTexturePosition(const QSize &size) { return GetTexturePosition(size.width(), size.height()); } QPointF ViewerDisplayWidget::GetTexturePosition(const double &x, const double &y) { return QPointF(x / gizmo_params_.width(), y / gizmo_params_.height()); } rational ViewerDisplayWidget::GetGizmoTime() { return GetAdjustedTime(GetTimeTarget(), gizmos_, time_, NodeParam::kInput); } bool ViewerDisplayWidget::IsHandDrag(QMouseEvent *event) const { return event->button() == Qt::MiddleButton || Core::instance()->tool() == Tool::kHand; } void ViewerDisplayWidget::UpdateMatrix() { combined_matrix_ = scale_matrix_ * translate_matrix_; update(); } QTransform ViewerDisplayWidget::GenerateWorldTransform() { /* * Get matrix elements (roughly) as below in column major order * * | Sx 0 0 Tx | * | 0 Sy 0 Ty | * | 0 0 Sz Tz | * | 0 0 0 1 | */ float *d = combined_matrix_.data(); QTransform world; // Move corner of canvas to correct point world.translate(width() * 0.5 - width() * *(d)*0.5, height() * 0.5 - height() * *(d + 5) * 0.5); // Scale world.scale(*(d), *(d + 5)); // Translate for mouse movement world.translate(*(d + 12) * width() * 0.5 / *(d), *(d + 13) * height() * 0.5 / *(d + 5)); return world; } OLIVE_NAMESPACE_EXIT