Runtime: Support Full System.Drawing Functionality on .NET Core

Created on 25 May 2017  ·  103Comments  ·  Source: dotnet/runtime

Update: The following is the current plan.

  • [x] Port the .NET Framework implementation to .NET Core. WIP, See dotnet/corefx#20593 .
  • [x] Move mono's libgdiplus-based managed implementation from mono to corefx
  • [x] Ensure that there is a code-sharing story between mono and corefx. This likely means removing the source code from mono and importing code from the submodule link that already exists. In progress. Mono has begun using some portions of the Windows implementation already.
  • [x] Port mono's tests for System.Drawing, making any modifications necessary to ensure that they pass on the Windows implementation, which is our baseline for compatibility. In progress
  • [ ] Consolidate differences between the Windows and Unix versions of the library. Document all differences in behavior and mark test cases appropriately.

System.Drawing has been identified as one of the top most-used assemblies that are missing from .NET Core. Although there are 3rd-party alternatives that provide more modern versions of the same functionality (ImageSharp, SkiaSharp, etc.), we believe there is still value in providing a binary-compatible implementation of System.Drawing itself. This issue is intended to document and track the work items involved. There are still lots of open questions; feedback and suggestions are encouraged.

Priorities:

  • Simplify migration from .NET Framework to .NET Core. We need to be binary compatible (no recompilation needed for existing assets), and we need to match the behavior as closely as possible.
  • Support System.Drawing where mono does, and facilitate code sharing between our implementations. As part of this, we will be open-sourcing the original Windows implementation from .NET Framework. This should allow us to improve mono's implementation where it diverges, and hopefully reduce the maintenance burden between us by consolidating code and effort.
  • NON-GOALS: We do not view this library as an opportunity for innovation, nor will we invest significant amounts of effort into optimizing or refactoring it. High performance, rich OS-specific functionality, and a redesigned, modern API are not areas we are interested in investing in as part of this effort. There are alternative libraries which target all of those issues and more.

    Plan

System.Drawing is, fundamentally, a very thin wrapper over the GDI+ component on Windows. Worse yet, it is not available for UWP applications, so support is limited strictly to Win32 applications. This makes cross-platform implementations difficult, and the largest chunk of work involved with this effort will be figuring out how we can support the library on all of our platforms, and maintaining compatibility between them. There are several existing implementations that we can leverage, but none will support all of our platforms by themselves. We may need several distinct backends in order to get full cross-platform support, although some options could avoid that. The below chart maps implementation approaches (rows) with platform applicability (columns):

|Win32 | UWP | macOS | Linux / Others
-----|--------|-------|---------|---------
.NET Framework Version (GDI+) | ✓ | X | X | X
Direct2D/Win2D|✓|✓|X|X
Mono/libgdiplus|✓|X|✓|✓
Xamarin/CoreGraphics|X|X|✓|X
Managed|✓|✓|✓|✓

.NET Framework Version (GDI+)

This is the existing implementation from the .NET Framework on Windows. It is a very thin wrapper around GDI+.

  • :heavy_check_mark: Baseline implementation. Well-tested, stable.
  • :x: Can only be used in non-UWP Windows apps (Win32).

Direct2D/Win2D

In order to get UWP support, we could implement a compatibility layer over Direct2D, or Win2D.

  • :heavy_check_mark: GPU acceleration
  • :x: Only buys us UWP support over the existing .NET Framework implementation.
  • :x: High implementation cost
  • Can be shared between Win32 and UWP applications (Windows 8+ only).

Mono/libgdiplus

This is the implementation used in mono. It uses a complicated native shim called "libgdiplus", which relies on a large set of native binaries.

  • :heavy_check_mark: libgdiplus is available in a large number of package managers across the Unix ecosystem already. This means we can treat the dependency the same way we do the other native dependencies for .NET Core.
  • :x: Native code adds a lot of complexity for new devs.

Xamarin/CoreGraphics

This is a specialized implementation used by Xamarin, in support of macOS and iOS. Unlike the "libgdiplus" mono version, it only uses CoreGraphics (a system component).

  • :heavy_check_mark: No extra native dependencies; only uses system libraries.
  • :x: macOS-specific. Could be used for iOS (not supported by .NET Core yet).

Managed (ImageSharp or custom)

One option is to implement the library in managed code. We can call into an existing library, like ImageSharp, for image-processing and graphics functionality. This is very desirable for a lot of reasons, but it does have a higher implementation cost.

  • :heavy_check_mark: Can be used everywhere.
  • :heavy_check_mark: Can achieve uniform behavior everywhere (at the expense of conformity with .NET Framework).
  • :heavy_check_mark: Lower maintenance costs in the long-term.
  • :x: High implementation cost.

Windows-Specific

One option is to treat the library as a Windows-specific component, similar to Registry. Attempting to install and load the library on other platforms would throw PlatformNotSupportedException's, as Registry does.

Tests

We do not have a very good existing test suite in the .NET Framework. The library is mainly covered indirectly, through other components like WinForms. Mono has a good set of conformance tests for their implementation, which we should be able to re-use. Where appropriate, we should also consider test cases which directly compare output from the .NET Framework implementation and other implementations (assuming we choose to support such things).

Location

An open question is where this library should live. If we take third-party dependencies to support some platforms, we may not want this code to live in corefx.

Printing Functionality

One thing skimmed over above is the fact that System.Drawing exposes a lot of functionality for printing images and documents. This poses a different problem from image-processing and graphics, which can be implemented portably. We may decide to exclude all printing-related portions of the library.

Future investments

Once the library is supported on all platforms, we will accept small, targeted improvements to the library, keeping in mind that legacy compatibility is the only important metric for the library. We will not want to merge any code that refactors, optimizes, or augments the Windows version until we have a fully-working cross-platform implementation. This matches our strategy with other compatibility libraries, like System.IO.SerialPort.

@marek-safar @jimbobsquarepants @ViktorHofer @qmfrederik @karelz

Design Discussion area-System.Drawing enhancement

Most helpful comment

It is worth updating the original issue with the new information provided in the comments.

Mono's System.Drawing implementation was used for Mono's Windows.Forms implementation and we spent a few years tuning it to work with various third party component vendors. We delegated the hard graphic problems to Cairo.

It was also built so that on Windows, it would use the system provided GDI+ library, and on other platforms, it uses Mono's libgdiplus, it achieves this by having libgdiplus surface the same API as the Windows libgdiplus.

This means a couple of things:

  • That Mono's System.Drawing is actually a very thin layer on top of libgdiplus.
  • that on Windows, you get to use the real GDI+, which is quite nice.

While the implementation works, the font handling capabilities are complicated on Unix, so we used FreeType to get this information. While this works great on Linux systems, it is very slow on first use on a Mac that typically lacks FreeType, so if I was going to do the work in this space, I would start by improving libgdiplus to have a swappable font backend and use Apple's CoreText on OSX to avoid the initial font cache creation problems.

Cairo and the underlying libraries are faster than any C# code you can write.

That said, if I was going to take on the long-term maintenance of System.Drawing, today I would take a different approach.

I would use SkiaSharp as the backing engine as it is actively maintained, actively developed, actively fine tuned and optimized for both Chrome and Android. It also addresses some of the design limitations of Cairo, both the bottlenecks of the API as well as issues with the coordinate space that happen due to loss of precision with huge matrix transformations (Mozilla went through this, and made essentially this switch as well).

I would start with the sysdrawing-coregraphics which is a project that moved most of the logic from libgdiplus from C to C# and replaced the calls to Cairo with calls to CoreGraphics.

And then I would replace the calls to CoreGraphics with calls to the Skia APIs.

Last year I did some work along those lines on my spare time, where I mostly butchered the codebase so it would compile without CoreGraphics and I was planning on adding the Skia support. The time to do that never materialized. If you are interested in that fork, you can find it here.

All 103 comments

@mellinoe Thanks for the additional information!

