Jump to content

'Clipwarp' - co-author request + source code


Red ochre
 Share

Recommended Posts

Clipwarpbodgethumb.png Clipwarp1thumb.pngClipwarp2thumb.pngClipwarp3thumb.pngclipwarpbetamanrunonbigthumb.png CWbrushmaskmode3thumb.png

I really need some help with a potentially very useful plugin.
R.O.I problems (rectangles of infuriation!).

I have written the code in codelab using illnab's clipboard code. I'm getting great results (on alternate R.O.Is) and with a little bodging (running the effect on a second copy of the layer displaced by 2 pix and then making the top layer unaltered pix transparent) I got the first image above.(I made a little plugin to help do this!).

I am assuming that a VS soloution is going to be the answer - unless anyone knows of a way to get this working in codelab?
I am not a developer and Visual C# 2010 express is beyond me. I installed the 2005 effect plugin template and got to stage 5 of Boltbait's to do list, but got an error. In truth I have very little idea of what I would do if I could even set up the project correctly. Many of you have decades of programming experience, knowledge and education ... I don't (you may have noticed :-)).

Would anyone like to co-author this with me? (please).
Maybe even a team effort?

The code
The effect could have many uses including 'glass text' or distorting textures around shapes. It works by the user first creating a 'warp' map (basically a B+G+R tone gradient across an object - my 'objectedge' plugin is ideal for this). When this is copied to the clipboard the current, top and left pixels are compared to give a direction. This is then used to apply a distort based on the source layer's tone value. It can also work the other way round to distort a clipboard texture over an object on the current layer - retaing transparency. The 'mode 2' option is just an experiment but may be useful for creating textures.

This is similar but different to the 'alpha-displacement' plugin in that the direction of the distort will normally be related to the object's shape.

I suspect the main ROI problem is in getting the 3 clipboard pixel values. The other method of averaging out 4 source pixels is used in other successful codelab plugins (squirklewarp & Compotool).

Here is the codelab code and attached dll for 'Clipwarp'.
(fun to play with even in its current state - reasonable results on larger images - ROI problem less obvious).
I'm sure my coding will be ugly - so any improvements on that will be useful too.
I've tried to annotate it as best I can, please ask questions if I haven't explained what I'm aiming to do.

Many thanks in advance and a Merry Christmas to all the 'wizards'.
I have removed the beta dll to avoid confusion.


[HIDE]

/* =================================================== */

/* Clipwarp*/

/* ========================================== ======== */

// Name: Clipwarp
// Author: Red ochre (John Robbins)using illnab1024 clipboard code
// Submenu: Advanced
// URL: http://www.getpaint.net/redirect/plugins.html
// Title:Clipwarp                    

