i420_rgb.c 19.9 KB
Newer Older
1 2 3
/*****************************************************************************
 * i420_rgb.c : YUV to bitmap RGB conversion module for vlc
 *****************************************************************************
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
4
 * Copyright (C) 2000, 2001, 2004, 2008 VLC authors and VideoLAN
5
 * $Id$
6
 *
7 8
 * Authors: Sam Hocevar <sam@zoy.org>
 *          Damien Fouilleul <damienf@videolan.org>
9
 *
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
10 11 12
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
13
 * (at your option) any later version.
14
 *
15 16
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
17 18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
19
 *
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
20 21 22
 * You should have received a copy of the GNU Lesser 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.
23 24 25 26 27 28
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/

29 30 31 32
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

33 34
#include <math.h>                                            /* exp(), pow() */

35
#include <vlc_common.h>
36
#include <vlc_plugin.h>
37
#include <vlc_filter.h>
38
#include <vlc_picture.h>
39
#include <vlc_cpu.h>
40 41

#include "i420_rgb.h"
42 43 44 45 46 47 48 49 50
#ifdef PLAIN
# include "i420_rgb_c.h"
static picture_t *I420_RGB8_Filter( filter_t *, picture_t * );
static picture_t *I420_RGB16_Filter( filter_t *, picture_t * );
static picture_t *I420_RGB32_Filter( filter_t *, picture_t * );

static void SetGammaTable( int *pi_table, double f_gamma );
static void SetYUV( filter_t * );
static void Set8bppPalette( filter_t *, uint8_t * );
51
#else
52 53 54 55 56 57
static picture_t *I420_R5G5B5_Filter( filter_t *, picture_t * );
static picture_t *I420_R5G6B5_Filter( filter_t *, picture_t * );
static picture_t *I420_A8R8G8B8_Filter( filter_t *, picture_t * );
static picture_t *I420_R8G8B8A8_Filter( filter_t *, picture_t * );
static picture_t *I420_B8G8R8A8_Filter( filter_t *, picture_t * );
static picture_t *I420_A8B8G8R8_Filter( filter_t *, picture_t * );
58 59
#endif

60 61 62
/*****************************************************************************
 * RGB2PIXEL: assemble RGB components to a pixel value, returns a uint32_t
 *****************************************************************************/
63 64 65 66 67 68 69
#define RGB2PIXEL( p_filter, i_r, i_g, i_b )                 \
    (((((uint32_t)i_r) >> p_filter->fmt_out.video.i_rrshift) \
                       << p_filter->fmt_out.video.i_lrshift) \
   | ((((uint32_t)i_g) >> p_filter->fmt_out.video.i_rgshift) \
                       << p_filter->fmt_out.video.i_lgshift) \
   | ((((uint32_t)i_b) >> p_filter->fmt_out.video.i_rbshift) \
                       << p_filter->fmt_out.video.i_lbshift))
70

71
/*****************************************************************************
72
 * Module descriptor.
73 74 75 76
 *****************************************************************************/
static int  Activate   ( vlc_object_t * );
static void Deactivate ( vlc_object_t * );

77
vlc_module_begin ()
78
#if defined (SSE2)
79
    set_description( N_( "SSE2 I420,IYUV,YV12 to "
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
80
                        "RV15,RV16,RV24,RV32 conversions") )
81
    set_capability( "video converter", 120 )
82
# define vlc_CPU_capable() vlc_CPU_SSE2()
83 84 85
#elif defined (MMX)
    set_description( N_( "MMX I420,IYUV,YV12 to "
                        "RV15,RV16,RV24,RV32 conversions") )
86
    set_capability( "video converter", 100 )
87 88 89 90
# define vlc_CPU_capable() vlc_CPU_MMX()
#else
    set_description( N_("I420,IYUV,YV12 to "
                       "RGB2,RV15,RV16,RV24,RV32 conversions") )
91
    set_capability( "video converter", 80 )
92
# define vlc_CPU_capable() (true)
93
#endif
94 95
    set_callbacks( Activate, Deactivate )
