Exemplo n.º 1
0
        private async void ImportButton_Click(object sender, RoutedEventArgs e)
        {
            if (_canvas == null)
            {
                return;
            }

            //######################################################
            // These measurements are coming out all over the place.
            // Needs a more-or-less complete re-working. The wet ink
            // is functioning much better -- try using that?
            //######################################################


            // scale target based on canvas zoom
            var zoom           = _canvas.CurrentZoom();
            int expectedWidth  = (int)(ActualWidth * zoom);
            int expectedHeight = (int)(ActualHeight * zoom);

            // render to image
            var rtb = new RenderTargetBitmap();
            await rtb.RenderAsync(ImageToImport, expectedWidth, expectedHeight).NotNull(); // Render control to RenderTargetBitmap

            // DPI mismatch -- use first attempt to calculate the real scale and render again.
            var scaleW = expectedWidth / (float)rtb.PixelWidth;
            var scaleH = expectedHeight / (float)rtb.PixelHeight;

            // render to image
            rtb = new RenderTargetBitmap();
            await rtb.RenderAsync(ImageToImport, (int)(expectedWidth * scaleW), (int)(expectedHeight * scaleH)).NotNull(); // Render control to RenderTargetBitmap

            // get pixels from RTB
            var pixelBuffer = await rtb.GetPixelsAsync().NotNull(); // BGRA 8888 format

            var rawImage = new RawImageInterleaved_UInt8 {
                Data   = pixelBuffer.ToArray(),
                Height = rtb.PixelHeight,
                Width  = rtb.PixelWidth
            };

            // render to canvas
            int left   = (int)(Margin.Left);
            int top    = (int)(Margin.Top);
            int right  = left + (int)Width;
            int bottom = top + (int)Height;

            _canvas.ImportBytesScaled(rawImage, left, top, right, bottom);

            // ReSharper disable RedundantAssignment
            pixelBuffer = null;
            rtb         = null;
            GC.Collect();
            // ReSharper restore RedundantAssignment

            // make ourself invisible
            Visibility = Visibility.Collapsed;
        }
Exemplo n.º 2
0
        private async void ImportButton_Click(object sender, RoutedEventArgs e)
        {
            if (_canvas == null || textBlockToRender == null)
            {
                return;
            }

            var margin = textBlockToRender.Margin.Top;

            // scale target based on canvas zoom
            var zoom           = _canvas.CurrentZoom();
            int expectedWidth  = (int)(ActualWidth * zoom);
            int expectedHeight = (int)((ActualHeight - margin) * zoom);

            // render to image
            var rtb = new RenderTargetBitmap();
            await rtb.RenderAsync(textBlockToRender, expectedWidth, expectedHeight).NotNull(); // Render control to RenderTargetBitmap

            // DPI mismatch -- use first attempt to calculate the real scale and render again.
            var scaleW = expectedWidth / (float)rtb.PixelWidth;
            var scaleH = expectedHeight / (float)rtb.PixelHeight;

            // render to image
            rtb = new RenderTargetBitmap();
            await rtb.RenderAsync(textBlockToRender, (int)(expectedWidth * scaleW), (int)(expectedHeight * scaleH)).NotNull(); // Render control to RenderTargetBitmap

            // get pixels from RTB
            var pixelBuffer = await rtb.GetPixelsAsync().NotNull(); // BGRA 8888 format

            var rawImage = new RawImageInterleaved_UInt8 {
                Data   = pixelBuffer.ToArray(),
                Height = rtb.PixelHeight,
                Width  = rtb.PixelWidth
            };

            // render to canvas
            int left   = (int)(Margin.Left);
            int top    = (int)(Margin.Top + textBlockToRender.Margin.Top);
            int right  = left + (int)Width;
            int bottom = top + (int)(Height - margin);

            _canvas.ImportBytesScaled(rawImage, left, top, right, bottom);

            // ReSharper disable RedundantAssignment
            pixelBuffer = null;
            rtb         = null;
            GC.Collect();
            // ReSharper restore RedundantAssignment

            // make ourself invisible
            Visibility = Visibility.Collapsed;
        }
