• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

paulmthompson / WhiskerToolbox / 17956774620

23 Sep 2025 07:21PM UTC coverage: 68.919% (+0.06%) from 68.862%
17956774620

push

github

paulmthompson
clang tidy fixes for common analysis dashboard widgets

52 of 73 new or added lines in 6 files covered. (71.23%)

576 existing lines in 4 files now uncovered.

41684 of 60483 relevant lines covered (68.92%)

1138.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

46.52
/src/WhiskerToolbox/Media_Widget/MediaMask_Widget/MediaMask_Widget.cpp
1
#include "MediaMask_Widget.hpp"
2
#include "ui_MediaMask_Widget.h"
3

4
#include "Collapsible_Widget/Section.hpp"
5
#include "CoreGeometry/masks.hpp"
6
#include "DataManager/DataManager.hpp"
7
#include "DataManager/Masks/Mask_Data.hpp"
8
#include "ImageProcessing/OpenCVUtility.hpp"
9
#include "MaskDilationWidget/MaskDilationWidget.hpp"
10
#include "Media_Widget/Media_Window/Media_Window.hpp"
11
#include "SelectionWidgets/MaskBrushSelectionWidget.hpp"
12
#include "SelectionWidgets/MaskNoneSelectionWidget.hpp"
13

14
#include <QLabel>
15
#include <QVBoxLayout>
16

17
#include <cmath>
18
#include <iostream>
19
#include <set>
20

21
MediaMask_Widget::MediaMask_Widget(std::shared_ptr<DataManager> data_manager, Media_Window * scene, QWidget * parent)
3✔
22
    : QWidget(parent),
23
      ui(new Ui::MediaMask_Widget),
6✔
24
      _data_manager{std::move(data_manager)},
3✔
25
      _scene{scene} {
6✔
26
    ui->setupUi(this);
3✔
27

28
    // Set size policy for proper resizing
29
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
3✔
30

31
    // Setup selection modes
32
    _selection_modes["(None)"] = Selection_Mode::None;
3✔
33
    _selection_modes["Brush"] = Selection_Mode::Brush;
3✔
34

35
    ui->selection_mode_combo->addItems(QStringList(_selection_modes.keys()));
3✔
36

37
    connect(ui->selection_mode_combo, &QComboBox::currentTextChanged, this, &MediaMask_Widget::_toggleSelectionMode);
3✔
38

39
    connect(ui->color_picker, &ColorPicker_Widget::alphaChanged,
9✔
40
            this, &MediaMask_Widget::_setMaskAlpha);
6✔
41
    connect(ui->color_picker, &ColorPicker_Widget::colorChanged,
9✔
42
            this, &MediaMask_Widget::_setMaskColor);
6✔
43

44
    // Connect bounding box control
45
    connect(ui->show_bounding_box_checkbox, &QCheckBox::toggled,
9✔
46
            this, &MediaMask_Widget::_toggleShowBoundingBox);
6✔
47

48
    // Connect outline control
49
    connect(ui->show_outline_checkbox, &QCheckBox::toggled,
9✔
50
            this, &MediaMask_Widget::_toggleShowOutline);
6✔
51

52
    // Connect transparency control
53
    connect(ui->use_as_transparency_checkbox, &QCheckBox::toggled,
9✔
54
            this, &MediaMask_Widget::_toggleUseAsTransparency);
6✔
55

56
    // Setup the selection mode widgets
57
    _setupSelectionModePages();
3✔
58

59
    // Setup the dilation widget
60
    _setupDilationWidget();
3✔
61
}
3✔
62

63
MediaMask_Widget::~MediaMask_Widget() {
6✔
64
    delete ui;
3✔
65
}
6✔
66

67
void MediaMask_Widget::showEvent(QShowEvent * event) {
2✔
68

69
    static_cast<void>(event);
70

71
    std::cout << "MediaMask_Widget Show Event" << std::endl;
2✔
72
    connect(_scene, &Media_Window::leftClickCanvas, this, &MediaMask_Widget::_clickedInVideo);
2✔
73
    connect(_scene, &Media_Window::rightClickCanvas, this, &MediaMask_Widget::_rightClickedInVideo);
2✔
74
    connect(_scene, &Media_Window::mouseMoveCanvas, this, &MediaMask_Widget::_mouseMoveInVideo);
2✔
75
    connect(_scene, &Media_Window::leftRelease, this, &MediaMask_Widget::_mouseReleased);
2✔
76
    connect(_scene, &Media_Window::rightRelease, this, &MediaMask_Widget::_mouseReleased);
2✔
77
}
2✔
78

