Jump to content

Recommended Posts

@MJW Your solution worked!

 

Releasing the code here for testing purpose.

 

 

// Name: Axis-Based Shift Grouped Pixel to Boundary
// Submenu: Effects / Distortion
// Author: Reptorian
// Title: Axis-Based Shift Grouped Pixel to Boundary
// Version: 1.0
// Desc: Extended version of Gravity by MadJik. Converted from G'MIC-QT rep_sptbwgp cli filter which is also created by Reptorian.
// Keywords: Gravity
// URL: https://forums.getpaint.net/profile/85868-reptillian/
// Help:
#region UICode
DoubleSliderControl positionperc = -100; // [-100,100] Position of Pixels (%)
DoubleSliderControl influenceperc = 100; // [0,100] Influence Factor (%)
IntSliderControl alpha_threshold = 255; // [1,255] Alpha Threshold
ListBoxControl axis = 1; // Axis|Horizontal|Vertical
#endregion

 

int[] rowNonZeroAlphas = null;
int[] columnNonZeroAlphas = null;
ColorBgra[,] altsrf = null;

 

void PreRender(Surface dst, Surface src)
{
    int w=src.Width,h=src.Height;
    columnNonZeroAlphas = new int[w];rowNonZeroAlphas = new int[h];
    altsrf = new ColorBgra[w,h];
    int non_alpha_count = 0;
    int tx,ty,nx,ny,fx,fy,N;
    double tny,tnx;
    
    double position = (positionperc / 100 + 1) / 2;
    double influence_factor = influenceperc / 100;
    double invert_factor = 1 - influence_factor;
    
    if (axis==1)
    {
        for (int x = 0 ; x < w ; x++)
        {
            non_alpha_count = 0 ;            
            for (int y = 0; y < h; y++){if (src[x,y].A >= alpha_threshold){non_alpha_count++;}}
            columnNonZeroAlphas[x] = non_alpha_count ;
        }
        
        for (int x = 0 ; x < w ; x++)
        {
            ny = h - 1;
            if (columnNonZeroAlphas[x] != 0)
            {
                N = columnNonZeroAlphas[x];
                ty = h - N;
                ny-=(int)(Math.Round(ty * position));
                for (int y = h - 1 ; y >= 0 ; y--)
                {
                    if (N != 0)
                    {
                        if (src[x,y].A >= alpha_threshold)
                        {
                            tny = influence_factor * (double)(ny) + invert_factor * (double)(y);
                            
                            if (position == 1)
                            { fy = (int)(Math.Floor(tny)) ; }
                            else
                            { fy = (int)(Math.Ceiling(tny)) ; }
                            
                            N--;
                            altsrf[x,fy] = src[x,y] ;
                            ny--;                            
                        }
                    }
                    else {break;}
                }
            }
        }
    }
    else
    {
        for (int y = 0 ; y < h ; y++)
        {
            non_alpha_count = 0 ;            
            for (int x = 0; x < w; x++){if (src[x,y].A >= alpha_threshold){non_alpha_count++;}}
            rowNonZeroAlphas[y] = non_alpha_count ;
        }
        
        for (int y = 0 ; y < h ; y++)
        {
            nx = 0;
            if (rowNonZeroAlphas[y] != 0)
            {
                N = rowNonZeroAlphas[y];
                tx = w - N;
                nx+=(int)(Math.Round(tx * position));
                for (int x = 0 ; x < w ; x++)
                {
                    if (N != 0)
                    {
                        if (src[x,y].A >= alpha_threshold)
                        {
                            tnx = influence_factor * (double)(nx) + invert_factor * (double)(x);
                            
                            if (position == 1)
                            { fx = (int)(Math.Ceiling(tnx)) ; }
                            else
                            { fx = (int)(Math.Floor(tnx)) ; }
                            
                            N--;
                            altsrf[fx,y] = src[x,y] ;
                            nx++;                            
                        }
                    }
                    else {break;}
                }
            }
        }
    }
}

 