I actually have a good experience with using Mono/libgdiplus via CoreCompat.System.Drawing. Here's why:

  • Mono/libgdiplus actually does run on .NET Core on Windows. When on Windows, Mono's System.Drawing just uses GDI+ from Windows. So I think you can add a checkbox to 'Win32' for Mono's System.Drawing
  • I don't think the native dependencies need to be a problem. libgdiplus ships as a native component for most operating systems .NET Core supports. All Linux distros which .NET Core supports include libgdiplus in their mainline repositories: Ubuntu, Debian, Fedora, OpenSuse - even Alpine has it. Plus, libgidplus was recently added to Homebrew, too.
    So, I would suggest treating libgdiplus as a the single native dependency which would be acquired from the Linux distro or Homebrew - using the same acquisition mechanism for other .NET Core dependencies, such as curl or OpenSSL.
  • Mono's System.Drawing does include the Printing components, so you would get those for free, too.
  • Mono's System.Drawing runs natively on netstandard2.0 (and all unit tests pass!) and is very close to running on netstandard1.6.
  • Could you elaborate on "Native code adds a lot of complexity for new devs"?
  • Regarding "Behavior and performance issues", I'm not really aware of them, so it would be good to have them listed, too.
  • Since it's part of Mono, Mono's System.Drawing is still being maintained.

Another option would be to use the System.Drawing code from NetFX and have it consume libgdiplus instead of GDI+ on Linux. There's been some talk of open sourcing System.Drawing before, not sure if that's still an option? It would be fairly easy to implement, too.

Regarding UWP, do you currently have demand for System.Drawing on UWP?

/cc @akoeplinger

All Linux distros which .NET Core supports include libgdiplus in their mainline repositories: Ubuntu, Debian, Fedora, OpenSuse - even Alpine has it. Plus, libgidplus was recently added to Homebrew, too.

That is good to know; thanks for the info. If we are able to treat it as a fully external dependency, like the other native libs we use, then I would have less hesitation about using it. I'm not an expert on libgdiplus, so I would certainly like to understand more about it. The folks that I have talked with have been hesitant to readily suggest that we go ahead and use it, but it may be okay in the face of less-promising alternatives. In general, though, I think we should be really cautious about any new native dependencies we adopt in .NET Core. They make a lot of things more complicated for us and for customers.

Could you elaborate on "Native code adds a lot of complexity for new devs"?

I was trying to express the idea that folks trying to submit fixes for System.Drawing will have a harder time contributing to an external C library than a managed C# library here. Just a different skill-set, is all.

There's been some talk of open sourcing System.Drawing before, not sure if that's still an option? It would be fairly easy to implement, too.

At the very least, we will be using the .NET Framework version for Windows, so we will certainly open-source it. We can attempt to use that same implementation against libgdiplus on other platforms, but I fear that would involve reworking tons of the native code. Then again, that is just a guess on my part.

Regarding UWP, do you currently have demand for System.Drawing on UWP?

We view System.Drawing as one of the main libraries blocking old customers (and their code) from migrating to our new platforms, including UWP. That's not exactly the question you asked, but the end result is: we'd like to support System.Drawing everywhere, because it has proven to be an important dependency for existing libraries.

At the very least, we will be using the .NET Framework version for Windows, so we will certainly open-source it. We can attempt to use that same implementation against libgdiplus on other platforms, but I fear that would involve reworking tons of the native code. Then again, that is just a guess on my part.

My guess is that the .NET Framework version will work well with libgdiplus. It's an assumption based on my experience with libgdiplus and how it tries to be binary compatible with GDI+.
The proof of the pudding is in the eating, though. If you can make the .NET Framework System.Drawing source code available under an open source license, I'd be happy to give it a spin, and run Mono's unit tests for System.Drawing on .NET Framework's System.Drawing backed by libgdiplus running on .NET Core on, say, Linux -- that would give the definitive answer.

I was trying to express the idea that folks trying to submit fixes for System.Drawing will have a harder time contributing to an external C library than a managed C# library here. Just a different skill-set, is all.

That's a fair point, although on the other hand the C libraries that libgdiplus uses - such as libpng, libjpeg,... have matured for a long time so there's some value to that, too. Graphics code requires a special skillset anyway, perhaps the bulk of people with that skillset are knowledgeable in C?

Perhaps another option is to start with libgdiplus and move code from unmanaged to managed. It could help kick start the System.Drawing implementation by reusing libgdiplus and move towards the goal of cross-platform managed code step by step.

Perhaps another option is to start with libgdiplus and move code from unmanaged to managed. It could help kick start the System.Drawing implementation by reusing libgdiplus and move towards the goal of cross-platform managed code step by step.

If I was to build a managed 2D graphics library (I am) I wouldn't, by choice, re-implement the System.Drawing API.

That said @mellinoe I'm happy to offer and assist in migration if the decision was made to use ImageSharp as an underlying managed library. In Jamestopia though we wouldn't bother and would actively push developers to migrate to better API's. Seems a lot cheaper and easier for MS.

There are some fundamental differences in how the two libraries currently work which would have to be discussed and workarounds considered. We're still unfinished so we have great flexibility to alter the API to improve flexibility and fill gaps which would help a lot.

Pixel Formats
ImageSharp expands all image formats into one of 20 different pixel formats we bundle. System.Drawing stores images in Format8bppIndexed format and similar in their indexed form. There would have to be a workaround developed to do the same.

Image Formats

  • Gif - Our output for gif is different to System.Drawing. We support animated gif plus transparency, we also quantize the output using Octrees by default and dither the result. This is all configurable but we would have to limit our encoder to use the default System.Drawing palette.
  • Png - We use all the available filters to encode pngs to reduce file size plus we support indexed png. We can add a switch to not filter the output scanlines to make the types compatible.
  • Jpeg - Our output is very slightly different to System.Drawing (I think due to rounding errors on decode) and our decoder performance is not as good just now. We need help on that to make it up to scratch.
  • Bmp - Should be no issues there. We support more output types but that can be limited.
  • Tiff - We have a tiff format in the works. Great progress is being made but it is unfinished.

MetaData
System.Drawing bundles everything together into the PropertyItem class from EXIF metadata to gif loop count and frame delay and makes no effort to decode that data. We've split out metadata into generic, EXIF, and ICC components and offer full reading/writing for those data types. You would have to create Property items to match that data and store the raw bytes.

Affine Transforms - Resize, Crop etc
We would need to do some work to ensure that we can produce the same output as System.Drawing. I'm not sure what sampler it uses for the different quality enumerations. We also don't support matrix operations within resize operations so there would have to be work done there.

Drawing
We offer drawing with a very similar API to System.Drawing. There may be a few gaps but I imagine. I know off-hand we haven't implemented all the Hatch bushes @tocsoft is our resident expert there so he can fill you in on what is/isn't there.

I'm sure there's more...

Regarding "Behavior and performance issues", I'm not really aware of them, so it would be good to have them listed, too.

@qmfrederik I had a very, very quick scan of the libgdi codebase and immediately found unsupported areas 1, 2. I've no idea what is out there. I remember having issues with Image.FromStream(stream, useembeddedColorManagement) in the past but I don't know if that is still an issue.

Graphics code requires a special skillset anyway, perhaps the bulk of people with that skillset are knowledgeable in C?

Btw I couldn't read/write C if you held a gun to my head plus I'm teaching myself graphics programming as I write ImagSharp. Shocking 😝

It is worth updating the original issue with the new information provided in the comments.

Mono's System.Drawing implementation was used for Mono's Windows.Forms implementation and we spent a few years tuning it to work with various third party component vendors. We delegated the hard graphic problems to Cairo.

It was also built so that on Windows, it would use the system provided GDI+ library, and on other platforms, it uses Mono's libgdiplus, it achieves this by having libgdiplus surface the same API as the Windows libgdiplus.

This means a couple of things:

  • That Mono's System.Drawing is actually a very thin layer on top of libgdiplus.
  • that on Windows, you get to use the real GDI+, which is quite nice.

While the implementation works, the font handling capabilities are complicated on Unix, so we used FreeType to get this information. While this works great on Linux systems, it is very slow on first use on a Mac that typically lacks FreeType, so if I was going to do the work in this space, I would start by improving libgdiplus to have a swappable font backend and use Apple's CoreText on OSX to avoid the initial font cache creation problems.

Cairo and the underlying libraries are faster than any C# code you can write.

That said, if I was going to take on the long-term maintenance of System.Drawing, today I would take a different approach.

I would use SkiaSharp as the backing engine as it is actively maintained, actively developed, actively fine tuned and optimized for both Chrome and Android. It also addresses some of the design limitations of Cairo, both the bottlenecks of the API as well as issues with the coordinate space that happen due to loss of precision with huge matrix transformations (Mozilla went through this, and made essentially this switch as well).

I would start with the sysdrawing-coregraphics which is a project that moved most of the logic from libgdiplus from C to C# and replaced the calls to Cairo with calls to CoreGraphics.

And then I would replace the calls to CoreGraphics with calls to the Skia APIs.

