private ConsoleBitmapDiffFrame PrepareDiffFrame(ConsoleBitmap bitmap) { ConsoleBitmapDiffFrame diff = new ConsoleBitmapDiffFrame(); diff.Diffs = new List <ConsoleBitmapPixelDiff>(); int changes = 0; for (int y = 0; y < bitmap.Height; y++) { for (int x = 0; x < bitmap.Width; x++) { var pixel = bitmap.GetPixel(x, y); if (pixel.HasChanged) { changes++; if (pixel.Value.HasValue) { diff.Diffs.Add(new ConsoleBitmapPixelDiff() { X = x, Y = y, Value = pixel.Value.Value }); } } } } return(diff); }
private ConsoleBitmapDiffFrame PrepareDiffFrame(ConsoleBitmapRawFrame previous, ConsoleBitmap bitmap) { ConsoleBitmapDiffFrame diff = new ConsoleBitmapDiffFrame(); diff.Diffs = new List <ConsoleBitmapPixelDiff>(); int changes = 0; for (int y = 0; y < GetEffectiveHeight(bitmap); y++) { for (int x = 0; x < GetEffectiveWidth(bitmap); x++) { var pixel = bitmap.GetPixel(GetEffectiveLeft + x, GetEffectiveTop + y); var hasPreviousPixel = previous.Pixels.Length == GetEffectiveWidth(bitmap) && previous.Pixels[0].Length == GetEffectiveHeight(bitmap); var previousPixel = hasPreviousPixel ? previous.Pixels[x][y] : default(ConsoleCharacter); if (pixel.HasChanged || hasPreviousPixel == false || (pixel.Value.HasValue && pixel.Value.Value.Equals(previousPixel) == false)) { changes++; if (pixel.Value.HasValue) { diff.Diffs.Add(new ConsoleBitmapPixelDiff() { X = x, Y = y, Value = pixel.Value.Value }); } } } } return(diff); }
/// <summary> /// Serializes the given diff frame. /// /// A serialized diff frame is always a single line with this structure: /// /// All data values are surrounded in square brackets like [dataValue] /// /// Segment1 - Timestamp in the format: [$timestampInTicks$] where $timestampInTicks$ represents a 64 bit non-negative integer /// Segment2 - The type of frame, in this case [Diff] /// Segment3 - The diff data /// /// The first pixel will be preceded by color markers for foreground (e.g. [F=Red]) and background (e.g. [B=Red]) which means that subsequence characters have those color characteristics. /// If the next pixel is a different foreground and/or background color then there will be color markers for those changes in between the pixel data values /// If the next pixel shares the same foreground and background then there will be no color markers in between those pixels. This saves space. /// Diff pixels are surrounded in square brackets in this format: [xCoordinate,yCoordinate,pixelValue]. /// pixelValue is generally a single character, but square brackets are encoded a OB for the opening bracket and CB for the closing bracket /// /// </summary> /// <param name="frame">a raw frame</param> /// <returns>a serialized string</returns> public string SerializeFrame(ConsoleBitmapDiffFrame frame) { StringBuilder builder = new StringBuilder(); builder.Append($"[{frame.Timestamp.Ticks}]"); builder.Append("[Diff]"); ConsoleColor?lastFg = null; ConsoleColor?lastBg = null; foreach (var diff in frame.Diffs) { if (lastFg.HasValue == false || lastFg.Value != diff.Value.ForegroundColor) { lastFg = diff.Value.ForegroundColor; builder.Append($"[F={lastFg}]"); } if (lastBg.HasValue == false || lastBg.Value != diff.Value.BackgroundColor) { lastBg = diff.Value.BackgroundColor; builder.Append($"[B={lastBg}]"); } string appendValue; var pixelCharValue = diff.Value.Value; if (pixelCharValue == '[') { appendValue = "OB"; } else if (pixelCharValue == ']') { appendValue = "CB"; } else { appendValue = pixelCharValue + ""; } builder.Append($"[{diff.X},{diff.Y},{appendValue}]"); } builder.AppendLine(); var ret = builder.ToString(); return(ret); }
/// <summary> /// Deserializes the given frame given a known width and height. /// </summary> /// <param name="serializedFrame">the frame data</param> /// <param name="width">the known width of the frame</param> /// <param name="height">the known height of the frame</param> /// <returns>a deserialized frame that's either a raw frame or a diff frame, depending on what was in the serialized string</returns> public ConsoleBitmapFrame DeserializeFrame(string serializedFrame, int width, int height) { var tokens = tokenizer.Tokenize(serializedFrame); var reader = new TokenReader<Token>(tokens); reader.Expect("["); var timestampToken = reader.Advance(); var timestamp = new TimeSpan(long.Parse(timestampToken.Value)); reader.Expect("]"); reader.Expect("["); reader.Advance(); var isDiff = reader.Current.Value == "Diff"; reader.Expect("]"); if (isDiff) { var diffFrame = new ConsoleBitmapDiffFrame() { Timestamp = timestamp, Diffs = new System.Collections.Generic.List<ConsoleBitmapPixelDiff>() }; var lastBackground = ConsoleString.DefaultBackgroundColor; var lastForeground = ConsoleString.DefaultForegroundColor; while (reader.CanAdvance(skipWhitespace: true)) { reader.Expect("[", skipWhiteSpace: true); if (reader.Peek().Value.StartsWith("F=") || reader.Peek().Value.StartsWith("B=")) { reader.Advance(); var match = ColorSpecifierRegex.Match(reader.Current.Value); if (match.Success == false) throw new FormatException($"Unexpected token {reader.Current.Value} at position {reader.Current.Position} "); var isForeground = match.Groups["ForB"].Value == "F"; if (isForeground) { if (Enum.TryParse(match.Groups["color"].Value, out ConsoleColor c)) { lastForeground = (RGB)c; } else if (RGB.TryParse(match.Groups["color"].Value, out lastForeground) == false) { throw new ArgumentException($"Expected a color @ {reader.Position}"); } } else { if (Enum.TryParse(match.Groups["color"].Value, out ConsoleColor c)) { lastBackground = (RGB)c; } else if (RGB.TryParse(match.Groups["color"].Value, out lastBackground) == false) { throw new ArgumentException($"Expected a color @ {reader.Position}"); } } reader.Expect("]"); } else { var match = PixelDiffRegex.Match(reader.Advance().Value); if (match.Success == false) throw new FormatException("Could not parse pixel diff"); var valGroup = match.Groups["val"].Value; char? nextChar = valGroup.Length == 1 ? valGroup[0] : valGroup == "OB" ? '[' : valGroup == "CB" ? ']' : new char?(); if (nextChar.HasValue == false) throw new FormatException($"Unexpected token {nextChar} @ {reader.Position}"); diffFrame.Diffs.Add(new ConsoleBitmapPixelDiff() { X = int.Parse(match.Groups["x"].Value), Y = int.Parse(match.Groups["y"].Value), Value = new ConsoleCharacter(nextChar.Value, lastForeground, lastBackground), }); reader.Expect("]"); } } return diffFrame; } else { var rawFrame = new ConsoleBitmapRawFrame() { Timestamp = timestamp, Pixels = new ConsoleCharacter[width][] }; for (var i = 0; i < width; i++) { rawFrame.Pixels[i] = new ConsoleCharacter[height]; } var x = 0; var y = 0; var lastFg = ConsoleString.DefaultForegroundColor; var lastBg = ConsoleString.DefaultBackgroundColor; while (reader.CanAdvance(skipWhitespace:true)) { reader.Expect("[", skipWhiteSpace:true); var next = reader.Advance(); var match = ColorSpecifierRegex.Match(next.Value); if (match.Success) { var isForeground = match.Groups["ForB"].Value == "F"; if (isForeground) { if(Enum.TryParse<ConsoleColor>(match.Groups["color"].Value, out ConsoleColor c)) { lastFg = c; } else if(RGB.TryParse(match.Groups["color"].Value, out lastFg) == false) { throw new ArgumentException($"Expected a color @ {reader.Position}"); } } else { if (Enum.TryParse<ConsoleColor>(match.Groups["color"].Value, out ConsoleColor c)) { lastBg = c; } else if (RGB.TryParse(match.Groups["color"].Value, out lastBg) == false) { throw new ArgumentException($"Expected a color @ {reader.Position}"); } } } else { char? nextChar = next.Value.Length == 1 ? next.Value[0] : next.Value == "OB" ? '[' : next.Value == "CB" ? ']' : new char?(); if (nextChar.HasValue == false) throw new FormatException($"Unexpected token {nextChar} @ {next.Position}"); rawFrame.Pixels[x][y++] = new ConsoleCharacter(nextChar.Value, lastFg, lastBg); if (y == height) { y = 0; x++; } } reader.Expect("]"); } return rawFrame; } }