1 // -*- c-basic-offset: 4 -*-
2
3 /** @file MaskEditorPanel.cpp
4 *
5 * @brief implementation of MaskEditorPanel Class
6 *
7 * @author Thomas Modes
8 *
9 */
10
11 /* This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public
13 * License as published by the Free Software Foundation; either
14 * version 2 of the License, or (at your option) any later version.
15 *
16 * This software is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public
22 * License along with this software. If not, see
23 * <http://www.gnu.org/licenses/>.
24 *
25 */
26
27 #include "panoinc_WX.h"
28 #include "panoinc.h"
29 #include <hugin_utils/stl_utils.h>
30
31 // hugin's
32 #include "base_wx/platform.h"
33 #include "hugin/MainFrame.h"
34 #include "hugin/config_defaults.h"
35 #include "base_wx/CommandHistory.h"
36 #include "base_wx/PanoCommand.h"
37 #include "hugin/MaskEditorPanel.h"
38 #include "hugin/MaskLoadDialog.h"
39 #include <wx/clipbrd.h>
40 #include "hugin/TextKillFocusHandler.h"
41
BEGIN_EVENT_TABLE(MaskEditorPanel,wxPanel)42 BEGIN_EVENT_TABLE(MaskEditorPanel, wxPanel)
43 EVT_LIST_ITEM_SELECTED(XRCID("mask_editor_images_list"), MaskEditorPanel::OnImageSelect)
44 EVT_LIST_ITEM_DESELECTED(XRCID("mask_editor_images_list"), MaskEditorPanel::OnImageSelect)
45 EVT_LIST_ITEM_SELECTED(XRCID("mask_editor_mask_list"), MaskEditorPanel::OnMaskSelect)
46 EVT_LIST_ITEM_DESELECTED(XRCID("mask_editor_mask_list"), MaskEditorPanel::OnMaskSelect)
47 EVT_LIST_COL_END_DRAG(XRCID("mask_editor_mask_list"), MaskEditorPanel::OnColumnWidthChange)
48 EVT_CHOICE(XRCID("mask_editor_choice_zoom"), MaskEditorPanel::OnZoom)
49 EVT_CHOICE(XRCID("mask_editor_choice_masktype"), MaskEditorPanel::OnMaskTypeChange)
50 EVT_BUTTON(XRCID("mask_editor_add"), MaskEditorPanel::OnMaskAdd)
51 EVT_BUTTON(XRCID("mask_editor_load"), MaskEditorPanel::OnMaskLoad)
52 EVT_BUTTON(XRCID("mask_editor_save"), MaskEditorPanel::OnMaskSave)
53 EVT_BUTTON(XRCID("mask_editor_copy"), MaskEditorPanel::OnMaskCopy)
54 EVT_BUTTON(XRCID("mask_editor_paste"), MaskEditorPanel::OnMaskPaste)
55 EVT_BUTTON(XRCID("mask_editor_delete"), MaskEditorPanel::OnMaskDelete)
56 EVT_CHECKBOX(XRCID("mask_editor_show_active_masks"), MaskEditorPanel::OnShowActiveMasks)
57 EVT_COLOURPICKER_CHANGED(XRCID("mask_editor_colour_polygon_negative"),MaskEditorPanel::OnColourChanged)
58 EVT_COLOURPICKER_CHANGED(XRCID("mask_editor_colour_polygon_positive"),MaskEditorPanel::OnColourChanged)
59 EVT_COLOURPICKER_CHANGED(XRCID("mask_editor_colour_point_selected"),MaskEditorPanel::OnColourChanged)
60 EVT_COLOURPICKER_CHANGED(XRCID("mask_editor_colour_point_unselected"),MaskEditorPanel::OnColourChanged)
61 EVT_TEXT_ENTER (XRCID("crop_left_text") ,MaskEditorPanel::OnSetLeft )
62 EVT_TEXT_ENTER (XRCID("crop_right_text") ,MaskEditorPanel::OnSetRight )
63 EVT_TEXT_ENTER (XRCID("crop_top_text") ,MaskEditorPanel::OnSetTop )
64 EVT_TEXT_ENTER (XRCID("crop_bottom_text") ,MaskEditorPanel::OnSetBottom )
65 EVT_BUTTON ( XRCID("crop_reset_button") , MaskEditorPanel::OnResetButton )
66 EVT_CHECKBOX( XRCID("crop_autocenter_cb") , MaskEditorPanel::OnAutoCenter)
67 EVT_NOTEBOOK_PAGE_CHANGED(XRCID("mask_editor_mask_crop_notebook"), MaskEditorPanel::OnModeChanged)
68 END_EVENT_TABLE()
69
70 MaskEditorPanel::MaskEditorPanel()
71 {
72 DEBUG_TRACE("**********************");
73 m_pano = 0;
74 m_maskCropCtrl=NULL;
75 m_defaultMaskType=HuginBase::MaskPolygon::Mask_negative;
76 }
77
Create(wxWindow * parent,wxWindowID id,const wxPoint & pos,const wxSize & size,long style,const wxString & name)78 bool MaskEditorPanel::Create(wxWindow* parent, wxWindowID id,
79 const wxPoint& pos,
80 const wxSize& size,
81 long style,
82 const wxString& name)
83 {
84 DEBUG_TRACE(" Create called *************");
85 if (! wxPanel::Create(parent, id, pos, size, style, name))
86 {
87 return false;
88 }
89
90 m_selectedImages.clear();
91 m_MaskNr=UINT_MAX;
92 m_File="";
93
94 wxXmlResource::Get()->LoadPanel(this, wxT("mask_panel"));
95 wxPanel * panel = XRCCTRL(*this, "mask_panel", wxPanel);
96
97 wxBoxSizer *topsizer = new wxBoxSizer( wxVERTICAL );
98 topsizer->Add(panel, 1, wxEXPAND, 0);
99 SetSizer(topsizer);
100
101 m_editImg = XRCCTRL(*this, "mask_editor_polygon_editor", MaskImageCtrl);
102 assert(m_editImg);
103 m_editImg->Init(this);
104
105 // images list
106 m_imagesListMask = XRCCTRL(*this, "mask_editor_images_list", ImagesListMask);
107 // mask list
108 m_maskList = XRCCTRL(*this, "mask_editor_mask_list", wxListCtrl);
109 m_maskList->InsertColumn( 0, wxT("#"), wxLIST_FORMAT_RIGHT, 35);
110 m_maskList->InsertColumn( 1, _("Mask type"), wxLIST_FORMAT_LEFT, 120);
111
112 m_maskCropCtrl = XRCCTRL(*this, "mask_editor_mask_crop_notebook", wxNotebook);
113 DEBUG_ASSERT(m_maskCropCtrl);
114 m_maskCropCtrl->SetSelection(0);
115 m_maskMode=true;
116
117 //get saved width
118 wxConfigBase *config=wxConfigBase::Get();
119 for ( int j=0; j < m_maskList->GetColumnCount() ; j++ )
120 {
121 // -1 is auto
122 int width = config->Read(wxString::Format( wxT("/MaskEditorPanel/ColumnWidth%d"), j ), -1);
123 if(width != -1)
124 m_maskList->SetColumnWidth(j, width);
125 }
126 bool activeMasks;
127 config->Read(wxT("/MaskEditorPanel/ShowActiveMasks"),&activeMasks,false);
128 XRCCTRL(*this,"mask_editor_show_active_masks",wxCheckBox)->SetValue(activeMasks);
129 m_editImg->setDrawingActiveMasks(activeMasks);
130
131 //load and set colours
132 wxColour defaultColour;
133 defaultColour.Set(wxT(HUGIN_MASK_COLOUR_POLYGON_NEGATIVE));
134 wxColour colour = config->Read(wxT("/MaskEditorPanel/ColourPolygonNegative"), defaultColour.GetAsString(wxC2S_HTML_SYNTAX));
135 XRCCTRL(*this,"mask_editor_colour_polygon_negative",wxColourPickerCtrl)->SetColour(colour);
136 m_editImg->SetUserColourPolygonNegative(colour);
137 defaultColour.Set(wxT(HUGIN_MASK_COLOUR_POLYGON_POSITIVE));
138 colour = config->Read(wxT("/MaskEditorPanel/ColourPolygonPositive"), defaultColour.GetAsString(wxC2S_HTML_SYNTAX));
139 XRCCTRL(*this,"mask_editor_colour_polygon_positive",wxColourPickerCtrl)->SetColour(colour);
140 m_editImg->SetUserColourPolygonPositive(colour);
141 defaultColour.Set(wxT(HUGIN_MASK_COLOUR_POINT_SELECTED));
142 colour = config->Read(wxT("/MaskEditorPanel/ColourPointSelected"), defaultColour.GetAsString(wxC2S_HTML_SYNTAX));
143 XRCCTRL(*this,"mask_editor_colour_point_selected",wxColourPickerCtrl)->SetColour(colour);
144 m_editImg->SetUserColourPointSelected(colour);
145 defaultColour.Set(wxT(HUGIN_MASK_COLOUR_POINT_UNSELECTED));
146 colour = config->Read(wxT("/MaskEditorPanel/ColourPointUnselected"), defaultColour.GetAsString(wxC2S_HTML_SYNTAX));
147 XRCCTRL(*this,"mask_editor_colour_point_unselected",wxColourPickerCtrl)->SetColour(colour);
148 m_editImg->SetUserColourPointUnselected(colour);
149
150 // other controls
151 m_maskType = XRCCTRL(*this, "mask_editor_choice_masktype", wxChoice);
152 m_defaultMaskType=(HuginBase::MaskPolygon::MaskType)config->Read(wxT("/MaskEditorPanel/DefaultMaskType"), 0l);
153 m_maskType->SetSelection((int)m_defaultMaskType);
154 // disable some controls
155 m_maskType->Disable();
156 XRCCTRL(*this, "mask_editor_choice_zoom", wxChoice)->Disable();
157 XRCCTRL(*this, "mask_editor_add", wxButton)->Disable();
158 XRCCTRL(*this, "mask_editor_load", wxButton)->Disable();
159 XRCCTRL(*this, "mask_editor_save", wxButton)->Disable();
160 XRCCTRL(*this, "mask_editor_copy", wxButton)->Disable();
161 XRCCTRL(*this, "mask_editor_paste", wxButton)->Disable();
162 XRCCTRL(*this, "mask_editor_delete", wxButton)->Disable();
163
164 m_left_textctrl = XRCCTRL(*this,"crop_left_text", wxTextCtrl);
165 DEBUG_ASSERT(m_left_textctrl);
166 m_left_textctrl->PushEventHandler(new TextKillFocusHandler(this));
167
168 m_cropLens = XRCCTRL(*this, "crop_all_images_lens", wxCheckBox);
169 DEBUG_ASSERT(m_cropLens);
170 bool doCropImagesLens;
171 config->Read(wxT("/MaskEditorPanel/CropImagesLens"), &doCropImagesLens, true);
172 m_cropLens->SetValue(doCropImagesLens);
173 m_top_textctrl = XRCCTRL(*this,"crop_top_text", wxTextCtrl);
174 DEBUG_ASSERT(m_top_textctrl);
175 m_top_textctrl->PushEventHandler(new TextKillFocusHandler(this));
176
177 m_right_textctrl = XRCCTRL(*this,"crop_right_text", wxTextCtrl);
178 DEBUG_ASSERT(m_right_textctrl);
179 m_right_textctrl->PushEventHandler(new TextKillFocusHandler(this));
180
181 m_bottom_textctrl = XRCCTRL(*this,"crop_bottom_text", wxTextCtrl);
182 DEBUG_ASSERT(m_bottom_textctrl);
183 m_bottom_textctrl->PushEventHandler(new TextKillFocusHandler(this));
184
185 m_autocenter_cb = XRCCTRL(*this,"crop_autocenter_cb", wxCheckBox);
186 DEBUG_ASSERT(m_autocenter_cb);
187
188 //set shortcuts
189 wxAcceleratorEntry entries[2];
190 entries[0].Set(wxACCEL_CMD,(int)'C',XRCID("mask_editor_copy"));
191 entries[1].Set(wxACCEL_CMD,(int)'V',XRCID("mask_editor_paste"));
192 wxAcceleratorTable accel(2, entries);
193 SetAcceleratorTable(accel);
194
195 // apply zoom specified in xrc file
196 wxCommandEvent dummy;
197 dummy.SetInt(XRCCTRL(*this,"mask_editor_choice_zoom",wxChoice)->GetSelection());
198 OnZoom(dummy);
199 return true;
200 }
201
Init(HuginBase::Panorama * pano)202 void MaskEditorPanel::Init(HuginBase::Panorama * pano)
203 {
204 m_pano=pano;
205 m_imagesListMask->Init(m_pano);
206 m_imageGroups = new HuginBase::ConstStandardImageVariableGroups(*m_pano);
207 // observe the panorama
208 m_pano->addObserver(this);
209 }
210
~MaskEditorPanel()211 MaskEditorPanel::~MaskEditorPanel()
212 {
213 m_left_textctrl->PopEventHandler(true);
214 m_right_textctrl->PopEventHandler(true);
215 m_top_textctrl->PopEventHandler(true);
216 m_bottom_textctrl->PopEventHandler(true);
217 wxConfigBase* config = wxConfigBase::Get();
218 config->Write(wxT("/MaskEditorPanel/ShowActiveMasks"),XRCCTRL(*this,"mask_editor_show_active_masks",wxCheckBox)->GetValue());
219 config->Write(wxT("/MaskEditorPanel/DefaultMaskType"),(long)m_defaultMaskType);
220 config->Write(wxT("/MaskEditorPanel/CropImagesLens"), m_cropLens->GetValue());
221 config->Flush();
222
223 DEBUG_TRACE("dtor");
224 if (m_imageGroups)
225 {
226 delete m_imageGroups;
227 }
228 m_pano->removeObserver(this);
229 }
230
GetImgNr()231 size_t MaskEditorPanel::GetImgNr()
232 {
233 if(m_selectedImages.empty())
234 {
235 return UINT_MAX;
236 }
237 else
238 {
239 return *(m_selectedImages.begin());
240 };
241 };
242
setImage(unsigned int imgNr,bool updateListSelection)243 void MaskEditorPanel::setImage(unsigned int imgNr, bool updateListSelection)
244 {
245 DEBUG_TRACE("image " << imgNr);
246 bool restoreMaskSelection=(imgNr==GetImgNr());
247 bool updateImage=true;
248 if(imgNr==UINT_MAX)
249 {
250 m_selectedImages.clear();
251 }
252 else
253 {
254 m_selectedImages.insert(imgNr);
255 };
256 HuginBase::MaskPolygonVector masksToDraw;
257 if (imgNr == UINT_MAX)
258 {
259 m_File = "";
260 HuginBase::MaskPolygonVector mask;
261 m_currentMasks=mask;
262 m_editImg->setCrop(HuginBase::SrcPanoImage::NO_CROP,vigra::Rect2D(), false, hugin_utils::FDiff2D(), false);
263 }
264 else
265 {
266 const HuginBase::SrcPanoImage& image=m_pano->getImage(imgNr);
267 updateImage=(m_File!=image.getFilename());
268 if(updateImage)
269 m_File=image.getFilename();
270 else
271 if(GetRot(imgNr)!=m_editImg->getCurrentRotation())
272 {
273 updateImage=true;
274 m_File=image.getFilename();
275 };
276 m_currentMasks=image.getMasks();
277 masksToDraw=image.getActiveMasks();
278 m_editImg->setCrop(image.getCropMode(),image.getCropRect(), image.getAutoCenterCrop(), image.getRadialDistortionCenter(), image.isCircularCrop());
279 };
280 // update mask editor
281 if(updateImage)
282 m_editImg->setImage(m_File,m_currentMasks,masksToDraw,GetRot(imgNr));
283 else
284 m_editImg->setNewMasks(m_currentMasks,masksToDraw);
285 if (m_currentMasks.empty() || m_MaskNr >= m_currentMasks.size())
286 setMask(UINT_MAX);
287 // enables or disables controls
288 bool enableCtrl=(imgNr<UINT_MAX);
289 XRCCTRL(*this, "mask_editor_choice_zoom", wxChoice)->Enable(enableCtrl);
290 XRCCTRL(*this, "mask_editor_add", wxButton)->Enable(enableCtrl);
291 XRCCTRL(*this, "mask_editor_delete", wxButton)->Enable(enableCtrl && m_MaskNr<UINT_MAX);
292 XRCCTRL(*this, "mask_editor_load", wxButton)->Enable(enableCtrl);
293 XRCCTRL(*this, "mask_editor_save", wxButton)->Enable(enableCtrl && m_MaskNr<UINT_MAX);
294 XRCCTRL(*this, "mask_editor_paste", wxButton)->Enable(enableCtrl);
295 XRCCTRL(*this, "mask_editor_copy", wxButton)->Enable(enableCtrl && m_MaskNr<UINT_MAX);
296 UpdateMaskList(restoreMaskSelection);
297 // FIXME: lets hope that nobody holds references to these images..
298 ImageCache::getInstance().softFlush();
299 if(updateListSelection)
300 {
301 m_imagesListMask->SelectSingleImage(imgNr);
302 };
303 }
304
setMask(unsigned int maskNr)305 void MaskEditorPanel::setMask(unsigned int maskNr)
306 {
307 m_MaskNr=maskNr;
308 m_maskType->Enable(m_MaskNr<UINT_MAX);
309 m_editImg->setActiveMask(m_MaskNr);
310 XRCCTRL(*this,"mask_editor_delete", wxButton)->Enable(m_MaskNr<UINT_MAX);
311 XRCCTRL(*this, "mask_editor_save", wxButton)->Enable(m_MaskNr<UINT_MAX);
312 XRCCTRL(*this, "mask_editor_copy", wxButton)->Enable(m_MaskNr<UINT_MAX);
313 if(GetImgNr()<UINT_MAX && m_MaskNr<UINT_MAX)
314 m_maskType->SetSelection(m_currentMasks[m_MaskNr].getMaskType());
315 else
316 m_maskType->SetSelection((int)m_defaultMaskType);
317 };
318
UpdateMask()319 void MaskEditorPanel::UpdateMask()
320 {
321 if(GetImgNr()<UINT_MAX)
322 {
323 m_currentMasks=m_editImg->getNewMask();
324 PanoCommand::GlobalCmdHist::getInstance().addCommand(new PanoCommand::UpdateMaskForImgCmd(*m_pano, GetImgNr(), m_currentMasks));
325 };
326 };
327
AddMask()328 void MaskEditorPanel::AddMask()
329 {
330 if(GetImgNr()<UINT_MAX)
331 {
332 m_currentMasks=m_editImg->getNewMask();
333 m_currentMasks[m_currentMasks.size()-1].setMaskType(m_defaultMaskType);
334 PanoCommand::GlobalCmdHist::getInstance().addCommand(new PanoCommand::UpdateMaskForImgCmd(*m_pano, GetImgNr(), m_currentMasks));
335 //select added mask
336 SelectMask(m_currentMasks.size()-1);
337 m_editImg->selectAllMarkers();
338 };
339 };
340
SelectMask(unsigned int newMaskNr)341 void MaskEditorPanel::SelectMask(unsigned int newMaskNr)
342 {
343 if (GetImgNr() < UINT_MAX)
344 {
345 if (newMaskNr < m_currentMasks.size())
346 {
347 m_maskList->SetItemState(newMaskNr, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
348 }
349 else
350 {
351 if (m_MaskNr < UINT_MAX)
352 {
353 m_maskList->SetItemState(m_MaskNr, 0, wxLIST_STATE_SELECTED);
354 };
355 };
356 };
357 };
358
panoramaChanged(HuginBase::Panorama & pano)359 void MaskEditorPanel::panoramaChanged(HuginBase::Panorama &pano)
360 {
361 };
362
panoramaImagesChanged(HuginBase::Panorama & pano,const HuginBase::UIntSet & changed)363 void MaskEditorPanel::panoramaImagesChanged(HuginBase::Panorama &pano, const HuginBase::UIntSet &changed)
364 {
365 unsigned int nrImages = pano.getNrOfImages();
366 m_imageGroups->update();
367 ImageCache::getInstance().softFlush();
368 if (nrImages==0)
369 setImage(UINT_MAX);
370 else
371 {
372 // select some other image if we deleted the current image
373 if ((GetImgNr() < UINT_MAX) && (GetImgNr() >= nrImages))
374 {
375 for (auto i : m_selectedImages)
376 {
377 if (i >= nrImages)
378 {
379 m_selectedImages.erase(i);
380 };
381 };
382 setImage(nrImages - 1);
383 }
384 else
385 // update changed images
386 if(set_contains(changed,GetImgNr()))
387 {
388 unsigned int countOldMasks=m_currentMasks.size();
389 setImage(GetImgNr());
390 if(countOldMasks!=pano.getImage(GetImgNr()).getMasks().size())
391 SelectMask(UINT_MAX);
392 };
393 };
394
395 if (!m_selectedImages.empty())
396 {
397 if (set_contains(changed, GetImgNr()))
398 {
399 DisplayCrop(GetImgNr());
400 }
401 }
402 else
403 {
404 UpdateCropDisplay();
405 }
406
407 }
408
OnImageSelect(wxListEvent & e)409 void MaskEditorPanel::OnImageSelect(wxListEvent &e)
410 {
411 m_selectedImages=m_imagesListMask->GetSelected();
412 //select no mask
413 setMask(UINT_MAX);
414 setImage(GetImgNr());
415
416 const bool hasImage = !m_selectedImages.empty();
417 m_left_textctrl->Enable(hasImage);
418 m_top_textctrl->Enable(hasImage);
419 m_bottom_textctrl->Enable(hasImage);
420 m_right_textctrl->Enable(hasImage);
421 if (hasImage)
422 {
423 // show first image.
424 DisplayCrop(GetImgNr());
425 };
426 };
427
OnMaskSelect(wxListEvent & e)428 void MaskEditorPanel::OnMaskSelect(wxListEvent &e)
429 {
430 setMask(GetSelectedMask());
431 };
432
OnMaskTypeChange(wxCommandEvent & e)433 void MaskEditorPanel::OnMaskTypeChange(wxCommandEvent &e)
434 {
435 if(GetImgNr()<UINT_MAX && m_MaskNr<UINT_MAX)
436 {
437 m_currentMasks[m_MaskNr].setMaskType((HuginBase::MaskPolygon::MaskType)e.GetSelection());
438 m_defaultMaskType=(HuginBase::MaskPolygon::MaskType)e.GetSelection();
439 PanoCommand::GlobalCmdHist::getInstance().addCommand(new PanoCommand::UpdateMaskForImgCmd(*m_pano, GetImgNr(), m_currentMasks));
440 };
441 };
442
OnMaskAdd(wxCommandEvent & e)443 void MaskEditorPanel::OnMaskAdd(wxCommandEvent &e)
444 {
445 if(GetImgNr()<UINT_MAX)
446 {
447 //deselect current selected mask
448 if(m_MaskNr<UINT_MAX)
449 m_maskList->SetItemState(m_MaskNr,0,wxLIST_STATE_SELECTED);
450 setMask(UINT_MAX);
451 MainFrame::Get()->SetStatusText(_("Create a polygon mask by clicking with the left mouse button on image, set the last point with the right mouse button."),0);
452 m_editImg->startNewPolygon();
453 };
454 };
455
OnMaskSave(wxCommandEvent & e)456 void MaskEditorPanel::OnMaskSave(wxCommandEvent &e)
457 {
458 if(GetImgNr()<UINT_MAX && m_MaskNr<UINT_MAX)
459 {
460 wxFileDialog dlg(this, _("Save mask"),
461 wxConfigBase::Get()->Read(wxT("/actualPath"), wxT("")),
462 wxT(""), _("Mask files (*.msk)|*.msk|All files (*)|*"),
463 wxFD_SAVE | wxFD_OVERWRITE_PROMPT, wxDefaultPosition);
464 if (dlg.ShowModal() == wxID_OK)
465 {
466 wxString fn = dlg.GetPath();
467 if (fn.Right(4) != wxT(".msk"))
468 {
469 fn.Append(wxT(".msk"));
470 if (wxFile::Exists(fn))
471 {
472 int d = wxMessageBox(wxString::Format(_("File %s exists. Overwrite?"),
473 fn.c_str()), _("Save mask"),
474 wxYES_NO | wxICON_QUESTION);
475 if (d != wxYES) {
476 return;
477 }
478 }
479 };
480 wxFileName filename = fn;
481 std::ofstream maskFile(filename.GetFullPath().mb_str(HUGIN_CONV_FILENAME));
482 SaveMaskToStream(maskFile, m_pano->getImage(GetImgNr()).getSize(), m_currentMasks[m_MaskNr], GetImgNr());
483 maskFile.close();
484 };
485 }
486 };
487
OnMaskLoad(wxCommandEvent & e)488 void MaskEditorPanel::OnMaskLoad(wxCommandEvent &e)
489 {
490 if (GetImgNr()<UINT_MAX)
491 {
492 wxFileDialog dlg(this,_("Load mask"),
493 wxConfigBase::Get()->Read(wxT("/actualPath"),wxT("")),
494 wxT(""),_("Mask files (*.msk)|*.msk|All files (*)|*"),
495 wxFD_OPEN, wxDefaultPosition);
496 if (dlg.ShowModal() != wxID_OK)
497 {
498 MainFrame::Get()->SetStatusText(_("Load mask: cancel"));
499 return;
500 }
501 wxFileName filename(dlg.GetPath());
502 std::ifstream in(filename.GetFullPath().mb_str(HUGIN_CONV_FILENAME));
503 vigra::Size2D maskImageSize;
504 HuginBase::MaskPolygonVector loadedMasks;
505 LoadMaskFromStream(in, maskImageSize, loadedMasks, GetImgNr());
506 in.close();
507 if(maskImageSize.area()==0 || loadedMasks.empty())
508 {
509 wxMessageBox(wxString::Format(_("Could not parse mask from file %s."),dlg.GetPath().c_str()),_("Warning"),wxOK | wxICON_EXCLAMATION,this);
510 return;
511 };
512 // compare image size from file with that of current image alert user
513 // if different.
514 if (maskImageSize != m_pano->getImage(GetImgNr()).getSize())
515 {
516 MaskLoadDialog dlg(this);
517 dlg.initValues(m_pano->getImage(GetImgNr()),loadedMasks,maskImageSize);
518 if(dlg.ShowModal()!=wxID_OK)
519 {
520 // abort
521 return;
522 }
523 loadedMasks=dlg.getProcessedMask();
524 }
525 for(unsigned int i=0;i<loadedMasks.size();i++)
526 m_currentMasks.push_back(loadedMasks[i]);
527 // Update the pano with the imported masks
528 PanoCommand::GlobalCmdHist::getInstance().addCommand(new PanoCommand::UpdateMaskForImgCmd(*m_pano, GetImgNr(), m_currentMasks));
529 }
530 };
531
OnMaskCopy(wxCommandEvent & e)532 void MaskEditorPanel::OnMaskCopy(wxCommandEvent &e)
533 {
534 if(GetImgNr()<UINT_MAX && m_MaskNr<UINT_MAX && m_maskMode)
535 {
536 std::ostringstream stream;
537 SaveMaskToStream(stream, m_pano->getImage(GetImgNr()).getSize(), m_currentMasks[m_MaskNr], GetImgNr());
538 if (wxTheClipboard->Open())
539 {
540 // This data objects are held by the clipboard,
541 // so do not delete them in the app.
542 wxTheClipboard->SetData(new wxTextDataObject(wxString(stream.str().c_str(),wxConvLocal)));
543 wxTheClipboard->Close();
544 };
545 };
546 };
547
OnMaskPaste(wxCommandEvent & e)548 void MaskEditorPanel::OnMaskPaste(wxCommandEvent &e)
549 {
550 if(GetImgNr()<UINT_MAX && m_maskMode)
551 {
552 if (wxTheClipboard->Open())
553 {
554 vigra::Size2D maskImageSize;
555 HuginBase::MaskPolygonVector loadedMasks;
556 if (wxTheClipboard->IsSupported( wxDF_TEXT ))
557 {
558 wxTextDataObject data;
559 wxTheClipboard->GetData(data);
560 std::istringstream stream(std::string(data.GetText().mb_str()));
561 LoadMaskFromStream(stream, maskImageSize, loadedMasks, GetImgNr());
562 }
563 wxTheClipboard->Close();
564 if(maskImageSize.area()==0 || loadedMasks.empty())
565 {
566 wxBell();
567 return;
568 };
569 // compare image size from file with that of current image alert user
570 // if different.
571 if (maskImageSize != m_pano->getImage(GetImgNr()).getSize())
572 {
573 MaskLoadDialog dlg(this);
574 dlg.initValues(m_pano->getImage(GetImgNr()),loadedMasks,maskImageSize);
575 if(dlg.ShowModal()!=wxID_OK)
576 {
577 // abort
578 return;
579 }
580 loadedMasks=dlg.getProcessedMask();
581 }
582 for(unsigned int i=0;i<loadedMasks.size();i++)
583 m_currentMasks.push_back(loadedMasks[i]);
584 // Update the pano with the imported masks
585 PanoCommand::GlobalCmdHist::getInstance().addCommand(new PanoCommand::UpdateMaskForImgCmd(*m_pano, GetImgNr(), m_currentMasks));
586 };
587 };
588 };
589
OnMaskDelete(wxCommandEvent & e)590 void MaskEditorPanel::OnMaskDelete(wxCommandEvent &e)
591 {
592 if(GetImgNr()<UINT_MAX && m_MaskNr<UINT_MAX)
593 {
594 HuginBase::MaskPolygonVector editedMasks=m_currentMasks;
595 editedMasks.erase(editedMasks.begin()+m_MaskNr);
596 //setMask(UINT_MAX);
597 PanoCommand::GlobalCmdHist::getInstance().addCommand(new PanoCommand::UpdateMaskForImgCmd(*m_pano, GetImgNr(), editedMasks));
598 };
599 };
600
OnZoom(wxCommandEvent & e)601 void MaskEditorPanel::OnZoom(wxCommandEvent & e)
602 {
603 int posX = 0;
604 int posY = 0;
605 const wxSize ctrlSize = m_editImg->GetClientSize();
606 if (m_editImg->getScale() > 0)
607 {
608 // remember old scroll position
609 posX = (m_editImg->GetScrollPos(wxHORIZONTAL) + ctrlSize.GetWidth() / 2) / m_editImg->getScale();
610 posY = (m_editImg->GetScrollPos(wxVERTICAL) + ctrlSize.GetHeight() / 2) / m_editImg->getScale();
611 };
612 double factor;
613 switch (e.GetSelection())
614 {
615 case 0:
616 factor = 1;
617 break;
618 case 1:
619 // fit to window
620 factor = 0;
621 break;
622 case 2:
623 factor = 2;
624 break;
625 case 3:
626 factor = 1.5;
627 break;
628 case 4:
629 factor = 0.75;
630 break;
631 case 5:
632 factor = 0.5;
633 break;
634 case 6:
635 factor = 0.25;
636 break;
637 default:
638 DEBUG_ERROR("unknown scale factor");
639 factor = 1;
640 }
641 m_editImg->setScale(factor);
642 if (factor > 0)
643 {
644 m_editImg->Scroll(posX*factor - ctrlSize.GetWidth() / 2, posY*factor - ctrlSize.GetHeight() / 2);
645 };
646 if (e.GetString() == "update_selection")
647 {
648 XRCCTRL(*this, "mask_editor_choice_zoom", wxChoice)->SetSelection(e.GetSelection());
649 };
650 }
651
OnColourChanged(wxColourPickerEvent & e)652 void MaskEditorPanel::OnColourChanged(wxColourPickerEvent &e)
653 {
654 if(e.GetId()==XRCID("mask_editor_colour_polygon_negative"))
655 {
656 m_editImg->SetUserColourPolygonNegative(e.GetColour());
657 wxConfigBase::Get()->Write(wxT("/MaskEditorPanel/ColourPolygonNegative"),e.GetColour().GetAsString(wxC2S_HTML_SYNTAX));
658 }
659 else
660 if(e.GetId()==XRCID("mask_editor_colour_polygon_positive"))
661 {
662 m_editImg->SetUserColourPolygonPositive(e.GetColour());
663 wxConfigBase::Get()->Write(wxT("/MaskEditorPanel/ColourPolygonPositive"),e.GetColour().GetAsString(wxC2S_HTML_SYNTAX));
664 }
665 else
666 if(e.GetId()==XRCID("mask_editor_colour_point_selected"))
667 {
668 m_editImg->SetUserColourPointSelected(e.GetColour());
669 wxConfigBase::Get()->Write(wxT("/MaskEditorPanel/ColourPointSelected"),e.GetColour().GetAsString(wxC2S_HTML_SYNTAX));
670 }
671 else
672 {
673 m_editImg->SetUserColourPointUnselected(e.GetColour());
674 wxConfigBase::Get()->Write(wxT("/MaskEditorPanel/ColourPointUnselected"),e.GetColour().GetAsString(wxC2S_HTML_SYNTAX));
675 }
676 m_editImg->Refresh(true);
677 };
678
UpdateMaskList(bool restoreSelection)679 void MaskEditorPanel::UpdateMaskList(bool restoreSelection)
680 {
681 unsigned int oldSelection=GetSelectedMask();
682 m_maskList->Freeze();
683 if(GetImgNr()<UINT_MAX)
684 {
685 if(!m_currentMasks.empty())
686 {
687 if(m_maskList->GetItemCount()!=m_currentMasks.size())
688 {
689 if(m_maskList->GetItemCount()<(int)m_currentMasks.size())
690 {
691 //added masks
692 for(int i=m_maskList->GetItemCount();i<(int)m_currentMasks.size();i++)
693 m_maskList->InsertItem(i,wxString::Format(wxT("%d"),i));
694 }
695 else
696 {
697 //deleted masks
698 for(int i=m_maskList->GetItemCount()-1;i>=(int)m_currentMasks.size();i--)
699 m_maskList->DeleteItem(i);
700 };
701 };
702 for(unsigned int i=0;i<m_currentMasks.size();i++)
703 {
704 m_maskList->SetItem(i,1,m_maskType->GetString(m_currentMasks[i].getMaskType()));
705 if(!restoreSelection && i==oldSelection)
706 m_maskList->SetItemState(i,0, wxLIST_STATE_SELECTED);
707 };
708 }
709 else
710 m_maskList->DeleteAllItems();
711 }
712 else
713 m_maskList->DeleteAllItems();
714 m_maskList->Thaw();
715 m_maskType->Enable(m_maskList->GetSelectedItemCount()>0);
716 }
717
GetSelectedMask()718 unsigned int MaskEditorPanel::GetSelectedMask()
719 {
720 for(unsigned int i=0;i<(unsigned int)m_maskList->GetItemCount();i++)
721 {
722 if(m_maskList->GetItemState(i,wxLIST_STATE_SELECTED) & wxLIST_STATE_SELECTED)
723 return i;
724 };
725 return UINT_MAX;
726 };
727
OnColumnWidthChange(wxListEvent & e)728 void MaskEditorPanel::OnColumnWidthChange( wxListEvent & e )
729 {
730 int colNum = e.GetColumn();
731 wxConfigBase::Get()->Write( wxString::Format(wxT("/MaskEditorPanel/ColumnWidth%d"),colNum), m_maskList->GetColumnWidth(colNum) );
732 }
733
GetRot(const unsigned int imgNr)734 MaskImageCtrl::ImageRotation MaskEditorPanel::GetRot(const unsigned int imgNr)
735 {
736 if(imgNr==UINT_MAX)
737 return MaskImageCtrl::ROT0;
738
739 double pitch=m_pano->getImage(imgNr).getPitch();
740 double roll=m_pano->getImage(imgNr).getRoll();
741
742 MaskImageCtrl::ImageRotation rot = MaskImageCtrl::ROT0;
743 // normalize roll angle
744 while (roll > 360) roll-= 360;
745 while (roll < 0) roll += 360;
746
747 while (pitch > 180) pitch -= 360;
748 while (pitch < -180) pitch += 360;
749 const bool headOver = (pitch > 90 || pitch < -90);
750
751 if (wxConfig::Get()->Read("/CPEditorPanel/AutoRot", 1L))
752 {
753 if (roll >= 315 || roll < 45)
754 {
755 rot = headOver ? MaskImageCtrl::ROT180 : MaskImageCtrl::ROT0;
756 }
757 else
758 {
759 if (roll >= 45 && roll < 135)
760 {
761 rot = headOver ? MaskImageCtrl::ROT270 : MaskImageCtrl::ROT90;
762 }
763 else
764 {
765 if (roll >= 135 && roll < 225)
766 {
767 rot = headOver ? MaskImageCtrl::ROT0 : MaskImageCtrl::ROT180;
768 }
769 else
770 {
771 rot = headOver ? MaskImageCtrl::ROT90 : MaskImageCtrl::ROT270;
772 };
773 };
774 };
775 };
776 return rot;
777 }
778
OnShowActiveMasks(wxCommandEvent & e)779 void MaskEditorPanel::OnShowActiveMasks(wxCommandEvent &e)
780 {
781 m_editImg->setDrawingActiveMasks(e.IsChecked());
782 };
783
DisplayCrop(int imgNr)784 void MaskEditorPanel::DisplayCrop(int imgNr)
785 {
786 const HuginBase::SrcPanoImage & img = m_pano->getImage(imgNr);
787 m_cropMode=img.getCropMode();
788 m_cropRect=img.getCropRect();
789 m_autoCenterCrop=img.getAutoCenterCrop();
790
791 int dx = hugin_utils::roundi(img.getRadialDistortionCenterShift().x);
792 int dy = hugin_utils::roundi(img.getRadialDistortionCenterShift().y);
793 /// @todo can this be done with img.getSize() / 2 + img.getRadialDistortionCenterShift()?
794 m_cropCenter = vigra::Point2D(img.getSize().width()/2 + dx, img.getSize().height()/2 + dy);
795
796 UpdateCropDisplay();
797 }
798
799 // transfer our state to panorama
UpdateCrop(bool updateFromImgCtrl)800 void MaskEditorPanel::UpdateCrop(bool updateFromImgCtrl)
801 {
802 // set crop image options.
803 if(updateFromImgCtrl)
804 {
805 m_cropRect=m_editImg->getCrop();
806 };
807 std::vector<HuginBase::SrcPanoImage> srcImgs;
808 HuginBase::UIntSet imgs;
809 if (m_cropLens->IsChecked())
810 {
811 const HuginBase::UIntSetVector lensImageVector = m_imageGroups->getLenses().getPartsSet();
812 for (auto i : m_selectedImages)
813 {
814 for (auto j : lensImageVector)
815 {
816 if (set_contains(j, i))
817 {
818 std::copy(j.begin(), j.end(), std::inserter(imgs, imgs.begin()));
819 break;
820 };
821 };
822 };
823 }
824 else
825 {
826 imgs = m_selectedImages;
827 }
828 for (auto i:imgs)
829 {
830 HuginBase::SrcPanoImage img=m_pano->getSrcImage(i);
831 img.setCropRect(m_cropRect);
832 img.setAutoCenterCrop(m_autoCenterCrop);
833 srcImgs.push_back(img);
834 };
835
836 PanoCommand::GlobalCmdHist::getInstance().addCommand(
837 new PanoCommand::UpdateSrcImagesCmd(*m_pano, imgs, srcImgs)
838 );
839 }
840
UpdateCropFromImage()841 void MaskEditorPanel::UpdateCropFromImage()
842 {
843 m_cropRect=m_editImg->getCrop();
844 UpdateCropDisplay();
845 };
846
847 // redraw display with new information
UpdateCropDisplay()848 void MaskEditorPanel::UpdateCropDisplay()
849 {
850 DEBUG_TRACE("")
851 m_autocenter_cb->SetValue(m_autoCenterCrop);
852 m_left_textctrl->SetValue(wxString::Format(wxT("%d"),m_cropRect.left()));
853 m_right_textctrl->SetValue(wxString::Format(wxT("%d"),m_cropRect.right()));
854 m_top_textctrl->SetValue(wxString::Format(wxT("%d"),m_cropRect.top()));
855 m_bottom_textctrl->SetValue(wxString::Format(wxT("%d"),m_cropRect.bottom()));
856 }
857
858
OnSetTop(wxCommandEvent & e)859 void MaskEditorPanel::OnSetTop(wxCommandEvent & e)
860 {
861 long val;
862 if (m_top_textctrl->GetValue().ToLong(&val))
863 {
864 m_cropRect.setUpperLeft(vigra::Point2D(m_cropRect.left(), val));
865 if (m_autoCenterCrop)
866 {
867 CenterCrop();
868 UpdateCropDisplay();
869 };
870 UpdateCrop();
871 }
872 else
873 {
874 wxLogError(_("Please enter a valid number"));
875 };
876 };
877
OnSetBottom(wxCommandEvent & e)878 void MaskEditorPanel::OnSetBottom(wxCommandEvent & e)
879 {
880 long val;
881 if (m_bottom_textctrl->GetValue().ToLong(&val))
882 {
883 m_cropRect.setLowerRight(vigra::Point2D(m_cropRect.right(), val));
884 if (m_autoCenterCrop)
885 {
886 CenterCrop();
887 UpdateCropDisplay();
888 }
889 UpdateCrop();
890 }
891 else
892 {
893 wxLogError(_("Please enter a valid number"));
894 };
895 };
896
OnSetLeft(wxCommandEvent & e)897 void MaskEditorPanel::OnSetLeft(wxCommandEvent & e)
898 {
899 long val = 0;
900 if (m_left_textctrl->GetValue().ToLong(&val))
901 {
902 m_cropRect.setUpperLeft(vigra::Point2D(val, m_cropRect.top()));
903 if (m_autoCenterCrop)
904 {
905 CenterCrop();
906 UpdateCropDisplay();
907 }
908 UpdateCrop();
909 }
910 else
911 {
912 wxLogError(_("Please enter a valid number"));
913 };
914 };
915
OnSetRight(wxCommandEvent & e)916 void MaskEditorPanel::OnSetRight(wxCommandEvent & e)
917 {
918 long val = 0;
919 if (m_right_textctrl->GetValue().ToLong(&val))
920 {
921 m_cropRect.setLowerRight(vigra::Point2D(val, m_cropRect.bottom()));
922 if (m_autoCenterCrop)
923 {
924 CenterCrop();
925 UpdateCropDisplay();
926 };
927 UpdateCrop();
928 }
929 else
930 {
931 wxLogError(_("Please enter a valid number"));
932 };
933 };
934
OnResetButton(wxCommandEvent & e)935 void MaskEditorPanel::OnResetButton(wxCommandEvent & e)
936 {
937 // suitable defaults.
938 m_cropRect.setUpperLeft(vigra::Point2D(0,0));
939 m_cropRect.setLowerRight(vigra::Point2D(0,0));
940 m_autoCenterCrop = true;
941 m_cropMode=HuginBase::SrcPanoImage::NO_CROP;
942 UpdateCropDisplay();
943 UpdateCrop();
944 }
945
OnAutoCenter(wxCommandEvent & e)946 void MaskEditorPanel::OnAutoCenter(wxCommandEvent & e)
947 {
948 m_autoCenterCrop = e.IsChecked();
949 if (m_autoCenterCrop)
950 {
951 CenterCrop();
952 UpdateCropDisplay();
953 };
954 UpdateCrop();
955 }
956
CenterCrop()957 void MaskEditorPanel::CenterCrop()
958 {
959 vigra::Diff2D d(m_cropRect.width()/2, m_cropRect.height() / 2);
960 m_cropRect.setUpperLeft( m_cropCenter - d);
961 m_cropRect.setLowerRight( m_cropCenter + d);
962 }
963
OnModeChanged(wxNotebookEvent & e)964 void MaskEditorPanel::OnModeChanged(wxNotebookEvent& e)
965 {
966 if(m_maskCropCtrl==NULL)
967 {
968 return;
969 };
970 if(m_maskCropCtrl->GetSelection()==0)
971 {
972 m_maskMode=true;
973 size_t imgNr=GetImgNr();
974 m_selectedImages.clear();
975 m_selectedImages.insert(imgNr);
976 m_imagesListMask->SetSingleSelect(true);
977 m_imagesListMask->SelectSingleImage(imgNr);
978 m_editImg->SetMaskMode(true);
979 }
980 else
981 {
982 m_maskMode=false;
983 m_imagesListMask->SetSingleSelect(false);
984 m_editImg->SetMaskMode(false);
985 SelectMask(UINT_MAX);
986 };
987 m_editImg->Refresh();
988 };
989
SwitchToCropMode()990 void MaskEditorPanel::SwitchToCropMode()
991 {
992 m_maskCropCtrl->SetSelection(1);
993 wxNotebookEvent dummy;
994 OnModeChanged(dummy);
995 }
996
IMPLEMENT_DYNAMIC_CLASS(MaskEditorPanel,wxPanel)997 IMPLEMENT_DYNAMIC_CLASS(MaskEditorPanel, wxPanel)
998
999 MaskEditorPanelXmlHandler::MaskEditorPanelXmlHandler()
1000 : wxXmlResourceHandler()
1001 {
1002 AddWindowStyles();
1003 }
1004
DoCreateResource()1005 wxObject *MaskEditorPanelXmlHandler::DoCreateResource()
1006 {
1007 XRC_MAKE_INSTANCE(cp, MaskEditorPanel)
1008
1009 cp->Create(m_parentAsWindow,
1010 GetID(),
1011 GetPosition(), GetSize(),
1012 GetStyle(wxT("style")),
1013 GetName());
1014
1015 SetupWindow(cp);
1016
1017 return cp;
1018 }
1019
CanHandle(wxXmlNode * node)1020 bool MaskEditorPanelXmlHandler::CanHandle(wxXmlNode *node)
1021 {
1022 return IsOfClass(node, wxT("MaskEditorPanel"));
1023 }
1024
1025 IMPLEMENT_DYNAMIC_CLASS(MaskEditorPanelXmlHandler, wxXmlResourceHandler)
1026