vlc_module_end ()
96 97 98 99 100 101 102 103

/*****************************************************************************
 * Activate: allocate a chroma function
 *****************************************************************************
 * This function allocates and initializes a chroma function
 *****************************************************************************/
static int Activate( vlc_object_t *p_this )
{
104
    filter_t *p_filter = (filter_t *)p_this;
105
#ifdef PLAIN
106 107 108
    size_t i_tables_size;
#endif

109
    if( !vlc_CPU_capable() )
110
        return VLC_EGENERIC;
111 112
    if( p_filter->fmt_out.video.i_width & 1
     || p_filter->fmt_out.video.i_height & 1 )
113
    {
114
        return VLC_EGENERIC;
115 116
    }

117 118 119 120 121
    if( p_filter->fmt_in.video.orientation != p_filter->fmt_out.video.orientation )
    {
        return VLC_EGENERIC;
    }

122
    switch( p_filter->fmt_in.video.i_chroma )
123
    {
124 125
        case VLC_CODEC_YV12:
        case VLC_CODEC_I420:
126
            switch( p_filter->fmt_out.video.i_chroma )
127
            {
128
#ifndef PLAIN
129 130
                case VLC_CODEC_RGB15:
                case VLC_CODEC_RGB16:
131
                    /* If we don't have support for the bitmasks, bail out */
132 133 134
                    if( ( p_filter->fmt_out.video.i_rmask == 0x7c00
                       && p_filter->fmt_out.video.i_gmask == 0x03e0
                       && p_filter->fmt_out.video.i_bmask == 0x001f ) )
135
                    {
136 137
                        /* R5G5B6 pixel format */
                        msg_Dbg(p_this, "RGB pixel format is R5G5B5");
138
                        p_filter->pf_video_filter = I420_R5G5B5_Filter;
139
                    }
140 141 142
                    else if( ( p_filter->fmt_out.video.i_rmask == 0xf800
                            && p_filter->fmt_out.video.i_gmask == 0x07e0
                            && p_filter->fmt_out.video.i_bmask == 0x001f ) )
143 144 145
                    {
                        /* R5G6B5 pixel format */
                        msg_Dbg(p_this, "RGB pixel format is R5G6B5");
146
                        p_filter->pf_video_filter = I420_R5G6B5_Filter;
147 148
                    }
                    else
149
                        return VLC_EGENERIC;
150
                    break;
151
                case VLC_CODEC_RGB32:
152
                    /* If we don't have support for the bitmasks, bail out */
153 154 155
                    if( p_filter->fmt_out.video.i_rmask == 0x00ff0000
                     && p_filter->fmt_out.video.i_gmask == 0x0000ff00
                     && p_filter->fmt_out.video.i_bmask == 0x000000ff )
156
                    {
157 158
                        /* A8R8G8B8 pixel format */
                        msg_Dbg(p_this, "RGB pixel format is A8R8G8B8");
159
                        p_filter->pf_video_filter = I420_A8R8G8B8_Filter;
160
                    }
161 162 163
                    else if( p_filter->fmt_out.video.i_rmask == 0xff000000
                          && p_filter->fmt_out.video.i_gmask == 0x00ff0000
                          && p_filter->fmt_out.video.i_bmask == 0x0000ff00 )
164 165 166
                    {
                        /* R8G8B8A8 pixel format */
                        msg_Dbg(p_this, "RGB pixel format is R8G8B8A8");
167
                        p_filter->pf_video_filter = I420_R8G8B8A8_Filter;
168
                    }
169 170 171
                    else if( p_filter->fmt_out.video.i_rmask == 0x0000ff00
                          && p_filter->fmt_out.video.i_gmask == 0x00ff0000
                          && p_filter->fmt_out.video.i_bmask == 0xff000000 )
172 173 174
                    {
                        /* B8G8R8A8 pixel format */
                        msg_Dbg(p_this, "RGB pixel format is B8G8R8A8");
175
                        p_filter->pf_video_filter = I420_B8G8R8A8_Filter;
176
                    }
177 178 179
                    else if( p_filter->fmt_out.video.i_rmask == 0x000000ff
                          && p_filter->fmt_out.video.i_gmask == 0x0000ff00
                          && p_filter->fmt_out.video.i_bmask == 0x00ff0000 )
180 181 182
                    {
                        /* A8B8G8R8 pixel format */
                        msg_Dbg(p_this, "RGB pixel format is A8B8G8R8");
183
                        p_filter->pf_video_filter = I420_A8B8G8R8_Filter;
184
                    }
185
                    else
186
                        return VLC_EGENERIC;
187
                    break;
188
#else
189 190 191 192 193 194 195 196
                case VLC_CODEC_RGB8:
                    p_filter->pf_video_filter = I420_RGB8_Filter;
                    break;
                case VLC_CODEC_RGB15:
                case VLC_CODEC_RGB16:
                    p_filter->pf_video_filter = I420_RGB16_Filter;
                    break;
                case VLC_CODEC_RGB32:
197
                    p_filter->pf_video_filter = I420_RGB32_Filter;
198
                    break;
199
#endif
200
                default:
201
                    return VLC_EGENERIC;
202 203 204 205
            }
            break;

        default:
206
            return VLC_EGENERIC;
207
    }
208

209 210
    p_filter->p_sys = malloc( sizeof( filter_sys_t ) );
    if( p_filter->p_sys == NULL )
211
    {
212
        return VLC_EGENERIC;
213 214
    }

215
    switch( p_filter->fmt_out.video.i_chroma )
216
    {
217
#ifdef PLAIN
218
        case VLC_CODEC_RGB8:
219
            p_filter->p_sys->p_buffer = malloc( VOUT_MAX_WIDTH );
220 221
            break;
#endif
222 223
        case VLC_CODEC_RGB15:
        case VLC_CODEC_RGB16:
224
            p_filter->p_sys->p_buffer = malloc( VOUT_MAX_WIDTH * 2 );
225
            break;
226 227
        case VLC_CODEC_RGB24:
        case VLC_CODEC_RGB32:
228
            p_filter->p_sys->p_buffer = malloc( VOUT_MAX_WIDTH * 4 );
229 230
            break;
        default:
231
            p_filter->p_sys->p_buffer = NULL;
232 233 234
            break;
    }

235
    if( p_filter->p_sys->p_buffer == NULL )
236
    {
237 238
        free( p_filter->p_sys );
        return VLC_EGENERIC;
239 240
    }

241 242
    p_filter->p_sys->p_offset = malloc( p_filter->fmt_out.video.i_width
                    * ( ( p_filter->fmt_out.video.i_chroma
243
                           == VLC_CODEC_RGB8 ) ? 2 : 1 )
244
                    * sizeof( int ) );
245
    if( p_filter->p_sys->p_offset == NULL )
246
    {
247 248 249
        free( p_filter->p_sys->p_buffer );
        free( p_filter->p_sys );
        return VLC_EGENERIC;
250 251
    }

252
#ifdef PLAIN
253
    switch( p_filter->fmt_out.video.i_chroma )
254
    {
255
    case VLC_CODEC_RGB8:
256
        i_tables_size = sizeof( uint8_t ) * PALETTE_TABLE_SIZE;
257
        break;
258 259
    case VLC_CODEC_RGB15:
    case VLC_CODEC_RGB16:
260
        i_tables_size = sizeof( uint16_t ) * RGB_TABLE_SIZE;
261 262
        break;
    default: /* RV24, RV32 */
263
        i_tables_size = sizeof( uint32_t ) * RGB_TABLE_SIZE;
264 265 266
        break;
    }

267 268
    p_filter->p_sys->p_base = malloc( i_tables_size );
    if( p_filter->p_sys->p_base == NULL )
269
    {
270 271 272
        free( p_filter->p_sys->p_offset );
        free( p_filter->p_sys->p_buffer );
        free( p_filter->p_sys );
273 274 275
        return -1;
    }

276
    SetYUV( p_filter );
277 278
#endif

279
    return 0;
280 281 282 283 284 285 286 287 288
}

