Jump to content

Tiled Form (Proof of Concept Plugin)


Recommended Posts

I had tried converting g'mic-qt `Tiled Form` which is under Testing->Reptorian into a paint.net plugin. Not able to code in shape, so I went with clipboard to create a proof of concept plugin.

 

The pseudo code for this plugin

1. Load Two Images (Target Image, Shape Image)

2. On Shape Image, transformation are applied such as mirror x/mirror y. Then it is cropped and fitted inside each rectangles. Form ratio less than 1 will ensure shape boundary has a length on at least one dimension being always smaller than each rectangle. It should be normalized though that's for later.

3. Create tile surface which will reflect only on x or y, or both or periodic mode.

4. Find weighed averages for rectangle using shapes.

5. Use Tile Surface to interpolate between weighed averages of all pixels that resides within rectangles. Then, it's done.

 

For evidence to support this proof of concept, insert a shape into your clipboard. Black Background and white shape. Then run this plugin. You will see a different version of TR's Tiled Pixels.

 

Some bugs still remain on this proof of concept though.

 

Spoiler

// Name:
// Submenu:
// Author:
// Title:
// Version:
// Desc:
// Keywords:
// URL:
// Help:
#region UICode
IntSliderControl shape_width = 30; // [4,100] Shape Width
IntSliderControl shape_height = 30; // [4,100] Shape Height
DoubleSliderControl shape_ratio = 90; // [0.1,100] Shape Ratio (%)
AngleControl shape_rotation = 45; // [-180,180] Shape Rotation
ListBoxControl shape_mirror = 0; // Shape Mirror Axis|None|X|Y
ListBoxControl tile_boundary = 0; // Tile Boundary|Periodic|Mirror X|Mirror Y|Mirror XY
#endregion
#if DEBUG
#endif

int tile_width,tile_height;

// Ref_Shape_Clipboard surface
Surface Ref_Shape_Clipboard = null;
Surface Rotated_Surface = null;
Surface Crop_Surface = null;
Surface Resized_Surface = null;
Surface Inside_Shape_Multiply = null;
Surface Outside_Shape_Multiply = null;
Surface Mini_Inside_Shape_Multiply = null;
Surface Mini_Outside_Shape_Multiply = null;

int [,] Mini_Inside_Shape_R;
int [,] Mini_Inside_Shape_G;
int [,] Mini_Inside_Shape_B;
int [,] Mini_Inside_Shape_A;
int [,] Mini_Outside_Shape_R;
int [,] Mini_Outside_Shape_G;
int [,] Mini_Outside_Shape_B;
int [,] Mini_Outside_Shape_A;

float [,] End_Surface;
float [,] Tile_Surface;

private Surface clipboardSurface = null;
private bool readClipboard = false;
private bool activate_fill = true;
bool activate_pixelate_mode;

protected override void OnDispose(bool disposing)
{
    if (disposing)
    {
        // Release any surfaces or effects you've created
        if (Ref_Shape_Clipboard != null) Ref_Shape_Clipboard.Dispose();
        Ref_Shape_Clipboard = null;
        if (clipboardSurface != null) clipboardSurface.Dispose();
        clipboardSurface = null;
    }

    base.OnDispose(disposing);
}