79
void MediaMask_Widget::hideEvent(QHideEvent * event) {
5✔
80

81
    static_cast<void>(event);
82

83
    std::cout << "MediaMask_Widget Hide Event" << std::endl;
5✔
84
    disconnect(_scene, &Media_Window::leftClickCanvas, this, &MediaMask_Widget::_clickedInVideo);
5✔
85
    disconnect(_scene, &Media_Window::rightClickCanvas, this, &MediaMask_Widget::_rightClickedInVideo);
5✔
86
    disconnect(_scene, &Media_Window::mouseMoveCanvas, this, &MediaMask_Widget::_mouseMoveInVideo);
5✔
87
    disconnect(_scene, &Media_Window::leftRelease, this, &MediaMask_Widget::_mouseReleased);
5✔
88
    disconnect(_scene, &Media_Window::rightRelease, this, &MediaMask_Widget::_mouseReleased);
5✔
89

90
    // Clean up hover circle when switching away from mask widget
91
    _scene->setShowHoverCircle(false);
5✔
92
}
5✔
93

94
void MediaMask_Widget::_setupSelectionModePages() {
3✔
95
    // Create the "None" mode page using our widget
96
    _noneSelectionWidget = new mask_widget::MaskNoneSelectionWidget();
3✔
97
    ui->mode_stacked_widget->addWidget(_noneSelectionWidget);
3✔
98

99
    // Create the "Brush" mode page using our new widget
100
    _brushSelectionWidget = new mask_widget::MaskBrushSelectionWidget();
3✔
101
    ui->mode_stacked_widget->addWidget(_brushSelectionWidget);
3✔
102

103
    // Connect signals from the brush selection widget
104
    connect(_brushSelectionWidget, &mask_widget::MaskBrushSelectionWidget::brushSizeChanged,
9✔
105
            this, &MediaMask_Widget::_setBrushSize);
6✔
106
    connect(_brushSelectionWidget, &mask_widget::MaskBrushSelectionWidget::hoverCircleVisibilityChanged,
9✔
107
            this, &MediaMask_Widget::_toggleShowHoverCircle);
6✔
108
    connect(_brushSelectionWidget, &mask_widget::MaskBrushSelectionWidget::allowEmptyMaskChanged,
9✔
109
            this, &MediaMask_Widget::_onAllowEmptyMaskChanged);
6✔
110

111
    // Set initial page
112
    ui->mode_stacked_widget->setCurrentIndex(0);
3✔
113
}
3✔
114

115
void MediaMask_Widget::setActiveKey(std::string const & key) {
3✔
116
    _active_key = key;
3✔
117
    ui->name_label->setText(QString::fromStdString(key));
3✔
118

119
    // Set the color picker to the current mask color if available
120
    if (!key.empty()) {
3✔
121
        auto config = _scene->getMaskConfig(key);
3✔
122

123
        if (config) {
3✔
124
            ui->color_picker->setColor(QString::fromStdString(config.value()->hex_color));
3✔
125
            ui->color_picker->setAlpha(static_cast<int>(config.value()->alpha * 100));
3✔
126

127
            // Set bounding box checkbox
128
            ui->show_bounding_box_checkbox->blockSignals(true);
3✔
129
            ui->show_bounding_box_checkbox->setChecked(config.value()->show_bounding_box);
3✔
130
            ui->show_bounding_box_checkbox->blockSignals(false);
3✔
131

132
            // Set outline checkbox
133
            ui->show_outline_checkbox->blockSignals(true);
3✔
134
            ui->show_outline_checkbox->setChecked(config.value()->show_outline);
3✔
135
            ui->show_outline_checkbox->blockSignals(false);
3✔
136

137
            // Set transparency checkbox
138
            ui->use_as_transparency_checkbox->blockSignals(true);
3✔
139
            ui->use_as_transparency_checkbox->setChecked(config.value()->use_as_transparency);
3✔
140
            ui->use_as_transparency_checkbox->blockSignals(false);
3✔
141
        }
142
    }
143
}
3✔
144