/*****************************************************************************
 * Deactivate: free the chroma function
 *****************************************************************************
 * This function frees the previously allocated chroma function
 *****************************************************************************/
static void Deactivate( vlc_object_t *p_this )
{
289
    filter_t *p_filter = (filter_t *)p_this;
290

291
#ifdef PLAIN
292
    free( p_filter->p_sys->p_base );
293
#endif
294 295 296
    free( p_filter->p_sys->p_offset );
    free( p_filter->p_sys->p_buffer );
    free( p_filter->p_sys );
297 298
}

299
#ifndef PLAIN
300 301 302 303 304 305
VIDEO_FILTER_WRAPPER( I420_R5G5B5 )
VIDEO_FILTER_WRAPPER( I420_R5G6B5 )
VIDEO_FILTER_WRAPPER( I420_A8R8G8B8 )
VIDEO_FILTER_WRAPPER( I420_R8G8B8A8 )
VIDEO_FILTER_WRAPPER( I420_B8G8R8A8 )
VIDEO_FILTER_WRAPPER( I420_A8B8G8R8 )
306 307 308 309
#else
VIDEO_FILTER_WRAPPER( I420_RGB8 )
VIDEO_FILTER_WRAPPER( I420_RGB16 )
VIDEO_FILTER_WRAPPER( I420_RGB32 )
310

