2 Commits

Author SHA1 Message Date
  Philipp AUER bc0d43e790 finish basic functionality for pokemon sprites 5 years ago
  Philipp AUER cac6073207 update .gitignore 5 years ago
6 changed files with 276 additions and 1 deletions
  1. 2
    1
      .gitignore
  2. 28
    0
      .vscode/launch.json
  3. 15
    0
      .vscode/tasks.json
  4. 164
    0
      ImageLib/BitmapProcessor.cs
  5. 52
    0
      Program.cs
  6. 15
    0
      agbidx.csproj

+ 2
- 1
.gitignore View File

@@ -4,4 +4,5 @@
4 4
 !.vscode/tasks.json
5 5
 !.vscode/launch.json
6 6
 !.vscode/extensions.json
7
-
7
+bin/
8
+obj/

+ 28
- 0
.vscode/launch.json View File

@@ -0,0 +1,28 @@
1
+{
2
+   // Use IntelliSense to find out which attributes exist for C# debugging
3
+   // Use hover for the description of the existing attributes
4
+   // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
5
+   "version": "0.2.0",
6
+   "configurations": [
7
+        {
8
+            "name": ".NET Core Launch (console)",
9
+            "type": "coreclr",
10
+            "request": "launch",
11
+            "preLaunchTask": "build",
12
+            // If you have changed target frameworks, make sure to update the program path.
13
+            "program": "${workspaceFolder}/bin/Debug/netcoreapp2.1/agbidx.dll",
14
+            "args": ["-v", "-i", "/home/karathan/Downloads/Sprites aus der ROM/Pokemon Sprites/kanto", "-o", "/home/karathan/Downloads/Sprites aus der ROM/Pokemon Sprites/kanto/rendered"],
15
+            "cwd": "${workspaceFolder}",
16
+            // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
17
+            "console": "internalConsole",
18
+            "stopAtEntry": false,
19
+            "internalConsoleOptions": "openOnSessionStart"
20
+        },
21
+        {
22
+            "name": ".NET Core Attach",
23
+            "type": "coreclr",
24
+            "request": "attach",
25
+            "processId": "${command:pickProcess}"
26
+        }
27
+    ,]
28
+}

+ 15
- 0
.vscode/tasks.json View File

@@ -0,0 +1,15 @@
1
+{
2
+    "version": "2.0.0",
3
+    "tasks": [
4
+        {
5
+            "label": "build",
6
+            "command": "dotnet",
7
+            "type": "process",
8
+            "args": [
9
+                "build",
10
+                "${workspaceFolder}/agbidx.csproj"
11
+            ],
12
+            "problemMatcher": "$msCompile"
13
+        }
14
+    ]
15
+}

+ 164
- 0
ImageLib/BitmapProcessor.cs View File

