Beispiel #1
0
        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);
        }
Beispiel #2
0
        /// <summary>
        /// Writes the given bitmap image as a frame to the stream.  If this is the first image or more than half of the pixels have
        /// changed then a raw frame will be written.   Otherwise, a diff frame will be written.
        ///
        /// This method uses the system's wall clock to determine the timestamp for this frame. The timestamp will be
        /// relative to the wall clock time when the first frame was written.
        /// </summary>
        /// <param name="bitmap">the image to write</param>
        /// <param name="desiredFrameTime">if provided, sstamp the frame with this time, otherwise stamp it with the wall clock delta from the first frame time</param>
        /// <param name="force">if true, writes the frame even if there are no changes</param>
        /// <returns>the same bitmap that was passed in</returns>
        public ConsoleBitmap WriteFrame(ConsoleBitmap bitmap, bool force = false, TimeSpan?desiredFrameTime = null)
        {
            if (pausedAt.HasValue)
            {
                return(bitmap);
            }

            var rawFrame = GetRawFrame(bitmap);

            var now = DateTime.UtcNow - TotalPauseTime;

            if (firstFrameTime.HasValue == false)
            {
                rawFrame.Timestamp = TimeSpan.Zero;
                firstFrameTime     = now;
            }
            else
            {
                rawFrame.Timestamp = desiredFrameTime.HasValue ? desiredFrameTime.Value : now - firstFrameTime.Value;
            }

            if (lastFrame == null)
            {
                StreamHeader(bitmap);
                writer.Write(serializer.SerializeFrame(rawFrame));
                FramesWritten++;
            }
            else
            {
                if (GetEffectiveWidth(bitmap) != lastFrame.Pixels.Length || GetEffectiveHeight(bitmap) != lastFrame.Pixels[0].Length)
                {
                    throw new InvalidOperationException("Video frame has changed size");
                }

                var diff = PrepareDiffFrame(lastFrame, bitmap);
                diff.Timestamp = rawFrame.Timestamp;

                var numPixels = GetEffectiveWidth(bitmap) * GetEffectiveHeight(bitmap);
                if (force || diff.Diffs.Count > numPixels / 2)
                {
                    writer.Write(serializer.SerializeFrame(rawFrame));
                    FramesWritten++;
                }
                else if (diff.Diffs.Count > 0)
                {
                    writer.Write(serializer.SerializeFrame(diff));
                    FramesWritten++;
                }
            }

            lastFrame = rawFrame;
            return(bitmap);
        }
Beispiel #3
0
        /// <summary>
        /// Serializes the given raw frame.
        ///
        /// A serialized raw 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 [Raw]
        /// Segment3 - The raw bitmap 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.
        ///     Each pixel value is surrounded by square brackets like [A] if the pixel value was A.
        ///     Each pixel value is generally a single character, but square brackets are encoded a OB for the opening bracket and CB for the closing bracket
        ///     The pixels are ordered vertically starting at x = 0, y = 0.
        ///     There are no markers for the end of a vertical scan line since you're assumed to know the size from the header.
        /// </summary>
        /// <param name="frame">a raw frame</param>
        /// <returns>a serialized string</returns>
        public string SerializeFrame(ConsoleBitmapRawFrame frame)
        {
            StringBuilder builder = new StringBuilder();

            builder.Append($"[{frame.Timestamp.Ticks}]");
            builder.Append("[Raw]");
            ConsoleColor?lastFg = null;
            ConsoleColor?lastBg = null;

            for (var x = 0; x < frame.Pixels.Length; x++)
            {
                for (var y = 0; y < frame.Pixels[0].Length; y++)
                {
                    if (lastFg.HasValue == false || lastFg.Value != frame.Pixels[x][y].ForegroundColor)
                    {
                        lastFg = frame.Pixels[x][y].ForegroundColor;
                        builder.Append($"[F={lastFg}]");
                    }

                    if (lastBg.HasValue == false || lastBg.Value != frame.Pixels[x][y].BackgroundColor)
                    {
                        lastBg = frame.Pixels[x][y].BackgroundColor;
                        builder.Append($"[B={lastBg}]");
                    }

                    string appendValue;
                    var    pixelCharValue = frame.Pixels[x][y].Value;
                    if (pixelCharValue == '[')
                    {
                        appendValue = "OB";
                    }
                    else if (pixelCharValue == ']')
                    {
                        appendValue = "CB";
                    }
                    else
                    {
                        appendValue = pixelCharValue + "";
                    }


                    builder.Append('[' + appendValue + ']');
                }
            }
            builder.AppendLine();

            var ret = builder.ToString();

            return(ret);
        }
        private ConsoleBitmapRawFrame GetRawFrame(ConsoleBitmap bitmap)
        {
            var rawFrame = new ConsoleBitmapRawFrame();

            rawFrame.Pixels = new ConsoleCharacter[bitmap.Width][];
            for (int x = 0; x < bitmap.Width; x++)
            {
                rawFrame.Pixels[x] = new ConsoleCharacter[bitmap.Height];
                for (int y = 0; y < bitmap.Height; y++)
                {
                    var pixelValue = bitmap.GetPixel(x, y).Value.HasValue ? bitmap.GetPixel(x, y).Value.Value : new ConsoleCharacter(' ');
                    rawFrame.Pixels[x][y] = pixelValue;
                }
            }
            return(rawFrame);
        }
