File: pixel.hpp

package info (click to toggle)
gamera 1:3.4.2+git20160808.1725654-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 22,312 kB
  • ctags: 24,991
  • sloc: xml: 122,324; ansic: 52,869; cpp: 50,664; python: 35,034; makefile: 118; sh: 101
file content (666 lines) | stat: -rw-r--r-- 20,392 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
/*
 *
 * Copyright (C) 2001-2005 Ichiro Fujinaga, Michael Droettboom, Karl MacMillan
 *               2013      Christoph Dalitz
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#ifndef kwm11162001_pixel_hpp
#define kwm11162001_pixel_hpp

/**
 * This header contains the definition for all of the standard pixels in 
 * Gamera.  These include:
 *
 *  RGB - color pixels
 *  Complex - complex number pixels are convenient for fourier image
 *          (frequency domain) processing algorithms.  These values are
 *          similar to float values, but there are two values for each pixel.
 *  Float - floating point pixels that are convenient for many image processing
 *          algorithms
 *  GreyScale - grey scale pixels that hold values from 0 - 255 (8bit)
 *  OneBit - one bit pixels for black and white images.  These pixels actually
 *           can hold more than 2 values, which is used for labeling the pixels
 *           (using connected-components for example).  This seems like a lot
 *           of space to waste on one bit images, but if run-length encoding
 *           is used the space should be minimul.
 *
 * In addition to the pixels themselves, there is information about the pixels
 * (white/black values, etc).
 */

#include "gamera_limits.hpp"
#include "vigra/rgbvalue.hxx"
#include "vigra/colorconversions.hxx"
#include <complex>
//#include <stdint.h>

using namespace vigra;

namespace Gamera {

  /************************************************************************
   * PIXEL TYPES
   ************************************************************************/

  /**
   * Floating-point pixel.
   *
   * The Gamera::FloatPixel type represents a single pixel in a
   * floating-point image. For floating-point images 0 is considerd
   * black and max is considered white.
   */
  typedef double FloatPixel;

  /**
   * GreyScalePixel
   *
   * The Gamera::GreyScalePixel type is for 8bit greyscale images. For GreyScale
   * images 0 is considerd black and 255 is considered white.
   */
  typedef unsigned char GreyScalePixel;

  /**
   * Grey16Pixel
   *
   * The Gamera::Grey16Pixel type is for 16bit greyscale images.
   */
  typedef unsigned int Grey16Pixel;
  /*
  // This does not work because OneBit pixel is already of this type:
  //typedef unsigned short Grey16Pixel;
  //typedef uint16_t Grey16Pixel;
  */
  /*
  // For some reason, VIGRA does not work with user-defined pixel types:
  struct Grey16Pixel {
    short value;
    Grey16Pixel(int v) {value=short(v);}
    Grey16Pixel() {value=0;}
    short operator=(Grey16Pixel n) {return value=n.value;}
    short operator=(short n) {return value=n;}
    short operator=(int n) {return value=short(n);}
    bool operator==(Grey16Pixel n) {return value==n.value;}
    bool operator==(short n) {return value==n;}
    bool operator==(int n) {return value==(short)n;}
    short operator-=(Grey16Pixel n) {return value-=n.value;}
    short operator-=(short n) {return value-=n;}
    short operator-=(int n) {return value-=(short)n;}
    short operator+=(Grey16Pixel n) {return value+=n.value;}
    short operator+=(short n) {return value+=n;}
    short operator+=(int n) {return value+=(short)n;}
    bool operator>(Grey16Pixel n) {return value>n.value;}
    bool operator>(short n) {return value>n;}
    bool operator>(int n) {return value>(short)n;}
    bool operator>=(Grey16Pixel n) {return value>=n.value;}
    bool operator>=(short n) {return value>=n;}
    bool operator>=(int n) {return value>=(short)n;}
    bool operator<(Grey16Pixel n) {return value<n.value;}
    bool operator<(short n) {return value<n;}
    bool operator<(int n) {return value<(short)n;}
    bool operator<=(Grey16Pixel n) {return value<=n.value;}
    bool operator<=(short n) {return value<=n;}
    bool operator<=(int n) {return value<=(short)n;}
    short operator-(short n) {return value-n;}
    short operator+(short n) {return value+n;}
    short operator*(short n) {return value*n;}
    double operator*(double n) {return value*n;}
    short operator/(short n) {return value*n;}
    //int operator() {return value;}
    operator short() {return value;}
    operator short*() {return &value;}
    operator int() {return value;}
    operator long int() {return value;}
    int operator++() {return value++;}
    int operator--() {return value--;}
    //operator&() {return &value;}
  };
  int operator*(double x, Grey16Pixel p) {return int(x*p.value);}
  int operator*(int x, Grey16Pixel p) {return x*p.value;}
  long int operator*(long int x, Grey16Pixel p) {return x*p.value;}
  */