@@ -0,0 +1,164 @@
1
+using System;
2
+using System.Collections.Generic;
3
+using System.Threading.Tasks;
4
+using System.Drawing;
5
+using System.Drawing.Imaging;
6
+using System.IO;
7
+using System.Linq;
8
+
9
+namespace agbidx.ImageLib
10
+{
11
+    public static class BitmapProcessor
12
+    {
13
+        public static async Task ProcessPokemonImage(string outDirectory, string inPath, bool verbose, bool scanDirectories, bool recursive)
14
+        {
15
+            //Create the original bitmap
16
+            if(Directory.Exists(inPath))
17
+            {
18
+                if(scanDirectories)
19
+                {
20
+                    List<Task> subTasks = new List<Task>();
21
+                    foreach(string s in Directory.GetFiles(inPath))
22
+                    {
23
+                        subTasks.Add(ProcessPokemonImage(outDirectory, s, verbose, recursive, recursive));
24
+                    }
25
+                    foreach(string s in Directory.GetDirectories(inPath))
26
+                    {
27
+                        subTasks.Add(ProcessPokemonImage(outDirectory, s, verbose, recursive, recursive));
28
+                    }
29
+                    await Task.WhenAll(subTasks);
30
+                }
31
+
32
+            }
33
+            else
34
+            {
35
+                await Task.Run(() => {
36
+                    Bitmap input = new Bitmap(inPath);
37
+                    ProcessPokemonImageFront(input, outDirectory, Path.GetFileNameWithoutExtension(inPath), verbose);
38
+                });
39
+            }
40
+
41
+        }
42
+
43
+        private static Color[] Quantize4bppColors(Bitmap bmp)
44
+        {
45
+            List<Color> colors = new List<Color>(16);
46
+            BitmapData bitmapData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);
47
+            int bytesPerPixel = System.Drawing.Bitmap.GetPixelFormatSize(bmp.PixelFormat) / 8;
48
+            int heightInPixels = bitmapData.Height;
49
+            int widthInBytes = bitmapData.Width * bytesPerPixel;
50
+            int palCount = 0;
51
+            unsafe
52
+            {
53
+                byte* ptrFirstPixel = (byte*)bitmapData.Scan0;
54
+                for (int y = 0; y < heightInPixels; y++)
55
+                {
56
+                    byte* currentLine = ptrFirstPixel + (y * bitmapData.Stride);
57
+                    for (int x = 0; x < widthInBytes; x = x + bytesPerPixel)
58
+                    {
59
+                        int blue = currentLine[x];
60
+                        int green = currentLine[x + 1];
61
+                        int red = currentLine[x + 2];
62
+                        if (!colors.Any(c => c.R == red && c.G == green && c.B == blue))
63
+                        {
64
+                            colors.Add(Color.FromArgb(red,green,blue));
65
+                            palCount++;
66
+                            if(palCount > 16)
67
+                                throw new ArgumentException("The image has too many colors", "bmp");
68
+                        }
69
+                            
70
+                    }
71
+                }
72
+            }
73
+            bmp.UnlockBits(bitmapData);
74
+            return colors.ToArray();
75
+        }
76
+
77
+        private static Bitmap RenderBitmapFromPalette(Bitmap original, Color[] palette)
78
+        {
79
+            Bitmap output = new Bitmap(original.Width,original.Height, PixelFormat.Format4bppIndexed);
80
+            BitmapData targetData = output.LockBits(new Rectangle(0, 0, original.Width,original.Height),
81
+                ImageLockMode.ReadWrite, PixelFormat.Format4bppIndexed);
82
+            BitmapData sourceData = original.LockBits(new Rectangle(0, 0, original.Width,original.Height),
83
+                ImageLockMode.ReadOnly, original.PixelFormat);
84
+
85
+            int bytesPerPixel = System.Drawing.Bitmap.GetPixelFormatSize(original.PixelFormat) / 8;
86
+            int heightInPixels = sourceData.Height;
87
+            int widthInBytes = sourceData.Width * bytesPerPixel;
88
+            unsafe
89
+            {
90
+                byte* ptrFirstPixel = (byte*)sourceData.Scan0;
91
+                for (int y = 0; y < heightInPixels; y++)
92
+                {
93
+                    byte* currentLine = ptrFirstPixel + (y * sourceData.Stride);
94
+                    byte* pTarget = (byte*) (targetData.Scan0 + y*targetData.Stride);
95
+                    byte prevValue = 0;
96
+                    for (int x = 0; x < widthInBytes; x = x + bytesPerPixel)
97
+                    {
98
+                        int blue = currentLine[x];
99
+                        int green = currentLine[x + 1];
100
+                        int red = currentLine[x + 2];
101
+                        if (!palette.Any(c => c.R == red && c.G == green && c.B == blue))
102
+                            throw new ArgumentException($"The color palette does not contain the required entry {red}:{green}:{blue}.","palette");
103
+                        else
104
+                        {
105
+                            byte colorIndex = (byte)Array.IndexOf(palette, Color.FromArgb(red,green,blue));
106
+                            if((x/bytesPerPixel) % 2 == 0)
107
+                                prevValue = colorIndex;
108
+                            else
109
+                            {
110
+                                *pTarget = (byte)((prevValue<<4) | (colorIndex));
111
+                                pTarget++;
112
+                            }
113
+
114
+                        }
115
+                    }
116
+                }
117
+            }
118
+
119
+            output.UnlockBits(targetData);
120
+            original.UnlockBits(sourceData);
121
+
122
+            ColorPalette pal = output.Palette;
123
+            for(int i = 0; i < ((palette.Length > 16) ? 16 : palette.Length); ++i)
124
+            {
125
+                pal.Entries[i] = palette[i];
126
+            }
127
+            output.Palette = pal;
128
+            return output;
129
+        }
130
+        private static void ProcessPokemonImageFront(Bitmap inFile, string outDirectory, string name, bool verbose)
131
+        {
132
+            Bitmap normal = new Bitmap(128,64, PixelFormat.Format32bppRgb);
133
+            Bitmap shiny = new Bitmap(128,64, PixelFormat.Format32bppRgb);
134
+            using(Graphics g = Graphics.FromImage(normal))
135
+            {
136
+                g.DrawImage(inFile,new Rectangle(0,0,64,64),new Rectangle(0,0,64,64), GraphicsUnit.Pixel);
137
+                g.DrawImage(inFile,new Rectangle(64,0,64,64),new Rectangle(2*64,0,64,64), GraphicsUnit.Pixel);
138
+            }
139
+            using(Graphics g = Graphics.FromImage(shiny))
140
+            {
141
+                g.DrawImage(inFile ,new Rectangle(0,0,64,64),new Rectangle(1*64,0,64,64), GraphicsUnit.Pixel);
142
+                g.DrawImage(inFile ,new Rectangle(64,0,64,64),new Rectangle(3*64,0,64,64), GraphicsUnit.Pixel);
143
+            }
144
+
145
+            Color[] normalPalette = Quantize4bppColors(normal);
146
+            Color[] shinyPalette = Quantize4bppColors(shiny);
147
+
148
+            Bitmap normalIndexed = RenderBitmapFromPalette(normal, normalPalette);
149
+            Bitmap shinyIndexed = RenderBitmapFromPalette(shiny, shinyPalette);
150
+
151
+            Bitmap frontNormalIndexed = normalIndexed.Clone(new Rectangle(0,0,64,64), PixelFormat.Format4bppIndexed);
152
+            Bitmap backShinyIndexed = shinyIndexed.Clone(new Rectangle(64,0,64,64), PixelFormat.Format4bppIndexed);
153
+
154
+            frontNormalIndexed.Save(Path.Combine(outDirectory, name+"_frontnormal.png"),ImageFormat.Png);
155
+            backShinyIndexed.Save(Path.Combine(outDirectory,name+"_backshiny.png"),ImageFormat.Png);
156
+
157
+            if(verbose)
158
+            {
159
+                Console.WriteLine("Processed " + Path.Combine(outDirectory, name+"_frontnormal.png"));
160
+                Console.WriteLine("Processed " + Path.Combine(outDirectory,name+"_backshiny.png"));
161
+            }
162
+        }
163
+    }
164
+}