void Render(Surface dst, Surface src, Rectangle rect)
{
    Rectangle selection = EnvironmentParameters.SelectionBounds;
    for (int y = rect.Top; y < rect.Bottom; y++)
    {
        if (IsCancelRequested) return;
        for (int x = rect.Left; x < rect.Right; x++)
        {
            dst[x,y] = altsrf[x,y] ;
        }
    }
}

 

 

 

Edited by Reptillian
  • Like 1
Link to post
Share on other sites
2 hours ago, toe_head2001 said:

Obviously, writing outside of the selection is still a bad practice, and just wastes compute resources.

 

Would you expand of this. Do you just mean the write has no effect, so the effort is wasted, or does it waste resources in some other way? If it just wastes the effort to compute the unused pixels, is that behavior that's intended to remain that way, so that it can be relied on? There are probably lots of cases where it would be convenient to clip at most to the selection's bounding rectangle, and not worry that there will be some wasted computations if the user happens to have an elliptical selection.

 

Also, does  is writing outside the ROI in Render() allowed, in the sense that it will properly write into pixels if they're selected? To paraphrase Ghostbusters, are the ROI bounds more guidelines than rules?

  • Like 1
Link to post
Share on other sites
26 minutes ago, MJW said:

Do you just mean the write has no effect, so the effort is wasted

 

Yeah, pretty much.

 

28 minutes ago, MJW said:

is that behavior that's intended to remain that way, so that it can be relied on?

 

I believe so. If pixels outside of the selection are modified, that would be considered a bug.

 

30 minutes ago, MJW said:

Also, does writing outside the ROI in Render() allowed, in the sense that it will properly write into pixels if they're selected?

 

Yes, it will work. Using the ROI is, of course, the recommended route, and probably the most efficient.

  • Like 1
Link to post
Share on other sites
  • 1 month later...

Swirl can be found in Effects > Distort submenu.

Spoiler

// Name: Swirl
// Submenu: Distort
// Author: NSD
// Title: Swirl
// Version: 1.0
// Desc:
// Keywords:
// URL:

#region UICode
DoubleSliderControl intensity = 0.0; // [-1,1] Amount / Direction
DoubleSliderControl zoom = 0; // [0.0,0.999] Zoom
PanSliderControl pan = Pair.Create(0.0, 0.0); // Center
#endregion

void Render(Surface dst, Surface src, Rectangle rect)
{
    int x, y;
    double offx, offy, dx, dy, th, rd, ct, offset, xPan, yPan, zm;
    ColorBgra CurrentPixel;

    Rectangle sel = EnvironmentParameters.SelectionBounds;
    dst.CopySurface(src, rect.Location, rect);

    ct = intensity * 0.2;
    offset = Math.PI / 2.0;

    xPan = (pan.First + 1) / 2 * (sel.Right - sel.Left) + sel.Left;
    yPan = (pan.Second + 1) / 2 * (sel.Bottom - sel.Top) + sel.Top;

    zm  = 1 - zoom;

    for (y = rect.Top; y < rect.Bottom; y++)
    {
        if (IsCancelRequested) return;

        for (x = rect.Left; x < rect.Right; x++)
        {
            dx = x - xPan;
            dy = y - yPan;

            rd = Math.Sqrt((dx * dx + dy * dy) * zm);
            th = Math.Atan2(dx, -dy) - offset;

            offx = rd * Math.Cos(th + ct * rd);
            offy = rd * Math.Sin(th + ct * rd);

            offx += xPan;
            offy += yPan;

            CurrentPixel = src.GetBilinearSampleWrapped((float)offx, (float)offy);
            dst[x,y] = CurrentPixel;
        }
    }
}

 

 

 

Swirl.zip

  • Like 2
  • Upvote 1
Link to post
Share on other sites
  • 1 month later...

This plugin can be found in the Effects Menu.
You need to put OptionBasedLibrary v0.7.8 dlc and dll files in paint.net folder and
WarpTextOnPath dll and dlc files in Effects folder.

 

The UI:
gyPoYl0.png

 

Spoiler





