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

Remove image sharp #2069

Merged
merged 7 commits into from
Jun 4, 2023
Merged

Remove image sharp #2069

merged 7 commits into from
Jun 4, 2023

Conversation

pgrawehr
Copy link
Contributor

@pgrawehr pgrawehr commented Apr 12, 2023

Fixes #1767 (and probably a few others)

  • Removes all references to ImageSharp from Iot.Device.Bindings.dll
  • Replace standard types (Point, Color, Size, Rectangle) with the types from System.Drawing.Primitives.dll (these are supported on all platforms and even part of the core runtime)
  • Provide a new BitmapImage class, which is an abstract image type
  • Provide a ImageFactoryRegistry static class to create instances of images
  • Provide a SkiaSharpConnector as (one possible) adapter that provides the actual implementation.

I suggest to create a new package for the SkiaSharpConnector (maybe better name it SkiaSharpAdapter?), because other adapters are possible (e.g. plain old System.Drawing.Imaging would do if only used on Windows) and because SkiaSharp is a quite heavy dependency (~100MB with the native dependencies)

Rough testing done, but a quick test of all the heavier affected bindings would be good:

Microsoft Reviewers: Open in CodeFlow

@pgrawehr pgrawehr added breaking-change area-device-bindings Device Bindings for audio, sensor, motor, and display hardware that can used with System.Device.Gpio labels Apr 12, 2023
@joperezr
Copy link
Member

[Traige]: FYI @raffaeler and @krwq, we are going to do a design review next week to look into this one in more detail. @pgrawehr will go over the problem and design, so we can try to get this in for 3.0.

@joperezr joperezr added this to the v3.0.0 milestone Apr 13, 2023
@pgrawehr
Copy link
Contributor Author

pgrawehr commented Apr 13, 2023

[Triage]

With Jose's words, this PR tries to solve these problems:

  • We are removing our tight dependency from ImageSharp
  • We try to avoid just replacing all ImageSharp types we currently expose to whatever new API we use, to avoid further breaking changes in case we need to change APIs again
  • Instead, in the APIs we expose, we only use system types (e.g. from System.Drawing.Primitives) and some self-made abstraction types (e.g. BitmapImage)
  • We provide an implementation for this abstraction (using SkiaSharp),
  • Optionally, we might provide an implementation using other implementations, like the old one from System.Drawing.Common, for Windows only.
  • The abstraction layer sits on top of Iot.Device.Bindings, so that it doesn't need to be referenced when not needed.

@@ -22,6 +22,7 @@
<!-- Excluding samples and test projects when getting source files -->
<_ExcludeProjectReferences Include="$(DeviceRoot)**/samples/**/*.csproj" />
<_ExcludeProjectReferences Include="$(DeviceRoot)**/tests/**/*.csproj" />
<_ExcludeProjectReferences Include="$(DeviceRoot)SkiaSharpConnector/*.csproj" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why do we need to exclude this? This would make it so that the connector won't be part of the iot.device.bindings library.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see you created an extra package for that. Can you explain what is the reasoning for separating that piece?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's by design. The intent is that the connector is in an extra nuget, so that it can be exchanged for another implementation without touching Iot.device.bindings or its API. That means that we can provide different connectors without affecting the API (or the user could write their own).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not following why keeping it as part of IoT.Device.Bindings would not allow for an external implementation without touching Iot.device.bindings. The part that seems wrong to me, is that we are adding an abstract class with no default implementation on the main library, which seems wrong, as ideally we would want IoT.Device.bindings to not require additional packages on top for some bindings to work. Model of having an included implementation on the library, plus the ability to use a different one via a NuGet package seems to satisfy both goals.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to avoid that the iot.device.bindings again references large dependencies. SkiaSharp's native implementation file is ~10MB per platform, and one might not want to carry this dependency around if one doesn't need it. There are many bindings that don't need any imaging features, so if you only use those bindings, you would not need to have a large dependency lying around for nothing.

So either we have some bindings that require an additional package to work, or we have many bindings that pull a dependency they don't need. I prefer the first solution.

@@ -35,60 +35,58 @@ public override int PinCount
get;
}

public override int ConvertPinNumberToLogicalNumberingScheme(int pinNumber)
/// <inheritdoc />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm failing to understand how all of the changes in the Arduino device are related to this PR. Are those just there because when working on the sample you made some adjustments? Does it make sense to separate these changes into a separate PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, most of these changes are improvements for the example. I'm considering to split that into a separate PR, but I'm waiting until the design has been roughly finalized, to avoid diverging branches.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, yeah, we'd probably want those in a separate PR. If anything, it'll make reviewing this PR easier 😃

@pgrawehr
Copy link
Contributor Author

