2021-04-18 07:35:25 +04:00
/*
2022-07-15 06:00:50 +04:00
Copyright ( C ) 1997 - 2022 Sam Lantinga < slouken @ libsdl . org >
2021-04-18 07:35:25 +04:00
This software is provided ' as - is ' , without any express or implied
warranty . In no event will the authors be held liable for any damages
arising from the use of this software .
Permission is granted to anyone to use this software for any purpose ,
including commercial applications , and to alter it and redistribute it
freely .
*/
# include <stdlib.h>
# include <stdio.h>
# include <string.h>
# include "SDL.h"
# include "SDL_test_font.h"
# include "testyuv_cvt.h"
/* 422 (YUY2, etc) formats are the largest */
# define MAX_YUV_SURFACE_SIZE(W, H, P) (H*4*(W+P+1) / 2)
/* Return true if the YUV format is packed pixels */
static SDL_bool is_packed_yuv_format ( Uint32 format )
{
return ( format = = SDL_PIXELFORMAT_YUY2 | |
format = = SDL_PIXELFORMAT_UYVY | |
format = = SDL_PIXELFORMAT_YVYU ) ;
}
/* Create a surface with a good pattern for verifying YUV conversion */
static SDL_Surface * generate_test_pattern ( int pattern_size )
{
SDL_Surface * pattern = SDL_CreateRGBSurfaceWithFormat ( 0 , pattern_size , pattern_size , 0 , SDL_PIXELFORMAT_RGB24 ) ;
if ( pattern ) {
int i , x , y ;
Uint8 * p , c ;
const int thickness = 2 ; /* Important so 2x2 blocks of color are the same, to avoid Cr/Cb interpolation over pixels */
/* R, G, B in alternating horizontal bands */
for ( y = 0 ; y < pattern - > h ; y + = thickness ) {
for ( i = 0 ; i < thickness ; + + i ) {
p = ( Uint8 * ) pattern - > pixels + ( y + i ) * pattern - > pitch + ( ( y / thickness ) % 3 ) ;
for ( x = 0 ; x < pattern - > w ; + + x ) {
* p = 0xFF ;
p + = 3 ;
}
}
}
/* Black and white in alternating vertical bands */
c = 0xFF ;
for ( x = 1 * thickness ; x < pattern - > w ; x + = 2 * thickness ) {
for ( i = 0 ; i < thickness ; + + i ) {
p = ( Uint8 * ) pattern - > pixels + ( x + i ) * 3 ;
for ( y = 0 ; y < pattern - > h ; + + y ) {
SDL_memset ( p , c , 3 ) ;
p + = pattern - > pitch ;
}
}
if ( c ) {
c = 0x00 ;
} else {
c = 0xFF ;
}
}
}
return pattern ;
}
static SDL_bool verify_yuv_data ( Uint32 format , const Uint8 * yuv , int yuv_pitch , SDL_Surface * surface )
{
const int tolerance = 20 ;
const int size = ( surface - > h * surface - > pitch ) ;
Uint8 * rgb ;
SDL_bool result = SDL_FALSE ;
rgb = ( Uint8 * ) SDL_malloc ( size ) ;
if ( ! rgb ) {
SDL_LogError ( SDL_LOG_CATEGORY_APPLICATION , " Out of memory " ) ;
return SDL_FALSE ;
}
if ( SDL_ConvertPixels ( surface - > w , surface - > h , format , yuv , yuv_pitch , surface - > format - > format , rgb , surface - > pitch ) = = 0 ) {
int x , y ;
result = SDL_TRUE ;
for ( y = 0 ; y < surface - > h ; + + y ) {
const Uint8 * actual = rgb + y * surface - > pitch ;
const Uint8 * expected = ( const Uint8 * ) surface - > pixels + y * surface - > pitch ;
for ( x = 0 ; x < surface - > w ; + + x ) {
int deltaR = ( int ) actual [ 0 ] - expected [ 0 ] ;
int deltaG = ( int ) actual [ 1 ] - expected [ 1 ] ;
int deltaB = ( int ) actual [ 2 ] - expected [ 2 ] ;
int distance = ( deltaR * deltaR + deltaG * deltaG + deltaB * deltaB ) ;
if ( distance > tolerance ) {
SDL_LogError ( SDL_LOG_CATEGORY_APPLICATION , " Pixel at %d,%d was 0x%.2x,0x%.2x,0x%.2x, expected 0x%.2x,0x%.2x,0x%.2x, distance = %d \n " , x , y , actual [ 0 ] , actual [ 1 ] , actual [ 2 ] , expected [ 0 ] , expected [ 1 ] , expected [ 2 ] , distance ) ;
result = SDL_FALSE ;
}
actual + = 3 ;
expected + = 3 ;
}
}
} else {
SDL_LogError ( SDL_LOG_CATEGORY_APPLICATION , " Couldn't convert %s to %s: %s \n " , SDL_GetPixelFormatName ( format ) , SDL_GetPixelFormatName ( surface - > format - > format ) , SDL_GetError ( ) ) ;
}
SDL_free ( rgb ) ;
return result ;
}
static int run_automated_tests ( int pattern_size , int extra_pitch )
{
const Uint32 formats [ ] = {
SDL_PIXELFORMAT_YV12 ,
SDL_PIXELFORMAT_IYUV ,
SDL_PIXELFORMAT_NV12 ,
SDL_PIXELFORMAT_NV21 ,
SDL_PIXELFORMAT_YUY2 ,
SDL_PIXELFORMAT_UYVY ,
SDL_PIXELFORMAT_YVYU
} ;
int i , j ;
SDL_Surface * pattern = generate_test_pattern ( pattern_size ) ;
const int yuv_len = MAX_YUV_SURFACE_SIZE ( pattern - > w , pattern - > h , extra_pitch ) ;
Uint8 * yuv1 = ( Uint8 * ) SDL_malloc ( yuv_len ) ;
Uint8 * yuv2 = ( Uint8 * ) SDL_malloc ( yuv_len ) ;
int yuv1_pitch , yuv2_pitch ;
int result = - 1 ;
if ( ! pattern | | ! yuv1 | | ! yuv2 ) {
SDL_LogError ( SDL_LOG_CATEGORY_APPLICATION , " Couldn't allocate test surfaces " ) ;
goto done ;
}
/* Verify conversion from YUV formats */
for ( i = 0 ; i < SDL_arraysize ( formats ) ; + + i ) {
if ( ! ConvertRGBtoYUV ( formats [ i ] , pattern - > pixels , pattern - > pitch , yuv1 , pattern - > w , pattern - > h , SDL_GetYUVConversionModeForResolution ( pattern - > w , pattern - > h ) , 0 , 100 ) ) {
SDL_LogError ( SDL_LOG_CATEGORY_APPLICATION , " ConvertRGBtoYUV() doesn't support converting to %s \n " , SDL_GetPixelFormatName ( formats [ i ] ) ) ;
goto done ;
}
yuv1_pitch = CalculateYUVPitch ( formats [ i ] , pattern - > w ) ;
if ( ! verify_yuv_data ( formats [ i ] , yuv1 , yuv1_pitch , pattern ) ) {
SDL_LogError ( SDL_LOG_CATEGORY_APPLICATION , " Failed conversion from %s to RGB \n " , SDL_GetPixelFormatName ( formats [ i ] ) ) ;
goto done ;
}
}
/* Verify conversion to YUV formats */
for ( i = 0 ; i < SDL_arraysize ( formats ) ; + + i ) {
yuv1_pitch = CalculateYUVPitch ( formats [ i ] , pattern - > w ) + extra_pitch ;
if ( SDL_ConvertPixels ( pattern - > w , pattern - > h , pattern - > format - > format , pattern - > pixels , pattern - > pitch , formats [ i ] , yuv1 , yuv1_pitch ) < 0 ) {
SDL_LogError ( SDL_LOG_CATEGORY_APPLICATION , " Couldn't convert %s to %s: %s \n " , SDL_GetPixelFormatName ( pattern - > format - > format ) , SDL_GetPixelFormatName ( formats [ i ] ) , SDL_GetError ( ) ) ;
goto done ;
}
if ( ! verify_yuv_data ( formats [ i ] , yuv1 , yuv1_pitch , pattern ) ) {
SDL_LogError ( SDL_LOG_CATEGORY_APPLICATION , " Failed conversion from RGB to %s \n " , SDL_GetPixelFormatName ( formats [ i ] ) ) ;
goto done ;
}
}
/* Verify conversion between YUV formats */
for ( i = 0 ; i < SDL_arraysize ( formats ) ; + + i ) {
for ( j = 0 ; j < SDL_arraysize ( formats ) ; + + j ) {
yuv1_pitch = CalculateYUVPitch ( formats [ i ] , pattern - > w ) + extra_pitch ;
yuv2_pitch = CalculateYUVPitch ( formats [ j ] , pattern - > w ) + extra_pitch ;
if ( SDL_ConvertPixels ( pattern - > w , pattern - > h , pattern - > format - > format , pattern - > pixels , pattern - > pitch , formats [ i ] , yuv1 , yuv1_pitch ) < 0 ) {
SDL_LogError ( SDL_LOG_CATEGORY_APPLICATION , " Couldn't convert %s to %s: %s \n " , SDL_GetPixelFormatName ( pattern - > format - > format ) , SDL_GetPixelFormatName ( formats [ i ] ) , SDL_GetError ( ) ) ;
goto done ;
}
if ( SDL_ConvertPixels ( pattern - > w , pattern - > h , formats [ i ] , yuv1 , yuv1_pitch , formats [ j ] , yuv2 , yuv2_pitch ) < 0 ) {
SDL_LogError ( SDL_LOG_CATEGORY_APPLICATION , " Couldn't convert %s to %s: %s \n " , SDL_GetPixelFormatName ( formats [ i ] ) , SDL_GetPixelFormatName ( formats [ j ] ) , SDL_GetError ( ) ) ;
goto done ;
}
if ( ! verify_yuv_data ( formats [ j ] , yuv2 , yuv2_pitch , pattern ) ) {
SDL_LogError ( SDL_LOG_CATEGORY_APPLICATION , " Failed conversion from %s to %s \n " , SDL_GetPixelFormatName ( formats [ i ] ) , SDL_GetPixelFormatName ( formats [ j ] ) ) ;
goto done ;
}
}
}
/* Verify conversion between YUV formats in-place */
for ( i = 0 ; i < SDL_arraysize ( formats ) ; + + i ) {
for ( j = 0 ; j < SDL_arraysize ( formats ) ; + + j ) {
if ( is_packed_yuv_format ( formats [ i ] ) ! = is_packed_yuv_format ( formats [ j ] ) ) {
/* Can't change plane vs packed pixel layout in-place */
continue ;
}
yuv1_pitch = CalculateYUVPitch ( formats [ i ] , pattern - > w ) + extra_pitch ;
yuv2_pitch = CalculateYUVPitch ( formats [ j ] , pattern - > w ) + extra_pitch ;
if ( SDL_ConvertPixels ( pattern - > w , pattern - > h , pattern - > format - > format , pattern - > pixels , pattern - > pitch , formats [ i ] , yuv1 , yuv1_pitch ) < 0 ) {
SDL_LogError ( SDL_LOG_CATEGORY_APPLICATION , " Couldn't convert %s to %s: %s \n " , SDL_GetPixelFormatName ( pattern - > format - > format ) , SDL_GetPixelFormatName ( formats [ i ] ) , SDL_GetError ( ) ) ;
goto done ;
}
if ( SDL_ConvertPixels ( pattern - > w , pattern - > h , formats [ i ] , yuv1 , yuv1_pitch , formats [ j ] , yuv1 , yuv2_pitch ) < 0 ) {
SDL_LogError ( SDL_LOG_CATEGORY_APPLICATION , " Couldn't convert %s to %s: %s \n " , SDL_GetPixelFormatName ( formats [ i ] ) , SDL_GetPixelFormatName ( formats [ j ] ) , SDL_GetError ( ) ) ;
goto done ;
}
if ( ! verify_yuv_data ( formats [ j ] , yuv1 , yuv2_pitch , pattern ) ) {
SDL_LogError ( SDL_LOG_CATEGORY_APPLICATION , " Failed conversion from %s to %s \n " , SDL_GetPixelFormatName ( formats [ i ] ) , SDL_GetPixelFormatName ( formats [ j ] ) ) ;
goto done ;
}
}
}
result = 0 ;
done :
SDL_free ( yuv1 ) ;
SDL_free ( yuv2 ) ;
SDL_FreeSurface ( pattern ) ;
return result ;
}
int
main ( int argc , char * * argv )
{
struct {
SDL_bool enable_intrinsics ;
int pattern_size ;
int extra_pitch ;
} automated_test_params [ ] = {
/* Test: even width and height */
{ SDL_FALSE , 2 , 0 } ,
{ SDL_FALSE , 4 , 0 } ,
/* Test: odd width and height */
{ SDL_FALSE , 1 , 0 } ,
{ SDL_FALSE , 3 , 0 } ,
/* Test: even width and height, extra pitch */
{ SDL_FALSE , 2 , 3 } ,
{ SDL_FALSE , 4 , 3 } ,
/* Test: odd width and height, extra pitch */
{ SDL_FALSE , 1 , 3 } ,
{ SDL_FALSE , 3 , 3 } ,
/* Test: even width and height with intrinsics */
{ SDL_TRUE , 32 , 0 } ,
/* Test: odd width and height with intrinsics */
{ SDL_TRUE , 33 , 0 } ,
{ SDL_TRUE , 37 , 0 } ,
/* Test: even width and height with intrinsics, extra pitch */
{ SDL_TRUE , 32 , 3 } ,
/* Test: odd width and height with intrinsics, extra pitch */
{ SDL_TRUE , 33 , 3 } ,
{ SDL_TRUE , 37 , 3 } ,
} ;
int arg = 1 ;
const char * filename ;
SDL_Surface * original ;
SDL_Surface * converted ;
SDL_Window * window ;
SDL_Renderer * renderer ;
SDL_Texture * output [ 3 ] ;
const char * titles [ 3 ] = { " ORIGINAL " , " SOFTWARE " , " HARDWARE " } ;
char title [ 128 ] ;
const char * yuv_name ;
const char * yuv_mode ;
Uint32 rgb_format = SDL_PIXELFORMAT_RGBX8888 ;
Uint32 yuv_format = SDL_PIXELFORMAT_YV12 ;
int current = 0 ;
int pitch ;
Uint8 * raw_yuv ;
Uint32 then , now , i , iterations = 100 ;
SDL_bool should_run_automated_tests = SDL_FALSE ;
while ( argv [ arg ] & & * argv [ arg ] = = ' - ' ) {
if ( SDL_strcmp ( argv [ arg ] , " --jpeg " ) = = 0 ) {
SDL_SetYUVConversionMode ( SDL_YUV_CONVERSION_JPEG ) ;
} else if ( SDL_strcmp ( argv [ arg ] , " --bt601 " ) = = 0 ) {
SDL_SetYUVConversionMode ( SDL_YUV_CONVERSION_BT601 ) ;
} else if ( SDL_strcmp ( argv [ arg ] , " --bt709 " ) = = 0 ) {
SDL_SetYUVConversionMode ( SDL_YUV_CONVERSION_BT709 ) ;
} else if ( SDL_strcmp ( argv [ arg ] , " --auto " ) = = 0 ) {
SDL_SetYUVConversionMode ( SDL_YUV_CONVERSION_AUTOMATIC ) ;
} else if ( SDL_strcmp ( argv [ arg ] , " --yv12 " ) = = 0 ) {
yuv_format = SDL_PIXELFORMAT_YV12 ;
} else if ( SDL_strcmp ( argv [ arg ] , " --iyuv " ) = = 0 ) {
yuv_format = SDL_PIXELFORMAT_IYUV ;
} else if ( SDL_strcmp ( argv [ arg ] , " --yuy2 " ) = = 0 ) {
yuv_format = SDL_PIXELFORMAT_YUY2 ;
} else if ( SDL_strcmp ( argv [ arg ] , " --uyvy " ) = = 0 ) {
yuv_format = SDL_PIXELFORMAT_UYVY ;
} else if ( SDL_strcmp ( argv [ arg ] , " --yvyu " ) = = 0 ) {
yuv_format = SDL_PIXELFORMAT_YVYU ;
} else if ( SDL_strcmp ( argv [ arg ] , " --nv12 " ) = = 0 ) {
yuv_format = SDL_PIXELFORMAT_NV12 ;
} else if ( SDL_strcmp ( argv [ arg ] , " --nv21 " ) = = 0 ) {
yuv_format = SDL_PIXELFORMAT_NV21 ;
} else if ( SDL_strcmp ( argv [ arg ] , " --rgb555 " ) = = 0 ) {
rgb_format = SDL_PIXELFORMAT_RGB555 ;
} else if ( SDL_strcmp ( argv [ arg ] , " --rgb565 " ) = = 0 ) {
rgb_format = SDL_PIXELFORMAT_RGB565 ;
} else if ( SDL_strcmp ( argv [ arg ] , " --rgb24 " ) = = 0 ) {
rgb_format = SDL_PIXELFORMAT_RGB24 ;
} else if ( SDL_strcmp ( argv [ arg ] , " --argb " ) = = 0 ) {
rgb_format = SDL_PIXELFORMAT_ARGB8888 ;
} else if ( SDL_strcmp ( argv [ arg ] , " --abgr " ) = = 0 ) {
rgb_format = SDL_PIXELFORMAT_ABGR8888 ;
} else if ( SDL_strcmp ( argv [ arg ] , " --rgba " ) = = 0 ) {
rgb_format = SDL_PIXELFORMAT_RGBA8888 ;
} else if ( SDL_strcmp ( argv [ arg ] , " --bgra " ) = = 0 ) {
rgb_format = SDL_PIXELFORMAT_BGRA8888 ;
} else if ( SDL_strcmp ( argv [ arg ] , " --automated " ) = = 0 ) {
should_run_automated_tests = SDL_TRUE ;
} else {
SDL_LogError ( SDL_LOG_CATEGORY_APPLICATION , " Usage: %s [--jpeg|--bt601|-bt709|--auto] [--yv12|--iyuv|--yuy2|--uyvy|--yvyu|--nv12|--nv21] [--rgb555|--rgb565|--rgb24|--argb|--abgr|--rgba|--bgra] [image_filename] \n " , argv [ 0 ] ) ;
return 1 ;
}
+ + arg ;
}
/* Run automated tests */
if ( should_run_automated_tests ) {
for ( i = 0 ; i < SDL_arraysize ( automated_test_params ) ; + + i ) {
SDL_LogInfo ( SDL_LOG_CATEGORY_APPLICATION , " Running automated test, pattern size %d, extra pitch %d, intrinsics %s \n " ,
automated_test_params [ i ] . pattern_size ,
automated_test_params [ i ] . extra_pitch ,
automated_test_params [ i ] . enable_intrinsics ? " enabled " : " disabled " ) ;
if ( run_automated_tests ( automated_test_params [ i ] . pattern_size , automated_test_params [ i ] . extra_pitch ) < 0 ) {
return 2 ;
}
}
return 0 ;
}
if ( argv [ arg ] ) {
filename = argv [ arg ] ;
} else {
filename = " testyuv.bmp " ;
}
original = SDL_ConvertSurfaceFormat ( SDL_LoadBMP ( filename ) , SDL_PIXELFORMAT_RGB24 , 0 ) ;
if ( ! original ) {
SDL_LogError ( SDL_LOG_CATEGORY_APPLICATION , " Couldn't load %s: %s \n " , filename , SDL_GetError ( ) ) ;
return 3 ;
}
raw_yuv = SDL_calloc ( 1 , MAX_YUV_SURFACE_SIZE ( original - > w , original - > h , 0 ) ) ;
ConvertRGBtoYUV ( yuv_format , original - > pixels , original - > pitch , raw_yuv , original - > w , original - > h ,
SDL_GetYUVConversionModeForResolution ( original - > w , original - > h ) ,
0 , 100 ) ;
pitch = CalculateYUVPitch ( yuv_format , original - > w ) ;
converted = SDL_CreateRGBSurfaceWithFormat ( 0 , original - > w , original - > h , 0 , rgb_format ) ;
if ( ! converted ) {
SDL_LogError ( SDL_LOG_CATEGORY_APPLICATION , " Couldn't create converted surface: %s \n " , SDL_GetError ( ) ) ;
return 3 ;
}
then = SDL_GetTicks ( ) ;
for ( i = 0 ; i < iterations ; + + i ) {
SDL_ConvertPixels ( original - > w , original - > h , yuv_format , raw_yuv , pitch , rgb_format , converted - > pixels , converted - > pitch ) ;
}
now = SDL_GetTicks ( ) ;
SDL_LogInfo ( SDL_LOG_CATEGORY_APPLICATION , " %d iterations in %d ms, %.2fms each \n " , iterations , ( now - then ) , ( float ) ( now - then ) / iterations ) ;
window = SDL_CreateWindow ( " YUV test " ,
SDL_WINDOWPOS_UNDEFINED ,
SDL_WINDOWPOS_UNDEFINED ,
original - > w , original - > h ,
0 ) ;
if ( ! window ) {
SDL_LogError ( SDL_LOG_CATEGORY_APPLICATION , " Couldn't create window: %s \n " , SDL_GetError ( ) ) ;
return 4 ;
}
renderer = SDL_CreateRenderer ( window , - 1 , 0 ) ;
if ( ! renderer ) {
SDL_LogError ( SDL_LOG_CATEGORY_APPLICATION , " Couldn't create renderer: %s \n " , SDL_GetError ( ) ) ;
return 4 ;
}
output [ 0 ] = SDL_CreateTextureFromSurface ( renderer , original ) ;
output [ 1 ] = SDL_CreateTextureFromSurface ( renderer , converted ) ;
output [ 2 ] = SDL_CreateTexture ( renderer , yuv_format , SDL_TEXTUREACCESS_STREAMING , original - > w , original - > h ) ;
if ( ! output [ 0 ] | | ! output [ 1 ] | | ! output [ 2 ] ) {
SDL_LogError ( SDL_LOG_CATEGORY_APPLICATION , " Couldn't set create texture: %s \n " , SDL_GetError ( ) ) ;
return 5 ;
}
SDL_UpdateTexture ( output [ 2 ] , NULL , raw_yuv , pitch ) ;
yuv_name = SDL_GetPixelFormatName ( yuv_format ) ;
if ( SDL_strncmp ( yuv_name , " SDL_PIXELFORMAT_ " , 16 ) = = 0 ) {
yuv_name + = 16 ;
}
switch ( SDL_GetYUVConversionModeForResolution ( original - > w , original - > h ) ) {
case SDL_YUV_CONVERSION_JPEG :
yuv_mode = " JPEG " ;
break ;
case SDL_YUV_CONVERSION_BT601 :
yuv_mode = " BT.601 " ;
break ;
case SDL_YUV_CONVERSION_BT709 :
yuv_mode = " BT.709 " ;
break ;
default :
yuv_mode = " UNKNOWN " ;
break ;
}
{ int done = 0 ;
while ( ! done )
{
SDL_Event event ;
while ( SDL_PollEvent ( & event ) > 0 ) {
if ( event . type = = SDL_QUIT ) {
done = 1 ;
}
if ( event . type = = SDL_KEYDOWN ) {
if ( event . key . keysym . sym = = SDLK_ESCAPE ) {
done = 1 ;
} else if ( event . key . keysym . sym = = SDLK_LEFT ) {
- - current ;
} else if ( event . key . keysym . sym = = SDLK_RIGHT ) {
+ + current ;
}
}
if ( event . type = = SDL_MOUSEBUTTONDOWN ) {
if ( event . button . x < ( original - > w / 2 ) ) {
- - current ;
} else {
+ + current ;
}
}
}
/* Handle wrapping */
if ( current < 0 ) {
current + = SDL_arraysize ( output ) ;
}
if ( current > = SDL_arraysize ( output ) ) {
current - = SDL_arraysize ( output ) ;
}
SDL_RenderClear ( renderer ) ;
SDL_RenderCopy ( renderer , output [ current ] , NULL , NULL ) ;
SDL_SetRenderDrawColor ( renderer , 0xFF , 0xFF , 0xFF , 0xFF ) ;
if ( current = = 0 ) {
SDLTest_DrawString ( renderer , 4 , 4 , titles [ current ] ) ;
} else {
SDL_snprintf ( title , sizeof ( title ) , " %s %s %s " , titles [ current ] , yuv_name , yuv_mode ) ;
SDLTest_DrawString ( renderer , 4 , 4 , title ) ;
}
SDL_RenderPresent ( renderer ) ;
SDL_Delay ( 10 ) ;
}
}
SDL_Quit ( ) ;
return 0 ;
}
/* vi: set ts=4 sw=4 expandtab: */