Last year I did some work along those lines on my spare time, where I mostly butchered the codebase so it would compile without CoreGraphics and I was planning on adding the Skia support. The time to do that never materialized. If you are interested in that fork, you can find it here.

Just had a thought. What about server support. What are the plans there? That should factor into any decision making.

So what is the goal here? Only API compatibility? This is what I understood from what @mellinoe said.

If this is the case, @migueldeicaza comparison between native code and C# isn't very relevant, as long as C# performance is decent:

Cairo and the underlying libraries are faster than any C# code you can write.

I mean if building the API in managed code is an option, let's go for it.

On the other hand, if we settled on "let's just create something" option, let's not go with Win32-only option; this makes it a bit confusing.

Great initiative!! Its better to have System.Drawing implemented on top of set of interfaces and let the community develop each implementation.
Example we can use DirectX on windows and OpenGL on linux and mac OS. but the System.Drawing will internally call the interfaces which is platform agnostic.
If we are going for windows only option then its not going to make any difference to existing .net frameworks.

You need good APIs for that that to be a viable idea. This is a stopgap not a solution.

I'd personally prefer a more forward-looking approach with an officially supported modern cross-platform graphics API for all .Net platforms, and a generic and optional System.Drawing implementation on top of it. But i'm aware that's a rather extensive and expensive solution and, thus, probably unrealistic.

I would love to see something like an implementation on top of ImageSharp. This hypothetical System.Drawing port isn't for high performance or high quality use - it's just to remove a blocker for porting legacy code in which image handling might be one small function. It would be fine if it was slower than the original netfx System.Drawing or didn't produce bit-identical output in all ops- it might even be able to get away with throwing NotSupported for some rarely-used apis.

Building on top of a community library with a modern API would provide a path forward for people to move their code to make direct use of that implentation later on.

Whilst I'm aware that a lot of people use System.Drawing, the WPF System.Windows.Media api is a lot more capable in many areas, and more similar to the UWP API so personally I would rather see a better, more flexible/modern API than System.Drawing available, although of course there may be valuable things in System.Drawing that may be worth implementing

IMHO System.Drawing should be left as Windows only. It is already being used in .Net Framework projects using ASP.Net Core, but those will still be using Windows since those are enterprise applications and I don't see them moving their whole infrastructure "because now they can".

IMHO System.Drawing should be left as Windows only. It is already being used in .Net Framework projects using ASP.Net Core, but those will still be using Windows since those are enterprise applications and I don't see them moving their whole infrastructure "because now they can".

I disagree. ASP.NET Core is being used by more and more open source projects and startups because of cross-platform support and its speed improvements in comparison to nodeJS. I think it's viable to support System.Drawing on all environments where .NET Core and ASP.NET Core is running.

I'm a little worried about binary compatibility here. I think that we definitely need a System.Drawing implementation which is compatible with .NET Framework but I also think that we should build something (together with the community) which is modern, scalable and fast. It might be a good idea to 1. port the existing lib to core (managed) and 2. build a successor of System.Drawing which has all the innovations and improvements which aren't possible with the current API spec.

@ViktorHofer it was said that there are other more modern alternatives like ImageSharp or SkiaSharp. That's why I think it's pointless since those open source projects and startups should use those other alternatives.

@ViktorHofer I've literally spent the last two years of my life doing just that with ImageSharp. It's already faster than System.Drawing in many respects and has a much simpler/easier to extend API.

It needs the community to step up to help me push it over the line. Building something else would be a crushing blow to me.

@JimBobSquarePants Then we should definitely also talk about building upon existing libraries 👍 If you put a lot of effort into it we shouldn't exclude ImageSharp from the discussion.

What's the goal?

  1. Is it so people can move their apps from Framework to Core with minimal changes?
  2. Is it so people can move their apps from Framework on Windows to Core on Linux?
  3. Is it so people can develop new server apps with an api that is remarked as:
    > Classes within the System.Drawing namespace are not supported for use within a Windows or ASP.NET service. Attempting to use these classes from within one of these application types may produce unexpected problems, such as diminished service performance and run-time exceptions.

I had initially imagined it was for (1) where people used System.Drawing in spite of the warnings; but maybe is for (2) and (3)?

@JimBobSquarePants What needs to be done to remove the "in early stages (alpha). As such, we cannot support its use on production environments until the library reaches release candidate status." warning from your project?

It's rather off-putting, and given the way that the .NET Core team have redefined 'Release Candidate' to mean what 'Alpha Experimental Technology Preview' might have once meant, it's now anyone's guess in the world of .NET what "early stages (alpha)" might mean, but it does sound like "not something I want to invest time in right now".

If you want to mop-up the world of System.Drawing users - I'm one, with a important (to us) ASP.Net application which can't currently be ported to .NET Core because it depends on, inter alia, System.Drawing - then you perhaps need to work a little on the optics for that market.

  • Would it be feasible to have a big table of features with green check-marks and red crosses to show the current state of feature parity with System.Drawing and then get rid of the generic warning?
  • Can you get onto Nuget rather than MyGet? (albeit marked pre-release?)
  • Can you make it clear that it works with full .NET, rather than people needing to pick through the platform types?
  • Don't publicly admit that you taught yourself graphics programming while working on this library. :-)

I don't want to see anyone suffer 'a crushing blow', especially an unintended one from a myopic pachyderm like MS, but I went to look at ImageSharp this morning and came away discouraged.

I'd go with path of least resistance - identify which scenario would be the cheapest and do that. Put warnings all around that it's just compatibility layer that should not be used for new projects. If feeling bad about leftover manpower invest it in improving ImageSharp :)