  /**
   * OneBitPixel
   *
   * The Gamera::OneBitPixel type is for OneBitImages. For OneBit
   * images > 0 is considerd black and 0 is considered white. Also, see the note
   * at the beginning of this file about why OneBitPixels are so large.
   */
  typedef unsigned short OneBitPixel;

  /** 
   * ComplexPixel
   *
   * The Gamera::ComplexPixel type represents a pixel with two values:
   * real and imaginary.  These values are accessed by real() and imag()
   * functions.  Most functions follow normal std::complex behavior.
   * Other behavior will generally mimic the floating-point pixel type often
   * by the same operation applied only to the real part of the pixel.
   */
  typedef std::complex<double> ComplexPixel;

  /**
   * RGB Pixels
   *
   * The Gamera::RGB pixel type is derived from the Vigra class RGBValue. The
   * only reason that this is a derived class instead of directly using the
   * Vigra type is to provide conversion operators to and from the standard
   * Gamera types (instead of using Vigra style promotion traits) and to provide
   * overloaded red, green, and blue functions instead of the set* functions
   * in the Vigra class.
   */
  template<class T>
  class Rgb : public RGBValue<T> {
  protected:
    using RGBValue<T>::data_;
    
  public:
    using RGBValue<T>::luminance;

    /**
     * Construct a RGB pixel from a GreyScalePixel. RGB are all
     * set to the passed in GreyScalePixel.
     */
    explicit Rgb(GreyScalePixel grey) : RGBValue<T>(grey) { }
    
    /**
     * Construct a RGB pixel from a Grey16Pixel. RGB are all
     * set to the passed in Grey16Pixel.
     */
    explicit Rgb(Grey16Pixel grey) : RGBValue<T>(grey) { }

    /**
     * Construct a RGB pixel from a Float. RGB are all
     * set to the passed in Float (which is truncated first).
     */
    explicit Rgb(FloatPixel f) : RGBValue<T>((T)f) { }

    /**
     * Construct a RGB pixel from a Complex.  RGB are all
     * set to the real part passed in Complex (which is truncated
     * first).
     */
    explicit Rgb(ComplexPixel j) : RGBValue<T>((T)j.real()) { }

    /**
     * Construct a RGB Pixel from a OneBitPixel. Appropriate conversion
     * is done.
     */
    explicit Rgb(OneBitPixel s) {
      // TODO: fix for new ONEBIT
      if (s > 0) {
	RGBValue<T>(1);
      } else {
	RGBValue<T>(0);
      }
    }

    /**
     * Default constructor - RGB are all set to 0.
     */
    Rgb() : RGBValue<T>() { }

    /**
     * Copy constructor.
     */
    template <class U>
    Rgb(RGBValue<U> const & r) : RGBValue<T>(r) { }

    Rgb(const Rgb& other) : RGBValue<T>(other) { }
    
    /**
     * Construct a RGB pixel from the passed in red, green, and blue
     * values.
     */
    Rgb(T red, T green, T blue) : RGBValue<T>(red, green, blue) { }

    /**
     * Construct a RGB pixel from the values contained in the iterator
     * range passed in.
     */
    template<class I>
    Rgb(I i, const I end) : RGBValue<T>(i, end) { }

    /**
     * equality of RGB values
     */
    bool operator==(const Rgb<T>& other) const {
      return (red() == other.red() &&
              green() == other.green() &&
              blue() == other.blue());
    }