Exemplo n.º 3
0
        private void AntiAliasRow([NotNull] RawImageInterleaved_UInt8 img, int img_yi, double scale_y, double imgyo, [NotNull] byte[] src, [NotNull] int[] src_aa)
        {
            // Measure AA
            var    rowbytes = img.Width * 4;
            var    yoff_f   = Range(0.0, (img_yi * scale_y) + imgyo, img.Height - 1);
            var    yoff     = (int)yoff_f;
            double y_frac   = yoff_f - yoff;
            var    imul     = (int)(255 * y_frac);
            var    mul      = 255 - imul;

            var end    = img.Width * 4;
            var src_yi = Range(0, yoff * rowbytes, src.Length - (rowbytes + 4 + end));

            // pre-calc AA row data from two source lines
            for (int x = 0; x < end; x += 4)
            {
                src_aa[x + 0] = ((src[src_yi + 0 + x] * mul) + (src[src_yi + 0 + x + rowbytes] * imul)) >> 8;
                src_aa[x + 1] = ((src[src_yi + 1 + x] * mul) + (src[src_yi + 1 + x + rowbytes] * imul)) >> 8;
                src_aa[x + 2] = ((src[src_yi + 2 + x] * mul) + (src[src_yi + 2 + x + rowbytes] * imul)) >> 8;
                src_aa[x + 3] = ((src[src_yi + 3 + x] * mul) + (src[src_yi + 3 + x + rowbytes] * imul)) >> 8;
            }
        }
Exemplo n.º 4
0
        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();
                });
            }
        }
Exemplo n.º 5
0
        /// <summary>
        /// Write an image onto a tile, with transparency.
        /// Returns true if any pixels were changed.
        /// `tileArea` is in tile space. `imageArea` is in image space.
        /// </summary>
        /// <remarks>The scaling here is only intended for small variations (between 0.6x and 1.4x). The
        /// Source image should be scaled if you are outside of this range</remarks>
        private bool AlphaMapImageToTileScaled(RawImageInterleaved_UInt8 img, ICachedTile tile, Quad imageArea, Quad tileArea)
        {
            // This needs to be improved
            var dst = tile?.GetTileData();
            var src = img?.Data;

            if (src == null || dst == null || imageArea == null || tileArea == null)
            {
                return(false);
            }
            if (img.Width < 1 || img.Height < 1)
            {
                return(false);
            }

            bool changed = false;

            // start and end limits on tile
            int x0 = (int)Math.Max(tileArea.X, 0);
            int x1 = (int)Math.Min(tileArea.X + tileArea.Width, TileImageSize);
            int y0 = (int)Math.Max(tileArea.Y, 0);
            int y1 = (int)Math.Min(tileArea.Y + tileArea.Height, TileImageSize);

            int dst_width  = x1 - x0;
            int dst_height = y1 - y0;

            if (dst_width < 1 || dst_height < 1)
            {
                return(false);
            }

            double scale_x = imageArea.Width / dst_width;
            double scale_y = imageArea.Height / dst_height;

            var imgyo = imageArea.Y;
            var imgxo = imageArea.X;

            // AA caches
            int[] src_aa = new int[(img.Width + 1) * 4];

            for (int y = y0; y < y1; y++)
            {
                var img_yi = y - y0;

                // prepare a scaled row here
                AntiAliasRow(img, img_yi, scale_y, imgyo, src, src_aa);

                // copy image row
                for (int x = x0; x < x1; x++)
                {
                    var img_xi = x - x0;

                    // Measure AA
                    var    xoff_f = Range(0.0, (img_xi * scale_x) + imgxo, img.Width);
                    var    xoff   = (int)xoff_f;
                    double x_frac = xoff_f - xoff;
                    var    imul   = (int)(255 * x_frac);
                    var    mul    = 255 - imul;

                    var src_xi = xoff * 4;

                    var src_i  = Range(0, src_xi, src_aa.Length - 4);     // offset into source raw image
                    var src_i2 = Range(0, src_xi + 4, src_aa.Length - 4); // offset into source raw image
                    var dst_i  = y * (TileImageSize * 4) + (x * 4);       // offset into tile data
                    if (dst_i < 0)
                    {
                        continue;
                    }
                    if (dst_i >= dst.Length)
                    {
                        continue;
                    }

                    // Threshold alpha
                    if (src_aa[src_i + 3] < 2 && src_aa[src_i2 + 3] < 2)
                    {
                        continue;
                    }

                    // Take source samples and do AA
                    var srcB = ((src_aa[src_i + 0] * mul) + (src_aa[src_i2 + 0] * imul)) >> 8;
                    var srcG = ((src_aa[src_i + 1] * mul) + (src_aa[src_i2 + 1] * imul)) >> 8;
                    var srcR = ((src_aa[src_i + 2] * mul) + (src_aa[src_i2 + 2] * imul)) >> 8;
                    var srcA = ((src_aa[src_i + 3] * mul) + (src_aa[src_i2 + 3] * imul)) >> 8;

                    var newAlpha = srcA / 255.0f;
                    var oldAlpha = 1.0f - newAlpha;

                    // Alpha blend over existing color
                    // This for plain alpha:
                    //dst[dst_i + 0] = Clip((dst[dst_i + 0] * oldAlpha) + (src[src_i + 0] * newAlpha));
                    //dst[dst_i + 1] = Clip((dst[dst_i + 1] * oldAlpha) + (src[src_i + 1] * newAlpha));
                    //dst[dst_i + 2] = Clip((dst[dst_i + 2] * oldAlpha) + (src[src_i + 2] * newAlpha));

                    // This for pre-multiplied alpha
                    dst[dst_i + 0] = Clip((dst[dst_i + 0] * oldAlpha) + srcB);
                    dst[dst_i + 1] = Clip((dst[dst_i + 1] * oldAlpha) + srcG);
                    dst[dst_i + 2] = Clip((dst[dst_i + 2] * oldAlpha) + srcR);
                    dst[dst_i + 3] = 255; // tile alpha is always 100%

                    changed = true;
                }
            }
            return(changed);
        }