#region UICode
byte Amount1 = 0; // mode|1. distort current layer by clipboard tone|2. distort current layer by its own tone|3. distort & render clipboard image by current layers' tone (src Alpha)
double Amount2 = 100; // [0,1000] Overall displacement
double Amount3 = 0; // [-1,1] X                      displacement ratio                       Y
byte Amount4 = 0; // curvature type|linear|Tanh (round)|Sine
byte Amount5 = 0; // edge behaviour|reflect|repeat|none
bool Amount6 = true; // [0,1] reverse displacement direction
bool Amount7 = true; // [0,1] direction from gradient
byte Amount8 = 0; // tones to use|darkness|lightness|distance from mid-grey|uniform
int Amount9 = 200; // [1,200] Maximum displacement(% of largest side)
#endregion
//clipwarpSrc method here - to return a colorBGRA of the new pixel
private ColorBgra clipwarpSrc(Surface src,Surface img,Rectangle rect,int x,int y,int W,int H,int maxdis)
{// start method by giving it something to return
ColorBgra cp = src[x,y];int B = cp.B;int G = cp.G;int R = cp.R;int A = cp.A;
//find clipgradient
//Ideally would find gradient by averaging blocks of 3 pixels in similar manner, so 8 pixels would be used total, 6 for xwarp, 6 for ywarp.
                double ltone = 1, mtone = 1, ttone = 1;
				//left middle and top pixels assign all to current pixel to give a value
                ColorBgra l = cp,m = cp,t = cp;
                //out of bounds protection
                if(y >= rect.Top + 1 && y < rect.Bottom - 1
                    && x >= rect.Left + 1 && x < rect.Right - 1)
                {   if(img != null)//probably over protective as this was done before passing to method
					{
                    l = img.GetBilinearSampleWrapped(x - 1,y );//left
                    m = img.GetBilinearSampleWrapped(x,y );//middle
                    t = img.GetBilinearSampleWrapped(x,y - 1);//top
					}
					// if clipboard doesn't have image or source selected - use source
                    if(Amount1 == 1 || img == null){l = src[x -1,y];m = cp;t = src[x,y -1];}
					// work out simple tone values of l (left),m (middle) and t(top) pixels
                    ltone = (l.B + l.G + l.R)/765.0;//could loose division and simply reduce Amount2 range
                    mtone = (m.B + m.G + m.R)/765.0;
                    ttone = (t.B + t.G + t.R)/765.0;
        
               
                    // value range 0 to 1
                    
                 }
// simplified to use 3 pixels only, originally averaged blocks of 3 for left,middle and top values, (expecting smoother result and larger value range).
double xgrad = (ltone - mtone);
double ygrad = (ttone - mtone);// range - 1 to 1
double bothgrad = (xgrad + ygrad)/2;


				
	// warp bit here!!!!!!!!!!!!!
double tone = (1 - mtone);//using 'blackness'
if(Amount8 == 1){tone = mtone;}//whiteness
if(Amount8 == 2){tone = Math.Abs(0.5 - mtone) * 2;}//distance from grey
if(Amount8 == 3){tone = 1;}//just tone gradient
double PI = Math.PI;
// a lot of room for experimenting with these curvature values
if(Amount4 == 1){xgrad = Math.Tanh(xgrad);ygrad = Math.Tanh(ygrad);}// try this, maybe multiply by PI?
if(Amount4 == 2){xgrad = Math.Sin(xgrad * 2 * PI);ygrad = Math.Sin(ygrad * 2 * PI);}//not great, maybe try arcsine/2PI
int xsign = Math.Sign(xgrad);//tone gradient directions
int ysign = Math.Sign(ygrad);
if(Amount7){xsign = 1;ysign = 1;}// use gradient direction
if(Amount6){xsign = xsign * -1;ysign = ysign * -1;}//reverse direction
// the equation below actually squares the sign so positive unless Amount6 or Amount7
double xwarp = xsign * xgrad * Amount2 * tone * 100 * (1 - Amount3);
double ywarp = ysign * ygrad * Amount2 * tone * 100 * (1 + Amount3);
if(Math.Abs(xwarp) > maxdis){xwarp = maxdis * xsign;}
if(Math.Abs(ywarp) > maxdis){ywarp = maxdis * ysign;}
double nxP = x + xwarp;
double nyP = y - ywarp;



				
if(Amount5 == 0)// reflect src if not on canvas  
    {
        if(nxP < 0)     {nxP = - nxP;}
        if(nxP >= W - 1){int repx = (int)(nxP/(W-1));
                         if(repx%2 >= 1){nxP = (W - 1)- nxP%(W - 1);}
                         else if(repx%2 < 1 ){nxP =  nxP%(W - 1);}
                        }
        if(nyP < 0)     {nyP = - nyP;}
        if(nyP >= H - 1){int repy = (int)(nyP/(H-1));
                         if(repy%2 >= 1){nyP = (H - 1)- nyP%(H - 1);}
                         else if(repy%2 < 1 ){nyP =  nyP%(H - 1);}
                         }
     }
if(Amount5 == 1)// straight repeat src  
    {
        if(nxP < 0)     {nxP = W - Math.Abs(nxP%(W - 1));}
        if(nxP >= W - 1){nxP = nxP%(W - 1);}
        if(nyP < 0)     {nyP = H - Math.Abs(nyP%(H - 1));}
        if(nyP >= H - 1){nyP = nyP%(H - 1);}
    }
 // protect against out of bounds exeption.......................................
    if((nxP >= 0 && nxP < W - 1 )
    && (nyP >= 0 && nyP < H - 1 ))
    { // I don't think this bit causes the R.O.I problems as similar is used in 'Compotool' & 'Squirklewarp'
	  // which don't show these R.O.I lines
	  // could it be accessing clipboard from 1 thread? - way beyond me anyway
	  // 
	  //Ideally I would incorperate some form of smoothing by averaging the pixels between
	  // the last nxP (last x,y loop) and the current pixel nxP - same for nyP.
        int iX = (int)(nxP);
        int iY = (int)(nyP);
        double rX = nxP - iX;double arX = 1 - rX;// rX = decimal part of new x 
        double rY = nyP - iY;double arY = 1 - rY;// rY = decimal part of new y
		ColorBgra ll = src[iX,iY], lr = src[iX + 1,iY], ul = src[iX,iY + 1], ur = src[iX + 1,iY + 1];//may need to declare seperately
        
        B = (int)((((ll.B * arX) + (lr.B * rX)) * arY) + (((ul.B * arX) + (ur.B * rX)) * rY));
        G = (int)((((ll.G * arX) + (lr.G * rX)) * arY) + (((ul.G * arX) + (ur.G * rX)) * rY));
        R = (int)((((ll.R * arX) + (lr.R * rX)) * arY) + (((ul.R * arX) + (ur.R * rX)) * rY));
        A = (int)((((ll.A * arX) + (lr.A * rX)) * arY) + (((ul.A * arX) + (ur.A * rX)) * rY));
    }
// return is last bit
    return ColorBgra.FromBgra( Int32Util.ClampToByte(, Int32Util.ClampToByte(G), Int32Util.ClampToByte®, Int32Util.ClampToByte(A));
}// ends clipwarpSrc method.............................................................................................................
//...............................................................................................................................
// start srcwarpClip................................................................................................................
private ColorBgra srcwarpClip(Surface src,Surface img,Rectangle rect,int x,int y,int W,int H,int maxdis)
{ColorBgra cp = src[x,y];int B = cp.B;int G = cp.G;int R = cp.R;int A = cp.A;
	double ltone = 1, mtone = 1, ttone = 1;
	ColorBgra l = cp; ColorBgra m = cp; ColorBgra t = cp;//to give a value
                //out of bounds protection
                if(y >= rect.Top + 1 && y < rect.Bottom - 1
                    && x >= rect.Left + 1 && x < rect.Right - 1)
                {  
					l = src[x - 1,y];//from source layer not clipboard this time
                    m = cp;
                    t = src[x,y - 1];
					// work out simple tone values of l (left),m (middle) and t(top) pixels
                    ltone = (l.B + l.G + l.R)/765.0;
                    mtone = (m.B + m.G + m.R)/765.0;
                    ttone = (t.B + t.G + t.R)/765.0;// value range 0 to 1
                }
// simplified to use 3 pixels only
double xgrad = (ltone - mtone);
double ygrad = (ttone - mtone);// range - 1 to 1
double bothgrad = (xgrad + ygrad)/2;


				
	// warp bit again!!!!!!!!!!!!! this time warping the clipboard image!!!!!!!!!!!!!!
double tone = (1 - mtone);
if(Amount8 == 1){tone = mtone;}
if(Amount8 == 2){tone = Math.Abs(0.5 - mtone) * 2;}
if(Amount8 == 3){tone = 1;}
double PI = Math.PI;
if(Amount4 == 1){xgrad = Math.Tanh(xgrad);ygrad = Math.Tanh(ygrad);}// try this, maybe multiply by PI?
if(Amount4 == 2){xgrad = Math.Sin(xgrad * 2 * PI);ygrad = Math.Sin(ygrad * 2 * PI);}
int xsign = Math.Sign(xgrad);
int ysign = Math.Sign(ygrad);
if(Amount7){xsign = 1;ysign = 1;}// use gradient direction
if(Amount6){xsign = xsign * -1;ysign = ysign * -1;}//reverse direction
// the equation below actually squares the sign so positive unless Amount6 or Amount7
double xwarp = xsign * xgrad * Amount2 * tone * 100 * (1 - Amount3);
double ywarp = ysign * ygrad * Amount2 * tone * 100 * (1 + Amount3);
if(Math.Abs(xwarp) > maxdis){xwarp = maxdis * xsign;}
if(Math.Abs(ywarp) > maxdis){ywarp = maxdis * ysign;}
double nxP = x + xwarp;
double nyP = y - ywarp;

				
if(Amount5 == 0)// reflect    
    {
        if(nxP < 0)     {nxP = - nxP;}
        if(nxP >= W - 1){int repx = (int)(nxP/(W-1));
                         if(repx%2 >= 1){nxP = (W - 1)- nxP%(W - 1);}
                         else if(repx%2 < 1 ){nxP =  nxP%(W - 1);}
                        }
        if(nyP < 0)     {nyP = - nyP;}
        if(nyP >= H - 1){int repy = (int)(nyP/(H-1));
                         if(repy%2 >= 1){nyP = (H - 1)- nyP%(H - 1);}
                         else if(repy%2 < 1 ){nyP =  nyP%(H - 1);}
                         }
     }
if(Amount5 == 1)// straight repeat  
    {
        if(nxP < 0)     {nxP = W - Math.Abs(nxP%(W - 1));}
        if(nxP >= W - 1){nxP = nxP%(W - 1);}
        if(nyP < 0)     {nyP = H - Math.Abs(nyP%(H - 1));}
        if(nyP >= H - 1){nyP = nyP%(H - 1);}
    }
 // protect against out of bounds exeption.......................................
    if((nxP >= 0 && nxP < W - 1 )
    && (nyP >= 0 && nyP < H - 1 ))
    { 
        int iX = (int)(nxP);
        int iY = (int)(nyP);
        double rX = nxP - iX;double arX = 1 - rX;// rX = decimal part of new x 
        double rY = nyP - iY;double arY = 1 - rY;// rY = decimal part of new y
// get clipboard pixels - shouldn't matter if clipboard smaller than src as illnab's code repeats the values if too small
	ColorBgra ll = cp;ColorBgra lr = cp;ColorBgra ul = cp;ColorBgra ur = cp;// to give value
        	if(img != null)
					{
                     ll = img.GetBilinearSampleWrapped(iX,iY);
                     lr = img.GetBilinearSampleWrapped(iX + 1,iY);
					 ul = img.GetBilinearSampleWrapped(iX,iY + 1);
					 ur = img.GetBilinearSampleWrapped(iX + 1,iY + 1);
					}
        

        B = (int)((((ll.B * arX) + (lr.B * rX)) * arY) + (((ul.B * arX) + (ur.B * rX)) * rY));
        G = (int)((((ll.G * arX) + (lr.G * rX)) * arY) + (((ul.G * arX) + (ur.G * rX)) * rY));
        R = (int)((((ll.R * arX) + (lr.R * rX)) * arY) + (((ul.R * arX) + (ur.R * rX)) * rY));
        // all clip board alpha is 255 
		A = cp.A;
        
    }
// return is last bit
//ColorBgra cp = src[x,y];int B = cp.B;int G = cp.G;int R = cp.R;int A = cp.A;
return ColorBgra.FromBgra( Int32Util.ClampToByte(, Int32Util.ClampToByte(G), Int32Util.ClampToByte®, Int32Util.ClampToByte(A));
}
// ends srcwarpClip....................................................................................................................

// illnabs clipboard method here down-----------------------------------------------------------------------------------------------------------------

protected Surface img // all illnab1024's code
{
    get { if (_img != null)
            return _img; 
          else
          {
            System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(GetImageFromClipboard));
            t.SetApartmentState(System.Threading.ApartmentState.STA); 
            t.Start();
            t.Join();
            return _img;
          }
        }
}
private Surface _img = null;
private void GetImageFromClipboard()
{
    Bitmap aimg = null;
    IDataObject clippy;
    try
    {
        clippy = Clipboard.GetDataObject();
        if (clippy != null)
        {
            aimg = (Bitmap)clippy.GetData(typeof(System.Drawing.Bitmap));
        }
    }
    catch (Exception )
    {
    }
    if (aimg != null)
    {
        _img = Surface.CopyFromBitmap(aimg);
    }
    else
    {
        _img = null;
    }
}
// end of illnabs method ---------------------------------------------------------------------------------------------------------------


void Render(Surface dst, Surface src, Rectangle rect)
{ Rectangle selection = this.EnvironmentParameters.GetSelection(src.Bounds).GetBoundsInt();
  int H = selection.Bottom - selection.Top;
  int W = selection.Right - selection.Left;
  int maxside = Math.Max(W,H);
  int maxdis = maxside * (Amount9/100);// maximum displacement
  // not really happy with this way of limiting maximum displacement
  // only there as very primitive smoothing
   
    for (int y = rect.Top; y < rect.Bottom; y++)
    {
        for (int x = rect.Left; x < rect.Right; x++)
        {
            
                switch(Amount1)
                {
                    case 0:// distort src by clipboard tone
                    if(img != null){dst[x,y] = clipwarpSrc(src,img,rect,x,y,W,H,maxdis);}
                    break;
                    case 1:// distort src by its own tone
                    dst[x,y] = clipwarpSrc(src,img,rect,x,y,W,H,maxdis);
                    break;
                    case 2:// distort and render clipboard image by src tone, keep src Alpha unchanged
					if(img != null){dst[x,y] = srcwarpClip(src,img,rect,x,y,W,H,maxdis);}
                    break;
                }
            if (img == null && Amount1 != 1)// if nothing on clipboard and source not selected then dst = src
                {dst[x,y] = src[x,y];}
				
				
			
           
        }
    }
}


 

Clipwarpicon.png

  • Upvote 1

 

Red ochre Plugin pack.............. Diabolical Drawings ................Real Paintings

 

PdnForumSig2.jpg

Link to comment
Share on other sites

Rendering to a separate surface in OnSetRenderInfo fixes the striping problem, although it does require using Visual C# 2010 Express.

 

The project file is attached,  running Visual Studio with elevated permissions allows the plugin to be automatically coped to Paint.NET's Effects folder.

 

ClipWarpSrc.zip

PdnSig.png

Plugin Pack | PSFilterPdn | Content Aware Fill | G'MICPaint Shop Pro Filetype | RAW Filetype | WebP Filetype

The small increase in performance you get coding in C++ over C# is hardly enough to offset the headache of coding in the C++ language. ~BoltBait

 

Link to comment
Share on other sites

  • 2 weeks later...

I think 'Clipwarp' is now ready to be published, however I think it would be a good idea if someone competent (Null54) double checks the code before I unleash this into the wild. Unfortunately the full project file is too big to attach so I've attached just the CS code.- I haven't changed anything regarding Disposing so assume it's all o.k.

ClipWarpCScode.zip

I have decided to put the effect under the 'Tools' heading, unless everyone objects? - the other option would be to create a 'Clipboard' heading. The Distort menu is not really correct and is overcrowded and the only similar plugin 'Alpha-displacement' is not under a sub-heading at all.

Hopefully I will publish this soon, when I've written some decent instructions and provided there are no bugs.
I've had no problems with it and am very pleased with results I'm getting.




I am also attaching the .dll for those who wish explore what it can do.

ClipWarp.zip


How to use (very briefly - I will write a mini-tutorial when it is published properly):

1. Open a background picture.
2. If it's a photo, resize it to something sensible  - say 800 to 1600 pixels wide.
3. Create a new transparent layer on top of this.
4. Create an object eg. text, line or simple shape.
5. Use my 'Object edge' plugin to create a tone gradient over the object - this can be done more than once by setting the second colour mix to zero and gradually lowering the blur radius. The distortion is governed by the change in tone rather than simply the tone. (tone gradient (calculated from 3 pixels), has a direction. simple tone on one pixel doesn't).
You can even hand (mouse) draw an object with shades of grey, then smooth these out with 'OverBlur' (this maintains the object edges). Or even create something simple using 'Sculptris'! - best as an object on transparent background though. An object with a tone gradient might look like this ThisCWobject.png
6. Duplicate this layer and keep below the background picture - it can be useful for shading later.
7. Select and copy the Background layer to the clipboard.
8. Move to the top object layer, deselect and run 'Clipwarp' - this version is under Effects/Tools.
9. Play with all the settings, different types of tonally graduated objects and report back any good results!

Note: If nothing is copied to the clipboard, the effect will distort the current layer by its own tone gradient.
If the object layer is copied to the clipboard and the effect run on the background layer it will give similar results to running it the other way round, but it will take longer and the distorted 'object' will be on the same layer as the background, which is less useful.
 

 

Red ochre Plugin pack.............. Diabolical Drawings ................Real Paintings

 

PdnForumSig2.jpg

Link to comment
Share on other sites

As you noted in the comments, the tone and curvatureType if blocks could be converted to a switch statement which may be more efficient.

 

Also the else if statement could be used for the if blocks after if (edgeBehaviour == EdgeBehaviour.Reflect), this would stop any unneeded comparisons after it found the correct value.

 

Those are only minor optimizations; the plugin looks great as is. :)

 

  • Upvote 2

PdnSig.png

Plugin Pack | PSFilterPdn | Content Aware Fill | G'MICPaint Shop Pro Filetype | RAW Filetype | WebP Filetype

The small increase in performance you get coding in C++ over C# is hardly enough to offset the headache of coding in the C++ language. ~BoltBait

 

Link to comment
Share on other sites

Is this released yet? I'm kinda excited because the displacement map plugin is a little buggy...

 

what I do all summer Emote Cursor Pack 'noob gallery

No, Paint.NET is not spyware...but, installing it is an IQ test. ~BoltBait

Blend modes are like the filling in your sandwich. It's the filling that can change your experience of the sandwich. ~Ego Eram Reputo

Link to comment
Share on other sites

Many thanks Null54 :) - this would not exist without your help.

 