    /* This is totally arbitrary, and doesn't make sense in terms
       of "colors", but it will make using RGB as a key in a std::map
       work.
    */
    bool operator<(const Rgb<T>& other) const {
      /* This does not work on all platforms and compilers:
      const typename vigra::NumericTraits<T>::Promote s = 
	(typename vigra::NumericTraits<T>::Promote)vigra::NumericTraits<T>::max;
      const typename vigra::NumericTraits<T>::Promote s2 = s * s;
      return (red() * s2 + green() * s + blue() <
	      other.red() * s2 + other.green() * s + other.blue());
      */
      if (red() < other.red()) return true;
      if (red() > other.red()) return false;
      if (green() < other.green()) return true;
      if (green() > other.green()) return false;
      if (blue() < other.blue()) return true;
      return false;
    }

    /// Set the red component to the passed in value.
    void red(T v) {
      this->setRed(v);
    }

    /// Set the green component to the passed in value.
    void green(T v) {
      this->setGreen(v);
    }

    /// Set the blue component to the passed in value.
    void blue(T v) {
      this->setBlue(v);
    }

    /// Retrieve the red component - the returned value is an lvalue.
    T const & red() const {
      return data_[0];
    }

    /// Retrieve the green component - the returned value is an lvalue.
    T const & green() const {
      return data_[1];
    }

    /// Retrieve the blue component - the returned value is an lvalue.
    T const & blue() const {
      return data_[2];
    }

    /// Return the hue of this pixel.
    FloatPixel const hue() {
      FloatPixel maxc = (FloatPixel)std::max(data_[0], std::max(data_[1], data_[2]));
      FloatPixel minc = (FloatPixel)std::min(data_[0], std::min(data_[1], data_[2]));
      if (minc == maxc)
	return 0;
      FloatPixel den = (maxc - minc);
      FloatPixel rc = (maxc - data_[0]) / den;
      FloatPixel gc = (maxc - data_[1]) / den;
      FloatPixel bc = (maxc - data_[2]) / den;
      FloatPixel h;
      if (data_[0] == maxc)
	h = bc - gc;
      else if (data_[1] == maxc)
	h = 2.0 + rc - bc;
      else
	h = 4.0 + gc - rc;
      h /= 6.0;
      h -= floor(h);
      return h;
    }

    /// Return the saturation of this pixel
    FloatPixel const saturation() {
      FloatPixel maxc = (FloatPixel)std::max(data_[0], std::max(data_[1], data_[2]));
      FloatPixel minc = (FloatPixel)std::min(data_[0], std::min(data_[1], data_[2]));
      if (minc == maxc)
	return 0;
      return (maxc - minc) / maxc;
    }

    /// Return the value of this pixel (max of RGB)
    FloatPixel const value() {
      return (FloatPixel)((float)(std::max(data_[0], std::max(data_[1], data_[2])))/255.0);
    }

    // conversion to CIE color space XYZ
    FloatPixel const cie_x() {
      RGB2XYZFunctor<FloatPixel> rgb2xyz_func;
      RGB2XYZFunctor<FloatPixel>::result_type xyz;
      xyz = rgb2xyz_func( RGBValue<FloatPixel>(data_[0], data_[1], data_[2]) );
      return xyz[0];
    }
    FloatPixel const cie_y() {
      RGB2XYZFunctor<FloatPixel> rgb2xyz_func;
      RGB2XYZFunctor<FloatPixel>::result_type xyz;
      xyz = rgb2xyz_func( RGBValue<FloatPixel>(data_[0], data_[1], data_[2]) );
      return xyz[1];
	}
    FloatPixel const cie_z() {
      RGB2XYZFunctor<FloatPixel> rgb2xyz_func;
      RGB2XYZFunctor<FloatPixel>::result_type xyz;
      xyz = rgb2xyz_func( RGBValue<FloatPixel>(data_[0], data_[1], data_[2]) );
      return xyz[2];
    }

    // conversion to CIE color space Lab
	FloatPixel const cie_Lab_L() {
      RGB2LabFunctor<FloatPixel> rgb2lab_func;
      RGB2LabFunctor<FloatPixel>::result_type lab;
      lab = rgb2lab_func( RGBValue<FloatPixel>(data_[0], data_[1], data_[2]) );
      return lab[0];
    }
	FloatPixel const cie_Lab_a() {
      RGB2LabFunctor<FloatPixel> rgb2lab_func;
      RGB2LabFunctor<FloatPixel>::result_type lab;
      lab = rgb2lab_func( RGBValue<FloatPixel>(data_[0], data_[1], data_[2]) );
      return lab[1];
    }
	FloatPixel const cie_Lab_b() {
      RGB2LabFunctor<FloatPixel> rgb2lab_func;
      RGB2LabFunctor<FloatPixel>::result_type lab;
      lab = rgb2lab_func( RGBValue<FloatPixel>(data_[0], data_[1], data_[2]) );
      return lab[2];
    }
	  