// Name: WarpTextOnBezier
// Submenu:
// Author: NSD
// Title:
// Version: 1.0
// Desc:
// Keywords:
// URL:
// Help:
// Force Single Render Call

#region UICode
MultiLineTextboxControl text = "WARP TEXT ON A BEZIER CURVE"; // [32767]
FontFamily font = new FontFamily("Georgia");
DoubleSliderControl fontSize = 120; // [5,800] Font size
CheckboxControl bold = true; // Bold
CheckboxControl italic = false; // Italic
PanSliderControl p0 = Pair.Create(-0.500, 0.740); //
PanSliderControl p1 = Pair.Create(-1.000, -1.000); //
PanSliderControl p2 = Pair.Create(1.000, -1.000); //
PanSliderControl p3 = Pair.Create(0.500, 0.740); //
ListBoxControl<TextPathPositionEnum> textPathPosition = TextPathPositionEnum.OverPath; //|Over path|Center path|Under path
ListBoxControl<RenderModeEnum> renderMode = RenderModeEnum.Normal; //|Normal|Mask|Transparent
CheckboxControl viewPoints = true; // Show path
#endregion


Surface wrk = null;
float c1, r1, c2, r2, c3, r3, c4, r4, d, r;
PointF [] points;


protected override void OnDispose(bool disposing)
{
  if (disposing)
  {
    if (wrk != null) wrk.Dispose();
    wrk = null;
  }
  base.OnDispose(disposing);
}

void PreRender(Surface dst, Surface src)
{
  if (wrk == null)
  {
    wrk = new Surface(src.Size);
  }

  else
  {
    wrk.Clear(Color.Transparent);
  }

  Rectangle sel = EnvironmentParameters.SelectionBounds;
  c1 = ((float)p0.First + 1) / 2 * (sel.Right - sel.Left) + sel.Left;
  r1 = ((float)p0.Second + 1) / 2 * (sel.Bottom - sel.Top)+ sel.Top;

  c2 = ((float)p1.First + 1) / 2 * (sel.Right - sel.Left) + sel.Left;;
  r2 = ((float)p1.Second + 1) / 2 * (sel.Bottom - sel.Top)+ sel.Top;;

  c3 = ((float)p2.First + 1) / 2 * (sel.Right - sel.Left) + sel.Left;;
  r3 = ((float)p2.Second + 1) / 2 * (sel.Bottom - sel.Top)+ sel.Top;;

  c4 = ((float)p3.First + 1) / 2 * (sel.Right - sel.Left) + sel.Left;;
  r4 = ((float)p3.Second + 1) / 2 * (sel.Bottom - sel.Top)+ sel.Top;;

  Size selSize = EnvironmentParameters.SelectionBounds.Size;

  d = Math.Min(selSize.Width, selSize.Height) / 20;
  if (d <= 1) return;
  r = d / 2;
  points = new PointF[4];
  points[0] = new PointF(c1, r1);
  points[1] = new PointF(c2, r2);
  points[2] = new PointF(c3, r3);
  points[3] = new PointF(c4, r4);
}