Exemplo n.º 6
0
        /// <summary>
        /// Draw an entire image onto the canvas, scaled to fit in the given rectangle.
        /// Expects screen-space co-ordinates.
        /// <para></para>
        /// Returns false if any of the tiles couldn't be written to
        /// </summary>
        public bool ImportBytesScaled(RawImageInterleaved_UInt8 img, int left, int top, int right, int bottom)
        {
            if (img == null)
            {
                return(false);
            }
            if (left >= right || top >= bottom)
            {
                return(true);
            }

            var tl = ScreenToCanvas(left, top);
            var br = ScreenToCanvas(right, bottom);

            var tileLeft   = tl.TilePosition?.X ?? throw new Exception("Invalid tile calculation");
            var tileTop    = tl.TilePosition.Y;
            var tileRight  = br.TilePosition?.X ?? throw new Exception("Invalid tile calculation");
            var tileBottom = br.TilePosition.Y;

            double targetNativeTop    = ((double)tileTop * TileImageSize) + tl.Y;
            double targetNativeBottom = ((double)tileBottom * TileImageSize) + br.Y;
            double targetNativeLeft   = ((double)tileLeft * TileImageSize) + tl.X;
            double targetNativeRight  = ((double)tileRight * TileImageSize) + br.X;

            double scale_x = img.Width / (targetNativeRight - targetNativeLeft);
            double scale_y = img.Height / (targetNativeBottom - targetNativeTop);

            // scan through the tiles we cover, and scale segments to fit.
            for (int ty = tileTop; ty <= tileBottom; ty++)
            {
                // see if we're at the top or bottom of the tiles covered
                var localNativeTop    = (double)ty * TileImageSize;
                var localNativeBottom = ((double)ty + 1) * TileImageSize;
                var tgtTop            = (localNativeTop < targetNativeTop) ? tl.Y : 0;
                var tgtHeight         = (localNativeBottom > targetNativeBottom) ? br.Y : TileImageSize;
                tgtHeight -= tgtTop;

                for (int tx = tileLeft; tx <= tileRight; tx++)
                {
                    // see if we're at the left or right of the tiles covered
                    var localNativeLeft  = (double)tx * TileImageSize;
                    var localNativeRight = ((double)tx + 1) * TileImageSize;
                    var tgtLeft          = (localNativeLeft < targetNativeLeft) ? tl.X : 0;
                    var tgtWidth         = (localNativeRight > targetNativeRight) ? br.X : TileImageSize;
                    tgtWidth -= tgtLeft;

                    // calculate what area of the image to use
                    var imgLeft = localNativeLeft - targetNativeLeft;
                    var imgTop  = localNativeTop - targetNativeTop;
                    if (imgLeft < 0)
                    {
                        imgLeft = 0;
                    }
                    if (imgTop < 0)
                    {
                        imgTop = 0;
                    }

                    // Ensure the tile is ready to be drawn on:
                    var key = new PositionKey(tx, ty);
                    var ok  = PrepareTileForDraw(key, out var tile);
                    if (!ok)
                    {
                        Logging.WriteLogMessage($"Preparing tile failed: {tx}, {ty}");
                        return(false);
                    }

                    // This could be kicked out to threads?:
                    var changed = AlphaMapImageToTileScaled(img, tile,
                                                            imageArea: new Quad(imgLeft, imgTop, tgtWidth * scale_x, tgtHeight * scale_y),
                                                            tileArea: new Quad(tgtLeft, tgtTop, tgtWidth, tgtHeight)
                                                            );

                    if (changed)
                    {
                        _lastChangedTiles.Add(key);
                        tile.Invalidate();
                        tile.SetState(TileState.Ready);
                        ThreadPool.UnsafeQueueUserWorkItem(xkey => { WriteTileToBackingStoreSync((PositionKey)xkey, tile); }, key);
                    }
                }
            }
            return(true);
        }