Beispiel #5
0
        private ConsoleBitmapRawFrame GetRawFrame(ConsoleBitmap bitmap)
        {
            var rawFrame = new ConsoleBitmapRawFrame();

            rawFrame.Pixels = new ConsoleCharacter[GetEffectiveWidth(bitmap)][];
            for (int x = 0; x < GetEffectiveWidth(bitmap); x++)
            {
                rawFrame.Pixels[x] = new ConsoleCharacter[GetEffectiveHeight(bitmap)];
                for (int y = 0; y < GetEffectiveHeight(bitmap); y++)
                {
                    var pixel      = bitmap.GetPixel(GetEffectiveLeft + x, GetEffectiveTop + y);
                    var pixelValue = pixel.Value.HasValue ? pixel.Value.Value : defaultChar;
                    rawFrame.Pixels[x][y] = pixelValue;
                }
            }
            return(rawFrame);
        }
        /// <summary>
        /// Writes the given bitmap image as a frame to the stream.  If this is the first image or more than half of the pixels have
        /// changed then a raw frame will be written.   Otherwise, a diff frame will be written.
        ///
        /// This method uses the system's wall clock to determine the timestamp for this frame. The timestamp will be
        /// relative to the wall clock time when the first frame was written.
        /// </summary>
        /// <param name="bitmap">the image to write</param>
        /// <returns>the same bitmap that was passed in</returns>
        public ConsoleBitmap WriteFrame(ConsoleBitmap bitmap)
        {
            var rawFrame = GetRawFrame(bitmap);

            var now = DateTime.UtcNow;

            if (firstFrameTime.HasValue == false)
            {
                rawFrame.Timestamp = TimeSpan.Zero;
                firstFrameTime     = now;
            }
            else
            {
                rawFrame.Timestamp = now - firstFrameTime.Value;
            }

            var frameTime = firstFrameTime.HasValue == false ? TimeSpan.Zero : now - firstFrameTime.Value;

            if (lastFrame == null)
            {
                rawFrame.Timestamp = frameTime;
                StreamHeader(bitmap);
                writer.Write(serializer.SerializeFrame(rawFrame));
            }
            else
            {
                var diff = PrepareDiffFrame(bitmap);
                diff.Timestamp = frameTime;
                if (diff.Diffs.Count > bitmap.Width * bitmap.Height / 2)
                {
                    writer.Write(serializer.SerializeFrame(rawFrame));
                }
                else if (diff.Diffs.Count > 0)
                {
                    writer.Write(serializer.SerializeFrame(diff));
                }
            }

            lastFrame = rawFrame;
            return(bitmap);
        }
Beispiel #7
0
        /// <summary>
        /// Writes the given bitmap image as a frame to the stream.  If this is the first image or more than half of the pixels have
        /// changed then a raw frame will be written.   Otherwise, a diff frame will be written.
        ///
        /// This method uses the system's wall clock to determine the timestamp for this frame. The timestamp will be
        /// relative to the wall clock time when the first frame was written.
        /// </summary>
        /// <param name="bitmap">the image to write</param>
        /// <param name="desiredFrameTime">if provided, sstamp the frame with this time, otherwise stamp it with the wall clock delta from the first frame time</param>
        /// <param name="force">if true, writes the frame even if there are no changes</param>
        /// <returns>the same bitmap that was passed in</returns>
        public ConsoleBitmap WriteFrame(ConsoleBitmap bitmap, bool force = false, TimeSpan?desiredFrameTime = null)
        {
            var rawFrame = GetRawFrame(bitmap);

            var now = DateTime.UtcNow;

            if (firstFrameTime.HasValue == false)
            {
                rawFrame.Timestamp = TimeSpan.Zero;
                firstFrameTime     = now;
            }
            else
            {
                rawFrame.Timestamp = desiredFrameTime.HasValue ? desiredFrameTime.Value : now - firstFrameTime.Value;
            }

            if (lastFrame == null)
            {
                StreamHeader(bitmap);
                writer.Write(serializer.SerializeFrame(rawFrame));
                FramesWritten++;
            }
            else
            {
                var diff = PrepareDiffFrame(bitmap);
                diff.Timestamp = rawFrame.Timestamp;
                if (force || diff.Diffs.Count > bitmap.Width * bitmap.Height / 2)
                {
                    writer.Write(serializer.SerializeFrame(rawFrame));
                    FramesWritten++;
                }
                else if (diff.Diffs.Count > 0)
                {
                    writer.Write(serializer.SerializeFrame(diff));
                    FramesWritten++;
                }
            }

            lastFrame = rawFrame;
            return(bitmap);
        }
Beispiel #8
0
        /// <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;
            }
        }