pgrawehr commented May 4, 2023

This is now mostly ready for review. A few points still:

  • The new package needs to be generated. Haven't found how to do that yet.
  • The example code and the actual graphics library changes should be separated to separate PRs or at least commits.

@pgrawehr pgrawehr marked this pull request as ready for review May 4, 2023 20:07
This is an adapter that uses SkiaSharp as Bitmap handling backend
@pgrawehr
Copy link
Contributor Author

I have now rebased the PR and separated the changes into logical units. Even though they're not fully independent, it should be easier to review now.

I suggest to use Rebase or real merge instead of squash for this PR, to keep the stuff separated in history.

@@ -6,7 +6,7 @@
<PreReleaseVersionLabel>prerelease</PreReleaseVersionLabel>
<MicrosoftDotNetGenAPIPackageVersion>7.0.0-beta.23211.2</MicrosoftDotNetGenAPIPackageVersion>
<!-- dotnet/corefx dependencies -->
<SystemDrawingCommonPackageVersion>5.0.3</SystemDrawingCommonPackageVersion>
<SystemDrawingCommonPackageVersion>6.0.0</SystemDrawingCommonPackageVersion>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason why we are picking 6.0 as opposed to latest?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just took the version matching the TargetRuntime version we currently use, but I suppose we could use the 7.0 version as well.

@@ -1,6 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:BuildHat.Models.BuildHatInformation</Target>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's really hard to go through all of these, but I'm assuming they are pretty much all around removing APIs that exposed ImageSharp types, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are mainly three kinds of changes:

  • Apis that were changed from ImageSharp to System.Drawing (e.g. ImageSharp.Color to System.Drawing.Color)
  • Apis that were using ImageSharp bitmap types now take Iot.Device.Common.BitmapImage
  • Individual types that were in incorrect namespaces

For the user, the changes typically only require adapting their using definitions.

BitmapImage is the new abstract bitmap type.
Changed most bindings to use the new type, and also changed
bindings to use System.Drawing again for those types that will
still be supported on all platforms (Color, Size, Rectangle, Point)
@pgrawehr
Copy link
Contributor Author

Isolated the relevant changes now. The Ili9341 binding is temporarily broken now, though. The fix will be provided in a separate PR.

<Project>
<!-- Packaging related properties -->
<PropertyGroup>
<MajorVersion>2</MajorVersion>
Copy link
Member

@krwq krwq Jun 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we actually care about those versions and settings? Presumably adapter is not getting shipped directly as nuget?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait a sec.. looking at code later seems like this will be a new package, do we actually need it? could this be constructed similarly to other common projects for Iot.Device.Bindings? I might have missed the triage meeting where you discussed this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we want this as a separate package (to reduce dependencies for those who do not need any bitmap functionality or want to use another imaging library).

I'm not sure how the version for the release is actually computed in our setup though (question for @joperezr )

Copy link
Member

