Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to Blit Pixels Directly into Bitmap (WriteableBitmap) #16218

Open
Obelison opened this issue Jul 3, 2024 · 3 comments
Open

Ability to Blit Pixels Directly into Bitmap (WriteableBitmap) #16218

Obelison opened this issue Jul 3, 2024 · 3 comments
Labels
api API addition enhancement

Comments

@Obelison
Copy link

Obelison commented Jul 3, 2024

Is your feature request related to a problem? Please describe.

I would like to be able to write pixels directly into the (back?) buffer of a Bitmap, but cannot find a way of doing this using the published Avalonia interface.

Describe the solution you'd like

I mistakenly thought that the CopyPixels method in Avalonia.Media.Imaging writes pixels from a buffer into the Bitmap, but looking under the hood at the code, I see that it is copying the Bitmap pixels to the given buffer. My request is to effectively do the 'reverse' of the CopyPixels method, where the pixels in the buffer are copied (blit'ed) into the Bitmap. It could effectively be the same parameters, eg:

public void Blit(PixelRect destinationRect, IntPtr buffer, int bufferSize, int stride);

... and almost the same code as in the underlying CopyPixelsCore method of Bitmap, except that we replace sourceRect with destinationRect throughout, and replace line 214 and 215 with:
var dstAddress = fb.Address + fb.RowBytes * (destinationRect.Y + y) + offsetX;
var srcAddress = buffer + stride * y;

Is this something that could be done?

Describe alternatives you've considered

  1. I have tried CopyPixels, which of course did the opposite.
  2. I would like to have tried to use WriteableBitmap.BackBuffer, as found in the System.Windows.Media.Imaging version of WriteableBitmap, however, I appreciate that this may well be almost impossible for the Avalonia version.
  3. I have tried using my own version of the implementation I have suggested in the previous section, which looks a lot like:
    using (var locked = ((WriteableBitmap)bitmap!).Lock())
    {
        for (int y = 0; y < buffer.ImageHeight; y++)
            Unsafe.CopyBlock((locked.Address + locked.RowBytes * y).ToPointer(),
                (buffer.ImageAddress + y * (int)stride).ToPointer(), stride);
    }

where buffer is an object that contains the (RGBA8888) pixels (bytes), and the ImageAddress, ImageHeight, & ImageWidth properties.
This fails to write anything into the Bitmap, which may be a local issue with other parts of my code, so having a correct implementation within Avalonia would really help. However, I do think it would be useful for the community to have this addition to the Bitmap class and its children.

Additional context

No response

@stevemonaco
Copy link
Contributor

So the general approach should work. See

using (var l = writeableBitmap.Lock())
{
for(var r = 0; r<256; r++)
{
Marshal.Copy(data, r * 256, new IntPtr(l.Address.ToInt64() + r * l.RowBytes), 256);
}
}
and https://github.com/stevemonaco/AvaloniaDemos/blob/master/RealTimeBitmapAdapter/ImageAdapters/WriteableBitmapAdapter.cs#L63

If this WriteableBitmap is already assigned to an Image.Source before the mutation, then you need to manually call theImage.InvalidateVisual(); so it refreshes. This is different from WPF.

Otherwise, provide a minimal repro. There may be some bad assumptions, such as assuming the stride for the WriteableBitmap and your pixel buffer are the same...though you'd probably have a crash if that were the case.

There probably should be some public API here, but I prefer working directly with SkiaSharp when directly wrapping an existing pixel buffer. See https://github.com/stevemonaco/AvaloniaDemos/blob/master/SkiaBitmapAdapter/Views/SkiaBitmapView.axaml.cs#L90 for an example with SKBitmap.

@maxkatz6 maxkatz6 added the api API addition label Jul 3, 2024
@Obelison
Copy link
Author

Obelison commented Jul 4, 2024

Many thanks for this info @stevemonaco, very helpful.

Actually, I am trying to display a stream of images grabbed from a camera in real-time. So with my specific application, I am trying to overwrite the image with another image with the same parameters (from the same camera). It turns out that a simpler way of doing the blit under these circumstances is with a more-or-less one-liner:

using (var locked = ((WriteableBitmap)bitmap).Lock())
{
Unsafe.CopyBlock(locked.Address.ToPointer(), buffer.ImageAddress.ToPointer(),
buffer.ImageSize);
}

In my application, 'image' is a property in a ViewModel that's bound to an Image control. I am not calling InvalidateVisual() on the Image, so I am guessing this is the problem. Am I correct in assuming that I should post the call to InvalidateVisual() into the main/UI thread?

So the general approach should work. See

using (var l = writeableBitmap.Lock())
{
for(var r = 0; r<256; r++)
{
Marshal.Copy(data, r * 256, new IntPtr(l.Address.ToInt64() + r * l.RowBytes), 256);
}
}

and https://github.com/stevemonaco/AvaloniaDemos/blob/master/RealTimeBitmapAdapter/ImageAdapters/WriteableBitmapAdapter.cs#L63
If this WriteableBitmap is already assigned to an Image.Source before the mutation, then you need to manually call theImage.InvalidateVisual(); so it refreshes. This is different from WPF.

Otherwise, provide a minimal repro. There may be some bad assumptions, such as assuming the stride for the WriteableBitmap and your pixel buffer are the same...though you'd probably have a crash if that were the case.

There probably should be some public API here, but I prefer working directly with SkiaSharp when directly wrapping an existing pixel buffer. See https://github.com/stevemonaco/AvaloniaDemos/blob/master/SkiaBitmapAdapter/Views/SkiaBitmapView.axaml.cs#L90 for an example with SKBitmap.

@stevemonaco
Copy link
Contributor

Am I correct in assuming that I should post the call to InvalidateVisual() into the main/UI thread?

Dispatching this to the UI thread appears to be necessary so you don't invalidate while a render pass is currently underway.

In some apps, I have an Action property on the VM that the View sets like: ViewModel.OnImageModified = () => _image.InvalidateVisual();. An event/callback/message would work too. In realtime apps, I'm usually triggering updates via the View anyways (via TopLevel.RequestAnimationFrame). So those are some alternate approaches to using the Dispatcher from within the ViewModel.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api API addition enhancement
Projects
None yet
Development

No branches or pull requests

3 participants