    GreyScalePixel const cyan() {
      return std::numeric_limits<T>::max() - data_[0];
    }
    GreyScalePixel const magenta() {
      return std::numeric_limits<T>::max() - data_[1];
    }
    GreyScalePixel const yellow() {
      return std::numeric_limits<T>::max() - data_[2];
    }

//     /// Conversion operator to a FloatPixel
//     operator FloatPixel() {
//       return FloatPixel(luminance());
//     }

//     /// Conversion operator to a ComplexPixel
//     operator ComplexPixel() {
//       ComplexPixel temp;
//       temp.real = luminance();
//       temp.imag = 0;
//       return ComplexPixel(temp);
//     }

//     /// Conversion operator to a GreyScalePixel
//     operator GreyScalePixel() {
//       return GreyScalePixel(luminance());
//     }

//     /// Conversion operator to a Grey16Pixel
//     operator Grey16Pixel() {
//       return Grey16Pixel(luminance());
//     }

//     /// Conversion operator to a OneBitPixel
//     operator OneBitPixel() {
//       if (luminance())
// 	return 1;
//       else
// 	return 0;
//     }
  };

  /// This is the standard form of the RGB pixels
  typedef Rgb<GreyScalePixel> RGBPixel;

  
  
  /*
   * This is a test for black/white regardless of the pixel type. For some
   * pixel types this test is complicated and this also allows us to use 0
   * for white in OneBit images and max for white in others without sacrificing
   * generality in the algorithms.
   *
   * This default implementation is here mainly for CCProxies (see
   * connected_components.hpp).  Most of the real implementations are
   * further down.
   */
  template<class T>
  inline bool is_black(T value) {
    return value;
  }

  /*
   * This is here for the same reason as is_black above.
   */
  template<class T>
  inline bool is_white(T value) {
    return !value;
  }

  /*
   * pixel_traits allows us to find out certain properties of pixels in a generic
   * way. Again, this is primarily to allow the easy switching between min is white
   * and min is black representations for different pixel types.
   */
  template<class T>
  struct pixel_traits {
    static T white() {
      return std::numeric_limits<T>::max();
    }
    static T black() {
      return 0;
    }
    static T default_value() {
      return white();
    }
  };

  /*
   * Helper functions to get black/white from a given T that has a value_type
   * member that is a pixel - i.e.
   *
   * DenseImage<OneBitPixel> ob;
   * black(ob);
   *
   * The pixel_traits syntax is just too horrible to make users go through to
   * get white/black.  From within a template function it looks like:
   *
   * Gamera::pixel_traits<typename T::value_type>::white();
   *
   */
  template<class T>
  typename T::value_type black(T& container) {
    return pixel_traits<typename T::value_type>::black();
  }

  template<class T>
  typename T::value_type white(T& container) {
    return pixel_traits<typename T::value_type>::white();
  }


  /*
    Everything beyond this point is implementation
   */

  // Specializations for black/white
  template<>
  inline bool is_black<FloatPixel>(FloatPixel value) {
    return value <= 0;
  }

  template<>
  inline bool is_black<GreyScalePixel>(GreyScalePixel value) {
    return value == 0;
  }

  template<>
  inline bool is_black<Grey16Pixel>(Grey16Pixel value) {
    return value == 0;
  }

  template<>
  inline bool is_black<RGBPixel>(RGBPixel value) {
    return (value.green() == 0 && value.red() == 0 && value.blue() == 0);
  }

  template<>
  inline bool is_black<OneBitPixel>(OneBitPixel value) {
    return value != 0;
  }

  template<>
  inline bool is_white<FloatPixel>(FloatPixel value) {
    return (value == std::numeric_limits<GreyScalePixel>::max());
  }

  template<>
  inline bool is_white<GreyScalePixel>(GreyScalePixel value) {
    return (value == std::numeric_limits<GreyScalePixel>::max());
  }

  template<>
  inline bool is_white<Grey16Pixel>(Grey16Pixel value) {
    return (value == std::numeric_limits<Grey16Pixel>::max());
  }