@ pdnnoob - this is quite different from 'displacement map' or 'alpha-displacement' because it takes the direction of the gradient to get the distortion direction.

I believe the other 2 (I love the alpha-displacement plugin btw) use the tone for the proportion of displacement with the user deciding the direction and amount. - I've made that sound far more complicated than it is :roll:.

Give it a go ;) - it's more or less finished, I just need to write more detailed instructions.

 

Red ochre Plugin pack.............. Diabolical Drawings ................Real Paintings

 

PdnForumSig2.jpg

Link to comment
Share on other sites

Nice work Sasha, B)

I was just thinking 'good the instructions must be ok' - then Welshy posts! :D

 

I probably used too many words - I know I only scan read tutorials - which is why I go off on my own tangent.

 

2nd try: (I know how good you are Welshy - not trying to be patronising - If you can get the concept I'm sure you'll take the idea and run with it) - here's the simplest way to use it.

 

1. open a picture - this is the background.

2. create new transparent layer above this.

3. draw a squiggly line - width say 30 pix

4. run 'Object edge' with the mix sliders fully right - should make the line look like a bit like a tube.White middle black edges.

5. copy the background.

6. run 'clipwarp' on the layer with the line.

 

That's it!

 

Always better to say if something isn't clear - I have the talent of confusing the brightest people! :D

  • Upvote 1

 