void Render(Surface dst, Surface src, Rectangle rect)
{
  dst.CopySurface(src, rect.Location, rect);

  using (RenderArgs ra = renderMode == 0 ? new RenderArgs(dst) : new RenderArgs(wrk))
  using (GraphicsPath textPath = new GraphicsPath())
  using (SolidBrush sBrush = new SolidBrush(Color.Blue))
  using (Pen pen = new Pen(Color.FromArgb(255, Color.Red), 3))
  using (SolidBrush pointBr = new SolidBrush(Color.YellowGreen))
  {
    pen.LineJoin = LineJoin.Round;
    pen.DashStyle = DashStyle.Solid;
    Graphics g = ra.Graphics;
    g.Clip = new Region(rect);
    g.SmoothingMode = SmoothingMode.AntiAlias;
    g.TextRenderingHint = TextRenderingHint.AntiAlias;
    FontStyle myStyle = FontStyle.Regular;
    if (bold) myStyle |= FontStyle.Bold;
    if (italic) myStyle |= FontStyle.Italic;
    Font fnt = new Font(font.Name, (float)fontSize, myStyle);
    FontFamily ff = new FontFamily(font.Name);
    StringFormat format = new StringFormat();
    format.Alignment = StringAlignment.Near;
    format.LineAlignment = StringAlignment.Near;

    switch (textPathPosition)
    {
      case TextPathPositionEnum.CenterPath:
        format.Alignment = StringAlignment.Near;
        format.LineAlignment = StringAlignment.Center;
        break;
      case TextPathPositionEnum.OverPath:
        format.Alignment = StringAlignment.Near;
        format.LineAlignment = StringAlignment.Far;
        break;
      case TextPathPositionEnum.UnderPath:
        format.Alignment = StringAlignment.Near;
        format.LineAlignment = StringAlignment.Near;
        break;
    }

    if (viewPoints)
    {
      g.DrawBeziers(pen, points);
      pen.DashStyle = DashStyle.Dash;
      g.DrawLine(pen, c1, r1, c2, r2);
      g.DrawLine(pen, c3, r3, c4, r4);
      pen.DashStyle = DashStyle.Solid;

      g.FillEllipse(pointBr, new RectangleF(c1 - r, r1 - r, d, d));
      g.DrawEllipse(Pens.Black, new RectangleF(c1 - r, r1 - r, d, d));
      g.DrawString("1", new Font("Tahoma", r), Brushes.Black, new RectangleF(c1 - 0.7f * r, r1 - r, d, d));

      g.FillEllipse(pointBr, new RectangleF(c2 - r, r2 - r, d, d));
      g.DrawEllipse(Pens.Black, new RectangleF(c2 - r, r2 - r, d, d));
      g.DrawString("2", new Font("Tahoma", r), Brushes.Black, new RectangleF(c2 - 0.7f * r, r2 - r, d, d));

      g.FillEllipse(pointBr, new RectangleF(c3 - r, r3 - r, d, d));
      g.DrawEllipse(Pens.Black, new RectangleF(c3 - r, r3 - r, d, d));
      g.DrawString("3", new Font("Tahoma", r), Brushes.Black, new RectangleF(c3 - 0.7f * r, r3 - r, d, d));

      g.FillEllipse(pointBr, new RectangleF(c4 - r, r4 - r, d, d));
      g.DrawEllipse(Pens.Black, new RectangleF(c4 - r, r4 - r, d, d));
      g.DrawString("4", new Font("Tahoma", r), Brushes.Black, new RectangleF(c4 - 0.7f * r, r4 - r, d, d));
    }

    textPath.AddString(text, ff, (int)myStyle, (float)fontSize, new PointF(0, 0), format);
    if(text.Length > 0)
    {
      g.DrawPath(pen, BezierWarp(textPath, g));
      g.FillPath(sBrush, BezierWarp(textPath, g));
    }

    if (renderMode == RenderModeEnum.Mask)//creates mask
    {
      for (int y = rect.Top; y < rect.Bottom; y++)
      {
        if (IsCancelRequested) return;
        for (int x = rect.Left; x < rect.Right; x++)
        {
          ColorBgra CurrentPixel = src[x, y];
          CurrentPixel.A = Int32Util.ClampToByte(wrk[x, y].A);
          dst[x, y] = CurrentPixel;
        }
      }
    }

    if (renderMode == RenderModeEnum.Transparent)//Transparent checked
    {
      for (int y = rect.Top; y < rect.Bottom; y++)
      {
        if (IsCancelRequested) return;
        for (int x = rect.Left; x < rect.Right; x++)
        {
          ColorBgra CurrentPixel = src[x, y];
          CurrentPixel.A = Int32Util.ClampToByte(255 - wrk[x, y].A);
          dst[x, y] = CurrentPixel;
        }
      }
    }

  }
}

