Commit 0b0378ea authored by Laurent Aimar's avatar Laurent Aimar Committed by Rémi Duraffort

Fixed...

Fixed colorthres/erase/extract/gaussianblur/gradient/motionblur/motiondetect/noise/psychedelic/sharpen filters.

input and output picture pitches may not be equal (corruptions or segfaults).
(cherry picked from commit eb07c341123fd85843c56ded82bd1e323fc660cb)
Signed-off-by: default avatarRémi Duraffort <ivoire@videolan.org>
parent 7659cde9
......@@ -186,8 +186,6 @@ static picture_t *Filter( filter_t *p_filter, picture_t *p_pic )
{
picture_t *p_outpic;
filter_sys_t *p_sys = p_filter->p_sys;
uint8_t *p_in_y, *p_in_u, *p_in_v, *p_in_end_u;
uint8_t *p_out_y, *p_out_u, *p_out_v;
vlc_mutex_lock( &p_sys->lock );
int i_simthres = p_sys->i_simthres;
......@@ -204,23 +202,8 @@ static picture_t *Filter( filter_t *p_filter, picture_t *p_pic )
return NULL;
}
p_in_u = p_pic->p[U_PLANE].p_pixels;
p_in_v = p_pic->p[V_PLANE].p_pixels;
p_in_y = p_pic->p[Y_PLANE].p_pixels;
p_in_end_u = p_in_u + p_pic->p[U_PLANE].i_visible_lines
* p_pic->p[U_PLANE].i_pitch - 8;
p_out_y = p_outpic->p[Y_PLANE].p_pixels;
p_out_u = p_outpic->p[U_PLANE].p_pixels;
p_out_v = p_outpic->p[V_PLANE].p_pixels;
/* Create grayscale version of input */
vlc_memcpy( p_out_y, p_in_y, p_pic->p[Y_PLANE].i_visible_lines
* p_pic->p[Y_PLANE].i_pitch - 8 );
vlc_memset( p_out_u, 0x80, p_pic->p[U_PLANE].i_visible_lines
* p_pic->p[U_PLANE].i_pitch - 8 );
vlc_memset( p_out_v, 0x80, p_pic->p[U_PLANE].i_visible_lines
* p_pic->p[U_PLANE].i_pitch - 8 );
/* Copy the Y plane */
plane_CopyPixels( &p_outpic->p[Y_PLANE], &p_pic->p[Y_PLANE] );
/*
* Do the U and V planes
......@@ -232,31 +215,44 @@ static picture_t *Filter( filter_t *p_filter, picture_t *p_pic )
112 * i_blue + 128) >> 8) + 128;
int i_v = (int8_t)(( 112 * i_red - 94 * i_green -
18 * i_blue + 128) >> 8) + 128;
int refu = i_u - 0x80; /*bright red*/
int refv = i_v - 0x80;
int reflength = sqrt(refu*refu+refv*refv);
while( p_in_u < p_in_end_u ) {
/* Length of color vector */
int inu = (*p_in_u) - 0x80;
int inv = (*p_in_v) - 0x80;
int length = sqrt(inu*inu+inv*inv);
int diffu = refu * length - inu *reflength;
int diffv = refv * length - inv *reflength;
long long int difflen2=diffu*diffu;
difflen2 +=diffv*diffv;
long long int thres = length*reflength;
thres *= thres;
if( length > i_satthres && (difflen2*i_simthres< thres ) ) {
*p_out_u = *p_in_u;
*p_out_v = *p_in_v;
// fprintf(stderr,"keeping color %d %d\n", length, difflen2);
for( int y = 0; y < p_pic->p[U_PLANE].i_visible_lines; y++ )
{
uint8_t *p_src_u = &p_pic->p[U_PLANE].p_pixels[y * p_pic->p[U_PLANE].i_pitch];
uint8_t *p_src_v = &p_pic->p[V_PLANE].p_pixels[y * p_pic->p[V_PLANE].i_pitch];
uint8_t *p_dst_u = &p_outpic->p[U_PLANE].p_pixels[y * p_outpic->p[U_PLANE].i_pitch];
uint8_t *p_dst_v = &p_outpic->p[V_PLANE].p_pixels[y * p_outpic->p[V_PLANE].i_pitch];
for( int x = 0; x < p_pic->p[U_PLANE].i_visible_pitch; x++ )
{
/* Length of color vector */
int inu = *p_src_u - 0x80;
int inv = *p_src_v - 0x80;
int length = sqrt(inu*inu+inv*inv);
int diffu = refu * length - inu *reflength;
int diffv = refv * length - inv *reflength;
long long int difflen2=diffu*diffu;
difflen2 +=diffv*diffv;
long long int thres = length*reflength;
thres *= thres;
if( length > i_satthres && (difflen2*i_simthres< thres ) )
{
*p_dst_u++ = *p_src_u;
*p_dst_v++ = *p_src_v;
}
else
{
*p_dst_u++ = 0x80;
*p_dst_v++ = 0x80;
}
p_src_u++;
p_src_v++;
}
p_in_u++;
p_in_v++;
p_out_u++;
p_out_v++;
}
return CopyInfoAndRelease( p_outpic, p_pic );
......@@ -266,8 +262,6 @@ static picture_t *FilterPacked( filter_t *p_filter, picture_t *p_pic )
{
picture_t *p_outpic;
filter_sys_t *p_sys = p_filter->p_sys;
uint8_t *p_in_y, *p_in_u, *p_in_v, *p_in_end_u;
uint8_t *p_out_y, *p_out_u, *p_out_v;
vlc_mutex_lock( &p_sys->lock );
int i_simthres = p_sys->i_simthres;
......@@ -287,22 +281,9 @@ static picture_t *FilterPacked( filter_t *p_filter, picture_t *p_pic )
int i_y_offset, i_u_offset, i_v_offset;
GetPackedYuvOffsets( p_filter->fmt_in.video.i_chroma,
&i_y_offset, &i_u_offset, &i_v_offset );
p_in_y = p_pic->p->p_pixels+i_y_offset;
p_in_u = p_pic->p->p_pixels+i_u_offset;
p_in_v = p_pic->p->p_pixels+i_v_offset;
p_in_end_u = p_in_u + p_pic->p->i_visible_lines
* p_pic->p->i_pitch - 8;
p_out_y = p_outpic->p->p_pixels+i_y_offset;
p_out_u = p_outpic->p->p_pixels+i_u_offset;
p_out_v = p_outpic->p->p_pixels+i_v_offset;
/* Create grayscale version of input */
vlc_memcpy( p_outpic->p->p_pixels, p_pic->p->p_pixels,
p_pic->p->i_visible_lines * p_pic->p->i_pitch - 8 );
/*
* Do the U and V planes
* Copy Y and do the U and V planes
*/
int i_red = ( i_color & 0xFF0000 ) >> 16;
int i_green = ( i_color & 0xFF00 ) >> 8;
......@@ -315,32 +296,41 @@ static picture_t *FilterPacked( filter_t *p_filter, picture_t *p_pic )
int refv = i_v - 0x80;
int reflength = sqrt(refu*refu+refv*refv);
while( p_in_u < p_in_end_u ) {
/* Length of color vector */
int inu = (*p_in_u) - 0x80;
int inv = (*p_in_v) - 0x80;
int length = sqrt(inu*inu+inv*inv);
int diffu = refu * length - inu *reflength;
int diffv = refv * length - inv *reflength;
long long int difflen2=diffu*diffu;
difflen2 +=diffv*diffv;
long long int thres = length*reflength;
thres *= thres;
if( length > i_satthres && (difflen2*i_simthres< thres ) ) {
*p_out_u = *p_in_u;
*p_out_v = *p_in_v;
// fprintf(stderr,"keeping color %d %d\n", length, difflen2);
}
else
for( int y = 0; y < p_pic->p->i_visible_lines; y++ )
{
uint8_t *p_src = &p_pic->p->p_pixels[y * p_pic->p->i_pitch];
uint8_t *p_dst = &p_outpic->p->p_pixels[y * p_outpic->p->i_pitch];
for( int x = 0; x < p_pic->p->i_visible_pitch / 4; x++ )
{
*p_out_u = 0x80;
*p_out_v = 0x80;
p_dst[i_y_offset + 0] = p_src[i_y_offset + 0];
p_dst[i_y_offset + 2] = p_src[i_y_offset + 2];
/* Length of color vector */
int inu = p_src[i_u_offset] - 0x80;
int inv = p_src[i_v_offset] - 0x80;
int length = sqrt(inu*inu+inv*inv);
int diffu = refu * length - inu *reflength;
int diffv = refv * length - inv *reflength;
long long int difflen2=diffu*diffu;
difflen2 +=diffv*diffv;
long long int thres = length*reflength;
thres *= thres;
if( length > i_satthres && (difflen2*i_simthres< thres ) )
{
p_dst[i_u_offset] = p_src[i_u_offset];
p_dst[i_v_offset] = p_src[i_v_offset];
}
else
{
p_dst[i_u_offset] = 0x80;
p_dst[i_v_offset] = 0x80;
}
p_dst += 4;
p_src += 4;
}
p_in_u+=4;
p_in_v+=4;
p_out_u+=4;
p_out_v+=4;
}
return CopyInfoAndRelease( p_outpic, p_pic );
......
......@@ -243,13 +243,11 @@ static void FilterErase( filter_t *p_filter, picture_t *p_inpic,
for( int i_plane = 0; i_plane < p_inpic->i_planes; i_plane++ )
{
const int i_pitch = p_inpic->p[i_plane].i_pitch;
const int i_pitch = p_outpic->p[i_plane].i_pitch;
const int i_2pitch = i_pitch<<1;
const int i_visible_pitch = p_inpic->p[i_plane].i_visible_pitch;
const int i_lines = p_inpic->p[i_plane].i_lines;
const int i_visible_lines = p_inpic->p[i_plane].i_visible_lines;
uint8_t *p_inpix = p_inpic->p[i_plane].p_pixels;
uint8_t *p_outpix = p_outpic->p[i_plane].p_pixels;
uint8_t *p_mask = p_sys->p_mask->A_PIXELS;
int i_x = p_sys->i_x, i_y = p_sys->i_y;
......@@ -276,7 +274,7 @@ static void FilterErase( filter_t *p_filter, picture_t *p_inpic,
i_width = __MIN( i_visible_pitch - i_x, i_width );
/* Copy original pixel buffer */
vlc_memcpy( p_outpix, p_inpix, i_pitch * i_lines );
plane_CopyPixels( &p_outpic->p[i_plane], &p_inpic->p[i_plane] );
/* Horizontal linear interpolation of masked areas */
p_outpix = p_outpic->p[i_plane].p_pixels + i_y*i_pitch + i_x;
......
This diff is collapsed.
......@@ -252,7 +252,7 @@ static picture_t *Filter( filter_t *p_filter, picture_t *p_pic )
const int i_visible_lines = p_pic->p[i_plane].i_visible_lines;
const int i_visible_pitch = p_pic->p[i_plane].i_visible_pitch;
const int i_pitch = p_pic->p[i_plane].i_pitch;
const int i_in_pitch = p_pic->p[i_plane].i_pitch;
int i_line, i_col;
const int x_factor = p_pic->p[Y_PLANE].i_visible_pitch/i_visible_pitch-1;
......@@ -264,7 +264,7 @@ static picture_t *Filter( filter_t *p_filter, picture_t *p_pic )
{
type_t t_value = 0;
int x;
const int c = i_line*i_pitch+i_col;
const int c = i_line*i_in_pitch+i_col;
for( x = __MAX( -i_dim, -i_col*(x_factor+1) );
x <= __MIN( i_dim, (i_visible_pitch - i_col)*(x_factor+1) + 1 );
x++ )
......@@ -281,17 +281,17 @@ static picture_t *Filter( filter_t *p_filter, picture_t *p_pic )
{
type_t t_value = 0;
int y;
const int c = i_line*i_pitch+i_col;
const int c = i_line*i_in_pitch+i_col;
for( y = __MAX( -i_dim, (-i_line)*(y_factor+1) );
y <= __MIN( i_dim, (i_visible_lines - i_line)*(y_factor+1) - 1 );
y++ )
{
t_value += pt_distribution[y+i_dim] *
pt_buffer[c+(y>>y_factor)*i_pitch];
pt_buffer[c+(y>>y_factor)*i_in_pitch];
}
const type_t t_scale = pt_scale[(i_line<<y_factor)*(i_pitch<<x_factor)+(i_col<<x_factor)];
p_out[c] = (uint8_t)(t_value / t_scale); // FIXME wouldn't it be better to round instead of trunc ?
const type_t t_scale = pt_scale[(i_line<<y_factor)*(i_in_pitch<<x_factor)+(i_col<<x_factor)];
p_out[i_line * p_outpic->p[i_plane].i_pitch + i_col] = (uint8_t)(t_value / t_scale); // FIXME wouldn't it be better to round instead of trunc ?
}
}
}
......
......@@ -360,12 +360,8 @@ static void FilterGradient( filter_t *p_filter, picture_t *p_inpic,
if( p_filter->p_sys->b_cartoon )
{
vlc_memcpy( p_outpic->p[U_PLANE].p_pixels,
p_inpic->p[U_PLANE].p_pixels,
p_outpic->p[U_PLANE].i_lines * p_outpic->p[U_PLANE].i_pitch );
vlc_memcpy( p_outpic->p[V_PLANE].p_pixels,
p_inpic->p[V_PLANE].p_pixels,
p_outpic->p[V_PLANE].i_lines * p_outpic->p[V_PLANE].i_pitch );
plane_CopyPixels( &p_outpic->p[U_PLANE], &p_inpic->p[U_PLANE] );
plane_CopyPixels( &p_outpic->p[V_PLANE], &p_inpic->p[V_PLANE] );
}
else
{
......@@ -504,12 +500,8 @@ static void FilterEdge( filter_t *p_filter, picture_t *p_inpic,
if( p_filter->p_sys->b_cartoon )
{
vlc_memcpy( p_outpic->p[U_PLANE].p_pixels,
p_inpic->p[U_PLANE].p_pixels,
p_outpic->p[U_PLANE].i_lines * p_outpic->p[U_PLANE].i_pitch );
vlc_memcpy( p_outpic->p[V_PLANE].p_pixels,
p_inpic->p[V_PLANE].p_pixels,
p_outpic->p[V_PLANE].i_lines * p_outpic->p[V_PLANE].i_pitch );
plane_CopyPixels( &p_outpic->p[U_PLANE], &p_inpic->p[U_PLANE] );
plane_CopyPixels( &p_outpic->p[V_PLANE], &p_inpic->p[V_PLANE] );
}
else
{
......@@ -687,15 +679,9 @@ static void FilterHough( filter_t *p_filter, picture_t *p_inpic,
vlc_memset( p_hough, 0, i_diag * i_nb_steps * sizeof(int) );
vlc_memcpy(
p_outpic->p[Y_PLANE].p_pixels, p_inpic->p[Y_PLANE].p_pixels,
p_outpic->p[Y_PLANE].i_lines * p_outpic->p[Y_PLANE].i_pitch );
vlc_memcpy(
p_outpic->p[U_PLANE].p_pixels, p_inpic->p[U_PLANE].p_pixels,
p_outpic->p[U_PLANE].i_lines * p_outpic->p[U_PLANE].i_pitch );
vlc_memcpy(
p_outpic->p[V_PLANE].p_pixels, p_inpic->p[V_PLANE].p_pixels,
p_outpic->p[V_PLANE].i_lines * p_outpic->p[V_PLANE].i_pitch );
plane_CopyPixels( &p_outpic->p[Y_PLANE], &p_inpic->p[Y_PLANE] );
plane_CopyPixels( &p_outpic->p[U_PLANE], &p_inpic->p[U_PLANE] );
plane_CopyPixels( &p_outpic->p[V_PLANE], &p_inpic->p[V_PLANE] );
GaussianConvolution( p_inpic, p_smooth );
......
......@@ -43,7 +43,6 @@ static int Create ( vlc_object_t * );
static void Destroy ( vlc_object_t * );
static picture_t *Filter ( filter_t *, picture_t * );
static void RenderBlur ( filter_sys_t *, picture_t *, picture_t * );
static void Copy ( filter_t *, picture_t * );
static int MotionBlurCallback( vlc_object_t *, char const *,
vlc_value_t, vlc_value_t, void * );
......@@ -79,11 +78,11 @@ static const char *const ppsz_filter_options[] = {
*****************************************************************************/
struct filter_sys_t
{
picture_t *p_tmp;
bool b_first;
vlc_spinlock_t lock;
int i_factor;
uint8_t **pp_planes;
int i_planes;
};
/*****************************************************************************
......@@ -98,6 +97,14 @@ static int Create( vlc_object_t *p_this )
if( p_filter->p_sys == NULL )
return VLC_ENOMEM;
p_filter->p_sys->p_tmp = picture_NewFromFormat( &p_filter->fmt_in.video );
if( !p_filter->p_sys->p_tmp )
{
free( p_filter->p_sys );
return VLC_ENOMEM;
}
p_filter->p_sys->b_first = true;
p_filter->pf_video_filter = Filter;
config_ChainParse( p_filter, FILTER_PREFIX, ppsz_filter_options,
......@@ -109,8 +116,6 @@ static int Create( vlc_object_t *p_this )
var_AddCallback( p_filter, FILTER_PREFIX "factor",
MotionBlurCallback, p_filter->p_sys );
p_filter->p_sys->pp_planes = NULL;
p_filter->p_sys->i_planes = 0;
return VLC_SUCCESS;
}
......@@ -126,9 +131,7 @@ static void Destroy( vlc_object_t *p_this )
MotionBlurCallback, p_filter->p_sys );
vlc_spin_destroy( &p_filter->p_sys->lock );
while( p_filter->p_sys->i_planes-- )
free( p_filter->p_sys->pp_planes[p_filter->p_sys->i_planes] );
free( p_filter->p_sys->pp_planes );
picture_Release( p_filter->p_sys->p_tmp );
free( p_filter->p_sys );
}
......@@ -149,24 +152,16 @@ static picture_t *Filter( filter_t *p_filter, picture_t *p_pic )
return NULL;
}
if( !p_sys->pp_planes )
if( p_sys->b_first )
{
/* initialise our picture buffer */
int i_plane;
p_sys->i_planes = p_pic->i_planes;
p_sys->pp_planes =
(uint8_t**)malloc( p_sys->i_planes * sizeof( uint8_t * ) );
for( i_plane = 0; i_plane < p_pic->i_planes; i_plane++ )
{
p_sys->pp_planes[i_plane] = (uint8_t*)malloc(
p_pic->p[i_plane].i_pitch * p_pic->p[i_plane].i_visible_lines );
}
Copy( p_filter, p_pic );
picture_CopyPixels( p_sys->p_tmp, p_pic );
p_sys->b_first = false;
}
/* Get a new picture */
RenderBlur( p_sys, p_pic, p_outpic );
Copy( p_filter, p_outpic );
picture_CopyPixels( p_sys->p_tmp, p_outpic );
return CopyInfoAndRelease( p_outpic, p_pic );
}
......@@ -186,14 +181,13 @@ static void RenderBlur( filter_sys_t *p_sys, picture_t *p_newpic,
for( i_plane = 0; i_plane < p_outpic->i_planes; i_plane++ )
{
uint8_t *p_old, *p_new, *p_out, *p_out_end, *p_out_line_end;
const int i_pitch = p_outpic->p[i_plane].i_pitch;
const int i_visible_pitch = p_outpic->p[i_plane].i_visible_pitch;
const int i_visible_lines = p_outpic->p[i_plane].i_visible_lines;
p_out = p_outpic->p[i_plane].p_pixels;
p_new = p_newpic->p[i_plane].p_pixels;
p_old = p_sys->pp_planes[i_plane];
p_out_end = p_out + i_pitch * i_visible_lines;
p_old = p_sys->p_tmp->p[i_plane].p_pixels;
p_out_end = p_out + p_outpic->p[i_plane].i_pitch * i_visible_lines;
while ( p_out < p_out_end )
{
p_out_line_end = p_out + i_visible_pitch;
......@@ -204,24 +198,13 @@ static void RenderBlur( filter_sys_t *p_sys, picture_t *p_newpic,
((*p_new++) * i_newfactor)) >> 7;
}
p_old += i_pitch - i_visible_pitch;
p_new += i_pitch - i_visible_pitch;
p_out += i_pitch - i_visible_pitch;
p_old += p_sys->p_tmp->p[i_plane].i_pitch - i_visible_pitch;
p_new += p_newpic->p[i_plane].i_pitch - i_visible_pitch;
p_out += p_outpic->p[i_plane].i_pitch - i_visible_pitch;
}
}
}
static void Copy( filter_t *p_filter, picture_t *p_pic )
{
int i_plane;
for( i_plane = 0; i_plane < p_pic->i_planes; i_plane++ )
{
vlc_memcpy(
p_filter->p_sys->pp_planes[i_plane], p_pic->p[i_plane].p_pixels,
p_pic->p[i_plane].i_pitch * p_pic->p[i_plane].i_visible_lines );
}
}
static int MotionBlurCallback( vlc_object_t *p_this, char const *psz_var,
vlc_value_t oldval, vlc_value_t newval,
void *p_data )
......
......@@ -168,13 +168,6 @@ static picture_t *Filter( filter_t *p_filter, picture_t *p_inpic )
const uint8_t *p_inpix = p_inpic->p[Y_PLANE].p_pixels;
const int i_src_pitch = p_inpic->p[Y_PLANE].i_pitch;
if( !p_sys->b_old )
{
picture_Copy( p_sys->p_old, p_inpic );
p_sys->b_old = true;
return p_inpic;
}
p_outpic = filter_NewPicture( p_filter );
if( !p_outpic )
{
......@@ -183,6 +176,14 @@ static picture_t *Filter( filter_t *p_filter, picture_t *p_inpic )
}
picture_Copy( p_outpic, p_inpic );
if( !p_sys->b_old )
{
picture_Copy( p_sys->p_old, p_inpic );
picture_Release( p_inpic );
p_sys->b_old = true;
return p_outpic;
}
/**
* Substract Y planes
*/
......
......@@ -100,7 +100,8 @@ static picture_t *Filter( filter_t *p_filter, picture_t *p_pic )
const int i_num_lines = p_pic->p[i_index].i_visible_lines;
const int i_num_cols = p_pic->p[i_index].i_visible_pitch;
const int i_pitch = p_pic->p[i_index].i_pitch;
const int i_in_pitch = p_pic->p[i_index].i_pitch;
const int i_out_pitch = p_outpic->p[i_index].i_pitch;
int i_line, i_col;
......@@ -109,7 +110,7 @@ static picture_t *Filter( filter_t *p_filter, picture_t *p_pic )
if( vlc_mrand48()&7 )
{
/* line isn't noisy */
vlc_memcpy( p_out+i_line*i_pitch, p_in+i_line*i_pitch,
vlc_memcpy( p_out+i_line*i_out_pitch, p_in+i_line*i_in_pitch,
i_num_cols );
}
else
......@@ -120,12 +121,12 @@ static picture_t *Filter( filter_t *p_filter, picture_t *p_pic )
{
if( ((unsigned)vlc_mrand48())%noise_level )
{
p_out[i_line*i_pitch+i_col] =
p_in[i_line*i_pitch+i_col];
p_out[i_line*i_out_pitch+i_col] =
p_in[i_line*i_in_pitch+i_col];
}
else
{
p_out[i_line*i_pitch+i_col] = (vlc_mrand48()&3)*0x7f;
p_out[i_line*i_out_pitch+i_col] = (vlc_mrand48()&3)*0x7f;
}
}
}
......
......@@ -174,8 +174,7 @@ static picture_t *Filter( filter_t *p_filter, picture_t *p_pic )
}
/* luminance */
vlc_memcpy( p_outpic->p[Y_PLANE].p_pixels, p_pic->p[Y_PLANE].p_pixels,
p_outpic->p[Y_PLANE].i_lines * p_outpic->p[Y_PLANE].i_pitch );
plane_CopyPixels( &p_outpic->p[Y_PLANE], &p_pic->p[Y_PLANE] );
/* image visualization */
fmt_out = p_filter->fmt_out.video;
......
......@@ -184,8 +184,8 @@ static picture_t *Filter( filter_t *p_filter, picture_t *p_pic )
/* process the Y plane */
p_src = p_pic->p[Y_PLANE].p_pixels;
p_out = p_outpic->p[Y_PLANE].p_pixels;
i_src_pitch = p_pic->p[Y_PLANE].i_visible_pitch;
i_out_pitch = p_outpic->p[Y_PLANE].i_visible_pitch;
i_src_pitch = p_pic->p[Y_PLANE].i_pitch;
i_out_pitch = p_outpic->p[Y_PLANE].i_pitch;
/* perform convolution only on Y plane. Avoid border line. */
vlc_mutex_lock( &p_filter->p_sys->lock );
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment