private void DumpImage(SourceImage srcImg) { return; /* Bitmap bmp = new Bitmap(srcImg.CropW, srcImg.CropH, PixelFormat.Format32bppArgb); for(int x=0; x<srcImg.CropW; ++x) for (int y = 0; y < srcImg.CropH; ++y) { int sx = x + srcImg.CropX; int sy = y + srcImg.CropY; int color = srcImg.Data[sx + sy*srcImg.Stride]; bmp.SetPixel(x, y, Color.FromArgb(color)); } bmp.Save(@"c:\tmp\cram\" + srcImg.ShortName); */ }
private void ProcessImage(string filename) { // Try to load the image from file Image origImg; SourceImage srcImg = new SourceImage(); srcImg.Filename = filename; // Some of these path conversions can fail, if they do, fallback to the // default path try { switch (Settings.PathMode) { case PathMode.Short: srcImg.ShortName = Path.GetFileName(filename); break; case PathMode.Full: srcImg.ShortName = Path.GetFullPath(filename); break; case PathMode.Relative: srcImg.ShortName = PathUtil.RelativePathTo(Path.GetFullPath(Settings.RelativePathBase), Path.GetFullPath(filename)); break; } } catch (Exception) { Log("Error adjusting path, using original: {0}", filename); srcImg.ShortName = filename; } try { origImg = Image.FromFile(srcImg.Filename); } catch (Exception) { Log("Error opening file, skipping: {0}", srcImg.ShortName); ++numFailed; return; } // Probably can't happen, but just in case if (origImg.Width == 0 || origImg.Height == 0) { Log("Image has a zero dimension, skipping: {0}", srcImg.Filename); ++numFailed; return; } // TODO: Only do this if necessary? // If that succeeded, then copy it to a bitmap with a known pixel format (ARGB) // to do some work on it Bitmap bmp = new Bitmap(origImg.Width, origImg.Height, PixelFormat.Format32bppArgb); using (Graphics graphics = Graphics.FromImage(bmp)) { graphics.Clear(Color.Transparent); graphics.DrawImage(origImg, 0, 0, origImg.Width, origImg.Height); } // Clean up original image origImg.Dispose(); // Copy pixels out toa raw int array -- this is about 400x faster than GetPixel() int pixels = bmp.Width * bmp.Height; int[] pixelData = new int[pixels]; BitmapData data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); System.Runtime.InteropServices.Marshal.Copy(data.Scan0, pixelData, 0, pixels); bmp.UnlockBits(data); // Fill in some fields on the source image info block srcImg.Data = pixelData; srcImg.Stride = data.Stride / 4; srcImg.Width = bmp.Width; srcImg.Height = bmp.Height; srcImg.CropW = srcImg.Width; srcImg.CropH = srcImg.Height; bmp.Dispose(); // Perform colorkeying ColorKey(srcImg); // Perform cropping of transparent areas if (!Crop(srcImg)) { Log("Image has zero dimension after cropping, skipping: {0}", srcImg.ShortName); ++numFailed; return; } DumpImage(srcImg); if (Settings.Unique) { string md5 = ComputeHash(srcImg); if (uniqueTable.ContainsKey(md5)) { // TODO: Check if the images are really exactly the same! SourceImage dupOf = uniqueTable[md5]; srcImg.DuplicateOf = dupOf; Log("Duplicate found! {0} has same hash as {1}: {2}", srcImg.ShortName, dupOf.ShortName, md5); ++numDups; } else { uniqueTable[md5] = srcImg; ++numUnique; } } else ++numUnique; // Save this to the source image list SourceImages.Add(srcImg); }
private int DetectColorKey(SourceImage srcImg) { int w = srcImg.Width, h = srcImg.Height; // Lol repetitive code :( int a = srcImg.Data[0], b = srcImg.Data[w - 1], c = srcImg.Data[(h - 1)*(srcImg.Stride)], d = srcImg.Data[(h - 1)*(srcImg.Stride) + w - 1]; // If any of them are partially transparent, assume image had an alpha channel and thus // colorkey is 100% transparent -- i.e., don't do anything if (((a & 0xFF000000)>>24) != 255 || ((b & 0xFF000000)>>24) != 255 || ((c & 0xFF000000)>>24) != 255 || ((d & 0xFF000000)>>24) != 255) return 0; int aScore = 0, bScore = 0, cScore = 0, dScore = 0; aScore = ((b == a) ? 1 : 0) + ((c == a) ? 1 : 0) + ((d == a) ? 1 : 0); bScore = ((a == b) ? 1 : 0) + ((c == b) ? 1 : 0) + ((d == b) ? 1 : 0); cScore = ((a == c) ? 1 : 0) + ((b == c) ? 1 : 0) + ((d == c) ? 1 : 0); dScore = ((a == d) ? 1 : 0) + ((b == d) ? 1 : 0) + ((c == d) ? 1 : 0); if (aScore >= bScore && aScore >= cScore && aScore >= dScore) return a; else if (bScore >= aScore && bScore >= cScore && bScore >= dScore) return b; else if (cScore >= aScore && cScore >= bScore && cScore >= dScore) return c; else return d; }
private Bitmap DataToBitmap(SourceImage image) { Bitmap bmp = new Bitmap(image.Width, image.Height, PixelFormat.Format32bppArgb); BitmapData data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); System.Runtime.InteropServices.Marshal.Copy(image.Data, 0, data.Scan0, image.Width*image.Height); bmp.UnlockBits(data); return bmp; }
// Return true if image was nonzero sized private bool Crop(SourceImage srcImg) { // Bail if we're not cropping if (!Settings.Crop) return true; bool atTop = true; int topBlankRows = 0; int bottomBlankRows = 0; int leftBlankCols = srcImg.Width; int rightBlankCols = srcImg.Height; bool anySolidRows = false; // Basically, for each row we scan left to right to find the first solid pixel. // If there's none, then we treat this as an empty row, and thus affect the min/max // Y values. Otherwise, we scan from the right to get a right bound, then // use this to affect the min/max X values. for (int y = 0; y < srcImg.Height; ++y) { // First, start counting blanks from the left. // If we hit a solid pixel, save its position int x; for (x = 0; x < srcImg.Width; ++x) { int c = srcImg.Data[x + y * srcImg.Stride]; if ((c & 0xFF000000) != 0) break; } // If the row was totally blank if (x == srcImg.Width) { if (atTop) topBlankRows++; else bottomBlankRows++; } // Otherwise, we got a solid row. // First solid pixel is in column 'x' else { atTop = false; bottomBlankRows = 0; int rowLeftBlankCols = x; // If we hit any solid pixels, start counting from the right to // see what the max X is for (x = srcImg.Width - 1; x > rowLeftBlankCols; --x) { int c = srcImg.Data[x + y * srcImg.Stride]; if ((c & 0xFF000000) != 0) break; } int rowRightBlankCols = srcImg.Width - x - 1; leftBlankCols = Math.Min(leftBlankCols, rowLeftBlankCols); rightBlankCols = Math.Min(rightBlankCols, rowRightBlankCols); anySolidRows = true; } } // Now, perform the cropping crap (or at least set up the proper vars) if (anySolidRows) { srcImg.CropX = leftBlankCols; srcImg.CropY = topBlankRows; srcImg.CropW = srcImg.Width - leftBlankCols - rightBlankCols; srcImg.CropH = srcImg.Height - topBlankRows - bottomBlankRows; return true; } else { srcImg.CropX = 0; srcImg.CropY = 0; srcImg.CropW = 0; srcImg.CropH = 0; return false; } }
private string ComputeHash(SourceImage srcImg) { MD5CryptoServiceProvider provider = new MD5CryptoServiceProvider(); // NOTE: This assumes that stride is always equal to width and thus // rows don't have junk in them // TODO: Note that this is a bit of a waste of time :( // I miss pointer casts, maybe I can learn to do it with 'unsafe'... byte[] buf = new byte[srcImg.CropW * srcImg.CropH * 4]; // Only convert the area inside the crop rect int i = 0; for (int x=0; x < srcImg.CropW; ++x) for (int y = 0; y < srcImg.CropH; ++y) { int col = srcImg.Data[srcImg.CropX + x + (srcImg.CropY + y)*srcImg.Stride]; // Treat blank pixels as equal if (((col & 0xFF000000) >> 24) == 0) { buf[i * 4] = buf[i * 4 + 1] = buf[i * 4 + 2] = buf[i * 4 + 3] = 0; } else { buf[i * 4] = (byte)(col & 0x000000FF); buf[i * 4 + 1] = (byte)((col & 0x0000FF00) >> 8); buf[i * 4 + 2] = (byte)((col & 0x00FF0000) >> 16); buf[i * 4 + 3] = (byte)((col & 0xFF000000) >> 24); } ++i; } byte[] hash = provider.ComputeHash(buf); StringBuilder builder = new StringBuilder(); foreach (byte b in hash) builder.AppendFormat("{0:x2}", b); return builder.ToString(); /* Log(" MD5: {0}", builder.ToString()); Log(" Pixels: {0}", i); */ }
private void ColorKey(SourceImage srcImg) { // Bail if we're not colorkeying at all if (Settings.ColorKeyMode == ColorKeyMode.Disabled) return; int colorKey; // Figure out color key if (Settings.ColorKeyMode == ColorKeyMode.Automatic) { colorKey = DetectColorKey(srcImg); } else // specific { colorKey = Settings.ColorKeyColor.ToArgb(); } if ((colorKey & 0xFF000000) == 0) return; // Replace pixels matching colorkey w/ transparent // (NOTE: This is probably not fast) for (int y = 0; y < srcImg.Height; ++y) for (int x = 0; x < srcImg.Width; ++x) { int c = srcImg.Data[x + srcImg.Stride*y]; if (c == colorKey) srcImg.Data[x + srcImg.Stride * y] = 0; } }