  template<>
  inline bool is_white<RGBPixel>(RGBPixel value) {
    return (value.red() == std::numeric_limits<GreyScalePixel>::max()
	    && value.green() == std::numeric_limits<GreyScalePixel>::max()
	    && value.blue() == std::numeric_limits<GreyScalePixel>::max());
  }

  template<>
  inline bool is_white<OneBitPixel>(OneBitPixel value) {
    return value == 0;
  }

  /*
    Specialization for pixel_traits
  */

  template<>
  inline OneBitPixel pixel_traits<OneBitPixel>::black() {
    return 1;
  }

  template<>
  inline OneBitPixel pixel_traits<OneBitPixel>::white() {
    return 0;
  }

  template<>
  inline Grey16Pixel pixel_traits<Grey16Pixel>::white() {
    return 65535;  // 2^16 - 1
  }

  template<>
  inline RGBPixel pixel_traits<RGBPixel>::black() {
    return RGBPixel(0, 0, 0);
  }
  
  template<>
  inline RGBPixel pixel_traits<RGBPixel>::white() {
    return RGBPixel(std::numeric_limits<GreyScalePixel>::max(),
		    std::numeric_limits<GreyScalePixel>::max(),
		    std::numeric_limits<GreyScalePixel>::max());
  }

  template<>
  inline FloatPixel pixel_traits<FloatPixel>::default_value() {
    return 0.0;
  }

  template<>
  inline ComplexPixel pixel_traits<ComplexPixel>::white() {
    return ComplexPixel(std::numeric_limits<double>::max(), 0.0);
  }

  template<>
  inline ComplexPixel pixel_traits<ComplexPixel>::black() {
    return ComplexPixel(0.0, 0.0);
  }

  template<>
  inline ComplexPixel pixel_traits<ComplexPixel>::default_value() {
    return pixel_traits<ComplexPixel>::black();
  }

  /*
   * Inversion of pixel values
   *
   * Generically invert pixel values.
   */

  inline FloatPixel invert(FloatPixel value) {
    // Hard to know what makes sense here... MGD
    return -value;
  }

  inline ComplexPixel invert(ComplexPixel value) {
    return -value;
  }

  inline GreyScalePixel invert(GreyScalePixel value) {
    return std::numeric_limits<GreyScalePixel>::max() - value;
  }

  inline Grey16Pixel invert(Grey16Pixel value) {
    return std::numeric_limits<Grey16Pixel>::max() - value;
  }

  inline RGBPixel invert(RGBPixel value) {
    return RGBPixel(std::numeric_limits<RGBPixel::value_type>::max() -
		    value.red(),
		    std::numeric_limits<RGBPixel::value_type>::max() -
		    value.green(),
		    std::numeric_limits<RGBPixel::value_type>::max() -
		    value.blue());
  }

  inline OneBitPixel invert(OneBitPixel value) {
    if (is_white(value))
      return pixel_traits<OneBitPixel>::black();
    else
      return pixel_traits<OneBitPixel>::white();
  }

  /*
   * Blend pixels together.
   */
  inline FloatPixel blend(FloatPixel original, FloatPixel add, double alpha) {
    return alpha * original + (1.0 - alpha) * add; 
  }

  inline ComplexPixel blend(ComplexPixel original, ComplexPixel add, double alpha) {
    return alpha * original + (1.0 - alpha) * add;
  }

  inline GreyScalePixel blend(GreyScalePixel original, GreyScalePixel add, double alpha) {
    return (GreyScalePixel)(alpha * double(original) + (1.0 - alpha) * double(add));
  }

  inline Grey16Pixel blend(Grey16Pixel original, GreyScalePixel add, double alpha) {
    return (Grey16Pixel)(alpha * original + (1.0 - alpha) * add);
  }

  inline RGBPixel blend(RGBPixel original, RGBPixel add, double alpha) {
    double inv_alpha = 1.0 - alpha;
    return RGBPixel(GreyScalePixel(original.red() * alpha + add.red() * inv_alpha),
		    GreyScalePixel(original.green() * alpha + add.green() * inv_alpha),
		    GreyScalePixel(original.blue() * alpha + add.blue() * inv_alpha));
  }

  inline OneBitPixel blend(OneBitPixel original, RGBPixel add, double alpha) {
    if (alpha > 0.5)
      return original;
    return add.luminance();
  }

};

#endif