311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
/*****************************************************************************
 * SetGammaTable: return intensity table transformed by gamma curve.
 *****************************************************************************
 * pi_table is a table of 256 entries from 0 to 255.
 *****************************************************************************/
static void SetGammaTable( int *pi_table, double f_gamma )
{
    int i_y;                                               /* base intensity */

    /* Use exp(gamma) instead of gamma */
    f_gamma = exp( f_gamma );

    /* Build gamma table */
    for( i_y = 0; i_y < 256; i_y++ )
    {
326
        pi_table[ i_y ] = (int)( pow( (double)i_y / 256, f_gamma ) * 256 );
327 328 329 330 331 332
    }
}

/*****************************************************************************
 * SetYUV: compute tables and set function pointers
 *****************************************************************************/
333
static void SetYUV( filter_t *p_filter )
334
{
335 336 337 338
    int          pi_gamma[256];                               /* gamma table */
    volatile int i_index;                                 /* index in tables */
                   /* We use volatile here to work around a strange gcc-3.3.4
                    * optimization bug */
339 340

    /* Build gamma table */
341
    SetGammaTable( pi_gamma, 0 ); //p_filter/*FIXME wasn't used anywhere anyway*/->f_gamma );
342 343 344 345 346 347

    /*
     * Set pointers and build YUV tables
     */

    /* Color: build red, green and blue tables */
348
    switch( p_filter->fmt_out.video.i_chroma )
349
    {
350
    case VLC_CODEC_RGB8:
351 352
        p_filter->p_sys->p_rgb8 = (uint8_t *)p_filter->p_sys->p_base;
        Set8bppPalette( p_filter, p_filter->p_sys->p_rgb8 );
353 354
        break;

355 356
    case VLC_CODEC_RGB15:
    case VLC_CODEC_RGB16:
357
        p_filter->p_sys->p_rgb16 = (uint16_t *)p_filter->p_sys->p_base;
358 359
        for( i_index = 0; i_index < RED_MARGIN; i_index++ )
        {
360 361
            p_filter->p_sys->p_rgb16[RED_OFFSET - RED_MARGIN + i_index] = RGB2PIXEL( p_filter, pi_gamma[0], 0, 0 );
            p_filter->p_sys->p_rgb16[RED_OFFSET + 256 + i_index] =        RGB2PIXEL( p_filter, pi_gamma[255], 0, 0 );
362 363 364
        }
        for( i_index = 0; i_index < GREEN_MARGIN; i_index++ )
        {
365 366
            p_filter->p_sys->p_rgb16[GREEN_OFFSET - GREEN_MARGIN + i_index] = RGB2PIXEL( p_filter, 0, pi_gamma[0], 0 );
            p_filter->p_sys->p_rgb16[GREEN_OFFSET + 256 + i_index] =          RGB2PIXEL( p_filter, 0, pi_gamma[255], 0 );
367 368 369
        }
        for( i_index = 0; i_index < BLUE_MARGIN; i_index++ )
        {
370 371
            p_filter->p_sys->p_rgb16[BLUE_OFFSET - BLUE_MARGIN + i_index] = RGB2PIXEL( p_filter, 0, 0, pi_gamma[0] );
            p_filter->p_sys->p_rgb16[BLUE_OFFSET + BLUE_MARGIN + i_index] = RGB2PIXEL( p_filter, 0, 0, pi_gamma[255] );
372 373 374
        }
        for( i_index = 0; i_index < 256; i_index++ )
        {
375 376 377
            p_filter->p_sys->p_rgb16[RED_OFFSET + i_index] =   RGB2PIXEL( p_filter, pi_gamma[ i_index ], 0, 0 );
            p_filter->p_sys->p_rgb16[GREEN_OFFSET + i_index] = RGB2PIXEL( p_filter, 0, pi_gamma[ i_index ], 0 );
            p_filter->p_sys->p_rgb16[BLUE_OFFSET + i_index] =  RGB2PIXEL( p_filter, 0, 0, pi_gamma[ i_index ] );
378 379 380
        }
        break;

381 382
    case VLC_CODEC_RGB24:
    case VLC_CODEC_RGB32:
383
        p_filter->p_sys->p_rgb32 = (uint32_t *)p_filter->p_sys->p_base;
384 385
        for( i_index = 0; i_index < RED_MARGIN; i_index++ )
        {
386 387
            p_filter->p_sys->p_rgb32[RED_OFFSET - RED_MARGIN + i_index] = RGB2PIXEL( p_filter, pi_gamma[0], 0, 0 );
            p_filter->p_sys->p_rgb32[RED_OFFSET + 256 + i_index] =        RGB2PIXEL( p_filter, pi_gamma[255], 0, 0 );
388 389 390
        }
        for( i_index = 0; i_index < GREEN_MARGIN; i_index++ )
        {
391 392
            p_filter->p_sys->p_rgb32[GREEN_OFFSET - GREEN_MARGIN + i_index] = RGB2PIXEL( p_filter, 0, pi_gamma[0], 0 );
            p_filter->p_sys->p_rgb32[GREEN_OFFSET + 256 + i_index] =          RGB2PIXEL( p_filter, 0, pi_gamma[255], 0 );
393 394 395
        }
        for( i_index = 0; i_index < BLUE_MARGIN; i_index++ )
        {
396 397
            p_filter->p_sys->p_rgb32[BLUE_OFFSET - BLUE_MARGIN + i_index] = RGB2PIXEL( p_filter, 0, 0, pi_gamma[0] );
            p_filter->p_sys->p_rgb32[BLUE_OFFSET + BLUE_MARGIN + i_index] = RGB2PIXEL( p_filter, 0, 0, pi_gamma[255] );
398 399 400
        }
        for( i_index = 0; i_index < 256; i_index++ )
        {
401 402 403
            p_filter->p_sys->p_rgb32[RED_OFFSET + i_index] =   RGB2PIXEL( p_filter, pi_gamma[ i_index ], 0, 0 );
            p_filter->p_sys->p_rgb32[GREEN_OFFSET + i_index] = RGB2PIXEL( p_filter, 0, pi_gamma[ i_index ], 0 );
            p_filter->p_sys->p_rgb32[BLUE_OFFSET + i_index] =  RGB2PIXEL( p_filter, 0, 0, pi_gamma[ i_index ] );
404 405 406 407 408
        }
        break;
    }
}

