/// <inheritdoc /> public virtual IFramebufferReference GrabFramebufferReference(Size size, IImmutableSet <Screen> layout) { if (_disposed) { throw new ObjectDisposedException(nameof(RfbRenderTarget)); } PixelSize requiredPixelSize = Conversions.GetPixelSize(size); // Creation of a new buffer necessary? // No synchronization necessary, because the only conflicting write operation is // in this same method and the field is marked volatile to avoid caching issues. // ReSharper disable once InconsistentlySynchronizedField bool sizeChanged = _bitmap == null || _bitmap.PixelSize != requiredPixelSize; WriteableBitmap bitmap; if (sizeChanged) { // Create new bitmap with required size and the format that is preferred by the current platform (therefore 'null'). // TODO: Detect DPI dynamically bitmap = new WriteableBitmap(requiredPixelSize, new Vector(96.0f, 96.0f), null); // Wait for the rendering being finished before replacing the bitmap lock (_bitmapReplacementLock) { _bitmap?.Dispose(); _bitmap = bitmap; } } else { // ReSharper disable once InconsistentlySynchronizedField bitmap = _bitmap !; } // Lock framebuffer and return as converted reference // ReSharper disable once InconsistentlySynchronizedField ILockedFramebuffer lockedFramebuffer = bitmap.Lock(); return(new AvaloniaFramebufferReference(lockedFramebuffer, () => Dispatcher.UIThread.Post(() => { if (sizeChanged) { InvalidateMeasure(); } InvalidateVisual(); }))); }
/// <summary> /// Finds all of the bodies in the provided image (areas of connected black pixels). /// </summary> /// <param name="Image">The image to find the bodies in</param> /// <returns>A collection of bodies in the image</returns> public List <Body> FindBodies(EtchableImage Image) { List <Body> bodies = new List <Body>(); bool[,] visitedPixels = new bool[Image.Width, Image.Height]; using (ILockedFramebuffer buffer = Image.Bitmap.Lock()) { IntPtr address = buffer.Address; for (int y = 0; y < Image.Height; y++) { IntPtr rowOffsetAddress = address + (y * Image.Width * 4); for (int x = 0; x < Image.Width; x++) { // Ignore pixels we've already looked at if (visitedPixels[x, y]) { continue; } // Set the flag for this pixel because now we've looked at it visitedPixels[x, y] = true; // Ignore white pixels IntPtr pixelAddress = rowOffsetAddress + (x * 4); // Get the blue channel since that isn't used for path drawing, // so it will always have the correct pixel value of 0 or 255 byte pixelValue = Marshal.ReadByte(pixelAddress); if (pixelValue == 0xFF) { continue; } // If we get here, we have a new body! Body body = ProcessBody(buffer, x, y, visitedPixels); bodies.Add(body); } } } return(bodies); }
public static unsafe uint[] ToBitmap(WriteableBitmap wb, out int width, out int height) { using (ILockedFramebuffer l = wb.Lock()) { uint * bmpAddress = (uint *)l.Address.ToPointer(); PixelSize ps = wb.PixelSize; height = ps.Height; width = ps.Width; uint[] arr = new uint[height * width]; for (int py = 0; py < height; py++) { for (int px = 0; px < width; px++) { int i = px + (py * width); arr[i] = *(bmpAddress + i); } } return(arr); } }
private unsafe void RenderTick() { var time = new TimeBarrier(MaxFPS); time.Start(); DateTime lastRenderTime = DateTime.Now; while (!_isDisposed) { DateTime now = DateTime.Now; using (ILockedFramebuffer l = _screen.Lock()) { uint *bmpAddress = (uint *)l.Address.ToPointer(); Game.Instance.RenderTick(bmpAddress, RenderWidth, RenderHeight, _showFPS ? (int)Math.Round(1_000 / now.Subtract(lastRenderTime).TotalMilliseconds) : (int?)null); } InvalidateVisual(); lastRenderTime = now; time.Wait(); } time.Stop(); }
private WriteableBitmap GetSurface(PixelBuffer pixelBuffer) { if (!Monitor.IsEntered(_syncRoot)) { throw new InvalidOperationException(); } WriteableBitmap surface = pixelBuffer.Surface; if (surface is null || surface.PixelSize != new PixelSize(pixelBuffer.Width, pixelBuffer.Height)) { surface?.Dispose(); surface = new WriteableBitmap(new PixelSize(pixelBuffer.Width, pixelBuffer.Height), OffscreenGraphics.DpiScale.Dpi, PixelFormat.Bgra8888, AlphaFormat.Premul); pixelBuffer.Surface = surface; } using (ILockedFramebuffer frameBuffer = surface.Lock()) { Marshal.Copy(pixelBuffer.DIB, 0, frameBuffer.Address, pixelBuffer.Size); pixelBuffer.ClearDirtyRectangle(); } return(surface); }
public static unsafe uint[][] LoadBitmapSheet(string resource, int spriteWidth, int spriteHeight) { using (WriteableBitmap wb = ToWriteableBitmap(new Bitmap(Utils.GetResourceStream(resource)))) using (ILockedFramebuffer l = wb.Lock()) { uint * bmpAddress = (uint *)l.Address.ToPointer(); PixelSize ps = wb.PixelSize; int sheetWidth = ps.Width; int sheetHeight = ps.Height; int numSpritesX = sheetWidth / spriteWidth; int numSpritesY = sheetHeight / spriteHeight; uint[][] sprites = new uint[numSpritesX * numSpritesY][]; int sprite = 0; for (int sy = 0; sy < numSpritesY; sy++) { for (int sx = 0; sx < numSpritesX; sx++) { sprites[sprite++] = GetBitmapUnchecked(bmpAddress, sheetWidth, sx * spriteWidth, sy * spriteHeight, spriteWidth, spriteHeight); } } return(sprites); } }
/// <summary> /// Generates a thumbnail of the page. /// </summary> /// <returns>A <see cref="WriteableBitmap"/> containing the thumbnail of the page.</returns> private WriteableBitmap GenerateThumbnail() { //Render the whole page. Rectangle bounds = Document.Pages[this.FindControl <PDFRenderer>("MuPDFRenderer").PageNumber].Bounds; //Determine the appropriate zoom factor to render a thumbnail of the right size for the NavigatorCanvas, taking into account DPI scaling double maxDimension = Math.Max(bounds.Width, bounds.Height); double zoom = 200 / maxDimension * ((VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1); //Get the actual size in pixels of the image. RoundedRectangle roundedBounds = bounds.Round(zoom); //Initialize the image WriteableBitmap bmp = new WriteableBitmap(new PixelSize(roundedBounds.Width, roundedBounds.Height), new Vector(96, 96), Avalonia.Platform.PixelFormat.Rgba8888, AlphaFormat.Unpremul); //Render the page to the bitmap, without marshaling. using (ILockedFramebuffer fb = bmp.Lock()) { Document.Render(this.FindControl <PDFRenderer>("MuPDFRenderer").PageNumber, bounds, zoom, PixelFormats.RGBA, fb.Address); } return(bmp); }
public unsafe void DrawAll(bool borderBlocks, bool wasResized) { WriteableBitmap bmp = borderBlocks ? BorderBlocksBitmap : BlocksBitmap; using (ILockedFramebuffer l = bmp.Lock()) { uint *bmpAddress = (uint *)l.Address.ToPointer(); int width = borderBlocks ? BorderWidth : Width; int height = borderBlocks ? BorderHeight : Height; int bmpWidth = width * Overworld.Block_NumPixelsX; int bmpHeight = height * Overworld.Block_NumPixelsY; RenderUtils.FillColor(bmpAddress, bmpWidth, bmpHeight, 0, 0, bmpWidth, bmpHeight, 0xFF000000); Block[][] arr = borderBlocks ? BorderBlocks : Blocks; for (int y = 0; y < height; y++) { Block[] arrY = arr[y]; for (int x = 0; x < width; x++) { arrY[x].BlocksetBlock.Draw(bmpAddress, bmpWidth, bmpHeight, x * Overworld.Block_NumPixelsX, y * Overworld.Block_NumPixelsY); } } } OnDrew?.Invoke(this, borderBlocks, wasResized); }
static WriteableBitmap set_data(byte[] buffer, int width, int height) { WriteableBitmap bmp = new WriteableBitmap(new PixelSize(width, height), new Vector(96, 96), PixelFormat.Rgba8888); int stride = width * 4; using (ILockedFramebuffer fb = bmp.Lock()) { for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { unsafe { byte *data = (byte *)fb.Address; data[y * stride + x * 4] = buffer[y * width * 3 + x * 3 + 0]; data[y * stride + x * 4 + 1] = buffer[y * width * 3 + x * 3 + 1]; data[y * stride + x * 4 + 2] = buffer[y * width * 3 + x * 3 + 2]; data[y * stride + x * 4 + 3] = 255; } } } } return(bmp); }
public FramebufferShim(ILockedFramebuffer target) : base(target.Size, target.Dpi, target.Format) { _target = target; }
public FramebufferShim(ILockedFramebuffer target) : base(target.Width, target.Height, target.Dpi.X, target.Dpi.Y, target.Format) { _target = target; }
public LockedFramebufferWrapper(ILockedFramebuffer inner, IDisposable disposable) { _inner = inner; _disposable = disposable; }
/// <summary> /// Breaks the provided image into horizontal raster etch segments. /// </summary> /// <param name="Image">The image to get the raster etch lines for</param> /// <returns>A collection of horizontal lines (each of which can have multiple segments) /// necessary to etch the image in raster mode.</returns> private List <Line> GetRasterEtchLines(EtchableImage Image) { List <Line> lines = new List <Line>(); using (ILockedFramebuffer bitmapBuffer = Image.Bitmap.Lock()) { IntPtr bitmapAddress = bitmapBuffer.Address; for (int y = 0; y < Image.Height; y++) { Line line = new Line(y); bool isInEtchSegment = false; int etchStart = 0; for (int x = 0; x < Image.Width; x++) { // Get the blue channel - since this is a black and white image, the channel doesn't really matter byte pixelValue = Marshal.ReadByte(bitmapAddress); // This is a white pixel if (pixelValue > 127) { if (isInEtchSegment) { // Create a new etch segment from the recorded start to the previous pixel EtchSegment segment = new EtchSegment(etchStart, x - 1); line.Segments.Add(segment); isInEtchSegment = false; } } // This is a black pixel else { if (!isInEtchSegment) { // Start a new etch segment isInEtchSegment = true; etchStart = x; } // Check if we're at the last pixel in the row but still in an etch segment else if (x == Image.Width - 1) { EtchSegment segment = new EtchSegment(etchStart, x); line.Segments.Add(segment); isInEtchSegment = false; } } // Move to the next pixel in the bitmap buffer bitmapAddress += 4; } // Ignore lines with no etch segments if (line.Segments.Count > 0) { lines.Add(line); } } } return(lines); }
public FramebufferProxy(ILockedFramebuffer fb, Action onDispose) { _fb = fb; _onDispose = onDispose; }
/// <summary> /// Finds the outline for a body given its starting (top left) pixel, in the form of a continuous path /// that's suitable for etching. /// </summary> /// <param name="ImageBuffer">The buffer containing the pixel values of the image being processed</param> /// <param name="StartPoint">The top-left point of the body</returns> public Path GetOutlinePath(ILockedFramebuffer ImageBuffer, Point StartPoint) { List <Point> outline = new List <Point>(); HashSet <Point> visitedPoints = new HashSet <Point>(); Point currentPoint = StartPoint; List <Point> currentNeighbors = GetNeighbors(currentPoint, ImageBuffer.Size); while (true) { // Add this point to the outline outline.Add(currentPoint); visitedPoints.Add(currentPoint); // Check each neighbor point to find the next one in the outline bool foundNextOutlinePoint = false; foreach (Point currentNeighbor in currentNeighbors) { // If this point is already part of the outline, ignore it if (visitedPoints.Contains(currentNeighbor)) { continue; } // If this point is white, ignore it byte currentNeighborValue = GetPixelValueAtPoint(ImageBuffer, currentNeighbor); if (currentNeighborValue == 0xFF) { continue; } // Check if this point has any white pixel neighbors; if it does, it is on the outline and // becomes the next point. Start the search from the current point, going clockwise, so it // will always try to find the outermost point and won't cut through the middle of really thin // bodies. List <Point> nextNeighbors = GetNeighbors(currentNeighbor, ImageBuffer.Size, currentPoint); foreach (Point nextNeighbor in nextNeighbors) { byte nextNeighborValue = GetPixelValueAtPoint(ImageBuffer, nextNeighbor); if (nextNeighborValue == 0xFF) { currentPoint = currentNeighbor; currentNeighbors = nextNeighbors; foundNextOutlinePoint = true; break; } } if (foundNextOutlinePoint) { break; } // Check if this point is at the image boundary; if it is, it's on the outline and // becomes the next point. if (currentNeighbor.Y == 0 || currentNeighbor.Y == ImageBuffer.Size.Height - 1 || currentNeighbor.X == 0 || currentNeighbor.X == ImageBuffer.Size.Width - 1) { currentPoint = currentNeighbor; currentNeighbors = nextNeighbors; foundNextOutlinePoint = true; break; } } // We couldn't find any more points that belong to the outline, so we're done. if (!foundNextOutlinePoint) { Path path = new Path(outline); return(path); } } }
public FramebufferDrawingContextImpl(SKCanvas canvas, SKSurface surface, ILockedFramebuffer framebuffer) : base(canvas) { _canvas = canvas; _surface = surface; _framebuffer = framebuffer; }
public static Bitmap RenderString(string str, StringRenderStyle style) { // Return null for bad strings if (string.IsNullOrWhiteSpace(str)) { return(null); } string path; int charHeight, spaceWidth; switch (style) { case StringRenderStyle.BattleName: path = "BattleName"; charHeight = 11; spaceWidth = 2; break; case StringRenderStyle.BattleLevel: path = "BattleLevel"; charHeight = 10; spaceWidth = 7; break; case StringRenderStyle.BattleHP: path = "BattleHP"; charHeight = 8; spaceWidth = 0; break; default: path = "Default"; charHeight = 15; spaceWidth = 4; break; } int index; string GetCharKey() { string key = $"FONT_{path}_"; if (index + 6 <= str.Length && str.Substring(index, 6) == "[PKMN]") { key += "PKMN"; index += 6; } else if (index + 4 <= str.Length && str.Substring(index, 4) == "[LV]") { key += "LV"; index += 4; } else { key += ((int)str[index]).ToString("X"); index++; } const string questionMark = "FONT_Default_3F"; return(DoesResourceExist($"Kermalis.PokemonBattleEngineClient.FONT.{path}.{key}.png") ? key : questionMark); } // Measure how large the string will end up int stringWidth = 0, stringHeight = charHeight, curLineWidth = 0; index = 0; while (index < str.Length) { if (str[index] == ' ') { index++; curLineWidth += spaceWidth; } else if (str[index] == '\r') { index++; continue; } else if (str[index] == '\n') { index++; stringHeight += charHeight + 1; if (curLineWidth > stringWidth) { stringWidth = curLineWidth; } curLineWidth = 0; } else { string key = GetCharKey(); if (!loadedBitmaps.ContainsKey(key)) { loadedBitmaps.TryAdd(key, UriToBitmap(new Uri($"resm:Kermalis.PokemonBattleEngineClient.FONT.{path}.{key}.png?assembly=PokemonBattleEngineClient"))); } curLineWidth += loadedBitmaps[key].PixelSize.Width; } } if (curLineWidth > stringWidth) { stringWidth = curLineWidth; } // Draw the string var wb = new WriteableBitmap(new PixelSize(stringWidth, stringHeight), new Vector(96, 96), PixelFormat.Bgra8888); using (IRenderTarget rtb = AvaloniaLocator.Current.GetService <IPlatformRenderInterface>().CreateRenderTarget(new[] { new WriteableBitmapSurface(wb) })) using (IDrawingContextImpl ctx = rtb.CreateDrawingContext(null)) { double x = 0, y = 0; index = 0; while (index < str.Length) { if (str[index] == ' ') { index++; x += spaceWidth; } else if (str[index] == '\r') { index++; continue; } else if (str[index] == '\n') { index++; y += charHeight + 1; x = 0; } else { Bitmap bmp = loadedBitmaps[GetCharKey()]; ctx.DrawImage(bmp.PlatformImpl, 1.0, new Rect(0, 0, bmp.PixelSize.Width, charHeight), new Rect(x, y, bmp.PixelSize.Width, charHeight)); x += bmp.PixelSize.Width; } } } // Edit colors using (ILockedFramebuffer l = wb.Lock()) { uint primary = 0xFFFFFFFF, secondary = 0xFF000000, tertiary = 0xFF808080; switch (style) { case StringRenderStyle.MenuBlack: primary = 0xFF5A5252; secondary = 0xFFA5A5AD; break; case StringRenderStyle.BattleWhite: //secondary = 0xF0FFFFFF; break; // Looks horrible because of Avalonia's current issues case StringRenderStyle.MenuWhite: secondary = 0xFF848484; break; case StringRenderStyle.BattleName: case StringRenderStyle.BattleLevel: primary = 0xFFF7F7F7; secondary = 0xFF181818; break; case StringRenderStyle.BattleHP: primary = 0xFFF7F7F7; secondary = 0xFF101010; tertiary = 0xFF9C9CA5; break; } for (int x = 0; x < stringWidth; x++) { for (int y = 0; y < stringHeight; y++) { var address = new IntPtr(l.Address.ToInt64() + (x * sizeof(uint)) + (y * l.RowBytes)); uint pixel = (uint)Marshal.ReadInt32(address); if (pixel == 0xFFFFFFFF) { Marshal.WriteInt32(address, (int)primary); } else if (pixel == 0xFF000000) { Marshal.WriteInt32(address, (int)secondary); } else if (pixel == 0xFF808080) { Marshal.WriteInt32(address, (int)tertiary); } } } } return(wb); }