Red ochre Plugin pack.............. Diabolical Drawings ................Real Paintings

 

PdnForumSig2.jpg

Link to comment
Share on other sites

Nice work Welshblue & Sasha, great examples of what this plugin can do, well done Red Ochre & Null54 for the creation of it. I have downloaded this and will have a little play around with it. Rep's to both authors :)

 

And Rep's to Sasha & Welshblue for demonstrating the plugin's potential ;)

 

ZXCBOoZ.png

 

 

Link to comment
Share on other sites

Thanks NN79 and great pictures Sasha & Welshy!

 

I've published this in the correct place now, so if you want to post any more pictures on that thread it would be great. All helps to show what the plugin can do!

 

Red ochre Plugin pack.............. Diabolical Drawings ................Real Paintings

 

PdnForumSig2.jpg

Link to comment
Share on other sites

 

@ pdnnoob - this is quite different from 'displacement map' or 'alpha-displacement' because it takes the direction of the gradient to get the distortion direction.

I believe the other 2 (I love the alpha-displacement plugin btw) use the tone for the proportion of displacement with the user deciding the direction and amount. - I've made that sound far more complicated than it is :roll:.

Give it a go ;) - it's more or less finished, I just need to write more detailed instructions.

I already understood the differences, but I use displacement map as if it does this! Thanks for the plugin!

 

what I do all summer Emote Cursor Pack 'noob gallery

No, Paint.NET is not spyware...but, installing it is an IQ test. ~BoltBait

Blend modes are like the filling in your sandwich. It's the filling that can change your experience of the sandwich. ~Ego Eram Reputo

Link to comment
Share on other sites

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.

 Share

×
×
  • Create New...