145
void MediaMask_Widget::_setMaskAlpha(int alpha) {
×
UNCOV
146
    float const alpha_float = static_cast<float>(alpha) / 100;
×
147

148
    if (!_active_key.empty()) {
×
149
        auto mask_opts = _scene->getMaskConfig(_active_key);
×
150
        if (mask_opts.has_value()) {
×
UNCOV
151
            mask_opts.value()->alpha = alpha_float;
×
152
        }
UNCOV
153
        _scene->UpdateCanvas();
×
154
    }
UNCOV
155
}
×
156

157
void MediaMask_Widget::_setMaskColor(QString const & hex_color) {
×
158
    if (!_active_key.empty()) {
×
159
        auto mask_opts = _scene->getMaskConfig(_active_key);
×
160
        if (mask_opts.has_value()) {
×
UNCOV
161
            mask_opts.value()->hex_color = hex_color.toStdString();
×
162
        }
UNCOV
163
        _scene->UpdateCanvas();
×
164
    }
UNCOV
165
}
×
166

167
void MediaMask_Widget::_toggleSelectionMode(QString text) {
2✔
168
    _selection_mode = _selection_modes[text];
2✔
169

170
    // Switch to the appropriate page in the stacked widget
171
    int pageIndex = static_cast<int>(_selection_mode);
2✔
172
    ui->mode_stacked_widget->setCurrentIndex(pageIndex);
2✔
173

174
    // Update hover circle visibility based on the mode
175
    if (_selection_mode == Selection_Mode::Brush) {
2✔
176
        _scene->setShowHoverCircle(_brushSelectionWidget->isHoverCircleVisible());
2✔
177
        _scene->setHoverCircleRadius(static_cast<double>(_brushSelectionWidget->getBrushSize()));
2✔
178
    } else {
UNCOV
179
        _scene->setShowHoverCircle(false);
×
180
    }
181

182
    std::cout << "MediaMask_Widget selection mode changed to: " << text.toStdString() << std::endl;
2✔
183
}
2✔
184

185
void MediaMask_Widget::_clickedInVideo(CanvasCoordinates const & canvas_coords) {
2✔
186
    if (_active_key.empty()) {
2✔
187
        std::cout << "No active mask key" << std::endl;
×
UNCOV
188
        return;
×
189
    }
190

191
    std::cout << "Left clicked in video at canvas (" << canvas_coords.x << ", " << canvas_coords.y
2✔
192
              << ") with selection mode: " << static_cast<int>(_selection_mode) << std::endl;
2✔
193

194
    // Process the click based on selection mode
195
    switch (_selection_mode) {
2✔
UNCOV
196
        case Selection_Mode::None:
×
197
            // Do nothing in None mode
UNCOV
198
            break;
×
199
        case Selection_Mode::Brush:
2✔
200
            _is_dragging = true;
2✔
201
            _is_adding_mode = true;
2✔
202
            _addToMask(canvas_coords);
2✔
203
            break;
2✔
204
    }
205
}
206

207
void MediaMask_Widget::_rightClickedInVideo(CanvasCoordinates const & canvas_coords) {
×
208
    if (_active_key.empty() || _selection_mode != Selection_Mode::Brush) {
×
UNCOV
209
        return;
×
210
    }
211

UNCOV
212
    std::cout << "Right clicked in video at canvas (" << canvas_coords.x << ", " << canvas_coords.y << ")" << std::endl;
×
213

214
    _is_dragging = true;
×
215
    _is_adding_mode = false;
×
UNCOV
216
    _removeFromMask(canvas_coords);
×
217
}
218

219
void MediaMask_Widget::_setBrushSize(int size) {
×
220
    if (_selection_mode == Selection_Mode::Brush) {
×
UNCOV
221
        _scene->setHoverCircleRadius(static_cast<double>(size));
×
222
    }
223
    std::cout << "Brush size set to: " << size << std::endl;
×
UNCOV
224
}
×
225

226
void MediaMask_Widget::_toggleShowHoverCircle(bool checked) {
×
227
    if (_selection_mode == Selection_Mode::Brush) {
×
UNCOV
228
        _scene->setShowHoverCircle(checked);
×
229
    }
230
    std::cout << "Show hover circle " << (checked ? "enabled" : "disabled") << std::endl;
×
UNCOV
231
}
×
232