@willdaen Out of curiosity, did you have a look at CoreCompat.System.Drawing (Mono's Sysyem.Drawing ported to Core)? Would that meet your requirements?

@Kukkimonsuta Yeah, that kind of is what I thought this was about - a cross platform impkementation of System.Drawing which people can (temporarly) use as they move to .NET Core - basically removing one of the adoption blockers.

It may not be the most pure solution, but it kind of is what libgdiplus was built for.

@willdean

I went to look at ImageSharp this morning and came away discouraged.

Really? Well that's terribly disappointing.

You didn't see the feature list linked to from the readme? I need to update that actually to add all our Porter Duff support and flesh out some other things.

Can you make it clear that it works with full .NET, rather than people needing to pick through the platform types?

It's in the readme.

Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and embedded/IoT scenarios

The message means exactly what it says. The API is subject to change. (Btw I don't think MS have redefined anything, they just rushed things certainly but alpha, beta etc is not defined by them)

What needs to be done to remove the "in early stages (alpha). As such, we cannot support its use on production environments until the library reaches release candidate status." warning from your project?

We need to do two things.

  1. Get the jpeg decoder refactored to reduce memory usage and speed it up.
  2. Change the IPixel interface to not pack/unpack from bytes and use predefined structs instead. RGBA32, RGB24, BGRA32, BGR24 and also use refs for the Vector4 packing/unpacking

After that I'm happy to move to beta and add the library to Nuget.

It's been a very slow alpha as I want to get this right. There's simply no point creating a half/baked graphics library. No one will use it and you'll end up wasting your time.

P.S. I've no shame in admitting I learned by doing. It's the absolute best way to thoroughly understand things IMO and I see it as an encouragement that others can do the same. 😄

@JimBobSquarePants Thanks for the response - I am really reluctant to hijack this thread by extending discussion about ImageSharp specifically... I'll give it a try and file any issues on your repo.

@qmfrederik I haven't looked at CoreCompat.System.Drawing - we're blocked on several things (SignalR is another) and I haven't really searched hard for alternatives to everything which is missing - I just keep a vague eye on what's happening to the things I know will cause us problems.

Lots of great feedback from anyone, thank you! (I didn't catch up on all of it)

Here's how I look at System.Drawing priorities and options:

Priorities

  1. Primary goal of System.Drawing is to simplify migration from Desktop to .NET Core (on Windows), i.e. high-compat between Desktop and Core implementation on Windows.
  2. Second-level goal is to support System.Drawing where it is needed -- currently Mono needs it on Mac & Linux (Mono is trying to reuse CoreFX libraries / source code, so synergy is desired).

    • Note: Mono has additional requirement on iOS -- size has to be small (i.e. it requires using Mac APIs - @marek-safar to confirm if I didn't mix it up with our discussion about HttpClient).

  3. Third-level (stretch) goal is to simplify migration from Windows to Linux/Mac on .NET Core.

    • Note: Easy migration does not necessarily imply great perf.

  4. Non-goals are high-perf, rich OS-specific capabilities, modern API surface, etc. (anything not listed in (1)-(3)).

Solutions

(2) Mono needs it on Mac & Linux

  • From our chats with @marek-safar we learned that Mono implementation on Linux is extremely involved - it requires special builds for specific versions of distros (not even just distros, but their versions).
  • We agreed with @marek-safar that Linux implementation's complexities are not something we should try to port to CoreFX (due to high cost, little value). We should rather prepare simple migration path (see (3)).
  • @migueldeicaza how does that align with your reply? Is libgdiplus the complex piece @marek-safar told me about? Is there a viable option to NOT migrate that part into CoreFX?

(3) simplify migration from Windows to Linux/Mac

  • Platforms which are easy to implement (e.g. Mac implementation from Mono), we should just implement.
  • For platforms which are not feasible to implement due to cost/benefit, we should recommend migration to 3rd party libraries (ImageSharp, SkiaSharp, etc.).
  • To make migration easier for devs, we could invest into shims which fake old System.Drawing API surface on top of the libraries. The shims would likely be in different namespace though (e.g. SkiaSharp.Drawing.Compat), so a small code change and/or cross-compilation would be required. The shims should probably live in the library repo, or in separate repo (not tied to CoreFX itself to avoid dependency of CoreFX on the libraries).

@karelz Regarding the Mono implementation on Linux, I don't think it is involved at all. In fact, libgdiplus, the native part, ships with all Linux distros .NET Core supports. So you don't need to port it, the distros have already done that for you. You can declare a dependency on libgdiplus pretty much the same way you do for openssl or curl - acquire it via the distro instead of NuGet.

You can consider distributing libgdiplus and its dependencies as native components un NuGet packages. That does get complicated, fast - I tried with CoreCompat.System.Drawing.

Mono/libgdiplus gets you 1, 2 and 3 at almost no cost - most of the work has already been done. The result is in the CoreCompat.System.Drawing NuGet package.

@qmfrederik I was relying only on info from @marek-safar who works on Mono - so I let him chime in.

Mr @qmfrederik is correct, libgdiplus is available on pretty much every Linux distribution already and it only relies on the unmanaged pieces. If Marek had any feedback on dealing with build complexity, it would be around you trying to ship a forked version of yours, and then you would have the same issues that you have in shipping CoreCLR - namely that you will want to compile for each system.

So that should not be an issue if you want to take the fastest/easiest path to bring System.Drawing.

You do not need to worry about iOS, for historical reasons System.Drawing is not used there and we do not even distribute it (beyond the basic Rectangle, Point types).

On iOS, the world has gone with either CoreGraphics or our cross-platform SkiaSharp.

Given the outlined priorities from @karelz, I would change my recommendation. Given these priorities, really System.Drawing wont be a long-term foundation for graphics, and I would essentially just reuse the Mono code.

Rely on gdiplus, and use Mono's System.Drawing stack (and ifdef out parts that wont work on .NET Core 2).

You can certainly choose a different path, but given the above goals, it sounds like investing on rewriting this code is a waste of time.

Given these priorities, really System.Drawing wont be a long-term foundation for graphics, and I would essentially just reuse the Mono code.

That was my thinking as well. With the caveat that we might want to use Desktop implementation on Windows to have higher-confidence compat (I've been bitten by HttpListener Mono compat recently, so I'm a bit cautious now ;)).

given the above goals, it sounds like investing on rewriting this code is a waste of time.

Agreed. We didn't plan huge investment. And I am not fan of wasting effort.

Overall, I don't think System.Drawing is such a great API & implementation that we want to innovate on it -- other projects like SkiaSharp, ImageSharp already did that, so let's rely on them to innovate for future in the graphics space. We should not reinvent the wheel in CoreFX, unless there are really good reasons. One .NET ecosystem to rule them all! ;)

Mono's System.Drawing approach has the advantage that it calls into the native GDI+, so on Windows, you get to use the one that ships with the system already. And if we get approval for release of .NET's System.Drawing, you can swap that out as well.

(I've been bitten by HttpListener Mono compat recently, so I'm a bit cautious now ;)).

The feeling is mutual @karelz, Mono has also been bitten by code in CoreFX :-)

I agree that more code/library sharing with Mono would be beneficial overall.
If we can address the compat concern on Windows (likely with lots of tests), I'd be fine with that approach :)

The feeling is mutual @karelz, Mono has also been bitten by code in CoreFX :-)

When you hit those cases (or if you have a list from the past), please send the feedback to us/me. While it is usually not actionable right away, it is critical to prevent such unfortunate situations in future. Without knowing about such cases, we won't fully understand the impact we have on Mono and we will likely keep doing what we're doing, causing you the same pain over and over again. Let's try to prevent that.

What would the next steps be? Building a NuGet package for .NET Core from Mono's sources is straightforward, as should be setting up CI and tests. Would Mono produce the package or would the code be exported to a separate repository under dotnet? There's also the work of ingesting the NetFX code if possible.

@karelz What part of the compat with Windows are you concerned about? Like I said, our System.Drawing is a thin layer on top of GDI+, so on Windows, it is the Windows GDI+ that does all the heavy lifting.

There are likely gaps in our implementation (both on the managed and unmanaged side) and those should be filled.

Perhaps the most comprehensive test that we have is Mono's Windows.Forms implementation, which emulates the entire toolkit (even on Windows) on top of pure GDI+. There are two other test suites, a unit testing suite that is already in place that tests some elements of GDI+ and one that we had for fuzzy-comparison of image outputs, this latter test suite needs to be brought back to life.

Logistically speaking, I would like to keep the same source tree compiling on both Mono and .NET Core to maximize code sharing, and avoid forks as this is a single unit.

Let us start by saying that we should add the proper ifdefs to make the source code compile both against .NET Core and Mono.

  • We can keep System.Drawing in Mono, and modify to build against .NET Core add build tools to produce a NuGet that is suitable to be published (easiest).

  • We can extract System.Drawing into a separate git module, to preserve history and have both Mono and CoreFX take a dependency.

  • We can outright copy the source code into CoreFX, and switch Mono to it, but we would lose the source control history that goes with it for future generations to debug. Perhaps the first commit should point to a mono/branch so people can resume archeologic debugging at that point.

@migueldeicaza Mono's System.Drawing+libgdiplus already compiles and works on .NET Core due to some great PRs that @qmfrederik sent over the last week/months :)

I agree that open sourcing the managed System.Drawing layer from .NET Framework would be great so we can replace our managed layer and be more compatible (I'm assuming that most of the work is done by the underlying gdiplus in the .NET Framework implementation too).

This would mean that on Windows you'd end up with a highly compatible version and other platforms have Mono's libgdiplus underneath. Moving to a separate repo or into corefx sounds good to me.

And of course we should promote ImageSharp etc as the modern alternative!

Thanks for all the great feedback, everyone. I'll try to summarize the main points discussed, and also try to clear up a couple of things.

  • Goals of the library. This whole effort is _strictly_ for compatibility. I think Karel described the priorities well, and especially the non-goals. I will copy that information up into my original post at the top. The important thing here is that we don't view this as an opportunity for innovation or for reinventing the wheel in any way. There are already really great community projects being driven by very smart, motivated, and dedicated people; there is no reason for us to undermine those efforts.

  • libgdiplus: One important piece of info that I didn't originally have is that libgdiplus is already available in tons of package managers across the Unix ecosystem. Including distros like Alpine. This makes it feasible to treat it like "any other native dependency", and expect users to download it from their system's package manager. My original belief was that it was always bundled and installed with mono, which would have made it much harder to depend on in .NET Core.

  • .NET Framework's version of System.Drawing. A few people brought this up, and I just wanted to clarify: we already have the "go-ahead" to open-source the Windows implementation that is being used in .NET Framework. I've gotten it to compile and have run some tests, but I need to do the usual clean-up and "sanitization" of the code before I share it, because it is coming from our internal, closed-source codebase. I'm going to do some tests now to see how much blows up when I try to run our .NET Framework implementation against libgdiplus on Ubuntu.

  • UWP support: Realistically, the only way I think we will get UWP support for this is if we write some sort of wrapper around API's that are available on UWP, or a managed implementation. I am okay with scoping this out of the discussion for now (and for the initial .NET Core implementation), but I'd like us to keep it in mind when thinking about the investments we are making. If we end up making large changes or rewrites of the code, we should do so in a way that supports UWP.

@mellinoe If you want to give things a try, have a look at the code here: https://github.com/corecompat/system.drawing. It contains a .NET Core 2.0 solution which compiles Mono's System.Drawing and runs the unit tests, and has CI support (Travis for Linux and AppVeyor for Windows)

Whatever happens, make sure it 100% works in Azure. GDI+ is highly restricted right now and most PDF libraries depend on this through System.Drawing

@JimBobSquarePants I own a couple of NuGet packages (SharpAdbClient, RemoteViewing, CoreCompat.Selenium) which somewhere in their public API return images (screenshots, really). These packages don't process the image -- it's the consumers of these packages that will do image processing.

Currently, they depend on System.Drawing and return an Image object. That creates a hard dependency on System.Drawing, something I don't necessarily want to do.

So I'm trying to figure out what I (and I guess others which encounter the same use case) can do to make sure my packages don't have a hard dependency on System.Drawing, and let my consumers decide how to process the images.

The only alternatives I can think of right now is

  • Return a byte[] or Stream object, which seems suboptimal, as it doesn't give me an option to communicate that 1) I'm returning an image object and 2) some of the image metadata (at least which codec,...).
  • Agree on some kind of base class in netstandard which contains a byte array and the necessary metadata so other libraries can process it
  • Have ImageSharp and others implement some kind of "System.Drawing bridge" -- i.e. you being able to consume a System.Drawing.Image and then convert it to your own Image class.