// This single-threaded function is called after the UI changes and before the Render function is called
// The purpose is to prepare anything you'll need in the Render function
void PreRender(Surface dst, Surface src)
{
    int w = src.Width;
    int h = src.Height;
    int ww = w - 1;
    int hh = h - 1;
    double f_w = (double)(w);
    double f_h = (double)(h);
    int mini_width = (int)(Math.Ceiling(f_w/(double)(shape_width)));
    int mini_height = (int)(Math.Ceiling(f_h/(double)(shape_height)));
    int nearest_width = mini_width*shape_width;
    int nearest_height = mini_height*shape_height;
    double f_ww,f_hh,f_nw,f_nh,half_f_nw,half_f_nh,f_x,f_y,nx,ny,f_form_w,f_form_h;
    int nw,nh,nwx,nhy,min_x,max_x,min_y,max_y,crop_w,crop_h,form_w,form_h,off_x,off_y,end_off_x,end_off_y,tx,ty,sx,sy,posx,posy;
    double abs_shape_rotation = Math.Abs(shape_rotation);
    double ang = Math.PI * shape_rotation/180;
    double pos_ang = Math.Abs(Math.PI * shape_rotation/180);
    double absang = Math.Abs(pos_ang);
    double cos_ang = Math.Cos(ang);
    double sin_ang = Math.Sin(ang);
    double pos_cos_ang = Math.Cos(pos_ang);
    double pos_sin_ang = Math.Sin(pos_ang);
    double half_pi = Math.PI / 2;
    double alt_pos_cos_ang = Math.Cos(pos_ang - half_pi);
    double alt_pos_sin_ang = Math.Sin(pos_ang - half_pi);
    bool use_180;
    bool neg_90 = shape_rotation == -90;
    ColorBgra Rotated_Surface_Color;
    ColorBgra Resized_Surface_Color;
    ColorBgra Current_Color;
    int R,G,B,A;
    float f_offx,f_offy,F_AVG,F_R,F_G,F_B,F_A,shade,t_ir,t_ig,t_ib,t_ia,t_or,t_og,t_ob,t_oa;
    float w_avg = 0;
    float i_w_avg=0;
    activate_pixelate_mode = false;

    Mini_Inside_Shape_R = new int [mini_width,mini_height];
    Mini_Inside_Shape_G = new int [mini_width,mini_height];
    Mini_Inside_Shape_B = new int [mini_width,mini_height];
    Mini_Inside_Shape_A = new int [mini_width,mini_height];
    Mini_Outside_Shape_R= new int [mini_width,mini_height];
    Mini_Outside_Shape_G= new int [mini_width,mini_height];
    Mini_Outside_Shape_B= new int [mini_width,mini_height];
    Mini_Outside_Shape_A= new int [mini_width,mini_height];

    if (abs_shape_rotation==180) { use_180 = true;}
    else {use_180 = false;}

    if (Ref_Shape_Clipboard == null)
    {
        Ref_Shape_Clipboard = new Surface(src.Size);
    }

    if (!readClipboard)
    {
        readClipboard = true;
        clipboardSurface = Services.GetService<IClipboardService>().TryGetSurface();
    }

    // Copy from the Clipboard to the Ref_Shape_Clipboard surface
    if (readClipboard){
        for (int y = 0; y < Ref_Shape_Clipboard.Size.Height; y++)
        {
            if (IsCancelRequested) return;
            for (int x = 0; x < Ref_Shape_Clipboard.Size.Width; x++)
            {
                if (clipboardSurface != null)
                {
                    switch(shape_mirror){
                        case 0:
                            Ref_Shape_Clipboard[x,y] = clipboardSurface.GetBilinearSampleWrapped(x,y);
                            break;
                        case 1:
                            Ref_Shape_Clipboard[x,y] = clipboardSurface.GetBilinearSampleWrapped(ww-x,y);
                            break;
                        case 2:
                            Ref_Shape_Clipboard[x,y] = clipboardSurface.GetBilinearSampleWrapped(x,hh-y);
                            break;
                
                    }
                }

                else
                {
                Ref_Shape_Clipboard[x,y] = Color.Transparent;
                }
            }
        }   
    }

    //Copy rotated clipboard image from rotated clipboardsurface into rotated_surface.
    if (abs_shape_rotation>0 && abs_shape_rotation!=180){
        if(abs_shape_rotation==90){
            nw = h; 
            nh = w;
            nwx = nw - 1;
            nhy = nh - 1;
            Rotated_Surface = new Surface(nw,nh);
            
            for (int y = 0 ; y < nh ; y++){
                for(int x = 0 ; x < nw ; x++){
                    if (neg_90) {Rotated_Surface[x,y] = Ref_Shape_Clipboard[nhy-y,nwx-x];}
                    else {Rotated_Surface[x,y] = Ref_Shape_Clipboard[y,x];}
                }
            }
        }
        else{
            if(abs_shape_rotation<90){
                f_nw = Math.Abs(f_w*pos_cos_ang+f_h*pos_sin_ang);
                f_nh = Math.Abs(f_w*pos_sin_ang+f_h*pos_cos_ang);
            }
            else{
                f_nw = Math.Abs(f_h*alt_pos_cos_ang+f_w*alt_pos_sin_ang);
                f_nh = Math.Abs(f_h*alt_pos_sin_ang+f_w*alt_pos_cos_ang);

            }
            nw = (int)(Math.Round(f_nw));
            nh = (int)(Math.Round(f_nh));
            Debug.WriteLine(nw);
            Debug.WriteLine(nh);
            half_f_nw = f_nw / 2;
            half_f_nh = f_nh / 2;
            f_ww=(double)(nw-1);
            f_hh=(double)(nh-1);
            Rotated_Surface = new Surface(nw,nh);
            for (int y = 0 ; y < nh ; y++){
                f_y = (double)(y)-half_f_nh;
                for(int x = 0 ; x < nw ; x++){
                    f_x = (double)(x)-half_f_nw;
                    nx = f_x * cos_ang - f_y * sin_ang + half_f_nw;
                    ny = f_x * sin_ang + f_y * cos_ang + half_f_nh;
                    if((nx>=0&&nx<=f_ww)&&(ny>=0&&ny<=f_hh)){
                        Rotated_Surface[x,y]=Ref_Shape_Clipboard.GetBilinearSample((float)(nx),(float)(ny));
                    }
                    else {Rotated_Surface[x,y]=ColorBgra.Transparent;}
                }
            }
        }
    }
    else{
        nw = w;
        nh = h;
        nwx = nw - 1;
        nhy = nh - 1;
        Rotated_Surface = new Surface(nw,nh);

        for (int y = 0 ; y < nh ; y++){
            for(int x = 0 ; x < nw ; x++){
                if (use_180){Rotated_Surface[x,y] = Ref_Shape_Clipboard[nwx-x,nhy-y];}
                else{Rotated_Surface[x,y] = Ref_Shape_Clipboard[x,y];}
            }
        }

    }
    nwx = nw - 1;
    nhy = nh - 1;

    //find(min_x)
    min_x = nw-1;
    for (int y = 0 ; y < nh ; y++){
        for (int x = 0; x < nw ; x++){
            Rotated_Surface_Color = Rotated_Surface[x,y];
            A = Rotated_Surface_Color.A;
            if(A>0){
                R=Rotated_Surface_Color.R;
                G=Rotated_Surface_Color.G;
                B=Rotated_Surface_Color.B;
                if(R>0||G>0||B>0){
                    if(x<min_x){min_x=x;break;}
                }
            }
        }
        if(min_x==0){break;}
    }

    //find(max_x)
    max_x = 0;
    for (int y = 0 ; y < nh ; y++){
        for (int x = nwx; x >= 0 ; x--){
            Rotated_Surface_Color = Rotated_Surface[x,y];
            A = Rotated_Surface_Color.A;
            if(A>0){
                R=Rotated_Surface_Color.R;
                G=Rotated_Surface_Color.G;
                B=Rotated_Surface_Color.B;
                if(R>0||G>0||B>0){
                    if(x>max_x){max_x=x;break;}
                }
            }
        }
        if(max_x==nwx){break;}
    }

    //find(min_y)
    min_y=nh-1;
    for (int x = 0 ; x < nw ; x++){
        for (int y = 0 ; y < nh ; y++){
            Rotated_Surface_Color = Rotated_Surface[x,y];
            A = Rotated_Surface_Color.A;
            if(A>0){
                R=Rotated_Surface_Color.R;
                G=Rotated_Surface_Color.G;
                B=Rotated_Surface_Color.B;
                if(R>0||G>0||B>0){
                    if(y<min_y){min_y=y;break;}
                }
            }
        }
        if(min_y==0){break;}
    }

    //find(min_y)
    max_y=0;
    for (int x = 0 ; x < nw ; x++){
        for (int y = nhy ; y >= 0 ; y--){
            Rotated_Surface_Color = Rotated_Surface[x,y];
            A = Rotated_Surface_Color.A;
            if(A>0){
                R=Rotated_Surface_Color.R;
                G=Rotated_Surface_Color.G;
                B=Rotated_Surface_Color.B;
                if(R>0||G>0||B>0){
                    if(y>max_y){max_y=y;break;}
                }
            }
        }
        if(max_y==nhy){break;}
    }
    crop_w = max_x - min_x + 1;
    crop_h = max_y - min_y + 1;

    if (crop_w>0){
        Crop_Surface = new Surface(crop_w,crop_h);
        for (int x=0 ; x < crop_w ; x++){
            for(int y=0 ; y < crop_h ; y++){
                Crop_Surface[x,y]=Rotated_Surface.GetPoint(x+min_x,y+min_y);
            }
        }
        if (shape_ratio<100){
            f_form_w = (double)(shape_width) * shape_ratio/100;
            f_form_h = (double)(shape_height) * shape_ratio/100;
            form_w = (int)(f_form_w);
            form_h = (int)(f_form_h);
            Resized_Surface = new Surface(form_w,form_h);
            Resized_Surface.FitSurface(ResamplingAlgorithm.Bicubic,Crop_Surface);
            f_offx = (float)(shape_width) - (float)(f_form_w);
            f_offy = (float)(shape_height) - (float)(f_form_h);
            f_offx/=2;
            f_offy/=2;
            off_x = (int)(Math.Round(f_offx));
            off_y = (int)(Math.Round(f_offy));
            Debug.WriteLine(off_x);
            end_off_x = off_x + form_w ;
            end_off_y = off_y + form_h ;
            End_Surface = new float[shape_width,shape_height];
            for (int x=0 ; x < shape_width ; x++){
               for (int y=0 ; y< shape_height ; y++){
                   if((x>=off_x&&x<end_off_x)&&(y>=off_x&&y<end_off_x)){
                    Resized_Surface_Color = Resized_Surface[x-off_x,y-off_y];
                    F_R = (float)(Resized_Surface_Color.R);
                    F_G = (float)(Resized_Surface_Color.G);
                    F_B = (float)(Resized_Surface_Color.B);
                    F_A = (float)(Resized_Surface_Color.A)/255;
                    F_AVG = (F_R+F_G+F_B)/765;
                    shade = F_AVG * F_A;
                    End_Surface[x,y] = shade;
                    w_avg+=shade;
                   }
                   else{
                       End_Surface[x,y]=0;
                   }
               }
            }
            w_avg = w_avg / ((float)(shape_width) * (float)(shape_height));
            i_w_avg=1-w_avg;
        }
        else{
            Resized_Surface = new Surface(shape_width,shape_height);
            Resized_Surface.FitSurface(ResamplingAlgorithm.Bicubic,Crop_Surface);
            End_Surface = new float[shape_width,shape_height];
            for (int x=0 ; x < shape_width ; x++){
                for (int y=0 ; y< shape_height ; y++){
                    Resized_Surface_Color = Resized_Surface[x,y];
                    F_R = (float)(Resized_Surface_Color.R);
                    F_G = (float)(Resized_Surface_Color.G);
                    F_B = (float)(Resized_Surface_Color.B);
                    F_A = (float)(Resized_Surface_Color.A)/255;
                    F_AVG = (F_R+F_G+F_B)/765;
                    shade = F_AVG * F_A;
                    End_Surface[x,y] = shade;
                    w_avg+=shade;
                }
            }
            w_avg = w_avg / ((float)(shape_width) * (float)(shape_height));
            i_w_avg=1-w_avg;
        }
        tile_width = shape_width;
        tile_height = shape_height;
        switch(tile_boundary){
            case 0:
                Tile_Surface = End_Surface;
                break;
            case 1:
                tile_width = shape_width*2;
                tile_height = shape_height;
                Tile_Surface = new float [tile_width,tile_height];
                for (int x = 0 ; x < tile_width ; x++){
                    if(x < shape_width){tx = x;}
                    else { tx = (shape_width - 1) - x % shape_width;}
                    for (int y = 0 ; y < tile_height ; y++){
                        Tile_Surface[x,y] = End_Surface[tx,y];
                    }
                }
                break;
            case 2:
                tile_width = shape_width;
                tile_height = shape_height*2;
                Tile_Surface = new float [tile_width,tile_height];
                for (int y = 0 ; y < tile_height ; y++){
                    if(y < shape_height){ty = y;}
                    else { ty = (shape_height - 1) - y % shape_height;}
                    for (int x = 0 ; x < tile_width ; x++){
                        Tile_Surface[x,y] = End_Surface[x,ty];
                    }
                }
                break;
            case 3:
                tile_width = shape_width*2;
                tile_height = shape_height*2;
                Tile_Surface = new float [tile_width,tile_height];
                for (int y = 0 ; y < tile_height ; y++){
                    if(y < shape_height){ty = y;}
                    else { ty = (shape_height - 1) - y % shape_height;}
                    for (int x = 0 ; x < tile_width ; x++){
                        if(x < shape_width){tx = x;}
                        else { tx = (shape_width - 1) - x % shape_width;}
                        Tile_Surface[x,y] = End_Surface[tx,ty];
                    }
                }
                break;
        }
        Inside_Shape_Multiply = new Surface(nearest_width,nearest_height);
        for ( int y = 0 ; y < nearest_height ; y++){
            sy = Math.Min(hh,y);
            for (int x = 0 ; x < nearest_width ; x++){
                sx = Math.Min(ww,x);
                Inside_Shape_Multiply[x,y]=src[sx,sy];
            }
        }
        Outside_Shape_Multiply = Inside_Shape_Multiply;
        for (int y = 0 ; y < nearest_height ; y++){
            for (int x = 0; x < nearest_width ; x++){
                Current_Color = Inside_Shape_Multiply[x,y];
                t_ir = (float)(Current_Color.R) * Tile_Surface[x%tile_width,y%tile_height];
                t_ig = (float)(Current_Color.G) * Tile_Surface[x%tile_width,y%tile_height];
                t_ib = (float)(Current_Color.B) * Tile_Surface[x%tile_width,y%tile_height];
                t_ia = (float)(Current_Color.A) * Tile_Surface[x%tile_width,y%tile_height];
                t_or = (float)(Current_Color.R) * (1-Tile_Surface[x%tile_width,y%tile_height]);
                t_og = (float)(Current_Color.G) * (1-Tile_Surface[x%tile_width,y%tile_height]);
                t_ob = (float)(Current_Color.B) * (1-Tile_Surface[x%tile_width,y%tile_height]);
                t_oa = (float)(Current_Color.A) * (1-Tile_Surface[x%tile_width,y%tile_height]);
                posx = x / shape_width; posy = y / shape_height;
                Mini_Inside_Shape_R[posx,posy]+=(int)(Math.Round(t_ir));
                Mini_Inside_Shape_G[posx,posy]+=(int)(Math.Round(t_ig));
                Mini_Inside_Shape_B[posx,posy]+=(int)(Math.Round(t_ib));
                Mini_Inside_Shape_A[posx,posy]+=(int)(Math.Round(t_ia));
                Mini_Outside_Shape_R[posx,posy]+=(int)(Math.Round(t_or));
                Mini_Outside_Shape_G[posx,posy]+=(int)(Math.Round(t_og));
                Mini_Outside_Shape_B[posx,posy]+=(int)(Math.Round(t_ob));
                Mini_Outside_Shape_A[posx,posy]+=(int)(Math.Round(t_oa));
            }
        }
        float d_shape_area = (float)(shape_width)*(float)(shape_height)*w_avg;
        float d_shape_area_2 = (float)(shape_width)*(float)(shape_height)*i_w_avg;
        for (int y = 0 ; y < mini_height ; y++){
            for (int x = 0 ; x < mini_width ; x++){
                Mini_Inside_Shape_R[x,y]=(int)(Math.Round((float)(Mini_Inside_Shape_R[x,y])/(d_shape_area)));
                Mini_Inside_Shape_G[x,y]=(int)(Math.Round((float)(Mini_Inside_Shape_G[x,y])/(d_shape_area)));
                Mini_Inside_Shape_B[x,y]=(int)(Math.Round((float)(Mini_Inside_Shape_B[x,y])/(d_shape_area)));
                Mini_Inside_Shape_A[x,y]=(int)(Math.Round((float)(Mini_Inside_Shape_A[x,y])/(d_shape_area)));
                Mini_Outside_Shape_R[x,y]=(int)(Math.Round((float)(Mini_Outside_Shape_R[x,y])/(d_shape_area_2)));
                Mini_Outside_Shape_G[x,y]=(int)(Math.Round((float)(Mini_Outside_Shape_G[x,y])/(d_shape_area_2)));
                Mini_Outside_Shape_B[x,y]=(int)(Math.Round((float)(Mini_Outside_Shape_B[x,y])/(d_shape_area_2)));
                Mini_Outside_Shape_A[x,y]=(int)(Math.Round((float)(Mini_Outside_Shape_A[x,y])/(d_shape_area_2)));
            }
        }
    }
    else{
        activate_pixelate_mode = true;
        int shape_area = shape_width*shape_height;
        int current_x,current_y,window_x,window_y;
        ColorBgra Window_Color;
        for (int y = 0 ; y < mini_height ; y++){
            current_y = y * shape_height;
            for (int x = 0 ; x < mini_width ; x++){
                current_x = x * shape_width;
                for (int py = 0 ; py < shape_height ; py++){
                    window_y = Math.Min(hh,py+current_y);
                    for (int px = 0 ; px < shape_width ; px++){
                        window_x = Math.Min(ww,px+current_x);
                        Window_Color = src[window_x,window_y];
                        Mini_Inside_Shape_R[x,y]+=Window_Color.R;
                        Mini_Inside_Shape_G[x,y]+=Window_Color.G;
                        Mini_Inside_Shape_B[x,y]+=Window_Color.B;
                        Mini_Inside_Shape_A[x,y]+=Window_Color.A;
                    }
                }
                Mini_Inside_Shape_R[x,y]/=shape_area;
                Mini_Inside_Shape_G[x,y]/=shape_area;
                Mini_Inside_Shape_B[x,y]/=shape_area;
                Mini_Inside_Shape_A[x,y]/=shape_area;
            }
        }
    }
}