233
void MediaMask_Widget::_toggleShowBoundingBox(bool checked) {
×
234
    if (!_active_key.empty()) {
×
235
        auto mask_opts = _scene->getMaskConfig(_active_key);
×
236
        if (mask_opts.has_value()) {
×
UNCOV
237
            mask_opts.value()->show_bounding_box = checked;
×
238
        }
UNCOV
239
        _scene->UpdateCanvas();
×
240
    }
241
    std::cout << "Show bounding box " << (checked ? "enabled" : "disabled") << std::endl;
×
UNCOV
242
}
×
243

244
void MediaMask_Widget::_toggleShowOutline(bool checked) {
×
245
    if (!_active_key.empty()) {
×
246
        auto mask_opts = _scene->getMaskConfig(_active_key);
×
247
        if (mask_opts.has_value()) {
×
UNCOV
248
            mask_opts.value()->show_outline = checked;
×
249
        }
UNCOV
250
        _scene->UpdateCanvas();
×
251
    }
252
    std::cout << "Show outline " << (checked ? "enabled" : "disabled") << std::endl;
×
UNCOV
253
}
×
254

255
void MediaMask_Widget::_toggleUseAsTransparency(bool checked) {
×
UNCOV
256
    std::cout << "Transparency checkbox toggled: " << (checked ? "enabled" : "disabled") << std::endl;
×
257

258
    if (!_active_key.empty()) {
×
259
        auto mask_opts = _scene->getMaskConfig(_active_key);
×
260
        if (mask_opts.has_value()) {
×
261
            mask_opts.value()->use_as_transparency = checked;
×
UNCOV
262
            std::cout << "Updated mask config for key: " << _active_key << std::endl;
×
263
        } else {
UNCOV
264
            std::cout << "No mask config found for key: " << _active_key << std::endl;
×
265
        }
UNCOV
266
        _scene->UpdateCanvas();
×
267
    } else {
UNCOV
268
        std::cout << "No active key set" << std::endl;
×
269
    }
270
    std::cout << "Use as transparency " << (checked ? "enabled" : "disabled") << std::endl;
×
UNCOV
271
}
×
272

273
void MediaMask_Widget::_setupDilationWidget() {
3✔
274
    // Create the dilation widget and section
275
    _dilation_widget = new MaskDilationWidget(this);
3✔
276
    _dilation_section = new Section(this, "Mask Dilation");
3✔
277
    _dilation_section->setContentLayout(*new QVBoxLayout());
3✔
278
    _dilation_section->layout()->addWidget(_dilation_widget);
3✔
279
    _dilation_section->autoSetContentLayout();
3✔
280

281
    // Connect dilation widget signals
282
    connect(_dilation_widget, &MaskDilationWidget::optionsChanged,
9✔
283
            this, &MediaMask_Widget::_onDilationOptionsChanged);
6✔
284
    connect(_dilation_widget, &MaskDilationWidget::applyRequested,
9✔
285
            this, &MediaMask_Widget::_onDilationApplyRequested);
6✔
286

287
    // Replace the placeholder widget with the dilation section
288
    auto * layout = qobject_cast<QVBoxLayout *>(ui->verticalLayout);
3✔
289
    if (layout) {
3✔
290
        // Find the placeholder widget
291
        for (int i = 0; i < layout->count(); ++i) {
18✔
292
            QLayoutItem * item = layout->itemAt(i);
18✔
293
            if (item && item->widget() && item->widget() == ui->dilation_section_placeholder) {
18✔
294
                // Replace the placeholder with the dilation section
295
                layout->removeWidget(ui->dilation_section_placeholder);
3✔
296
                ui->dilation_section_placeholder->hide();
3✔
297
                layout->insertWidget(i, _dilation_section);
3✔
298
                break;
3✔
299
            }
300
        }
301
    }
302
}
3✔
303

304
void MediaMask_Widget::_onDilationOptionsChanged(MaskDilationOptions const & options) {
×
305
    if (options.active && options.preview) {
×
UNCOV
306
        _applyMaskDilation(options);
×
307
    } else {
UNCOV
308
        _restoreOriginalMaskData();
×
309
    }
UNCOV
310
}
×
311

