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); }