/src/ogre/OgreMain/src/OgreImageResampler.h
Line | Count | Source |
1 | | /* |
2 | | ----------------------------------------------------------------------------- |
3 | | This source file is part of OGRE |
4 | | (Object-oriented Graphics Rendering Engine) |
5 | | For the latest info, see http://www.ogre3d.org/ |
6 | | |
7 | | Copyright (c) 2000-2014 Torus Knot Software Ltd |
8 | | |
9 | | Permission is hereby granted, free of charge, to any person obtaining a copy |
10 | | of this software and associated documentation files (the "Software"), to deal |
11 | | in the Software without restriction, including without limitation the rights |
12 | | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
13 | | copies of the Software, and to permit persons to whom the Software is |
14 | | furnished to do so, subject to the following conditions: |
15 | | |
16 | | The above copyright notice and this permission notice shall be included in |
17 | | all copies or substantial portions of the Software. |
18 | | |
19 | | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
20 | | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
21 | | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
22 | | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
23 | | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
24 | | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
25 | | THE SOFTWARE. |
26 | | ----------------------------------------------------------------------------- |
27 | | */ |
28 | | #ifndef OGREIMAGERESAMPLER_H |
29 | | #define OGREIMAGERESAMPLER_H |
30 | | |
31 | | #include <algorithm> |
32 | | |
33 | | // this file is inlined into OgreImage.cpp! |
34 | | // do not include anywhere else. |
35 | | namespace Ogre { |
36 | | /** \addtogroup Core |
37 | | * @{ |
38 | | */ |
39 | | /** \addtogroup Image |
40 | | * @{ |
41 | | */ |
42 | | |
43 | | // variable name hints: |
44 | | // sx_48 = 16/48-bit fixed-point x-position in source |
45 | | // stepx = difference between adjacent sx_48 values |
46 | | // sx1 = lower-bound integer x-position in source |
47 | | // sx2 = upper-bound integer x-position in source |
48 | | // sxf = fractional weight between sx1 and sx2 |
49 | | // x,y,z = location of output pixel in destination |
50 | | |
51 | | // nearest-neighbor resampler, does not convert formats. |
52 | | // templated on bytes-per-pixel to allow compiler optimizations, such |
53 | | // as simplifying memcpy() and replacing multiplies with bitshifts |
54 | | template<unsigned int elemsize> struct NearestResampler { |
55 | 0 | static void scale(const PixelBox& src, const PixelBox& dst) { |
56 | | // assert(src.format == dst.format); |
57 | | |
58 | | // srcdata stays at beginning, pdst is a moving pointer |
59 | 0 | uchar* srcdata = (uchar*)src.getTopLeftFrontPixelPtr(); |
60 | 0 | uchar* pdst = (uchar*)dst.getTopLeftFrontPixelPtr(); |
61 | | |
62 | | // sx_48,sy_48,sz_48 represent current position in source |
63 | | // using 16/48-bit fixed precision, incremented by steps |
64 | 0 | uint64 stepx = ((uint64)src.getWidth() << 48) / dst.getWidth(); |
65 | 0 | uint64 stepy = ((uint64)src.getHeight() << 48) / dst.getHeight(); |
66 | 0 | uint64 stepz = ((uint64)src.getDepth() << 48) / dst.getDepth(); |
67 | | |
68 | | // note: ((stepz>>1) - 1) is an extra half-step increment to adjust |
69 | | // for the center of the destination pixel, not the top-left corner |
70 | 0 | uint64 sz_48 = (stepz >> 1) - 1; |
71 | 0 | for (size_t z = dst.front; z < dst.back; z++, sz_48 += stepz) { |
72 | 0 | size_t srczoff = (size_t)(sz_48 >> 48) * src.slicePitch; |
73 | | |
74 | 0 | uint64 sy_48 = (stepy >> 1) - 1; |
75 | 0 | for (size_t y = dst.top; y < dst.bottom; y++, sy_48 += stepy) { |
76 | 0 | size_t srcyoff = (size_t)(sy_48 >> 48) * src.rowPitch; |
77 | | |
78 | 0 | uint64 sx_48 = (stepx >> 1) - 1; |
79 | 0 | for (size_t x = dst.left; x < dst.right; x++, sx_48 += stepx) { |
80 | 0 | uchar* psrc = srcdata + |
81 | 0 | elemsize*((size_t)(sx_48 >> 48) + srcyoff + srczoff); |
82 | 0 | memcpy(pdst, psrc, elemsize); |
83 | 0 | pdst += elemsize; |
84 | 0 | } |
85 | 0 | pdst += elemsize*dst.getRowSkip(); |
86 | 0 | } |
87 | 0 | pdst += elemsize*dst.getSliceSkip(); |
88 | 0 | } |
89 | 0 | } Unexecuted instantiation: Ogre::NearestResampler<1u>::scale(Ogre::PixelBox const&, Ogre::PixelBox const&) Unexecuted instantiation: Ogre::NearestResampler<2u>::scale(Ogre::PixelBox const&, Ogre::PixelBox const&) Unexecuted instantiation: Ogre::NearestResampler<3u>::scale(Ogre::PixelBox const&, Ogre::PixelBox const&) Unexecuted instantiation: Ogre::NearestResampler<4u>::scale(Ogre::PixelBox const&, Ogre::PixelBox const&) Unexecuted instantiation: Ogre::NearestResampler<6u>::scale(Ogre::PixelBox const&, Ogre::PixelBox const&) Unexecuted instantiation: Ogre::NearestResampler<8u>::scale(Ogre::PixelBox const&, Ogre::PixelBox const&) Unexecuted instantiation: Ogre::NearestResampler<12u>::scale(Ogre::PixelBox const&, Ogre::PixelBox const&) Unexecuted instantiation: Ogre::NearestResampler<16u>::scale(Ogre::PixelBox const&, Ogre::PixelBox const&) |
90 | | }; |
91 | | |
92 | | |
93 | | // default floating-point linear resampler, does format conversion |
94 | | struct LinearResampler { |
95 | 0 | static void scale(const PixelBox& src, const PixelBox& dst) { |
96 | 0 | size_t srcelemsize = PixelUtil::getNumElemBytes(src.format); |
97 | 0 | size_t dstelemsize = PixelUtil::getNumElemBytes(dst.format); |
98 | | |
99 | | // srcdata stays at beginning, pdst is a moving pointer |
100 | 0 | uchar* srcdata = (uchar*)src.getTopLeftFrontPixelPtr(); |
101 | 0 | uchar* pdst = (uchar*)dst.getTopLeftFrontPixelPtr(); |
102 | | |
103 | | // sx_48,sy_48,sz_48 represent current position in source |
104 | | // using 16/48-bit fixed precision, incremented by steps |
105 | 0 | uint64 stepx = ((uint64)src.getWidth() << 48) / dst.getWidth(); |
106 | 0 | uint64 stepy = ((uint64)src.getHeight() << 48) / dst.getHeight(); |
107 | 0 | uint64 stepz = ((uint64)src.getDepth() << 48) / dst.getDepth(); |
108 | | |
109 | | // note: ((stepz>>1) - 1) is an extra half-step increment to adjust |
110 | | // for the center of the destination pixel, not the top-left corner |
111 | 0 | uint64 sz_48 = (stepz >> 1) - 1; |
112 | 0 | for (size_t z = dst.front; z < dst.back; z++, sz_48+=stepz) { |
113 | | // temp is 16/16 bit fixed precision, used to adjust a source |
114 | | // coordinate (x, y, or z) backwards by half a pixel so that the |
115 | | // integer bits represent the first sample (eg, sx1) and the |
116 | | // fractional bits are the blend weight of the second sample |
117 | 0 | unsigned int temp = static_cast<unsigned int>(sz_48 >> 32); |
118 | |
|
119 | 0 | temp = (temp > 0x8000)? temp - 0x8000 : 0; |
120 | 0 | uint32 sz1 = temp >> 16; // src z, sample #1 |
121 | 0 | uint32 sz2 = std::min(sz1+1,src.getDepth()-1);// src z, sample #2 |
122 | 0 | float szf = (temp & 0xFFFF) / 65536.f; // weight of sample #2 |
123 | |
|
124 | 0 | uint64 sy_48 = (stepy >> 1) - 1; |
125 | 0 | for (size_t y = dst.top; y < dst.bottom; y++, sy_48+=stepy) { |
126 | 0 | temp = static_cast<unsigned int>(sy_48 >> 32); |
127 | 0 | temp = (temp > 0x8000)? temp - 0x8000 : 0; |
128 | 0 | uint32 sy1 = temp >> 16; // src y #1 |
129 | 0 | uint32 sy2 = std::min(sy1+1,src.getHeight()-1);// src y #2 |
130 | 0 | float syf = (temp & 0xFFFF) / 65536.f; // weight of #2 |
131 | | |
132 | 0 | uint64 sx_48 = (stepx >> 1) - 1; |
133 | 0 | for (size_t x = dst.left; x < dst.right; x++, sx_48+=stepx) { |
134 | 0 | temp = static_cast<unsigned int>(sx_48 >> 32); |
135 | 0 | temp = (temp > 0x8000)? temp - 0x8000 : 0; |
136 | 0 | uint32 sx1 = temp >> 16; // src x #1 |
137 | 0 | uint32 sx2 = std::min(sx1+1,src.getWidth()-1);// src x #2 |
138 | 0 | float sxf = (temp & 0xFFFF) / 65536.f; // weight of #2 |
139 | | |
140 | 0 | ColourValue x1y1z1, x2y1z1, x1y2z1, x2y2z1; |
141 | 0 | ColourValue x1y1z2, x2y1z2, x1y2z2, x2y2z2; |
142 | |
|
143 | 0 | #define UNPACK(dst,x,y,z) PixelUtil::unpackColour(&dst, src.format, \ |
144 | 0 | srcdata + srcelemsize*((x)+(y)*src.rowPitch+(z)*src.slicePitch)) |
145 | |
|
146 | 0 | UNPACK(x1y1z1,sx1,sy1,sz1); UNPACK(x2y1z1,sx2,sy1,sz1); |
147 | 0 | UNPACK(x1y2z1,sx1,sy2,sz1); UNPACK(x2y2z1,sx2,sy2,sz1); |
148 | 0 | UNPACK(x1y1z2,sx1,sy1,sz2); UNPACK(x2y1z2,sx2,sy1,sz2); |
149 | 0 | UNPACK(x1y2z2,sx1,sy2,sz2); UNPACK(x2y2z2,sx2,sy2,sz2); |
150 | 0 | #undef UNPACK |
151 | |
|
152 | 0 | ColourValue accum = |
153 | 0 | x1y1z1 * ((1.0f - sxf)*(1.0f - syf)*(1.0f - szf)) + |
154 | 0 | x2y1z1 * ( sxf *(1.0f - syf)*(1.0f - szf)) + |
155 | 0 | x1y2z1 * ((1.0f - sxf)* syf *(1.0f - szf)) + |
156 | 0 | x2y2z1 * ( sxf * syf *(1.0f - szf)) + |
157 | 0 | x1y1z2 * ((1.0f - sxf)*(1.0f - syf)* szf ) + |
158 | 0 | x2y1z2 * ( sxf *(1.0f - syf)* szf ) + |
159 | 0 | x1y2z2 * ((1.0f - sxf)* syf * szf ) + |
160 | 0 | x2y2z2 * ( sxf * syf * szf ); |
161 | |
|
162 | 0 | PixelUtil::packColour(accum, dst.format, pdst); |
163 | |
|
164 | 0 | pdst += dstelemsize; |
165 | 0 | } |
166 | 0 | pdst += dstelemsize*dst.getRowSkip(); |
167 | 0 | } |
168 | 0 | pdst += dstelemsize*dst.getSliceSkip(); |
169 | 0 | } |
170 | 0 | } |
171 | | }; |
172 | | |
173 | | |
174 | | // float32 linear resampler, converts FLOAT32_RGB/FLOAT32_RGBA only. |
175 | | // avoids overhead of pixel unpack/repack function calls |
176 | | struct LinearResampler_Float32 { |
177 | 0 | static void scale(const PixelBox& src, const PixelBox& dst) { |
178 | 0 | size_t srcchannels = PixelUtil::getNumElemBytes(src.format) / sizeof(float); |
179 | 0 | size_t dstchannels = PixelUtil::getNumElemBytes(dst.format) / sizeof(float); |
180 | | // assert(srcchannels == 3 || srcchannels == 4); |
181 | | // assert(dstchannels == 3 || dstchannels == 4); |
182 | | |
183 | | // srcdata stays at beginning, pdst is a moving pointer |
184 | 0 | float* srcdata = (float*)src.getTopLeftFrontPixelPtr(); |
185 | 0 | float* pdst = (float*)dst.getTopLeftFrontPixelPtr(); |
186 | | |
187 | | // sx_48,sy_48,sz_48 represent current position in source |
188 | | // using 16/48-bit fixed precision, incremented by steps |
189 | 0 | uint64 stepx = ((uint64)src.getWidth() << 48) / dst.getWidth(); |
190 | 0 | uint64 stepy = ((uint64)src.getHeight() << 48) / dst.getHeight(); |
191 | 0 | uint64 stepz = ((uint64)src.getDepth() << 48) / dst.getDepth(); |
192 | | |
193 | | // note: ((stepz>>1) - 1) is an extra half-step increment to adjust |
194 | | // for the center of the destination pixel, not the top-left corner |
195 | 0 | uint64 sz_48 = (stepz >> 1) - 1; |
196 | 0 | for (size_t z = dst.front; z < dst.back; z++, sz_48+=stepz) { |
197 | | // temp is 16/16 bit fixed precision, used to adjust a source |
198 | | // coordinate (x, y, or z) backwards by half a pixel so that the |
199 | | // integer bits represent the first sample (eg, sx1) and the |
200 | | // fractional bits are the blend weight of the second sample |
201 | 0 | unsigned int temp = static_cast<unsigned int>(sz_48 >> 32); |
202 | |
|
203 | 0 | temp = (temp > 0x8000)? temp - 0x8000 : 0; |
204 | 0 | uint32 sz1 = temp >> 16; // src z, sample #1 |
205 | 0 | uint32 sz2 = std::min(sz1+1,src.getDepth()-1);// src z, sample #2 |
206 | 0 | float szf = (temp & 0xFFFF) / 65536.f; // weight of sample #2 |
207 | |
|
208 | 0 | uint64 sy_48 = (stepy >> 1) - 1; |
209 | 0 | for (size_t y = dst.top; y < dst.bottom; y++, sy_48+=stepy) { |
210 | 0 | temp = static_cast<unsigned int>(sy_48 >> 32); |
211 | 0 | temp = (temp > 0x8000)? temp - 0x8000 : 0; |
212 | 0 | uint32 sy1 = temp >> 16; // src y #1 |
213 | 0 | uint32 sy2 = std::min(sy1+1,src.getHeight()-1);// src y #2 |
214 | 0 | float syf = (temp & 0xFFFF) / 65536.f; // weight of #2 |
215 | | |
216 | 0 | uint64 sx_48 = (stepx >> 1) - 1; |
217 | 0 | for (size_t x = dst.left; x < dst.right; x++, sx_48+=stepx) { |
218 | 0 | temp = static_cast<unsigned int>(sx_48 >> 32); |
219 | 0 | temp = (temp > 0x8000)? temp - 0x8000 : 0; |
220 | 0 | uint32 sx1 = temp >> 16; // src x #1 |
221 | 0 | uint32 sx2 = std::min(sx1+1,src.getWidth()-1);// src x #2 |
222 | 0 | float sxf = (temp & 0xFFFF) / 65536.f; // weight of #2 |
223 | | |
224 | | // process R,G,B,A simultaneously for cache coherence? |
225 | 0 | float accum[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; |
226 | |
|
227 | 0 | #define ACCUM3(x,y,z,factor) \ |
228 | 0 | { float f = factor; \ |
229 | 0 | size_t off = (x+y*src.rowPitch+z*src.slicePitch)*srcchannels; \ |
230 | 0 | accum[0]+=srcdata[off+0]*f; accum[1]+=srcdata[off+1]*f; \ |
231 | 0 | accum[2]+=srcdata[off+2]*f; } |
232 | |
|
233 | 0 | #define ACCUM4(x,y,z,factor) \ |
234 | 0 | { float f = factor; \ |
235 | 0 | size_t off = (x+y*src.rowPitch+z*src.slicePitch)*srcchannels; \ |
236 | 0 | accum[0]+=srcdata[off+0]*f; accum[1]+=srcdata[off+1]*f; \ |
237 | 0 | accum[2]+=srcdata[off+2]*f; accum[3]+=srcdata[off+3]*f; } |
238 | |
|
239 | 0 | if (srcchannels == 3 || dstchannels == 3) { |
240 | | // RGB, no alpha |
241 | 0 | ACCUM3(sx1,sy1,sz1,(1.0f-sxf)*(1.0f-syf)*(1.0f-szf)); |
242 | 0 | ACCUM3(sx2,sy1,sz1, sxf *(1.0f-syf)*(1.0f-szf)); |
243 | 0 | ACCUM3(sx1,sy2,sz1,(1.0f-sxf)* syf *(1.0f-szf)); |
244 | 0 | ACCUM3(sx2,sy2,sz1, sxf * syf *(1.0f-szf)); |
245 | 0 | ACCUM3(sx1,sy1,sz2,(1.0f-sxf)*(1.0f-syf)* szf ); |
246 | 0 | ACCUM3(sx2,sy1,sz2, sxf *(1.0f-syf)* szf ); |
247 | 0 | ACCUM3(sx1,sy2,sz2,(1.0f-sxf)* syf * szf ); |
248 | 0 | ACCUM3(sx2,sy2,sz2, sxf * syf * szf ); |
249 | 0 | accum[3] = 1.0f; |
250 | 0 | } else { |
251 | | // RGBA |
252 | 0 | ACCUM4(sx1,sy1,sz1,(1.0f-sxf)*(1.0f-syf)*(1.0f-szf)); |
253 | 0 | ACCUM4(sx2,sy1,sz1, sxf *(1.0f-syf)*(1.0f-szf)); |
254 | 0 | ACCUM4(sx1,sy2,sz1,(1.0f-sxf)* syf *(1.0f-szf)); |
255 | 0 | ACCUM4(sx2,sy2,sz1, sxf * syf *(1.0f-szf)); |
256 | 0 | ACCUM4(sx1,sy1,sz2,(1.0f-sxf)*(1.0f-syf)* szf ); |
257 | 0 | ACCUM4(sx2,sy1,sz2, sxf *(1.0f-syf)* szf ); |
258 | 0 | ACCUM4(sx1,sy2,sz2,(1.0f-sxf)* syf * szf ); |
259 | 0 | ACCUM4(sx2,sy2,sz2, sxf * syf * szf ); |
260 | 0 | } |
261 | |
|
262 | 0 | memcpy(pdst, accum, sizeof(float)*dstchannels); |
263 | |
|
264 | 0 | #undef ACCUM3 |
265 | 0 | #undef ACCUM4 |
266 | |
|
267 | 0 | pdst += dstchannels; |
268 | 0 | } |
269 | 0 | pdst += dstchannels*dst.getRowSkip(); |
270 | 0 | } |
271 | 0 | pdst += dstchannels*dst.getSliceSkip(); |
272 | 0 | } |
273 | 0 | } |
274 | | }; |
275 | | |
276 | | |
277 | | |
278 | | // byte linear resampler, does not do any format conversions. |
279 | | // only handles pixel formats that use 1 byte per color channel. |
280 | | // 2D only; punts 3D pixelboxes to default LinearResampler (slow). |
281 | | // templated on bytes-per-pixel to allow compiler optimizations, such |
282 | | // as unrolling loops and replacing multiplies with bitshifts |
283 | | template<unsigned int channels> struct LinearResampler_Byte { |
284 | 0 | static void scale(const PixelBox& src, const PixelBox& dst) { |
285 | | // assert(src.format == dst.format); |
286 | | |
287 | | // only optimized for 2D |
288 | 0 | if (src.getDepth() > 1 || dst.getDepth() > 1) { |
289 | 0 | LinearResampler::scale(src, dst); |
290 | 0 | return; |
291 | 0 | } |
292 | | |
293 | | // srcdata stays at beginning of slice, pdst is a moving pointer |
294 | 0 | uchar* srcdata = (uchar*)src.getTopLeftFrontPixelPtr(); |
295 | 0 | uchar* pdst = (uchar*)dst.getTopLeftFrontPixelPtr(); |
296 | | |
297 | | // sx_48,sy_48 represent current position in source |
298 | | // using 16/48-bit fixed precision, incremented by steps |
299 | 0 | uint64 stepx = ((uint64)src.getWidth() << 48) / dst.getWidth(); |
300 | 0 | uint64 stepy = ((uint64)src.getHeight() << 48) / dst.getHeight(); |
301 | | |
302 | 0 | uint64 sy_48 = (stepy >> 1) - 1; |
303 | 0 | for (size_t y = dst.top; y < dst.bottom; y++, sy_48+=stepy) { |
304 | | // bottom 28 bits of temp are 16/12 bit fixed precision, used to |
305 | | // adjust a source coordinate backwards by half a pixel so that the |
306 | | // integer bits represent the first sample (eg, sx1) and the |
307 | | // fractional bits are the blend weight of the second sample |
308 | 0 | unsigned int temp = static_cast<unsigned int>(sy_48 >> 36); |
309 | 0 | temp = (temp > 0x800)? temp - 0x800: 0; |
310 | 0 | unsigned int syf = temp & 0xFFF; |
311 | 0 | uint32 sy1 = temp >> 12; |
312 | 0 | uint32 sy2 = std::min(sy1+1, src.bottom-src.top-1); |
313 | 0 | size_t syoff1 = sy1 * src.rowPitch; |
314 | 0 | size_t syoff2 = sy2 * src.rowPitch; |
315 | |
|
316 | 0 | uint64 sx_48 = (stepx >> 1) - 1; |
317 | 0 | for (size_t x = dst.left; x < dst.right; x++, sx_48+=stepx) { |
318 | 0 | temp = static_cast<unsigned int>(sx_48 >> 36); |
319 | 0 | temp = (temp > 0x800)? temp - 0x800 : 0; |
320 | 0 | unsigned int sxf = temp & 0xFFF; |
321 | 0 | uint32 sx1 = temp >> 12; |
322 | 0 | uint32 sx2 = std::min(sx1+1, src.right-src.left-1); |
323 | |
|
324 | 0 | unsigned int sxfsyf = sxf*syf; |
325 | 0 | for (unsigned int k = 0; k < channels; k++) { |
326 | 0 | unsigned int accum = |
327 | 0 | srcdata[(sx1 + syoff1)*channels+k]*(0x1000000-(sxf<<12)-(syf<<12)+sxfsyf) + |
328 | 0 | srcdata[(sx2 + syoff1)*channels+k]*((sxf<<12)-sxfsyf) + |
329 | 0 | srcdata[(sx1 + syoff2)*channels+k]*((syf<<12)-sxfsyf) + |
330 | 0 | srcdata[(sx2 + syoff2)*channels+k]*sxfsyf; |
331 | | // accum is computed using 8/24-bit fixed-point math |
332 | | // (maximum is 0xFF000000; rounding will not cause overflow) |
333 | 0 | *pdst++ = static_cast<uchar>((accum + 0x800000) >> 24); |
334 | 0 | } |
335 | 0 | } |
336 | 0 | pdst += channels*dst.getRowSkip(); |
337 | 0 | } |
338 | 0 | } Unexecuted instantiation: Ogre::LinearResampler_Byte<1u>::scale(Ogre::PixelBox const&, Ogre::PixelBox const&) Unexecuted instantiation: Ogre::LinearResampler_Byte<2u>::scale(Ogre::PixelBox const&, Ogre::PixelBox const&) Unexecuted instantiation: Ogre::LinearResampler_Byte<3u>::scale(Ogre::PixelBox const&, Ogre::PixelBox const&) Unexecuted instantiation: Ogre::LinearResampler_Byte<4u>::scale(Ogre::PixelBox const&, Ogre::PixelBox const&) |
339 | | }; |
340 | | /** @} */ |
341 | | /** @} */ |
342 | | |
343 | | } |
344 | | |
345 | | #endif |