312
void MediaMask_Widget::_onDilationApplyRequested() {
×
313
    _applyDilationPermanently();
×
UNCOV
314
}
×
315

316
void MediaMask_Widget::_applyMaskDilation(MaskDilationOptions const & options) {
×
317
    if (_active_key.empty()) {
×
UNCOV
318
        return;
×
319
    }
320

321
    auto mask_data = _data_manager->getData<MaskData>(_active_key);
×
322
    if (!mask_data) {
×
UNCOV
323
        return;
×
324
    }
325

326
    // Store original data if not already stored
UNCOV
327
    _storeOriginalMaskData();
×
328

UNCOV
329
    auto const & original_masks = _original_mask_data[_active_key];
×
330

331
    // Apply dilation to each mask at current time
332
    std::vector<std::vector<Point2D<uint32_t>>> dilated_masks;
×
UNCOV
333
    auto image_size = mask_data->getImageSize();
×
334

335
    for (auto const & single_mask: original_masks) {
×
336
        if (!single_mask.empty()) {
×
337
            auto dilated_mask = ImageProcessing::dilate_mask(single_mask, image_size, options);
×
338
            dilated_masks.push_back(dilated_mask);
×
UNCOV
339
        }
×
340
    }
341

342
    // Set preview data in the Media_Window
343
    _scene->setPreviewMaskData(_active_key, dilated_masks, true);
×
UNCOV
344
    _preview_active = true;
×
345

346
    // Update the display by updating the canvas
347
    _scene->UpdateCanvas();
×
UNCOV
348
}
×
349

350
void MediaMask_Widget::_applyDilationPermanently() {
×
351
    if (!_preview_active || _active_key.empty()) {
×
UNCOV
352
        return;
×
353
    }
354

355
    auto mask_data = _data_manager->getData<MaskData>(_active_key);
×
356
    if (!mask_data) {
×
UNCOV
357
        return;
×
358
    }
359

UNCOV
360
    auto preview_masks = _scene->getPreviewMaskData(_active_key);
×
361

UNCOV
362
    auto current_index_and_frame = _data_manager->getCurrentIndexAndFrame(TimeKey("time"));
×
363

364
    // Clear existing masks at this time
UNCOV
365
    mask_data->clearAtTime(current_index_and_frame, false);
×
366

367
    // Add the dilated masks
368
    for (auto const & dilated_mask: preview_masks) {
×
369
        if (!dilated_mask.empty()) {
×
UNCOV
370
            mask_data->addAtTime(current_index_and_frame, dilated_mask, false);
×
371
        }
372
    }
373

374
    // Notify observers
UNCOV
375
    mask_data->notifyObservers();
×
376

377
    // Clear preview data
378
    _scene->setPreviewMaskData(_active_key, {}, false);
×
379
    _preview_active = false;
×
UNCOV
380
    _original_mask_data.clear();
×
381

382
    // Reset the dilation widget
383
    MaskDilationOptions default_options;
×
UNCOV
384
    _dilation_widget->setOptions(default_options);
×
385

386
    std::cout << "Mask dilation applied permanently" << std::endl;
×
UNCOV
387
}
×
388

389
void MediaMask_Widget::_storeOriginalMaskData() {
×
390
    if (_active_key.empty() || _original_mask_data.count(_active_key) > 0) {
×
UNCOV
391
        return;// Already stored or no active key
×
392
    }
393

394
    auto mask_data = _data_manager->getData<MaskData>(_active_key);
×
395
    if (!mask_data) {
×
UNCOV
396
        return;
×
397
    }
398

399
    // Get current time and store the original mask data
400
    auto current_time = _data_manager->getCurrentTime();
×
UNCOV
401
    auto const & masks_at_time = mask_data->getAtTime(TimeFrameIndex(current_time));
×
402

403
    _original_mask_data[_active_key] = masks_at_time;
×
UNCOV
404
}
×
405

406
void MediaMask_Widget::_restoreOriginalMaskData() {
×
407
    if (!_preview_active) {
×
UNCOV
408
        return;
×
409
    }
410

411
    // Clear preview data from Media_Window
412
    _scene->setPreviewMaskData(_active_key, {}, false);
×
UNCOV
413
    _preview_active = false;
×
414

415
    // Update the display
UNCOV
416
    _scene->UpdateCanvas();
×
417
}
418