@krwq krwq Jun 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for version we either match Iot.Device.Bindings and System.Device.Gpio or we start from 1 and only update when needed (I'm personally not super opinionated on that - matching versions seems easier)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should match, which is the case now (IIRC). But we should (in a separate PR) maybe improve the way the version number is calculated or defined, so we're sure we get the same number everywhere. That is burried somewhere in the arcade repo I guess, and I don't really understand that stuff.

@@ -57,8 +55,7 @@ public void DoTest()
float blueCorrection = 0.95f;
while (!Console.KeyAvailable)
{
var hsv = new Hsv(angle, 1.0f, 1.0f);
var rgb = converter.ToRgb(hsv);
Color rgb = Color.Blue;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this sample just displaying blue rather than color changing over time?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this will be fixed with the next PR.

@@ -1,7 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace BuildHat.Models
namespace Iot.Device.BuildHat.Models
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this need sample update?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment, it appears to build fine.

/// A test method
/// </summary>
/// <param name="lcd">The lcd driver</param>
public static void ValueTest(ICharacterLcd lcd)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where are you calling this new method?

{
if (s_currentFactory == null)
{
throw new InvalidOperationException("No image factory registered. Call BitmapImage.RegisterImageFactory() with a suitable implementation first. Consult the documentation for further information");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if there are some steps needed it might be good to possibly display a link where they can find more info. We should discuss if we want to do that during our triage meeting and if so what's the location we want those at

return s_currentFactory!.CreateBitmap(width, height, pixelFormat);
}

private static void VerifyFactoryAvailable()
Copy link
Member

@krwq krwq Jun 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you put [MemberNotNull(nameof(s_currentFactory))] on this method then you won't need ! like: s_currentFactory! everywhere

/// Return the data pointer as a raw span of integers. For 32bit images, this equals to one pixel per entry.
/// </summary>
/// <returns>A span of integers</returns>
public virtual Span<int> AsIntSpan()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this need to be virtual? could it be just regular method? do we actually need it? Would be better if callsite did conversion

/// </summary>
/// <param name="stream">The stream to save the data to</param>
/// <param name="format">The image format</param>
public abstract void SaveToStream(Stream stream, ImageFileType format);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider WriteToStream or just WriteTo since Stream is an argument it's obvious we're writing to stream


if (Environment.OSVersion.Platform != PlatformID.Win32NT)
{
throw new PlatformNotSupportedException("This operation is only supported on Windows");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wasn't system.drawing available on Linux as well (IIRC it needed libgdi+)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer. System.Drawing V6.0+ is only supported on Windows. Without this test, I get a warning about an unsupported platform-specific call.

/// This declarative interface provides the basis for drawing functions using image specific APIs.
/// This interface is empty. The implementation is provided via extension methods.
/// </summary>
public interface IGraphics : IDisposable
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not just regular class in that case? what's the value of this being interface and having extension methods elsewhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't really matter whether it's an interface or an empty class type. In either case, it's intended to be a handle only. The reason I'm using extension methods is because it is up to the implementation to provide the set of drawing functions. With this approach, it is possible to add new drawing functions without changing the interface.

/// <summary>
/// Undefined
/// </summary>
Undefined,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we should stick with either Undefined or Unspecified everywhere

@@ -6,6 +6,7 @@
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

#pragma warning disable CA1416 // Temporarily, will be removed after binding is updated
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please remove the comment and replace with link to new issue (unless it's already in the other PR)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment everywhere where applies

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's part of the other PR.

Color.FromRgba(0, 0, 0, byte.MaxValue),
Color.FromRgba(0, 0, 0, byte.MaxValue),
Color.FromRgba(0, 0, 0, byte.MaxValue)
Color.FromArgb(255, 0, 0, 0),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can just not pass the first value now

var myBitmap = BitmapImage.CreateFromStream(stream);
var g = myBitmap.GetDrawingApi();
g.DrawText(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), "Tahoma", 20, Color.White, new Point(0, 0));
using (var ms = new MemoryStream())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any particular reason for not using non-nesting using?

@@ -7,6 +7,7 @@

<ItemGroup>
<ProjectReference Include="../../../Media.csproj" />
<ProjectReference Include="..\..\..\..\SkiaSharpAdapter\SkiaSharpAdapter.csproj" />
Copy link
Member

@krwq krwq Jun 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix/me\please

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(fixing line above would be more consistent though)

/// <inheritdoc />
public BitmapImage CreateFromStream(Stream file)
{
using var image = SKImage.FromEncodedData(file);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

once this gets disposed presumably bitmap won't?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jup, it wouldn't work otherwise...

/// <summary>
/// An abstract class that acts as a data container for device-dependent pixel arrays
/// </summary>
public abstract class RawPixelContainer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could this potentially use your bitmap image with correct image format?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided against it, because the data format used by this class (or rather, it's descendants) is hardware-specific. If possible, I want to keep the BitmapImage to only support 32-Bit RGB images, to keep things simple and fast.

@@ -0,0 +1,13 @@
<Project>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overall do we need to update samples to mention that package should be used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's done (or will be done) for all projects requiring the imaging library.


<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<LangVersion>10</LangVersion>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use Latest instead? That would make sure that this is not pinning down in the future.

@@ -10,7 +10,7 @@
using System.Threading.Tasks;
using Iot.Device.Ft4222;
using Iot.Device.Ili9341;

#pragma warning disable CA1416 // Temporarily, will be removed after binding is updated
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you either explain what is this for or add a link to an issue to track us actually doing this work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be fixed with #2084, which shall be reviewed and merged just after.

Copy link
Member

@Ellerbach Ellerbach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing work! Ship it :-)

src/devices/RGBLedMatrix/RgbLedMatrix.cs Outdated Show resolved Hide resolved
src/devices/RGBLedMatrix/RgbLedMatrix.cs Outdated Show resolved Hide resolved
Copy link
Member

@krwq krwq left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address as you see fit since there is nothing particularly blocking. It would be good to cleanup the library from helper methods.

@pgrawehr pgrawehr merged commit ded7ddf into dotnet:main Jun 4, 2023
@pgrawehr pgrawehr deleted the RemoveImageSharp branch June 4, 2023 18:17
@github-actions github-actions bot locked and limited conversation to collaborators Dec 8, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-device-bindings Device Bindings for audio, sensor, motor, and display hardware that can used with System.Device.Gpio breaking-change
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Library requirements to replace System.Drawing
5 participants