/*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team Modifications Copyright (C) 2025 mikesolar 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 . ***/ #ifndef OAK_VIEWERGLWIDGET_H #define OAK_VIEWERGLWIDGET_H #include #include #include #include #include "codec/frame.h" #include "node/color/colormanager/colormanager.h" #include "node/gizmo/text.h" #include "node/node.h" #include "node/output/track/tracklist.h" #include "node/traverser.h" #include "tool/tool.h" #include "viewerqueue.h" #include "viewersafemargininfo.h" #include "viewertexteditor.h" #include "widget/manageddisplay/manageddisplay.h" #include "widget/timetarget/timetarget.h" namespace olive { /** * @brief The inner display/rendering widget of a Viewer class. * * Actual composition occurs elsewhere offscreen and * multithreaded, so its main purpose is receiving a finalized OpenGL texture and displaying it. * * The main entry point is SetTexture() which will receive an OpenGL texture ID, store it, and then call update() to * draw it on screen. The drawing function is in paintGL() (called during the update() process by Qt) and is fairly * simple OpenGL drawing code standardized around OpenGL ES 3.2 Core. * * If the texture has been modified and you're 100% sure this widget is using the same texture object, it's possible * to call update() directly to trigger a repaint, however this is not recommended. If you are not 100% sure it'll be * the same texture object, use SetTexture() since it will nearly always be faster to just set it than to check *and* * set it. */ class ViewerDisplayWidget : public ManagedDisplayWidget, public TimeTargetObject { Q_OBJECT public: /** * @brief ViewerGLWidget Constructor * * @param parent * * QWidget parent. */ ViewerDisplayWidget(QWidget *parent = nullptr); virtual ~ViewerDisplayWidget() override; const ViewerSafeMarginInfo &get_safe_margin() const; void set_safe_margins(const ViewerSafeMarginInfo &safe_margin); void set_gizmos(Node *node); const VideoParams &get_video_params() const { return gizmo_params_; } void set_video_params(const VideoParams ¶ms); const AudioParams &get_audio_params() const { return gizmo_audio_params_; } void set_audio_params(const AudioParams &p); void set_time(const Rational &time); void set_subtitle_tracks(Sequence *list); void set_show_widget_background(bool e) { show_widget_background_ = e; update(); } /** * @brief Transform a point from viewer space to the buffer space. * Multiplies by the inverted transform matrix to undo the scaling and translation. */ QPointF transform_viewer_space_to_buffer_space(const QPointF &pos); bool is_deinterlacing() const { return deinterlace_; } void reset_fps_timer(); bool get_show_fps() const { return show_fps_; } bool get_show_subtitles() const { return show_subtitles_; } void set_show_subtitles(bool e) { show_subtitles_ = e; update(); } void increment_skipped_frames(); void increment_frame_count() { fps_timer_update_count_++; } TexturePtr get_current_texture() const { return texture_; } ColorProcessorPtr get_current_color_processor() { return color_service(); } void play(const int64_t &start_timestamp, const int &playback_speed, const Rational &timebase, bool start_updating); void pause(); ViewerQueue *queue() { return &queue_; } /** * @brief Feed the facade playback position as this display's clock * * The facade playback engine owns the master clock now; the * ViewerWidget polls it (oakengine_playback_get_position) and pushes * the timestamp here for update_from_queue() to pop by. */ void set_playback_timestamp(int64_t ts) { external_ts_.store(ts); } QPointF screen_to_scene_point(const QPoint &p); virtual bool eventFilter(QObject *o, QEvent *e) override; public slots: /** * @brief Set the transformation matrix to draw with * * Set this if you want the drawing to pass through some sort of transform (most of the time you won't want this). */ void set_matrix_translate(const QMatrix4x4 &mat); /** * @brief Set the scale matrix. */ void set_matrix_zoom(const QMatrix4x4 &mat); void set_matrix_crop(const QMatrix4x4 &mat); /** * @brief Enables or disables whether this color at the cursor should be emitted * * Since tracking the mouse every movement, reading pixels, and doing color transforms are processor intensive, we * have an option for it. Ideally, this should be connected to a PixelSamplerPanel::visibilityChanged signal so that * it can automatically be enabled when the user is pixel sampling and disabled for optimization when they're not. */ void set_signal_cursor_color_enabled(bool e); void set_image(const QVariant &buffer); void set_blank(); /** * @brief Changes the pointer type if the tool is changed to the hand tool. Otherwise resets the pointer to it's * normal type. */ void update_cursor(); void tool_changed(); /** * @brief Enables/disables a basic deinterlace on the viewer */ void set_deinterlacing(bool e); void set_show_fps(bool e); void request_start_editing_text(); signals: /** * @brief Signal emitted when the user starts dragging from the viewer */ void drag_started(const QPoint &p); /** * @brief Signal emitted when a hand drag starts */ void hand_drag_started(); /** * @brief Signal emitted when a hand drag moves */ void hand_drag_moved(int x, int y); /** * @brief Signal emitted when a hand drag ends */ void hand_drag_ended(); /** * @brief Signal emitted when cursor color is enabled and the user's mouse position changes */ void cursor_color(const Color &reference, const Color &display); void drag_entered(QDragEnterEvent *event); void drag_left(QDragLeaveEvent *event); void dropped(QDropEvent *event); void texture_changed(TexturePtr texture); void queue_starved(); void queue_no_longer_starved(); void create_addable_at(const QRectF &rect); protected: QTransform generate_world_transform(); QTransform generate_display_transform(); QTransform generate_gizmo_transform(NodeTraverser >, const TimeRange &range); QTransform generate_gizmo_transform() { NodeTraverser t; t.set_cache_video_params(gizmo_params_); return generate_gizmo_transform(t, generate_gizmo_time()); } TimeRange generate_gizmo_time() { Rational node_time = get_gizmo_time(); return TimeRange(node_time, node_time + gizmo_params_.frame_rate_as_time_base()); } virtual TexturePtr load_custom_texture_from_frame(const QVariant &v) { return nullptr; } protected slots: /** * @brief Paint function to display the texture (received in SetTexture()) on screen. * * Simple OpenGL drawing function for painting the texture on screen. Standardized around OpenGL ES 3.2 Core. */ virtual void on_paint() override; virtual void on_destroy() override; private: QPointF get_texture_position(const QPoint &screen_pos); QPointF get_texture_position(const QSize &size); QPointF get_texture_position(const double &x, const double &y); static void draw_text_with_crude_shadow(QPainter *painter, const QRect &rect, const QString &text, const QTextOption &opt = QTextOption()); Rational get_gizmo_time(); bool is_hand_drag(QMouseEvent *event) const; void update_matrix(); NodeGizmo *try_gizmo_press(const NodeValueRow &row, const QPointF &p); void open_text_gizmo(TextGizmo *text, QMouseEvent *event = nullptr); bool on_mouse_press(QMouseEvent *e); bool on_mouse_move(QMouseEvent *e); bool on_mouse_release(QMouseEvent *e); bool on_mouse_double_click(QMouseEvent *e); bool on_key_press(QKeyEvent *e); bool on_key_release(QKeyEvent *e); void emit_color_at_cursor(QMouseEvent *e); void draw_subtitle_tracks(); QPointF get_virtual_pos_for_text_edit(const QPointF &p) { return text_transform_inverted_.map(p) - text_edit_pos_; } template void forward_drag_event_to_text_edit(T *event); bool forward_mouse_event_to_text_edit(QMouseEvent *event, bool check_if_outside = false); bool forward_event_to_text_edit(QEvent *event); QPointF adjust_pos_by_v_align(QPointF p); void close_text_editor(); void generate_gizmo_transforms(); void draw_blank(const VideoParams &device_params); void draw_backend_neutral(const ColorTransformJob &ctj, QPainter *painter); bool draw_backend_neutral_frame(const FramePtr &frame, QPainter *painter); bool draw_backend_neutral_texture(const TexturePtr &texture, QPainter *painter); /** * @brief Internal reference to the OpenGL texture to draw. Set in SetTexture() and used in paintGL(). */ TexturePtr texture_; /** * @brief Internal texture to deinterlace to */ TexturePtr deinterlace_texture_; /** * @brief Offscreen texture for backend-neutral viewer rendering. * * The texture is rendered at device resolution and read back to a QImage so * it can be painted with QPainter on the plain QWidget inner surface. */ TexturePtr backend_neutral_texture_; /** * @brief CPU readback buffer for backend_neutral_texture_. */ QByteArray backend_neutral_buffer_; QImage backend_neutral_cpu_image_; FramePtr backend_neutral_cpu_display_frame_; FramePtr backend_neutral_cpu_source_frame_; TexturePtr backend_neutral_cpu_source_texture_; QString backend_neutral_cpu_color_id_; /** * @brief Deinterlace shader */ QVariant deinterlace_shader_; /** * @brief Blank shader */ QVariant blank_shader_; /** * @brief Translation only matrix (defaults to identity). */ QMatrix4x4 translate_matrix_; /** * @brief Scale only matrix. */ QMatrix4x4 scale_matrix_; /** * @brief Crop only matrix */ QMatrix4x4 crop_matrix_; /** * @brief Cached result of translate_matrix_ and scale_matrix_ multiplied */ QMatrix4x4 combined_matrix_; QMatrix4x4 combined_matrix_flipped_; bool signal_cursor_color_; ViewerSafeMarginInfo safe_margin_; Node *gizmos_; NodeValueRow gizmo_db_; VideoParams gizmo_params_; AudioParams gizmo_audio_params_; QPoint gizmo_start_drag_; QPoint gizmo_last_drag_; TimeRange gizmo_draw_time_; NodeGizmo *current_gizmo_; bool gizmo_drag_started_; QTransform gizmo_last_draw_transform_; QTransform gizmo_last_draw_transform_inverted_; bool show_subtitles_; Sequence *subtitle_tracks_; Rational time_; /** * @brief Position of mouse to calculate delta from. */ QPoint hand_last_drag_pos_; bool hand_dragging_; bool deinterlace_; qint64 fps_timer_start_; int fps_timer_update_count_; bool show_fps_; int frames_skipped_; QVector frame_rate_averages_; int frame_rate_average_count_; bool show_widget_background_; QVariant load_frame_; int playback_speed_; enum PushMode { /// New frame to push to internal texture k_push_frame, /// Internal texture reference is up to date, keep showing it k_push_unnecessary, /// Draw blank/black screen k_push_blank, /// Draw nothing (not even a black frame) k_push_null, }; PushMode push_mode_; // Playback ViewerQueue queue_; std::atomic external_ts_{ -1 }; Rational playback_timebase_; bool add_band_; QPoint add_band_start_; QPoint add_band_end_; bool queue_starved_; TextGizmo *active_text_gizmo_; QPointF text_edit_pos_; ViewerTextEditor *text_edit_; ViewerTextEditorToolBar *text_toolbar_; QTransform text_transform_; QTransform text_transform_inverted_; private slots: void update_from_queue(); void text_edit_changed(); void text_edit_destroyed(); void subtitles_changed(const TimeRange &r); void focus_changed(QWidget *old, QWidget *now); QRectF update_active_text_gizmo_size(); }; } #endif // OAK_VIEWERGLWIDGET_H