419
void MediaMask_Widget::_addToMask(CanvasCoordinates const & canvas_coords) {
6✔
420
    auto mask_data = _data_manager->getData<MaskData>(_active_key);
6✔
421
    if (!mask_data) {
6✔
422
        if (_debug_performance) {
×
UNCOV
423
            std::cout << "Error: Could not retrieve mask data for key: " << _active_key << std::endl;
×
424
        }
UNCOV
425
        return;
×
426
    }
427

428
    // Get mask image size for coordinate transformation
429
    auto mask_image_size = mask_data->getImageSize();
6✔
430
    if (mask_image_size.width <= 0 || mask_image_size.height <= 0) {
6✔
431
        if (_debug_performance) {
×
UNCOV
432
            std::cout << "Error: Invalid mask image size" << std::endl;
×
433
        }
UNCOV
434
        return;
×
435
    }
436

437
    // Get canvas size for transformation
438
    auto [canvas_width, canvas_height] = _scene->getCanvasSize();
6✔
439
    if (canvas_width <= 0 || canvas_height <= 0) {
6✔
440
        if (_debug_performance) {
×
UNCOV
441
            std::cout << "Error: Invalid canvas size" << std::endl;
×
442
        }
UNCOV
443
        return;
×
444
    }
445

446
    // Transform canvas coordinates directly to mask coordinates
447
    // This maintains separation of concerns - no need to access media data
448
    float x_mask_raw = (canvas_coords.x / static_cast<float>(canvas_width)) * static_cast<float>(mask_image_size.width);
6✔
449
    float y_mask_raw = (canvas_coords.y / static_cast<float>(canvas_height)) * static_cast<float>(mask_image_size.height);
6✔
450

451
    // Get brush size and scale it separately for each dimension to preserve circle shape
452
    int brush_radius_canvas = _brushSelectionWidget->getBrushSize();
6✔
453
    float scale_x = static_cast<float>(mask_image_size.width) / static_cast<float>(canvas_width);
6✔
454
    float scale_y = static_cast<float>(mask_image_size.height) / static_cast<float>(canvas_height);
6✔
455

456
    // Calculate scaled brush radius for each dimension
457
    float brush_radius_x = static_cast<float>(brush_radius_canvas) * scale_x;
6✔
458
    float brush_radius_y = static_cast<float>(brush_radius_canvas) * scale_y;
6✔
459

460
    // Use the transformed coordinates directly - hover circle is now centered on cursor
461
    float x_mask = x_mask_raw;
6✔
462
    float y_mask = y_mask_raw;
6✔
463

464
    // Generate ellipse pixels using the new general-purpose function
465
    auto brush_pixels = generate_ellipse_pixels(x_mask, y_mask, brush_radius_x, brush_radius_y);
6✔
466

467

468
    auto current_index_and_frame = _data_manager->getCurrentIndexAndFrame(TimeKey("time"));
6✔
469
    auto const & existing_masks = mask_data->getAtTime(current_index_and_frame);
6✔
470

471
    // Get or create the primary mask (index 0)
472
    std::vector<Point2D<uint32_t>> primary_mask;
6✔
473
    if (!existing_masks.empty()) {
6✔
474
        primary_mask = existing_masks[0];// Copy the existing mask at index 0
4✔
475
    }
476
    // If no masks exist, primary_mask starts empty
477

478
    // Create a set of existing pixels in the primary mask for fast lookup
479
    std::set<std::pair<int, int>> existing_pixel_set;
6✔
480
    for (auto const & point: primary_mask) {
1,849✔
481
        existing_pixel_set.insert({static_cast<int>(point.x),
5,529✔
482
                                   static_cast<int>(point.y)});
3,686✔
483
    }
484

485
    // Add new pixels that don't already exist in the primary mask
486
    int added_count = 0;
6✔
487
    for (auto const & pixel: brush_pixels) {
1,962✔
488
        std::pair<int, int> pixel_key = {static_cast<int>(pixel.x),
3,912✔
489
                                         static_cast<int>(pixel.y)};
1,956✔
490
        if (existing_pixel_set.find(pixel_key) == existing_pixel_set.end()) {
1,956✔
491
            primary_mask.push_back(pixel);
1,712✔
492
            existing_pixel_set.insert(pixel_key);// Update set to avoid adding duplicates within this operation
1,712✔
493
            added_count++;
1,712✔
494
        }
495
    }
496

497
    // Only update the mask data if we added new pixels
498
    if (added_count > 0) {
6✔
499
        // Clear all masks at this time
500
        mask_data->clearAtTime(current_index_and_frame, false);
6✔
501
        mask_data->addAtTime(current_index_and_frame, std::move(primary_mask), false);
6✔
502

503
        // Notify observers
504
        mask_data->notifyObservers();
6✔
505
    }
506

507
    if (_debug_performance) {
6✔
508
        std::cout << "BRUSH ADD: Added " << added_count << " new pixels (out of " << brush_pixels.size()
×
509
                  << " generated) to primary mask at index 0. Total mask size: "
×
UNCOV
510
                  << (primary_mask.size() - added_count + added_count) << " pixels" << std::endl;
×
511
    }
512
}
6✔
513

