Ejemplo n.º 1
0
        // Converts the specified image into an AllRGB version by looping
        // randomly through the source pixels and choosing the 'nearest'
        // remaining color to map it to. The number of color bits per channel
        // used dictates the size of the output image ([image]_allRGBv2.png).
        // Uses the specified color space for coordinate locations.
        static void allRGBify(string path, string maskPath, int bitsPerChannel, ColorSpace cs)
        {
            if ((bitsPerChannel & 1) != 0) {
                Console.Out.WriteLine("bitsPerChannel must be divisible by 2");
                return;
            } else if (bitsPerChannel > 8) {
                Console.Out.WriteLine("Only up to 8 bits per channel are supported");
                return;
            }

            DateTime startTime = DateTime.Now;

            // imageSize = 2^(3*bitsPerChannel/2)
            int imageSize = 1 << (3 * bitsPerChannel >> 1);
            int numPixels = imageSize * imageSize;

            Bitmap bitmap = new Bitmap(Image.FromFile(path), imageSize, imageSize);
            BitmapData bitmapData = bitmap.LockBits(
                new Rectangle(0, 0, imageSize, imageSize),
                ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

            // Load the raw pixel data, BGRBGRBGR..... Note that it may contain
            // padding at the end of rows; need to be very careful when indexing!
            int pixelBytes = bitmapData.Stride * bitmapData.Height;
            Debug.Assert(pixelBytes >= numPixels * 3);
            byte[] pixels = new byte[pixelBytes];
            System.Runtime.InteropServices.Marshal.Copy(
                bitmapData.Scan0, pixels, 0, pixelBytes);

            // If available, load the pixel mask as well
            // True is high priority, false is low.
            bool[,] maskFlags = new bool[imageSize, imageSize];
            if (maskPath != null && maskPath != "") {

                Bitmap mask = new Bitmap(Image.FromFile(maskPath), imageSize, imageSize);

                for (int y = 0; y < imageSize; ++y) {
                    for (int x = 0; x < imageSize; ++x) {
                        if (mask.GetPixel(x, y).R == 0) {
                            maskFlags[y, x] = true;
                        }
                    }
                }
            }

            // Set up error diffusion, if it is turned on. At the same time,
            // calculate the average color of the image for the initial error
            // term.
            float[, ,] pixelError = null;   // To be added to the corresponding
            // pixel's color to get the target
            // color for lookup.
            // Note: Order is y,x,{R,G,B}.
            bool[,] donePixels = null;
            if (USE_ERROR_DIFFUSION) {
                donePixels = new bool[imageSize, imageSize];
                pixelError = new float[imageSize, imageSize, 3];

                initializeErrorDiffusion(pixelError, pixels, bitmapData.Stride, imageSize);
            }

            // A random ordering of the pixels to process.
            Pair<int, int>[] coords = getRandomPixelOrdering(maskFlags, imageSize, imageSize);

            // A KDTree of all the candidate colors the pixels can be mapped to.
            // We periodically need to rebuild this tree to keep it from getting
            // unbalanced.
            KDTree kd = buildKDTreeOfColors(bitsPerChannel, cs);
            int pixelsTillRebuild = coords.Length * 1 / 4;
            pixelsTillRebuild = Math.Min(pixelsTillRebuild, 1 << 19);

            // Actually look up the colours, writing back to the pixel array as
            // we go.
            for (int i = 0; i < coords.Length; ++i) {
                if ((i & ((1 << 7) - 1)) == 0) {
                    Console.Out.WriteLine("Done " + i + " pixels");

                    // If 'r' is caught, rebuild spatial index immediately.
                    if (Console.KeyAvailable) {
                        ConsoleKeyInfo key = Console.ReadKey(true);
                        switch (key.KeyChar) {
                            case 'r':
                                pixelsTillRebuild = 0;
                                break;
                            default:
                                break;
                        }
                    }
                }

                if (pixelsTillRebuild == 0) {
                    rebuildKDTree(ref kd);
                    pixelsTillRebuild = Math.Max((coords.Length - i) * 1 / 4, 1000);
                    pixelsTillRebuild = Math.Min(pixelsTillRebuild, 1 << 19);
                }
                pixelsTillRebuild--;

                Pair<int, int> coord = coords[i];
                int x = coord.First;
                int y = coord.Second;
                int pixelIndex = y * bitmapData.Stride + 3 * x;
                byte oldR;
                byte oldG;
                byte oldb;
                if (USE_ERROR_DIFFUSION) {
                    oldR = capToByte(pixels[pixelIndex + 2] +
                        pixelError[y, x, 0]);
                    oldG = capToByte(pixels[pixelIndex + 1] +
                        pixelError[y, x, 1]);
                    oldb = capToByte(pixels[pixelIndex + 0] +
                        pixelError[y, x, 2]);
                } else {
                    oldR = pixels[pixelIndex + 2];
                    oldG = pixels[pixelIndex + 1];
                    oldb = pixels[pixelIndex + 0];
                }
                ColorLocation oldColor = new ColorLocation(oldR, oldG, oldb, cs);

                ColorLocation newColor = (ColorLocation)kd.nearest(oldColor.Location);
                kd.delete(newColor.Location);

                if (USE_ERROR_DIFFUSION) {
                    pixelError[y, x, 0] += pixels[pixelIndex + 2] - newColor.R;
                    pixelError[y, x, 1] += pixels[pixelIndex + 1] - newColor.G;
                    pixelError[y, x, 2] += pixels[pixelIndex + 0] - newColor.B;
                    markPixelDoneAndSpreadError(donePixels, pixelError, imageSize, x, y);
                }

                pixels[pixelIndex + 2] = newColor.R;
                pixels[pixelIndex + 1] = newColor.G;
                pixels[pixelIndex + 0] = newColor.B;
            }

            // Finally, load the pixels back into the bitmap and save out.
            System.Runtime.InteropServices.Marshal.Copy(pixels, 0, bitmapData.Scan0, pixelBytes);
            bitmap.UnlockBits(bitmapData);
            string newFileName =
                Path.GetDirectoryName(path) +
                Path.DirectorySeparatorChar +
                Path.GetFileNameWithoutExtension(path) +
                "_allRGBv2.png";
            bitmap.Save(newFileName, ImageFormat.Png);

            TimeSpan totalTime = DateTime.Now - startTime;
            Console.Out.WriteLine("Generating " + Path.GetFileName(newFileName) +
                                  " took " + totalTime.ToString());
        }
Ejemplo n.º 2
0
        // Build a KDTree of all the possible colors, indexed by location in the
        // chosen color space. When fewer than 8 bits per pixel are used, the
        // low order bits are skipped within each channel.
        static KDTree buildKDTreeOfColors(int bitsPerChannel, ColorSpace cs)
        {
            int colorsPerChannel = 1 << bitsPerChannel;
            int shift = 8 - bitsPerChannel;

            ColorLocation[] colors = new ColorLocation[colorsPerChannel * colorsPerChannel * colorsPerChannel];
            int colorIndex = 0;
            for (int r = 0; r < colorsPerChannel; ++r) {
                for (int g = 0; g < colorsPerChannel; ++g) {
                    for (int b = 0; b < colorsPerChannel; ++b) {
                        ColorLocation c = new ColorLocation((byte)(r << shift),
                                              (byte)(g << shift),
                                              (byte)(b << shift),
                                              cs);
                        colors[colorIndex] = c;
                        colorIndex++;
                    }
                }
            }
            colors.Shuffle();
            KDTree kd = new KDTree();
            for (int i = 0; i < colors.Length; ++i) {
                ColorLocation c = colors[i];
                kd.insert(c.Location, c);
            }
            colors = null;
            return kd;
        }