// Here is the main multi-threaded render function
// The dst canvas is broken up into rectangles and
// your job is to write to each pixel of that rectangle
void Render(Surface dst, Surface src, Rectangle rect)
{
    float i_r,i_g,i_b,i_a,o_r,o_g,o_b,o_a,r,g,b,a;
    bool debug_mode=false;
    Debug.WriteLine(activate_pixelate_mode);
    // Step through each row of the current rectangle
    for (int y = rect.Top; y < rect.Bottom; y++)
    {
        if (IsCancelRequested) return;
        for (int x = rect.Left; x < rect.Right; x++)
        {
            if (debug_mode){
                dst[x,y]=Rotated_Surface[x,y];
            }
            else
            {
            if (activate_pixelate_mode){
                dst[x,y]=ColorBgra.FromBgraClamped(Mini_Inside_Shape_B[x/shape_width,y/shape_height],Mini_Inside_Shape_G[x/shape_width,y/shape_height],Mini_Inside_Shape_R[x/shape_width,y/shape_height],Mini_Inside_Shape_A[x/shape_width,y/shape_height]);
            }
            else{
                i_r=(float)(Mini_Inside_Shape_R[x/shape_width,y/shape_height]);
                i_g=(float)(Mini_Inside_Shape_G[x/shape_width,y/shape_height]);
                i_b=(float)(Mini_Inside_Shape_B[x/shape_width,y/shape_height]);
                i_a=(float)(Mini_Inside_Shape_A[x/shape_width,y/shape_height]);
                o_r=(float)(Mini_Outside_Shape_R[x/shape_width,y/shape_height]);
                o_g=(float)(Mini_Outside_Shape_G[x/shape_width,y/shape_height]);
                o_b=(float)(Mini_Outside_Shape_B[x/shape_width,y/shape_height]);
                o_a=(float)(Mini_Outside_Shape_A[x/shape_width,y/shape_height]);
                r=i_r*Tile_Surface[x%tile_width,y%tile_height]+o_r*(1-Tile_Surface[x%tile_width,y%tile_height]);
                g=i_g*Tile_Surface[x%tile_width,y%tile_height]+o_g*(1-Tile_Surface[x%tile_width,y%tile_height]);
                b=i_b*Tile_Surface[x%tile_width,y%tile_height]+o_b*(1-Tile_Surface[x%tile_width,y%tile_height]);
                a=i_a*Tile_Surface[x%tile_width,y%tile_height]+o_a*(1-Tile_Surface[x%tile_width,y%tile_height]);
                dst[x,y]=ColorBgra.FromBgraClamped((int)(Math.Round(b)),(int)(Math.Round(g)),(int)(Math.Round(r)),(int)(Math.Round(a)));
            }
            }
        }
    }
}

 

 

Edited by Reptillian
EDIT: I figured it out. I fixed some more issues. Bugs still remain.
Link to post
Share on other sites
  • Reptillian changed the title to Tiled Form (Proof of Concept Plugin)

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...