514
void MediaMask_Widget::_removeFromMask(CanvasCoordinates const & canvas_coords) {
×
515
    auto mask_data = _data_manager->getData<MaskData>(_active_key);
×
516
    if (!mask_data) {
×
517
        if (_debug_performance) {
×
UNCOV
518
            std::cout << "Error: Could not retrieve mask data for key: " << _active_key << std::endl;
×
519
        }
UNCOV
520
        return;
×
521
    }
522

523
    // Get mask image size for coordinate transformation
524
    auto mask_image_size = mask_data->getImageSize();
×
525
    if (mask_image_size.width <= 0 || mask_image_size.height <= 0) {
×
526
        if (_debug_performance) {
×
UNCOV
527
            std::cout << "Error: Invalid mask image size" << std::endl;
×
528
        }
UNCOV
529
        return;
×
530
    }
531

532
    // Get canvas size for transformation
533
    auto [canvas_width, canvas_height] = _scene->getCanvasSize();
×
534
    if (canvas_width <= 0 || canvas_height <= 0) {
×
535
        if (_debug_performance) {
×
UNCOV
536
            std::cout << "Error: Invalid canvas size" << std::endl;
×
537
        }
UNCOV
538
        return;
×
539
    }
540

541
    // Transform canvas coordinates directly to mask coordinates
542
    // This maintains separation of concerns - no need to access media data
543
    float x_mask_raw = (canvas_coords.x / static_cast<float>(canvas_width)) * static_cast<float>(mask_image_size.width);
×
UNCOV
544
    float y_mask_raw = (canvas_coords.y / static_cast<float>(canvas_height)) * static_cast<float>(mask_image_size.height);
×
545

546
    // Get brush size and scale it separately for each dimension to preserve circle shape
547
    int brush_radius_canvas = _brushSelectionWidget->getBrushSize();
×
548
    float scale_x = static_cast<float>(mask_image_size.width) / static_cast<float>(canvas_width);
×
UNCOV
549
    float scale_y = static_cast<float>(mask_image_size.height) / static_cast<float>(canvas_height);
×
550

551
    // Calculate scaled brush radius for each dimension
552
    float brush_radius_x = static_cast<float>(brush_radius_canvas) * scale_x;
×
UNCOV
553
    float brush_radius_y = static_cast<float>(brush_radius_canvas) * scale_y;
×
554

555
    // Use the transformed coordinates directly - hover circle is now centered on cursor
556
    float x_mask = x_mask_raw;
×
UNCOV
557
    float y_mask = y_mask_raw;
×
558

559
    // Get current time and existing masks
560
    auto current_index_and_frame = _data_manager->getCurrentIndexAndFrame(TimeKey("time"));
×
UNCOV
561
    auto const & existing_masks = mask_data->getAtTime(current_index_and_frame);
×
562

563
    // Check if there's a primary mask (index 0) to remove from
564
    if (existing_masks.empty()) {
×
565
        if (_debug_performance) {
×
UNCOV
566
            std::cout << "BRUSH REMOVE: No mask exists to remove from" << std::endl;
×
567
        }
UNCOV
568
        return;
×
569
    }
570

571
    // Work with the primary mask (index 0)
572
    auto const & primary_mask = existing_masks[0];
×
573
    std::vector<Point2D<uint32_t>> filtered_mask;
×
UNCOV
574
    int removed_count = 0;
×
575

576
    // Filter out pixels within brush radius from the primary mask
UNCOV
577
    for (auto const & point: primary_mask) {
×
578
        // Calculate elliptical distance from brush center
579
        float dx = point.x - x_mask;
×
580
        float dy = point.y - y_mask;
×
581
        float normalized_dx = dx / brush_radius_x;
×
582
        float normalized_dy = dy / brush_radius_y;
×
UNCOV
583
        float ellipse_distance = normalized_dx * normalized_dx + normalized_dy * normalized_dy;
×
584

585
        // Keep pixels outside brush ellipse
586
        if (ellipse_distance > 1.0f) {
×
UNCOV
587
            filtered_mask.push_back(point);
×
588
        } else {
UNCOV
589
            removed_count++;
×
590
        }
591
    }
592

593
    // Only update if we actually removed pixels
UNCOV
594
    if (removed_count > 0) {
×
595

UNCOV
596
        mask_data->clearAtTime(current_index_and_frame, false);
×
597

598
        // Add the filtered mask back if it still has points OR if empty masks are allowed
599
        if (!filtered_mask.empty() || _allow_empty_mask) {
×
UNCOV
600
            mask_data->addAtTime(current_index_and_frame, std::move(filtered_mask), false);
×
601
        }
602

603
        // Notify observers
UNCOV
604
        mask_data->notifyObservers();
×
605
    }
606

607
    if (_debug_performance) {
×
608
        std::cout << "BRUSH REMOVE: Removed " << removed_count << " pixels from primary mask at index 0. "
×
UNCOV
609
                  << "Remaining mask size: " << filtered_mask.size() << " pixels" << std::endl;
×
610
    }
UNCOV
611
}
×
612

