1 | /* |
2 | Copyright 2005-2007 Adobe Systems Incorporated |
3 | |
4 | Use, modification and distribution are subject to the Boost Software License, |
5 | Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at |
6 | http://www.boost.org/LICENSE_1_0.txt). |
7 | |
8 | See http://stlab.adobe.com/gil for most recent version including documentation. |
9 | */ |
10 | |
11 | /*************************************************************************************************/ |
12 | |
13 | #ifndef GIL_COLOR_BASE_HPP |
14 | #define GIL_COLOR_BASE_HPP |
15 | |
16 | //////////////////////////////////////////////////////////////////////////////////////// |
17 | /// \file |
18 | /// \brief pixel class and related utilities |
19 | /// \author Lubomir Bourdev and Hailin Jin \n |
20 | /// Adobe Systems Incorporated |
21 | /// \date 2005-2007 \n Last updated on May 6, 2007 |
22 | /// |
23 | //////////////////////////////////////////////////////////////////////////////////////// |
24 | |
25 | #include <cassert> |
26 | #include <boost/mpl/range_c.hpp> |
27 | #include <boost/mpl/size.hpp> |
28 | #include <boost/mpl/vector_c.hpp> |
29 | #include <boost/type_traits.hpp> |
30 | #include <boost/utility/enable_if.hpp> |
31 | |
32 | #include "gil_config.hpp" |
33 | #include "utilities.hpp" |
34 | #include "gil_concept.hpp" |
35 | |
36 | namespace boost { namespace gil { |
37 | |
38 | // Forward-declare |
39 | template <typename P> P* memunit_advanced(const P* p, std::ptrdiff_t diff); |
40 | |
41 | // Forward-declare semantic_at_c |
42 | template <int K, typename ColorBase> |
43 | typename disable_if<is_const<ColorBase>,typename kth_semantic_element_reference_type<ColorBase,K>::type>::type semantic_at_c(ColorBase& p); |
44 | template <int K, typename ColorBase> |
45 | typename kth_semantic_element_const_reference_type<ColorBase,K>::type semantic_at_c(const ColorBase& p); |
46 | |
47 | // Forward declare element_reference_type |
48 | template <typename ColorBase> struct element_reference_type; |
49 | template <typename ColorBase> struct element_const_reference_type; |
50 | template <typename ColorBase, int K> struct kth_element_type; |
51 | template <typename ColorBase, int K> struct kth_element_type<const ColorBase,K> : public kth_element_type<ColorBase,K> {}; |
52 | template <typename ColorBase, int K> struct kth_element_reference_type; |
53 | template <typename ColorBase, int K> struct kth_element_reference_type<const ColorBase,K> : public kth_element_reference_type<ColorBase,K> {}; |
54 | template <typename ColorBase, int K> struct kth_element_const_reference_type; |
55 | template <typename ColorBase, int K> struct kth_element_const_reference_type<const ColorBase,K> : public kth_element_const_reference_type<ColorBase,K> {}; |
56 | |
57 | namespace detail { |
58 | |
59 | template <typename DstLayout, typename SrcLayout, int K> |
60 | struct mapping_transform |
61 | : public mpl::at<typename SrcLayout::channel_mapping_t, |
62 | typename detail::type_to_index<typename DstLayout::channel_mapping_t,mpl::integral_c<int,K> >::type |
63 | >::type {}; |
64 | |
65 | /// \defgroup ColorBaseModelHomogeneous detail::homogeneous_color_base |
66 | /// \ingroup ColorBaseModel |
67 | /// \brief A homogeneous color base holding one color element. Models HomogeneousColorBaseConcept or HomogeneousColorBaseValueConcept |
68 | /// If the element type models Regular, this class models HomogeneousColorBaseValueConcept. |
69 | |
70 | |
71 | /// \brief A homogeneous color base holding one color element. Models HomogeneousColorBaseConcept or HomogeneousColorBaseValueConcept |
72 | /// \ingroup ColorBaseModelHomogeneous |
73 | template <typename Element, typename Layout> |
74 | struct homogeneous_color_base<Element,Layout,1> { |
75 | private: |
76 | Element _v0; |
77 | public: |
78 | typedef Layout layout_t; |
79 | typename element_reference_type<homogeneous_color_base>::type at(mpl::int_<0>) { return _v0; } |
80 | typename element_const_reference_type<homogeneous_color_base>::type at(mpl::int_<0>) const { return _v0; } |
81 | |
82 | homogeneous_color_base() {} |
83 | homogeneous_color_base(Element v) : _v0(v) {} |
84 | |
85 | // grayscale pixel values are convertible to channel type |
86 | operator Element () const { return _v0; } |
87 | |
88 | template <typename E2, typename L2> homogeneous_color_base(const homogeneous_color_base<E2,L2,1>& c) : _v0(at_c<0>(c)) {} |
89 | }; |
90 | |
91 | |
92 | /// \brief A homogeneous color base holding two color elements. Models HomogeneousColorBaseConcept or HomogeneousColorBaseValueConcept |
93 | /// \ingroup ColorBaseModelHomogeneous |
94 | template <typename Element, typename Layout> |
95 | struct homogeneous_color_base<Element,Layout,2> { |
96 | private: |
97 | Element _v0, _v1; |
98 | public: |
99 | typedef Layout layout_t; |
100 | typename element_reference_type<homogeneous_color_base>::type at(mpl::int_<0>) { return _v0; } |
101 | typename element_const_reference_type<homogeneous_color_base>::type at(mpl::int_<0>) const { return _v0; } |
102 | typename element_reference_type<homogeneous_color_base>::type at(mpl::int_<1>) { return _v1; } |
103 | typename element_const_reference_type<homogeneous_color_base>::type at(mpl::int_<1>) const { return _v1; } |
104 | |
105 | homogeneous_color_base() {} |
106 | explicit homogeneous_color_base(Element v) : _v0(v), _v1(v) {} |
107 | homogeneous_color_base(Element v0, Element v1) : _v0(v0), _v1(v1) {} |
108 | |
109 | template <typename E2, typename L2> homogeneous_color_base(const homogeneous_color_base<E2,L2,2>& c) : |
110 | _v0(at_c<mapping_transform<Layout,L2,0>::value>(c)), |
111 | _v1(at_c<mapping_transform<Layout,L2,1>::value>(c)) {} |
112 | |
113 | // Support for l-value reference proxy copy construction |
114 | template <typename E2, typename L2> homogeneous_color_base( homogeneous_color_base<E2,L2,2>& c) : |
115 | _v0(at_c<mapping_transform<Layout,L2,0>::value>(c)), |
116 | _v1(at_c<mapping_transform<Layout,L2,1>::value>(c)) {} |
117 | |
118 | // Support for planar_pixel_iterator construction and dereferencing |
119 | template <typename P> homogeneous_color_base(P* p,bool) : |
120 | _v0(&semantic_at_c<0>(*p)), |
121 | _v1(&semantic_at_c<1>(*p)) {} |
122 | template <typename Ref> Ref deref() const { |
123 | return Ref(*semantic_at_c<0>(*this), |
124 | *semantic_at_c<1>(*this)); } |
125 | |
126 | // Support for planar_pixel_reference offset constructor |
127 | template <typename Ptr> homogeneous_color_base(const Ptr& ptr, std::ptrdiff_t diff) |
128 | : _v0(*memunit_advanced(semantic_at_c<0>(ptr),diff)), |
129 | _v1(*memunit_advanced(semantic_at_c<1>(ptr),diff)) {} |
130 | |
131 | // Support for planar_pixel_reference operator[] |
132 | Element at_c_dynamic(std::size_t i) const { |
133 | if (i==0) return _v0; |
134 | return _v1; |
135 | } |
136 | }; |
137 | |
138 | /// \brief A homogeneous color base holding three color elements. Models HomogeneousColorBaseConcept or HomogeneousColorBaseValueConcept |
139 | /// \ingroup ColorBaseModelHomogeneous |
140 | template <typename Element, typename Layout> |
141 | struct homogeneous_color_base<Element,Layout,3> { |
142 | private: |
143 | Element _v0, _v1, _v2; |
144 | public: |
145 | typedef Layout layout_t; |
146 | typename element_reference_type<homogeneous_color_base>::type at(mpl::int_<0>) { return _v0; } |
147 | typename element_const_reference_type<homogeneous_color_base>::type at(mpl::int_<0>) const { return _v0; } |
148 | typename element_reference_type<homogeneous_color_base>::type at(mpl::int_<1>) { return _v1; } |
149 | typename element_const_reference_type<homogeneous_color_base>::type at(mpl::int_<1>) const { return _v1; } |
150 | typename element_reference_type<homogeneous_color_base>::type at(mpl::int_<2>) { return _v2; } |
151 | typename element_const_reference_type<homogeneous_color_base>::type at(mpl::int_<2>) const { return _v2; } |
152 | |
153 | homogeneous_color_base() {} |
154 | explicit homogeneous_color_base(Element v) : _v0(v), _v1(v), _v2(v) {} |
155 | homogeneous_color_base(Element v0, Element v1, Element v2) : _v0(v0), _v1(v1), _v2(v2) {} |
156 | |
157 | template <typename E2, typename L2> homogeneous_color_base(const homogeneous_color_base<E2,L2,3>& c) : |
158 | _v0(gil::at_c<mapping_transform<Layout,L2,0>::value>(c)), |
159 | _v1(gil::at_c<mapping_transform<Layout,L2,1>::value>(c)), |
160 | _v2(gil::at_c<mapping_transform<Layout,L2,2>::value>(c)) {} |
161 | |
162 | // Support for l-value reference proxy copy construction |
163 | template <typename E2, typename L2> homogeneous_color_base( homogeneous_color_base<E2,L2,3>& c) : |
164 | _v0(gil::at_c<mapping_transform<Layout,L2,0>::value>(c)), |
165 | _v1(gil::at_c<mapping_transform<Layout,L2,1>::value>(c)), |
166 | _v2(gil::at_c<mapping_transform<Layout,L2,2>::value>(c)) {} |
167 | |
168 | // Support for planar_pixel_iterator construction and dereferencing |
169 | template <typename P> homogeneous_color_base(P* p,bool) : |
170 | _v0(&semantic_at_c<0>(*p)), |
171 | _v1(&semantic_at_c<1>(*p)), |
172 | _v2(&semantic_at_c<2>(*p)) {} |
173 | template <typename Ref> Ref deref() const { |
174 | return Ref(*semantic_at_c<0>(*this), |
175 | *semantic_at_c<1>(*this), |
176 | *semantic_at_c<2>(*this)); } |
177 | |
178 | // Support for planar_pixel_reference offset constructor |
179 | template <typename Ptr> homogeneous_color_base(const Ptr& ptr, std::ptrdiff_t diff) |
180 | : _v0(*memunit_advanced(semantic_at_c<0>(ptr),diff)), |
181 | _v1(*memunit_advanced(semantic_at_c<1>(ptr),diff)), |
182 | _v2(*memunit_advanced(semantic_at_c<2>(ptr),diff)) {} |
183 | |
184 | // Support for planar_pixel_reference operator[] |
185 | Element at_c_dynamic(std::size_t i) const { |
186 | switch (i) { |
187 | case 0: return _v0; |
188 | case 1: return _v1; |
189 | } |
190 | return _v2; |
191 | } |
192 | }; |
193 | |
194 | /// \brief A homogeneous color base holding four color elements. Models HomogeneousColorBaseConcept or HomogeneousColorBaseValueConcept |
195 | /// \ingroup ColorBaseModelHomogeneous |
196 | template <typename Element, typename Layout> |
197 | struct homogeneous_color_base<Element,Layout,4> { |
198 | private: |
199 | Element _v0, _v1, _v2, _v3; |
200 | public: |
201 | typedef Layout layout_t; |
202 | typename element_reference_type<homogeneous_color_base>::type at(mpl::int_<0>) { return _v0; } |
203 | typename element_const_reference_type<homogeneous_color_base>::type at(mpl::int_<0>) const { return _v0; } |
204 | typename element_reference_type<homogeneous_color_base>::type at(mpl::int_<1>) { return _v1; } |
205 | typename element_const_reference_type<homogeneous_color_base>::type at(mpl::int_<1>) const { return _v1; } |
206 | typename element_reference_type<homogeneous_color_base>::type at(mpl::int_<2>) { return _v2; } |
207 | typename element_const_reference_type<homogeneous_color_base>::type at(mpl::int_<2>) const { return _v2; } |
208 | typename element_reference_type<homogeneous_color_base>::type at(mpl::int_<3>) { return _v3; } |
209 | typename element_const_reference_type<homogeneous_color_base>::type at(mpl::int_<3>) const { return _v3; } |
210 | homogeneous_color_base() {} |
211 | explicit homogeneous_color_base(Element v) : _v0(v), _v1(v), _v2(v), _v3(v) {} |
212 | homogeneous_color_base(Element v0, Element v1, Element v2, Element v3) : _v0(v0), _v1(v1), _v2(v2), _v3(v3) {} |
213 | |
214 | template <typename E2, typename L2> homogeneous_color_base(const homogeneous_color_base<E2,L2,4>& c) : |
215 | _v0(at_c<mapping_transform<Layout,L2,0>::value>(c)), |
216 | _v1(at_c<mapping_transform<Layout,L2,1>::value>(c)), |
217 | _v2(at_c<mapping_transform<Layout,L2,2>::value>(c)), |
218 | _v3(at_c<mapping_transform<Layout,L2,3>::value>(c)) {} |
219 | |
220 | // Support for l-value reference proxy copy construction |
221 | template <typename E2, typename L2> homogeneous_color_base( homogeneous_color_base<E2,L2,4>& c) : |
222 | _v0(at_c<mapping_transform<Layout,L2,0>::value>(c)), |
223 | _v1(at_c<mapping_transform<Layout,L2,1>::value>(c)), |
224 | _v2(at_c<mapping_transform<Layout,L2,2>::value>(c)), |
225 | _v3(at_c<mapping_transform<Layout,L2,3>::value>(c)) {} |
226 | |
227 | // Support for planar_pixel_iterator construction and dereferencing |
228 | template <typename P> homogeneous_color_base(P* p,bool) : |
229 | _v0(&semantic_at_c<0>(*p)), |
230 | _v1(&semantic_at_c<1>(*p)), |
231 | _v2(&semantic_at_c<2>(*p)), |
232 | _v3(&semantic_at_c<3>(*p)) {} |
233 | |
234 | template <typename Ref> Ref deref() const { |
235 | return Ref(*semantic_at_c<0>(*this), |
236 | *semantic_at_c<1>(*this), |
237 | *semantic_at_c<2>(*this), |
238 | *semantic_at_c<3>(*this)); } |
239 | |
240 | // Support for planar_pixel_reference offset constructor |
241 | template <typename Ptr> homogeneous_color_base(const Ptr& ptr, std::ptrdiff_t diff) |
242 | : _v0(*memunit_advanced(semantic_at_c<0>(ptr),diff)), |
243 | _v1(*memunit_advanced(semantic_at_c<1>(ptr),diff)), |
244 | _v2(*memunit_advanced(semantic_at_c<2>(ptr),diff)), |
245 | _v3(*memunit_advanced(semantic_at_c<3>(ptr),diff)) {} |
246 | |
247 | // Support for planar_pixel_reference operator[] |
248 | Element at_c_dynamic(std::size_t i) const { |
249 | switch (i) { |
250 | case 0: return _v0; |
251 | case 1: return _v1; |
252 | case 2: return _v2; |
253 | } |
254 | return _v3; |
255 | } |
256 | }; |
257 | |
258 | /// \brief A homogeneous color base holding five color elements. Models HomogeneousColorBaseConcept or HomogeneousColorBaseValueConcept |
259 | /// \ingroup ColorBaseModelHomogeneous |
260 | template <typename Element, typename Layout> |
261 | struct homogeneous_color_base<Element,Layout,5> { |
262 | private: |
263 | Element _v0, _v1, _v2, _v3, _v4; |
264 | public: |
265 | typedef Layout layout_t; |
266 | typename element_reference_type<homogeneous_color_base>::type at(mpl::int_<0>) { return _v0; } |
267 | typename element_const_reference_type<homogeneous_color_base>::type at(mpl::int_<0>) const { return _v0; } |
268 | typename element_reference_type<homogeneous_color_base>::type at(mpl::int_<1>) { return _v1; } |
269 | typename element_const_reference_type<homogeneous_color_base>::type at(mpl::int_<1>) const { return _v1; } |
270 | typename element_reference_type<homogeneous_color_base>::type at(mpl::int_<2>) { return _v2; } |
271 | typename element_const_reference_type<homogeneous_color_base>::type at(mpl::int_<2>) const { return _v2; } |
272 | typename element_reference_type<homogeneous_color_base>::type at(mpl::int_<3>) { return _v3; } |
273 | typename element_const_reference_type<homogeneous_color_base>::type at(mpl::int_<3>) const { return _v3; } |
274 | typename element_reference_type<homogeneous_color_base>::type at(mpl::int_<4>) { return _v4; } |
275 | typename element_const_reference_type<homogeneous_color_base>::type at(mpl::int_<4>) const { return _v4; } |
276 | homogeneous_color_base() {} |
277 | explicit homogeneous_color_base(Element v) : _v0(v), _v1(v), _v2(v), _v3(v), _v4(v) {} |
278 | homogeneous_color_base(Element v0, Element v1, Element v2, Element v3, Element v4) : _v0(v0), _v1(v1), _v2(v2), _v3(v3), _v4(v4) {} |
279 | |
280 | template <typename E2, typename L2> homogeneous_color_base(const homogeneous_color_base<E2,L2,5>& c) : |
281 | _v0(at_c<mapping_transform<Layout,L2,0>::value>(c)), |
282 | _v1(at_c<mapping_transform<Layout,L2,1>::value>(c)), |
283 | _v2(at_c<mapping_transform<Layout,L2,2>::value>(c)), |
284 | _v3(at_c<mapping_transform<Layout,L2,3>::value>(c)), |
285 | _v4(at_c<mapping_transform<Layout,L2,4>::value>(c)) {} |
286 | |
287 | // Support for l-value reference proxy copy construction |
288 | template <typename E2, typename L2> homogeneous_color_base( homogeneous_color_base<E2,L2,5>& c) : |
289 | _v0(at_c<mapping_transform<Layout,L2,0>::value>(c)), |
290 | _v1(at_c<mapping_transform<Layout,L2,1>::value>(c)), |
291 | _v2(at_c<mapping_transform<Layout,L2,2>::value>(c)), |
292 | _v3(at_c<mapping_transform<Layout,L2,3>::value>(c)), |
293 | _v4(at_c<mapping_transform<Layout,L2,4>::value>(c)) {} |
294 | |
295 | // Support for planar_pixel_iterator construction and dereferencing |
296 | template <typename P> homogeneous_color_base(P* p,bool) : |
297 | _v0(&semantic_at_c<0>(*p)), |
298 | _v1(&semantic_at_c<1>(*p)), |
299 | _v2(&semantic_at_c<2>(*p)), |
300 | _v3(&semantic_at_c<3>(*p)), |
301 | _v4(&semantic_at_c<4>(*p)) {} |
302 | |
303 | template <typename Ref> Ref deref() const { |
304 | return Ref(*semantic_at_c<0>(*this), |
305 | *semantic_at_c<1>(*this), |
306 | *semantic_at_c<2>(*this), |
307 | *semantic_at_c<3>(*this), |
308 | *semantic_at_c<4>(*this)); } |
309 | |
310 | // Support for planar_pixel_reference offset constructor |
311 | template <typename Ptr> homogeneous_color_base(const Ptr& ptr, std::ptrdiff_t diff) |
312 | : _v0(*memunit_advanced(semantic_at_c<0>(ptr),diff)), |
313 | _v1(*memunit_advanced(semantic_at_c<1>(ptr),diff)), |
314 | _v2(*memunit_advanced(semantic_at_c<2>(ptr),diff)), |
315 | _v3(*memunit_advanced(semantic_at_c<3>(ptr),diff)), |
316 | _v4(*memunit_advanced(semantic_at_c<4>(ptr),diff)) {} |
317 | |
318 | // Support for planar_pixel_reference operator[] |
319 | Element at_c_dynamic(std::size_t i) const { |
320 | switch (i) { |
321 | case 0: return _v0; |
322 | case 1: return _v1; |
323 | case 2: return _v2; |
324 | case 3: return _v3; |
325 | } |
326 | return _v4; |
327 | } |
328 | }; |
329 | |
330 | // The following way of casting adjacent channels (the contents of color_base) into an array appears to be unsafe |
331 | // -- there is no guarantee that the compiler won't add any padding between adjacent channels. |
332 | // Note, however, that GIL _must_ be compiled with compiler settings ensuring there is no padding in the color base structs. |
333 | // This is because the color base structs must model the interleaved organization in memory. In other words, the client may |
334 | // have existing RGB image in the form "RGBRGBRGB..." and we must be able to represent it with an array of RGB color bases (i.e. RGB pixels) |
335 | // with no padding. We have tested with char/int/float/double channels on gcc and VC and have so far discovered no problem. |
336 | // We have even tried using strange channels consisting of short + char (3 bytes). With the default 4-byte alignment on VC, the size |
337 | // of this channel is padded to 4 bytes, so an RGB pixel of it will be 4x3=12 bytes. The code below will still work properly. |
338 | // However, the client must nevertheless ensure that proper compiler settings are used for their compiler and their channel types. |
339 | |
340 | template <typename Element, typename Layout, int K> |
341 | typename element_reference_type<homogeneous_color_base<Element,Layout,K> >::type |
342 | dynamic_at_c(homogeneous_color_base<Element,Layout,K>& cb, std::size_t i) { |
343 | assert(i<K); |
344 | return (gil_reinterpret_cast<Element*>(&cb))[i]; |
345 | } |
346 | |
347 | template <typename Element, typename Layout, int K> |
348 | typename element_const_reference_type<homogeneous_color_base<Element,Layout,K> >::type |
349 | dynamic_at_c(const homogeneous_color_base<Element,Layout,K>& cb, std::size_t i) { |
350 | assert(i<K); |
351 | return (gil_reinterpret_cast_c<const Element*>(&cb))[i]; |
352 | } |
353 | |
354 | template <typename Element, typename Layout, int K> |
355 | typename element_reference_type<homogeneous_color_base<Element&,Layout,K> >::type |
356 | dynamic_at_c(const homogeneous_color_base<Element&,Layout,K>& cb, std::size_t i) { |
357 | assert(i<K); |
358 | return cb.at_c_dynamic(i); |
359 | } |
360 | |
361 | template <typename Element, typename Layout, int K> |
362 | typename element_const_reference_type<homogeneous_color_base<const Element&,Layout,K> >::type |
363 | dynamic_at_c(const homogeneous_color_base<const Element&,Layout,K>& cb, std::size_t i) { |
364 | assert(i<K); |
365 | return cb.at_c_dynamic(i); |
366 | } |
367 | |
368 | |
369 | } // namespace detail |
370 | |
371 | template <typename Element, typename Layout, int K1, int K> |
372 | struct kth_element_type<detail::homogeneous_color_base<Element,Layout,K1>, K> { |
373 | typedef Element type; |
374 | }; |
375 | |
376 | template <typename Element, typename Layout, int K1, int K> |
377 | struct kth_element_reference_type<detail::homogeneous_color_base<Element,Layout,K1>, K> : public add_reference<Element> {}; |
378 | |
379 | template <typename Element, typename Layout, int K1, int K> |
380 | struct kth_element_const_reference_type<detail::homogeneous_color_base<Element,Layout,K1>, K> : public add_reference<typename add_const<Element>::type> {}; |
381 | |
382 | /// \brief Provides mutable access to the K-th element, in physical order |
383 | /// \ingroup ColorBaseModelHomogeneous |
384 | template <int K, typename E, typename L, int N> inline |
385 | typename add_reference<E>::type |
386 | at_c( detail::homogeneous_color_base<E,L,N>& p) { return p.at(mpl::int_<K>()); } |
387 | |
388 | /// \brief Provides constant access to the K-th element, in physical order |
389 | /// \ingroup ColorBaseModelHomogeneous |
390 | template <int K, typename E, typename L, int N> inline |
391 | typename add_reference<typename add_const<E>::type>::type |
392 | at_c(const detail::homogeneous_color_base<E,L,N>& p) { return p.at(mpl::int_<K>()); } |
393 | |
394 | namespace detail { |
395 | struct swap_fn { |
396 | template <typename T> void operator()(T& x, T& y) const { |
397 | using std::swap; |
398 | swap(x,y); |
399 | } |
400 | }; |
401 | } |
402 | template <typename E, typename L, int N> inline |
403 | void swap(detail::homogeneous_color_base<E,L,N>& x, detail::homogeneous_color_base<E,L,N>& y) { |
404 | static_for_each(x,y,detail::swap_fn()); |
405 | } |
406 | |
407 | |
408 | } } // namespace boost::gil |
409 | |
410 | #endif |
411 | |