private static void LockToGrid(CanvasPixelPosition canvasOffset, [NotNull] TileCanvas target, ref double x, ref double y) { if (canvasOffset == null) { return; } var scale = 1.0 / target.CurrentZoom(); var grid = TileCanvas.GridSize * scale; var bias = (3 * grid) / 5.0; x = Math.Round((x + bias) / grid) * grid; x -= (canvasOffset.X * scale) % grid; y = Math.Round((y + bias) / grid) * grid; y -= (canvasOffset.Y * scale) % grid; }
/// <summary> /// Finish a pen stoke. It should be rendered into the tile store. /// </summary> public void CommitTo(TileCanvas tileCanvas) { if (tileCanvas == null) { return; } try { lock (_dryLock){ _dryingInk.Add(_stroke.ToArray()); _stroke.Clear(); // ready for next } // render and copy on a separate thread DryWaitingStroke(tileCanvas); } catch (Exception ex) { Logging.WriteLogMessage(ex.ToString()); } }
private void DryWaitingStroke([NotNull] TileCanvas tileCanvas) { // Try to get a waiting stroke (peek, so we can draw the waiting stroke) DPoint[][] waitingStrokes; lock (_dryLock) { waitingStrokes = _dryingInk.ToArray(); _dryingInk.Clear(); if (waitingStrokes != null) { _renderingInk.UnionWith(waitingStrokes); } } tileCanvas.Invalidate(); // show progress if the render is slow. _renderTarget.Invalidate(); if (waitingStrokes == null) { return; } foreach (var strokeToRender in waitingStrokes) { if (strokeToRender.Length < 1) { continue; } // Figure out what part of the screen is covered var clipRegion = MeasureDrawing(strokeToRender, tileCanvas.CurrentZoom()); var pixelWidth = (int)clipRegion.Width; var pixelHeight = (int)clipRegion.Height; // draw to an image byte[] bytes; using (var offscreen = new CanvasRenderTarget(CanvasDevice.GetSharedDevice(), pixelWidth, pixelHeight, 96, DirectXPixelFormat.B8G8R8A8UIntNormalized, CanvasAlphaMode.Premultiplied)) { using (var ds = offscreen.CreateDrawingSession()) { ds?.Clear(Colors.Transparent); DrawToSession(ds, strokeToRender, clipRegion, tileCanvas.CurrentZoom()); } bytes = offscreen.GetPixelBytes(); } // render into tile cache var uncropped = new RawImageInterleaved_UInt8 { Data = bytes, Width = pixelWidth, Height = pixelHeight }; var visualWidth = (int)Math.Ceiling(pixelWidth / tileCanvas.CurrentZoom()); var visualHeight = (int)Math.Ceiling(pixelHeight / tileCanvas.CurrentZoom()); var visualTop = (int)Math.Floor(clipRegion.Y + 0.5); var visualLeft = (int)Math.Floor(clipRegion.X + 0.5); var visualRight = visualLeft + visualWidth; var visualBottom = visualTop + visualHeight; ThreadPool.QueueUserWorkItem(canv => { var ok = tileCanvas.ImportBytesScaled(uncropped, visualLeft, visualTop, visualRight, visualBottom); if (!ok) { Logging.WriteLogMessage("Tile byte import failed when drawing strokes"); } lock (_dryLock) { _renderingInk.Remove(strokeToRender); } tileCanvas.Invalidate(); // show finished strokes _renderTarget.Invalidate(); }); } }
/// <summary> /// Continue a pen stroke /// </summary> public void Stroke(PointerEventArgs penEvent, CanvasPixelPosition canvasOffset, [NotNull] TileCanvas target) { try { if (penEvent?.CurrentPoint?.Properties == null) { return; } var x = penEvent.CurrentPoint.Position.X; var y = penEvent.CurrentPoint.Position.Y; var isEraser = penEvent.CurrentPoint.Properties.IsEraser || penEvent.CurrentPoint.Properties.IsRightButtonPressed; // treat right-click as erase if (penEvent.KeyModifiers.HasFlag(VirtualKeyModifiers.Control)) { // hacky straight line tool: if (_stroke.Count > 1) { _stroke.RemoveAt(_stroke.Count - 1); // replace last point instead of adding a new one } } if (penEvent.KeyModifiers.HasFlag(VirtualKeyModifiers.Menu))// this is actually *ALT* { LockToGrid(canvasOffset, target, ref x, ref y); } _stroke.Add(new DPoint { X = x, Y = y, StylusId = GuessPointerId(penEvent.CurrentPoint), Pressure = penEvent.CurrentPoint.Properties.Pressure, IsErase = isEraser }); _renderTarget.Invalidate(); } catch (Exception ex) { Logging.WriteLogMessage(ex.ToString()); } }