613
void MediaMask_Widget::_mouseMoveInVideo(CanvasCoordinates const & canvas_coords) {
4✔
614
    // Only process mouse move if we're in brush mode and currently dragging
615
    if (_active_key.empty() || _selection_mode != Selection_Mode::Brush || !_is_dragging) {
4✔
UNCOV
616
        return;
×
617
    }
618

619
    // Continue adding or removing based on the mode set when dragging started
620
    if (_is_adding_mode) {
4✔
621
        _addToMask(canvas_coords);
4✔
622
    } else {
UNCOV
623
        _removeFromMask(canvas_coords);
×
624
    }
625
}
626

627
void MediaMask_Widget::_mouseReleased() {
2✔
628
    // Stop dragging when mouse is released
629
    bool was_dragging = _is_dragging;
2✔
630
    _is_dragging = false;
2✔
631

632
    // Update canvas once when brush drag operation is completed
633
    if (_selection_mode == Selection_Mode::Brush && was_dragging) {
2✔
634
        _scene->UpdateCanvas();
2✔
635
        if (_debug_performance) {
2✔
UNCOV
636
            std::cout << "Brush drag operation completed, canvas updated" << std::endl;
×
637
        }
638
    }
639
}
2✔
640

641
void MediaMask_Widget::_onAllowEmptyMaskChanged(bool allow) {
×
642
    _allow_empty_mask = allow;
×
643
    if (_debug_performance) {
×
UNCOV
644
        std::cout << "Allow empty mask setting changed to: " << (allow ? "enabled" : "disabled") << std::endl;
×
645
    }
UNCOV
646
}
×
647

648
void MediaMask_Widget::resizeEvent(QResizeEvent * event) {
2✔
649
    QWidget::resizeEvent(event);
2✔
650

651
    // Ensure the color picker and stacked widget components resize properly
652
    int availableWidth = width() - 20;// Account for margins
2✔
653

654
    // Update color picker width to use available space
655
    ui->color_picker->setFixedWidth(qMin(availableWidth, 400));// Cap at reasonable maximum
2✔
656

657
    // Update stacked widget width
658
    ui->mode_stacked_widget->setFixedWidth(availableWidth);
2✔
659

660
    // Update all widgets in the stacked widget
661
    for (int i = 0; i < ui->mode_stacked_widget->count(); ++i) {
6✔
662
        QWidget * widget = ui->mode_stacked_widget->widget(i);
4✔
663
        if (widget) {
4✔
664
            widget->setFixedWidth(availableWidth);
4✔
665
        }
666
    }
667
}
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc