OpenCPN Partial API docs
waypointman_gui.cpp
1 
2 /***************************************************************************
3  *
4  * Project: OpenCPN
5  * Purpose: implement waypointman_gui.h: WayPointman drawing stuff
6  * Author: David Register, Alec Leamas
7  *
8  ***************************************************************************
9  * Copyright (C) 2022 by David Register, Alec Leamas *
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  * This program 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 *
19  * GNU General Public License for more details. *
20  * *
21  * You should have received a copy of the GNU General Public License *
22  * along with this program; if not, write to the *
23  * Free Software Foundation, Inc., *
24  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
25  ******************A********************************************************/
26 
27 #if defined(__ANDROID__)
28 #include <qopengl.h>
29 #include <GL/gl_private.h> // this is a cut-down version of gl.h
30 #include <GLES2/gl2.h>
31 
32 #elif defined(ocpnUSE_GL)
33 
34 #if defined(__MSVC__)
35 #include "glew.h"
36 #include <GL/glu.h>
37 
38 #elif defined(__WXOSX__)
39 #include <OpenGL/gl.h>
40 #include <OpenGL/glu.h>
41 typedef void (* _GLUfuncptr)();
42 #define GL_COMPRESSED_RGB_FXT1_3DFX 0x86B0
43 
44 #elif defined(__WXQT__) || defined(__WXGTK__)
45 #include <GL/glew.h>
46 #include <GL/glu.h>
47 #endif // ocpnUSE_GL
48 #endif
49 
50 #include <wx/arrstr.h>
51 #include <wx/bitmap.h>
52 #include <wx/dir.h>
53 #include <wx/filename.h>
54 #include <wx/gdicmn.h>
55 #include <wx/log.h>
56 #include <wx/string.h>
57 #include <wx/utils.h>
58 
59 #include "model/base_platform.h"
60 #include "model/cutil.h"
61 #include "model/MarkIcon.h"
62 #include "model/route_point.h"
63 #include "styles.h"
64 #include "svg_utils.h"
65 #include "waypointman_gui.h"
66 #include "ocpn_plugin.h"
67 
68 extern BasePlatform* g_BasePlatform;
69 extern float g_MarkScaleFactorExp;
70 extern ocpnStyle::StyleManager *g_StyleManager;
71 extern bool g_bUserIconsFirst;
72 
73 static int CompareMarkIcons(MarkIcon *mi1, MarkIcon *mi2) {
74  return (mi1->icon_name.CmpNoCase(mi2->icon_name));
75 }
76 
77 
78 void WayPointmanGui::ProcessUserIcons(ocpnStyle::Style *style,
79  double displayDPmm) {
80  wxString msg;
81  msg.Printf(_T("DPMM: %g ScaleFactorExp: %g"), displayDPmm,
82  g_MarkScaleFactorExp);
83  wxLogMessage(msg);
84 
85  wxString UserIconPath = g_BasePlatform->GetPrivateDataDir();
86  wxChar sep = wxFileName::GetPathSeparator();
87  if (UserIconPath.Last() != sep) UserIconPath.Append(sep);
88  UserIconPath.Append(_T("UserIcons/"));
89 
90  wxLogMessage(_T("Looking for UserIcons at ") + UserIconPath);
91 
92  if (wxDir::Exists(UserIconPath)) {
93  wxLogMessage(_T("Loading UserIcons from ") + UserIconPath);
94  wxArrayString FileList;
95 
96  wxBitmap default_bm = wxBitmap(1,1); //empty
97 
98  int n_files =
99  wxDir::GetAllFiles(UserIconPath, &FileList, _T(""), wxDIR_FILES);
100 
101  for (int ifile = 0; ifile < n_files; ifile++) {
102  wxString name = g_bUserIconsFirst ? FileList[n_files - ifile - 1] : FileList[ifile];
103 
104  wxFileName fn(name);
105  wxString iconname = fn.GetName();
106  wxBitmap icon1;
107  if (fn.GetExt().Lower() == _T("xpm")) {
108  if (icon1.LoadFile(name, wxBITMAP_TYPE_XPM)) {
109  wxLogMessage(_T("Adding icon: ") + iconname);
110  wxImage image = icon1.ConvertToImage();
111  ProcessIcon(image, iconname, iconname, g_bUserIconsFirst);
112  }
113  }
114  if (fn.GetExt().Lower() == _T("png")) {
115  if (icon1.LoadFile(name, wxBITMAP_TYPE_PNG)) {
116  wxLogMessage(_T("Adding icon: ") + iconname);
117  wxImage image = icon1.ConvertToImage();
118  ProcessIcon(image, iconname, iconname, g_bUserIconsFirst);
119  }
120  }
121  if (fn.GetExt().Lower() == _T("svg")) {
122  unsigned int w, h;
123  SVGDocumentPixelSize(name, w, h);
124 
125  // This is to be a mark icon
126  // Make it a nominal max size
127  double bm_size_nom = wxMin(wxMax(w,h), floor(displayDPmm * 20));
128  // We want certain minimal size for the icons, 15px (approx 3mm) be it
129  bm_size_nom = wxMax(bm_size_nom, 15);
130 
131  bm_size_nom /= OCPN_GetWinDIPScaleFactor();
132  bm_size_nom *= g_MarkScaleFactorExp;
133 
134  MarkIcon *pmi = NULL;
135  double aspect = h / w;
136 
137  // Make the rendered icon square, if necessary
138  if (fabs(aspect - 1.0) > .05) {
139  wxImage image =
140  LoadSVG(name, (int)bm_size_nom,
141  (int)bm_size_nom,
142  &default_bm).ConvertToImage();
143 
144  if (image.IsOk()) {
145  wxRect rClip = CropImageOnAlpha(image);
146  wxImage imageClip = image.GetSubImage(rClip);
147  imageClip.Rescale(bm_size_nom, bm_size_nom / aspect,
148  wxIMAGE_QUALITY_BICUBIC);
149  pmi = ProcessIcon(imageClip, iconname, iconname, g_bUserIconsFirst);
150  }
151  }
152  else {
153  const unsigned int bm_size = bm_size_nom; // horizontal
154  wxImage iconSVG = LoadSVG(name, bm_size, bm_size,
155  &default_bm, false).ConvertToImage();
156  wxRect rClip = CropImageOnAlpha(iconSVG);
157  wxImage imageClip = iconSVG.GetSubImage(rClip);
158  pmi = ProcessIcon(iconSVG, iconname, iconname, g_bUserIconsFirst);
159  }
160 
161  if (pmi) pmi->preScaled = true;
162  }
163  }
164  }
165 }
166 
167 MarkIcon* WayPointmanGui::ProcessIcon(wxImage image, const wxString& key,
168  const wxString& description, bool add_in_front) {
169  MarkIcon *pmi = 0;
170 
171  bool newIcon = true;
172 
173  // avoid adding duplicates
174  for (unsigned int i = 0; i < m_waypoint_man.m_pIconArray->GetCount(); i++) {
175  pmi = (MarkIcon *)m_waypoint_man.m_pIconArray->Item(i);
176  if (pmi->icon_name.IsSameAs(key)) {
177  newIcon = false;
178  delete pmi->piconBitmap;
179  break;
180  }
181  }
182 
183  if (newIcon) {
184  pmi = new MarkIcon;
185  pmi->icon_name = key; // Used for sorting
186  if (add_in_front)
187  m_waypoint_man.m_pIconArray->Insert(pmi, 0);
188  else {
189  m_waypoint_man.m_pIconArray->Add(pmi);
190  }
191  }
192 
193 
194  wxBitmap *pbm = new wxBitmap(image);
195  pmi->icon_name = key;
196  pmi->icon_description = description;
197  pmi->piconBitmap = NULL;
198  pmi->icon_texture = 0; /* invalidate */
199  pmi->preScaled = false;
200  pmi->iconImage = pbm->ConvertToImage();
201  pmi->m_blistImageOK = false;
202  delete pbm;
203 
204  return pmi;
205 }
206 
207 void WayPointmanGui::ProcessIcons(ocpnStyle::Style *style, double displayDPmm) {
208  for (unsigned int i = 0; i < m_waypoint_man.m_pIconArray->GetCount(); i++) {
209  MarkIcon *pmi = (MarkIcon *)m_waypoint_man.m_pIconArray->Item(i);
210  delete pmi->piconBitmap;
211  delete pmi;
212  }
213  m_waypoint_man.m_pIconArray->Clear();
214 
215  ProcessDefaultIcons(displayDPmm);
216 
217  // Load user defined icons.
218  // Done after default icons are initialized,
219  // so that user may substitute an icon by using the same name in the Usericons
220  // file.
221  ProcessUserIcons(style, displayDPmm);
222 
223  if (NULL != m_waypoint_man.pmarkicon_image_list) {
224  m_waypoint_man.pmarkicon_image_list->RemoveAll();
225  delete m_waypoint_man.pmarkicon_image_list;
226  m_waypoint_man.pmarkicon_image_list = NULL;
227  }
228 
229  // First find the largest bitmap size, to use as the base size for lists of
230  // icons
231  int w = 0;
232  int h = 0;
233 
234  for (unsigned int i = 0; i < m_waypoint_man.m_pIconArray->GetCount(); i++) {
235  MarkIcon *pmi = (MarkIcon *)m_waypoint_man.m_pIconArray->Item(i);
236  w = wxMax(w, pmi->iconImage.GetWidth());
237  h = wxMax(h, pmi->iconImage.GetHeight());
238  }
239 
240  m_waypoint_man.m_bitmapSizeForList = wxMax(w, h);
241  m_waypoint_man.m_bitmapSizeForList =
242  wxMin(100, m_waypoint_man.m_bitmapSizeForList);
243 }
244 
245 void WayPointmanGui::ProcessDefaultIcons(double displayDPmm) {
246  wxString iconDir = g_BasePlatform->GetSharedDataDir();
247  appendOSDirSlash(&iconDir);
248  iconDir.append(_T("uidata"));
249  appendOSDirSlash(&iconDir);
250  iconDir.append(_T("markicons"));
251  appendOSDirSlash(&iconDir);
252 
253  MarkIcon *pmi = 0;
254 
255  // Add the legacy icons to their own sorted array
256  if (m_waypoint_man.m_pLegacyIconArray)
257  m_waypoint_man.m_pLegacyIconArray->Clear();
258  else
259  m_waypoint_man.m_pLegacyIconArray =
260  new SortedArrayOfMarkIcon(CompareMarkIcons);
261 
262  pmi = ProcessLegacyIcon(iconDir + _T("Symbol-Empty.svg"), _T("empty"),
263  _T("Empty"), displayDPmm);
264  if (pmi) pmi->preScaled = true;
265  pmi = ProcessLegacyIcon(iconDir + _T("Symbol-Triangle.svg"), _T("triangle"),
266  _T("Triangle"), displayDPmm);
267  if (pmi) pmi->preScaled = true;
268  pmi = ProcessLegacyIcon(iconDir + _T("1st-Active-Waypoint.svg"),
269  _T("activepoint"), _T("Active WP"), displayDPmm);
270  if (pmi) pmi->preScaled = true;
271  pmi = ProcessLegacyIcon(iconDir + _T("Marks-Boarding-Location.svg"),
272  _T("boarding"), _T("Boarding Location"), displayDPmm);
273  if (pmi) pmi->preScaled = true;
274  pmi = ProcessLegacyIcon(iconDir + _T("Hazard-Airplane.svg"), _T("airplane"),
275  _T("Airplane"), displayDPmm);
276  if (pmi) pmi->preScaled = true;
277  pmi = ProcessLegacyIcon(iconDir + _T("1st-Anchorage.svg"), _T("anchorage"),
278  _T("Anchorage"), displayDPmm);
279  if (pmi) pmi->preScaled = true;
280  pmi = ProcessLegacyIcon(iconDir + _T("Symbol-Anchor2.svg"), _T("anchor"),
281  _T("Anchor"), displayDPmm);
282  if (pmi) pmi->preScaled = true;
283  pmi = ProcessLegacyIcon(iconDir + _T("Marks-Boundary.svg"), _T("boundary"),
284  _T("Boundary Mark"), displayDPmm);
285  if (pmi) pmi->preScaled = true;
286  pmi = ProcessLegacyIcon(iconDir + _T("Marks-Buoy-TypeA.svg"), _T("bouy1"),
287  _T("Bouy Type A"), displayDPmm);
288  if (pmi) pmi->preScaled = true;
289  pmi = ProcessLegacyIcon(iconDir + _T("Marks-Buoy-TypeB.svg"), _T("bouy2"),
290  _T("Bouy Type B"), displayDPmm);
291  if (pmi) pmi->preScaled = true;
292  pmi = ProcessLegacyIcon(iconDir + _T("Activity-Campfire.svg"), _T("campfire"),
293  _T("Campfire"), displayDPmm);
294  if (pmi) pmi->preScaled = true;
295  pmi = ProcessLegacyIcon(iconDir + _T("Activity-Camping.svg"), _T("camping"),
296  _T("Camping Spot"), displayDPmm);
297  if (pmi) pmi->preScaled = true;
298  pmi = ProcessLegacyIcon(iconDir + _T("Sea-Floor-Coral.svg"), _T("coral"),
299  _T("Coral"), displayDPmm);
300  if (pmi) pmi->preScaled = true;
301  pmi = ProcessLegacyIcon(iconDir + _T("Activity-Fishing.svg"), _T("fishhaven"),
302  _T("Fish Haven"), displayDPmm);
303  if (pmi) pmi->preScaled = true;
304  pmi = ProcessLegacyIcon(iconDir + _T("Activity-Fishing.svg"), _T("fishing"),
305  _T("Fishing Spot"), displayDPmm);
306  if (pmi) pmi->preScaled = true;
307  pmi = ProcessLegacyIcon(iconDir + _T("Activity-Fishing.svg"), _T("fish"),
308  _T("Fish"), displayDPmm);
309  if (pmi) pmi->preScaled = true;
310  pmi = ProcessLegacyIcon(iconDir + _T("Marks-Mooring-Buoy.svg"), _T("float"),
311  _T("Float"), displayDPmm);
312  if (pmi) pmi->preScaled = true;
313  pmi = ProcessLegacyIcon(iconDir + _T("Service-Food.svg"), _T("food"),
314  _T("Food"), displayDPmm);
315  if (pmi) pmi->preScaled = true;
316  pmi = ProcessLegacyIcon(iconDir + _T("Service-Fuel-Pump-Diesel-Petrol.svg"),
317  _T("fuel"), _T("Fuel"), displayDPmm);
318  if (pmi) pmi->preScaled = true;
319  pmi = ProcessLegacyIcon(iconDir + _T("Marks-Light-Green.svg"),
320  _T("greenlite"), _T("Green Light"), displayDPmm);
321  if (pmi) pmi->preScaled = true;
322  pmi = ProcessLegacyIcon(iconDir + _T("Sea-Floor-Sea-Weed.svg"), _T("kelp"),
323  _T("Kelp"), displayDPmm);
324  if (pmi) pmi->preScaled = true;
325  pmi = ProcessLegacyIcon(iconDir + _T("Marks-Light-TypeA.svg"), _T("light"),
326  _T("Light Type A"), displayDPmm);
327  if (pmi) pmi->preScaled = true;
328  pmi = ProcessLegacyIcon(iconDir + _T("Marks-Light-TypeB.svg"), _T("light1"),
329  _T("Light Type B"), displayDPmm);
330  if (pmi) pmi->preScaled = true;
331  pmi = ProcessLegacyIcon(iconDir + _T("Marks-Light-Vessel.svg"),
332  _T("litevessel"), _T("litevessel"), displayDPmm);
333  if (pmi) pmi->preScaled = true;
334  pmi = ProcessLegacyIcon(iconDir + _T("1st-Man-Overboard.svg"), _T("mob"),
335  _T("MOB"), displayDPmm);
336  if (pmi) pmi->preScaled = true;
337  pmi = ProcessLegacyIcon(iconDir + _T("Marks-Mooring-Buoy.svg"), _T("mooring"),
338  _T("Mooring Bouy"), displayDPmm);
339  if (pmi) pmi->preScaled = true;
340  pmi = ProcessLegacyIcon(iconDir + _T("Marks-Mooring-Buoy-Super.svg"),
341  _T("oilbouy"), _T("Oil Bouy"), displayDPmm);
342  if (pmi) pmi->preScaled = true;
343  pmi = ProcessLegacyIcon(iconDir + _T("Hazard-Oil-Platform.svg"),
344  _T("platform"), _T("Platform"), displayDPmm);
345  if (pmi) pmi->preScaled = true;
346  pmi = ProcessLegacyIcon(iconDir + _T("Marks-Light-Red-Green.svg"),
347  _T("redgreenlite"), _T("Red/Green Light"), displayDPmm);
348  if (pmi) pmi->preScaled = true;
349  pmi = ProcessLegacyIcon(iconDir + _T("Marks-Light-Red.svg"), _T("redlite"),
350  _T("Red Light"), displayDPmm);
351  if (pmi) pmi->preScaled = true;
352  pmi = ProcessLegacyIcon(iconDir + _T("Hazard-Rock-Exposed.svg"), _T("rock1"),
353  _T("Rock (exposed)"), displayDPmm);
354  if (pmi) pmi->preScaled = true;
355  pmi = ProcessLegacyIcon(iconDir + _T("Hazard-Rock-Awash.svg"), _T("rock2"),
356  _T("Rock, (awash)"), displayDPmm);
357  if (pmi) pmi->preScaled = true;
358  pmi = ProcessLegacyIcon(iconDir + _T("Hazard-Sandbar.svg"), _T("sand"),
359  _T("Sand"), displayDPmm);
360  if (pmi) pmi->preScaled = true;
361  pmi = ProcessLegacyIcon(iconDir + _T("Activity-Diving-Scuba-Flag.svg"),
362  _T("scuba"), _T("Scuba"), displayDPmm);
363  if (pmi) pmi->preScaled = true;
364  pmi = ProcessLegacyIcon(iconDir + _T("Hazard-Sandbar.svg"), _T("shoal"),
365  _T("Shoal"), displayDPmm);
366  if (pmi) pmi->preScaled = true;
367  pmi = ProcessLegacyIcon(iconDir + _T("Hazard-Snag.svg"), _T("snag"),
368  _T("Snag"), displayDPmm);
369  if (pmi) pmi->preScaled = true;
370  pmi = ProcessLegacyIcon(iconDir + _T("Symbol-Square.svg"), _T("square"),
371  _T("Square"), displayDPmm);
372  if (pmi) pmi->preScaled = true;
373  pmi = ProcessLegacyIcon(iconDir + _T("1st-Diamond.svg"), _T("diamond"),
374  _T("Diamond"), displayDPmm);
375  if (pmi) pmi->preScaled = true;
376  pmi = ProcessLegacyIcon(iconDir + _T("Symbol-Circle.svg"), _T("circle"),
377  _T("Circle"), displayDPmm);
378  if (pmi) pmi->preScaled = true;
379  pmi = ProcessLegacyIcon(iconDir + _T("Hazard-Wreck1.svg"), _T("wreck1"),
380  _T("Wreck A"), displayDPmm);
381  if (pmi) pmi->preScaled = true;
382  pmi = ProcessLegacyIcon(iconDir + _T("Hazard-Wreck2.svg"), _T("wreck2"),
383  _T("Wreck B"), displayDPmm);
384  if (pmi) pmi->preScaled = true;
385  pmi = ProcessLegacyIcon(iconDir + _T("Symbol-X-Small-Blue.svg"), _T("xmblue"),
386  _T("Blue X"), displayDPmm);
387  if (pmi) pmi->preScaled = true;
388  pmi = ProcessLegacyIcon(iconDir + _T("Symbol-X-Small-Green.svg"),
389  _T("xmgreen"), _T("Green X"), displayDPmm);
390  if (pmi) pmi->preScaled = true;
391  pmi = ProcessLegacyIcon(iconDir + _T("Symbol-X-Small-Red.svg"), _T("xmred"),
392  _T("Red X"), displayDPmm);
393  if (pmi) pmi->preScaled = true;
394 
395  // Add the extended icons to their own sorted array
396  if (m_waypoint_man.m_pExtendedIconArray) {
397  m_waypoint_man.m_pExtendedIconArray->Clear();
398  } else {
399  m_waypoint_man.m_pExtendedIconArray =
400  new SortedArrayOfMarkIcon(CompareMarkIcons);
401  }
402 
403 #if 0
404  wxArrayString FileList;
405  double bm_size = -1;
406 
407  int n_files = wxDir::GetAllFiles( iconDir, &FileList );
408 
409  // If the scale factor is not unity, measure the first icon in the list
410  // So that we may apply the scale factor exactly to all
411  if( fabs(g_ChartScaleFactorExp - 1.0) > 0.1){
412 
413  for( int ifile = 0; ifile < n_files; ifile++ ) {
414  wxString name = FileList[ifile];
415 
416  wxFileName fn( name );
417 
418  if( fn.GetExt().Lower() == _T("svg") ) {
419  wxBitmap bmt = LoadSVG(name, -1, -1 );
420  bm_size = bmt.GetWidth() * g_ChartScaleFactorExp;
421  break;
422  }
423  }
424  }
425 
426  for( int ifile = 0; ifile < n_files; ifile++ ) {
427  wxString name = FileList[ifile];
428 
429  wxFileName fn( name );
430  wxString iconname = fn.GetName();
431  wxBitmap icon1;
432  if( fn.GetExt().Lower() == _T("svg") ) {
433  wxImage iconSVG = LoadSVG( name, (int)bm_size, (int)bm_size );
434  MarkIcon * pmi = ProcessExtendedIcon( iconSVG, iconname, iconname );
435  if(pmi)
436  pmi->preScaled = true;
437  }
438  }
439 #else
440 
441  wxArrayString FileList;
442  // nominal size, but not less than 4 pixel
443  double bm_size = wxMax(4.0, floor(displayDPmm * 12.0));
444  bm_size /= OCPN_GetWinDIPScaleFactor();
445  bm_size *= g_MarkScaleFactorExp;
446 
447  int n_files = wxDir::GetAllFiles(iconDir, &FileList);
448 
449  g_BasePlatform->ShowBusySpinner();
450 
451  for (int ifile = 0; ifile < n_files; ifile++) {
452  wxString name = FileList[ifile];
453 
454  wxFileName fn(name);
455  wxString iconname = fn.GetName();
456  wxBitmap icon1;
457 
458  if (fn.GetExt().Lower() == _T("svg")) {
459  unsigned int w, h;
460 
461  SVGDocumentPixelSize(name, w, h);
462 
463  // We want certain minimal size for the icons
464  w = wxMax(wxMax(w, h), 15);
465  bm_size = w * g_MarkScaleFactorExp;
466  bm_size /= OCPN_GetWinDIPScaleFactor();
467 
468  wxBitmap bmp = LoadSVG(name, (int)bm_size, (int)bm_size);
469  if (bmp.IsOk()) {
470  wxImage iconSVG = bmp.ConvertToImage();
471 
472  MarkIcon *pmi = ProcessExtendedIcon(iconSVG, iconname, iconname);
473  if (pmi) pmi->preScaled = true;
474  } else {
475  wxLogMessage("Failed loading mark icon " + name);
476  }
477  }
478  }
479  g_BasePlatform->HideBusySpinner();
480 #endif
481 
482  // Walk the two sorted lists, adding icons to the un-sorted master list
483 
484  auto size = m_waypoint_man.m_pLegacyIconArray->GetCount();
485  for (unsigned int i = 0; i < size; i++) {
486  pmi = (MarkIcon *)m_waypoint_man.m_pLegacyIconArray->Item(i);
487  m_waypoint_man.m_pIconArray->Add(pmi);
488  }
489 
490  size = m_waypoint_man.m_pExtendedIconArray->GetCount();
491  for (unsigned int i = 0; i < size; i++) {
492  pmi = (MarkIcon *) m_waypoint_man.m_pExtendedIconArray->Item(i);
493 
494  // Do not add any icons from the extended array if they have already been
495  // used as legacy substitutes
496  bool noAdd = false;
497  auto legacy_count = m_waypoint_man.m_pLegacyIconArray->GetCount();
498  for (unsigned int j = 0; j < legacy_count; j++) {
499  MarkIcon *pmiLegacy =
500  (MarkIcon *)m_waypoint_man.m_pLegacyIconArray->Item(j);
501  if (pmiLegacy->icon_name.IsSameAs(pmi->icon_name)) {
502  noAdd = true;
503  break;
504  }
505  }
506  if (!noAdd) m_waypoint_man.m_pIconArray->Add(pmi);
507  }
508 }
509 
510 void WayPointmanGui::ReloadAllIcons(double displayDPmm) {
511  ProcessIcons(g_StyleManager->GetCurrentStyle(), displayDPmm);
512 
513  for (unsigned int i = 0; i < m_waypoint_man.m_pIconArray->GetCount(); i++) {
514  MarkIcon *pmi = (MarkIcon *)m_waypoint_man.m_pIconArray->Item(i);
515  wxImage dim_image;
516  if (m_waypoint_man.m_cs == GLOBAL_COLOR_SCHEME_DUSK) {
517  dim_image = m_waypoint_man.CreateDimImage(pmi->iconImage, .50);
518  pmi->iconImage = dim_image;
519  } else if (m_waypoint_man.m_cs == GLOBAL_COLOR_SCHEME_NIGHT) {
520  dim_image = m_waypoint_man.CreateDimImage(pmi->iconImage, .20);
521  pmi->iconImage = dim_image;
522  }
523  }
524  ReloadRoutepointIcons();
525 }
526 
527 void WayPointmanGui::SetColorScheme(ColorScheme cs, double displayDPmm) {
528  m_waypoint_man.m_cs = cs;
529  ReloadAllIcons(displayDPmm);
530 }
531 
532 MarkIcon *WayPointmanGui::ProcessLegacyIcon(wxString fileName, const wxString &key,
533  const wxString &description,
534  double displayDPmm) {
535  double bm_size = -1.0;
536 
537 #ifndef ocpnUSE_wxBitmapBundle
538 #ifndef __ANDROID__
539  if (fabs(g_MarkScaleFactorExp - 1.0) > 0.1) {
540  wxBitmap img = LoadSVG(fileName, -1, -1);
541  bm_size = img.GetWidth() * g_MarkScaleFactorExp;
542  bm_size /= OCPN_GetWinDIPScaleFactor();
543  }
544 #else
545  // Set the onscreen size of the symbol
546  // Compensate for various display resolutions
547  // Develop empirically, making a "diamond" symbol about 4 mm square
548  // Android uses "density buckets", so simple math produces poor results.
549  // Thus, these factors have been empirically tweaked to provide good results
550  // on a variety of devices
551  float nominal_legacy_icon_size_pixels = wxMax(4.0,
552  floor(displayDPmm * 12.0));
553  // legacy icon size
554  float pix_factor = nominal_legacy_icon_size_pixels / 68.0;
555 
556  unsigned int w, h;
557  SVGDocumentPixelSize(fileName, w, h);
558 
559  bm_size = w * pix_factor * g_MarkScaleFactorExp;
560 
561 #endif
562 #else
563  unsigned int w, h;
564  SVGDocumentPixelSize(fileName, w, h);
565  w = wxMax(wxMax(w, h), 15); // We want certain minimal size for the icons,
566  // 15px (approx 3mm) be it
567  bm_size = w * g_MarkScaleFactorExp; //SVGPixelsToDisplay(w);
568  bm_size /= OCPN_GetWinDIPScaleFactor();
569 #endif
570 
571  wxBitmap bm = LoadSVG(fileName, (int)bm_size, (int)bm_size);
572  if (!bm.IsOk())
573  return NULL;
574 
575  wxImage image =
576  LoadSVG(fileName, (int)bm_size, (int)bm_size).ConvertToImage();
577 
578  wxRect rClip = CropImageOnAlpha(image);
579  wxImage imageClip = image.GetSubImage(rClip);
580 
581  MarkIcon *pmi = 0;
582 
583  bool newIcon = true;
584 
585  // avoid adding duplicates
586  for (unsigned int i = 0; i < m_waypoint_man.m_pLegacyIconArray->GetCount(); i++) {
587  pmi = (MarkIcon *)m_waypoint_man.m_pLegacyIconArray->Item(i);
588  if (pmi->icon_name.IsSameAs(key)) {
589  newIcon = false;
590  delete pmi->piconBitmap;
591  break;
592  }
593  }
594 
595  if (newIcon) {
596  pmi = new MarkIcon;
597  pmi->icon_name = key; // Used for sorting
598  m_waypoint_man.m_pLegacyIconArray->Add(pmi);
599  }
600 
601  pmi->icon_name = key;
602  pmi->icon_description = description;
603  pmi->piconBitmap = NULL;
604  pmi->icon_texture = 0; /* invalidate */
605  pmi->preScaled = false;
606  pmi->iconImage = imageClip;
607  pmi->m_blistImageOK = false;
608 
609  return pmi;
610 }
611 
612 MarkIcon *WayPointmanGui::ProcessExtendedIcon(wxImage &image,
613  const wxString &key,
614  const wxString &description) {
615  MarkIcon *pmi = 0;
616 
617  bool newIcon = true;
618 
619  // avoid adding duplicates
620  auto size = m_waypoint_man.m_pExtendedIconArray->GetCount();
621  for (unsigned int i = 0; i < size; i++) {
622  pmi = (MarkIcon *)m_waypoint_man.m_pExtendedIconArray->Item(i);
623  if (pmi->icon_name.IsSameAs(key)) {
624  newIcon = false;
625  delete pmi->piconBitmap;
626  break;
627  }
628  }
629 
630  if (newIcon) {
631  pmi = new MarkIcon;
632  pmi->icon_name = key; // Used for sorting
633  m_waypoint_man.m_pExtendedIconArray->Add(pmi);
634  }
635 
636  wxRect rClip = CropImageOnAlpha(image);
637  wxImage imageClip = image.GetSubImage(rClip);
638 
639  pmi->icon_name = key;
640  pmi->icon_description = description;
641  pmi->piconBitmap = new wxBitmap(imageClip);
642  pmi->icon_texture = 0; /* invalidate */
643  pmi->preScaled = false;
644  pmi->iconImage = imageClip;
645  pmi->m_blistImageOK = false;
646 
647  return pmi;
648 }
649 
650 wxRect WayPointmanGui::CropImageOnAlpha(wxImage &image) {
651  const int w = image.GetWidth();
652  const int h = image.GetHeight();
653 
654  wxRect rv = wxRect(0, 0, w, h);
655  if (!image.HasAlpha()) return rv;
656 
657  unsigned char *pAlpha = image.GetAlpha();
658 
659  int leftCrop = w;
660  int topCrop = h;
661  int rightCrop = w;
662  int bottomCrop = h;
663 
664  // Horizontal
665  for (int i = 0; i < h; i++) {
666  int lineStartIndex = i * w;
667 
668  int j = 0;
669  while ((j < w) && (pAlpha[lineStartIndex + j] == 0)) j++;
670  leftCrop = wxMin(leftCrop, j);
671 
672  int k = w - 1;
673  while (k && (pAlpha[lineStartIndex + k] == 0)) k--;
674  rightCrop = wxMin(rightCrop, image.GetWidth() - k - 2);
675  }
676 
677  // Vertical
678  for (int i = 0; i < w; i++) {
679  int columnStartIndex = i;
680 
681  int j = 0;
682  while ((j < h) && (pAlpha[columnStartIndex + (j * w)] == 0)) j++;
683  topCrop = wxMin(topCrop, j);
684 
685  int k = h - 1;
686  while (k && (pAlpha[columnStartIndex + (k * w)] == 0)) k--;
687  bottomCrop = wxMin(bottomCrop, h - k - 2);
688  }
689 
690  int xcrop = wxMin(rightCrop, leftCrop);
691  int ycrop = wxMin(topCrop, bottomCrop);
692  int crop = wxMin(xcrop, ycrop);
693 
694  rv.x = wxMax(crop, 0);
695  rv.width = wxMax(1, w - (2 * crop));
696  rv.width = wxMin(rv.width, w);
697  rv.y = rv.x;
698  rv.height = rv.width;
699 
700  return rv;
701 }
702 
703 void WayPointmanGui::ReloadRoutepointIcons() {
704  // Iterate on the RoutePoint list, requiring each to reload icon
705 
706  wxRoutePointListNode *node = m_waypoint_man.m_pWayPointList->GetFirst();
707  while (node) {
708  RoutePoint *pr = node->GetData();
709  pr->ReLoadIcon();
710  node = node->GetNext();
711  }
712 }
713 
714 unsigned int WayPointmanGui::GetIconTexture(const wxBitmap *pbm, int &glw,
715  int &glh) {
716 #ifdef ocpnUSE_GL
717  int index = m_waypoint_man.GetIconIndex(pbm);
718  MarkIcon *pmi = (MarkIcon *)m_waypoint_man.m_pIconArray->Item(index);
719 
720  if (!pmi->icon_texture) {
721  /* make rgba texture */
722  wxImage image = pbm->ConvertToImage();
723  unsigned char *d = image.GetData();
724  if (d == 0) {
725  // don't create a texture with junk
726  return 0;
727  }
728 
729  glGenTextures(1, &pmi->icon_texture);
730  glBindTexture(GL_TEXTURE_2D, pmi->icon_texture);
731 
732  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
733  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
734  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
735 
736  int w = image.GetWidth(), h = image.GetHeight();
737 
738  pmi->tex_w = NextPow2(w);
739  pmi->tex_h = NextPow2(h);
740 
741  unsigned char *a = image.GetAlpha();
742 
743  unsigned char mr, mg, mb;
744  if (!a) image.GetOrFindMaskColour(&mr, &mg, &mb);
745 
746  unsigned char *e = new unsigned char[4 * w * h];
747  for (int y = 0; y < h; y++) {
748  for (int x = 0; x < w; x++) {
749  unsigned char r, g, b;
750  int off = (y * w + x);
751  r = d[off * 3 + 0];
752  g = d[off * 3 + 1];
753  b = d[off * 3 + 2];
754  e[off * 4 + 0] = r;
755  e[off * 4 + 1] = g;
756  e[off * 4 + 2] = b;
757 
758  e[off * 4 + 3] =
759  a ? a[off] : ((r == mr) && (g == mg) && (b == mb) ? 0 : 255);
760  }
761  }
762 
763  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pmi->tex_w, pmi->tex_h, 0, GL_RGBA,
764  GL_UNSIGNED_BYTE, NULL);
765  glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, e);
766 
767  delete[] e;
768  }
769 
770  glw = pmi->tex_w;
771  glh = pmi->tex_h;
772 
773  return pmi->icon_texture;
774 #else
775  return 0;
776 #endif
777 }
wxString & GetPrivateDataDir()
Return dir path for opencpn.log, etc., respecting -c cli option.