arklumpus / MuPDFCore

Multiplatform .NET bindings for MuPDF
GNU Affero General Public License v3.0
115 stars 20 forks source link

Accept and return ReadOnlySpan<byte> instead of IntPtr #16

Closed tzographos closed 2 years ago

tzographos commented 2 years ago

Hello, first of all let me say that this is a great library and especially the multithreaded render.
Is there somewhere on the roadmap the ability to expose an API with Span<byte> instead of IntPtr ?

The reason is that in most of the cases, after rendering an e.g. PDF file, we have to either change the exported file format or perform some sort of image manipulation (with another library e.g. ImageSharp). Therefore, it would be beneficial to avoid marshalling memory back, holding twice the amount.

The only way I know to expose a Span<byte> out of an IntPtr is by going into unsafe mode and casting IntPtr to a void* pointer but this would make my project require an /unsafe build, which I was really hoping to avoid.

Do you have any ideas?

Thanks, --Theodore

arklumpus commented 2 years ago

Hi! I'm glad you're finding the library useful!

This sounds like a good idea, I can start working on it when I get back to my computers (likely next week). I will update this issue when it's done.

arklumpus commented 2 years ago

Hi! Version 1.5.0 now has overloads of the render methods (both for the MuPDFDocument and the MuPDFMultiThreadedPageRenderer) that return Spans.

However, you should note that:

All in all, if you just need to manipulate the image using ImageSharp, it is probably better if you just use the MuPDFCore overload that returns a byte[]; in this way, you don't have to worry about memory management (since this a "normal" byte array that will be collected by the GC as usual), and ImageSharp is going to copy the data to its own memory storage anyways.

The MuPDFMultiThreadedPageRenderer does not have a Render overload working with byte[] arrays, but it's relatively straightforward to get an IntPtr from a byte[] staying in safe mode:

int bufferSize = ...

byte[] buffer = new byte[bufferSize];

GCHandle bufferHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
IntPtr bufferPointer = bufferHandle.AddrOfPinnedObject();

// Do stuff with bufferPointer.

bufferHandle.Free();

As long as you don't wait too much before freeing the GCHandle, the side effects on the GC shouldn't be too relevant.

In any case, if you need better performance, you may need to look into some other graphics library that lets you work directly with pixel data stored in unmanaged memory (perhaps something like SkiaSharp?)...

tzographos commented 2 years ago

This is great! Thanks for the detailed explanation. I will give it a spin right away!