Jump to content

SpriteSheet Grid Modifier


nycos62

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

Link to comment
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
Link to comment
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?

Link to comment
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

PaintNetSignature.png.6bca4e07f5d738b2436f83d0ce1b876f.png

Link to comment
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

(September 25th, 2023)  Sorry about any broken images in my posts. I am aware of the issue.

bp-sig.png
My Gallery  |  My Plugin Pack

Layman's Guide to CodeLab

Link to comment
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
Link to comment
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
  • Thanks 1
Link to comment
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...

 

 

 

 

Link to comment
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?

Link to comment
Share on other sites

  • 1 year later...

Hi folks,

 

this is the old codelab plugin palette switcher, but first read this :

Pixel Art Palette Switcher.zip

 

 

 

I was trying to achieve this kind of effect from a photo

goal.png.0f711e53994ebaa89b971ad64e8f6689.png

I did not find a plugin to get the palette of image and change the colors

so what have I done, I remembered my old palette switcher attempt with code lab

 

first get the palette of the lion with TR's Color Reducer, because of internet picture (jpg sadness... ... )

and save the palette, as lion.txt

273839475_TRscolorReducer.png.a5ebf8fdd368e35d75207952612ae855.png

 

 

then I took a random picture to test, my daughter for exemple :

 

here are the steps :

Gaussian Blur Radius 10

Black and White

Posterize (23)    ( because Lion.txt palette have 23 colors )

 

1381083546_testsample.thumb.png.b480df4ab686c185fa77e424f6c83664.png

 

then I used my old plugin Palette Switcher

run the plugin palette switcher once, it will draw on first line of the picture the current palette, and click OK

 

 

1714221554_paletteswitcher1.png.1625f447ec31fcb1d90d83bf72b8c138.png

 

 then draw the replacement palette on the 3rd line

 

1901456624_paletteswitcher2.png.29961832d1c00dac7284838b7d1155d1.png

then run the palette switcher plugin once again

 

Choose Reversion Method 'User Choice'

and check the checkbox apply palette

 

2125478824_paletteswitcher3.png.8b848eae5077e5767abda6b98d471ae3.png

another quick sample :

 

 

Spoiler

364697366_paletteswitcher5.thumb.png.5bd7d67ed250dfa961af97033d1d97f8.png

 

Spoiler

542649212_paletteswitcher6.thumb.png.4a549710e91c9e51fe05cc6f3f21627b.png

 

Spoiler

897638963_paletteswitcher7.png.d1192fa51aeb89ef47e624aa60a87848.png

 

Spoiler

1918836008_paletteswitcher8.thumb.png.f6a1fedb79c185b731717b22490802ad.png

 

 

Spoiler

1192437237_paletteswitcher9.thumb.png.8c429e571d3ae127bf8458bff72586fc.png

 

 

Spoiler

1451251721_paletteswitcher10.thumb.png.427869ee96b0c3a37a3f0bc012a25534.png

 

 

DO NOT USE ON A NON REDUCED PALETTE IMAGE WITH POSTERIZATION OR ELSE, OR IT WILL TAKE FOREVER TO COMPUTE THE RESULT

 

If someone knows a better way to achieve this, please let me know

 

Cheers !

 

Edited by nycos62
  • Like 1
  • Upvote 1
Link to comment
Share on other sites

These effects are generally achieved by manually creating the areas (likely in vector format since you can manipulate lines easier with it). Theoretically, you can achieve better results than what you did using anisotropic smoothing based on partial differential equations, and inpainting. I don't know how to describe that in terms of c# or more clearer, but it's something to look into if you want better result. Regardless I like the result, but making it better would take a lot of time, and a lot of research.

Edited by Reptillian

G'MIC Filter Developer

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.

×
×
  • Create New...