static unsafe int Main(string[] astrArgs) { // Command-line argument processing if (astrArgs.Length == 0) { PrintHelp(); return 0; } for (int i = 0; i < astrArgs.Length; i++) { switch (astrArgs[i]) { case "-?": PrintHelp(); return 0; case "-i": giclrInsert = Int32.Parse(astrArgs[++i]); break; case "-p": gcPaletteEntries = Int32.Parse(astrArgs[++i]); break; case "-c": gcColorEntries = Int32.Parse(astrArgs[++i]); break; case "-t": gfEliminateTransparentColor = true; break; case "-s": gfEliminateShadowColor = true; break; case "-v": gfVerbose = true; break; case "-u": gfPrintColorUsers = true; break; case "-6": gf6bitRGB = true; break; case "-h": gfPrintHistogram = true; break; case "-a": gfAnalyse = true; break; case "-n": gfPhotoshopPad = true; break; case "-f": AddFilesFromFile(astrArgs[++i]); break; case "-o": if (i + 1 >= astrArgs.Length) { Console.WriteLine("Error: -o command requires a filename argument"); return -1; } gstrOutputFileName = astrArgs[++i]; break; default: if (astrArgs[i][0] == '-') { Console.WriteLine("Error: invalid flag '{0}'", astrArgs[i]); return -1; } // Assume all 'unassociated' arguments are input filenames (potentially wildcarded) AddFiles(astrArgs[i]); break; } } if (gstrcFileNames.Count == 0) { Console.WriteLine("Error: no files specified"); return -1; } // Build a list of the colors used and count of uses for each bitmap ArrayList alstBitmapColorInfos = new ArrayList(); foreach (string strFileName in gstrcFileNames) { BitmapColorInfo bci = new BitmapColorInfo(); bci.strFileName = strFileName; bci.htColorCount = new Hashtable(); // Handle .PALs if (strFileName.ToLower().EndsWith(".pal")) { Palette pal = new Palette(strFileName); int i = 0; foreach (Color clr in pal.Colors) { Color clrT = clr; if (gf6bitRGB) clrT = Color.FromArgb(clr.R & 0xfc, clr.G & 0xfc, clr.B & 0xfc); // This hack causes the .PAL colors to be sorted at the head of the // combined palette while retaining the order they were found in the .PAL. if (!bci.htColorCount.Contains(clrT)) bci.htColorCount[clrT] = (Int32.MaxValue / 2) - i++; } // Handle everything else (bitmaps) } else { Bitmap bm = null; try { bm = new Bitmap(strFileName); } catch { Console.WriteLine("Error: {0} is not a recognized bitmap or palette file", strFileName); continue; } // Prep to filter out the transparent color Color clrTransparent = Color.GhostWhite; if (gfEliminateTransparentColor) clrTransparent = bm.GetPixel(0, 0); // Prep to filter out the shadow color Color clrShadow = Color.GhostWhite; if (gfEliminateShadowColor) clrShadow = Color.FromArgb(156, 212, 248); // Keep a per-bitmap list of unique colors and how many times they're used Hashtable ht = bci.htColorCount; // Lock down bits for speed Rectangle rc = new Rectangle(0, 0, bm.Width, bm.Height); BitmapData bmd = bm.LockBits(rc, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); byte *pbBase = (byte *)bmd.Scan0.ToPointer(); for (int y = 0; y < bm.Height; y++) { for (int x = 0; x < bm.Width; x++) { byte *pb = pbBase + y * bmd.Stride + x * 3; Color clr = Color.FromArgb(pb[2], pb[1], pb[0]); if (gfEliminateTransparentColor && clr == clrTransparent) continue; if (gfEliminateShadowColor && clr == clrShadow) continue; if (gf6bitRGB) clr = Color.FromArgb(clr.R & 0xfc, clr.G & 0xfc, clr.B & 0xfc); object obT = ht[clr]; if (obT == null) ht[clr] = 1; else ht[clr] = 1 + (int)obT; } } bm.UnlockBits(bmd); } if (gfVerbose) Console.WriteLine("{0} uses {1} colors", strFileName, bci.htColorCount.Count); if (gfPrintHistogram && gfVerbose) { foreach (DictionaryEntry de in bci.htColorCount) { Color clr = (Color)de.Key; Console.WriteLine("{0},{1},{2} : {3} occurances", clr.R, clr.G, clr.B, (int)de.Value); } Console.WriteLine(); } alstBitmapColorInfos.Add(bci); } if (alstBitmapColorInfos.Count == 0) { Console.WriteLine("Error: no valid bitmap files to process, terminating"); return -1; } // Combine all the color tables and count data Hashtable htCombined = new Hashtable(); foreach (BitmapColorInfo bci in alstBitmapColorInfos) { foreach (DictionaryEntry de in bci.htColorCount) { Color clr = (Color)de.Key; ColorCounter clrc = (ColorCounter)htCombined[clr]; if (clrc == null) { clrc = new ColorCounter(); clrc.cclr = (int)de.Value; htCombined[clr] = clrc; } else { int nAdd = (int)de.Value; if (nAdd > Int32.MaxValue / 3) clrc.cclr = (int)de.Value; else if (clrc.cclr < Int32.MaxValue / 3) clrc.cclr += nAdd; } clrc.alBitmaps.Add(bci.strFileName); } } int cclrCombined = htCombined.Count; Console.WriteLine("Combined palette has {0} unique colors", cclrCombined); // Sort everything by # colors used ColorCounter[] aclrcSorted = new ColorCounter[cclrCombined]; // int i = 0; // foreach (ColorCounter clrc in htCombined.Values) // acOccurancesSorted[i++] = clrc.cclr; htCombined.Values.CopyTo(aclrcSorted, 0); Color[] aclrSorted = new Color[cclrCombined]; htCombined.Keys.CopyTo(aclrSorted, 0); Array.Sort(aclrcSorted, aclrSorted); // Reverse so most-used colors come first // OPT: could do this inside the Sort above by specifying a custom IComparer Array.Reverse(aclrcSorted); Array.Reverse(aclrSorted); if (gfPrintHistogram || gfPrintColorUsers) { for (int i = 0; i < cclrCombined; i++) { Color clr = aclrSorted[i]; int cOccurances = aclrcSorted[i].cclr; if (cOccurances >= Int32.MaxValue / 3) Console.WriteLine("{0},{1},{2} : preloaded", clr.R, clr.G, clr.B); else Console.WriteLine("{0},{1},{2} : {3} occurances", clr.R, clr.G, clr.B, cOccurances); if (gfPrintColorUsers) { foreach (string strFileName in aclrcSorted[i].alBitmaps) { Console.WriteLine(" {0}", strFileName); } } } } // Print warning if # of unique colors is greater than the desired palette size // Truncate to match the requested size since other tools depend on this if (cclrCombined > gcColorEntries) { Console.WriteLine("Warning! {0} unique colors, {1} palette entries reserved. Truncating...", cclrCombined, gcColorEntries); Color[] aclrSortedT = new Color[gcColorEntries]; Array.Copy(aclrSorted, 0, aclrSortedT, 0, gcColorEntries); aclrSorted = aclrSortedT; ColorCounter[] aclrcSortedT = new ColorCounter[gcColorEntries]; Array.Copy(aclrcSorted, 0, aclrcSortedT, 0, gcColorEntries); aclrcSorted = aclrcSortedT; cclrCombined = gcColorEntries; } // Create the palette. Presorted colors start at 0. New colors start at giclrInsert. // giclrInsert == -1 means new colors are simply appended to the presorted colors. Color[] aclrPalette = aclrSorted; if (giclrInsert != -1) { // Init to transparent aclrPalette = new Color[gcColorEntries]; for (int i = 0; i < aclrPalette.Length; i++) aclrPalette[i] = Color.FromArgb(255, 0, 255); // Insert new colors appropriately int iclrBase = -1; for (int i = 0; i < cclrCombined; i++) { if (aclrcSorted[i].cclr >= Int32.MaxValue / 3) { aclrPalette[i] = aclrSorted[i]; continue; } if (iclrBase == -1) iclrBase = i; int iclrNew = giclrInsert + (i - iclrBase); if (iclrNew < aclrPalette.Length) aclrPalette[iclrNew] = aclrSorted[i]; } } // Write the output palette file, if requested if (gstrOutputFileName != null) { Palette pal = new Palette(aclrPalette); if (gfPhotoshopPad) pal.Pad(gcPaletteEntries, pal[pal.Length - 1]); else pal.Pad(gcPaletteEntries, Color.FromArgb(255, 0, 255)); pal.SaveJasc(gstrOutputFileName); } if (gfAnalyse) { Palette pal = new Palette(aclrPalette); // For each color find the nearest color in RGB space and print // the pair as well as the distance between them. for (int iclrA = 0; iclrA < aclrPalette.Length; iclrA++) { Color clrA = aclrPalette[iclrA]; // Find the entry, the long way int nLowest = 256 * 256 * 3; int iLowest = 0; for (int iclr = 0; iclr < aclrPalette.Length; iclr++) { if (iclr == iclrA) continue; Color clrPal = aclrPalette[iclr]; int dR = clrPal.R - clrA.R; int dG = clrPal.G - clrA.G; int dB = clrPal.B - clrA.B; int nD = dR * dR + dG * dG + dB * dB; if (nD < nLowest) { nLowest = nD; iLowest = iclr; } } Color clrB = aclrPalette[iLowest]; double n = Math.Sqrt(nLowest); Console.WriteLine("{8:#.##}\t[{3}] {0},{1},{2} \t[{7}] {4},{5},{6}", clrA.R, clrA.G, clrA.B, iclrA, clrB.R, clrB.G, clrB.B, iLowest, n); } } return 0; }
static unsafe void Main(string[] args) { // Get parameters Palette palIn = new Palette(args[0]); string strFileOut = args[1]; double dAlpha = Double.Parse(args[2]); // Create mapping byte[] ab = new byte[palIn.Length]; Palette palInHSB = new Palette(palIn.Length); for (int iclr = 0; iclr < palIn.Length; iclr++) { Color clr = palIn[iclr]; double h = clr.GetHue(); double s = clr.GetSaturation(); double l = clr.GetBrightness(); double r; double g; double b; MyHSLtoRGB(h, s, l * dAlpha, &r, &g, &b); Color clrShadow = Color.FromArgb((int)(r * 255.0), (int)(g * 255.0), (int)(b * 255.0)); ab[iclr] = (byte)palIn.FindClosestEntry(clrShadow); } // Write palette mapping Stream stm = new FileStream(strFileOut, FileMode.Create, FileAccess.Write, FileShare.None); BinaryWriter bwtr = new BinaryWriter(stm); bwtr.Write(ab); bwtr.Close(); #if false // Check it Palette palCheck = new Palette(palIn.Length); for (int iclr = 0; iclr < palIn.Length; iclr++) palCheck[iclr] = palIn[ab[iclr]]; palCheck.SaveJasc("shadow.pal"); #endif }