Example #1
0
 public HdrColor Pow(HdrColor exponent)
 {
     return(new HdrColor
     {
         R = Math.Pow(R, exponent.R),
         G = Math.Pow(G, exponent.G),
         B = Math.Pow(B, exponent.B)
     });
 }
        private static PaletteEntry GetClosestPaletteEntry(PaletteEntry[] palette, HdrColor color)
        {
            PaletteEntry closestEntry    = null;
            var          closestDistance = double.PositiveInfinity;

            for (var palleteIndex = 0; palleteIndex < palette.Length; palleteIndex++)
            {
                var currentEntry    = palette[palleteIndex];
                var currentDistance = (currentEntry.Color - color).Length;

                if (currentDistance < closestDistance)
                {
                    closestEntry    = currentEntry;
                    closestDistance = currentDistance;
                }
            }

            return(closestEntry);
        }
        private static PaletteEntry[] GeneratePalette(HdrColor[] subPixelColors)
        {
            var palette = new PaletteEntry[1 << subPixelColors.Length];

            for (var paletteIndex = 0; paletteIndex < palette.Length; paletteIndex++)
            {
                var currentColor = new HdrColor();
                var outputColor  = new bool[subPixelColors.Length];

                for (var subPixelIndex = 0; subPixelIndex < subPixelColors.Length; subPixelIndex++)
                {
                    if (((paletteIndex >> subPixelIndex) & 1) == 1)
                    {
                        currentColor += subPixelColors[subPixelIndex];
                        outputColor[subPixelIndex] = true;
                    }
                }

                palette[paletteIndex] = new PaletteEntry(currentColor, outputColor);
            }

            return(palette);
        }
 private record PaletteEntry(HdrColor Color, bool[] OutputColor);
        public static void Run(VideoConfiguration configuration)
        {
            var videoFile           = configuration.VideoFile;
            var outputBlueprintFile = configuration.OutputBlueprint;
            var outputJsonFile      = configuration.OutputJson;
            var frameWidth          = configuration.FrameWidth ?? 32;
            var frameHeight         = configuration.FrameHeight ?? 32;
            var colorMode           = configuration.ColorMode ?? ColorMode.Monochrome;
            var ditheringMode       = configuration.DitheringMode ?? DitheringMode.None;
            var romHeight           = configuration.RomHeight ?? 2;

            var maxFrames = romHeight * 32;

            var videoBuffer = new MemoryStream();

            using (var videoStream = File.OpenRead(videoFile))
            {
                videoStream.CopyTo(videoBuffer);
            }

            var pixelSize = colorMode switch
            {
                ColorMode.Monochrome => 1,
                ColorMode.RedGreenBlue => 2,
                ColorMode.RedGreenBlueWhite => 2,
                _ => throw new Exception($"Unexpected color mode: {colorMode}")
            };

            var palette = colorMode switch
            {
                ColorMode.Monochrome => GeneratePalette(new HdrColor[]
                {
                    HdrColor.FromRgb(1, 1, 1)
                }),
                ColorMode.RedGreenBlue => GeneratePalette(new HdrColor[]
                {
                    HdrColor.FromRgb(1, 0, 0),
                    HdrColor.FromRgb(0, 1, 0),
                    HdrColor.FromRgb(0, 0, 1)
                }),
                ColorMode.RedGreenBlueWhite => GeneratePalette(new HdrColor[]
                {
                    HdrColor.FromRgb(0.8, 0, 0),
                    HdrColor.FromRgb(0, 0.8, 0),
                    HdrColor.FromRgb(0, 0, 0.8),
                    HdrColor.FromRgb(0.8, 0.8, 0.8)
                }),
                _ => throw new Exception($"Unexpected color mode: {colorMode}")
            };

            // Information on dithering: https://cmitja.files.wordpress.com/2015/01/hellandtanner_imagedithering11algorithms.pdf
            var ditheringWeights = ditheringMode switch
            {
                DitheringMode.None => Array.Empty <DitheringWeight>(),
                DitheringMode.Sierra => new DitheringWeight[]
                {
                    new DitheringWeight(5 / 32d, 1, 0),
                    new DitheringWeight(3 / 32d, 2, 0),
                    new DitheringWeight(2 / 32d, -2, 1),
                    new DitheringWeight(4 / 32d, -1, 1),
                    new DitheringWeight(5 / 32d, 0, 1),
                    new DitheringWeight(4 / 32d, 1, 1),
                    new DitheringWeight(2 / 32d, 2, 1),
                    new DitheringWeight(2 / 32d, -1, 2),
                    new DitheringWeight(3 / 32d, 0, 2),
                    new DitheringWeight(2 / 32d, 1, 2),
                },
                DitheringMode.SierraLite => new DitheringWeight[]
                {
                    new DitheringWeight(2 / 4d, 1, 0),
                    new DitheringWeight(1 / 4d, -1, 1),
                    new DitheringWeight(1 / 4d, 0, 1)
                },
                DitheringMode.Temporal => new DitheringWeight[]
                {
                    new DitheringWeight(2 / 6d, 1, 0, 0),
                    new DitheringWeight(1 / 6d, -1, 1, 0),
                    new DitheringWeight(1 / 6d, 0, 1, 0),
                    new DitheringWeight(2 / 6d, 0, 0, 1)
                },
                _ => throw new Exception($"Unexpected dithering mode: {ditheringMode}")
            };

            var rawFrameWidth  = frameWidth / pixelSize;
            var rawFrameHeight = frameHeight / pixelSize;

            var video    = new Video(videoBuffer, rawFrameWidth, rawFrameHeight);
            var rawFrame = new int[rawFrameWidth * rawFrameHeight];
            var frames   = new List <bool[, ]>();

            var ditheringFrames = ditheringWeights.Max(ditheringWeight => ditheringWeight.Frame) + 1;
            var colorErrors     = new HdrColor[ditheringFrames][, ];

            for (var index = 0; index < ditheringFrames; index++)
            {
                colorErrors[index] = new HdrColor[frameHeight, frameWidth];
            }

            while (video.AdvanceFrame(rawFrame) && frames.Count < maxFrames)
            {
                var frame = new bool[frameHeight, frameWidth];
                frames.Add(frame);

                var totalBrightness = 0d;

                for (var rawY = 0; rawY < rawFrameHeight; rawY++)
                {
                    for (var rawX = 0; rawX < rawFrameWidth; rawX++)
                    {
                        var color = System.Drawing.Color.FromArgb(rawFrame[rawX + rawY * rawFrameWidth]);
                        totalBrightness += color.GetBrightness();
                    }
                }

                var averageBrightness = totalBrightness / (rawFrameWidth * rawFrameHeight);

                const double gammaMultiplier = 2;
                var          gamma           = gammaMultiplier * (averageBrightness is > 0.1 and < 0.9 ? Math.Log(0.5, averageBrightness) : 1);

                for (var rawY = 0; rawY < rawFrameHeight; rawY++)
                {
                    for (var rawX = 0; rawX < rawFrameWidth; rawX++)
                    {
                        var x                   = rawX * pixelSize;
                        var y                   = rawY * pixelSize;
                        var rawColor            = HdrColor.FromArgb(rawFrame[rawX + rawY * rawFrameWidth]);
                        var gammaCorrectedColor = rawColor.Pow(gamma);
                        var ditheredColor       = gammaCorrectedColor + colorErrors[0][rawY, rawX];
                        var closestPaletteEntry = GetClosestPaletteEntry(palette, ditheredColor);
                        var newColorError       = ditheredColor - closestPaletteEntry.Color;
                        var outputColor         = closestPaletteEntry.OutputColor;

                        for (var subPixelY = 0; subPixelY < pixelSize; subPixelY++)
                        {
                            for (var subPixelX = 0; subPixelX < pixelSize; subPixelX++)
                            {
                                var subPixelIndex = subPixelX + subPixelY * pixelSize;

                                if (subPixelIndex < outputColor.Length)
                                {
                                    frame[y + subPixelY, x + subPixelX] = outputColor[subPixelIndex];
                                }
                            }
                        }

                        for (var index = 0; index < ditheringWeights.Length; index++)
                        {
                            var ditheringWeight = ditheringWeights[index];
                            var errorX          = rawX + ditheringWeight.X;
                            var errorY          = rawY + ditheringWeight.Y;

                            if (errorX >= 0 && errorX < frameWidth && errorY < frameHeight)
                            {
                                colorErrors[ditheringWeight.Frame][errorY, errorX] += newColorError * ditheringWeight.Weight;
                            }
                        }
                    }
                }

                for (var index = 0; index < ditheringFrames - 1; index++)
                {
                    colorErrors[index] = colorErrors[index + 1];
                }

                colorErrors[ditheringFrames - 1] = new HdrColor[frameHeight, frameWidth];

                Console.Write('.');
            }

            Console.WriteLine($"Frames: {frames.Count}");

            var blueprint = VideoRomGenerator.Generate(new VideoMemoryConfiguration
            {
                SnapToGrid  = configuration.SnapToGrid,
                X           = configuration.X,
                Y           = configuration.Y,
                Width       = frameWidth,
                Height      = romHeight,
                BaseAddress = configuration.BaseAddress
            }, frames);

            BlueprintUtil.PopulateIndices(blueprint);

            var blueprintWrapper = new BlueprintWrapper {
                Blueprint = blueprint
            };

            BlueprintUtil.WriteOutBlueprint(outputBlueprintFile, blueprintWrapper);
            BlueprintUtil.WriteOutJson(outputJsonFile, blueprintWrapper);
        }