+ 52
- 0
Program.cs View File

@@ -0,0 +1,52 @@
1
+using System;
2
+using System.Collections.Generic;
3
+using System.IO;
4
+using System.Linq;
5
+using System.Threading.Tasks;
6
+using CommandLine;
7
+using CSharpx;
8
+
9
+namespace agbidx
10
+{
11
+    [Verb("pokemon", HelpText = "Parse a combination of Pokémon front- and backsprite, and output two files respectively.")]
12
+    class PokespriteOptions {
13
+        [Option('i', "input-files", Required = true, HelpText = "Input images to be processed")]
14
+        public IEnumerable<string> Input {get;set;}
15
+
16
+        [Option('o', "output-directory", Default = ".", Required = false, HelpText = "Output directory for the processed images, defaults to current directory")]
17
+        public string OutputDirectory{get;set;}
18
+
19
+        [Option('v', "verbose", Default = false, Required = false, HelpText = "Show Verbose information")]
20
+        public bool Verbose{get;set;}
21
+
22
+        [Option('r', "recursive", Default = false, Required = false, HelpText = "Recursively scan directories for images, by default only one level is scanned, if an input argument is a directory")]
23
+        public bool Recursive{get;set;}
24
+    }
25
+    class Program
26
+    {
27
+        static async Task<int> Main(string[] args)
28
+        {
29
+            return await CommandLine.Parser.Default.ParseArguments<PokespriteOptions>(args)
30
+	            .MapResult(
31
+	                async (PokespriteOptions opts) => await RunPokespritesAndReturnExitCode(opts),
32
+	                async (IEnumerable<Error> errs) => await Task.Run<int>(() => 1));
33
+        }
34
+
35
+        static async Task<int> RunPokespritesAndReturnExitCode(PokespriteOptions opts)
36
+        {
37
+            try
38
+            {
39
+                if(!Directory.Exists(opts.OutputDirectory))
40
+                    Directory.CreateDirectory(opts.OutputDirectory);
41
+                    await Task.WhenAll(opts.Input.Select(s => ImageLib.BitmapProcessor.ProcessPokemonImage(opts.OutputDirectory, s, opts.Verbose, true, opts.Recursive)));
42
+                return 0;
43
+            }
44
+            catch(Exception ex)
45
+            {
46
+                Console.WriteLine("An error occured: " + ex.Message);
47
+                return 1;
48
+            }
49
+
50
+        }
51
+    }
52
+}

+ 15
- 0
agbidx.csproj View File

@@ -0,0 +1,15 @@
1
+<Project Sdk="Microsoft.NET.Sdk">
2
+
3
+  <PropertyGroup>
4
+    <OutputType>Exe</OutputType>
5
+    <TargetFramework>netcoreapp2.1</TargetFramework>
6
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
7
+    <LangVersion>7.2</LangVersion>
8
+  </PropertyGroup>
9
+
10
+  <ItemGroup>
11
+    <PackageReference Include="CommandLineParser" Version="2.3.65" />
12
+    <PackageReference Include="System.Drawing.Common" Version="4.5.1" />
13
+  </ItemGroup>
14
+
15
+</Project>