nycos62

SpriteSheet Grid Modifier

Recommended Posts

new plugin : SpriteSheet Unpacker

 

Hello,

is there a recommended approach to identify points clusters or shape center ? (K-means clustering .. ? https://towardsdatascience.com/the-5-clustering-algorithms-data-scientists-need-to-know-a36d136ef68 )

 

I want to identify each sprite of a packed spritesheet to tile them on a grid

 

step 0 : put transparent color on background, expand canvas size OK

step0.png.041866b93a28161262ef802c33111ef3.png

 

step 1 : edge detection => get list of points (inner shape points / outer shape points)  OK

step1.png.71c20a77e1e2c250b1f270d242bd1c03.png

step 2 : identify clusters / get center point clusters ? with point list ? How to

step2_TODO.png.96eef3b842356dd508d780abb3ffbc17.png

step 3: copy each cluster in center of same size grid cells OK

 

 

Edited by nycos62

Share this post


Link to post
Share on other sites

^^ this assumes your sprites are single 'blobs'. What if a sprite is two or three 'objects' with transparent areas in between?

 

The only way I can think of to define a cluster is a flood fill algorithm. 

Share this post


Link to post
Share on other sites
1 hour ago, Ego Eram Reputo said:

^^ this assumes your sprites are single 'blobs'. What if a sprite is two or three 'objects' with transparent areas in between?

 

The only way I can think of to define a cluster is a flood fill algorithm. 

 

Unfortunatly, I can't use floodFill because Sprites are touching themselves in some spritesheets, there is not always a pixel between 2 sprites

 

I'm trying technoRobbo basic edge detection with a mix of other edge detection to extract pixel blobs

(in blue rectangles, the original sprite sheet has no transparent space between the sprites)

 

edgeDetection.png.90786f8f4266fc576bede229f33871a4.png

 

For sprite with 2 or 3 objects, I'm thinking of an optional parameter 'Sprite MinX MinY Size'

Edited by nycos62

Share this post


Link to post
Share on other sites

If they are touching you"re going to struggle to separate them with code. You really need the [x,y] offset into the sheet and Width x Height of each sprite (if they differ).

 

Save yourself the aggro and manually paste them into a grid?

Share this post


Link to post
Share on other sites
6 minutes ago, Ego Eram Reputo said:

If they are touching you"re going to struggle to separate them with code

I love challenges ;)  

Share this post


Link to post
Share on other sites

@nycos62

Your Grid modifier still crashes when moving the 4th slider. If you want a challenge try and find the math error.

 

File: C:\Program Files\paint.net\Effects\SpriteSheetGridModifier.dll
      Name: SpriteSheetGridModifierEffect.SpriteSheetGridModifierEffectPlugin
      Version: 0.1.6963.15631
      Author: Copyright ©2019 by Nycos62
      Copyright: Modify Cell Grid Size in all layers
      Website: https://www.getpaint.net/redirect/plugins.html
      Full error message: PaintDotNet.WorkerThreadException: Worker thread threw an exception ---> System.ArgumentOutOfRangeException: Coordinates out of range, max={Width=799, Height=599}
