diff --git a/sources/engine/Stride/Graphics/StandardImageHelper.Desktop.cs b/sources/engine/Stride/Graphics/StandardImageHelper.Desktop.cs
new file mode 100644
index 0000000000..8e348cb373
--- /dev/null
+++ b/sources/engine/Stride/Graphics/StandardImageHelper.Desktop.cs
@@ -0,0 +1,115 @@
+// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
+// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
+#if STRIDE_PLATFORM_DESKTOP
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using FreeImageAPI;
+using Stride.Core;
+using RotateFlipType = FreeImageAPI.RotateFlipType;
+
+namespace Stride.Graphics
+{
+ ///
+ /// This class is responsible to provide image loader for png, gif, bmp.
+ ///
+ partial class StandardImageHelper
+ {
+ static StandardImageHelper()
+ {
+ NativeLibraryHelper.PreloadLibrary("freeimage", typeof(StandardImageHelper));
+ }
+
+ public static unsafe Image LoadFromMemory(IntPtr pSource, int size, bool makeACopy, GCHandle? handle)
+ {
+ using var memoryStream = new UnmanagedMemoryStream((byte*)pSource, size, capacity: size, access: FileAccess.Read);
+ using var bitmap = FreeImageBitmap.FromStream(memoryStream);
+
+ bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
+ bitmap.ConvertColorDepth(FREE_IMAGE_COLOR_DEPTH.FICD_32_BPP);
+
+ var image = Image.New2D(bitmap.Width, bitmap.Height, 1, PixelFormat.B8G8R8A8_UNorm, 1, bitmap.Line);
+
+ try
+ {
+ // TODO: Test if still necessary
+ // Directly load image as RGBA instead of BGRA, because OpenGL ES devices don't support it out of the box (extension).
+ Utilities.CopyWithAlignmentFallback((void*)image.PixelBuffer[0].DataPointer, (void*)bitmap.Bits, (uint)image.PixelBuffer[0].BufferStride);
+ }
+ finally
+ {
+ if (handle != null)
+ handle.Value.Free();
+ else if (!makeACopy)
+ Utilities.FreeMemory(pSource);
+ }
+
+ return image;
+ }
+
+ public static void SaveGifFromMemory(PixelBuffer[] pixelBuffers, int count, ImageDescription description, Stream imageStream)
+ {
+ SaveFromMemory(pixelBuffers, description, imageStream, FREE_IMAGE_FORMAT.FIF_GIF);
+ }
+
+ public static void SaveTiffFromMemory(PixelBuffer[] pixelBuffers, int count, ImageDescription description, Stream imageStream)
+ {
+ SaveFromMemory(pixelBuffers, description, imageStream, FREE_IMAGE_FORMAT.FIF_TIFF);
+ }
+
+ public static void SaveBmpFromMemory(PixelBuffer[] pixelBuffers, int count, ImageDescription description, Stream imageStream)
+ {
+ SaveFromMemory(pixelBuffers, description, imageStream, FREE_IMAGE_FORMAT.FIF_BMP);
+ }
+
+ public static void SaveJpgFromMemory(PixelBuffer[] pixelBuffers, int count, ImageDescription description, Stream imageStream)
+ {
+ SaveFromMemory(pixelBuffers, description, imageStream, FREE_IMAGE_FORMAT.FIF_BMP);
+ }
+
+ public static void SavePngFromMemory(PixelBuffer[] pixelBuffers, int count, ImageDescription description, Stream imageStream)
+ {
+ SaveFromMemory(pixelBuffers, description, imageStream, FREE_IMAGE_FORMAT.FIF_PNG);
+ }
+
+ public static void SaveWmpFromMemory(PixelBuffer[] pixelBuffers, int count, ImageDescription description, Stream imageStream)
+ {
+ throw new NotImplementedException();
+ }
+
+ private static unsafe void SaveFromMemory(PixelBuffer[] pixelBuffers, ImageDescription description, Stream imageStream, FREE_IMAGE_FORMAT imageFormat)
+ {
+ using var bitmap = new FreeImageBitmap(description.Width, description.Height);
+ bitmap.ConvertColorDepth(FREE_IMAGE_COLOR_DEPTH.FICD_32_BPP);
+
+ // Copy memory
+ var format = description.Format;
+ if (format is PixelFormat.R8G8B8A8_UNorm or PixelFormat.R8G8B8A8_UNorm_SRgb)
+ {
+ CopyMemoryBGRA(bitmap.Bits, pixelBuffers[0].DataPointer, pixelBuffers[0].BufferStride);
+ }
+ else if (format is PixelFormat.B8G8R8A8_UNorm or PixelFormat.B8G8R8A8_UNorm_SRgb)
+ {
+ Utilities.CopyWithAlignmentFallback((void*)bitmap.Bits, (void*)pixelBuffers[0].DataPointer, (uint)pixelBuffers[0].BufferStride);
+ }
+ else if (format is PixelFormat.R8_UNorm or PixelFormat.A8_UNorm)
+ {
+ // TODO Ideally we will want to support grayscale images, but the SpriteBatch can only render RGBA for now
+ // so convert the grayscale image as an RGBA and save it
+ CopyMemoryRRR1(bitmap.Bits, pixelBuffers[0].DataPointer, pixelBuffers[0].BufferStride);
+ }
+ else
+ {
+ throw new ArgumentException(
+ message:
+ $"The pixel format {format} is not supported. Supported formats are {PixelFormat.B8G8R8A8_UNorm}, {PixelFormat.B8G8R8A8_UNorm_SRgb}, {PixelFormat.R8G8B8A8_UNorm}, {PixelFormat.R8G8B8A8_UNorm_SRgb}, {PixelFormat.R8_UNorm}, {PixelFormat.A8_UNorm}",
+ paramName: nameof(description));
+ }
+
+ // Save
+ bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
+ bitmap.Save(imageStream, imageFormat);
+ }
+ }
+}
+#endif
diff --git a/sources/engine/Stride/Graphics/StandardImageHelper.Windows.cs b/sources/engine/Stride/Graphics/StandardImageHelper.Windows.cs
deleted file mode 100644
index 37750c0be2..0000000000
--- a/sources/engine/Stride/Graphics/StandardImageHelper.Windows.cs
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
-// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-#if STRIDE_PLATFORM_DESKTOP
-using System;
-using System.Drawing;
-using System.Drawing.Imaging;
-using System.IO;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using Stride.Core;
-
-namespace Stride.Graphics
-{
- ///
- /// This class is responsible to provide image loader for png, gif, bmp.
- /// TODO: Replace using System.Drawing, as it is not available on all platforms (not on Windows 8/WP8).
- ///
- partial class StandardImageHelper
- {
- public static unsafe Image LoadFromMemory(IntPtr pSource, int size, bool makeACopy, GCHandle? handle)
- {
- using var memoryStream = new UnmanagedMemoryStream((byte*)pSource, size, capacity: size, access: FileAccess.Read);
- using var bitmap = (Bitmap)System.Drawing.Image.FromStream(memoryStream);
- var sourceArea = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
- // Lock System.Drawing.Bitmap
-
- var bitmapData = bitmap.LockBits(sourceArea, ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
- var image = Image.New2D(bitmap.Width, bitmap.Height, 1, PixelFormat.B8G8R8A8_UNorm, 1, bitmapData.Stride);
- // var dataRect = new DataRectangle(bitmapData.Stride, bitmapData.Scan0);
-
- try
- {
- // TODO: Test if still necessary
- // Directly load image as RGBA instead of BGRA, because OpenGL ES devices don't support it out of the box (extension).
- //image.Description.Format = PixelFormat.R8G8B8A8_UNorm;
- //CopyMemoryBGRA(image.PixelBuffer[0].DataPointer, bitmapData.Scan0, image.PixelBuffer[0].BufferStride);
- Utilities.CopyWithAlignmentFallback((void*)image.PixelBuffer[0].DataPointer, (void*)bitmapData.Scan0, (uint)image.PixelBuffer[0].BufferStride);
- }
- finally
- {
- bitmap.UnlockBits(bitmapData);
-
- if (handle != null)
- handle.Value.Free();
- else if (!makeACopy)
- Utilities.FreeMemory(pSource);
- }
-
- return image;
- }
-
- public static void SaveGifFromMemory(PixelBuffer[] pixelBuffers, int count, ImageDescription description, Stream imageStream)
- {
- SaveFromMemory(pixelBuffers, count, description, imageStream, ImageFormat.Gif);
- }
-
- public static void SaveTiffFromMemory(PixelBuffer[] pixelBuffers, int count, ImageDescription description, Stream imageStream)
- {
- SaveFromMemory(pixelBuffers, count, description, imageStream, ImageFormat.Tiff);
- }
-
- public static void SaveBmpFromMemory(PixelBuffer[] pixelBuffers, int count, ImageDescription description, Stream imageStream)
- {
- SaveFromMemory(pixelBuffers, count, description, imageStream, ImageFormat.Bmp);
- }
-
- public static void SaveJpgFromMemory(PixelBuffer[] pixelBuffers, int count, ImageDescription description, Stream imageStream)
- {
- SaveFromMemory(pixelBuffers, count, description, imageStream, ImageFormat.Jpeg);
- }
-
- public static void SavePngFromMemory(PixelBuffer[] pixelBuffers, int count, ImageDescription description, Stream imageStream)
- {
- SaveFromMemory(pixelBuffers, count, description, imageStream, ImageFormat.Png);
- }
-
- public static void SaveWmpFromMemory(PixelBuffer[] pixelBuffers, int count, ImageDescription description, Stream imageStream)
- {
- throw new NotImplementedException();
- }
-
- private static unsafe void SaveFromMemory(PixelBuffer[] pixelBuffers, int count, ImageDescription description, Stream imageStream, ImageFormat imageFormat)
- {
- using var bitmap = new Bitmap(description.Width, description.Height);
- var sourceArea = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
-
- // Lock System.Drawing.Bitmap
- var bitmapData = bitmap.LockBits(sourceArea, ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
-
- try
- {
- // Copy memory
- var format = description.Format;
- if (format == PixelFormat.R8G8B8A8_UNorm || format == PixelFormat.R8G8B8A8_UNorm_SRgb)
- {
- CopyMemoryBGRA(bitmapData.Scan0, pixelBuffers[0].DataPointer, pixelBuffers[0].BufferStride);
- }
- else if (format == PixelFormat.B8G8R8A8_UNorm || format == PixelFormat.B8G8R8A8_UNorm_SRgb)
- {
- Utilities.CopyWithAlignmentFallback((void*)bitmapData.Scan0, (void*)pixelBuffers[0].DataPointer, (uint)pixelBuffers[0].BufferStride);
- }
- else if (format == PixelFormat.R8_UNorm || format == PixelFormat.A8_UNorm)
- {
- // TODO Ideally we will want to support grayscale images, but the SpriteBatch can only render RGBA for now
- // so convert the grayscale image as an RGBA and save it
- CopyMemoryRRR1(bitmapData.Scan0, pixelBuffers[0].DataPointer, pixelBuffers[0].BufferStride);
- }
- else
- {
- throw new ArgumentException(
- message: $"The pixel format {format} is not supported. Supported formats are {PixelFormat.B8G8R8A8_UNorm}, {PixelFormat.B8G8R8A8_UNorm_SRgb}, {PixelFormat.R8G8B8A8_UNorm}, {PixelFormat.R8G8B8A8_UNorm_SRgb}, {PixelFormat.R8_UNorm}, {PixelFormat.A8_UNorm}",
- paramName: nameof(description));
- }
- }
- finally
- {
- bitmap.UnlockBits(bitmapData);
- }
-
- // Save
- bitmap.Save(imageStream, imageFormat);
- }
- }
-}
-#endif
diff --git a/sources/engine/Stride/Stride.csproj b/sources/engine/Stride/Stride.csproj
index ac4882991e..ec99888c23 100644
--- a/sources/engine/Stride/Stride.csproj
+++ b/sources/engine/Stride/Stride.csproj
@@ -32,6 +32,7 @@
+