Thoughts?

@qmfrederik

I think something like @karelz described - a compat pack would be the best idea that would live in my repo. We'd bridge Image via loading/saving from streams and common primitives also.

You guys are doing a great job. Keep it up.
One thing I understood by reading this thread is it's not going to be easy to port system.drawing for .Net core and preserving cross compatibility.

Since .Net core is already not compatible with existing .Net framework, what if we have a minimal implemention of an API from scratch. Which wraps around platform specific APIs or a common cross platform library. (Cairo)
My major concern is we should not lose the cross compatibility of .Net core for features.
I am not advocating that we should not implement against system.drawing, but we should not lose the cross compatibility for the sake of legacy code compatibility.( But it's still great if we can achieve both)

Again good job .Net core team.

we should not lose the cross compatibility for the sake of legacy code compatibility

@vibeeshan025 My understanding is System.Drawing is being provided for legacy code compatibility and it is a legacy api. So compatibility is the high bar. For new code alternative apis are be encouraged. Its not available on UWP and it is explicitly discouraged for certain applications:

Classes within the System.Drawing namespace are not supported for use within a Windows or ASP.NET service. Attempting to use these classes from within one of these application types may produce unexpected problems, such as diminished service performance and run-time exceptions.

@mellinoe very clear summary! 👍

Managed (ImageSharp or custom)

One option is to implement the library in managed code. We can call into an existing library, like ImageSharp, for image-processing and graphics functionality. This is very desirable for a lot of reasons, but it does have a higher implementation cost.

Voting for this option. I bet image resizing and cropping are the 80%. I assume the implementation for both is purely computational and does not depend on any particular O/S specific API and even if there is some GPU accelerated version I bet there is a non-accelerated counterpart. Perhaps there is an option to extract the relevant native implementation code from Windows use it to create a small cross-platform library. Open sourcing it would nice but don’t think many would care so long it works..

@karelz @mellinoe I think it'd be better to open source .NET System.Drawing and use that on netcore and Mono on Windows. That'd bring 100% compatibility with no effort.

On linux my concern was that you'll fall into same trap as you did with OpenSSL dependency which might not be that bad if you the library is there for backward compatibility solution only.

OSX has been mostly ignored here but having libgdiplus dependency there is certainly not smooth experience as it brings whole X11 (not part of OSX anymore) dependency making self-contained app build tricky.

@marek-safar Regarding macOS & x11, you can now build libgdiplus for macOS without X11 and acquire libgdiplus via Homebrew.

Deployment of libgdiplus would be similar to the OpenSSL dependency, with all its pros and cons.

@qmfrederik right but that disables X11 features which at quick check looks will break DPI scaling.

@marek-safar Yup, (automatic) DPI scaling will not work with libgdiplus on macOS. I assumed that you'd only really need DPI scaling when using WinForms or another UI on the Mac itself; and that this wouldn't be much (if any) of a problem when using System.Drawing in ASP.NET Core or CLI tools?

@JimBobSquarePants

So where are we on this?

Using libgdiplus looked promising for a while but there's two glaring issues as far as I can see.

  1. Missing or broken features in Mono System.Drawing
  2. Using the library on a server.

We've all read the warning but have continued to ignored it so far. We cannot possible recommend to migrating developers that it's ok to use the compat pack on a server surely?

@qmfrederik Deployment of libgdiplus would be similar to the OpenSSL dependency, with all its pros and cons.

We got rid of OpenSSL dependency on Mac, mostly due to strong feedback from Mac users. I don't want to follow the same route (depending on Homebrew-installed dependency, which Homebrew can break).
If Mac code needs something that is NOT part of the OS, we should not make it part of CoreFX to avoid another nightmare and being "different"/"special". (feel free to call me out if I don't get all the nuances -
my primary goal is to optimize for simplicity, complexity usually bites back)

Overall: It seems to me that there is no simple solution for Mac. If that's correct, I would suggest to rely on 3rd-party libraries shims (see https://github.com/dotnet/corefx/issues/20325#issuecomment-304315828).

@marek-safar wht did you mean by Linux having same problems as OpenSSL? Overall we do not mind that too much on Linux AFAIK (on Mac it was much worse).
@janvorli can you comment on our position towards OpenSSL dependency on Linux? I think we made our peace with it, right? What are the biggest cons and how should we think about adding yet-another-dependency like that?

@karelz Hmm, I'm not sure I follow - why was openssl a problem on macOS and not on Linux? I don't see much of a difference between asking a user to run brew install vs apt-get install. The feedback you have gotten seems to be different, though?

I know there were a couple of issues with homebrew moving openssl files from one folder to another; I think it's rather unlikely that will happen for libgdiplus.

Alternatively, for macOS, you could consider distributing the libgdiplus.dylib file as part of the native/osx-x64 folder in the NuGet package. I need to check, I don't think libgdiplus has non-OS dependencies on mac, so that's a route that may work.

That would bring you to this deployment scenario:

  • Managed code:

    • Use System.Drawing (preferably from NetFX) on all platforms

  • Native code:

    • Windows: use GDI+

    • Linux: require libgdiplus via the package manager

    • macOS: ship libgdiplus.dylib as part of the NuGet package

See dotnet/runtime#17872 and dotnet/runtime#17597 - the delivery mechanism of the dependency was different AFAIK.

Redistributing more components means more work in shipping security updates. One of the reasons we don't redistribute OpenSSL, libcurl (there were lots of debates and back-and-forth on the topic).

If we can depend on something like OpenSSL/libcurl to be deployed as a dependency, then it might be a good option. I let @mellinoe research the options and provide recommendation.

Looking at those issues, it seems part of the problem was that OS X used to include openSSL, and Apple was moving away from it.

I don't think you'll have the same issue with libgdiplus as it is not part of OS X, but a "pure" 3rd party library.

Small update from my end:

  • I'm going to focus on getting the Windows version merged in, probably in the next day or two. The plan is still to use the existing implementation from .NET Framework. As I mentioned before, I already have it building, but I need to do open-source-related cleanup of the codebase.
  • Add a base level of tests for the library, borrowing test cases from mono. The tests cases need to be modified, because they do not pass as-is against the Windows implementation.
  • In parallel with the tests, begin porting the managed wrapper over libgdiplus from https://github.com/mono/mono. I think we should move to a state where all of the sources are shared from corefx. I believe mono already consumes some code from corefx, but we don't do the reverse.
  • As for the libgdiplus dependency being problematic on macOS -- I do think it is slightly different from the OpenSSL dependency. Using OpenSSL seems to be actively discouraged over there, but I don't think there's any such problem for libgdiplus. It's just a regular library which should be easily installable. There is always the option of using the CoreGraphics-based implementation instead, but let's start with libgdiplus first, since it's easier.

@JimBobSquarePants I agree that the GDI+ restrictions in Azure are troubling. I'd like to understand what exactly the limitations are, and which Azure products it applies to. My initial reading is that restrictions only apply to Azure Web Apps. The elephant in the room here is that it has always been clearly messaged that GDI+ and System.Drawing are not supported for use in server scenarios, so the restriction shouldn't be at all surprising...

We can likely work with Azure to get certain APIs whitelisted...I think.

On Tue, May 30, 2017 at 12:01 PM, Eric Mellino notifications@github.com
wrote:

Small update from my end:

  • I'm going to focus on getting the Windows version merged in,
    probably in the next day or two. The plan is still to use the existing
    implementation from .NET Framework. As I mentioned before, I already have
    it building, but I need to do open-source-related cleanup of the codebase.
  • Add a base level of tests for the library, borrowing test cases from
    mono. The tests cases need to be modified, because they do not pass as-is
    against the Windows implementation.
  • In parallel with the tests, begin porting the "libgdiplus"
    implementation from mono. I believe we should move to a state where all of
    the sources are shared from corefx. I believe mono already consumes some
    code from corefx, but we don't do the reverse.
  • As for the libgdiplus dependency being problematic on macOS -- I do
    think it is slightly different from the OpenSSL dependency. Using OpenSSL
    seems to be actively discouraged over there, but I don't think there's any
    such problem for libgdiplus. It's just a regular library which should be
    easily installable. There is always the option of using the
    CoreGraphics-based implementation instead, but let's start with libgdiplus
    first, since it's easier.

@JimBobSquarePants https://github.com/jimbobsquarepants I agree that
the GDI+ restrictions in Azure are troubling. I'd like to understand what
exactly the limitations are, and which Azure products it applies to. My
initial reading is that restrictions only apply to Azure Web Apps. The
elephant in the room here is that it has always been clearly messaged that
GDI+ and System.Drawing are not supported for use in server scenarios, so
the restriction shouldn't be at all surprising...


You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/dotnet/corefx/issues/20325#issuecomment-304975429,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAALTGdI8Aoog2-R5sg9ZhOGd9EhK8CCks5r_GeUgaJpZM4NmyU5
.

--
Scott Hanselman
Donate to fight diabetes: http://hnsl.mn/fightdiabetes

I think Microsoft is missing the point (may be wrong)

What we, customers, really need is an "Official recommended library from Microsoft for image manipulation for Windows, Linux and Osx".

My suggestion:

  1. Microsoft pushes libraries like ImageSharp (or any that fits) into production-ready.
  2. Create a System.Drawing.Compat for very common/high usage only apis which calls the underlying library.

while an official way doesn't exist, customers will still say (me incl) that System.Drawing is needed.

@Bartmax It would have been really nice for that to happen. (I could really do with the help and you know, OSS and all that) but the cards are stacked against it.

I personally think that building this sends the wrong message and I really, really wish it wasn't happening. There's zero incentive for developers to move on from System.Drawing once it is included and we all know it will continue to be used on the server.

It's gonna murder any adoption rates of my work. 😞

ImageSharp is an incredible opportunity for MS to prove the power of their new performance orientated libraries Vector, Matrix etc, Span<T> and also test their API's in a real world context. There's no reason, without help, that the library could not be blazingly fast across all platforms and usher in a better environment for developers trying to use graphics libraries in .NET. It pains me that this opportunity is being ignored and it frustrates me how little help I've received.

I've lost count of the amount of leaky, poorly thought out, copy/pasted System.Drawing examples I've found out there in the wild (That's why I originally wrote ImagProcessor). It's certainly not something we should be encouraging.

IMO the biggest pain point for netcore adoption is not missing libraries. It's the tooling. With an IDE that doesn't required constant reopening of solutions and a faster, better testing environment missing libraries would come much faster.

The tools I want to use, already uses GDI+. So changes to System.Drawing isn't going to matter much for them.

As for the tools that could use the new System.Drawing, they won't rewrite their own toolkit to implement things with a new model without some serious pressure from their community. I have no idea how to get the cake and eat it too... but it's a very complex problem indeed.

@JimBobSquarePants

IMO the biggest pain point for netcore adoption is not missing libraries. It's the tooling. With an IDE that doesn't required constant reopening of solutions and a faster, better testing environment missing libraries would come much faster.

+100 on this. I have tried to help @JimBobSquarePants with ImageSharp, but been utterly defeated by the Core-Pox. Resharper won't run the tests, NCrunch won't run the tests, coverage tools don't work, and to put the tin-hat on it all, nugetting ImageSharp into a netfx project imports such an enormous pile of (broken) dependencies that the project stops running completely. I don't think much of this is ImageSharp's fault, and to be fair, quite a bit isn't corefx's fault, but it's the reality as of today.

I actually really wanted to help with ImageSharp (as it stands, it's not yet ready for our application), but I cannot tolerate the 100:1 bollocks:coding ratio. And I'm in the top 20 lines-touched corefx contributors list (that's a fluke, but still...), so goodness knows what @shanselman's mate at the Department of Forestry makes of it all.

It doesn't matter if @JimBobSquarePants is hurt and disappointed that the world doesn't beat a path to his library, that's just tough. It does matter if his project can't get finished because of the sorry state of the MS ecosystem.

I do not pretend to understand how MS is currently structured, but whoever it is who sits above both the .NET and VS teams needs to smash some people together with sufficient force that the guys from CERN come round to look at the debris.

@mellinoe If the main delivery mechanism for libgdiplus on Linux are the Linux distributions, keep in mind that all distros currently consider https://github.com/mono/libgdiplus as the upstream repository.

It may be of interest to find a way to keep the CoreFX and Mono version of libgdiplus in sync (for example, by forking and porting upstream, or just using the upstream repository)

I think folks can understand that there are two System.Drawing implementations - the Mono one and the .NET one. I assume the Mono implementation will start converging towards the .NET one as soon as it is open sourced.

It would be confusing to have two libgdiplus implementations.

Regarding to Windows environments where GDI+ is not available (Azure & Nano server?), libgdiplus could be used on Windows, too.

wht did you mean by Linux having same problems as OpenSSL? Overall we do not mind that too much on Linux AFAIK (on Mac it was much worse).

@karelz I meant you need rely on distro package manager to prepare the dependencies but I guess for the Linux this is ok.

@qmfrederik my assumption was that we will just have yet-another external dependency (libgdiplus) as you mentioned earlier:

You can declare a dependency on libgdiplus pretty much the same way you do for openssl or curl - acquire it via the distro instead of NuGet.

However, if libgdiplus is part of Mono and not part of distros, it may impact/complicate our build-from-source effort. @mellinoe can you please poke at it?
That sounds like non-trivial increased cost to support Linux. If that's the case, I would prefer to just not support Linux and rely on shims to 3rd party libraries as discussed above.

@karelz libgdiplus is part of Mono, and it is being distributed by the Linux distros as a package in their package management system. Pretty much like libcurl and openssl are their own projects, but redistributed by the Linux distros.

To be prefectly clear, today you can do apt-get install libgdiplus on vanilla Ubuntu & Debian, and yum install libgdiplus on vanilla CentOS and RHEL, to name a few.

So I was a bit confused by @mellinoe talking about importing libgdiplus into the corefx source tree. I didn't expect that would happen, just like libcurl and openssl are not imported in the corefx source tree either.

@qmfrederik In my last post, I was only referring to the managed part of mono's libgdiplus-based implementation. I think that I worded it poorly, and it seemed like I meant something else. I will edit that post to clarify. I don't think we should move https://github.com/mono/libgdiplus anywhere.

@mellinoe Makes sense, thanks for the clarification!

Of course using SkiaSharp as the backing engine is the best choice.

  • mature and stable codebase
  • best performances
  • most supported platforms
  • actively mantained
  • text and fonts handling
  • both software and openGL rendering support for fast layers composition on non DX/GDI platforms
  • many 2D graphic files formats already handled (including SVG)

I'm of the (very strong) opinion that if we decide to rewrite significant portions of the library, then we should do so with pure managed code, not a native wrapper like SkiaSharp. I've mentioned it above, but here's my reasoning:

  • Managed code is "infinitely more portable" than a native wrapper. By definition, it's going to work anywhere the runtime does, whereas Skia will need to be recompiled for different platforms.
  • It's not clear that Skia can be compiled in the "portable Linux" way that we need it to be for use in .NET Core. At the very least it represents extra work. Libgdiplus is acceptable here because it is available in most / all package managers for distros that we care about.
  • Although Skia is extremely fast and can leverage the GPU for acceleration, I don't think that's a high priority for a compatibility library like this.

In any case, the current plan is to just re-use the existing implementations for System.Drawing, and not write any more new code than is required. The above kicks in if we decide that we really need to rewrite large portions of the library for one reason or another.

what's the state of this ? only plan to support .net framework,mono,.net core and leave UWP ?

@John0King Yes, that's correct. At the moment we've not identified a good way to support UWP without essentially re-implementing the whole library.

I believe it still needs modifying to reference 2.0 shipped binaries. @weshaggard is @mellinoe unblocked from doing that now? Is there anything else you need him to do to make this ready to ship in hte metapackage?

@danmosemsft The only thing we're waiting for now is the re-branding of corefx/master to "preview1" instead of "preview2". If we ship a version now, there is some upgrade-related ugliness around the fact that newer versions of the package (if we wanted to ship them) would always have a "lower" version than something with "preview2" in the name.

The standard System.Drawing implementation should target to become a wrapper of the SkiaSharp library as soon as possible, for the following reasons:

  • Very soon many .Net devs will start using it for developing the GUI of NetCore cross platform apps, and if the performance are lacking we would end making a remake of the Java Swing disaster. A slow and sluggy GUI would make cross platform development with NETCore much less palatable and will throw a bad light on all NETCore apps, like happened with Swing apps, that were never able to compete with native applications.
  • It is not true that we need a managed implementation of System.Drawing. There is no such thing as a managed graphics back end. When it comes to graphics devices, you have to work low level. Pointers, bitwise operations, memory allocations and data structures that fit the specific hardware architecture are necessary. This is the very reason why the old System.Drawing is not easy to port, being specifically linked to the windows drivers and the non managed GDI back end.
  • The work to support all different platforms low level is huge. Creating and maintaining many differents graphic backends is a waste of everybody time. Skia is already a cross platform open project with many contributors worldwide and with specific code for low level support added in the years for dozen of hardware platforms and OS. SkiaSharp, being a Skia wrapper, is a definitive solution for NetCore when it comes to cross platform graphics and the only one we have to maintain for true multiplatform durable and open code.
  • Making the standard version of System.Drawing a wrapper around SkiaSharp will also help developers to learn how to use SkiaSharp directly when they need to do more complex things, in a gradual way. A System.Drawing based on an ad hoc and specific graphic back end made only for the System.Drawing library, would make very difficult for devs to evolve or expand their code from their original code using System.Drawing to something more. They will likely end sticking to their old and slow original code, because leaving System.Drawing and rewriting all code at once for SkiaSharp would be too expensive. An underling SkiaSharp back end would allows devs to only expand their code gradually and only in those places where they need to improve the code calling directly the SkiaSharp methods.
  • System.Drawing would become just a huge sample project useful to learn SkiaSharp, the one and only cross platform graphic library that NETCore developers need to study.

Sorry @Emasoft , I don't agree with much of this. SkiaSharp is a fine library, and there would be nothing inherently wrong with building a System.Drawing-compatible layer on top of it. But that endeavor, and the costs associated with it, just don't line up with what we are trying to accomplish with this effort right now. We are simply trying to provide a migration story for folks with legacy assets to move into the new world of .NET Core. We don't want this to be the new future-facing, modern library for image manipulation.

Very soon many .Net devs will start using it for developing the GUI of NetCore cross platform apps, and if the performance are lacking we would end making a remake of the Java Swing disaster. A slow and sluggy GUI would make cross platform development with NETCore much less palatable and will throw a bad light on all NETCore apps, like happened with Swing apps, that were never able to compete with native applications.

There are already multiple UI frameworks running on .NET Core -- none of them use System.Drawing, and none of them should. Using System.Drawing in a future-facing library would be a very foolish decision. By its very nature it is slow and poorly-organized, and it has a VERY dated and archaic API. Since it is a semi-transparent wrapper over a Windows component (GDI+), much of its surface area is intrinsically non-portable. If you're building a new, modern, cross-platform UI stack, you really should be building on top of a library which was designed for such an environment, not GDI+.

It is not true that we need a managed implementation of System.Drawing. There is no such thing as a managed graphics back end. When it comes to graphics devices, you have to work low level. Pointers, bitwise operations, memory allocations and data structures that fit the specific hardware architecture are necessary. This is the very reason why the old System.Drawing is not easy to port, being specifically linked to the windows drivers and the non managed GDI back end.

Managed code supports all of the things that you mentioned. We are also making it a top priority to ensure that these types of things can be accomplished with .NET. I don't think System.Drawing needs to (or ever should) be such a library, but I do believe these things can be built with .NET. There are so many advantages to a managed solution over a native wrapper; I think it would be a failure on our part if we don't try our best to make these things possible.

The work to support all different platforms low level is huge. Creating and maintaining many differents graphic backends is a waste of everybody time. Skia is already a cross platform open project with many contributors worldwide and with specific code for low level support added in the years for dozen of hardware platforms and OS. SkiaSharp, being a Skia wrapper, is a definitive solution for NetCore when it comes to cross platform graphics and the only one we have to maintain for true multiplatform durable and open code.

Yes, I want to support as few backends as possible. Given that the libgidplus backend is already mostly-functional, that seemed to be the way to go initially. Creating a SkiaSharp-based backend would constitute creating an entirely new wrapper layer; it's a LOT more work.

Making the standard version of System.Drawing a wrapper around SkiaSharp will also help developers to learn how to use SkiaSharp directly when they need to do more complex things, in a gradual way.

I understand what you're saying, and I agree to an extent. This reasoning was why I considered writing the wrapper layer over ImageSharp rather than libgdiplus. If we were considering writing a new wrapper layer, I would still strongly advocate that we use ImageSharp, because pulling in yet another native dependency is going in the wrong direction for .NET Core.

@mellinoe I totally agree with your stand on this; however @Emasoft does hint to a valid concern about users viewing System.Drawing as a legitimate / preferable dependency for future graphics development by virtue of its presence in the BCL. History shows that “fit for purpose" statements about API's “doesn’t hold water” they tend to cause confusion and uncertainty among users (example: NET FX System.Drawing not recommend in ASP.NET / My frustration with HttpListener labeled "legacy" in .NET Core). There is also a substantial cost involved in communicating such statements (doc’s/ blogs). I also suspect that future introductory books about .NET Core will choose to cover System.Drawing to address simple graphics scenarios and that will effectively further establish the API and extend its lifetime. So I think we need a more formal strategy to deal with components that are included in the BCL purely for backward compatibility. Perhaps putting such components in a dedicated “Compatibility” namespace so to align expectations upfront, down at the code level.

@clrjunkie

I think we need a more formal strategy to deal with components that are included in the BCL purely for backward compatibility.

Yes, this is something we've long struggled with on the team, and I think it's something we're still learning how to best manage. We've discussed various strategies, but haven't settled on anything satisfactory. Many of those past options have been removed to us in our more recent releases, so it's even more important for us to figure out how to message this stuff properly.

Hello,

Generally, for new code, I would advocate using SkiaSharp directly for the many arguments already made in this thread.

For providing System.Drawing to .NET Core, adopting libgdiplus has the advantage that it has very low friction, the work to package this across the board and make it available is already there and some of the hard pieces of text rendering are already in place.

A win with the current approach is that System.Drawing on Windows would consume the Windows libgdiplus, which means that compatibility there will be great. The downside is that unless folks file and provide good bug reports and patches, the Unix version will not match it - it is very close, but there are a few known gaps and some known gaps to some of those that worked on it that might not be extensively documented -- Ill file issues later.

For those concerned about performance, a path that could be taken in the future is to replace the use of Cairo in libgdiplus with Skia, and that would not affect the managed code at all, it would be a transparent swap out.

I would not go as far as @mellinoe has gone with his criticism of GDI+, I think some of it is painting it with a too wide brush, but this is right on target:

If you're building a new, modern, cross-platform UI stack, you really should be building on top of a library which was designed for such an environment, not GDI+.

Exactly. And given that there is something of the caliber of SkiaSharp, you should use that instead. And this is exactly what projects like Avalonia have done. Google is continuously evolving it, and we are keeping it fresh and up to date, and it is the foundation of two of the most used pieces of software in the world.

Is there any chance to get netstandard stub like it done for Microsoft.Win32.Registry?

@FixBo which stub do you mean? Throwing PlatformNotSupported exceptions on platforms which are not supported?

@FixBo you rae correct that we normally do that, eg in this case so code that references it could load on UAP (even if not use it). @FixBo what platform would you need this for -- what scenario?

@danmosemsft I'm in process of porting a set of libraries to netstandard. Some part's of them relies on Drawing, eg document exporting/printing. On the other hand document generation/pdf export/dataaccess can work on pure netstandart.
For now I have three options: change target to netcore, split dlls, use a lot of defines.

@FixBo Is your issue that System.Drawing.Common targets netcoreapp2.0 but not netstandard?

@qmfrederik Correct.

@FixBo I think the plan is to have System.Drawing.Common target netstandard but @mellinoe can confirm.

@qmfrederik At the moment, it actually relies on some private state in the Color structure which is not publically available in netstandard2.0, but are implemented (privately) in .NET Core 2.0. We would have to make some structural changes to the code to re-target to .NET Standard 2.0. Perhaps it is worth looking into after we are stable and solid.

Will System.Drawing.ColorTranslator (I miss System.Drawing.ColorTranslator.FromHtml) be available at net core 2.1?

@safern can you please comment on FromHtml?

I can't use https://www.nuget.org/packages/System.Drawing.Common from a .NET Standard project:
error NU1202: Package System.Drawing.Common 4.5.0-preview1-25718-03 is not compatible with netstandard2.0 (.NETStandard,Version=v2.0). Package System.Drawing.Common 4.5.0-preview1-25718-03 supports: netcoreapp2.0 (.NETCoreApp,Version=v2.0)

Do you have any plan to make it compatible with .NET Standard projects too. Library authors would appreciate it.

I can't use https://www.nuget.org/packages/System.Drawing.Common from a .NET Standard project:
error NU1202: Package System.Drawing.Common 4.5.0-preview1-25718-03 is not compatible with netstandard2.0 (.NETStandard,Version=v2.0). Package System.Drawing.Common 4.5.0-preview1-25718-03 supports: netcoreapp2.0 (.NETCoreApp,Version=v2.0)
Do you have any plan to make it compatible with .NET Standard projects too. Library authors would appreciate it.

Hi @VahidN thanks for bringing that up. We created issue: https://github.com/dotnet/corefx/issues/24675 and I'll be solving the issue in the upcoming days.

Will System.Drawing.ColorTranslator (I miss System.Drawing.ColorTranslator.FromHtml) be available at net core 2.1?

I will have to look why we didn't port it from the beginning. @jmroyb I'll update you on that.

@mellinoe and others: it has taken me considerable time but I’ve read - carefully - through each article nd every comment here. Without weighing in on some of the implementation specific details, I have a question that was mentioned in passing but not addressed.

What is the benefit to providing this as a part of .NET Core? Please do not misunderstand me - I appreciate the importance of System.Drawing as a blocking dependency preventing any legacy codebases from migrating to .NET Core. But why must this be addressed by adding a legacy, deprecated, highly system-specific library to the actual .NET Core/Standard specification and implementation? To rephrase: how would simply providing a NuGet package that implements, via one or more of the means discussed above, System.Drawing on these specific platforms not be a sufficient stopgap to help these developers that do not have access to the needed resources to migrate to a better/more modern alternative?

By including this in .NET Core, we are not only committing to providing a solution for platforms currently supported by CoreFx but also severely restricting what platforms CoreFx can be easily ported to in the future.

It’s all well and good to discuss how awesome SkiaSharp support is on Linux and Android and whatnot, but what happens when someone wants to port CoreFx to SPARC or even just some archaic ARM variant not supported by Skia or libgdiplus or whatnot? What about FreeBSD? Or another OS not yet developed?

I am all for developing this library, but I think careful thought needs to be given to its repercussions. I am speaking as a die-hard SWF fan that shunned Silverlight, WPF, and XAML until Windows 10 Creators Update, when I finally embraced UWP. I deeply appreciate the difficulties in learning the subtleties and nuances of new toolkit’s and the difficulties in migrating to a platform with missing dependencies. But what is to be gained by bending over backwards to support System.Drawing?

It seems this discussion has almost derailed into a conversation about support SWF on CoreFx and lost sight of the initial goal: to make it easier for applications consuming SD to migrate to CoreFx. I would really appreciate if you would take a step back and perhaps reevaluate this. I do appreciate the time and effort already invested in this undertaking, but none of it need by lost, simply publishing everything discussed but under the context of a separate package than part of .NET Core would accomplish all the same goals.

Now to ask a different question that was previously asked but not answered: while SD is definitely a major blocking concern for users of legacy .NET FX apps on Windows blocking their migration to CoreFx, what is the actual number of projects that would benefit from this being a Windows-only NuGet package vs this giant effort to support it across all platforms? How many organizations currently using SD are expected to migrate to .NET Core if SD were available and expand to non-Windows platforms that haven’t already used a better alternative to SD/GDI+?

@mqudsi I assume its being provided via the Windows Compatibility Pack for .NET Core rather than being part of .NET Standard

If I understand clearly your concern is that by adding System.Drawing to .NET Core it would make really hard to extend/port this platform to another framework, platform using different libraries right?

If that is your concern and I understood correctly, System.Drawing is not inbox in .NET Core neither netstandard, which means that it is shipped as an independent package in NuGet and if people want to use it they just reference that package in their project. This was done as a compatibility effort to make migration from .NET Framework to .NET Core easier. So we are accomplishing both goals, compatibility and we’re not adding this as part of the framework inbox. Which for your concern (which is reasonable) doesn’t affect you either from what I understood in your comment. Please feel free to correct me if that wasn’t your main concern.

To answer your last question, the point of .NET Core is provide portability and extensibility to different platforms, if developers have a .NET Framework app that use System.Drawing but they don’t want to port it because they don’t gain much because it will be Windows-Only supported (the same as they have in their current app), what would be the pint of this effort vs if we support it in Unix also, they gain the portability of their app for free and is a good incentive to move to .NET Core.

@mqudsi I assume its being provided via the Windows Compatibility Pack for .NET Core rather than being part of .NET Standard

Correct, and also as an individual package in NuGet.

Thank you! This is my second forray into the github page for CoreFX (first was my PR from earlier today) and presumed the issue being here meant it was part of “core” CoreFx.

Thanks for clarifying!

To answer your last question, the point of .NET Core is provide portability and extensibility to different platforms, if developers have a .NET Framework app that use System.Drawing but they don’t want to port it because they don’t gain much because it will be Windows-Only supported (the same as they have in their current app), what would be the pint of this effort vs if we support it in Unix also, they gain the portability of their app for free and is a good incentive to move to .NET Core.

I would argue that offering System.Drawing support on Unix would offer zero incentive to move to .NET Core

I would argue that offering System.Drawing support on Unix would offer zero incentive to move to .NET Core

There seems to be demand for System.Drawing on .NET Core; CoreCompat.System.Drawing, CoreCompat.System.Drawing.v2 and System.Drawing.Common combined have over 300,000 downloads on NuGet.

To put the "giant effort to support it across all platforms" in some context: Mono has ported System.Drawing to Linux and macOS more than 10 years ago. They've also made libgdiplus compatible with ARM-based OSes such as Android and iOS. What CoreFX is doing is consolidating the Windows, Linux and macOS implementations of System.Drawing.

At the end of the day, it's another NuGet package which is available to you, and you're free to use any imaging library which meets your requirements 😄.

There seems to be demand for System.Drawing on .NET Core; CoreCompat.System.Drawing, CoreCompat.System.Drawing.v2 and System.Drawing.Common combined have over 300,000 downloads on NuGet.

Now that System.Drawing is out of the BCL (a good thing) I think it's important we have new basic (but quality) image manipulation support as part of the BCL, specifically for image resizing and rotation which does not mandate any external dependency.

@safern is this issue still useful? We already have an issue to track marking the compat pack stable. If we want to track consolidating the Windows and Unix code further it would probably make sense to have a new more focused issue.

Agree. We can close this issue.

I was researching a way to migrate our apps toward net core with Platform Extensions and noticed that System.Drawing.ImageConverter is not present in Platform Extensions. Is it going to be suppported? Is this the best place to report such thing, or should I raise a separate issue or report it somewhere else ?

@voltcode probably should open a new issue; for tracking, if its not already in the Windows Compatibility Pack

@voltcode this would be a new issue, but actually it is already being added in https://github.com/dotnet/corefx/pull/33092 which you can track.

After that .NET Core should have everything that was in System.Drawing.dll in the System.Drawing namespace - I just checked.

doenst work with blazor Webassembly

Was this page helpful?
0 / 5 - 0 ratings

Related issues

v0l picture v0l  ·  3Comments

jzabroski picture jzabroski  ·  3Comments

btecu picture btecu  ·  3Comments

nalywa picture nalywa  ·  3Comments

chunseoklee picture chunseoklee  ·  3Comments