Quantum GIS API Documentation  1.7.5-Wroclaw
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
qgsmarkercatalogue.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsmarkercatalogue.cpp
3  -------------------
4 begin : March 2005
5 copyright : (C) 2005 by Radim Blazek
6 email : blazek@itc.it
7  ***************************************************************************/
8 /***************************************************************************
9  * *
10  * This program is free software; you can redistribute it and/or modify *
11  * it under the terms of the GNU General Public License as published by *
12  * the Free Software Foundation; either version 2 of the License, or *
13  * (at your option) any later version. *
14  * *
15  ***************************************************************************/
16 #include <cmath>
17 #include <assert.h>
18 
19 #include <QPen>
20 #include <QBrush>
21 #include <QPainter>
22 #include <QImage>
23 #include <QString>
24 #include <QStringList>
25 #include <QRect>
26 #include <QPointF>
27 #include <QPolygonF>
28 #include <QDir>
29 #include <QPicture>
30 #include <QSvgRenderer>
31 
32 #include "qgis.h"
33 #include "qgsapplication.h"
34 #include "qgsmarkercatalogue.h"
35 #include "qgslogger.h"
36 
37 // MSVC compiler doesn't have defined M_PI in math.h
38 #ifndef M_PI
39 #define M_PI 3.14159265358979323846
40 #endif
41 
42 #define DEG2RAD(x) ((x)*M_PI/180)
43 
44 //#define IMAGEDEBUG
45 
47 
49 {
50  refreshList();
51 }
52 
54 {
55  // Init list
56  mList.clear();
57 
58  // Hardcoded markers
59  mList.append( "hard:circle" );
60  mList.append( "hard:rectangle" );
61  mList.append( "hard:diamond" );
62  mList.append( "hard:pentagon" );
63  mList.append( "hard:cross" );
64  mList.append( "hard:cross2" );
65  mList.append( "hard:triangle" );
66  mList.append( "hard:equilateral_triangle" );
67  mList.append( "hard:star" );
68  mList.append( "hard:regular_star" );
69  mList.append( "hard:arrow" );
70 
71  // SVG
72  QStringList svgPaths = QgsApplication::svgPaths();
73  QgsDebugMsg( QString( "Application SVG Search paths: \n%1" ).arg( svgPaths.join( "\n" ) ) );
74 
75  for ( int i = 0; i < svgPaths.size(); i++ )
76  {
77  QDir dir( svgPaths[i] );
78  foreach( QString item, dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot ) )
79  {
80  svgPaths.insert( i + 1, dir.path() + "/" + item );
81  }
82 
83  QgsDebugMsg( QString( "Looking for svgs in %1" ).arg( dir.path() ) );
84 
85  foreach( QString item, dir.entryList( QStringList( "*.svg" ), QDir::Files ) )
86  {
87  // TODO test if it is correct SVG
88  mList.append( "svg:" + dir.path() + "/" + item );
89  }
90  }
91 
92  emit markersRefreshed();
93 }
94 
96 {
97  return mList;
98 }
99 
101 {
102 }
103 
105 {
106  if ( !QgsMarkerCatalogue::mMarkerCatalogue )
107  {
108  QgsMarkerCatalogue::mMarkerCatalogue = new QgsMarkerCatalogue();
109  }
110 
112 }
113 
114 QImage QgsMarkerCatalogue::imageMarker( QString fullName, double size, QPen pen, QBrush brush, double opacity )
115 {
116 
117  //
118  // First prepare the paintdevice that the marker will be drawn onto
119  //
120 
121  QImage myImage;
122  int imageSize;
123  if ( fullName.startsWith( "hard:" ) )
124  {
125  int pw = (( pen.width() == 0 ? 1 : pen.width() ) + 1 ) / 2 * 2; // make even (round up); handle cosmetic pen
126  imageSize = (( int ) size + pw ) / 2 * 2 + 1; // make image width, height odd; account for pen width
127  myImage = QImage( imageSize, imageSize, QImage::Format_ARGB32_Premultiplied );
128  }
129  else
130  {
131  // TODO Change this logic so width is size and height is same
132  // proportion of scale factor as in oritignal SVG TS XXX
133  //QPixmap myPixmap = QPixmap(width,height);
134  imageSize = (( int ) size ) / 2 * 2 + 1; // make image width, height odd
135  myImage = QImage( imageSize, imageSize, QImage::Format_ARGB32_Premultiplied );
136  }
137 
138  // starting with transparent QImage
139  myImage.fill( 0 );
140 
141  QPainter myPainter;
142  myPainter.begin( &myImage );
143  myPainter.setRenderHint( QPainter::Antialiasing );
144  myPainter.setOpacity( opacity );
145 
146  //
147  // Now pass the paintdevice along to have the marker rendered on it
148  //
149  if ( fullName.startsWith( "svg:" ) )
150  {
151  if ( svgMarker( &myPainter, fullName.mid( 4 ), size ) )
152  return myImage;
153 
154  QgsDebugMsg( QString( "%1 not found - replacing with hard:circle" ).arg( fullName ) );
155  fullName = "hard:circle";
156  }
157 
158  if ( fullName.startsWith( "font:" ) )
159  {
160  if ( fontMarker( &myPainter, fullName.mid( 5 ), size ) )
161  return myImage;
162 
163  QgsDebugMsg( QString( "%1 not found - replacing with hard:circle" ).arg( fullName ) );
164  fullName = "hard:circle";
165  }
166 
167  if ( fullName.endsWith( ".svg", Qt::CaseInsensitive ) )
168  {
169  if ( svgMarker( &myPainter, fullName, size ) )
170  return myImage;
171 
172  QgsDebugMsg( QString( "%1 not found - replacing with hard:circle" ).arg( fullName ) );
173  fullName = "hard:circle";
174  }
175 
176  if ( fullName.startsWith( "hard:" ) )
177  {
178  hardMarker( &myPainter, imageSize, fullName.mid( 5 ), size, pen, brush );
179 #ifdef IMAGEDEBUG
180  QgsDebugMsg( "*** Saving hard marker to hardMarker.png ***" );
181 #ifdef QGISDEBUG
182  myImage.save( "hardMarker.png" );
183 #endif
184 #endif
185  return myImage;
186  }
187 
188  return QImage(); // empty
189 }
190 
191 QPicture QgsMarkerCatalogue::pictureMarker( QString fullName, double size, QPen pen, QBrush brush, double opacity )
192 {
193 
194  //
195  // First prepare the paintdevice that the marker will be drawn onto
196  //
197  QPicture myPicture;
198  if ( fullName.left( 5 ) == "hard:" )
199  {
200  //Note teh +1 offset below is required because the
201  //otherwise the icons are getting clipped
202  myPicture = QPicture( size + 1 );
203  }
204  else
205  {
206  // TODO Change this logic so width is size and height is same
207  // proportion of scale factor as in oritignal SVG TS XXX
208  if ( size < 1 ) size = 1;
209  myPicture = QPicture( size );
210  }
211 
212  QPainter myPainter( &myPicture );
213  myPainter.setRenderHint( QPainter::Antialiasing );
214  myPainter.setOpacity( opacity );
215 
216  //
217  // Now pass the paintdevice along to have the marker rndered on it
218  //
219  if ( fullName.left( 4 ) == "svg:" )
220  {
221  if ( svgMarker( &myPainter, fullName.mid( 4 ), size ) )
222  return myPicture;
223 
224  QgsDebugMsg( QString( "%1 not found - replacing with hard:circle" ).arg( fullName ) );
225  fullName = "hard:circle";
226  }
227 
228  if ( fullName.left( 5 ) == "hard:" )
229  {
230  hardMarker( &myPainter, ( int ) size, fullName.mid( 5 ), size, pen, brush );
231  return myPicture;
232  }
233 
234  return QPicture(); // empty
235 }
236 
237 bool QgsMarkerCatalogue::fontMarker( QPainter *thepPainter, QString fullName, double scaleFactor )
238 {
239  QStringList args = fullName.split( "," );
240  if ( args.size() == 0 )
241  return false;
242 
243  QChar c;
244 
245  if ( args.size() > 0 )
246  {
247  if ( args[0] == "#" )
248  {
249  c = QChar( '#' );
250  }
251  else if ( args[0].startsWith( "#" ) )
252  {
253  c = QChar( args[0].mid( 1 ).toInt() );
254  }
255  else
256  {
257  c = args[0][0];
258  }
259  }
260 
261  QString family = args.size() >= 2 ? args[1] : "Helvetica";
262  int weight = args.size() >= 3 ? args[2].toInt() : -1;
263  int italic = args.size() >= 4 ? args[3].toInt() != 0 : false;
264 
265  thepPainter->setFont( QFont( family, scaleFactor, weight, italic ) );
266  thepPainter->drawText( 0, 0, c );
267 
268  return true;
269 }
270 
271 bool QgsMarkerCatalogue::svgMarker( QPainter * thepPainter, QString fileName, double scaleFactor )
272 {
273  QSvgRenderer mySVG;
274  if ( !mySVG.load( fileName ) )
275  return false;
276 
277  mySVG.render( thepPainter );
278 
279  return true;
280 }
281 
282 void QgsMarkerCatalogue::hardMarker( QPainter * thepPainter, int imageSize, QString name, double s, QPen pen, QBrush brush )
283 {
284  // Size of polygon symbols is calculated so that the boundingbox is circumscribed
285  // around a circle with diameter mPointSize
286 
287 #if 0
288  s = s - pen.widthF(); // to make the overall size of the symbol at the specified size
289 #else
290  // the size of the base symbol is at the specified size; the outline is applied additionally
291 #endif
292 
293  // Circle radius, is used for other figures also, when compensating for line
294  // width is necessary.
295  double r = s / 2; // get half the size of the figure to be rendered (the radius)
296 
297  QgsDebugMsgLevel( QString( "Hard marker size %1" ).arg( s ), 3 );
298 
299  // Find out center coordinates of the QImage to draw on.
300  double x_c = ( double )( imageSize / 2 ) + 0.5; // add 1/2 pixel for proper rounding when the figure's coordinates are added
301  double y_c = x_c; // is square image
302 
303  thepPainter->setPen( pen );
304  thepPainter->setBrush( brush );
305 
306  QgsDebugMsgLevel( QString( "Hard marker radius %1" ).arg( r ), 3 );
307 
308  if ( name == "circle" )
309  {
310  // "A stroked ellipse has a size of rectangle.size() plus the pen width."
311  // (from Qt doc)
312 
313  thepPainter->drawEllipse( QRectF( x_c - r, y_c - r, s, s ) ); // x,y,w,h
314  }
315  else if ( name == "rectangle" )
316  {
317  thepPainter->drawRect( QRectF( x_c - r, y_c - r, s, s ) ); // x,y,w,h
318  }
319  else if ( name == "diamond" )
320  {
321  QPolygonF pa;
322  pa << QPointF( x_c - r, y_c )
323  << QPointF( x_c, y_c + r )
324  << QPointF( x_c + r, y_c )
325  << QPointF( x_c, y_c - r );
326  thepPainter->drawPolygon( pa );
327  }
328  else if ( name == "pentagon" )
329  {
330  QPolygonF pa;
331  pa << QPointF( x_c + ( r * sin( DEG2RAD( 288.0 ) ) ), y_c - ( r * cos( DEG2RAD( 288.0 ) ) ) )
332  << QPointF( x_c + ( r * sin( DEG2RAD( 216.0 ) ) ), y_c - ( r * cos( DEG2RAD( 216.0 ) ) ) )
333  << QPointF( x_c + ( r * sin( DEG2RAD( 144.0 ) ) ), y_c - ( r * cos( DEG2RAD( 144.0 ) ) ) )
334  << QPointF( x_c + ( r * sin( DEG2RAD( 72.0 ) ) ), y_c - ( r * cos( DEG2RAD( 72.0 ) ) ) )
335  << QPointF( x_c, y_c - r );
336  thepPainter->drawPolygon( pa );
337  }
338  else if ( name == "cross" )
339  {
340  thepPainter->drawLine( QPointF( x_c - r, y_c ), QPointF( x_c + r, y_c ) ); // horizontal
341  thepPainter->drawLine( QPointF( x_c, y_c - r ), QPointF( x_c, y_c + r ) ); // vertical
342  }
343  else if ( name == "cross2" )
344  {
345  thepPainter->drawLine( QPointF( x_c - r, y_c - r ), QPointF( x_c + r, y_c + r ) );
346  thepPainter->drawLine( QPointF( x_c - r, y_c + r ), QPointF( x_c + r, y_c - r ) );
347  }
348  else if ( name == "triangle" )
349  {
350  QPolygonF pa;
351  pa << QPointF( x_c - r, y_c + r )
352  << QPointF( x_c + r, y_c + r )
353  << QPointF( x_c, y_c - r );
354  thepPainter->drawPolygon( pa );
355  }
356  else if ( name == "equilateral_triangle" )
357  {
358  QPolygonF pa;
359  pa << QPointF( x_c + ( r * sin( DEG2RAD( 240.0 ) ) ), y_c - ( r * cos( DEG2RAD( 240.0 ) ) ) )
360  << QPointF( x_c + ( r * sin( DEG2RAD( 120.0 ) ) ), y_c - ( r * cos( DEG2RAD( 120.0 ) ) ) )
361  << QPointF( x_c, y_c - r ); // 0
362  thepPainter->drawPolygon( pa );
363  }
364  else if ( name == "star" )
365  {
366  double oneSixth = 2 * r / 6;
367 
368  QPolygonF pa;
369  pa << QPointF( x_c, y_c - r )
370  << QPointF( x_c - oneSixth, y_c - oneSixth )
371  << QPointF( x_c - r, y_c - oneSixth )
372  << QPointF( x_c - oneSixth, y_c )
373  << QPointF( x_c - r, y_c + r )
374  << QPointF( x_c, y_c + oneSixth )
375  << QPointF( x_c + r, y_c + r )
376  << QPointF( x_c + oneSixth, y_c )
377  << QPointF( x_c + r, y_c - oneSixth )
378  << QPointF( x_c + oneSixth, y_c - oneSixth );
379  thepPainter->drawPolygon( pa );
380  }
381  else if ( name == "regular_star" )
382  {
383  // control the 'fatness' of the star: cos(72)/cos(36) gives the classic star shape
384  double inner_r = r * cos( DEG2RAD( 72.0 ) ) / cos( DEG2RAD( 36.0 ) );
385 
386  QPolygonF pa;
387  pa << QPointF( x_c + ( inner_r * sin( DEG2RAD( 324.0 ) ) ), y_c - ( inner_r * cos( DEG2RAD( 324.0 ) ) ) ) // 324
388  << QPointF( x_c + ( r * sin( DEG2RAD( 288.0 ) ) ), y_c - ( r * cos( DEG2RAD( 288 ) ) ) ) // 288
389  << QPointF( x_c + ( inner_r * sin( DEG2RAD( 252.0 ) ) ), y_c - ( inner_r * cos( DEG2RAD( 252.0 ) ) ) ) // 252
390  << QPointF( x_c + ( r * sin( DEG2RAD( 216.0 ) ) ), y_c - ( r * cos( DEG2RAD( 216.0 ) ) ) ) // 216
391  << QPointF( x_c, y_c + ( inner_r ) ) // 180
392  << QPointF( x_c + ( r * sin( DEG2RAD( 144.0 ) ) ), y_c - ( r * cos( DEG2RAD( 144.0 ) ) ) ) // 144
393  << QPointF( x_c + ( inner_r * sin( DEG2RAD( 108.0 ) ) ), y_c - ( inner_r * cos( DEG2RAD( 108.0 ) ) ) ) // 108
394  << QPointF( x_c + ( r * sin( DEG2RAD( 72.0 ) ) ), y_c - ( r * cos( DEG2RAD( 72.0 ) ) ) ) // 72
395  << QPointF( x_c + ( inner_r * sin( DEG2RAD( 36.0 ) ) ), y_c - ( inner_r * cos( DEG2RAD( 36.0 ) ) ) ) // 36
396  << QPointF( x_c, y_c - r ); // 0
397  thepPainter->drawPolygon( pa );
398  }
399  else if ( name == "arrow" )
400  {
401  double oneEight = r / 4;
402  double quarter = r / 2;
403 
404  QPolygonF pa;
405  pa << QPointF( x_c, y_c - r )
406  << QPointF( x_c + quarter, y_c - quarter )
407  << QPointF( x_c + oneEight, y_c - quarter )
408  << QPointF( x_c + oneEight, y_c + r )
409  << QPointF( x_c - oneEight, y_c + r )
410  << QPointF( x_c - oneEight, y_c - quarter )
411  << QPointF( x_c - quarter, y_c - quarter );
412  thepPainter->drawPolygon( pa );
413  }
414  thepPainter->end();
415 }