GraphicsPath BezierWarp(GraphicsPath textPath, Graphics g)
{
  // Calculate coefficients A thru H from the control points
  float A = c4 - 3 * c3 + 3 * c2 - c1;
  float B = 3 * c3 - 6 * c2 + 3 * c1;
  float C = 3 * c2 - 3 * c1;
  float D = c1;

  float E = r4 - 3 * r3 + 3 * r2 - r1;
  float F = 3 * r3 - 6 * r2 + 3 * r1;
  float G = 3 * r2 - 3 * r1;
  float H = r1;

  PointF[] pathPoints = textPath.PathPoints;
  RectangleF textBounds = textPath.GetBounds();

  for (int i = 0; i < pathPoints.Length; i++)
  {
    PointF pt = pathPoints[i];
    float textX = pt.X;
    float textY = pt.Y;

    // Normalize the x coordinate into the parameterized
    // value with a domain between 0 and 1.
    float t  =  textX / textBounds.Width;
    float t2 = (t * t);
    float t3 = (t * t * t);

    // Calculate spline point for parameter t
    float Sx = A * t3 + B * t2 + C * t + D;
    float Sy = E * t3 + F * t2 + G * t + H;

    // Calculate the tangent vector for the point
    float Tx = 3 * A * t2 + 2 * B * t + C;
    float Ty = 3 * E * t2 + 2 * F * t + G;

    // Rotate 90 or 270 degrees to make it a perpendicular
    float Px = - Ty;
    float Py = Tx;

    // Normalize the perpendicular into a unit vector
    float magnitude = (float)Math.Sqrt((Px * Px) + (Py * Py));
    Px /= magnitude;
    Py /= magnitude;

    // Assume that input text point y coord is the "height" or
    // distance from the spline.  Multiply the perpendicular
    // vector with y. it becomes the new magnitude of the vector
    Px *= textY;
    Py *= textY;

    // Translate the spline point using the resultant vector
    float finalX = Px + Sx;
    float finalY = Py + Sy;

    pathPoints[i] = new PointF(finalX, finalY);
  }

  return new GraphicsPath(pathPoints, textPath.PathTypes);
}

enum TextPathPositionEnum
{
  OverPath,
  CenterPath,
  UnderPath
}
enum RenderModeEnum
{
  Normal,
  Mask,
  Transparent
}

 

 

WarpTextOnPath.zip

 

http://www.mediafire.com/file/ignc6de43v3m90e/WarpTextOnPathOB.zip/file

Edited by NSD
Added the code.
  • Like 2
  • Upvote 2
Link to post
Share on other sites
  • 1 month later...
On 8/31/2020 at 11:42 AM, NSD said:

This plugin can be found in the Effects Menu.
You need to put OptionBasedLibrary v0.7.8 dlc and dll files in paint.net folder and
WarpTextOnPath dll and dlc files in Effects folder.

 

The UI:
gyPoYl0.png

 

 

WarpTextOnPath.zip 174.27 kB · 10 downloads

Is there a way to install this on the Windows Store version of Paint?

I feel kind of gypped that I purchased the app to support the developer after a good decade of free use, and it seems super difficultr to install plugins now :/

Link to post
Share on other sites

It is not difficult. It requires a once-only setup of the correct folder structure. See the instructions in the yellow panel on this page: https://www.getpaint.net/doc/latest/InstallPlugins.html#4

Link to post
Share on other sites

You're right, but the advantage of using OptionBased is that the users can place the plugin where they want.
To do this edit the .dlc file of plugin as follows:
1. change the extension from .dlc to .txt

2. open it with Notepad
3. the following line needs to be changed:
WarpTextOnPathEffectPlugin.SubmenuName=Text Formations
3. save the changes, close Notepad and then change the extension back to .dlc 
That's all. Now start PDN.

 

Edited by NSD
typo
  • Upvote 1
Link to post
Share on other sites

Thank you @NSD and @ReMake.

I copied and pasted the text above only for the plugin to disappear altogether. It was not in Effects, neither in Effects > Text Formations. I tried again without the spaces either side of "=" and it worked. It may have been a fluke, bit I thought I'd mention it anyway.

 

When (if?) the plugin is released in the main plugin forum, I think it would perhaps be easier for everyone if it was placed in the text sub-menu outright.

  • Upvote 1

Xkds4Lh.png

Link to post
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.

×
×
  • Create New...