409
static void Set8bppPalette( filter_t *p_filter, uint8_t *p_rgb8 )
410 411 412 413 414 415
{
    #define CLIP( x ) ( ((x < 0) ? 0 : (x > 255) ? 255 : x) << 8 )

    int y,u,v;
    int r,g,b;
    int i = 0, j = 0;
416 417 418
    uint16_t *p_cmap_r = p_filter->p_sys->p_rgb_r;
    uint16_t *p_cmap_g = p_filter->p_sys->p_rgb_g;
    uint16_t *p_cmap_b = p_filter->p_sys->p_rgb_b;
419

420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440
    unsigned char p_lookup[PALETTE_TABLE_SIZE];

    /* This loop calculates the intersection of an YUV box and the RGB cube. */
    for ( y = 0; y <= 256; y += 16, i += 128 - 81 )
    {
        for ( u = 0; u <= 256; u += 32 )
        {
            for ( v = 0; v <= 256; v += 32 )
            {
                r = y + ( (V_RED_COEF*(v-128)) >> SHIFT );
                g = y + ( (U_GREEN_COEF*(u-128)
                         + V_GREEN_COEF*(v-128)) >> SHIFT );
                b = y + ( (U_BLUE_COEF*(u-128)) >> SHIFT );

                if( r >= 0x00 && g >= 0x00 && b >= 0x00
                        && r <= 0xff && g <= 0xff && b <= 0xff )
                {
                    /* This one should never happen unless someone
                     * fscked up my code */
                    if( j == 256 )
                    {
441
                        msg_Err( p_filter, "no colors left in palette" );
442 443 444 445
                        break;
                    }

                    /* Clip the colors */
446 447 448 449 450
                    p_cmap_r[ j ] = CLIP( r );
                    p_cmap_g[ j ] = CLIP( g );
                    p_cmap_b[ j ] = CLIP( b );

#if 0
451 452 453
            printf("+++Alloc RGB cmap %d (%d, %d, %d)\n", j,
               p_cmap_r[ j ] >>8, p_cmap_g[ j ] >>8,
               p_cmap_b[ j ] >>8);
454
#endif
455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470

                    /* Allocate color */
                    p_lookup[ i ] = 1;
                    p_rgb8[ i++ ] = j;
                    j++;
                }
                else
                {
                    p_lookup[ i ] = 0;
                    p_rgb8[ i++ ] = 0;
                }
            }
        }
    }

    /* The colors have been allocated, we can set the palette */
471 472
    /* FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME
    p_filter->fmt_out.video.pf_setpalette( p_filter, p_cmap_r, p_cmap_g, p_cmap_b );*/
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

#if 0
    /* There will eventually be a way to know which colors
     * couldn't be allocated and try to find a replacement */
    p_vout->i_white_pixel = 0xff;
    p_vout->i_black_pixel = 0x00;
    p_vout->i_gray_pixel = 0x44;
    p_vout->i_blue_pixel = 0x3b;
#endif

    /* This loop allocates colors that got outside the RGB cube */
    for ( i = 0, y = 0; y <= 256; y += 16, i += 128 - 81 )
    {
        for ( u = 0; u <= 256; u += 32 )
        {
            for ( v = 0; v <= 256; v += 32, i++ )
            {
                int u2, v2, dist, mindist = 100000000;

                if( p_lookup[ i ] || y == 0 )
                {
                    continue;
                }

                /* Heavy. yeah. */
                for( u2 = 0; u2 <= 256; u2 += 32 )
                {
                    for( v2 = 0; v2 <= 256; v2 += 32 )
                    {
                        j = ((y>>4)<<7) + (u2>>5)*9 + (v2>>5);
                        dist = (u-u2)*(u-u2) + (v-v2)*(v-v2);

                        /* Find the nearest color */
                        if( p_lookup[ j ] && dist < mindist )
                        {
                            p_rgb8[ i ] = p_rgb8[ j ];
                            mindist = dist;
                        }

                        j -= 128;

                        /* Find the nearest color */
                        if( p_lookup[ j ] && dist + 128 < mindist )
                        {
                            p_rgb8[ i ] = p_rgb8[ j ];
                            mindist = dist + 128;
                        }
                    }
                }
            }
        }
    }
}
#endif