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 (hasPreviousPixel == false || (pixel.EqualsIn(previousPixel) == false)) { changes++; diff.Diffs.Add(new ConsoleBitmapPixelDiff() { X = x, Y = y, Value = pixel }); } } } return(diff); }
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); rawFrame.Pixels[x][y] = pixel; } } 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> /// <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 (isFinished) { throw new NotSupportedException("Already finished"); } 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); Append(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) { var frame = serializer.SerializeFrame(rawFrame); //checking to make sure we can deserialize what we just wrote so that if we can't // we still have time to debug. I'd love to get rid of this check for perf, but // there have been some cases where I wasn't able to read back what was written and if // that edge case creeps up I want to catch it early. var deserialized = serializer.DeserializeFrame(frame, bitmap.Width, bitmap.Height); var frameBack = serializer.SerializeFrame((ConsoleBitmapRawFrame)deserialized); if (frameBack.Equals(frame) == false) { throw new Exception("Serialization failure"); } if (frame.EndsWith("\n") == false) { throw new Exception(); } Append(frame); FramesWritten++; } else if (diff.Diffs.Count > 0) { Append(serializer.SerializeFrame(diff)); FramesWritten++; } } lastFrame = rawFrame; return(bitmap); }