Parameter name: (x,y)
Actual value was {X=2,Y=-1}.
   at PaintDotNet.ExceptionUtil.ThrowArgumentOutOfRangeException(String paramName, Object actualValue, String message) in D:\src\pdn\src\Base\ExceptionUtil.cs:line 88
   at PaintDotNet.Surface.GetSetItemThrow(Int32 x, Int32 y) in D:\src\pdn\src\Core\Surface.cs:line 813
   at SpriteSheetGridModifierEffect.SpriteSheetGridModifierEffectPlugin.Render(Surface dst, Surface src, Rectangle rect)
   at SpriteSheetGridModifierEffect.SpriteSheetGridModifierEffectPlugin.OnRender(Rectangle[] rois, Int32 startIndex, Int32 length)
   at PaintDotNet.Effects.Effect`1.Render(EffectConfigToken parameters, RenderArgs dstArgs, RenderArgs srcArgs, Rectangle[] rois, Int32 startIndex, Int32 length) in D:\src\pdn\src\Effects\Effect`1.cs:line 99
   at PaintDotNet.Effects.BackgroundEffectRenderer.RenderWithClipMask(Effect effect, EffectConfigToken token, RenderArgs dstArgs, RenderArgs srcArgs, RectInt32[] rois, IRenderer`1 clipMaskRenderer) in D:\src\pdn\src\PaintDotNet\Effects\BackgroundEffectRenderer.cs:line 196
   at PaintDotNet.Effects.BackgroundEffectRenderer.RendererContext.RenderTile(EffectConfigToken token, Int32 tileIndex) in D:\src\pdn\src\PaintDotNet\Effects\BackgroundEffectRenderer.cs:line 175
   at PaintDotNet.Effects.BackgroundEffectRenderer.RendererContext.RenderNextTile(EffectConfigToken token) in D:\src\pdn\src\PaintDotNet\Effects\BackgroundEffectRenderer.cs:line 167
   at PaintDotNet.Effects.BackgroundEffectRenderer.ThreadFunction() in D:\src\pdn\src\PaintDotNet\Effects\BackgroundEffectRenderer.cs:line 267
   --- End of inner exception stack trace ---
   at PaintDotNet.Effects.BackgroundEffectRenderer.DrainExceptions() in D:\src\pdn\src\PaintDotNet\Effects\BackgroundEffectRenderer.cs:line 443
   at PaintDotNet.Effects.BackgroundEffectRenderer.Abort() in D:\src\pdn\src\PaintDotNet\Effects\BackgroundEffectRenderer.cs:line 399
   at PaintDotNet.Effects.BackgroundEffectRenderer.Start() in D:\src\pdn\src\PaintDotNet\Effects\BackgroundEffectRenderer.cs:line 345
   at PaintDotNet.Menus.EffectMenuBase.<>c__DisplayClass41_4.<RunEffectImpl>b__4() in D:\src\pdn\src\PaintDotNet\Menus\EffectMenuBase.cs:line 926

I think when a negative number is created it causes the crash. But I'm no coder (yet)

 

  • Like 1

Share this post


Link to post
Share on other sites
45 minutes ago, AndrewDavid said:

Your Grid modifier still crashes when moving the 4th slider.

 

I believe this is where the negative values get introduced:

 

On 1/26/2019 at 6:44 PM, nycos62 said:

int XDiff = Amount5 - Amount3;
int YDiff = Amount6 - Amount4;

 

 

Are you trying to make the new cells smaller than the original cells? I think this plugin is only meant to increase the cell size.

  • Like 1

Share this post


Link to post
Share on other sites
15 hours ago, AndrewDavid said:

If you want a challenge try and find the math error.

challenge accepted ;) thank you for the tests

 

I never thought to decrease cellSize , toe_head2001 was right :) 

 

// Name: SpriteSheet Grid Modifier
// Submenu: PixelArt
// Author: Nycos62
// Title: SpriteSheet Grid Modifier
// Version: 0.1
// Desc: Modify Cell Grid Size in all layers
// Keywords:
// URL:
// Help:
#region UICode
IntSliderControl Amount1 = 6; // [0,2000] X Cell number
IntSliderControl Amount2 = 6; // [0,2000] Y Cell number
IntSliderControl Amount3 = 14; // [0,2000] Original Cell width (X)
IntSliderControl Amount4 = 14; // [0,2000] Original Cell height (Y)
IntSliderControl Amount5 = 20; // [0,2000] Desired Cell width (New X)
IntSliderControl Amount6 = 20; // [0,2000] Desired Cell height (New Y)
#endregion

void Render(Surface dst, Surface src, Rectangle rect)
{
    //clear dest
    dst.Clear(rect, ColorBgra.Transparent);
    
    int XOffset = 0;
    int YOffset = 0;
    int XDiff = Amount5 - Amount3;
    int YDiff = Amount6 - Amount4;
    if (XDiff %2 == 0)
        XOffset = XDiff / 2;
    else
        XOffset = (int)(XDiff / 2); //TODO verify
    if (YDiff %2 == 0)
        YOffset = YDiff / 2;
    else
        YOffset = (int)(YDiff / 2); //TODO verify
    
    int XOffsetAfter = XDiff - XOffset;
    int YOffsetAfter = YDiff - YOffset;
    
    for (int cx = 0; cx < Amount1; cx++)
    {
        int XDecay = Math.Max(cx,0) * XOffsetAfter;
        for (int cy = 0; cy < Amount2; cy++)
        {
            int YDecay = Math.Max(cy,0) * YOffsetAfter;
            
            if (IsCancelRequested) return;
            
            //Copy cell with Offset
            for (int cell_posX = 0; cell_posX < Amount3; cell_posX++)
            {
                
                for (int cell_posY = 0; cell_posY < Amount4; cell_posY++)
                {   
                    int Xdest = cx * Amount3 + cell_posX + (cx+1) * XOffset + XDecay;
                    int Ydest = cy * Amount4 + cell_posY + (cy+1) * YOffset + YDecay;
                    int XSource = cx * Amount3 + cell_posX;
                    int YSource = cy * Amount4 + cell_posY;
                    //avoid drawing out of bounds dst/src [x,y]
                    if (Xdest < dst.Bounds.Width && Ydest < dst.Bounds.Height && 
                        XSource < dst.Bounds.Width && YSource < dst.Bounds.Height &&
                        Xdest > 0 && Ydest > 0 &&
                        Xdest < Amount1 * Amount5 && Ydest < Amount2 * Amount6)
                        dst[Xdest, Ydest] = src[XSource, YSource];
                }
            }
        }
    }
}

 

Edited by nycos62
  • Like 1

Share this post


Link to post
Share on other sites

new plugin : Pixel Art Palette Reverse (dll=> ReversePalette.zip) (spritesheet unpacker plugin is paused...)


*remember it's for pixel Art so if you use huge picture with huge amount of different color it going to be slow as hell

 

purpose :  extract and manipulate palette

 

you want to apply shadow modification on sprite, you want to invert palette of sprite

1439875438_exempleofuse.png.6f5429b2a79a799e79f002d112bdc3cc.png

 

1137322000_exempleofuse2.png.074913376254a3dcf43976587e12d4d6.png

 

First check box shows palette grabbed in the selection, if no selection, full picture is processed

 

 

Future Add Reverse "By Color Family", TODO.. 

(need to code how to sort palette by color HUE range and reverse it)

1709246676_futurerelease.thumb.png.ee1234f86b827df7fb97afdb6100a2f3.png

 

with "User Choice", you draw on the third line of pixels your custom palette to change colors directly on the result

(if your canvas width is smaller than palette and you are using "User Choice" option, colors out of canvas are replaced with transparent

 

123101581_usercustompalette.thumb.png.6303ee7cb53943f538849a3958b2625d.png

 

// Name: Pixel Art Reverse Palette
// Submenu: PixelArt
// Author: Nycos62
// Title: Reverse palette in selected area(s)
// Version: 0.1
// Desc: reversing the palette in selected zone
// Keywords:
// URL:
// Help:
#region UICode
bool Amount1 = true; // [0,1] Show Palette and Reversed Palette
bool Amount2 = false; // [0,1] Apply modified Palette
byte Amount3 = 0; // Reversion Method|Full palette Sort|Full Intensity|By Grabbing Order|By Color Family(TODO...)|User choice(Draw Your custom Palette on 3rd line)
bool Amount4 = true; //[0,1] Ignore transparent Color (0,0,0,0)
bool Amount5 = true; //[0,1] Ignore semi transparent Colors
#endregion

void Render(Surface dst, Surface src, Rectangle rect)
{
    //Getting selected Pixels
    System.Drawing.Drawing2D.RegionData selection = EnvironmentParameters.GetSelection(src.Bounds).GetRegionData();
    Region reg = new Region(selection);
    RectangleF[] rects = reg.GetRegionScans(new Matrix());

    //Grabbing Palette
    List<ColorBgra> colorsInSelection = new List<ColorBgra>();
    foreach(RectangleF r in rects)
    {
        for (int y = (int)r.Top; y < r.Bottom; y++)
        {
            if (IsCancelRequested) return;
            if (y > 2)//Ignore top 3 lines to grab palette
                for (int x = (int)r.Left; x < r.Right; x++)
                {
                    if (!colorsInSelection.Contains(src[x,y]))
                    {
                        if ((Amount5 && src[x,y].A == 255) || !Amount5)
                            if ((Amount4 && src[x,y] != ColorBgra.Transparent) || !Amount4)
                                colorsInSelection.Add(src[x,y]);
                    }
                }
        }
    }
    
    //Copy into Result Palette
    System.Collections.ArrayList sortedColors = new System.Collections.ArrayList();
    for (int i = 0; i < colorsInSelection.Count; i++)
    {
        ColorBgra col = new ColorBgra();
        col.A = colorsInSelection[i].A;
        col.B = colorsInSelection[i].B;
        col.G = colorsInSelection[i].G;
        col.Bgra = colorsInSelection[i].Bgra;
        sortedColors.Add(col);
    }
    
    //ApplySort
    if (Amount3 == 0 || Amount3 == 4)    
        sortedColors.Sort(new _ColorSorter());
    if (Amount3 == 1)
        sortedColors.Sort(new _IntensitySorter());
    
    //Copy Sorted Palette To Original palette
    colorsInSelection = new List<ColorBgra>();
    for (int i = 0; i < sortedColors.Count; i++)
    {
        ColorBgra col = new ColorBgra();
        col.A = ((ColorBgra)sortedColors[i]).A;
        col.B = ((ColorBgra)sortedColors[i]).B;
        col.G = ((ColorBgra)sortedColors[i]).G;
        col.Bgra = ((ColorBgra)sortedColors[i]).Bgra;
        colorsInSelection.Add(col);
    }
    
    if (IsCancelRequested) return;
    
    //Reverse Palette
    if (Amount3 == 4)
    {
        sortedColors = new System.Collections.ArrayList();
        
        for (int x = 0; x < colorsInSelection.Count; x++)
        {
            if (x < dst.Bounds.Width)
            {                
                sortedColors.Add(dst[x,2]);
            }
            else
            {
                sortedColors.Add(ColorBgra.Transparent);
            }
        }
    }
    else
        sortedColors.Reverse();
    
    
    //Show palette on upperLeft image corner
    for (int i = 0; i < dst.Bounds.Width; i++)
    {
        if (Amount1 && i<colorsInSelection.Count)
        {            
            dst[i,0] = colorsInSelection[i];
            dst[i,1] = (ColorBgra)sortedColors[i];
        }
        else
        {
            dst[i,0] = src[i,0];
            dst[i,1] = src[i,1];
        }
    }
    
    if (IsCancelRequested) return;
    
    //Apply change
    
        foreach(RectangleF r in rects)
        {
            for (int y = (int)r.Top; y < r.Bottom; y++)
            {
                if (IsCancelRequested) return;
                for (int x = (int)r.Left; x < r.Right; x++)
                {
                    //avoid draw on palette
                    if (!(x < colorsInSelection.Count && y < 3))
                    {
                        if (Amount2)
                        {              
                                    
                            if (colorsInSelection.Contains(src[x,y]))                                                
                                dst[x,y] = (ColorBgra)sortedColors[colorsInSelection.IndexOf(src[x,y])];
                        }
                        else
                        {
                            dst[x,y] = src[x,y];
                        }
                    }
                }
            }
        }
    
    
}

internal class _ColorSorter: System.Collections.IComparer 
{
    public int Compare (object x, object y) {
        // local variables
        Color   cx, cy;
        float   hx, hy, sx, sy, bx, by;

        // get Color values
        cx = ((ColorBgra) x).ToColor();
        cy = ((ColorBgra) y).ToColor();
        // get saturation values
        sx = cx.GetSaturation ();
        sy = cy.GetSaturation ();
        // get hue values
        hx = cx.GetHue ();
        hy = cy.GetHue ();
        // get brightness values
        bx = cx.GetBrightness ();
        by = cy.GetBrightness ();

        // determine order
        // 1 : hue       
        if (hx < hy) return -1; 
        else if (hx > hy) return 1;
        else {
            // 2 : saturation
            if (sx < sy) return -1;
            else if (sx > sy) return 1;
            else {
                // 3 : brightness
                if (bx < by) return -1;
                else if (bx > by) return 1;
                else return 0;
            }
        }
    }
}

internal class _IntensitySorter: System.Collections.IComparer 
{
    public int Compare (object x, object y) {        
        return ((ColorBgra)x).GetIntensity().CompareTo(((ColorBgra)y).GetIntensity());
    }
}

 

cheers !

 

Sorting Colors "By Family" :

public string Classify(Color c)
{
    float hue = c.GetHue();
    float sat = c.GetSaturation();
    float lgt = c.GetLightness();

    if (lgt < 0.2)  return "Blacks";
    if (lgt > 0.8)  return "Whites";

    if (sat < 0.25) return "Grays";

    if (hue < 30)   return "Reds";
    if (hue < 90)   return "Yellows";
    if (hue < 150)  return "Greens";
    if (hue < 210)  return "Cyans";
    if (hue < 270)  return "Blues";
    if (hue < 330)  return "Magentas";
    return "Reds";
}

 

Edited by nycos62

Share this post


Link to post
Share on other sites

wow... colors are really complicated to classify :

what is the limit between a brown, a grey, a black, and an orange, a yellow, a green color ?.. need to add trackbars to adjust palette color range detection depending on the picture palette...

 

 

 

 

Share this post


Link to post
Share on other sites

Color Family is an odd way to select shades - as you've found :) 

 

Why not use the Colorwheel control to give the user all the color options they would ever need?

Share this post


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.