From 3a60b7edfa226742c9029956964c954aa5d4f8d1 Mon Sep 17 00:00:00 2001
From: Tarek Mahmoud Sayed <tarekms@microsoft.com>
Date: Tue, 20 Sep 2022 15:51:14 -0700
Subject: [PATCH 1/7] Remove the System.Drawing dependency

---
 THIRD-PARTY-NOTICES.TXT                       |   2 +-
 build/ci/job-template.yml                     |   4 +-
 build/vsts-ci.yml                             |   4 +-
 docs/building/unix-instructions.md            |   3 +-
 ...teSchedulingCifarResnetTransferLearning.cs |  17 +-
 .../ApplyONNXModelWithInMemoryImages.cs       |  24 +-
 .../Dynamic/Transforms/ApplyOnnxModel.cs      |   8 +-
 .../ImageAnalytics/ConvertToGrayScale.cs      |  21 +-
 .../ConvertToGrayScaleInMemory.cs             |  49 +-
 .../ImageAnalytics/ConvertToImage.cs          |  15 +-
 .../ImageAnalytics/ExtractPixels.cs           |  26 +-
 .../Transforms/ImageAnalytics/LoadImages.cs   |  21 +-
 .../Transforms/ImageAnalytics/ResizeImages.cs |  24 +-
 eng/Versions.props                            |   4 +-
 .../ExtensionsCatalog.cs                      |  12 +-
 src/Microsoft.ML.ImageAnalytics/ImageBase.cs  | 480 ++++++++++++++++
 .../ImageGrayscale.cs                         |  35 +-
 .../ImageLoader.cs                            |  18 +-
 .../ImagePixelExtractor.cs                    | 155 +++--
 .../ImageResizer.cs                           | 134 +----
 src/Microsoft.ML.ImageAnalytics/ImageType.cs  |   7 +-
 .../Microsoft.ML.ImageAnalytics.csproj        |   8 +-
 .../VectorToImageTransform.cs                 | 105 ++--
 .../ExtensionsCatalog.cs                      |   2 +-
 .../OnnxTransformTests.cs                     |  24 +-
 test/Microsoft.ML.Tests/ImagesTests.cs        | 542 ++++++++++--------
 .../TensorflowTests.cs                        |  28 -
 27 files changed, 1087 insertions(+), 685 deletions(-)
 create mode 100644 src/Microsoft.ML.ImageAnalytics/ImageBase.cs

diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT
index 49d4e0c2a8..3ec43d38e4 100644
--- a/THIRD-PARTY-NOTICES.TXT
+++ b/THIRD-PARTY-NOTICES.TXT
@@ -84,4 +84,4 @@ Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
-limitations under the License.
\ No newline at end of file
+limitations under the License.
diff --git a/build/ci/job-template.yml b/build/ci/job-template.yml
index d45c43139f..ef307cf492 100644
--- a/build/ci/job-template.yml
+++ b/build/ci/job-template.yml
@@ -68,11 +68,11 @@ jobs:
     steps:
     # Extra MacOS step required to install OS-specific dependencies
     - ${{ if and(contains(parameters.pool.vmImage, 'macOS'), not(contains(parameters.name, 'cross')))  }}:
-      - script: export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=TRUE && brew update && brew install mono-libgdiplus && brew unlink libomp && brew install $(Build.SourcesDirectory)/build/libomp.rb --build-from-source --formula
+      - script: export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=TRUE && brew update && brew unlink libomp && brew install $(Build.SourcesDirectory)/build/libomp.rb --build-from-source --formula
         displayName: Install MacOS build dependencies
     # Extra Apple MacOS step required to install OS-specific dependencies
     - ${{ if and(contains(parameters.pool.vmImage, 'macOS'), contains(parameters.name, 'cross'))  }}:
-      - script: brew update && brew install mono-libgdiplus && brew install libomp && brew link libomp --force
+      - script: brew update && brew install libomp && brew link libomp --force
         displayName: Install MacOS ARM build dependencies
     - ${{ if and( eq(parameters.nightlyBuild, 'true'), eq(parameters.pool.vmImage, 'ubuntu-18.04')) }}:
       - bash: echo "##vso[task.setvariable variable=LD_LIBRARY_PATH]$(nightlyBuildRunPath):$LD_LIBRARY_PATH"
diff --git a/build/vsts-ci.yml b/build/vsts-ci.yml
index dc6590f4be..849df8aacc 100644
--- a/build/vsts-ci.yml
+++ b/build/vsts-ci.yml
@@ -121,7 +121,7 @@ jobs:
   pool:
     vmImage: macOS-12
   steps:
-  - script: brew update && brew unlink python@3.8 && brew install mono-libgdiplus && brew install $(Build.SourcesDirectory)/build/libomp.rb --build-from-source --formula && brew link libomp --force
+  - script: brew update && brew unlink python@3.8 && brew install $(Build.SourcesDirectory)/build/libomp.rb --build-from-source --formula && brew link libomp --force
     displayName: Install build dependencies
   - script: ./restore.sh
     displayName: restore all projects
@@ -157,7 +157,7 @@ jobs:
       rm -rf /usr/local/bin/2to3
     displayName: MacOS Homebrew bug Workaround
     continueOnError: true
-  - script: brew update && brew unlink python@3.8 && brew install mono-libgdiplus && brew install libomp && brew link libomp --force
+  - script: brew update && brew unlink python@3.8 && brew install libomp && brew link libomp --force
     displayName: Install build dependencies
   - script: ./restore.sh
     displayName: restore all projects
diff --git a/docs/building/unix-instructions.md b/docs/building/unix-instructions.md
index fb5e4222b1..cb9c48a75e 100644
--- a/docs/building/unix-instructions.md
+++ b/docs/building/unix-instructions.md
@@ -65,13 +65,12 @@ macOS 10.13 (High Sierra) or higher is needed to build dotnet/machinelearning. W
 On macOS a few components are needed which are not provided by a default developer setup:
 * cmake 3.10.3
 * libomp 7
-* libgdiplus
 * gettext
 * All the requirements necessary to run .NET Core 3.1 applications. To view macOS prerequisites click [here](https://docs.microsoft.com/en-us/dotnet/core/install/macos?tabs=netcore31#dependencies).
 
 One way of obtaining CMake and other required libraries is via [Homebrew](https://brew.sh):
 ```sh
-$ brew update && brew install cmake https://raw.githubusercontent.com/dotnet/machinelearning/main/build/libomp.rb mono-libgdiplus gettext && brew link gettext --force && brew link libomp --force
+$ brew update && brew install cmake https://raw.githubusercontent.com/dotnet/machinelearning/main/build/libomp.rb gettext && brew link gettext --force && brew link libomp --force
 ```
 
 Please note that newer versions of Homebrew [don't allow installing directly from a URL](https://github.com/Homebrew/brew/issues/8791). If you run into this issue, you may need to download libomp.rb first and install it with the local file instead.
diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Trainers/MulticlassClassification/ImageClassification/LearningRateSchedulingCifarResnetTransferLearning.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Trainers/MulticlassClassification/ImageClassification/LearningRateSchedulingCifarResnetTransferLearning.cs
index 43cfcda48c..c129e50314 100644
--- a/docs/samples/Microsoft.ML.Samples/Dynamic/Trainers/MulticlassClassification/ImageClassification/LearningRateSchedulingCifarResnetTransferLearning.cs
+++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Trainers/MulticlassClassification/ImageClassification/LearningRateSchedulingCifarResnetTransferLearning.cs
@@ -78,10 +78,10 @@ public static void Example()
             {
                 FeatureColumnName = "Image",
                 LabelColumnName = "Label",
-                // Just by changing/selecting InceptionV3/MobilenetV2 
-                // here instead of 
+                // Just by changing/selecting InceptionV3/MobilenetV2
+                // here instead of
                 // ResnetV2101 you can try a different architecture/
-                // pre-trained model. 
+                // pre-trained model.
                 Arch = ImageClassificationTrainer.Architecture.ResnetV2101,
                 Epoch = 182,
                 BatchSize = 128,
@@ -92,8 +92,8 @@ public static void Example()
                 ReuseTrainSetBottleneckCachedValues = false,
                 // Use linear scaling rule and Learning rate decay as an option
                 // This is known to do well for Cifar dataset and Resnet models
-                // You can also try other types of Learning rate scheduling 
-                // methods available in LearningRateScheduler.cs  
+                // You can also try other types of Learning rate scheduling
+                // methods available in LearningRateScheduler.cs
                 LearningRateScheduler = new LsrDecay()
             };
 
@@ -111,7 +111,7 @@ public static void Example()
 
             // Train the model.
             // This involves calculating the bottleneck values, and then
-            // training the final layer. Sample output is: 
+            // training the final layer. Sample output is:
             // Phase: Bottleneck Computation, Dataset used: Train, Image Index:   1
             // Phase: Bottleneck Computation, Dataset used: Train, Image Index:   2
             // ...
@@ -271,8 +271,9 @@ public static string DownloadImageSet(string imagesDownloadFolder)
             // get a set of images to teach the network about the new classes
             // CIFAR dataset ( 50000 train images and 10000 test images )
             string fileName = "cifar10.zip";
-            string url = $"https://aka.ms/mlnet-resources/" +
-                "datasets/cifar10.zip";
+
+            // https://github.com/YoongiKim/CIFAR-10-images
+            string url = $"https://github.com/YoongiKim/CIFAR-10-images/archive/refs/heads/master.zip";
 
             Download(url, imagesDownloadFolder, fileName);
             UnZip(Path.Combine(imagesDownloadFolder, fileName),
diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ApplyONNXModelWithInMemoryImages.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ApplyONNXModelWithInMemoryImages.cs
index 71bd61bf90..ae21348c03 100644
--- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ApplyONNXModelWithInMemoryImages.cs
+++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ApplyONNXModelWithInMemoryImages.cs
@@ -1,5 +1,4 @@
 using System;
-using System.Drawing;
 using System.Linq;
 using Microsoft.ML;
 using Microsoft.ML.Data;
@@ -26,8 +25,8 @@ public static void Example()
             // input /output of the used ONNX model.
             var dataPoints = new ImageDataPoint[]
             {
-                new ImageDataPoint(Color.Red),
-                new ImageDataPoint(Color.Green)
+                new ImageDataPoint(red: 255, green: 0, blue: 0), // Red color
+                new ImageDataPoint(red: 0, green: 128, blue: 0)  // Green color
             };
 
             // Convert training data to IDataView, the general data type used in
@@ -91,7 +90,7 @@ private class ImageDataPoint
 
             // Image will be consumed by ONNX image multiclass classification model.
             [ImageType(height, width)]
-            public Bitmap Image { get; set; }
+            public ImageBase Image { get; set; }
 
             // Expected output of ONNX model. It contains probabilities of all
             // classes. Note that the ColumnName below should match the output name
@@ -104,12 +103,19 @@ public ImageDataPoint()
                 Image = null;
             }
 
-            public ImageDataPoint(Color color)
+            public ImageDataPoint(byte red, byte green, byte blue)
             {
-                Image = new Bitmap(width, height);
-                for (int i = 0; i < width; ++i)
-                    for (int j = 0; j < height; ++j)
-                        Image.SetPixel(i, j, color);
+                byte[] imageData = new byte[width * height * 4]; // 4 for the red, green, blue and alpha colors
+                for (int i = 0; i < imageData.Length; i += 4)
+                {
+                    // Fill the buffer with the Bgra32 format
+                    imageData[i] = blue;
+                    imageData[i + 1] = green;
+                    imageData[i + 2] = red;
+                    imageData[i + 3] = 255;
+                }
+
+                Image = ImageBase.CreateBgra32Image(width, height, imageData);
             }
         }
     }
diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ApplyOnnxModel.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ApplyOnnxModel.cs
index 4c3cdf89ed..dbec6c77f8 100644
--- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ApplyOnnxModel.cs
+++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ApplyOnnxModel.cs
@@ -1,4 +1,5 @@
 using System;
+using System.IO;
 using System.Linq;
 using Microsoft.ML;
 using Microsoft.ML.Data;
@@ -10,8 +11,9 @@ public static class ApplyOnnxModel
         public static void Example()
         {
             // Download the squeeznet image model from ONNX model zoo, version 1.2
-            // https://github.com/onnx/models/tree/master/squeezenet or use
-            // Microsoft.ML.Onnx.TestModels nuget.
+            // https://github.com/onnx/models/tree/master/squeezenet or
+            // https://s3.amazonaws.com/download.onnx/models/opset_8/squeezenet.tar.gz
+            // or use Microsoft.ML.Onnx.TestModels nuget.
             var modelPath = @"squeezenet\00000001\model.onnx";
 
             // Create ML pipeline to score the data using OnnxScoringEstimator
@@ -56,7 +58,7 @@ public static void Example()
         // inputSize is the overall dimensions of the model input tensor.
         private const int inputSize = 224 * 224 * 3;
 
-        // A class to hold sample tensor data. Member name should match  
+        // A class to hold sample tensor data. Member name should match
         // the inputs that the model expects (in this case, data_0)
         public class TensorData
         {
diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScale.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScale.cs
index 24cb63849b..a108aa97e7 100644
--- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScale.cs
+++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScale.cs
@@ -1,5 +1,4 @@
 using System;
-using System.Drawing;
 using System.IO;
 using Microsoft.ML;
 using Microsoft.ML.Data;
@@ -9,7 +8,7 @@ namespace Samples.Dynamic
     public static class ConvertToGrayscale
     {
         // Sample that loads images from the file system, and converts them to
-        // grayscale. 
+        // grayscale.
         public static void Example()
         {
             // Create a new ML context, for ML.NET operations. It can be used for
@@ -20,7 +19,7 @@ public static void Example()
             // list of the files from the dotnet/machinelearning/test/data/images/.
             // If you inspect the fileSystem, after running this line, an "images"
             // folder will be created, containing 4 images, and a .tsv file
-            // enumerating the images. 
+            // enumerating the images.
             var imagesDataFile = Microsoft.ML.SamplesUtils.DatasetUtils
                 .GetSampleImages();
 
@@ -42,7 +41,7 @@ public static void Example()
             }).Load(imagesDataFile);
 
             var imagesFolder = Path.GetDirectoryName(imagesDataFile);
-            // Image loading pipeline. 
+            // Image loading pipeline.
             var pipeline = mlContext.Transforms.LoadImages("ImageObject",
                 imagesFolder, "ImagePath")
                 .Append(mlContext.Transforms.ConvertToGrayscale("Grayscale",
@@ -67,12 +66,12 @@ private static void PrintColumns(IDataView transformedData)
                 .Schema))
             {
                 // Note that it is best to get the getters and values *before*
-                // iteration, so as to faciliate buffer sharing (if applicable), and
+                // iteration, so as to facilitate buffer sharing (if applicable), and
                 // column -type validation once, rather than many times.
                 ReadOnlyMemory<char> imagePath = default;
                 ReadOnlyMemory<char> name = default;
-                Bitmap imageObject = null;
-                Bitmap grayscaleImageObject = null;
+                ImageBase imageObject = null;
+                ImageBase grayscaleImageObject = null;
 
                 var imagePathGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
                     .Schema["ImagePath"]);
@@ -80,10 +79,10 @@ private static void PrintColumns(IDataView transformedData)
                 var nameGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
                     .Schema["Name"]);
 
-                var imageObjectGetter = cursor.GetGetter<Bitmap>(cursor.Schema[
+                var imageObjectGetter = cursor.GetGetter<ImageBase>(cursor.Schema[
                     "ImageObject"]);
 
-                var grayscaleGetter = cursor.GetGetter<Bitmap>(cursor.Schema[
+                var grayscaleGetter = cursor.GetGetter<ImageBase>(cursor.Schema[
                     "Grayscale"]);
 
                 while (cursor.MoveNext())
@@ -94,8 +93,8 @@ private static void PrintColumns(IDataView transformedData)
                     grayscaleGetter(ref grayscaleImageObject);
 
                     Console.WriteLine("{0, -25} {1, -25} {2, -25} {3, -25}",
-                        imagePath, name, imageObject.PhysicalDimension,
-                        grayscaleImageObject.PhysicalDimension);
+                        imagePath, name, $"Width={imageObject.Width}, Height={imageObject.Height}",
+                        $"Width={grayscaleImageObject.Width}, Height={grayscaleImageObject.Height}");
                 }
 
                 // Dispose the image.
diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScaleInMemory.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScaleInMemory.cs
index 1d7f662e85..7596ba46da 100644
--- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScaleInMemory.cs
+++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScaleInMemory.cs
@@ -1,6 +1,6 @@
 using System;
-using System.Drawing;
 using Microsoft.ML;
+using Microsoft.ML.Data;
 using Microsoft.ML.Transforms.Image;
 
 namespace Samples.Dynamic
@@ -11,16 +11,17 @@ public static void Example()
         {
             var mlContext = new MLContext();
             // Create an image list.
-            var images = new[] { new ImageDataPoint(2, 3, Color.Blue), new
-                ImageDataPoint(2, 3, Color.Red) };
+            var images = new[]
+            {
+                new ImageDataPoint(2, 3, red: 0, green: 0, blue: 255),    // Blue color
+                new ImageDataPoint(2, 3, red: 255, green: 0, blue: 0) };  // red color
 
             // Convert the list of data points to an IDataView object, which is
             // consumable by ML.NET API.
             var data = mlContext.Data.LoadFromEnumerable(images);
 
             // Convert image to gray scale.
-            var pipeline = mlContext.Transforms.ConvertToGrayscale("GrayImage",
-                "Image");
+            var pipeline = mlContext.Transforms.ConvertToGrayscale("GrayImage", "Image");
 
             // Fit the model.
             var model = pipeline.Fit(data);
@@ -37,15 +38,16 @@ public static void Example()
             {
                 var image = dataPoint.Image;
                 var grayImage = dataPoint.GrayImage;
-                for (int x = 0; x < grayImage.Width; ++x)
+                ReadOnlySpan<byte> imageData = image.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
+                ReadOnlySpan<byte> grayImageData = grayImage.Get32bbpImageData(out int alphaIndex1, out int redIndex1, out int greenIndex1, out int blueIndex1);
+                int pixelSize = image.BitsPerPixel / 8;
+
+                for (int i = 0; i < imageData.Length; i += pixelSize)
                 {
-                    for (int y = 0; y < grayImage.Height; ++y)
-                    {
-                        var pixel = image.GetPixel(x, y);
-                        var grayPixel = grayImage.GetPixel(x, y);
-                        Console.WriteLine($"The original pixel is {pixel} and its" +
-                            $"pixel in gray is {grayPixel}");
-                    }
+                    string pixelString = $"[A = {imageData[i + alphaIndex]}, R = {imageData[i + redIndex]}, G = {imageData[i + greenIndex]}, B = {imageData[i + blueIndex]}]";
+                    string grayPixelString = $"[A = {grayImageData[i + alphaIndex1]}, R = {grayImageData[i + redIndex1]}, G = {grayImageData[i + greenIndex1]}, B = {grayImageData[i + blueIndex1]}]";
+
+                    Console.WriteLine($"The original pixel is {pixelString} and its pixel in gray is {grayPixelString}");
                 }
             }
 
@@ -67,10 +69,10 @@ public static void Example()
         private class ImageDataPoint
         {
             [ImageType(3, 4)]
-            public Bitmap Image { get; set; }
+            public ImageBase Image { get; set; }
 
             [ImageType(3, 4)]
-            public Bitmap GrayImage { get; set; }
+            public ImageBase GrayImage { get; set; }
 
             public ImageDataPoint()
             {
@@ -78,12 +80,19 @@ public ImageDataPoint()
                 GrayImage = null;
             }
 
-            public ImageDataPoint(int width, int height, Color color)
+            public ImageDataPoint(int width, int height, byte red, byte green, byte blue)
             {
-                Image = new Bitmap(width, height);
-                for (int i = 0; i < width; ++i)
-                    for (int j = 0; j < height; ++j)
-                        Image.SetPixel(i, j, color);
+                byte[] imageData = new byte[width * height * 4]; // 4 for the red, green, blue and alpha colors
+                for (int i = 0; i < imageData.Length; i += 4)
+                {
+                    // Fill the buffer with the Bgra32 format
+                    imageData[i] = blue;
+                    imageData[i + 1] = green;
+                    imageData[i + 2] = red;
+                    imageData[i + 3] = 255;
+                }
+
+                Image = ImageBase.CreateBgra32Image(width, height, imageData);
             }
         }
     }
diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToImage.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToImage.cs
index 0d817de957..a64f5baf74 100644
--- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToImage.cs
+++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToImage.cs
@@ -1,6 +1,5 @@
 using System;
 using System.Collections.Generic;
-using System.Drawing;
 using System.Linq;
 using Microsoft.ML;
 using Microsoft.ML.Data;
@@ -29,7 +28,7 @@ public static void Example()
             // consumable by ML.NET API.
             var data = mlContext.Data.LoadFromEnumerable(dataPoints);
 
-            // Image loading pipeline. 
+            // Image loading pipeline.
             var pipeline = mlContext.Transforms.ConvertToImage(imageHeight,
                 imageWidth, "Image", "Features")
                 .Append(mlContext.Transforms.ExtractPixels("Pixels", "Image"));
@@ -55,11 +54,11 @@ private static void PrintColumns(IDataView transformedData)
                 .Schema))
             {
                 // Note that it is best to get the getters and values *before*
-                // iteration, so as to faciliate buffer sharing (if applicable), and
+                // iteration, so as to facilitate buffer sharing (if applicable), and
                 // column -type validation once, rather than many times.
                 VBuffer<float> features = default;
                 VBuffer<float> pixels = default;
-                Bitmap imageObject = null;
+                ImageBase imageObject = null;
 
                 var featuresGetter = cursor.GetGetter<VBuffer<float>>(cursor.Schema[
                     "Features"]);
@@ -67,7 +66,7 @@ private static void PrintColumns(IDataView transformedData)
                 var pixelsGetter = cursor.GetGetter<VBuffer<float>>(cursor.Schema[
                     "Pixels"]);
 
-                var imageGetter = cursor.GetGetter<Bitmap>(cursor.Schema["Image"]);
+                var imageGetter = cursor.GetGetter<ImageBase>(cursor.Schema["Image"]);
                 while (cursor.MoveNext())
                 {
 
@@ -76,9 +75,9 @@ private static void PrintColumns(IDataView transformedData)
                     imageGetter(ref imageObject);
 
                     Console.WriteLine("{0, -25} {1, -25} {2, -25}", string.Join(",",
-                        features.DenseValues().Take(5)) + "...", imageObject
-                        .PhysicalDimension, string.Join(",", pixels.DenseValues()
-                        .Take(5)) + "...");
+                        features.DenseValues().Take(5)) + "...",
+                        $"Width={imageObject.Width}, Height={imageObject.Height}",
+                        string.Join(",", pixels.DenseValues().Take(5)) + "...");
                 }
 
                 // Dispose the image.
diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ExtractPixels.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ExtractPixels.cs
index b16d37b40d..d020262dca 100644
--- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ExtractPixels.cs
+++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ExtractPixels.cs
@@ -1,5 +1,4 @@
 using System;
-using System.Drawing;
 using System.IO;
 using System.Linq;
 using Microsoft.ML;
@@ -11,7 +10,7 @@ public static class ExtractPixels
     {
         // Sample that loads the images from the file system, resizes them (
         // ExtractPixels requires a resizing operation), and extracts the values of
-        // the pixels as a vector. 
+        // the pixels as a vector.
         public static void Example()
         {
             // Create a new ML context, for ML.NET operations. It can be used for
@@ -22,7 +21,7 @@ public static void Example()
             // list of the files from the dotnet/machinelearning/test/data/images/.
             // If you inspect the fileSystem, after running this line, an "images"
             // folder will be created, containing 4 images, and a .tsv file
-            // enumerating the images. 
+            // enumerating the images.
             var imagesDataFile = Microsoft.ML.SamplesUtils.DatasetUtils
                 .GetSampleImages();
 
@@ -44,7 +43,7 @@ public static void Example()
             }).Load(imagesDataFile);
 
             var imagesFolder = Path.GetDirectoryName(imagesDataFile);
-            // Image loading pipeline. 
+            // Image loading pipeline.
             var pipeline = mlContext.Transforms.LoadImages("ImageObject",
                 imagesFolder, "ImagePath")
                 .Append(mlContext.Transforms.ResizeImages("ImageObjectResized",
@@ -55,7 +54,7 @@ public static void Example()
 
             var transformedData = pipeline.Fit(data).Transform(data);
 
-            // Preview the transformedData. 
+            // Preview the transformedData.
             PrintColumns(transformedData);
 
             // ImagePath    Name         ImageObject               ImageObjectResized        Pixels
@@ -74,13 +73,13 @@ private static void PrintColumns(IDataView transformedData)
                 .Schema))
             {
                 // Note that it is best to get the getters and values *before*
-                // iteration, so as to faciliate buffer sharing (if applicable), and
+                // iteration, so as to facilitate buffer sharing (if applicable), and
                 // column -type validation once, rather than many times.
 
                 ReadOnlyMemory<char> imagePath = default;
                 ReadOnlyMemory<char> name = default;
-                Bitmap imageObject = null;
-                Bitmap resizedImageObject = null;
+                ImageBase imageObject = null;
+                ImageBase resizedImageObject = null;
                 VBuffer<float> pixels = default;
 
                 var imagePathGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
@@ -89,10 +88,10 @@ private static void PrintColumns(IDataView transformedData)
                 var nameGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
                     .Schema["Name"]);
 
-                var imageObjectGetter = cursor.GetGetter<Bitmap>(cursor.Schema[
+                var imageObjectGetter = cursor.GetGetter<ImageBase>(cursor.Schema[
                     "ImageObject"]);
 
-                var resizedImageGetter = cursor.GetGetter<Bitmap>(cursor.Schema[
+                var resizedImageGetter = cursor.GetGetter<ImageBase>(cursor.Schema[
                     "ImageObjectResized"]);
 
                 var pixelsGetter = cursor.GetGetter<VBuffer<float>>(cursor.Schema[
@@ -108,9 +107,10 @@ private static void PrintColumns(IDataView transformedData)
                     pixelsGetter(ref pixels);
 
                     Console.WriteLine("{0, -25} {1, -25} {2, -25} {3, -25} " +
-                        "{4, -25}", imagePath, name, imageObject.PhysicalDimension,
-                        resizedImageObject.PhysicalDimension, string.Join(",",
-                        pixels.DenseValues().Take(5)) + "...");
+                        "{4, -25}", imagePath, name,
+                        $"Width={imageObject.Width}, Height={imageObject.Height}",
+                        $"Width={resizedImageObject.Width}, Height={resizedImageObject.Height}",
+                        string.Join(",", pixels.DenseValues().Take(5)) + "...");
                 }
 
                 // Dispose the image.
diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/LoadImages.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/LoadImages.cs
index 2113d2a92b..80cabbee56 100644
--- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/LoadImages.cs
+++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/LoadImages.cs
@@ -1,5 +1,4 @@
 using System;
-using System.Drawing;
 using System.IO;
 using Microsoft.ML;
 using Microsoft.ML.Data;
@@ -8,7 +7,7 @@ namespace Samples.Dynamic
 {
     public static class LoadImages
     {
-        // Loads the images of the imagesFolder into an IDataView. 
+        // Loads the images of the imagesFolder into an IDataView.
         public static void Example()
         {
             // Create a new ML context, for ML.NET operations. It can be used for
@@ -19,7 +18,7 @@ public static void Example()
             // list of the files from the dotnet/machinelearning/test/data/images/.
             // If you inspect the fileSystem, after running this line, an "images"
             // folder will be created, containing 4 images, and a .tsv file
-            // enumerating the images. 
+            // enumerating the images.
             var imagesDataFile = Microsoft.ML.SamplesUtils.DatasetUtils
                 .GetSampleImages();
 
@@ -41,15 +40,15 @@ public static void Example()
             }).Load(imagesDataFile);
 
             var imagesFolder = Path.GetDirectoryName(imagesDataFile);
-            // Image loading pipeline. 
+            // Image loading pipeline.
             var pipeline = mlContext.Transforms.LoadImages("ImageObject",
                 imagesFolder, "ImagePath");
 
             var transformedData = pipeline.Fit(data).Transform(data);
 
             PrintColumns(transformedData);
-            // Preview the transformedData. 
-            // ImagePath    Name         ImageObject           
+            // Preview the transformedData.
+            // ImagePath    Name         ImageObject
             // tomato.bmp   tomato       {Width=800, Height=534}
             // banana.jpg   banana       {Width=800, Height=288}
             // hotdog.jpg   hotdog       {Width=800, Height=391}
@@ -70,7 +69,7 @@ private static void PrintColumns(IDataView transformedData)
                 // and column-type validation once, rather than many times.
                 ReadOnlyMemory<char> imagePath = default;
                 ReadOnlyMemory<char> name = default;
-                Bitmap imageObject = null;
+                ImageBase imageObject = null;
 
                 var imagePathGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
                     .Schema["ImagePath"]);
@@ -78,18 +77,18 @@ private static void PrintColumns(IDataView transformedData)
                 var nameGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
                     .Schema["Name"]);
 
-                var imageObjectGetter = cursor.GetGetter<Bitmap>(cursor.Schema[
+                var imageObjectGetter = cursor.GetGetter<ImageBase>(cursor.Schema[
                     "ImageObject"]);
 
                 while (cursor.MoveNext())
                 {
-
                     imagePathGetter(ref imagePath);
                     nameGetter(ref name);
                     imageObjectGetter(ref imageObject);
 
-                    Console.WriteLine("{0, -25} {1, -25} {2, -25}", imagePath, name,
-                        imageObject.PhysicalDimension);
+                    Console.WriteLine("{0, -25} {1, -25} {2, -25}",
+                        imagePath, name,
+                        $"Width={imageObject.Width}, Height={imageObject.Height}");
                 }
 
                 // Dispose the image.
diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ResizeImages.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ResizeImages.cs
index ec3a884f7e..6f05ba107b 100644
--- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ResizeImages.cs
+++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ResizeImages.cs
@@ -1,5 +1,4 @@
 using System;
-using System.Drawing;
 using System.IO;
 using Microsoft.ML;
 using Microsoft.ML.Data;
@@ -8,7 +7,7 @@ namespace Samples.Dynamic
 {
     public static class ResizeImages
     {
-        // Example on how to load the images from the file system, and resize them. 
+        // Example on how to load the images from the file system, and resize them.
         public static void Example()
         {
             // Create a new ML context, for ML.NET operations. It can be used for
@@ -19,7 +18,7 @@ public static void Example()
             // list of the files from the dotnet/machinelearning/test/data/images/.
             // If you inspect the fileSystem, after running this line, an "images"
             // folder will be created, containing 4 images, and a .tsv file
-            // enumerating the images. 
+            // enumerating the images.
             var imagesDataFile = Microsoft.ML.SamplesUtils.DatasetUtils
                 .GetSampleImages();
 
@@ -41,7 +40,7 @@ public static void Example()
             }).Load(imagesDataFile);
 
             var imagesFolder = Path.GetDirectoryName(imagesDataFile);
-            // Image loading pipeline. 
+            // Image loading pipeline.
             var pipeline = mlContext.Transforms.LoadImages("ImageObject",
                 imagesFolder, "ImagePath")
                 .Append(mlContext.Transforms.ResizeImages("ImageObjectResized",
@@ -50,7 +49,7 @@ public static void Example()
             var transformedData = pipeline.Fit(data).Transform(data);
             // The transformedData IDataView contains the resized images now.
 
-            // Preview the transformedData. 
+            // Preview the transformedData.
             PrintColumns(transformedData);
 
             // ImagePath    Name         ImageObject               ImageObjectResized
@@ -69,12 +68,12 @@ private static void PrintColumns(IDataView transformedData)
                 .Schema))
             {
                 // Note that it is best to get the getters and values *before*
-                // iteration, so as to faciliate buffer sharing (if applicable), and
+                // iteration, so as to facilitate buffer sharing (if applicable), and
                 // column -type validation once, rather than many times.
                 ReadOnlyMemory<char> imagePath = default;
                 ReadOnlyMemory<char> name = default;
-                Bitmap imageObject = null;
-                Bitmap resizedImageObject = null;
+                ImageBase imageObject = null;
+                ImageBase resizedImageObject = null;
 
                 var imagePathGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
                     .Schema["ImagePath"]);
@@ -82,10 +81,10 @@ private static void PrintColumns(IDataView transformedData)
                 var nameGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
                     .Schema["Name"]);
 
-                var imageObjectGetter = cursor.GetGetter<Bitmap>(cursor.Schema[
+                var imageObjectGetter = cursor.GetGetter<ImageBase>(cursor.Schema[
                     "ImageObject"]);
 
-                var resizedImageGetter = cursor.GetGetter<Bitmap>(cursor.Schema[
+                var resizedImageGetter = cursor.GetGetter<ImageBase>(cursor.Schema[
                     "ImageObjectResized"]);
 
                 while (cursor.MoveNext())
@@ -96,8 +95,9 @@ private static void PrintColumns(IDataView transformedData)
                     resizedImageGetter(ref resizedImageObject);
 
                     Console.WriteLine("{0, -25} {1, -25} {2, -25} {3, -25}",
-                        imagePath, name, imageObject.PhysicalDimension,
-                        resizedImageObject.PhysicalDimension);
+                        imagePath, name,
+                        $"Width={imageObject.Width}, Height={imageObject.Height}",
+                        $"Width={resizedImageObject.Width}, Height={resizedImageObject.Height}");
                 }
 
                 // Dispose the image.
diff --git a/eng/Versions.props b/eng/Versions.props
index 76f10699f7..09eb58a488 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -16,10 +16,10 @@
     <MicrosoftBclAsyncInterfacesVersion>6.0.0</MicrosoftBclAsyncInterfacesVersion>
     <MicrosoftExtensionsVersion>2.1.0</MicrosoftExtensionsVersion>
     <MicrosoftExtensionsDependencyInjectionVersion>6.0.0</MicrosoftExtensionsDependencyInjectionVersion>
-    <SystemBuffersVersion>4.5.0</SystemBuffersVersion>
+    <SkiaSharpVersion>2.88.3</SkiaSharpVersion>
+    <SystemBuffersVersion>4.5.1</SystemBuffersVersion>
     <SystemCodeDomVersion>4.5.0</SystemCodeDomVersion>
     <SystemCollectionsImmutableVersion>1.5.0</SystemCollectionsImmutableVersion>
-    <SystemDrawingCommonVersion>4.5.0</SystemDrawingCommonVersion>
     <SystemIOFileSystemAccessControl>4.5.0</SystemIOFileSystemAccessControl>
     <SystemMemoryVersion>4.5.3</SystemMemoryVersion>
     <SystemReflectionEmitLightweightVersion>4.3.0</SystemReflectionEmitLightweightVersion>
diff --git a/src/Microsoft.ML.ImageAnalytics/ExtensionsCatalog.cs b/src/Microsoft.ML.ImageAnalytics/ExtensionsCatalog.cs
index 4ba2aed578..5d0d188795 100644
--- a/src/Microsoft.ML.ImageAnalytics/ExtensionsCatalog.cs
+++ b/src/Microsoft.ML.ImageAnalytics/ExtensionsCatalog.cs
@@ -21,7 +21,7 @@ public static class ImageEstimatorsCatalog
         /// <param name="outputColumnName">Name of the column resulting from the transformation of <paramref name="inputColumnName"/>.
         /// This column's data type will be the same as that of the input column.</param>
         /// <param name="inputColumnName">Name of the column to convert images to grayscale from.
-        /// This estimator operates only on <see cref="System.Drawing.Bitmap"/>.</param>
+        /// This estimator operates only on <see cref="Microsoft.ML.Data.ImageBase"/>.</param>
         /// <example>
         /// <format type="text/markdown">
         /// <![CDATA[
@@ -37,7 +37,7 @@ public static ImageGrayscalingEstimator ConvertToGrayscale(this TransformsCatalo
         /// to grayscale images in a new column: <see cref="InputOutputColumnPair.OutputColumnName" />.
         ///</summary>
         /// <param name="catalog">The transform's catalog.</param>
-        /// <param name="columns">The pairs of input and output columns. This estimator operates only on <see cref="System.Drawing.Bitmap"/>.</param>
+        /// <param name="columns">The pairs of input and output columns. This estimator operates only on <see cref="Microsoft.ML.Data.ImageBase"/>.</param>
         /// <example>
         /// <format type="text/markdown">
         /// <![CDATA[
@@ -58,7 +58,7 @@ internal static ImageGrayscalingEstimator ConvertToGrayscale(this TransformsCata
         /// </summary>
         /// <param name="catalog">The transform's catalog.</param>
         /// <param name="outputColumnName">Name of the column resulting from the transformation of <paramref name="inputColumnName"/>.
-        /// This column's data type will be <see cref="System.Drawing.Bitmap"/>.</param>
+        /// This column's data type will be <see cref="Microsoft.ML.Data.ImageBase"/>.</param>
         /// <param name="inputColumnName">Name of the column with paths to the images to load.
         /// This estimator operates over text data.</param>
         /// <param name="imageFolder">Folder where to look for images.</param>
@@ -98,7 +98,7 @@ public static ImageLoadingEstimator LoadRawImageBytes(this TransformsCatalog cat
         /// <param name="outputColumnName">Name of the column resulting from the transformation of <paramref name="inputColumnName"/>.
         /// This column's data type will be a known-sized vector of <see cref="System.Single"/> or <see cref="System.Byte"/> depending on <paramref name="outputAsFloatArray"/>.</param>
         /// <param name="inputColumnName">Name of the column with images.
-        /// This estimator operates over <see cref="System.Drawing.Bitmap"/>.</param>
+        /// This estimator operates over <see cref="Microsoft.ML.Data.ImageBase"/>.</param>
         /// <param name="colorsToExtract">The colors to extract from the image.</param>
         /// <param name="orderOfExtraction">The order in which to extract colors from pixel.</param>
         /// <param name="interleavePixelColors">Whether to interleave the pixels colors, meaning keep them in the <paramref name="orderOfExtraction"/> order, or leave them in the planner form:
@@ -142,7 +142,7 @@ internal static ImagePixelExtractingEstimator ExtractPixels(this TransformsCatal
         /// <param name="outputColumnName">Name of the column resulting from the transformation of <paramref name="inputColumnName"/>.
         /// This column's data type will be the same as that of the input column.</param>
         /// <param name="inputColumnName">Name of the column with images.
-        /// This estimator operates over <see cref="System.Drawing.Bitmap"/>.</param>
+        /// This estimator operates over <see cref="Microsoft.ML.Data.ImageBase"/>.</param>
         /// <param name="imageWidth">The transformed image width.</param>
         /// <param name="imageHeight">The transformed image height.</param>
         /// <param name="resizing"> The type of image resizing as specified in <see cref="ImageResizingEstimator.ResizingKind"/>.</param>
@@ -202,7 +202,7 @@ internal static VectorToImageConvertingEstimator ConvertToImage(this TransformsC
         /// <param name="imageHeight">The height of the output images.</param>
         /// <param name="imageWidth">The width of the output images.</param>
         /// <param name="outputColumnName">Name of the column resulting from the transformation of <paramref name="inputColumnName"/>.
-        /// This column's data type will be <see cref="System.Drawing.Bitmap"/>.</param>
+        /// This column's data type will be <see cref="Microsoft.ML.Data.ImageBase"/>.</param>
         /// <param name="inputColumnName">Name of the column with data to be converted to image.
         /// This estimator operates over known-sized vector of <see cref="System.Single"/>, <see cref="System.Double"/> and <see cref="System.Byte"/>.</param>
         /// <param name="colorsPresent">Specifies which <see cref="ImagePixelExtractingEstimator.ColorBits"/> are in present the input pixel vectors. The order of colors is specified in <paramref name="orderOfColors"/>.</param>
diff --git a/src/Microsoft.ML.ImageAnalytics/ImageBase.cs b/src/Microsoft.ML.ImageAnalytics/ImageBase.cs
new file mode 100644
index 0000000000..6c7ca360f2
--- /dev/null
+++ b/src/Microsoft.ML.ImageAnalytics/ImageBase.cs
@@ -0,0 +1,480 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using SkiaSharp;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.ML.Data
+{
+    /// <summary>
+    /// Provides the base class for the image provider which allow registering a provider to use instead of the default provider.
+    /// </summary>
+    internal abstract class ImageProvider
+    {
+        internal static ImageProvider DefaultProvider { get; set; }
+
+        /// <summary>
+        /// Register an image provider to use instead of the default provider.
+        /// </summary>
+        /// <param name="provider">A provider to use for imaging operations.</param>
+        public void RegisterDefaultProvider(ImageProvider provider) => DefaultProvider = provider;
+
+        /// <summary>
+        /// Create image object from a stream.
+        /// </summary>
+        /// <param name="imageStream">The stream to create the image from.</param>
+        /// <returns>Image object.</returns>
+        public abstract ImageBase CreateImageFromStream(Stream imageStream);
+
+        /// <summary>
+        /// Create image object from the pixel data buffer span.
+        /// </summary>
+        /// <param name="width">The width of the image in pixels.</param>
+        /// <param name="height">The height of the image in pixels.</param>
+        /// <param name="imagePixelData">The pixels data to create the image from.</param>
+        /// <returns>Image object.</returns>
+        public abstract ImageBase CreateBgra32ImageFromPixelData(int width, int height, Span<byte> imagePixelData);
+    }
+
+    /// <summary>
+    /// The mode to decide how the image should be resized.
+    /// </summary>
+    public enum ImageResizeMode
+    {
+        /// <summary>
+        /// Pads the resized image to fit the bounds of its container.
+        /// </summary>
+        Pad,
+
+        /// <summary>
+        /// Ignore aspect ratio and squeeze/stretch into target dimensions.
+        /// </summary>
+        Fill,
+
+        /// <summary>
+        /// Resized image to fit the bounds of its container using cropping with top anchor.
+        /// </summary>
+        CropAnchorTop,
+
+        /// <summary>
+        /// Resized image to fit the bounds of its container using cropping with bottom anchor.
+        /// </summary>
+        CropAnchorBottom,
+
+        /// <summary>
+        /// Resized image to fit the bounds of its container using cropping with left anchor.
+        /// </summary>
+        CropAnchorLeft,
+
+        /// <summary>
+        /// Resized image to fit the bounds of its container using cropping with right anchor.
+        /// </summary>
+        CropAnchorRight,
+
+        /// <summary>
+        /// Resized image to fit the bounds of its container using cropping with central anchor.
+        /// </summary>
+        CropAnchorCentral
+    }
+
+    /// <summary>
+    /// Base class provide all interfaces for imaging operations.
+    /// </summary>
+    public abstract class ImageBase : IDisposable
+    {
+        /// <summary>
+        /// Gets or sets the image tag.
+        /// </summary>
+        public string Tag { get; set; }
+
+        /// <summary>
+        /// Gets the image width in pixels.
+        /// </summary>
+        public abstract int Width { get; }
+
+        /// <summary>
+        /// Gets the image height in pixels.
+        /// </summary>
+        public abstract int Height { get; }
+
+        /// <summary>
+        /// Gets how many bits per pixel used by current image object.
+        /// </summary>
+        public abstract int BitsPerPixel { get; }
+
+        /// <summary>
+        /// Create image object from a stream.
+        /// </summary>
+        /// <param name="imageStream">The stream to create the image from.</param>
+        /// <returns>Image object.</returns>
+        public static ImageBase CreateFromStream(Stream imageStream)
+        {
+            ImageProvider provider = ImageProvider.DefaultProvider;
+            return provider is not null ? provider.CreateImageFromStream(imageStream) : SkiaSharpImage.Create(imageStream);
+        }
+
+        /// <summary>
+        /// Create BRGA32 pixel format image object from the pixel data buffer span.
+        /// </summary>
+        /// <param name="width">The width of the image in pixels.</param>
+        /// <param name="height">The height of the image in pixels.</param>
+        /// <param name="imagePixelData">The pixels data to create the image from.</param>
+        /// <returns>Image object.</returns>
+        public static ImageBase CreateBgra32Image(int width, int height, Span<byte> imagePixelData)
+        {
+            ImageProvider provider = ImageProvider.DefaultProvider;
+            return provider is not null ? provider.CreateBgra32ImageFromPixelData(width, height, imagePixelData) : SkiaSharpImage.CreateFromPixelData(width, height, imagePixelData);
+        }
+
+        /// <summary>
+        /// Clones the current image with resizing it.
+        /// </summary>
+        /// <param name="width">The new width of the image.</param>
+        /// <param name="height">The new height of the image.</param>
+        /// <param name="mode">How to resize the image.</param>
+        /// <returns>The new cloned image.</returns>
+        public abstract ImageBase CloneWithResizing(int width, int height, ImageResizeMode mode);
+
+        /// <summary>
+        /// Clones the current image with grayscale.
+        /// </summary>
+        /// <returns>The new cloned image.</returns>
+        public abstract ImageBase CloneWithGrayscale();
+
+        /// <summary>
+        /// Gets the image pixel data and how the colors are ordered in the used pixel format.
+        /// </summary>
+        /// <param name="alphaIndex">The index of the alpha in the pixel format.</param>
+        /// <param name="redIndex">The index of the red color in the pixel format.</param>
+        /// <param name="greenIndex">The index of the green color in the pixel format.</param>
+        /// <param name="blueIndex">The index of the blue color in the pixel format.</param>
+        /// <returns>The buffer containing the image pixel data.</returns>
+        public abstract ReadOnlySpan<byte> Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
+
+        /// <summary>
+        /// Save the current image to a file.
+        /// </summary>
+        /// <param name="imagePath">The path of the file to save the image to.</param>
+        /// <remarks>The saved image encoding will be detected from the file extension.</remarks>
+        public abstract void Save(string imagePath);
+
+        /// <summary>
+        /// Releases the unmanaged resources used by the image object and optionally releases the managed resources.
+        /// </summary>
+        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
+        protected virtual void Dispose(bool disposing)
+        {
+        }
+
+        /// <summary>
+        /// Releases all resources used by the image object.
+        /// </summary>
+        public void Dispose() => Dispose(disposing: true);
+    }
+
+    internal class SkiaSharpImage : ImageBase
+    {
+        private SKBitmap _image;
+
+        private SkiaSharpImage(SKBitmap image)
+        {
+            Debug.Assert(image is not null);
+
+            // Most of the time SkiaSharp create images with Bgra8888 or Rgba8888 pixel format.
+            if (image.Info.ColorType != SKColorType.Bgra8888 && image.Info.ColorType != SKColorType.Rgba8888)
+            {
+                if (!image.CanCopyTo(SKColorType.Bgra8888))
+                {
+                    throw new InvalidOperationException("Unsupported image format.");
+                }
+
+                SKBitmap image1 = image.Copy(SKColorType.Bgra8888);
+                image.Dispose();
+                image = image1;
+            }
+
+            _image = image;
+        }
+
+        public static ImageBase Create(Stream imageStream)
+        {
+            if (imageStream is null)
+            {
+                throw new ArgumentNullException(nameof(imageStream));
+            }
+
+            SKBitmap image = SKBitmap.Decode(imageStream);
+            if (image is null)
+            {
+                throw new ArgumentException($"Invalid input stream contents", nameof(imageStream));
+            }
+
+            return new SkiaSharpImage(image);
+        }
+
+        public static unsafe ImageBase CreateFromPixelData(int width, int height, Span<byte> imagePixelData)
+        {
+            if (imagePixelData.Length != width * height * 4)
+            {
+                throw new ArgumentException($"Invalid {nameof(imagePixelData)} buffer size.");
+            }
+
+            SKBitmap image = new SKBitmap(new SKImageInfo(width, height, SKColorType.Bgra8888));
+
+            Debug.Assert(image.Info.BitsPerPixel == 32);
+            Debug.Assert(image.RowBytes * image.Height == width * height * 4);
+
+            imagePixelData.CopyTo(new Span<byte>(image.GetPixels().ToPointer(), image.Width * image.Height * 4));
+
+            return new SkiaSharpImage(image);
+        }
+
+        public override ReadOnlySpan<byte> Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex)
+        {
+            ThrowInvalidOperationExceptionIfDisposed();
+
+            if (_image.Info.ColorType == SKColorType.Rgba8888)
+            {
+                redIndex = 0;
+                greenIndex = 1;
+                blueIndex = 2;
+                alphaIndex = 3;
+            }
+            else
+            {
+                Debug.Assert(_image.Info.ColorType == SKColorType.Bgra8888);
+                blueIndex = 0;
+                greenIndex = 1;
+                redIndex = 2;
+                alphaIndex = 3;
+            }
+
+            Debug.Assert(_image.Info.BytesPerPixel == 4);
+
+            return _image.GetPixelSpan();
+        }
+
+        public override ImageBase CloneWithResizing(int width, int height, ImageResizeMode mode)
+        {
+            ThrowInvalidOperationExceptionIfDisposed();
+
+            SKBitmap image = mode switch
+            {
+                ImageResizeMode.Pad => ResizeWithPadding(width, height),
+                ImageResizeMode.Fill => ResizeFull(width, height),
+                >= ImageResizeMode.CropAnchorTop and <= ImageResizeMode.CropAnchorCentral => ResizeWithCrop(width, height, mode),
+                _ => throw new ArgumentException($"Invalid resize mode value.", nameof(mode))
+            };
+
+            if (image is null)
+            {
+                throw new InvalidOperationException($"Couldn't resize the image");
+            }
+
+            return new SkiaSharpImage(image);
+        }
+
+        private SKBitmap ResizeFull(int width, int height) => _image.Resize(new SKSizeI(width, height), SKFilterQuality.None);
+
+        private SKBitmap ResizeWithPadding(int width, int height)
+        {
+            float widthAspect = (float)width / _image.Width;
+            float heightAspect = (float)height / _image.Height;
+            int destX = 0;
+            int destY = 0;
+            float aspect;
+
+            if (heightAspect < widthAspect)
+            {
+                aspect = heightAspect;
+                destX = (int)((width - (_image.Width * aspect)) / 2);
+            }
+            else
+            {
+                aspect = widthAspect;
+                destY = (int)((height - (_image.Height * aspect)) / 2);
+            }
+
+            int destWidth = (int)(_image.Width * aspect);
+            int destHeight = (int)(_image.Height * aspect);
+
+            SKBitmap destBitmap = new SKBitmap(width, height, isOpaque: true);
+            SKRect srcRect = new SKRect(0, 0, _image.Width, _image.Height);
+            SKRect destRect = new SKRect(destX, destY, destX + destWidth, destY + destHeight);
+
+            using SKCanvas canvas = new SKCanvas(destBitmap);
+            using SKPaint paint = new SKPaint() { FilterQuality = SKFilterQuality.High };
+
+            canvas.DrawBitmap(_image, srcRect, destRect, paint);
+
+            return destBitmap;
+        }
+
+        private SKBitmap ResizeWithCrop(int width, int height, ImageResizeMode mode)
+        {
+            float widthAspect = (float)width / _image.Width;
+            float heightAspect = (float)height / _image.Height;
+            int destX = 0;
+            int destY = 0;
+            float aspect;
+
+            if (heightAspect < widthAspect)
+            {
+                aspect = widthAspect;
+                switch (mode)
+                {
+                    case ImageResizeMode.CropAnchorTop:
+                        destY = 0;
+                        break;
+                    case ImageResizeMode.CropAnchorBottom:
+                        destY = (int)(height - (_image.Height * aspect));
+                        break;
+                    default:
+                        destY = (int)((height - (_image.Height * aspect)) / 2);
+                        break;
+                }
+            }
+            else
+            {
+                aspect = heightAspect;
+                switch (mode)
+                {
+                    case ImageResizeMode.CropAnchorLeft:
+                        destX = 0;
+                        break;
+                    case ImageResizeMode.CropAnchorRight:
+                        destX = (int)(width - (_image.Width * aspect));
+                        break;
+                    default:
+                        destX = (int)((width - (_image.Width * aspect)) / 2);
+                        break;
+                }
+            }
+
+            int destWidth = (int)(_image.Width * aspect);
+            int destHeight = (int)(_image.Height * aspect);
+
+            SKBitmap dst = new SKBitmap(width, height, isOpaque: true);
+
+            SKRect srcRect = new SKRect(0, 0, _image.Width, _image.Height);
+            SKRect destRect = new SKRect(destX, destY, destX + destWidth, destY + destHeight);
+
+            using SKCanvas canvas = new SKCanvas(dst);
+            using SKPaint paint = new SKPaint() { FilterQuality = SKFilterQuality.High };
+
+            canvas.DrawBitmap(_image, srcRect, destRect, paint);
+
+            return dst;
+        }
+
+        // This matrix get multiplied to every pixel matrix [R G B A W] to average the colors values to get the grayscale effect.
+        private static readonly SKColorFilter _grayscaleColorMatrix = SKColorFilter.CreateColorMatrix(new float[]
+                                                                        {
+                                                                            0.3f, 0.59f, 0.11f, 0, 0,
+                                                                            0.3f, 0.59f, 0.11f, 0, 0,
+                                                                            0.3f, 0.59f, 0.11f, 0, 0,
+                                                                            0,    0,     0,     1, 0
+                                                                        });
+
+        public override ImageBase CloneWithGrayscale()
+        {
+            ThrowInvalidOperationExceptionIfDisposed();
+
+            SKBitmap dst = new SKBitmap(_image.Width, _image.Height, isOpaque: true);
+            using SKPaint paint = new SKPaint()
+            {
+                ColorFilter = _grayscaleColorMatrix,
+                FilterQuality = SKFilterQuality.High
+            };
+
+            SKBitmap destBitmap = new SKBitmap(_image.Width, _image.Height, isOpaque: true);
+            using SKCanvas canvas = new SKCanvas(destBitmap);
+            canvas.DrawBitmap(_image, 0f, 0f, paint: paint);
+            return new SkiaSharpImage(destBitmap);
+        }
+
+        public override int Width
+        {
+            get
+            {
+                ThrowInvalidOperationExceptionIfDisposed();
+                return _image.Width;
+            }
+        }
+
+        public override int Height
+        {
+            get
+            {
+                ThrowInvalidOperationExceptionIfDisposed();
+                return _image.Height;
+            }
+        }
+
+        public override int BitsPerPixel
+        {
+            get
+            {
+                ThrowInvalidOperationExceptionIfDisposed();
+                Debug.Assert(_image.Info.BitsPerPixel == 32);
+                return _image.Info.BitsPerPixel;
+            }
+        }
+
+        private static readonly Dictionary<string, SKEncodedImageFormat> _extensionToEncodingFormat = new Dictionary<string, SKEncodedImageFormat>(StringComparer.OrdinalIgnoreCase)
+        {
+            { ".bmp", SKEncodedImageFormat.Bmp },
+            { ".png", SKEncodedImageFormat.Png },
+            { ".jpg", SKEncodedImageFormat.Jpeg },
+            { ".jpeg", SKEncodedImageFormat.Jpeg },
+            { ".gif", SKEncodedImageFormat.Gif },
+            { ".ico", SKEncodedImageFormat.Ico },
+            { ".astc", SKEncodedImageFormat.Astc },
+            { ".avif", SKEncodedImageFormat.Avif },
+            { ".dng", SKEncodedImageFormat.Dng },
+            { ".heif", SKEncodedImageFormat.Heif },
+            { ".ktx", SKEncodedImageFormat.Ktx },
+            { ".pkm", SKEncodedImageFormat.Pkm },
+            { ".wbmp", SKEncodedImageFormat.Wbmp },
+            { ".webp", SKEncodedImageFormat.Webp }
+        };
+
+        public override void Save(string imagePath)
+        {
+            ThrowInvalidOperationExceptionIfDisposed();
+            string ext = Path.GetExtension(imagePath);
+
+            if (!_extensionToEncodingFormat.TryGetValue(ext, out SKEncodedImageFormat encodingFormat))
+            {
+                throw new ArgumentException($"Path with invalid image file extension.", nameof(imagePath));
+            }
+
+            using var stream = new FileStream(imagePath, FileMode.Create, FileAccess.Write);
+            SKData data = _image.Encode(encodingFormat, 100);
+            data.SaveTo(stream);
+        }
+
+        protected override void Dispose(bool disposing)
+        {
+            if (_image != null)
+            {
+                _image.Dispose();
+                _image = null;
+            }
+        }
+
+        private void ThrowInvalidOperationExceptionIfDisposed()
+        {
+            if (_image is null)
+            {
+                throw new InvalidOperationException("Object is disposed.");
+            }
+        }
+    }
+}
diff --git a/src/Microsoft.ML.ImageAnalytics/ImageGrayscale.cs b/src/Microsoft.ML.ImageAnalytics/ImageGrayscale.cs
index 7fc03117f1..d1c5acc718 100644
--- a/src/Microsoft.ML.ImageAnalytics/ImageGrayscale.cs
+++ b/src/Microsoft.ML.ImageAnalytics/ImageGrayscale.cs
@@ -4,8 +4,6 @@
 
 using System;
 using System.Collections.Generic;
-using System.Drawing;
-using System.Drawing.Imaging;
 using System.Linq;
 using System.Text;
 using Microsoft.ML;
@@ -140,16 +138,6 @@ private protected override void SaveModel(ModelSaveContext ctx)
             base.SaveColumns(ctx);
         }
 
-        private static readonly ColorMatrix _grayscaleColorMatrix = new ColorMatrix(
-                new float[][]
-                {
-                    new float[] {.3f, .3f, .3f, 0, 0},
-                    new float[] {.59f, .59f, .59f, 0, 0},
-                    new float[] {.11f, .11f, .11f, 0, 0},
-                    new float[] {0, 0, 0, 1, 0},
-                    new float[] {0, 0, 0, 0, 1}
-                });
-
         private protected override IRowMapper MakeRowMapper(DataViewSchema schema) => new Mapper(this, schema);
 
         private protected override void CheckInputColumn(DataViewSchema inputSchema, int col, int srcCol)
@@ -176,8 +164,8 @@ protected override Delegate MakeGetter(DataViewRow input, int iinfo, Func<int, b
                 Contracts.AssertValue(input);
                 Contracts.Assert(0 <= iinfo && iinfo < _parent.ColumnPairs.Length);
 
-                var src = default(Bitmap);
-                var getSrc = input.GetGetter<Bitmap>(input.Schema[ColMapNewToOld[iinfo]]);
+                var src = default(ImageBase);
+                var getSrc = input.GetGetter<ImageBase>(input.Schema[ColMapNewToOld[iinfo]]);
 
                 disposer =
                     () =>
@@ -189,8 +177,8 @@ protected override Delegate MakeGetter(DataViewRow input, int iinfo, Func<int, b
                         }
                     };
 
-                ValueGetter<Bitmap> del =
-                    (ref Bitmap dst) =>
+                ValueGetter<ImageBase> del =
+                    (ref ImageBase dst) =>
                     {
                         if (dst != null)
                             dst.Dispose();
@@ -199,16 +187,9 @@ protected override Delegate MakeGetter(DataViewRow input, int iinfo, Func<int, b
                         if (src == null || src.Height <= 0 || src.Width <= 0)
                             return;
 
-                        dst = new Bitmap(src.Width, src.Height);
-                        ImageAttributes attributes = new ImageAttributes();
-                        attributes.SetColorMatrix(_grayscaleColorMatrix);
-                        var srcRectangle = new Rectangle(0, 0, src.Width, src.Height);
-                        using (var g = Graphics.FromImage(dst))
-                        {
-                            g.DrawImage(src, srcRectangle, 0, 0, src.Width, src.Height, GraphicsUnit.Pixel, attributes);
-                        }
-
+                        dst = src.CloneWithGrayscale();
                         dst.Tag = src.Tag;
+
                         Contracts.Assert(dst.Width == src.Width && dst.Height == src.Height);
                     };
 
@@ -226,8 +207,8 @@ protected override Delegate MakeGetter(DataViewRow input, int iinfo, Func<int, b
     /// |  |  |
     /// | -- | -- |
     /// | Does this estimator need to look at the data to train its parameters? | No |
-    /// | Input column data type | <xref:System.Drawing.Bitmap> |
-    /// | Output column data type | <xref:System.Drawing.Bitmap> |
+    /// | Input column data type | <xref:Microsoft.ML.Data.ImageBase> |
+    /// | Output column data type | <xref:Microsoft.ML.Data.ImageBase> |
     /// | Required NuGet in addition to Microsoft.ML | Microsoft.ML.ImageAnalytics |
     /// | Exportable to ONNX | No |
     ///
diff --git a/src/Microsoft.ML.ImageAnalytics/ImageLoader.cs b/src/Microsoft.ML.ImageAnalytics/ImageLoader.cs
index 2b4502cb1f..8161ccbdad 100644
--- a/src/Microsoft.ML.ImageAnalytics/ImageLoader.cs
+++ b/src/Microsoft.ML.ImageAnalytics/ImageLoader.cs
@@ -217,7 +217,7 @@ private Delegate MakeGetterImageDataViewType(DataViewRow input, int iinfo, Func<
             {
                 Contracts.AssertValue(input);
                 Contracts.Assert(0 <= iinfo && iinfo < _parent.ColumnPairs.Length);
-                var lastImage = default(Bitmap);
+                var lastImage = default(ImageBase);
 
                 disposer = () =>
                 {
@@ -230,8 +230,8 @@ private Delegate MakeGetterImageDataViewType(DataViewRow input, int iinfo, Func<
 
                 var getSrc = input.GetGetter<ReadOnlyMemory<char>>(input.Schema[ColMapNewToOld[iinfo]]);
                 ReadOnlyMemory<char> src = default;
-                ValueGetter<Bitmap> del =
-                    (ref Bitmap dst) =>
+                ValueGetter<ImageBase> del =
+                    (ref ImageBase dst) =>
                     {
                         if (dst != null)
                         {
@@ -247,15 +247,11 @@ private Delegate MakeGetterImageDataViewType(DataViewRow input, int iinfo, Func<
                             if (!string.IsNullOrWhiteSpace(_parent.ImageFolder))
                                 path = Path.Combine(_parent.ImageFolder, path);
 
-                            // to avoid locking file, use the construct below to load bitmap
+                            // to avoid locking file, use the construct below to load the image
                             var bytes = File.ReadAllBytes(path);
                             var ms = new MemoryStream(bytes);
-                            dst = (Bitmap)Image.FromStream(ms);
+                            dst = ImageBase.CreateFromStream(ms);
                             dst.Tag = path;
-
-                            // Check for an incorrect pixel format which indicates the loading failed
-                            if (dst.PixelFormat == System.Drawing.Imaging.PixelFormat.DontCare)
-                                throw Host.Except($"Failed to load image {src.ToString()}.");
                         }
 
                         lastImage = dst;
@@ -385,14 +381,14 @@ protected override DataViewSchema.DetachedColumn[] GetOutputColumnsCore()
     /// | -- | -- |
     /// | Does this estimator need to look at the data to train its parameters? | No |
     /// | Input column data type | [Text](<xref:Microsoft.ML.Data.TextDataViewType>) |
-    /// | Output column data type | <xref:System.Drawing.Bitmap> |
+    /// | Output column data type | <xref:Microsoft.ML.Data.ImageBase> |
     /// | Required NuGet in addition to Microsoft.ML | Microsoft.ML.ImageAnalytics |
     /// | Exportable to ONNX | No |
     ///
     /// The resulting <xref:Microsoft.ML.Data.ImageLoadingTransformer> creates a new column, named as specified in the output column name parameters, and
     /// loads in it images specified in the input column.
     /// Loading is the first step of almost every pipeline that does image processing, and further analysis on images.
-    /// The images to load need to be in the formats supported by <xref:System.Drawing.Bitmap>.
+    /// The images to load need to be in the formats supported by <xref:Microsoft.ML.Data.ImageBase> implementation.
     /// For end-to-end image processing pipelines, and scenarios in your applications, see the
     /// [examples](https://github.com/dotnet/machinelearning-samples/tree/main/samples/csharp/getting-started) in the machinelearning-samples github repository.</a>
     ///
diff --git a/src/Microsoft.ML.ImageAnalytics/ImagePixelExtractor.cs b/src/Microsoft.ML.ImageAnalytics/ImagePixelExtractor.cs
index 6bcace5b83..60506e1465 100644
--- a/src/Microsoft.ML.ImageAnalytics/ImagePixelExtractor.cs
+++ b/src/Microsoft.ML.ImageAnalytics/ImagePixelExtractor.cs
@@ -4,8 +4,6 @@
 
 using System;
 using System.Collections.Generic;
-using System.Drawing;
-using System.Drawing.Imaging;
 using System.Linq;
 using System.Runtime.InteropServices;
 using System.Text;
@@ -322,8 +320,8 @@ private ValueGetter<VBuffer<TValue>> GetGetterCore<TValue>(DataViewRow input, in
                 Contracts.Assert(size == planes * height * width);
                 int cpix = height * width;
 
-                var getSrc = input.GetGetter<Bitmap>(input.Schema[ColMapNewToOld[iinfo]]);
-                var src = default(Bitmap);
+                var getSrc = input.GetGetter<ImageBase>(input.Schema[ColMapNewToOld[iinfo]]);
+                var src = default(ImageBase);
 
                 disposer =
                     () =>
@@ -349,20 +347,7 @@ private ValueGetter<VBuffer<TValue>> GetGetterCore<TValue>(DataViewRow input, in
 
                         Host.Check(src.Height == height && src.Width == width);
 
-                        if (src.PixelFormat != PixelFormat.Format32bppArgb && src.PixelFormat != PixelFormat.Format24bppRgb)
-                        {
-                            var clone = src.Clone(new Rectangle(0, 0, src.Width, src.Height), PixelFormat.Format32bppArgb);
-                            clone.Tag = src.Tag;
-                            src.Dispose();
-                            src = clone;
-                            using (var ch = Host.Start(nameof(ImagePixelExtractingTransformer)))
-                            {
-                                ch.Warning($"Encountered image {src.Tag} of unsupported pixel format {src.PixelFormat} but converting it to {nameof(PixelFormat.Format32bppArgb)}.");
-                            }
-                        }
-
                         var editor = VBufferEditor.Create(ref dst, size);
-                        var values = editor.Values;
 
                         float offset = ex.OffsetImage;
                         float scale = ex.ScaleImage;
@@ -377,92 +362,82 @@ private ValueGetter<VBuffer<TValue>> GetGetterCore<TValue>(DataViewRow input, in
 
                         ImagePixelExtractingEstimator.GetOrder(ex.OrderOfExtraction, ex.ColorsToExtract, out int a, out int r, out int b, out int g);
 
-                        BitmapData bmpData = null;
-                        try
+                        ReadOnlySpan<byte> pixelData = src.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
+
+                        int h = height;
+                        int w = width;
+                        int pixelByteCount = alphaIndex > 0 ? 4 : 3;
+                        int ix = 0;
+
+                        if (ex.InterleavePixelColors)
                         {
-                            bmpData = src.LockBits(new Rectangle(0, 0, src.Width, src.Height), ImageLockMode.ReadOnly, src.PixelFormat);
-                            int h = height;
-                            int w = width;
-                            byte[] row = new byte[bmpData.Stride];
-                            int pixelSize = System.Drawing.Image.GetPixelFormatSize(src.PixelFormat) / 8;
-                            Func<int, byte> alpha = pixelSize > 3 ? new Func<int, byte>(ix => row[ix + 3]) : new Func<int, byte>(ix => 255);
-
-                            if (ex.InterleavePixelColors)
+                            int idst = 0;
+                            for (int y = 0; y < h; ++y)
                             {
-                                int idst = 0;
-                                for (int y = 0; y < h; ++y)
+                                for (int x = 0; x < w; x++)
                                 {
-                                    Marshal.Copy(bmpData.Scan0 + bmpData.Stride * y, row, 0, bmpData.Stride);
-                                    for (int x = 0; x < w; x++)
+                                    if (!vb.IsEmpty)
+                                    {
+                                        if (a != -1) { vb[idst + a] = (byte)(alphaIndex > 0 ? pixelData[ix + alphaIndex] : 255); }
+                                        if (r != -1) { vb[idst + r] = pixelData[ix + redIndex]; }
+                                        if (g != -1) { vb[idst + g] = pixelData[ix + greenIndex]; }
+                                        if (b != -1) { vb[idst + b] = pixelData[ix + blueIndex]; }
+                                    }
+                                    else if (!needScale)
+                                    {
+                                        if (a != -1) { vf[idst + a] = (byte)(alphaIndex > 0 ? pixelData[ix + alphaIndex] : 255); }
+                                        if (r != -1) { vf[idst + r] = pixelData[ix + redIndex]; }
+                                        if (g != -1) { vf[idst + g] = pixelData[ix + greenIndex]; }
+                                        if (b != -1) { vf[idst + b] = pixelData[ix + blueIndex]; }
+                                    }
+                                    else
                                     {
-                                        var ix = x * pixelSize;
-                                        if (!vb.IsEmpty)
-                                        {
-                                            if (a != -1) { vb[idst + a] = alpha(ix); }
-                                            if (r != -1) { vb[idst + r] = row[ix + 2]; }
-                                            if (g != -1) { vb[idst + g] = row[ix + 1]; }
-                                            if (b != -1) { vb[idst + b] = row[ix + 0]; }
-                                        }
-                                        else if (!needScale)
-                                        {
-                                            if (a != -1) { vf[idst + a] = alpha(ix); }
-                                            if (r != -1) { vf[idst + r] = row[ix + 2]; }
-                                            if (g != -1) { vf[idst + g] = row[ix + 1]; }
-                                            if (b != -1) { vf[idst + b] = row[ix + 0]; }
-                                        }
-                                        else
-                                        {
-
-                                            if (a != -1) { vf[idst + a] = (alpha(ix) - offset) * scale; }
-                                            if (r != -1) { vf[idst + r] = (row[ix + 2] - offset) * scale; }
-                                            if (g != -1) { vf[idst + g] = (row[ix + 1] - offset) * scale; }
-                                            if (b != -1) { vf[idst + b] = (row[ix + 0] - offset) * scale; }
-                                        }
-                                        idst += ex.Planes;
+                                        if (a != -1) { vf[idst + a] = ((byte)(alphaIndex > 0 ? pixelData[ix + alphaIndex] : 255) - offset) * scale; }
+                                        if (r != -1) { vf[idst + r] = (pixelData[ix + redIndex] - offset) * scale; }
+                                        if (g != -1) { vf[idst + g] = (pixelData[ix + greenIndex] - offset) * scale; }
+                                        if (b != -1) { vf[idst + b] = (pixelData[ix + blueIndex] - offset) * scale; }
                                     }
+
+                                    ix += pixelByteCount;
+                                    idst += ex.Planes;
                                 }
-                                Contracts.Assert(idst == size);
                             }
-                            else
+                            Contracts.Assert(idst == size);
+                        }
+                        else
+                        {
+                            int idstMin = 0;
+                            for (int y = 0; y < h; ++y)
                             {
-                                int idstMin = 0;
-                                for (int y = 0; y < h; ++y)
+                                int idst = idstMin + y * w;
+                                for (int x = 0; x < w; x++, idst++)
                                 {
-                                    Marshal.Copy(bmpData.Scan0 + bmpData.Stride * y, row, 0, bmpData.Stride);
-                                    int idst = idstMin + y * w;
-                                    for (int x = 0; x < w; x++, idst++)
+                                    if (!vb.IsEmpty)
+                                    {
+                                        if (a != -1) vb[idst + cpix * a] = (byte)(alphaIndex > 0 ? pixelData[ix + alphaIndex] : 255);
+                                        if (r != -1) vb[idst + cpix * r] = pixelData[ix + redIndex];
+                                        if (g != -1) vb[idst + cpix * g] = pixelData[ix + greenIndex];
+                                        if (b != -1) vb[idst + cpix * b] = pixelData[ix + blueIndex];
+                                    }
+                                    else if (!needScale)
                                     {
-                                        var ix = x * pixelSize;
-                                        if (!vb.IsEmpty)
-                                        {
-                                            if (a != -1) vb[idst + cpix * a] = alpha(ix);
-                                            if (r != -1) vb[idst + cpix * r] = row[ix + 2];
-                                            if (g != -1) vb[idst + cpix * g] = row[ix + 1];
-                                            if (b != -1) vb[idst + cpix * b] = row[ix + 0];
-                                        }
-                                        else if (!needScale)
-                                        {
-                                            if (a != -1) vf[idst + cpix * a] = alpha(ix);
-                                            if (r != -1) vf[idst + cpix * r] = row[ix + 2];
-                                            if (g != -1) vf[idst + cpix * g] = row[ix + 1];
-                                            if (b != -1) vf[idst + cpix * b] = row[ix + 0];
-                                        }
-                                        else
-                                        {
-                                            if (a != -1) vf[idst + cpix * a] = (alpha(ix) - offset) * scale;
-                                            if (r != -1) vf[idst + cpix * r] = (row[ix + 2] - offset) * scale;
-                                            if (g != -1) vf[idst + cpix * g] = (row[ix + 1] - offset) * scale;
-                                            if (b != -1) vf[idst + cpix * b] = (row[ix + 0] - offset) * scale;
-                                        }
+                                        if (a != -1) vf[idst + cpix * a] = (byte)(alphaIndex > 0 ? pixelData[ix + alphaIndex] : 255);
+                                        if (r != -1) vf[idst + cpix * r] = pixelData[ix + redIndex];
+                                        if (g != -1) vf[idst + cpix * g] = pixelData[ix + greenIndex];
+                                        if (b != -1) vf[idst + cpix * b] = pixelData[ix + blueIndex];
                                     }
+                                    else
+                                    {
+                                        if (a != -1) vf[idst + cpix * a] = ((byte)(alphaIndex > 0 ? pixelData[ix + alphaIndex] : 255) - offset) * scale;
+                                        if (r != -1) vf[idst + cpix * r] = (pixelData[ix + redIndex] - offset) * scale;
+                                        if (g != -1) vf[idst + cpix * g] = (pixelData[ix + greenIndex] - offset) * scale;
+                                        if (b != -1) vf[idst + cpix * b] = (pixelData[ix + blueIndex] - offset) * scale;
+                                    }
+
+                                    ix += pixelByteCount;
                                 }
                             }
                         }
-                        finally
-                        {
-                            if (bmpData != null)
-                                src.UnlockBits(bmpData);
-                        }
 
                         dst = editor.Commit();
                     };
@@ -505,7 +480,7 @@ private VectorDataViewType[] ConstructTypes()
     /// |  |  |
     /// | -- | -- |
     /// | Does this estimator need to look at the data to train its parameters? | No |
-    /// | Input column data type | <xref:System.Drawing.Bitmap> |
+    /// | Input column data type | <xref:Microsoft.ML.Data.ImageBase> |
     /// | Output column data type | Known-sized vector of <xref:System.Single> or <xref:System.Byte> |
     /// | Required NuGet in addition to Microsoft.ML | Microsoft.ML.ImageAnalytics |
     /// | Exportable to ONNX | No |
diff --git a/src/Microsoft.ML.ImageAnalytics/ImageResizer.cs b/src/Microsoft.ML.ImageAnalytics/ImageResizer.cs
index 21a073aff5..abba92cb53 100644
--- a/src/Microsoft.ML.ImageAnalytics/ImageResizer.cs
+++ b/src/Microsoft.ML.ImageAnalytics/ImageResizer.cs
@@ -4,8 +4,6 @@
 
 using System;
 using System.Collections.Generic;
-using System.Drawing;
-using System.Drawing.Imaging;
 using System.Linq;
 using System.Text;
 using Microsoft.ML;
@@ -276,8 +274,8 @@ protected override Delegate MakeGetter(DataViewRow input, int iinfo, Func<int, b
                 Contracts.AssertValue(input);
                 Contracts.Assert(0 <= iinfo && iinfo < _parent._columns.Length);
 
-                var src = default(Bitmap);
-                var getSrc = input.GetGetter<Bitmap>(input.Schema[ColMapNewToOld[iinfo]]);
+                var src = default(ImageBase);
+                var getSrc = input.GetGetter<ImageBase>(input.Schema[ColMapNewToOld[iinfo]]);
                 var info = _parent._columns[iinfo];
 
                 disposer =
@@ -289,11 +287,13 @@ protected override Delegate MakeGetter(DataViewRow input, int iinfo, Func<int, b
                         }
                     };
 
-                ValueGetter<Bitmap> del =
-                    (ref Bitmap dst) =>
+                ValueGetter<ImageBase> del =
+                    (ref ImageBase dst) =>
                     {
                         if (dst != null)
+                        {
                             dst.Dispose();
+                        }
 
                         getSrc(ref src);
                         if (src == null || src.Height <= 0 || src.Width <= 0)
@@ -304,108 +304,24 @@ protected override Delegate MakeGetter(DataViewRow input, int iinfo, Func<int, b
                             return;
                         }
 
-                        int sourceWidth = src.Width;
-                        int sourceHeight = src.Height;
-                        int sourceX = 0;
-                        int sourceY = 0;
-                        int destX = 0;
-                        int destY = 0;
-                        int destWidth = 0;
-                        int destHeight = 0;
-                        float aspect = 0;
-                        float widthAspect = 0;
-                        float heightAspect = 0;
-
-                        widthAspect = (float)info.ImageWidth / sourceWidth;
-                        heightAspect = (float)info.ImageHeight / sourceHeight;
-
-                        if (info.Resizing == ImageResizingEstimator.ResizingKind.IsoPad)
-                        {
-                            widthAspect = (float)info.ImageWidth / sourceWidth;
-                            heightAspect = (float)info.ImageHeight / sourceHeight;
-                            if (heightAspect < widthAspect)
-                            {
-                                aspect = heightAspect;
-                                destX = (int)((info.ImageWidth - (sourceWidth * aspect)) / 2);
-                            }
-                            else
-                            {
-                                aspect = widthAspect;
-                                destY = (int)((info.ImageHeight - (sourceHeight * aspect)) / 2);
-                            }
-
-                            destWidth = (int)(sourceWidth * aspect);
-                            destHeight = (int)(sourceHeight * aspect);
-                        }
-                        else if (info.Resizing == ImageResizingEstimator.ResizingKind.IsoCrop)
-                        {
-                            if (heightAspect < widthAspect)
-                            {
-                                aspect = widthAspect;
-                                switch (info.Anchor)
-                                {
-                                    case ImageResizingEstimator.Anchor.Top:
-                                        destY = 0;
-                                        break;
-                                    case ImageResizingEstimator.Anchor.Bottom:
-                                        destY = (int)(info.ImageHeight - (sourceHeight * aspect));
-                                        break;
-                                    default:
-                                        destY = (int)((info.ImageHeight - (sourceHeight * aspect)) / 2);
-                                        break;
-                                }
-                            }
-                            else
-                            {
-                                aspect = heightAspect;
-                                switch (info.Anchor)
+                        dst = src.CloneWithResizing(
+                                info.ImageWidth,
+                                info.ImageHeight,
+                                info.Resizing switch
                                 {
-                                    case ImageResizingEstimator.Anchor.Left:
-                                        destX = 0;
-                                        break;
-                                    case ImageResizingEstimator.Anchor.Right:
-                                        destX = (int)(info.ImageWidth - (sourceWidth * aspect));
-                                        break;
-                                    default:
-                                        destX = (int)((info.ImageWidth - (sourceWidth * aspect)) / 2);
-                                        break;
-                                }
-                            }
-
-                            destWidth = (int)(sourceWidth * aspect);
-                            destHeight = (int)(sourceHeight * aspect);
-                        }
-                        else if (info.Resizing == ImageResizingEstimator.ResizingKind.Fill)
-                        {
-                            destWidth = info.ImageWidth;
-                            destHeight = info.ImageHeight;
-                        }
-
-                        // Graphics.DrawImage() does not support PixelFormat.Indexed. Hence convert the
-                        // pixel format to Format32bppArgb as described here https://stackoverflow.com/questions/17313285/graphics-on-indexed-image
-                        // For images with invalid pixel format also use Format32bppArgb to draw the resized image.
-                        // For images with Format16bppGrayScale or Format16bppArgb1555 GDI+ does not
-                        // support these formats, ref: https://bytes.com/topic/c-sharp/answers/278572-out-memory-graphics-fromimage
-                        if ((src.PixelFormat & PixelFormat.Indexed) != 0 ||
-                            src.PixelFormat == PixelFormat.Format16bppGrayScale ||
-                            src.PixelFormat == PixelFormat.Format16bppArgb1555 ||
-                            !Enum.IsDefined(typeof(PixelFormat), src.PixelFormat))
-                        {
-                            dst = new Bitmap(info.ImageWidth, info.ImageHeight);
-                            using (var ch = Host.Start(nameof(ImageResizingTransformer)))
-                            {
-                                ch.Warning($"Encountered image {src.Tag} of unsupported pixel format {src.PixelFormat} but converting it to {nameof(PixelFormat.Format32bppArgb)}.");
-                            }
-                        }
-                        else
-                            dst = new Bitmap(info.ImageWidth, info.ImageHeight, src.PixelFormat);
-
-                        var srcRectangle = new Rectangle(sourceX, sourceY, sourceWidth, sourceHeight);
-                        var destRectangle = new Rectangle(destX, destY, destWidth, destHeight);
-                        using (var g = Graphics.FromImage(dst))
-                        {
-                            g.DrawImage(src, destRectangle, srcRectangle, GraphicsUnit.Pixel);
-                        }
+                                    ImageResizingEstimator.ResizingKind.IsoPad => ImageResizeMode.Pad,
+                                    ImageResizingEstimator.ResizingKind.IsoCrop =>
+                                        info.Anchor switch
+                                        {
+                                            ImageResizingEstimator.Anchor.Top => ImageResizeMode.CropAnchorTop,
+                                            ImageResizingEstimator.Anchor.Bottom => ImageResizeMode.CropAnchorBottom,
+                                            ImageResizingEstimator.Anchor.Left => ImageResizeMode.CropAnchorLeft,
+                                            ImageResizingEstimator.Anchor.Right => ImageResizeMode.CropAnchorRight,
+                                            _ => ImageResizeMode.CropAnchorCentral
+                                        },
+                                    ImageResizingEstimator.ResizingKind.Fill => ImageResizeMode.Fill,
+                                    _ => throw new InvalidOperationException($"Invalid image resizing mode value")
+                                });
 
                         dst.Tag = src.Tag;
                         Contracts.Assert(dst.Width == info.ImageWidth && dst.Height == info.ImageHeight);
@@ -426,8 +342,8 @@ protected override Delegate MakeGetter(DataViewRow input, int iinfo, Func<int, b
     /// |  |  |
     /// | -- | -- |
     /// | Does this estimator need to look at the data to train its parameters? | No |
-    /// | Input column data type | <xref:System.Drawing.Bitmap> |
-    /// | Output column data type | <xref:System.Drawing.Bitmap> |
+    /// | Input column data type | <xref:Microsoft.ML.Data.ImageBase> |
+    /// | Output column data type | <xref:Microsoft.ML.Data.ImageBase> |
     /// | Required NuGet in addition to Microsoft.ML | Microsoft.ML.ImageAnalytics |
     /// | Exportable to ONNX | No |
     ///
diff --git a/src/Microsoft.ML.ImageAnalytics/ImageType.cs b/src/Microsoft.ML.ImageAnalytics/ImageType.cs
index a6024eac49..bdf7447401 100644
--- a/src/Microsoft.ML.ImageAnalytics/ImageType.cs
+++ b/src/Microsoft.ML.ImageAnalytics/ImageType.cs
@@ -3,7 +3,6 @@
 // See the LICENSE file in the project root for more information.
 
 using System;
-using System.Drawing;
 using Microsoft.ML.Data;
 using Microsoft.ML.Internal.Utilities;
 using Microsoft.ML.Runtime;
@@ -64,7 +63,7 @@ public override int GetHashCode()
 
         public override void Register()
         {
-            DataViewTypeManager.Register(new ImageDataViewType(Height, Width), typeof(Bitmap), this);
+            DataViewTypeManager.Register(new ImageDataViewType(Height, Width), typeof(ImageBase), this);
         }
     }
 
@@ -74,7 +73,7 @@ public sealed class ImageDataViewType : StructuredDataViewType
         public readonly int Width;
 
         public ImageDataViewType(int height, int width)
-           : base(typeof(Bitmap))
+           : base(typeof(ImageBase))
         {
             Contracts.CheckParam(height > 0, nameof(height), "Must be positive.");
             Contracts.CheckParam(width > 0, nameof(width), " Must be positive.");
@@ -84,7 +83,7 @@ public ImageDataViewType(int height, int width)
             Width = width;
         }
 
-        public ImageDataViewType() : base(typeof(Bitmap))
+        public ImageDataViewType() : base(typeof(ImageBase))
         {
         }
 
diff --git a/src/Microsoft.ML.ImageAnalytics/Microsoft.ML.ImageAnalytics.csproj b/src/Microsoft.ML.ImageAnalytics/Microsoft.ML.ImageAnalytics.csproj
index c724dd9429..7ce31c3614 100644
--- a/src/Microsoft.ML.ImageAnalytics/Microsoft.ML.ImageAnalytics.csproj
+++ b/src/Microsoft.ML.ImageAnalytics/Microsoft.ML.ImageAnalytics.csproj
@@ -5,11 +5,17 @@
     <TargetFramework>netstandard2.0</TargetFramework>
     <IncludeInPackage>Microsoft.ML.ImageAnalytics</IncludeInPackage>
     <PackageDescription>ML.NET component for Image support</PackageDescription>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
 
   <ItemGroup>
     <PackageReference Include="System.Buffers" Version="$(SystemBuffersVersion)" />
-    <PackageReference Include="System.Drawing.Common" Version="$(SystemDrawingCommonVersion)" />
+    <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="$(SystemRuntimeCompilerServicesUnsafeVersion)" />
+
+    <PackageReference Include="SkiaSharp" Version="$(SkiaSharpVersion)" />
+    <PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="$(SkiaSharpVersion)" />
+    <PackageReference Include="SkiaSharp.NativeAssets.macOS" Version="$(SkiaSharpVersion)" />
+    <PackageReference Include="SkiaSharp.NativeAssets.Win32" Version="$(SkiaSharpVersion)" />
   </ItemGroup>
 
   <ItemGroup>
diff --git a/src/Microsoft.ML.ImageAnalytics/VectorToImageTransform.cs b/src/Microsoft.ML.ImageAnalytics/VectorToImageTransform.cs
index cfd4f74f46..e75488ff24 100644
--- a/src/Microsoft.ML.ImageAnalytics/VectorToImageTransform.cs
+++ b/src/Microsoft.ML.ImageAnalytics/VectorToImageTransform.cs
@@ -4,8 +4,6 @@
 
 using System;
 using System.Collections.Generic;
-using System.Drawing;
-using System.Drawing.Imaging;
 using System.Linq;
 using System.Runtime.InteropServices;
 using System.Text;
@@ -343,7 +341,7 @@ protected override Delegate MakeGetter(DataViewRow input, int iinfo, Func<int, b
                     throw Contracts.Except("We only support float, double or byte arrays");
             }
 
-            private ValueGetter<Bitmap> GetterFromType<TValue>(PrimitiveDataViewType srcType, DataViewRow input, int iinfo,
+            private ValueGetter<ImageBase> GetterFromType<TValue>(PrimitiveDataViewType srcType, DataViewRow input, int iinfo,
                 VectorToImageConvertingEstimator.ColumnOptions ex, bool needScale) where TValue : IConvertible
             {
                 Contracts.Assert(typeof(TValue) == srcType.RawType);
@@ -355,7 +353,7 @@ private ValueGetter<Bitmap> GetterFromType<TValue>(PrimitiveDataViewType srcType
                 float scale = ex.ScaleImage;
 
                 return
-                    (ref Bitmap dst) =>
+                    (ref ImageBase dst) =>
                     {
                         getSrc(ref src);
                         if (src.GetValues().Length == 0)
@@ -366,70 +364,59 @@ private ValueGetter<Bitmap> GetterFromType<TValue>(PrimitiveDataViewType srcType
                         VBuffer<TValue> dense = default;
                         src.CopyToDense(ref dense);
                         var values = dense.GetValues();
-                        dst = new Bitmap(width, height);
-                        dst.SetResolution(width, height);
                         int cpix = height * width;
                         int position = 0;
                         ImagePixelExtractingEstimator.GetOrder(ex.Order, ex.Colors, out int a, out int r, out int b, out int g);
 
-                        BitmapData bmpData = null;
-                        try
+                        byte[] imageData = new byte[width * height * 4]; // 4 for bgra data blue, green, red, and alpha.
+                        int ix = 0;
+                        for (int y = 0; y < height; ++y)
                         {
-                            bmpData = dst.LockBits(new Rectangle(0, 0, dst.Width, dst.Height), ImageLockMode.WriteOnly, dst.PixelFormat);
-                            for (int y = 0; y < height; ++y)
+                            for (int x = 0; x < width; x++)
                             {
-                                byte[] row = new byte[bmpData.Stride];
-                                int ix = 0;
-                                for (int x = 0; x < width; x++)
+                                float red = ex.DefaultRed;
+                                float green = ex.DefaultGreen;
+                                float blue = ex.DefaultBlue;
+                                float alpha = ex.DefaultAlpha;
+                                if (ex.InterleavedColors)
                                 {
-                                    float red = ex.DefaultRed;
-                                    float green = ex.DefaultGreen;
-                                    float blue = ex.DefaultBlue;
-                                    float alpha = ex.DefaultAlpha;
-                                    if (ex.InterleavedColors)
-                                    {
-                                        if (ex.Alpha)
-                                            alpha = Convert.ToSingle(values[position + a]);
-                                        if (ex.Red)
-                                            red = Convert.ToSingle(values[position + r]);
-                                        if (ex.Green)
-                                            green = Convert.ToSingle(values[position + g]);
-                                        if (ex.Blue)
-                                            blue = Convert.ToSingle(values[position + b]);
-                                        position += ex.Planes;
-                                    }
-                                    else
-                                    {
-                                        position = y * width + x;
-                                        if (ex.Alpha) alpha = Convert.ToSingle(values[position + cpix * a]);
-                                        if (ex.Red) red = Convert.ToSingle(values[position + cpix * r]);
-                                        if (ex.Green) green = Convert.ToSingle(values[position + cpix * g]);
-                                        if (ex.Blue) blue = Convert.ToSingle(values[position + cpix * b]);
-                                    }
-                                    if (!needScale)
-                                    {
-                                        row[ix++] = (byte)blue;
-                                        row[ix++] = (byte)green;
-                                        row[ix++] = (byte)red;
-                                        row[ix++] = (byte)alpha;
-                                    }
-                                    else
-                                    {
-                                        row[ix++] = (byte)Math.Round(blue * scale - offset);
-                                        row[ix++] = (byte)Math.Round(green * scale - offset);
-                                        row[ix++] = (byte)Math.Round(red * scale - offset);
-                                        row[ix++] = (byte)(ex.Alpha ? Math.Round(alpha * scale - offset) : 0);
-                                    }
+                                    if (ex.Alpha)
+                                        alpha = Convert.ToSingle(values[position + a]);
+                                    if (ex.Red)
+                                        red = Convert.ToSingle(values[position + r]);
+                                    if (ex.Green)
+                                        green = Convert.ToSingle(values[position + g]);
+                                    if (ex.Blue)
+                                        blue = Convert.ToSingle(values[position + b]);
+                                    position += ex.Planes;
+                                }
+                                else
+                                {
+                                    position = y * width + x;
+                                    if (ex.Alpha) alpha = Convert.ToSingle(values[position + cpix * a]);
+                                    if (ex.Red) red = Convert.ToSingle(values[position + cpix * r]);
+                                    if (ex.Green) green = Convert.ToSingle(values[position + cpix * g]);
+                                    if (ex.Blue) blue = Convert.ToSingle(values[position + cpix * b]);
+                                }
+                                if (!needScale)
+                                {
+                                    imageData[ix++] = (byte)blue;
+                                    imageData[ix++] = (byte)green;
+                                    imageData[ix++] = (byte)red;
+                                    imageData[ix++] = (byte)alpha;
+                                }
+                                else
+                                {
+                                    imageData[ix++] = (byte)Math.Round(blue * scale - offset);
+                                    imageData[ix++] = (byte)Math.Round(green * scale - offset);
+                                    imageData[ix++] = (byte)Math.Round(red * scale - offset);
+                                    imageData[ix++] = (byte)(ex.Alpha ? Math.Round(alpha * scale - offset) : 0);
                                 }
-                                Marshal.Copy(row, 0, bmpData.Scan0 + y * bmpData.Stride, bmpData.Stride);
                             }
-                            dst.Tag = nameof(VectorToImageConvertingTransformer);
-                        }
-                        finally
-                        {
-                            if (bmpData != null)
-                                dst.UnlockBits(bmpData);
                         }
+
+                        dst = ImageBase.CreateBgra32Image(width, height, imageData);
+                        dst.Tag = nameof(VectorToImageConvertingTransformer);
                     };
             }
 
@@ -451,7 +438,7 @@ private static ImageDataViewType[] ConstructTypes(VectorToImageConvertingEstimat
     /// | -- | -- |
     /// | Does this estimator need to look at the data to train its parameters? | No |
     /// | Input column data type | Known-sized vector of <xref:System.Single>, <xref:System.Double> or <xref:System.Byte>. |
-    /// | Output column data type | <xref:System.Drawing.Bitmap> |
+    /// | Output column data type | <xref:Microsoft.ML.Data.ImageBase> |
     /// | Required NuGet in addition to Microsoft.ML | Microsoft.ML.ImageAnalytics |
     /// | Exportable to ONNX | No |
     ///
diff --git a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs
index c15d300ab5..60796474b0 100644
--- a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs
+++ b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs
@@ -451,7 +451,7 @@ private static void CheckRootCauseInput(IHostEnvironment host, RootCauseLocaliza
         /// <param name="seriesLength">The length of series that is kept in buffer for modeling (parameter N).</param>
         /// <param name="trainSize">The length of series from the beginning used for training.</param>
         /// <param name="horizon">The number of values to forecast.</param>
-        /// <param name="isAdaptive">The flag determing whether the model is adaptive.</param>
+        /// <param name="isAdaptive">The flag determining whether the model is adaptive.</param>
         /// <param name="discountFactor">The discount factor in [0,1] used for online updates.</param>
         /// <param name="rankSelectionMethod">The rank selection method.</param>
         /// <param name="rank">The desired rank of the subspace used for SSA projection (parameter r). This parameter should be in the range in [1, windowSize].
diff --git a/test/Microsoft.ML.OnnxTransformerTest/OnnxTransformTests.cs b/test/Microsoft.ML.OnnxTransformerTest/OnnxTransformTests.cs
index 09a08b4aed..061ffa3b6b 100644
--- a/test/Microsoft.ML.OnnxTransformerTest/OnnxTransformTests.cs
+++ b/test/Microsoft.ML.OnnxTransformerTest/OnnxTransformTests.cs
@@ -4,7 +4,6 @@
 
 using System;
 using System.Collections.Generic;
-using System.Drawing;
 using System.IO;
 using System.Linq;
 using Microsoft.ML.Data;
@@ -501,7 +500,7 @@ private class ImageDataPoint
             /// Image will be consumed by ONNX image multiclass classification model.
             /// </summary>
             [ImageType(Height, Width)]
-            public Bitmap Image { get; set; }
+            public ImageBase Image { get; set; }
 
             /// <summary>
             /// Output of ONNX model. It contains probabilities of all classes.
@@ -514,12 +513,19 @@ public ImageDataPoint()
                 Image = null;
             }
 
-            public ImageDataPoint(Color color)
+            public ImageDataPoint(byte red, byte green, byte blue)
             {
-                Image = new Bitmap(Width, Height);
-                for (int i = 0; i < Width; ++i)
-                    for (int j = 0; j < Height; ++j)
-                        Image.SetPixel(i, j, color);
+                byte[] imageData = new byte[Width * Height * 4]; // 4 for the red, green, blue and alpha colors
+                for (int i = 0; i < imageData.Length; i += 4)
+                {
+                    // Fill the buffer with the Bgra32 format
+                    imageData[i] = blue;
+                    imageData[i + 1] = green;
+                    imageData[i + 2] = red;
+                    imageData[i + 3] = 255;
+                }
+
+                Image = ImageBase.CreateBgra32Image(Width, Height, imageData);
             }
         }
 
@@ -535,8 +541,8 @@ public void OnnxModelInMemoryImage()
             // Create in-memory data points. Its Image/Scores field is the input/output of the used ONNX model.
             var dataPoints = new ImageDataPoint[]
             {
-                new ImageDataPoint(Color.Red),
-                new ImageDataPoint(Color.Green)
+                new ImageDataPoint(red: 255, green: 0, blue: 0),
+                new ImageDataPoint(red: 0, green: 255, blue: 0),
             };
 
             // Convert training data to IDataView, the general data type used in ML.NET.
diff --git a/test/Microsoft.ML.Tests/ImagesTests.cs b/test/Microsoft.ML.Tests/ImagesTests.cs
index 342aaa635a..1b5b358e8b 100644
--- a/test/Microsoft.ML.Tests/ImagesTests.cs
+++ b/test/Microsoft.ML.Tests/ImagesTests.cs
@@ -4,7 +4,6 @@
 
 using System;
 using System.Collections.Generic;
-using System.Drawing;
 using System.IO;
 using System.Linq;
 using System.Runtime.InteropServices;
@@ -153,22 +152,22 @@ public void TestSaveImages()
             {
                 var pathGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cropped.Schema["ImagePath"]);
                 ReadOnlyMemory<char> path = default;
-                var bitmapCropGetter = cursor.GetGetter<Bitmap>(cropped.Schema["ImageCropped"]);
-                Bitmap bitmap = default;
+                var imageCropGetter = cursor.GetGetter<ImageBase>(cropped.Schema["ImageCropped"]);
+                ImageBase image = default;
                 while (cursor.MoveNext())
                 {
                     pathGetter(ref path);
-                    bitmapCropGetter(ref bitmap);
-                    Assert.NotNull(bitmap);
+                    imageCropGetter(ref image);
+                    Assert.NotNull(image);
                     var fileToSave = GetOutputPath(Path.GetFileNameWithoutExtension(path.ToString()) + ".cropped.jpg");
-                    bitmap.Save(fileToSave, System.Drawing.Imaging.ImageFormat.Jpeg);
+                    image.Save(fileToSave);
                 }
             }
             Done();
         }
 
         [Fact]
-        public void TestGreyscaleTransformImages()
+        public void TestGrayscaleTransformImages()
         {
             IHostEnvironment env = new MLContext(1);
             var imageHeight = 150;
@@ -188,7 +187,7 @@ public void TestGreyscaleTransformImages()
             var cropped = new ImageResizingTransformer(env, "ImageCropped", imageWidth, imageHeight, "ImageReal").Transform(images);
 
             IDataView grey = new ImageGrayscalingTransformer(env, ("ImageGrey", "ImageCropped")).Transform(cropped);
-            var fname = nameof(TestGreyscaleTransformImages) + "_model.zip";
+            var fname = nameof(TestGrayscaleTransformImages) + "_model.zip";
 
             var fh = env.CreateOutputFile(fname);
             using (var ch = env.Start("save"))
@@ -200,19 +199,21 @@ public void TestGreyscaleTransformImages()
             grey.Schema.TryGetColumnIndex("ImageGrey", out int greyColumn);
             using (var cursor = grey.GetRowCursorForAllColumns())
             {
-                var bitmapGetter = cursor.GetGetter<Bitmap>(grey.Schema["ImageGrey"]);
-                Bitmap bitmap = default;
+                var imageGetter = cursor.GetGetter<ImageBase>(grey.Schema["ImageGrey"]);
+                ImageBase image = default;
                 while (cursor.MoveNext())
                 {
-                    bitmapGetter(ref bitmap);
-                    Assert.NotNull(bitmap);
-                    for (int x = 0; x < imageWidth; x++)
-                        for (int y = 0; y < imageHeight; y++)
-                        {
-                            var pixel = bitmap.GetPixel(x, y);
-                            // greyscale image has same values for R,G and B
-                            Assert.True(pixel.R == pixel.G && pixel.G == pixel.B);
-                        }
+                    imageGetter(ref image);
+                    Assert.NotNull(image);
+
+                    ReadOnlySpan<byte> imageData = image.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
+                    int pixelSize = image.BitsPerPixel / 8;
+
+                    for (int i = 0; i < imageData.Length; i += pixelSize)
+                    {
+                        // grayscale image has same values for R,G and B
+                        Assert.True(imageData[i + redIndex] == imageData[i + greenIndex] && imageData[i + greenIndex] == imageData[i + blueIndex]);
+                    }
                 }
             }
             Done();
@@ -222,7 +223,7 @@ public void TestGreyscaleTransformImages()
         public void TestGrayScaleInMemory()
         {
             // Create an image list.
-            var images = new List<ImageDataPoint>() { new ImageDataPoint(10, 10, Color.Blue), new ImageDataPoint(10, 10, Color.Red) };
+            var images = new List<ImageDataPoint>() { new ImageDataPoint(10, 10, red: 0, green: 0, blue: 255), new ImageDataPoint(10, 10, red: 255, green: 0, blue: 0) };
 
             // Convert the list of data points to an IDataView object, which is consumable by ML.NET API.
             var data = ML.Data.LoadFromEnumerable(images);
@@ -233,7 +234,7 @@ public void TestGrayScaleInMemory()
             // Fit the model.
             var model = pipeline.Fit(data);
 
-            // Test path: image files -> IDataView -> Enumerable of Bitmaps.
+            // Test path: image files -> IDataView -> Enumerable of images.
             var transformedData = model.Transform(data);
 
             // Load images in DataView back to Enumerable.
@@ -249,42 +250,40 @@ public void TestGrayScaleInMemory()
                 Assert.Equal(image.Width, grayImage.Width);
                 Assert.Equal(image.Height, grayImage.Height);
 
-                for (int x = 0; x < grayImage.Width; ++x)
+                ReadOnlySpan<byte> imageData = grayImage.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
+                int pixelSize = grayImage.BitsPerPixel / 8;
+
+                for (int i = 0; i < imageData.Length; i += pixelSize)
                 {
-                    for (int y = 0; y < grayImage.Height; ++y)
-                    {
-                        var pixel = grayImage.GetPixel(x, y);
-                        // greyscale image has same values for R, G and B.
-                        Assert.True(pixel.R == pixel.G && pixel.G == pixel.B);
-                    }
+                    // grayscale image has same values for R,G and B
+                    Assert.True(imageData[i + redIndex] == imageData[i + greenIndex] && imageData[i + greenIndex] == imageData[i + blueIndex]);
                 }
             }
 
             var engine = ML.Model.CreatePredictionEngine<ImageDataPoint, ImageDataPoint>(model);
-            var singleImage = new ImageDataPoint(17, 36, Color.Pink);
+            var singleImage = new ImageDataPoint(17, 36, red: 255, green: 192, blue: 203); // Pink color (255, 192, 203)
             var transformedSingleImage = engine.Predict(singleImage);
 
             Assert.Equal(singleImage.Image.Height, transformedSingleImage.GrayImage.Height);
             Assert.Equal(singleImage.Image.Width, transformedSingleImage.GrayImage.Width);
 
-            for (int x = 0; x < transformedSingleImage.GrayImage.Width; ++x)
+            ReadOnlySpan<byte> imageData1 = transformedSingleImage.GrayImage.Get32bbpImageData(out int alphaIndex1, out int redIndex1, out int greenIndex1, out int blueIndex1);
+            int pixelSize1 = transformedSingleImage.GrayImage.BitsPerPixel / 8;
+
+            for (int i = 0; i < imageData1.Length; i += pixelSize1)
             {
-                for (int y = 0; y < transformedSingleImage.GrayImage.Height; ++y)
-                {
-                    var pixel = transformedSingleImage.GrayImage.GetPixel(x, y);
-                    // greyscale image has same values for R, G and B.
-                    Assert.True(pixel.R == pixel.G && pixel.G == pixel.B);
-                }
+                // grayscale image has same values for R,G and B
+                Assert.True(imageData1[i + redIndex1] == imageData1[i + greenIndex1] && imageData1[i + greenIndex1] == imageData1[i + blueIndex1]);
             }
         }
 
         private class ImageDataPoint
         {
             [ImageType(10, 10)]
-            public Bitmap Image { get; set; }
+            public ImageBase Image { get; set; }
 
             [ImageType(10, 10)]
-            public Bitmap GrayImage { get; set; }
+            public ImageBase GrayImage { get; set; }
 
             public ImageDataPoint()
             {
@@ -292,12 +291,19 @@ public ImageDataPoint()
                 GrayImage = null;
             }
 
-            public ImageDataPoint(int width, int height, Color color)
+            public ImageDataPoint(int width, int height, byte red, byte green, byte blue)
             {
-                Image = new Bitmap(width, height);
-                for (int i = 0; i < width; ++i)
-                    for (int j = 0; j < height; ++j)
-                        Image.SetPixel(i, j, color);
+                byte[] imageData = new byte[width * height * 4]; // 4 for the red, green, blue and alpha colors
+                for (int i = 0; i < imageData.Length; i += 4)
+                {
+                    // Fill the buffer with the Bgra32 format
+                    imageData[i] = blue;
+                    imageData[i + 1] = green;
+                    imageData[i + 2] = red;
+                    imageData[i + 3] = 255;
+                }
+
+                Image = ImageBase.CreateBgra32Image(width, height, imageData);
             }
         }
 
@@ -321,38 +327,43 @@ public void TestBackAndForthConversionWithAlphaInterleave()
             var cropped = new ImageResizingTransformer(env, "ImageCropped", imageWidth, imageHeight, "ImageReal").Transform(images);
 
             var pixels = new ImagePixelExtractingTransformer(env, "ImagePixels", "ImageCropped", ImagePixelExtractingEstimator.ColorBits.All, interleavePixelColors: true, scaleImage: 2f / 19, offsetImage: 30).Transform(cropped);
-            IDataView backToBitmaps = new VectorToImageConvertingTransformer(env, "ImageRestored", imageHeight, imageWidth, "ImagePixels",
+            IDataView backToImages = new VectorToImageConvertingTransformer(env, "ImageRestored", imageHeight, imageWidth, "ImagePixels",
                ImagePixelExtractingEstimator.ColorBits.All, interleavedColors: true, scaleImage: 19 / 2f, offsetImage: -30).Transform(pixels);
 
             var fname = nameof(TestBackAndForthConversionWithAlphaInterleave) + "_model.zip";
 
             var fh = env.CreateOutputFile(fname);
             using (var ch = env.Start("save"))
-                TrainUtils.SaveModel(env, ch, fh, null, new RoleMappedData(backToBitmaps));
+                TrainUtils.SaveModel(env, ch, fh, null, new RoleMappedData(backToImages));
 
-            backToBitmaps = ModelFileUtils.LoadPipeline(env, fh.OpenReadStream(), new MultiFileSource(dataFile));
+            backToImages = ModelFileUtils.LoadPipeline(env, fh.OpenReadStream(), new MultiFileSource(dataFile));
             DeleteOutputPath(fname);
 
-            using (var cursor = backToBitmaps.GetRowCursorForAllColumns())
+            using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var bitmapGetter = cursor.GetGetter<Bitmap>(backToBitmaps.Schema["ImageRestored"]);
-                Bitmap restoredBitmap = default;
+                var imageGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageRestored"]);
+                ImageBase restoredImage = default;
 
-                var bitmapCropGetter = cursor.GetGetter<Bitmap>(backToBitmaps.Schema["ImageCropped"]);
-                Bitmap croppedBitmap = default;
+                var imageCropGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageCropped"]);
+                ImageBase croppedImage = default;
                 while (cursor.MoveNext())
                 {
-                    bitmapGetter(ref restoredBitmap);
-                    Assert.NotNull(restoredBitmap);
-                    bitmapCropGetter(ref croppedBitmap);
-                    Assert.NotNull(croppedBitmap);
-                    for (int x = 0; x < imageWidth; x++)
-                        for (int y = 0; y < imageHeight; y++)
-                        {
-                            var c = croppedBitmap.GetPixel(x, y);
-                            var r = restoredBitmap.GetPixel(x, y);
-                            Assert.True(c == r);
-                        }
+                    imageGetter(ref restoredImage);
+                    Assert.NotNull(restoredImage);
+                    imageCropGetter(ref croppedImage);
+                    Assert.NotNull(croppedImage);
+
+                    ReadOnlySpan<byte> restoredImageData = restoredImage.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
+                    ReadOnlySpan<byte> croppedImageData = croppedImage.Get32bbpImageData(out int alphaIndex1, out int redIndex1, out int greenIndex1, out int blueIndex1);
+
+                    int pixelSize = restoredImage.BitsPerPixel / 8;
+
+                    for (int i = 0; i < restoredImageData.Length; i += pixelSize)
+                    {
+                        Assert.Equal(restoredImageData[i + redIndex], croppedImageData[i + redIndex1]);
+                        Assert.Equal(restoredImageData[i + greenIndex], croppedImageData[i + greenIndex1]);
+                        Assert.Equal(restoredImageData[i + blueIndex], croppedImageData[i + blueIndex1]);
+                    }
                 }
             }
             Done();
@@ -378,38 +389,44 @@ public void TestBackAndForthConversionWithoutAlphaInterleave()
             var cropped = new ImageResizingTransformer(env, "ImageCropped", imageWidth, imageHeight, "ImageReal").Transform(images);
             var pixels = new ImagePixelExtractingTransformer(env, "ImagePixels", "ImageCropped", interleavePixelColors: true, scaleImage: 2f / 19, offsetImage: 30).Transform(cropped);
 
-            IDataView backToBitmaps = new VectorToImageConvertingTransformer(env, "ImageRestored", imageHeight, imageWidth, "ImagePixels",
+            IDataView backToImages = new VectorToImageConvertingTransformer(env, "ImageRestored", imageHeight, imageWidth, "ImagePixels",
                interleavedColors: true, scaleImage: 19 / 2f, offsetImage: -30).Transform(pixels);
 
             var fname = nameof(TestBackAndForthConversionWithoutAlphaInterleave) + "_model.zip";
 
             var fh = env.CreateOutputFile(fname);
             using (var ch = env.Start("save"))
-                TrainUtils.SaveModel(env, ch, fh, null, new RoleMappedData(backToBitmaps));
+                TrainUtils.SaveModel(env, ch, fh, null, new RoleMappedData(backToImages));
 
-            backToBitmaps = ModelFileUtils.LoadPipeline(env, fh.OpenReadStream(), new MultiFileSource(dataFile));
+            backToImages = ModelFileUtils.LoadPipeline(env, fh.OpenReadStream(), new MultiFileSource(dataFile));
             DeleteOutputPath(fname);
 
-            using (var cursor = backToBitmaps.GetRowCursorForAllColumns())
+            using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var bitmapGetter = cursor.GetGetter<Bitmap>(backToBitmaps.Schema["ImageRestored"]);
-                Bitmap restoredBitmap = default;
+                var imageGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageRestored"]);
+                ImageBase restoredImage = default;
 
-                var bitmapCropGetter = cursor.GetGetter<Bitmap>(backToBitmaps.Schema["ImageCropped"]);
-                Bitmap croppedBitmap = default;
+                var imageCropGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageCropped"]);
+                ImageBase croppedImage = default;
                 while (cursor.MoveNext())
                 {
-                    bitmapGetter(ref restoredBitmap);
-                    Assert.NotNull(restoredBitmap);
-                    bitmapCropGetter(ref croppedBitmap);
-                    Assert.NotNull(croppedBitmap);
-                    for (int x = 0; x < imageWidth; x++)
-                        for (int y = 0; y < imageHeight; y++)
-                        {
-                            var c = croppedBitmap.GetPixel(x, y);
-                            var r = restoredBitmap.GetPixel(x, y);
-                            Assert.True(c.R == r.R && c.G == r.G && c.B == r.B);
-                        }
+                    imageGetter(ref restoredImage);
+                    Assert.NotNull(restoredImage);
+                    imageCropGetter(ref croppedImage);
+                    Assert.NotNull(croppedImage);
+
+                    ReadOnlySpan<byte> restoredImageData = restoredImage.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
+                    ReadOnlySpan<byte> croppedImageData = croppedImage.Get32bbpImageData(out int alphaIndex1, out int redIndex1, out int greenIndex1, out int blueIndex1);
+
+                    int pixelSize = restoredImage.BitsPerPixel / 8;
+
+                    for (int i = 0; i < restoredImageData.Length; i += pixelSize)
+                    {
+                        Assert.True(
+                            croppedImageData[i + redIndex1] == restoredImageData[i + redIndex] &&
+                            croppedImageData[i + greenIndex1] == restoredImageData[i + greenIndex] &&
+                            croppedImageData[i + blueIndex1] == restoredImageData[i + blueIndex]);
+                    }
                 }
             }
             Done();
@@ -435,40 +452,43 @@ public void TestBackAndForthConversionWithDifferentOrder()
             var cropped = new ImageResizingTransformer(env, "ImageCropped", imageWidth, imageHeight, "ImageReal").Transform(images);
 
             var pixels = new ImagePixelExtractingTransformer(env, "ImagePixels", "ImageCropped", ImagePixelExtractingEstimator.ColorBits.All, orderOfExtraction: ImagePixelExtractingEstimator.ColorsOrder.ABRG).Transform(cropped);
-            IDataView backToBitmaps = new VectorToImageConvertingTransformer(env, "ImageRestored", imageHeight, imageWidth, "ImagePixels",
+            IDataView backToImages = new VectorToImageConvertingTransformer(env, "ImageRestored", imageHeight, imageWidth, "ImagePixels",
                ImagePixelExtractingEstimator.ColorBits.All, orderOfColors: ImagePixelExtractingEstimator.ColorsOrder.ABRG).Transform(pixels);
 
             var fname = nameof(TestBackAndForthConversionWithDifferentOrder) + "_model.zip";
 
             var fh = env.CreateOutputFile(fname);
             using (var ch = env.Start("save"))
-                TrainUtils.SaveModel(env, ch, fh, null, new RoleMappedData(backToBitmaps));
+                TrainUtils.SaveModel(env, ch, fh, null, new RoleMappedData(backToImages));
 
-            backToBitmaps = ModelFileUtils.LoadPipeline(env, fh.OpenReadStream(), new MultiFileSource(dataFile));
+            backToImages = ModelFileUtils.LoadPipeline(env, fh.OpenReadStream(), new MultiFileSource(dataFile));
             DeleteOutputPath(fname);
 
-            using (var cursor = backToBitmaps.GetRowCursorForAllColumns())
+            using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var bitmapGetter = cursor.GetGetter<Bitmap>(backToBitmaps.Schema["ImageRestored"]);
-                Bitmap restoredBitmap = default;
+                var imageGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageRestored"]);
+                ImageBase restoredImage = default;
 
-                var bitmapCropGetter = cursor.GetGetter<Bitmap>(backToBitmaps.Schema["ImageCropped"]);
-                Bitmap croppedBitmap = default;
+                var imageCropGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageCropped"]);
+                ImageBase croppedImage = default;
                 while (cursor.MoveNext())
                 {
-                    bitmapGetter(ref restoredBitmap);
-                    Assert.NotNull(restoredBitmap);
-                    bitmapCropGetter(ref croppedBitmap);
-                    Assert.NotNull(croppedBitmap);
-                    for (int x = 0; x < imageWidth; x++)
-                        for (int y = 0; y < imageHeight; y++)
-                        {
-                            var c = croppedBitmap.GetPixel(x, y);
-                            var r = restoredBitmap.GetPixel(x, y);
-                            if (c != r)
-                                Assert.False(true);
-                            Assert.True(c == r);
-                        }
+                    imageGetter(ref restoredImage);
+                    Assert.NotNull(restoredImage);
+                    imageCropGetter(ref croppedImage);
+                    Assert.NotNull(croppedImage);
+
+                    ReadOnlySpan<byte> restoredImageData = restoredImage.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
+                    ReadOnlySpan<byte> croppedImageData = croppedImage.Get32bbpImageData(out int alphaIndex1, out int redIndex1, out int greenIndex1, out int blueIndex1);
+
+                    int pixelSize = restoredImage.BitsPerPixel / 8;
+
+                    for (int i = 0; i < restoredImageData.Length; i += pixelSize)
+                    {
+                        Assert.Equal(restoredImageData[i + redIndex], croppedImageData[i + redIndex1]);
+                        Assert.Equal(restoredImageData[i + greenIndex], croppedImageData[i + greenIndex1]);
+                        Assert.Equal(restoredImageData[i + blueIndex], croppedImageData[i + blueIndex1]);
+                    }
                 }
             }
             Done();
@@ -494,38 +514,43 @@ public void TestBackAndForthConversionWithAlphaNoInterleave()
             var cropped = new ImageResizingTransformer(env, "ImageCropped", imageWidth, imageHeight, "ImageReal").Transform(images);
             var pixels = new ImagePixelExtractingTransformer(env, "ImagePixels", "ImageCropped", ImagePixelExtractingEstimator.ColorBits.All, scaleImage: 2f / 19, offsetImage: 30).Transform(cropped);
 
-            IDataView backToBitmaps = new VectorToImageConvertingTransformer(env, "ImageRestored", imageHeight, imageWidth, "ImagePixels",
+            IDataView backToImages = new VectorToImageConvertingTransformer(env, "ImageRestored", imageHeight, imageWidth, "ImagePixels",
                 ImagePixelExtractingEstimator.ColorBits.All, scaleImage: 19 / 2f, offsetImage: -30).Transform(pixels);
 
             var fname = nameof(TestBackAndForthConversionWithAlphaNoInterleave) + "_model.zip";
 
             var fh = env.CreateOutputFile(fname);
             using (var ch = env.Start("save"))
-                TrainUtils.SaveModel(env, ch, fh, null, new RoleMappedData(backToBitmaps));
+                TrainUtils.SaveModel(env, ch, fh, null, new RoleMappedData(backToImages));
 
-            backToBitmaps = ModelFileUtils.LoadPipeline(env, fh.OpenReadStream(), new MultiFileSource(dataFile));
+            backToImages = ModelFileUtils.LoadPipeline(env, fh.OpenReadStream(), new MultiFileSource(dataFile));
             DeleteOutputPath(fname);
 
-            using (var cursor = backToBitmaps.GetRowCursorForAllColumns())
+            using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var bitmapGetter = cursor.GetGetter<Bitmap>(backToBitmaps.Schema["ImageRestored"]);
-                Bitmap restoredBitmap = default;
+                var imageGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageRestored"]);
+                ImageBase restoredImage = default;
 
-                var bitmapCropGetter = cursor.GetGetter<Bitmap>(backToBitmaps.Schema["ImageCropped"]);
-                Bitmap croppedBitmap = default;
+                var imageCropGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageCropped"]);
+                ImageBase croppedImage = default;
                 while (cursor.MoveNext())
                 {
-                    bitmapGetter(ref restoredBitmap);
-                    Assert.NotNull(restoredBitmap);
-                    bitmapCropGetter(ref croppedBitmap);
-                    Assert.NotNull(croppedBitmap);
-                    for (int x = 0; x < imageWidth; x++)
-                        for (int y = 0; y < imageHeight; y++)
-                        {
-                            var c = croppedBitmap.GetPixel(x, y);
-                            var r = restoredBitmap.GetPixel(x, y);
-                            Assert.True(c == r);
-                        }
+                    imageGetter(ref restoredImage);
+                    Assert.NotNull(restoredImage);
+                    imageCropGetter(ref croppedImage);
+                    Assert.NotNull(croppedImage);
+
+                    ReadOnlySpan<byte> restoredImageData = restoredImage.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
+                    ReadOnlySpan<byte> croppedImageData = croppedImage.Get32bbpImageData(out int alphaIndex1, out int redIndex1, out int greenIndex1, out int blueIndex1);
+
+                    int pixelSize = restoredImage.BitsPerPixel / 8;
+
+                    for (int i = 0; i < restoredImageData.Length; i += pixelSize)
+                    {
+                        Assert.Equal(restoredImageData[i + redIndex], croppedImageData[i + redIndex1]);
+                        Assert.Equal(restoredImageData[i + greenIndex], croppedImageData[i + greenIndex1]);
+                        Assert.Equal(restoredImageData[i + blueIndex], croppedImageData[i + blueIndex1]);
+                    }
                 }
             }
             Done();
@@ -551,38 +576,43 @@ public void TestBackAndForthConversionWithoutAlphaNoInterleave()
             var cropped = new ImageResizingTransformer(env, "ImageCropped", imageWidth, imageHeight, "ImageReal").Transform(images);
             var pixels = new ImagePixelExtractingTransformer(env, "ImagePixels", "ImageCropped", scaleImage: 2f / 19, offsetImage: 30).Transform(cropped);
 
-            IDataView backToBitmaps = new VectorToImageConvertingTransformer(env, "ImageRestored", imageHeight, imageWidth, "ImagePixels",
+            IDataView backToImages = new VectorToImageConvertingTransformer(env, "ImageRestored", imageHeight, imageWidth, "ImagePixels",
                 scaleImage: 19 / 2f, offsetImage: -30).Transform(pixels);
 
             var fname = nameof(TestBackAndForthConversionWithoutAlphaNoInterleave) + "_model.zip";
 
             var fh = env.CreateOutputFile(fname);
             using (var ch = env.Start("save"))
-                TrainUtils.SaveModel(env, ch, fh, null, new RoleMappedData(backToBitmaps));
+                TrainUtils.SaveModel(env, ch, fh, null, new RoleMappedData(backToImages));
 
-            backToBitmaps = ModelFileUtils.LoadPipeline(env, fh.OpenReadStream(), new MultiFileSource(dataFile));
+            backToImages = ModelFileUtils.LoadPipeline(env, fh.OpenReadStream(), new MultiFileSource(dataFile));
             DeleteOutputPath(fname);
 
-            using (var cursor = backToBitmaps.GetRowCursorForAllColumns())
+            using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var bitmapGetter = cursor.GetGetter<Bitmap>(backToBitmaps.Schema["ImageRestored"]);
-                Bitmap restoredBitmap = default;
+                var imageGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageRestored"]);
+                ImageBase restoredImage = default;
 
-                var bitmapCropGetter = cursor.GetGetter<Bitmap>(backToBitmaps.Schema["ImageCropped"]);
-                Bitmap croppedBitmap = default;
+                var imageCropGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageCropped"]);
+                ImageBase croppedImage = default;
                 while (cursor.MoveNext())
                 {
-                    bitmapGetter(ref restoredBitmap);
-                    Assert.NotNull(restoredBitmap);
-                    bitmapCropGetter(ref croppedBitmap);
-                    Assert.NotNull(croppedBitmap);
-                    for (int x = 0; x < imageWidth; x++)
-                        for (int y = 0; y < imageHeight; y++)
-                        {
-                            var c = croppedBitmap.GetPixel(x, y);
-                            var r = restoredBitmap.GetPixel(x, y);
-                            Assert.True(c.R == r.R && c.G == r.G && c.B == r.B);
-                        }
+                    imageGetter(ref restoredImage);
+                    Assert.NotNull(restoredImage);
+                    imageCropGetter(ref croppedImage);
+                    Assert.NotNull(croppedImage);
+
+                    ReadOnlySpan<byte> restoredImageData = restoredImage.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
+                    ReadOnlySpan<byte> croppedImageData = croppedImage.Get32bbpImageData(out int alphaIndex1, out int redIndex1, out int greenIndex1, out int blueIndex1);
+
+                    int pixelSize = restoredImage.BitsPerPixel / 8;
+
+                    for (int i = 0; i < restoredImageData.Length; i += pixelSize)
+                    {
+                        Assert.Equal(restoredImageData[i + redIndex], croppedImageData[i + redIndex1]);
+                        Assert.Equal(restoredImageData[i + greenIndex], croppedImageData[i + greenIndex1]);
+                        Assert.Equal(restoredImageData[i + blueIndex], croppedImageData[i + blueIndex1]);
+                    }
                 }
             }
             Done();
@@ -609,38 +639,43 @@ public void TestBackAndForthConversionWithAlphaInterleaveNoOffset()
 
             var pixels = new ImagePixelExtractingTransformer(env, "ImagePixels", "ImageCropped", ImagePixelExtractingEstimator.ColorBits.All, interleavePixelColors: true).Transform(cropped);
 
-            IDataView backToBitmaps = new VectorToImageConvertingTransformer(env, "ImageRestored", imageHeight, imageWidth, "ImagePixels",
+            IDataView backToImages = new VectorToImageConvertingTransformer(env, "ImageRestored", imageHeight, imageWidth, "ImagePixels",
                 ImagePixelExtractingEstimator.ColorBits.All, interleavedColors: true).Transform(pixels);
 
             var fname = nameof(TestBackAndForthConversionWithAlphaInterleaveNoOffset) + "_model.zip";
 
             var fh = env.CreateOutputFile(fname);
             using (var ch = env.Start("save"))
-                TrainUtils.SaveModel(env, ch, fh, null, new RoleMappedData(backToBitmaps));
+                TrainUtils.SaveModel(env, ch, fh, null, new RoleMappedData(backToImages));
 
-            backToBitmaps = ModelFileUtils.LoadPipeline(env, fh.OpenReadStream(), new MultiFileSource(dataFile));
+            backToImages = ModelFileUtils.LoadPipeline(env, fh.OpenReadStream(), new MultiFileSource(dataFile));
             DeleteOutputPath(fname);
 
-            using (var cursor = backToBitmaps.GetRowCursorForAllColumns())
+            using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var bitmapGetter = cursor.GetGetter<Bitmap>(backToBitmaps.Schema["ImageRestored"]);
-                Bitmap restoredBitmap = default;
+                var imageGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageRestored"]);
+                ImageBase restoredImage = default;
 
-                var bitmapCropGetter = cursor.GetGetter<Bitmap>(backToBitmaps.Schema["ImageCropped"]);
-                Bitmap croppedBitmap = default;
+                var imageCropGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageCropped"]);
+                ImageBase croppedImage = default;
                 while (cursor.MoveNext())
                 {
-                    bitmapGetter(ref restoredBitmap);
-                    Assert.NotNull(restoredBitmap);
-                    bitmapCropGetter(ref croppedBitmap);
-                    Assert.NotNull(croppedBitmap);
-                    for (int x = 0; x < imageWidth; x++)
-                        for (int y = 0; y < imageHeight; y++)
-                        {
-                            var c = croppedBitmap.GetPixel(x, y);
-                            var r = restoredBitmap.GetPixel(x, y);
-                            Assert.True(c == r);
-                        }
+                    imageGetter(ref restoredImage);
+                    Assert.NotNull(restoredImage);
+                    imageCropGetter(ref croppedImage);
+                    Assert.NotNull(croppedImage);
+
+                    ReadOnlySpan<byte> restoredImageData = restoredImage.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
+                    ReadOnlySpan<byte> croppedImageData = croppedImage.Get32bbpImageData(out int alphaIndex1, out int redIndex1, out int greenIndex1, out int blueIndex1);
+
+                    int pixelSize = restoredImage.BitsPerPixel / 8;
+
+                    for (int i = 0; i < restoredImageData.Length; i += pixelSize)
+                    {
+                        Assert.Equal(restoredImageData[i + redIndex], croppedImageData[i + redIndex1]);
+                        Assert.Equal(restoredImageData[i + greenIndex], croppedImageData[i + greenIndex1]);
+                        Assert.Equal(restoredImageData[i + blueIndex], croppedImageData[i + blueIndex1]);
+                    }
                 }
             }
             Done();
@@ -667,37 +702,42 @@ public void TestBackAndForthConversionWithoutAlphaInterleaveNoOffset()
 
             var pixels = new ImagePixelExtractingTransformer(env, "ImagePixels", "ImageCropped", interleavePixelColors: true).Transform(cropped);
 
-            IDataView backToBitmaps = new VectorToImageConvertingTransformer(env, "ImageRestored", imageHeight, imageWidth, "ImagePixels", interleavedColors: true).Transform(pixels);
+            IDataView backToImages = new VectorToImageConvertingTransformer(env, "ImageRestored", imageHeight, imageWidth, "ImagePixels", interleavedColors: true).Transform(pixels);
 
             var fname = nameof(TestBackAndForthConversionWithoutAlphaInterleaveNoOffset) + "_model.zip";
 
             var fh = env.CreateOutputFile(fname);
             using (var ch = env.Start("save"))
-                TrainUtils.SaveModel(env, ch, fh, null, new RoleMappedData(backToBitmaps));
+                TrainUtils.SaveModel(env, ch, fh, null, new RoleMappedData(backToImages));
 
-            backToBitmaps = ModelFileUtils.LoadPipeline(env, fh.OpenReadStream(), new MultiFileSource(dataFile));
+            backToImages = ModelFileUtils.LoadPipeline(env, fh.OpenReadStream(), new MultiFileSource(dataFile));
             DeleteOutputPath(fname);
 
-            using (var cursor = backToBitmaps.GetRowCursorForAllColumns())
+            using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var bitmapGetter = cursor.GetGetter<Bitmap>(backToBitmaps.Schema["ImageRestored"]);
-                Bitmap restoredBitmap = default;
+                var imageGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageRestored"]);
+                ImageBase restoredImage = default;
 
-                var bitmapCropGetter = cursor.GetGetter<Bitmap>(backToBitmaps.Schema["ImageCropped"]);
-                Bitmap croppedBitmap = default;
+                var imageCropGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageCropped"]);
+                ImageBase croppedImage = default;
                 while (cursor.MoveNext())
                 {
-                    bitmapGetter(ref restoredBitmap);
-                    Assert.NotNull(restoredBitmap);
-                    bitmapCropGetter(ref croppedBitmap);
-                    Assert.NotNull(croppedBitmap);
-                    for (int x = 0; x < imageWidth; x++)
-                        for (int y = 0; y < imageHeight; y++)
-                        {
-                            var c = croppedBitmap.GetPixel(x, y);
-                            var r = restoredBitmap.GetPixel(x, y);
-                            Assert.True(c.R == r.R && c.G == r.G && c.B == r.B);
-                        }
+                    imageGetter(ref restoredImage);
+                    Assert.NotNull(restoredImage);
+                    imageCropGetter(ref croppedImage);
+                    Assert.NotNull(croppedImage);
+
+                    ReadOnlySpan<byte> restoredImageData = restoredImage.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
+                    ReadOnlySpan<byte> croppedImageData = croppedImage.Get32bbpImageData(out int alphaIndex1, out int redIndex1, out int greenIndex1, out int blueIndex1);
+
+                    int pixelSize = restoredImage.BitsPerPixel / 8;
+
+                    for (int i = 0; i < restoredImageData.Length; i += pixelSize)
+                    {
+                        Assert.Equal(restoredImageData[i + redIndex], croppedImageData[i + redIndex1]);
+                        Assert.Equal(restoredImageData[i + greenIndex], croppedImageData[i + greenIndex1]);
+                        Assert.Equal(restoredImageData[i + blueIndex], croppedImageData[i + blueIndex1]);
+                    }
                 }
             }
             Done();
@@ -724,38 +764,43 @@ public void TestBackAndForthConversionWithAlphaNoInterleaveNoOffset()
 
             var pixels = new ImagePixelExtractingTransformer(env, "ImagePixels", "ImageCropped", ImagePixelExtractingEstimator.ColorBits.All).Transform(cropped);
 
-            IDataView backToBitmaps = new VectorToImageConvertingTransformer(env, "ImageRestored", imageHeight, imageWidth, "ImagePixels",
+            IDataView backToImages = new VectorToImageConvertingTransformer(env, "ImageRestored", imageHeight, imageWidth, "ImagePixels",
                  ImagePixelExtractingEstimator.ColorBits.All).Transform(pixels);
 
             var fname = nameof(TestBackAndForthConversionWithAlphaNoInterleaveNoOffset) + "_model.zip";
 
             var fh = env.CreateOutputFile(fname);
             using (var ch = env.Start("save"))
-                TrainUtils.SaveModel(env, ch, fh, null, new RoleMappedData(backToBitmaps));
+                TrainUtils.SaveModel(env, ch, fh, null, new RoleMappedData(backToImages));
 
-            backToBitmaps = ModelFileUtils.LoadPipeline(env, fh.OpenReadStream(), new MultiFileSource(dataFile));
+            backToImages = ModelFileUtils.LoadPipeline(env, fh.OpenReadStream(), new MultiFileSource(dataFile));
             DeleteOutputPath(fname);
 
-            using (var cursor = backToBitmaps.GetRowCursorForAllColumns())
+            using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var bitmapGetter = cursor.GetGetter<Bitmap>(backToBitmaps.Schema["ImageRestored"]);
-                Bitmap restoredBitmap = default;
+                var imageGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageRestored"]);
+                ImageBase restoredImage = default;
 
-                var bitmapCropGetter = cursor.GetGetter<Bitmap>(backToBitmaps.Schema["ImageCropped"]);
-                Bitmap croppedBitmap = default;
+                var imageCropGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageCropped"]);
+                ImageBase croppedImage = default;
                 while (cursor.MoveNext())
                 {
-                    bitmapGetter(ref restoredBitmap);
-                    Assert.NotNull(restoredBitmap);
-                    bitmapCropGetter(ref croppedBitmap);
-                    Assert.NotNull(croppedBitmap);
-                    for (int x = 0; x < imageWidth; x++)
-                        for (int y = 0; y < imageHeight; y++)
-                        {
-                            var c = croppedBitmap.GetPixel(x, y);
-                            var r = restoredBitmap.GetPixel(x, y);
-                            Assert.True(c == r);
-                        }
+                    imageGetter(ref restoredImage);
+                    Assert.NotNull(restoredImage);
+                    imageCropGetter(ref croppedImage);
+                    Assert.NotNull(croppedImage);
+
+                    ReadOnlySpan<byte> restoredImageData = restoredImage.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
+                    ReadOnlySpan<byte> croppedImageData = croppedImage.Get32bbpImageData(out int alphaIndex1, out int redIndex1, out int greenIndex1, out int blueIndex1);
+
+                    int pixelSize = restoredImage.BitsPerPixel / 8;
+
+                    for (int i = 0; i < restoredImageData.Length; i += pixelSize)
+                    {
+                        Assert.Equal(restoredImageData[i + redIndex], croppedImageData[i + redIndex1]);
+                        Assert.Equal(restoredImageData[i + greenIndex], croppedImageData[i + greenIndex1]);
+                        Assert.Equal(restoredImageData[i + blueIndex], croppedImageData[i + blueIndex1]);
+                    }
                 }
             }
             Done();
@@ -781,37 +826,42 @@ public void TestBackAndForthConversionWithoutAlphaNoInterleaveNoOffset()
             var cropped = new ImageResizingTransformer(env, "ImageCropped", imageWidth, imageHeight, "ImageReal").Transform(images);
             var pixels = new ImagePixelExtractingTransformer(env, "ImagePixels", "ImageCropped").Transform(cropped);
 
-            IDataView backToBitmaps = new VectorToImageConvertingTransformer(env, "ImageRestored", imageHeight, imageWidth, "ImagePixels").Transform(pixels);
+            IDataView backToImages = new VectorToImageConvertingTransformer(env, "ImageRestored", imageHeight, imageWidth, "ImagePixels").Transform(pixels);
 
             var fname = nameof(TestBackAndForthConversionWithoutAlphaNoInterleaveNoOffset) + "_model.zip";
 
             var fh = env.CreateOutputFile(fname);
             using (var ch = env.Start("save"))
-                TrainUtils.SaveModel(env, ch, fh, null, new RoleMappedData(backToBitmaps));
+                TrainUtils.SaveModel(env, ch, fh, null, new RoleMappedData(backToImages));
 
-            backToBitmaps = ModelFileUtils.LoadPipeline(env, fh.OpenReadStream(), new MultiFileSource(dataFile));
+            backToImages = ModelFileUtils.LoadPipeline(env, fh.OpenReadStream(), new MultiFileSource(dataFile));
             DeleteOutputPath(fname);
 
-            using (var cursor = backToBitmaps.GetRowCursorForAllColumns())
+            using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var bitmapGetter = cursor.GetGetter<Bitmap>(backToBitmaps.Schema["ImageRestored"]);
-                Bitmap restoredBitmap = default;
+                var imageGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageRestored"]);
+                ImageBase restoredImage = default;
 
-                var bitmapCropGetter = cursor.GetGetter<Bitmap>(backToBitmaps.Schema["ImageCropped"]);
-                Bitmap croppedBitmap = default;
+                var imageCropGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageCropped"]);
+                ImageBase croppedImage = default;
                 while (cursor.MoveNext())
                 {
-                    bitmapGetter(ref restoredBitmap);
-                    Assert.NotNull(restoredBitmap);
-                    bitmapCropGetter(ref croppedBitmap);
-                    Assert.NotNull(croppedBitmap);
-                    for (int x = 0; x < imageWidth; x++)
-                        for (int y = 0; y < imageHeight; y++)
-                        {
-                            var c = croppedBitmap.GetPixel(x, y);
-                            var r = restoredBitmap.GetPixel(x, y);
-                            Assert.True(c.R == r.R && c.G == r.G && c.B == r.B);
-                        }
+                    imageGetter(ref restoredImage);
+                    Assert.NotNull(restoredImage);
+                    imageCropGetter(ref croppedImage);
+                    Assert.NotNull(croppedImage);
+
+                    ReadOnlySpan<byte> restoredImageData = restoredImage.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
+                    ReadOnlySpan<byte> croppedImageData = croppedImage.Get32bbpImageData(out int alphaIndex1, out int redIndex1, out int greenIndex1, out int blueIndex1);
+
+                    int pixelSize = restoredImage.BitsPerPixel / 8;
+
+                    for (int i = 0; i < restoredImageData.Length; i += pixelSize)
+                    {
+                        Assert.Equal(restoredImageData[i + redIndex], croppedImageData[i + redIndex1]);
+                        Assert.Equal(restoredImageData[i + greenIndex], croppedImageData[i + greenIndex1]);
+                        Assert.Equal(restoredImageData[i + blueIndex], croppedImageData[i + blueIndex1]);
+                    }
                 }
                 Done();
             }
@@ -839,34 +889,48 @@ public void ImageResizerTransformResizingModeFill()
             var rowView = pipe.Preview(data).RowView;
             Assert.Single(rowView);
 
-            using (var bitmap = (Bitmap)rowView.First().Values.Last().Value)
+            using (var image = (ImageBase)rowView.First().Values.Last().Value)
             {
+                ReadOnlySpan<byte> imageData = image.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
+                int pixelSize = image.BitsPerPixel / 8;
+
                 // these points must be white
-                var topLeft = bitmap.GetPixel(0, 0);
-                var topRight = bitmap.GetPixel(bitmap.Width - 1, 0);
-                var bottomLeft = bitmap.GetPixel(0, bitmap.Height - 1);
-                var bottomRight = bitmap.GetPixel(bitmap.Width - 1, bitmap.Height - 1);
-                var middle = bitmap.GetPixel(bitmap.Width / 2, bitmap.Height / 2);
+                (int red, int green, int blue) topLeft = (imageData[redIndex], imageData[greenIndex], imageData[blueIndex]);
+                int index = pixelSize * (image.Width - 1);
+                (int red, int green, int blue) topRight = (imageData[index + redIndex], imageData[index + greenIndex], imageData[index + blueIndex]);
+                index = pixelSize * (image.Width) * (image.Height - 1);
+                (int red, int green, int blue) bottomLeft = (imageData[index + redIndex], imageData[index + greenIndex], imageData[index + blueIndex]);
+                index = pixelSize * image.Width * image.Height - pixelSize;
+                (int red, int green, int blue) bottomRight = (imageData[index + redIndex], imageData[index + greenIndex], imageData[index + blueIndex]);
+                index = pixelSize * image.Width * ((image.Height / 2) - 1) + pixelSize * ((image.Width / 2) - 1);
+                (int red, int green, int blue) middle = (imageData[index + redIndex], imageData[index + greenIndex], imageData[index + blueIndex]);
 
                 // these points must be red
-                var midTop = bitmap.GetPixel(bitmap.Width / 2, bitmap.Height / 3);
-                var midBottom = bitmap.GetPixel(bitmap.Width / 2, bitmap.Height / 3 * 2);
-                var leftMid = bitmap.GetPixel(bitmap.Width / 3, bitmap.Height / 2);
-                var rightMid = bitmap.GetPixel(bitmap.Width / 3 * 2, bitmap.Height / 2);
+                index = pixelSize * image.Width * ((image.Height / 3) - 1) + pixelSize * ((image.Width / 2) - 1);
+                (int red, int green, int blue) midTop = (imageData[index + redIndex], imageData[index + greenIndex], imageData[index + blueIndex]);
+
+                index = pixelSize * image.Width * ((image.Height / 3 * 2) - 1) + pixelSize * ((image.Width / 2) - 1);
+                (int red, int green, int blue) midBottom = (imageData[index + redIndex], imageData[index + greenIndex], imageData[index + blueIndex]);
+
+                index = pixelSize * image.Width * ((image.Height / 2) - 1) + pixelSize * ((image.Width / 3) - 1);
+                (int red, int green, int blue) leftMid = (imageData[index + redIndex], imageData[index + greenIndex], imageData[index + blueIndex]);
+
+                index = pixelSize * image.Width * ((image.Height / 2) - 1) + pixelSize * ((image.Width / 3 * 2) - 1);
+                (int red, int green, int blue) rightMid = (imageData[index + redIndex], imageData[index + greenIndex], imageData[index + blueIndex]);
 
                 // it turns out rounding errors on certain platforms may lead to a test failure
                 // instead of checking for exactly FFFFFF and FF0000 we allow a small interval here to be safe
                 Assert.All(new[] { topLeft, topRight, bottomLeft, bottomRight, middle }, c =>
                 {
-                    Assert.True(c.R >= 250);
-                    Assert.True(c.G >= 250);
-                    Assert.True(c.B >= 250);
+                    Assert.True(c.red >= 250);
+                    Assert.True(c.green >= 250);
+                    Assert.True(c.blue >= 250);
                 });
                 Assert.All(new[] { midTop, midBottom, leftMid, rightMid }, c =>
                 {
-                    Assert.True(c.R >= 250);
-                    Assert.True(c.G < 6);
-                    Assert.True(c.B < 6);
+                    Assert.True(c.red >= 250);
+                    Assert.True(c.green < 6);
+                    Assert.True(c.blue < 6);
                 });
             }
 
@@ -914,7 +978,7 @@ private class DataPoint
         public class InMemoryImage
         {
             [ImageType(229, 299)]
-            public Bitmap LoadedImage;
+            public ImageBase LoadedImage;
             public string Label;
 
             public static List<InMemoryImage> LoadFromTsv(MLContext mlContext, string tsvPath, string imageFolder)
@@ -946,7 +1010,7 @@ public static List<InMemoryImage> LoadFromTsv(MLContext mlContext, string tsvPat
                                 new InMemoryImage()
                                 {
                                     Label = label,
-                                    LoadedImage = (Bitmap)Image.FromFile(imagePath)
+                                    LoadedImage = LoadImageFromFile(imagePath)
                                 }
                             );
                     }
@@ -955,12 +1019,18 @@ public static List<InMemoryImage> LoadFromTsv(MLContext mlContext, string tsvPat
                 return inMemoryImages;
 
             }
+
+            private static ImageBase LoadImageFromFile(string imagePath)
+            {
+                using Stream stream = new FileStream(imagePath, FileMode.Open);
+                return ImageBase.CreateFromStream(stream);
+            }
         }
 
         public class InMemoryImageOutput : InMemoryImage
         {
             [ImageType(100, 100)]
-            public Bitmap ResizedImage;
+            public ImageBase ResizedImage;
         }
 
         [Fact]
@@ -978,7 +1048,7 @@ public void ResizeInMemoryImages()
             var model = pipeline.Fit(dataView);
             var resizedDV = model.Transform(dataView);
             var rowView = resizedDV.Preview().RowView;
-            var resizedImage = (Bitmap)rowView.First().Values.Last().Value;
+            var resizedImage = (ImageBase)rowView.First().Values.Last().Value;
             Assert.Equal(100, resizedImage.Height);
             Assert.NotEqual(100, dataObjects[0].LoadedImage.Height);
 
diff --git a/test/Microsoft.ML.Tests/ScenariosWithDirectInstantiation/TensorflowTests.cs b/test/Microsoft.ML.Tests/ScenariosWithDirectInstantiation/TensorflowTests.cs
index 06cfe8ceba..65dffb65c1 100644
--- a/test/Microsoft.ML.Tests/ScenariosWithDirectInstantiation/TensorflowTests.cs
+++ b/test/Microsoft.ML.Tests/ScenariosWithDirectInstantiation/TensorflowTests.cs
@@ -1043,34 +1043,6 @@ public void TensorFlowTransformCifar()
 
                 Assert.Equal(7, numRows);
             }
-
-            Assert.Contains(
-                @"[Source=Mapper; ImageResizingTransformer, Kind=Warning] Encountered image " +
-                GetDataPath("images/tomato_indexedpixelformat.gif") +
-                " of unsupported pixel format Format8bppIndexed but converting it to Format32bppArgb.",
-                logMessages);
-
-            // taco_invalidpixelformat.jpg has '8207' pixel format on Windows but this format translates to Format32bppRgb
-            // on macOS and Linux, hence on Windows this image's pixel format is converted in resize transformer to Format32bppArgb
-            // and on linux and macOS it is not converted in resize transform since pixel format 'Format32bppRgb' can be resized but
-            // in ImagePixelExtractingTransformer it is converted to Format32bppArgb since there we just support two 
-            // pixel formats, i.e Format32bppArgb and Format16bppArgb.
-            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
-            {
-                Assert.Contains(
-                    @"[Source=Mapper; ImagePixelExtractingTransformer, Kind=Warning] Encountered image " +
-                    GetDataPath("images/taco_invalidpixelformat.jpg") +
-                    " of unsupported pixel format Format32bppRgb but converting it to Format32bppArgb.",
-                    logMessages);
-            }
-            else
-            {
-                Assert.Contains(
-                    @"[Source=Mapper; ImageResizingTransformer, Kind=Warning] Encountered image " +
-                    GetDataPath("images/taco_invalidpixelformat.jpg") +
-                    " of unsupported pixel format 8207 but converting it to Format32bppArgb.",
-                    logMessages);
-            }
         }
 
         [TensorFlowFact]

From d4765348305745bd2138bee9f7d8a126d37980b1 Mon Sep 17 00:00:00 2001
From: Tarek Mahmoud Sayed <tarekms@microsoft.com>
Date: Mon, 10 Oct 2022 10:10:30 -0700
Subject: [PATCH 2/7] Fix test failure with readonly file system

---
 test/Microsoft.ML.Tests/ImagesTests.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/Microsoft.ML.Tests/ImagesTests.cs b/test/Microsoft.ML.Tests/ImagesTests.cs
index 1b5b358e8b..54cd9a9943 100644
--- a/test/Microsoft.ML.Tests/ImagesTests.cs
+++ b/test/Microsoft.ML.Tests/ImagesTests.cs
@@ -1022,7 +1022,7 @@ public static List<InMemoryImage> LoadFromTsv(MLContext mlContext, string tsvPat
 
             private static ImageBase LoadImageFromFile(string imagePath)
             {
-                using Stream stream = new FileStream(imagePath, FileMode.Open);
+                using Stream stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read, FileShare.Read);
                 return ImageBase.CreateFromStream(stream);
             }
         }

From 6ff7600c6aac3d1c5a7ee4564fb3a6ddef51bbee Mon Sep 17 00:00:00 2001
From: Tarek Mahmoud Sayed <tarekms@microsoft.com>
Date: Wed, 12 Oct 2022 11:27:19 -0700
Subject: [PATCH 3/7] Update the public interfaces

---
 .../ApplyONNXModelWithInMemoryImages.cs       |   4 +-
 .../ImageAnalytics/ConvertToGrayScale.cs      |   8 +-
 .../ConvertToGrayScaleInMemory.cs             |   6 +-
 .../ImageAnalytics/ConvertToImage.cs          |   4 +-
 .../ImageAnalytics/ExtractPixels.cs           |   8 +-
 .../Transforms/ImageAnalytics/LoadImages.cs   |   4 +-
 .../Transforms/ImageAnalytics/ResizeImages.cs |   8 +-
 .../ExtensionsCatalog.cs                      |  12 +-
 .../ImageGrayscale.cs                         |  12 +-
 .../ImageLoader.cs                            |  12 +-
 .../ImagePixelExtractor.cs                    |   6 +-
 .../ImageResizer.cs                           |  14 +-
 src/Microsoft.ML.ImageAnalytics/ImageType.cs  |   6 +-
 .../{ImageBase.cs => Imager.cs}               | 314 +++++++-----------
 .../Microsoft.ML.ImageAnalytics.csproj        |   3 +-
 .../VectorToImageTransform.cs                 |   8 +-
 .../OnnxTransformTests.cs                     |   4 +-
 test/Microsoft.ML.Tests/ImagesTests.cs        |  98 +++---
 18 files changed, 235 insertions(+), 296 deletions(-)
 rename src/Microsoft.ML.ImageAnalytics/{ImageBase.cs => Imager.cs} (70%)

diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ApplyONNXModelWithInMemoryImages.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ApplyONNXModelWithInMemoryImages.cs
index ae21348c03..340dd2aeff 100644
--- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ApplyONNXModelWithInMemoryImages.cs
+++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ApplyONNXModelWithInMemoryImages.cs
@@ -90,7 +90,7 @@ private class ImageDataPoint
 
             // Image will be consumed by ONNX image multiclass classification model.
             [ImageType(height, width)]
-            public ImageBase Image { get; set; }
+            public Imager Image { get; set; }
 
             // Expected output of ONNX model. It contains probabilities of all
             // classes. Note that the ColumnName below should match the output name
@@ -115,7 +115,7 @@ public ImageDataPoint(byte red, byte green, byte blue)
                     imageData[i + 3] = 255;
                 }
 
-                Image = ImageBase.CreateBgra32Image(width, height, imageData);
+                Image = Imager.CreateFromBgra32PixelData(width, height, imageData);
             }
         }
     }
diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScale.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScale.cs
index a108aa97e7..28d8a96b72 100644
--- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScale.cs
+++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScale.cs
@@ -70,8 +70,8 @@ private static void PrintColumns(IDataView transformedData)
                 // column -type validation once, rather than many times.
                 ReadOnlyMemory<char> imagePath = default;
                 ReadOnlyMemory<char> name = default;
-                ImageBase imageObject = null;
-                ImageBase grayscaleImageObject = null;
+                Imager imageObject = null;
+                Imager grayscaleImageObject = null;
 
                 var imagePathGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
                     .Schema["ImagePath"]);
@@ -79,10 +79,10 @@ private static void PrintColumns(IDataView transformedData)
                 var nameGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
                     .Schema["Name"]);
 
-                var imageObjectGetter = cursor.GetGetter<ImageBase>(cursor.Schema[
+                var imageObjectGetter = cursor.GetGetter<Imager>(cursor.Schema[
                     "ImageObject"]);
 
-                var grayscaleGetter = cursor.GetGetter<ImageBase>(cursor.Schema[
+                var grayscaleGetter = cursor.GetGetter<Imager>(cursor.Schema[
                     "Grayscale"]);
 
                 while (cursor.MoveNext())
diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScaleInMemory.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScaleInMemory.cs
index 7596ba46da..6885d1b443 100644
--- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScaleInMemory.cs
+++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScaleInMemory.cs
@@ -69,10 +69,10 @@ public static void Example()
         private class ImageDataPoint
         {
             [ImageType(3, 4)]
-            public ImageBase Image { get; set; }
+            public Imager Image { get; set; }
 
             [ImageType(3, 4)]
-            public ImageBase GrayImage { get; set; }
+            public Imager GrayImage { get; set; }
 
             public ImageDataPoint()
             {
@@ -92,7 +92,7 @@ public ImageDataPoint(int width, int height, byte red, byte green, byte blue)
                     imageData[i + 3] = 255;
                 }
 
-                Image = ImageBase.CreateBgra32Image(width, height, imageData);
+                Image = Imager.CreateFromBgra32PixelData(width, height, imageData);
             }
         }
     }
diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToImage.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToImage.cs
index a64f5baf74..746bc35a26 100644
--- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToImage.cs
+++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToImage.cs
@@ -58,7 +58,7 @@ private static void PrintColumns(IDataView transformedData)
                 // column -type validation once, rather than many times.
                 VBuffer<float> features = default;
                 VBuffer<float> pixels = default;
-                ImageBase imageObject = null;
+                Imager imageObject = null;
 
                 var featuresGetter = cursor.GetGetter<VBuffer<float>>(cursor.Schema[
                     "Features"]);
@@ -66,7 +66,7 @@ private static void PrintColumns(IDataView transformedData)
                 var pixelsGetter = cursor.GetGetter<VBuffer<float>>(cursor.Schema[
                     "Pixels"]);
 
-                var imageGetter = cursor.GetGetter<ImageBase>(cursor.Schema["Image"]);
+                var imageGetter = cursor.GetGetter<Imager>(cursor.Schema["Image"]);
                 while (cursor.MoveNext())
                 {
 
diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ExtractPixels.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ExtractPixels.cs
index d020262dca..2fba66e785 100644
--- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ExtractPixels.cs
+++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ExtractPixels.cs
@@ -78,8 +78,8 @@ private static void PrintColumns(IDataView transformedData)
 
                 ReadOnlyMemory<char> imagePath = default;
                 ReadOnlyMemory<char> name = default;
-                ImageBase imageObject = null;
-                ImageBase resizedImageObject = null;
+                Imager imageObject = null;
+                Imager resizedImageObject = null;
                 VBuffer<float> pixels = default;
 
                 var imagePathGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
@@ -88,10 +88,10 @@ private static void PrintColumns(IDataView transformedData)
                 var nameGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
                     .Schema["Name"]);
 
-                var imageObjectGetter = cursor.GetGetter<ImageBase>(cursor.Schema[
+                var imageObjectGetter = cursor.GetGetter<Imager>(cursor.Schema[
                     "ImageObject"]);
 
-                var resizedImageGetter = cursor.GetGetter<ImageBase>(cursor.Schema[
+                var resizedImageGetter = cursor.GetGetter<Imager>(cursor.Schema[
                     "ImageObjectResized"]);
 
                 var pixelsGetter = cursor.GetGetter<VBuffer<float>>(cursor.Schema[
diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/LoadImages.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/LoadImages.cs
index 80cabbee56..5228a445db 100644
--- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/LoadImages.cs
+++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/LoadImages.cs
@@ -69,7 +69,7 @@ private static void PrintColumns(IDataView transformedData)
                 // and column-type validation once, rather than many times.
                 ReadOnlyMemory<char> imagePath = default;
                 ReadOnlyMemory<char> name = default;
-                ImageBase imageObject = null;
+                Imager imageObject = null;
 
                 var imagePathGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
                     .Schema["ImagePath"]);
@@ -77,7 +77,7 @@ private static void PrintColumns(IDataView transformedData)
                 var nameGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
                     .Schema["Name"]);
 
-                var imageObjectGetter = cursor.GetGetter<ImageBase>(cursor.Schema[
+                var imageObjectGetter = cursor.GetGetter<Imager>(cursor.Schema[
                     "ImageObject"]);
 
                 while (cursor.MoveNext())
diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ResizeImages.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ResizeImages.cs
index 6f05ba107b..30347999da 100644
--- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ResizeImages.cs
+++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ResizeImages.cs
@@ -72,8 +72,8 @@ private static void PrintColumns(IDataView transformedData)
                 // column -type validation once, rather than many times.
                 ReadOnlyMemory<char> imagePath = default;
                 ReadOnlyMemory<char> name = default;
-                ImageBase imageObject = null;
-                ImageBase resizedImageObject = null;
+                Imager imageObject = null;
+                Imager resizedImageObject = null;
 
                 var imagePathGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
                     .Schema["ImagePath"]);
@@ -81,10 +81,10 @@ private static void PrintColumns(IDataView transformedData)
                 var nameGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
                     .Schema["Name"]);
 
-                var imageObjectGetter = cursor.GetGetter<ImageBase>(cursor.Schema[
+                var imageObjectGetter = cursor.GetGetter<Imager>(cursor.Schema[
                     "ImageObject"]);
 
-                var resizedImageGetter = cursor.GetGetter<ImageBase>(cursor.Schema[
+                var resizedImageGetter = cursor.GetGetter<Imager>(cursor.Schema[
                     "ImageObjectResized"]);
 
                 while (cursor.MoveNext())
diff --git a/src/Microsoft.ML.ImageAnalytics/ExtensionsCatalog.cs b/src/Microsoft.ML.ImageAnalytics/ExtensionsCatalog.cs
index 5d0d188795..494f4436f3 100644
--- a/src/Microsoft.ML.ImageAnalytics/ExtensionsCatalog.cs
+++ b/src/Microsoft.ML.ImageAnalytics/ExtensionsCatalog.cs
@@ -21,7 +21,7 @@ public static class ImageEstimatorsCatalog
         /// <param name="outputColumnName">Name of the column resulting from the transformation of <paramref name="inputColumnName"/>.
         /// This column's data type will be the same as that of the input column.</param>
         /// <param name="inputColumnName">Name of the column to convert images to grayscale from.
-        /// This estimator operates only on <see cref="Microsoft.ML.Data.ImageBase"/>.</param>
+        /// This estimator operates only on <see cref="Microsoft.ML.Data.Imager"/>.</param>
         /// <example>
         /// <format type="text/markdown">
         /// <![CDATA[
@@ -37,7 +37,7 @@ public static ImageGrayscalingEstimator ConvertToGrayscale(this TransformsCatalo
         /// to grayscale images in a new column: <see cref="InputOutputColumnPair.OutputColumnName" />.
         ///</summary>
         /// <param name="catalog">The transform's catalog.</param>
-        /// <param name="columns">The pairs of input and output columns. This estimator operates only on <see cref="Microsoft.ML.Data.ImageBase"/>.</param>
+        /// <param name="columns">The pairs of input and output columns. This estimator operates only on <see cref="Microsoft.ML.Data.Imager"/>.</param>
         /// <example>
         /// <format type="text/markdown">
         /// <![CDATA[
@@ -58,7 +58,7 @@ internal static ImageGrayscalingEstimator ConvertToGrayscale(this TransformsCata
         /// </summary>
         /// <param name="catalog">The transform's catalog.</param>
         /// <param name="outputColumnName">Name of the column resulting from the transformation of <paramref name="inputColumnName"/>.
-        /// This column's data type will be <see cref="Microsoft.ML.Data.ImageBase"/>.</param>
+        /// This column's data type will be <see cref="Microsoft.ML.Data.Imager"/>.</param>
         /// <param name="inputColumnName">Name of the column with paths to the images to load.
         /// This estimator operates over text data.</param>
         /// <param name="imageFolder">Folder where to look for images.</param>
@@ -98,7 +98,7 @@ public static ImageLoadingEstimator LoadRawImageBytes(this TransformsCatalog cat
         /// <param name="outputColumnName">Name of the column resulting from the transformation of <paramref name="inputColumnName"/>.
         /// This column's data type will be a known-sized vector of <see cref="System.Single"/> or <see cref="System.Byte"/> depending on <paramref name="outputAsFloatArray"/>.</param>
         /// <param name="inputColumnName">Name of the column with images.
-        /// This estimator operates over <see cref="Microsoft.ML.Data.ImageBase"/>.</param>
+        /// This estimator operates over <see cref="Microsoft.ML.Data.Imager"/>.</param>
         /// <param name="colorsToExtract">The colors to extract from the image.</param>
         /// <param name="orderOfExtraction">The order in which to extract colors from pixel.</param>
         /// <param name="interleavePixelColors">Whether to interleave the pixels colors, meaning keep them in the <paramref name="orderOfExtraction"/> order, or leave them in the planner form:
@@ -142,7 +142,7 @@ internal static ImagePixelExtractingEstimator ExtractPixels(this TransformsCatal
         /// <param name="outputColumnName">Name of the column resulting from the transformation of <paramref name="inputColumnName"/>.
         /// This column's data type will be the same as that of the input column.</param>
         /// <param name="inputColumnName">Name of the column with images.
-        /// This estimator operates over <see cref="Microsoft.ML.Data.ImageBase"/>.</param>
+        /// This estimator operates over <see cref="Microsoft.ML.Data.Imager"/>.</param>
         /// <param name="imageWidth">The transformed image width.</param>
         /// <param name="imageHeight">The transformed image height.</param>
         /// <param name="resizing"> The type of image resizing as specified in <see cref="ImageResizingEstimator.ResizingKind"/>.</param>
@@ -202,7 +202,7 @@ internal static VectorToImageConvertingEstimator ConvertToImage(this TransformsC
         /// <param name="imageHeight">The height of the output images.</param>
         /// <param name="imageWidth">The width of the output images.</param>
         /// <param name="outputColumnName">Name of the column resulting from the transformation of <paramref name="inputColumnName"/>.
-        /// This column's data type will be <see cref="Microsoft.ML.Data.ImageBase"/>.</param>
+        /// This column's data type will be <see cref="Microsoft.ML.Data.Imager"/>.</param>
         /// <param name="inputColumnName">Name of the column with data to be converted to image.
         /// This estimator operates over known-sized vector of <see cref="System.Single"/>, <see cref="System.Double"/> and <see cref="System.Byte"/>.</param>
         /// <param name="colorsPresent">Specifies which <see cref="ImagePixelExtractingEstimator.ColorBits"/> are in present the input pixel vectors. The order of colors is specified in <paramref name="orderOfColors"/>.</param>
diff --git a/src/Microsoft.ML.ImageAnalytics/ImageGrayscale.cs b/src/Microsoft.ML.ImageAnalytics/ImageGrayscale.cs
index d1c5acc718..267b529f07 100644
--- a/src/Microsoft.ML.ImageAnalytics/ImageGrayscale.cs
+++ b/src/Microsoft.ML.ImageAnalytics/ImageGrayscale.cs
@@ -164,8 +164,8 @@ protected override Delegate MakeGetter(DataViewRow input, int iinfo, Func<int, b
                 Contracts.AssertValue(input);
                 Contracts.Assert(0 <= iinfo && iinfo < _parent.ColumnPairs.Length);
 
-                var src = default(ImageBase);
-                var getSrc = input.GetGetter<ImageBase>(input.Schema[ColMapNewToOld[iinfo]]);
+                var src = default(Imager);
+                var getSrc = input.GetGetter<Imager>(input.Schema[ColMapNewToOld[iinfo]]);
 
                 disposer =
                     () =>
@@ -177,8 +177,8 @@ protected override Delegate MakeGetter(DataViewRow input, int iinfo, Func<int, b
                         }
                     };
 
-                ValueGetter<ImageBase> del =
-                    (ref ImageBase dst) =>
+                ValueGetter<Imager> del =
+                    (ref Imager dst) =>
                     {
                         if (dst != null)
                             dst.Dispose();
@@ -207,8 +207,8 @@ protected override Delegate MakeGetter(DataViewRow input, int iinfo, Func<int, b
     /// |  |  |
     /// | -- | -- |
     /// | Does this estimator need to look at the data to train its parameters? | No |
-    /// | Input column data type | <xref:Microsoft.ML.Data.ImageBase> |
-    /// | Output column data type | <xref:Microsoft.ML.Data.ImageBase> |
+    /// | Input column data type | <xref:Microsoft.ML.Data.Imager> |
+    /// | Output column data type | <xref:Microsoft.ML.Data.Imager> |
     /// | Required NuGet in addition to Microsoft.ML | Microsoft.ML.ImageAnalytics |
     /// | Exportable to ONNX | No |
     ///
diff --git a/src/Microsoft.ML.ImageAnalytics/ImageLoader.cs b/src/Microsoft.ML.ImageAnalytics/ImageLoader.cs
index 8161ccbdad..e28be22ac1 100644
--- a/src/Microsoft.ML.ImageAnalytics/ImageLoader.cs
+++ b/src/Microsoft.ML.ImageAnalytics/ImageLoader.cs
@@ -217,7 +217,7 @@ private Delegate MakeGetterImageDataViewType(DataViewRow input, int iinfo, Func<
             {
                 Contracts.AssertValue(input);
                 Contracts.Assert(0 <= iinfo && iinfo < _parent.ColumnPairs.Length);
-                var lastImage = default(ImageBase);
+                var lastImage = default(Imager);
 
                 disposer = () =>
                 {
@@ -230,8 +230,8 @@ private Delegate MakeGetterImageDataViewType(DataViewRow input, int iinfo, Func<
 
                 var getSrc = input.GetGetter<ReadOnlyMemory<char>>(input.Schema[ColMapNewToOld[iinfo]]);
                 ReadOnlyMemory<char> src = default;
-                ValueGetter<ImageBase> del =
-                    (ref ImageBase dst) =>
+                ValueGetter<Imager> del =
+                    (ref Imager dst) =>
                     {
                         if (dst != null)
                         {
@@ -250,7 +250,7 @@ private Delegate MakeGetterImageDataViewType(DataViewRow input, int iinfo, Func<
                             // to avoid locking file, use the construct below to load the image
                             var bytes = File.ReadAllBytes(path);
                             var ms = new MemoryStream(bytes);
-                            dst = ImageBase.CreateFromStream(ms);
+                            dst = new Imager(ms);
                             dst.Tag = path;
                         }
 
@@ -381,14 +381,14 @@ protected override DataViewSchema.DetachedColumn[] GetOutputColumnsCore()
     /// | -- | -- |
     /// | Does this estimator need to look at the data to train its parameters? | No |
     /// | Input column data type | [Text](<xref:Microsoft.ML.Data.TextDataViewType>) |
-    /// | Output column data type | <xref:Microsoft.ML.Data.ImageBase> |
+    /// | Output column data type | <xref:Microsoft.ML.Data.Imager> |
     /// | Required NuGet in addition to Microsoft.ML | Microsoft.ML.ImageAnalytics |
     /// | Exportable to ONNX | No |
     ///
     /// The resulting <xref:Microsoft.ML.Data.ImageLoadingTransformer> creates a new column, named as specified in the output column name parameters, and
     /// loads in it images specified in the input column.
     /// Loading is the first step of almost every pipeline that does image processing, and further analysis on images.
-    /// The images to load need to be in the formats supported by <xref:Microsoft.ML.Data.ImageBase> implementation.
+    /// The images to load need to be in the formats supported by <xref:Microsoft.ML.Data.Imager> implementation.
     /// For end-to-end image processing pipelines, and scenarios in your applications, see the
     /// [examples](https://github.com/dotnet/machinelearning-samples/tree/main/samples/csharp/getting-started) in the machinelearning-samples github repository.</a>
     ///
diff --git a/src/Microsoft.ML.ImageAnalytics/ImagePixelExtractor.cs b/src/Microsoft.ML.ImageAnalytics/ImagePixelExtractor.cs
index 60506e1465..7b60560d74 100644
--- a/src/Microsoft.ML.ImageAnalytics/ImagePixelExtractor.cs
+++ b/src/Microsoft.ML.ImageAnalytics/ImagePixelExtractor.cs
@@ -320,8 +320,8 @@ private ValueGetter<VBuffer<TValue>> GetGetterCore<TValue>(DataViewRow input, in
                 Contracts.Assert(size == planes * height * width);
                 int cpix = height * width;
 
-                var getSrc = input.GetGetter<ImageBase>(input.Schema[ColMapNewToOld[iinfo]]);
-                var src = default(ImageBase);
+                var getSrc = input.GetGetter<Imager>(input.Schema[ColMapNewToOld[iinfo]]);
+                var src = default(Imager);
 
                 disposer =
                     () =>
@@ -480,7 +480,7 @@ private VectorDataViewType[] ConstructTypes()
     /// |  |  |
     /// | -- | -- |
     /// | Does this estimator need to look at the data to train its parameters? | No |
-    /// | Input column data type | <xref:Microsoft.ML.Data.ImageBase> |
+    /// | Input column data type | <xref:Microsoft.ML.Data.Imager> |
     /// | Output column data type | Known-sized vector of <xref:System.Single> or <xref:System.Byte> |
     /// | Required NuGet in addition to Microsoft.ML | Microsoft.ML.ImageAnalytics |
     /// | Exportable to ONNX | No |
diff --git a/src/Microsoft.ML.ImageAnalytics/ImageResizer.cs b/src/Microsoft.ML.ImageAnalytics/ImageResizer.cs
index abba92cb53..48456f5feb 100644
--- a/src/Microsoft.ML.ImageAnalytics/ImageResizer.cs
+++ b/src/Microsoft.ML.ImageAnalytics/ImageResizer.cs
@@ -274,8 +274,8 @@ protected override Delegate MakeGetter(DataViewRow input, int iinfo, Func<int, b
                 Contracts.AssertValue(input);
                 Contracts.Assert(0 <= iinfo && iinfo < _parent._columns.Length);
 
-                var src = default(ImageBase);
-                var getSrc = input.GetGetter<ImageBase>(input.Schema[ColMapNewToOld[iinfo]]);
+                var src = default(Imager);
+                var getSrc = input.GetGetter<Imager>(input.Schema[ColMapNewToOld[iinfo]]);
                 var info = _parent._columns[iinfo];
 
                 disposer =
@@ -287,8 +287,8 @@ protected override Delegate MakeGetter(DataViewRow input, int iinfo, Func<int, b
                         }
                     };
 
-                ValueGetter<ImageBase> del =
-                    (ref ImageBase dst) =>
+                ValueGetter<Imager> del =
+                    (ref Imager dst) =>
                     {
                         if (dst != null)
                         {
@@ -342,8 +342,8 @@ protected override Delegate MakeGetter(DataViewRow input, int iinfo, Func<int, b
     /// |  |  |
     /// | -- | -- |
     /// | Does this estimator need to look at the data to train its parameters? | No |
-    /// | Input column data type | <xref:Microsoft.ML.Data.ImageBase> |
-    /// | Output column data type | <xref:Microsoft.ML.Data.ImageBase> |
+    /// | Input column data type | <xref:Microsoft.ML.Data.Imager> |
+    /// | Output column data type | <xref:Microsoft.ML.Data.Imager> |
     /// | Required NuGet in addition to Microsoft.ML | Microsoft.ML.ImageAnalytics |
     /// | Exportable to ONNX | No |
     ///
@@ -371,7 +371,7 @@ internal static class Defaults
         }
 
         /// <summary>
-        /// Specifies how to resize the images: by croping them or padding in the direction needed to fill up.
+        /// Specifies how to resize the images: by cropping them or padding in the direction needed to fill up.
         /// </summary>
         public enum ResizingKind : byte
         {
diff --git a/src/Microsoft.ML.ImageAnalytics/ImageType.cs b/src/Microsoft.ML.ImageAnalytics/ImageType.cs
index bdf7447401..05d2792bae 100644
--- a/src/Microsoft.ML.ImageAnalytics/ImageType.cs
+++ b/src/Microsoft.ML.ImageAnalytics/ImageType.cs
@@ -63,7 +63,7 @@ public override int GetHashCode()
 
         public override void Register()
         {
-            DataViewTypeManager.Register(new ImageDataViewType(Height, Width), typeof(ImageBase), this);
+            DataViewTypeManager.Register(new ImageDataViewType(Height, Width), typeof(Imager), this);
         }
     }
 
@@ -73,7 +73,7 @@ public sealed class ImageDataViewType : StructuredDataViewType
         public readonly int Width;
 
         public ImageDataViewType(int height, int width)
-           : base(typeof(ImageBase))
+           : base(typeof(Imager))
         {
             Contracts.CheckParam(height > 0, nameof(height), "Must be positive.");
             Contracts.CheckParam(width > 0, nameof(width), " Must be positive.");
@@ -83,7 +83,7 @@ public ImageDataViewType(int height, int width)
             Width = width;
         }
 
-        public ImageDataViewType() : base(typeof(ImageBase))
+        public ImageDataViewType() : base(typeof(Imager))
         {
         }
 
diff --git a/src/Microsoft.ML.ImageAnalytics/ImageBase.cs b/src/Microsoft.ML.ImageAnalytics/Imager.cs
similarity index 70%
rename from src/Microsoft.ML.ImageAnalytics/ImageBase.cs
rename to src/Microsoft.ML.ImageAnalytics/Imager.cs
index 6c7ca360f2..fa92cd1bbf 100644
--- a/src/Microsoft.ML.ImageAnalytics/ImageBase.cs
+++ b/src/Microsoft.ML.ImageAnalytics/Imager.cs
@@ -13,212 +13,84 @@
 namespace Microsoft.ML.Data
 {
     /// <summary>
-    /// Provides the base class for the image provider which allow registering a provider to use instead of the default provider.
+    /// Provide interfaces for imaging operations.
     /// </summary>
-    internal abstract class ImageProvider
+    public class Imager : IDisposable
     {
-        internal static ImageProvider DefaultProvider { get; set; }
-
-        /// <summary>
-        /// Register an image provider to use instead of the default provider.
-        /// </summary>
-        /// <param name="provider">A provider to use for imaging operations.</param>
-        public void RegisterDefaultProvider(ImageProvider provider) => DefaultProvider = provider;
+        private SKBitmap _image;
 
         /// <summary>
-        /// Create image object from a stream.
+        /// Create a new imager instance from a stream.
         /// </summary>
         /// <param name="imageStream">The stream to create the image from.</param>
-        /// <returns>Image object.</returns>
-        public abstract ImageBase CreateImageFromStream(Stream imageStream);
-
-        /// <summary>
-        /// Create image object from the pixel data buffer span.
-        /// </summary>
-        /// <param name="width">The width of the image in pixels.</param>
-        /// <param name="height">The height of the image in pixels.</param>
-        /// <param name="imagePixelData">The pixels data to create the image from.</param>
-        /// <returns>Image object.</returns>
-        public abstract ImageBase CreateBgra32ImageFromPixelData(int width, int height, Span<byte> imagePixelData);
-    }
-
-    /// <summary>
-    /// The mode to decide how the image should be resized.
-    /// </summary>
-    public enum ImageResizeMode
-    {
-        /// <summary>
-        /// Pads the resized image to fit the bounds of its container.
-        /// </summary>
-        Pad,
-
-        /// <summary>
-        /// Ignore aspect ratio and squeeze/stretch into target dimensions.
-        /// </summary>
-        Fill,
-
-        /// <summary>
-        /// Resized image to fit the bounds of its container using cropping with top anchor.
-        /// </summary>
-        CropAnchorTop,
-
-        /// <summary>
-        /// Resized image to fit the bounds of its container using cropping with bottom anchor.
-        /// </summary>
-        CropAnchorBottom,
-
-        /// <summary>
-        /// Resized image to fit the bounds of its container using cropping with left anchor.
-        /// </summary>
-        CropAnchorLeft,
-
-        /// <summary>
-        /// Resized image to fit the bounds of its container using cropping with right anchor.
-        /// </summary>
-        CropAnchorRight,
-
-        /// <summary>
-        /// Resized image to fit the bounds of its container using cropping with central anchor.
-        /// </summary>
-        CropAnchorCentral
-    }
-
-    /// <summary>
-    /// Base class provide all interfaces for imaging operations.
-    /// </summary>
-    public abstract class ImageBase : IDisposable
-    {
-        /// <summary>
-        /// Gets or sets the image tag.
-        /// </summary>
-        public string Tag { get; set; }
-
-        /// <summary>
-        /// Gets the image width in pixels.
-        /// </summary>
-        public abstract int Width { get; }
-
-        /// <summary>
-        /// Gets the image height in pixels.
-        /// </summary>
-        public abstract int Height { get; }
+        public Imager(Stream imageStream)
+        {
+            if (imageStream is null)
+            {
+                throw new ArgumentNullException(nameof(imageStream));
+            }
 
-        /// <summary>
-        /// Gets how many bits per pixel used by current image object.
-        /// </summary>
-        public abstract int BitsPerPixel { get; }
+            _image = SKBitmap.Decode(imageStream);
+            if (_image is null)
+            {
+                throw new ArgumentException($"Invalid input stream contents", nameof(imageStream));
+            }
 
-        /// <summary>
-        /// Create image object from a stream.
-        /// </summary>
-        /// <param name="imageStream">The stream to create the image from.</param>
-        /// <returns>Image object.</returns>
-        public static ImageBase CreateFromStream(Stream imageStream)
-        {
-            ImageProvider provider = ImageProvider.DefaultProvider;
-            return provider is not null ? provider.CreateImageFromStream(imageStream) : SkiaSharpImage.Create(imageStream);
+            EnsureSupportedPixelFormat();
         }
 
         /// <summary>
-        /// Create BRGA32 pixel format image object from the pixel data buffer span.
+        /// Create a new imager instance from image file.
         /// </summary>
-        /// <param name="width">The width of the image in pixels.</param>
-        /// <param name="height">The height of the image in pixels.</param>
-        /// <param name="imagePixelData">The pixels data to create the image from.</param>
-        /// <returns>Image object.</returns>
-        public static ImageBase CreateBgra32Image(int width, int height, Span<byte> imagePixelData)
+        /// <param name="imagePath">The image file path to create the image from.</param>
+        public Imager(string imagePath)
         {
-            ImageProvider provider = ImageProvider.DefaultProvider;
-            return provider is not null ? provider.CreateBgra32ImageFromPixelData(width, height, imagePixelData) : SkiaSharpImage.CreateFromPixelData(width, height, imagePixelData);
-        }
-
-        /// <summary>
-        /// Clones the current image with resizing it.
-        /// </summary>
-        /// <param name="width">The new width of the image.</param>
-        /// <param name="height">The new height of the image.</param>
-        /// <param name="mode">How to resize the image.</param>
-        /// <returns>The new cloned image.</returns>
-        public abstract ImageBase CloneWithResizing(int width, int height, ImageResizeMode mode);
-
-        /// <summary>
-        /// Clones the current image with grayscale.
-        /// </summary>
-        /// <returns>The new cloned image.</returns>
-        public abstract ImageBase CloneWithGrayscale();
+            if (imagePath is null)
+            {
+                throw new ArgumentNullException(nameof(imagePath));
+            }
 
-        /// <summary>
-        /// Gets the image pixel data and how the colors are ordered in the used pixel format.
-        /// </summary>
-        /// <param name="alphaIndex">The index of the alpha in the pixel format.</param>
-        /// <param name="redIndex">The index of the red color in the pixel format.</param>
-        /// <param name="greenIndex">The index of the green color in the pixel format.</param>
-        /// <param name="blueIndex">The index of the blue color in the pixel format.</param>
-        /// <returns>The buffer containing the image pixel data.</returns>
-        public abstract ReadOnlySpan<byte> Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
+            _image = SKBitmap.Decode(imagePath);
+            if (_image is null)
+            {
+                throw new ArgumentException($"Invalid path", nameof(imagePath));
+            }
 
-        /// <summary>
-        /// Save the current image to a file.
-        /// </summary>
-        /// <param name="imagePath">The path of the file to save the image to.</param>
-        /// <remarks>The saved image encoding will be detected from the file extension.</remarks>
-        public abstract void Save(string imagePath);
+            EnsureSupportedPixelFormat();
+        }
 
-        /// <summary>
-        /// Releases the unmanaged resources used by the image object and optionally releases the managed resources.
-        /// </summary>
-        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
-        protected virtual void Dispose(bool disposing)
+        private Imager(SKBitmap image)
         {
+            _image = image;
+            EnsureSupportedPixelFormat();
         }
 
-        /// <summary>
-        /// Releases all resources used by the image object.
-        /// </summary>
-        public void Dispose() => Dispose(disposing: true);
-    }
-
-    internal class SkiaSharpImage : ImageBase
-    {
-        private SKBitmap _image;
-
-        private SkiaSharpImage(SKBitmap image)
+        private void EnsureSupportedPixelFormat()
         {
-            Debug.Assert(image is not null);
+            Debug.Assert(_image is not null);
 
             // Most of the time SkiaSharp create images with Bgra8888 or Rgba8888 pixel format.
-            if (image.Info.ColorType != SKColorType.Bgra8888 && image.Info.ColorType != SKColorType.Rgba8888)
+            if (_image.Info.ColorType != SKColorType.Bgra8888 && _image.Info.ColorType != SKColorType.Rgba8888)
             {
-                if (!image.CanCopyTo(SKColorType.Bgra8888))
+                if (!_image.CanCopyTo(SKColorType.Bgra8888))
                 {
                     throw new InvalidOperationException("Unsupported image format.");
                 }
 
-                SKBitmap image1 = image.Copy(SKColorType.Bgra8888);
-                image.Dispose();
-                image = image1;
-            }
-
-            _image = image;
-        }
-
-        public static ImageBase Create(Stream imageStream)
-        {
-            if (imageStream is null)
-            {
-                throw new ArgumentNullException(nameof(imageStream));
-            }
-
-            SKBitmap image = SKBitmap.Decode(imageStream);
-            if (image is null)
-            {
-                throw new ArgumentException($"Invalid input stream contents", nameof(imageStream));
+                SKBitmap image1 = _image.Copy(SKColorType.Bgra8888);
+                _image.Dispose();
+                _image = image1;
             }
-
-            return new SkiaSharpImage(image);
         }
 
-        public static unsafe ImageBase CreateFromPixelData(int width, int height, Span<byte> imagePixelData)
+        /// <summary>
+        /// Create BRGA32 pixel format imager object from the pixel data buffer span.
+        /// </summary>
+        /// <param name="width">The width of the image in pixels.</param>
+        /// <param name="height">The height of the image in pixels.</param>
+        /// <param name="imagePixelData">The pixels data to create the image from.</param>
+        /// <returns>Imager object.</returns>
+        public static unsafe Imager CreateFromBgra32PixelData(int width, int height, Span<byte> imagePixelData)
         {
             if (imagePixelData.Length != width * height * 4)
             {
@@ -232,10 +104,18 @@ public static unsafe ImageBase CreateFromPixelData(int width, int height, Span<b
 
             imagePixelData.CopyTo(new Span<byte>(image.GetPixels().ToPointer(), image.Width * image.Height * 4));
 
-            return new SkiaSharpImage(image);
+            return new Imager(image);
         }
 
-        public override ReadOnlySpan<byte> Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex)
+        /// <summary>
+        /// Gets the image pixel data and how the colors are ordered in the used pixel format.
+        /// </summary>
+        /// <param name="alphaIndex">The index of the alpha in the pixel format.</param>
+        /// <param name="redIndex">The index of the red color in the pixel format.</param>
+        /// <param name="greenIndex">The index of the green color in the pixel format.</param>
+        /// <param name="blueIndex">The index of the blue color in the pixel format.</param>
+        /// <returns>The byte span containing the image pixel data.</returns>
+        public ReadOnlySpan<byte> Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex)
         {
             ThrowInvalidOperationExceptionIfDisposed();
 
@@ -260,7 +140,7 @@ public override ReadOnlySpan<byte> Get32bbpImageData(out int alphaIndex, out int
             return _image.GetPixelSpan();
         }
 
-        public override ImageBase CloneWithResizing(int width, int height, ImageResizeMode mode)
+        internal Imager CloneWithResizing(int width, int height, ImageResizeMode mode)
         {
             ThrowInvalidOperationExceptionIfDisposed();
 
@@ -277,7 +157,7 @@ public override ImageBase CloneWithResizing(int width, int height, ImageResizeMo
                 throw new InvalidOperationException($"Couldn't resize the image");
             }
 
-            return new SkiaSharpImage(image);
+            return new Imager(image);
         }
 
         private SKBitmap ResizeFull(int width, int height) => _image.Resize(new SKSizeI(width, height), SKFilterQuality.None);
@@ -382,7 +262,7 @@ private SKBitmap ResizeWithCrop(int width, int height, ImageResizeMode mode)
                                                                             0,    0,     0,     1, 0
                                                                         });
 
-        public override ImageBase CloneWithGrayscale()
+        internal Imager CloneWithGrayscale()
         {
             ThrowInvalidOperationExceptionIfDisposed();
 
@@ -396,10 +276,18 @@ public override ImageBase CloneWithGrayscale()
             SKBitmap destBitmap = new SKBitmap(_image.Width, _image.Height, isOpaque: true);
             using SKCanvas canvas = new SKCanvas(destBitmap);
             canvas.DrawBitmap(_image, 0f, 0f, paint: paint);
-            return new SkiaSharpImage(destBitmap);
+            return new Imager(destBitmap);
         }
 
-        public override int Width
+        /// <summary>
+        /// Gets or sets the image tag.
+        /// </summary>
+        public string Tag { get; set; }
+
+        /// <summary>
+        /// Gets the image width in pixels.
+        /// </summary>
+        public int Width
         {
             get
             {
@@ -408,7 +296,10 @@ public override int Width
             }
         }
 
-        public override int Height
+        /// <summary>
+        /// Gets the image height in pixels.
+        /// </summary>
+        public int Height
         {
             get
             {
@@ -417,7 +308,10 @@ public override int Height
             }
         }
 
-        public override int BitsPerPixel
+        /// <summary>
+        /// Gets how many bits per pixel used by current image object.
+        /// </summary>
+        public int BitsPerPixel
         {
             get
             {
@@ -445,7 +339,12 @@ public override int BitsPerPixel
             { ".webp", SKEncodedImageFormat.Webp }
         };
 
-        public override void Save(string imagePath)
+        /// <summary>
+        /// Save the current image to a file.
+        /// </summary>
+        /// <param name="imagePath">The path of the file to save the image to.</param>
+        /// <remarks>The saved image encoding will be detected from the file extension.</remarks>
+        public void Save(string imagePath)
         {
             ThrowInvalidOperationExceptionIfDisposed();
             string ext = Path.GetExtension(imagePath);
@@ -460,7 +359,7 @@ public override void Save(string imagePath)
             data.SaveTo(stream);
         }
 
-        protected override void Dispose(bool disposing)
+        public void Dispose()
         {
             if (_image != null)
             {
@@ -477,4 +376,45 @@ private void ThrowInvalidOperationExceptionIfDisposed()
             }
         }
     }
+
+    /// <summary>
+    /// The mode to decide how the image should be resized.
+    /// </summary>
+    internal enum ImageResizeMode
+    {
+        /// <summary>
+        /// Pads the resized image to fit the bounds of its container.
+        /// </summary>
+        Pad,
+
+        /// <summary>
+        /// Ignore aspect ratio and squeeze/stretch into target dimensions.
+        /// </summary>
+        Fill,
+
+        /// <summary>
+        /// Resized image to fit the bounds of its container using cropping with top anchor.
+        /// </summary>
+        CropAnchorTop,
+
+        /// <summary>
+        /// Resized image to fit the bounds of its container using cropping with bottom anchor.
+        /// </summary>
+        CropAnchorBottom,
+
+        /// <summary>
+        /// Resized image to fit the bounds of its container using cropping with left anchor.
+        /// </summary>
+        CropAnchorLeft,
+
+        /// <summary>
+        /// Resized image to fit the bounds of its container using cropping with right anchor.
+        /// </summary>
+        CropAnchorRight,
+
+        /// <summary>
+        /// Resized image to fit the bounds of its container using cropping with central anchor.
+        /// </summary>
+        CropAnchorCentral
+    }
 }
diff --git a/src/Microsoft.ML.ImageAnalytics/Microsoft.ML.ImageAnalytics.csproj b/src/Microsoft.ML.ImageAnalytics/Microsoft.ML.ImageAnalytics.csproj
index 7ce31c3614..5edf8ebb14 100644
--- a/src/Microsoft.ML.ImageAnalytics/Microsoft.ML.ImageAnalytics.csproj
+++ b/src/Microsoft.ML.ImageAnalytics/Microsoft.ML.ImageAnalytics.csproj
@@ -12,10 +12,9 @@
     <PackageReference Include="System.Buffers" Version="$(SystemBuffersVersion)" />
     <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="$(SystemRuntimeCompilerServicesUnsafeVersion)" />
 
+    <!-- SkiaSharp reference Windows, UWP, and MacOS native dependencies packages. No need to explicity reference it here. -->
     <PackageReference Include="SkiaSharp" Version="$(SkiaSharpVersion)" />
     <PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="$(SkiaSharpVersion)" />
-    <PackageReference Include="SkiaSharp.NativeAssets.macOS" Version="$(SkiaSharpVersion)" />
-    <PackageReference Include="SkiaSharp.NativeAssets.Win32" Version="$(SkiaSharpVersion)" />
   </ItemGroup>
 
   <ItemGroup>
diff --git a/src/Microsoft.ML.ImageAnalytics/VectorToImageTransform.cs b/src/Microsoft.ML.ImageAnalytics/VectorToImageTransform.cs
index e75488ff24..addf61d45c 100644
--- a/src/Microsoft.ML.ImageAnalytics/VectorToImageTransform.cs
+++ b/src/Microsoft.ML.ImageAnalytics/VectorToImageTransform.cs
@@ -341,7 +341,7 @@ protected override Delegate MakeGetter(DataViewRow input, int iinfo, Func<int, b
                     throw Contracts.Except("We only support float, double or byte arrays");
             }
 
-            private ValueGetter<ImageBase> GetterFromType<TValue>(PrimitiveDataViewType srcType, DataViewRow input, int iinfo,
+            private ValueGetter<Imager> GetterFromType<TValue>(PrimitiveDataViewType srcType, DataViewRow input, int iinfo,
                 VectorToImageConvertingEstimator.ColumnOptions ex, bool needScale) where TValue : IConvertible
             {
                 Contracts.Assert(typeof(TValue) == srcType.RawType);
@@ -353,7 +353,7 @@ private ValueGetter<ImageBase> GetterFromType<TValue>(PrimitiveDataViewType srcT
                 float scale = ex.ScaleImage;
 
                 return
-                    (ref ImageBase dst) =>
+                    (ref Imager dst) =>
                     {
                         getSrc(ref src);
                         if (src.GetValues().Length == 0)
@@ -415,7 +415,7 @@ private ValueGetter<ImageBase> GetterFromType<TValue>(PrimitiveDataViewType srcT
                             }
                         }
 
-                        dst = ImageBase.CreateBgra32Image(width, height, imageData);
+                        dst = Imager.CreateFromBgra32PixelData(width, height, imageData);
                         dst.Tag = nameof(VectorToImageConvertingTransformer);
                     };
             }
@@ -438,7 +438,7 @@ private static ImageDataViewType[] ConstructTypes(VectorToImageConvertingEstimat
     /// | -- | -- |
     /// | Does this estimator need to look at the data to train its parameters? | No |
     /// | Input column data type | Known-sized vector of <xref:System.Single>, <xref:System.Double> or <xref:System.Byte>. |
-    /// | Output column data type | <xref:Microsoft.ML.Data.ImageBase> |
+    /// | Output column data type | <xref:Microsoft.ML.Data.Imager> |
     /// | Required NuGet in addition to Microsoft.ML | Microsoft.ML.ImageAnalytics |
     /// | Exportable to ONNX | No |
     ///
diff --git a/test/Microsoft.ML.OnnxTransformerTest/OnnxTransformTests.cs b/test/Microsoft.ML.OnnxTransformerTest/OnnxTransformTests.cs
index 061ffa3b6b..0796aeb9bc 100644
--- a/test/Microsoft.ML.OnnxTransformerTest/OnnxTransformTests.cs
+++ b/test/Microsoft.ML.OnnxTransformerTest/OnnxTransformTests.cs
@@ -500,7 +500,7 @@ private class ImageDataPoint
             /// Image will be consumed by ONNX image multiclass classification model.
             /// </summary>
             [ImageType(Height, Width)]
-            public ImageBase Image { get; set; }
+            public Imager Image { get; set; }
 
             /// <summary>
             /// Output of ONNX model. It contains probabilities of all classes.
@@ -525,7 +525,7 @@ public ImageDataPoint(byte red, byte green, byte blue)
                     imageData[i + 3] = 255;
                 }
 
-                Image = ImageBase.CreateBgra32Image(Width, Height, imageData);
+                Image = Imager.CreateFromBgra32PixelData(Width, Height, imageData);
             }
         }
 
diff --git a/test/Microsoft.ML.Tests/ImagesTests.cs b/test/Microsoft.ML.Tests/ImagesTests.cs
index 54cd9a9943..c4dbb35e21 100644
--- a/test/Microsoft.ML.Tests/ImagesTests.cs
+++ b/test/Microsoft.ML.Tests/ImagesTests.cs
@@ -152,8 +152,8 @@ public void TestSaveImages()
             {
                 var pathGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cropped.Schema["ImagePath"]);
                 ReadOnlyMemory<char> path = default;
-                var imageCropGetter = cursor.GetGetter<ImageBase>(cropped.Schema["ImageCropped"]);
-                ImageBase image = default;
+                var imageCropGetter = cursor.GetGetter<Imager>(cropped.Schema["ImageCropped"]);
+                Imager image = default;
                 while (cursor.MoveNext())
                 {
                     pathGetter(ref path);
@@ -199,8 +199,8 @@ public void TestGrayscaleTransformImages()
             grey.Schema.TryGetColumnIndex("ImageGrey", out int greyColumn);
             using (var cursor = grey.GetRowCursorForAllColumns())
             {
-                var imageGetter = cursor.GetGetter<ImageBase>(grey.Schema["ImageGrey"]);
-                ImageBase image = default;
+                var imageGetter = cursor.GetGetter<Imager>(grey.Schema["ImageGrey"]);
+                Imager image = default;
                 while (cursor.MoveNext())
                 {
                     imageGetter(ref image);
@@ -280,10 +280,10 @@ public void TestGrayScaleInMemory()
         private class ImageDataPoint
         {
             [ImageType(10, 10)]
-            public ImageBase Image { get; set; }
+            public Imager Image { get; set; }
 
             [ImageType(10, 10)]
-            public ImageBase GrayImage { get; set; }
+            public Imager GrayImage { get; set; }
 
             public ImageDataPoint()
             {
@@ -303,7 +303,7 @@ public ImageDataPoint(int width, int height, byte red, byte green, byte blue)
                     imageData[i + 3] = 255;
                 }
 
-                Image = ImageBase.CreateBgra32Image(width, height, imageData);
+                Image = Imager.CreateFromBgra32PixelData(width, height, imageData);
             }
         }
 
@@ -341,11 +341,11 @@ public void TestBackAndForthConversionWithAlphaInterleave()
 
             using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var imageGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageRestored"]);
-                ImageBase restoredImage = default;
+                var imageGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageRestored"]);
+                Imager restoredImage = default;
 
-                var imageCropGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageCropped"]);
-                ImageBase croppedImage = default;
+                var imageCropGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageCropped"]);
+                Imager croppedImage = default;
                 while (cursor.MoveNext())
                 {
                     imageGetter(ref restoredImage);
@@ -403,11 +403,11 @@ public void TestBackAndForthConversionWithoutAlphaInterleave()
 
             using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var imageGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageRestored"]);
-                ImageBase restoredImage = default;
+                var imageGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageRestored"]);
+                Imager restoredImage = default;
 
-                var imageCropGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageCropped"]);
-                ImageBase croppedImage = default;
+                var imageCropGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageCropped"]);
+                Imager croppedImage = default;
                 while (cursor.MoveNext())
                 {
                     imageGetter(ref restoredImage);
@@ -466,11 +466,11 @@ public void TestBackAndForthConversionWithDifferentOrder()
 
             using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var imageGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageRestored"]);
-                ImageBase restoredImage = default;
+                var imageGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageRestored"]);
+                Imager restoredImage = default;
 
-                var imageCropGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageCropped"]);
-                ImageBase croppedImage = default;
+                var imageCropGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageCropped"]);
+                Imager croppedImage = default;
                 while (cursor.MoveNext())
                 {
                     imageGetter(ref restoredImage);
@@ -528,11 +528,11 @@ public void TestBackAndForthConversionWithAlphaNoInterleave()
 
             using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var imageGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageRestored"]);
-                ImageBase restoredImage = default;
+                var imageGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageRestored"]);
+                Imager restoredImage = default;
 
-                var imageCropGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageCropped"]);
-                ImageBase croppedImage = default;
+                var imageCropGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageCropped"]);
+                Imager croppedImage = default;
                 while (cursor.MoveNext())
                 {
                     imageGetter(ref restoredImage);
@@ -590,11 +590,11 @@ public void TestBackAndForthConversionWithoutAlphaNoInterleave()
 
             using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var imageGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageRestored"]);
-                ImageBase restoredImage = default;
+                var imageGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageRestored"]);
+                Imager restoredImage = default;
 
-                var imageCropGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageCropped"]);
-                ImageBase croppedImage = default;
+                var imageCropGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageCropped"]);
+                Imager croppedImage = default;
                 while (cursor.MoveNext())
                 {
                     imageGetter(ref restoredImage);
@@ -653,11 +653,11 @@ public void TestBackAndForthConversionWithAlphaInterleaveNoOffset()
 
             using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var imageGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageRestored"]);
-                ImageBase restoredImage = default;
+                var imageGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageRestored"]);
+                Imager restoredImage = default;
 
-                var imageCropGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageCropped"]);
-                ImageBase croppedImage = default;
+                var imageCropGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageCropped"]);
+                Imager croppedImage = default;
                 while (cursor.MoveNext())
                 {
                     imageGetter(ref restoredImage);
@@ -715,11 +715,11 @@ public void TestBackAndForthConversionWithoutAlphaInterleaveNoOffset()
 
             using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var imageGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageRestored"]);
-                ImageBase restoredImage = default;
+                var imageGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageRestored"]);
+                Imager restoredImage = default;
 
-                var imageCropGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageCropped"]);
-                ImageBase croppedImage = default;
+                var imageCropGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageCropped"]);
+                Imager croppedImage = default;
                 while (cursor.MoveNext())
                 {
                     imageGetter(ref restoredImage);
@@ -778,11 +778,11 @@ public void TestBackAndForthConversionWithAlphaNoInterleaveNoOffset()
 
             using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var imageGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageRestored"]);
-                ImageBase restoredImage = default;
+                var imageGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageRestored"]);
+                Imager restoredImage = default;
 
-                var imageCropGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageCropped"]);
-                ImageBase croppedImage = default;
+                var imageCropGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageCropped"]);
+                Imager croppedImage = default;
                 while (cursor.MoveNext())
                 {
                     imageGetter(ref restoredImage);
@@ -839,11 +839,11 @@ public void TestBackAndForthConversionWithoutAlphaNoInterleaveNoOffset()
 
             using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var imageGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageRestored"]);
-                ImageBase restoredImage = default;
+                var imageGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageRestored"]);
+                Imager restoredImage = default;
 
-                var imageCropGetter = cursor.GetGetter<ImageBase>(backToImages.Schema["ImageCropped"]);
-                ImageBase croppedImage = default;
+                var imageCropGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageCropped"]);
+                Imager croppedImage = default;
                 while (cursor.MoveNext())
                 {
                     imageGetter(ref restoredImage);
@@ -889,7 +889,7 @@ public void ImageResizerTransformResizingModeFill()
             var rowView = pipe.Preview(data).RowView;
             Assert.Single(rowView);
 
-            using (var image = (ImageBase)rowView.First().Values.Last().Value)
+            using (var image = (Imager)rowView.First().Values.Last().Value)
             {
                 ReadOnlySpan<byte> imageData = image.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
                 int pixelSize = image.BitsPerPixel / 8;
@@ -978,7 +978,7 @@ private class DataPoint
         public class InMemoryImage
         {
             [ImageType(229, 299)]
-            public ImageBase LoadedImage;
+            public Imager LoadedImage;
             public string Label;
 
             public static List<InMemoryImage> LoadFromTsv(MLContext mlContext, string tsvPath, string imageFolder)
@@ -1020,17 +1020,17 @@ public static List<InMemoryImage> LoadFromTsv(MLContext mlContext, string tsvPat
 
             }
 
-            private static ImageBase LoadImageFromFile(string imagePath)
+            private static Imager LoadImageFromFile(string imagePath)
             {
                 using Stream stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read, FileShare.Read);
-                return ImageBase.CreateFromStream(stream);
+                return new Imager(stream);
             }
         }
 
         public class InMemoryImageOutput : InMemoryImage
         {
             [ImageType(100, 100)]
-            public ImageBase ResizedImage;
+            public Imager ResizedImage;
         }
 
         [Fact]
@@ -1048,7 +1048,7 @@ public void ResizeInMemoryImages()
             var model = pipeline.Fit(dataView);
             var resizedDV = model.Transform(dataView);
             var rowView = resizedDV.Preview().RowView;
-            var resizedImage = (ImageBase)rowView.First().Values.Last().Value;
+            var resizedImage = (Imager)rowView.First().Values.Last().Value;
             Assert.Equal(100, resizedImage.Height);
             Assert.NotEqual(100, dataObjects[0].LoadedImage.Height);
 

From 3d07849479ddeedbf29d268a62b67d5728adffb7 Mon Sep 17 00:00:00 2001
From: Tarek Mahmoud Sayed <tarekms@microsoft.com>
Date: Sat, 15 Oct 2022 14:44:59 -0700
Subject: [PATCH 4/7] Address the feedback

---
 .../ApplyONNXModelWithInMemoryImages.cs       |   4 +-
 .../ImageAnalytics/ConvertToGrayScale.cs      |   8 +-
 .../ConvertToGrayScaleInMemory.cs             |  25 +-
 .../ImageAnalytics/ConvertToImage.cs          |   4 +-
 .../ImageAnalytics/ExtractPixels.cs           |   8 +-
 .../Transforms/ImageAnalytics/LoadImages.cs   |   4 +-
 .../Transforms/ImageAnalytics/ResizeImages.cs |   8 +-
 .../ExtensionsCatalog.cs                      |  12 +-
 .../ImageGrayscale.cs                         |  12 +-
 .../ImageLoader.cs                            |  12 +-
 .../ImagePixelExtractor.cs                    |  14 +-
 .../ImageResizer.cs                           |  12 +-
 src/Microsoft.ML.ImageAnalytics/ImageType.cs  |   6 +-
 .../{Imager.cs => MLImage.cs}                 | 291 ++++++++++--------
 .../VectorToImageTransform.cs                 |   8 +-
 .../OnnxTransformTests.cs                     |   4 +-
 test/Microsoft.ML.Tests/ImagesTests.cs        | 285 ++++++++++++-----
 17 files changed, 448 insertions(+), 269 deletions(-)
 rename src/Microsoft.ML.ImageAnalytics/{Imager.cs => MLImage.cs} (78%)

diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ApplyONNXModelWithInMemoryImages.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ApplyONNXModelWithInMemoryImages.cs
index 340dd2aeff..084c10a8ed 100644
--- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ApplyONNXModelWithInMemoryImages.cs
+++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ApplyONNXModelWithInMemoryImages.cs
@@ -90,7 +90,7 @@ private class ImageDataPoint
 
             // Image will be consumed by ONNX image multiclass classification model.
             [ImageType(height, width)]
-            public Imager Image { get; set; }
+            public MLImage Image { get; set; }
 
             // Expected output of ONNX model. It contains probabilities of all
             // classes. Note that the ColumnName below should match the output name
@@ -115,7 +115,7 @@ public ImageDataPoint(byte red, byte green, byte blue)
                     imageData[i + 3] = 255;
                 }
 
-                Image = Imager.CreateFromBgra32PixelData(width, height, imageData);
+                Image = MLImage.CreateFromPixels(width, height, MLPixelFormat.Bgra32, imageData);
             }
         }
     }
diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScale.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScale.cs
index 28d8a96b72..000cff92db 100644
--- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScale.cs
+++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScale.cs
@@ -70,8 +70,8 @@ private static void PrintColumns(IDataView transformedData)
                 // column -type validation once, rather than many times.
                 ReadOnlyMemory<char> imagePath = default;
                 ReadOnlyMemory<char> name = default;
-                Imager imageObject = null;
-                Imager grayscaleImageObject = null;
+                MLImage imageObject = null;
+                MLImage grayscaleImageObject = null;
 
                 var imagePathGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
                     .Schema["ImagePath"]);
@@ -79,10 +79,10 @@ private static void PrintColumns(IDataView transformedData)
                 var nameGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
                     .Schema["Name"]);
 
-                var imageObjectGetter = cursor.GetGetter<Imager>(cursor.Schema[
+                var imageObjectGetter = cursor.GetGetter<MLImage>(cursor.Schema[
                     "ImageObject"]);
 
-                var grayscaleGetter = cursor.GetGetter<Imager>(cursor.Schema[
+                var grayscaleGetter = cursor.GetGetter<MLImage>(cursor.Schema[
                     "Grayscale"]);
 
                 while (cursor.MoveNext())
diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScaleInMemory.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScaleInMemory.cs
index 6885d1b443..5d337a9c35 100644
--- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScaleInMemory.cs
+++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToGrayScaleInMemory.cs
@@ -38,8 +38,23 @@ public static void Example()
             {
                 var image = dataPoint.Image;
                 var grayImage = dataPoint.GrayImage;
-                ReadOnlySpan<byte> imageData = image.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
-                ReadOnlySpan<byte> grayImageData = grayImage.Get32bbpImageData(out int alphaIndex1, out int redIndex1, out int greenIndex1, out int blueIndex1);
+
+                ReadOnlySpan<byte> imageData = image.Pixels;
+                (int alphaIndex, int redIndex, int greenIndex, int blueIndex) = image.PixelFormat switch
+                {
+                    MLPixelFormat.Bgra32 => (3, 2, 1, 0),
+                    MLPixelFormat.Rgba32 => (3, 0, 1, 2),
+                    _ => throw new InvalidOperationException($"Image pixel format is not supported")
+                };
+
+                ReadOnlySpan<byte> grayImageData = grayImage.Pixels;
+                (int alphaIndex1, int redIndex1, int greenIndex1, int blueIndex1) = grayImage.PixelFormat switch
+                {
+                    MLPixelFormat.Bgra32 => (3, 2, 1, 0),
+                    MLPixelFormat.Rgba32 => (3, 0, 1, 2),
+                    _ => throw new InvalidOperationException($"Image pixel format is not supported")
+                };
+
                 int pixelSize = image.BitsPerPixel / 8;
 
                 for (int i = 0; i < imageData.Length; i += pixelSize)
@@ -69,10 +84,10 @@ public static void Example()
         private class ImageDataPoint
         {
             [ImageType(3, 4)]
-            public Imager Image { get; set; }
+            public MLImage Image { get; set; }
 
             [ImageType(3, 4)]
-            public Imager GrayImage { get; set; }
+            public MLImage GrayImage { get; set; }
 
             public ImageDataPoint()
             {
@@ -92,7 +107,7 @@ public ImageDataPoint(int width, int height, byte red, byte green, byte blue)
                     imageData[i + 3] = 255;
                 }
 
-                Image = Imager.CreateFromBgra32PixelData(width, height, imageData);
+                Image = MLImage.CreateFromPixels(width, height, MLPixelFormat.Bgra32, imageData);
             }
         }
     }
diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToImage.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToImage.cs
index 746bc35a26..a40f846dd3 100644
--- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToImage.cs
+++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ConvertToImage.cs
@@ -58,7 +58,7 @@ private static void PrintColumns(IDataView transformedData)
                 // column -type validation once, rather than many times.
                 VBuffer<float> features = default;
                 VBuffer<float> pixels = default;
-                Imager imageObject = null;
+                MLImage imageObject = null;
 
                 var featuresGetter = cursor.GetGetter<VBuffer<float>>(cursor.Schema[
                     "Features"]);
@@ -66,7 +66,7 @@ private static void PrintColumns(IDataView transformedData)
                 var pixelsGetter = cursor.GetGetter<VBuffer<float>>(cursor.Schema[
                     "Pixels"]);
 
-                var imageGetter = cursor.GetGetter<Imager>(cursor.Schema["Image"]);
+                var imageGetter = cursor.GetGetter<MLImage>(cursor.Schema["Image"]);
                 while (cursor.MoveNext())
                 {
 
diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ExtractPixels.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ExtractPixels.cs
index 2fba66e785..bba800809f 100644
--- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ExtractPixels.cs
+++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ExtractPixels.cs
@@ -78,8 +78,8 @@ private static void PrintColumns(IDataView transformedData)
 
                 ReadOnlyMemory<char> imagePath = default;
                 ReadOnlyMemory<char> name = default;
-                Imager imageObject = null;
-                Imager resizedImageObject = null;
+                MLImage imageObject = null;
+                MLImage resizedImageObject = null;
                 VBuffer<float> pixels = default;
 
                 var imagePathGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
@@ -88,10 +88,10 @@ private static void PrintColumns(IDataView transformedData)
                 var nameGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
                     .Schema["Name"]);
 
-                var imageObjectGetter = cursor.GetGetter<Imager>(cursor.Schema[
+                var imageObjectGetter = cursor.GetGetter<MLImage>(cursor.Schema[
                     "ImageObject"]);
 
-                var resizedImageGetter = cursor.GetGetter<Imager>(cursor.Schema[
+                var resizedImageGetter = cursor.GetGetter<MLImage>(cursor.Schema[
                     "ImageObjectResized"]);
 
                 var pixelsGetter = cursor.GetGetter<VBuffer<float>>(cursor.Schema[
diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/LoadImages.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/LoadImages.cs
index 5228a445db..382cd5a86f 100644
--- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/LoadImages.cs
+++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/LoadImages.cs
@@ -69,7 +69,7 @@ private static void PrintColumns(IDataView transformedData)
                 // and column-type validation once, rather than many times.
                 ReadOnlyMemory<char> imagePath = default;
                 ReadOnlyMemory<char> name = default;
-                Imager imageObject = null;
+                MLImage imageObject = null;
 
                 var imagePathGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
                     .Schema["ImagePath"]);
@@ -77,7 +77,7 @@ private static void PrintColumns(IDataView transformedData)
                 var nameGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
                     .Schema["Name"]);
 
-                var imageObjectGetter = cursor.GetGetter<Imager>(cursor.Schema[
+                var imageObjectGetter = cursor.GetGetter<MLImage>(cursor.Schema[
                     "ImageObject"]);
 
                 while (cursor.MoveNext())
diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ResizeImages.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ResizeImages.cs
index 30347999da..ccb7f661a6 100644
--- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ResizeImages.cs
+++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/ImageAnalytics/ResizeImages.cs
@@ -72,8 +72,8 @@ private static void PrintColumns(IDataView transformedData)
                 // column -type validation once, rather than many times.
                 ReadOnlyMemory<char> imagePath = default;
                 ReadOnlyMemory<char> name = default;
-                Imager imageObject = null;
-                Imager resizedImageObject = null;
+                MLImage imageObject = null;
+                MLImage resizedImageObject = null;
 
                 var imagePathGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
                     .Schema["ImagePath"]);
@@ -81,10 +81,10 @@ private static void PrintColumns(IDataView transformedData)
                 var nameGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cursor
                     .Schema["Name"]);
 
-                var imageObjectGetter = cursor.GetGetter<Imager>(cursor.Schema[
+                var imageObjectGetter = cursor.GetGetter<MLImage>(cursor.Schema[
                     "ImageObject"]);
 
-                var resizedImageGetter = cursor.GetGetter<Imager>(cursor.Schema[
+                var resizedImageGetter = cursor.GetGetter<MLImage>(cursor.Schema[
                     "ImageObjectResized"]);
 
                 while (cursor.MoveNext())
diff --git a/src/Microsoft.ML.ImageAnalytics/ExtensionsCatalog.cs b/src/Microsoft.ML.ImageAnalytics/ExtensionsCatalog.cs
index 494f4436f3..132d64ba22 100644
--- a/src/Microsoft.ML.ImageAnalytics/ExtensionsCatalog.cs
+++ b/src/Microsoft.ML.ImageAnalytics/ExtensionsCatalog.cs
@@ -21,7 +21,7 @@ public static class ImageEstimatorsCatalog
         /// <param name="outputColumnName">Name of the column resulting from the transformation of <paramref name="inputColumnName"/>.
         /// This column's data type will be the same as that of the input column.</param>
         /// <param name="inputColumnName">Name of the column to convert images to grayscale from.
-        /// This estimator operates only on <see cref="Microsoft.ML.Data.Imager"/>.</param>
+        /// This estimator operates only on <see cref="Microsoft.ML.Data.MLImage"/>.</param>
         /// <example>
         /// <format type="text/markdown">
         /// <![CDATA[
@@ -37,7 +37,7 @@ public static ImageGrayscalingEstimator ConvertToGrayscale(this TransformsCatalo
         /// to grayscale images in a new column: <see cref="InputOutputColumnPair.OutputColumnName" />.
         ///</summary>
         /// <param name="catalog">The transform's catalog.</param>
-        /// <param name="columns">The pairs of input and output columns. This estimator operates only on <see cref="Microsoft.ML.Data.Imager"/>.</param>
+        /// <param name="columns">The pairs of input and output columns. This estimator operates only on <see cref="Microsoft.ML.Data.MLImage"/>.</param>
         /// <example>
         /// <format type="text/markdown">
         /// <![CDATA[
@@ -58,7 +58,7 @@ internal static ImageGrayscalingEstimator ConvertToGrayscale(this TransformsCata
         /// </summary>
         /// <param name="catalog">The transform's catalog.</param>
         /// <param name="outputColumnName">Name of the column resulting from the transformation of <paramref name="inputColumnName"/>.
-        /// This column's data type will be <see cref="Microsoft.ML.Data.Imager"/>.</param>
+        /// This column's data type will be <see cref="Microsoft.ML.Data.MLImage"/>.</param>
         /// <param name="inputColumnName">Name of the column with paths to the images to load.
         /// This estimator operates over text data.</param>
         /// <param name="imageFolder">Folder where to look for images.</param>
@@ -98,7 +98,7 @@ public static ImageLoadingEstimator LoadRawImageBytes(this TransformsCatalog cat
         /// <param name="outputColumnName">Name of the column resulting from the transformation of <paramref name="inputColumnName"/>.
         /// This column's data type will be a known-sized vector of <see cref="System.Single"/> or <see cref="System.Byte"/> depending on <paramref name="outputAsFloatArray"/>.</param>
         /// <param name="inputColumnName">Name of the column with images.
-        /// This estimator operates over <see cref="Microsoft.ML.Data.Imager"/>.</param>
+        /// This estimator operates over <see cref="Microsoft.ML.Data.MLImage"/>.</param>
         /// <param name="colorsToExtract">The colors to extract from the image.</param>
         /// <param name="orderOfExtraction">The order in which to extract colors from pixel.</param>
         /// <param name="interleavePixelColors">Whether to interleave the pixels colors, meaning keep them in the <paramref name="orderOfExtraction"/> order, or leave them in the planner form:
@@ -142,7 +142,7 @@ internal static ImagePixelExtractingEstimator ExtractPixels(this TransformsCatal
         /// <param name="outputColumnName">Name of the column resulting from the transformation of <paramref name="inputColumnName"/>.
         /// This column's data type will be the same as that of the input column.</param>
         /// <param name="inputColumnName">Name of the column with images.
-        /// This estimator operates over <see cref="Microsoft.ML.Data.Imager"/>.</param>
+        /// This estimator operates over <see cref="Microsoft.ML.Data.MLImage"/>.</param>
         /// <param name="imageWidth">The transformed image width.</param>
         /// <param name="imageHeight">The transformed image height.</param>
         /// <param name="resizing"> The type of image resizing as specified in <see cref="ImageResizingEstimator.ResizingKind"/>.</param>
@@ -202,7 +202,7 @@ internal static VectorToImageConvertingEstimator ConvertToImage(this TransformsC
         /// <param name="imageHeight">The height of the output images.</param>
         /// <param name="imageWidth">The width of the output images.</param>
         /// <param name="outputColumnName">Name of the column resulting from the transformation of <paramref name="inputColumnName"/>.
-        /// This column's data type will be <see cref="Microsoft.ML.Data.Imager"/>.</param>
+        /// This column's data type will be <see cref="Microsoft.ML.Data.MLImage"/>.</param>
         /// <param name="inputColumnName">Name of the column with data to be converted to image.
         /// This estimator operates over known-sized vector of <see cref="System.Single"/>, <see cref="System.Double"/> and <see cref="System.Byte"/>.</param>
         /// <param name="colorsPresent">Specifies which <see cref="ImagePixelExtractingEstimator.ColorBits"/> are in present the input pixel vectors. The order of colors is specified in <paramref name="orderOfColors"/>.</param>
diff --git a/src/Microsoft.ML.ImageAnalytics/ImageGrayscale.cs b/src/Microsoft.ML.ImageAnalytics/ImageGrayscale.cs
index 267b529f07..a6471040b6 100644
--- a/src/Microsoft.ML.ImageAnalytics/ImageGrayscale.cs
+++ b/src/Microsoft.ML.ImageAnalytics/ImageGrayscale.cs
@@ -164,8 +164,8 @@ protected override Delegate MakeGetter(DataViewRow input, int iinfo, Func<int, b
                 Contracts.AssertValue(input);
                 Contracts.Assert(0 <= iinfo && iinfo < _parent.ColumnPairs.Length);
 
-                var src = default(Imager);
-                var getSrc = input.GetGetter<Imager>(input.Schema[ColMapNewToOld[iinfo]]);
+                var src = default(MLImage);
+                var getSrc = input.GetGetter<MLImage>(input.Schema[ColMapNewToOld[iinfo]]);
 
                 disposer =
                     () =>
@@ -177,8 +177,8 @@ protected override Delegate MakeGetter(DataViewRow input, int iinfo, Func<int, b
                         }
                     };
 
-                ValueGetter<Imager> del =
-                    (ref Imager dst) =>
+                ValueGetter<MLImage> del =
+                    (ref MLImage dst) =>
                     {
                         if (dst != null)
                             dst.Dispose();
@@ -207,8 +207,8 @@ protected override Delegate MakeGetter(DataViewRow input, int iinfo, Func<int, b
     /// |  |  |
     /// | -- | -- |
     /// | Does this estimator need to look at the data to train its parameters? | No |
-    /// | Input column data type | <xref:Microsoft.ML.Data.Imager> |
-    /// | Output column data type | <xref:Microsoft.ML.Data.Imager> |
+    /// | Input column data type | <xref:Microsoft.ML.Data.MLImage> |
+    /// | Output column data type | <xref:Microsoft.ML.Data.MLImage> |
     /// | Required NuGet in addition to Microsoft.ML | Microsoft.ML.ImageAnalytics |
     /// | Exportable to ONNX | No |
     ///
diff --git a/src/Microsoft.ML.ImageAnalytics/ImageLoader.cs b/src/Microsoft.ML.ImageAnalytics/ImageLoader.cs
index e28be22ac1..328d2e8e77 100644
--- a/src/Microsoft.ML.ImageAnalytics/ImageLoader.cs
+++ b/src/Microsoft.ML.ImageAnalytics/ImageLoader.cs
@@ -217,7 +217,7 @@ private Delegate MakeGetterImageDataViewType(DataViewRow input, int iinfo, Func<
             {
                 Contracts.AssertValue(input);
                 Contracts.Assert(0 <= iinfo && iinfo < _parent.ColumnPairs.Length);
-                var lastImage = default(Imager);
+                var lastImage = default(MLImage);
 
                 disposer = () =>
                 {
@@ -230,8 +230,8 @@ private Delegate MakeGetterImageDataViewType(DataViewRow input, int iinfo, Func<
 
                 var getSrc = input.GetGetter<ReadOnlyMemory<char>>(input.Schema[ColMapNewToOld[iinfo]]);
                 ReadOnlyMemory<char> src = default;
-                ValueGetter<Imager> del =
-                    (ref Imager dst) =>
+                ValueGetter<MLImage> del =
+                    (ref MLImage dst) =>
                     {
                         if (dst != null)
                         {
@@ -250,7 +250,7 @@ private Delegate MakeGetterImageDataViewType(DataViewRow input, int iinfo, Func<
                             // to avoid locking file, use the construct below to load the image
                             var bytes = File.ReadAllBytes(path);
                             var ms = new MemoryStream(bytes);
-                            dst = new Imager(ms);
+                            dst = MLImage.CreateFromStream(ms);
                             dst.Tag = path;
                         }
 
@@ -381,14 +381,14 @@ protected override DataViewSchema.DetachedColumn[] GetOutputColumnsCore()
     /// | -- | -- |
     /// | Does this estimator need to look at the data to train its parameters? | No |
     /// | Input column data type | [Text](<xref:Microsoft.ML.Data.TextDataViewType>) |
-    /// | Output column data type | <xref:Microsoft.ML.Data.Imager> |
+    /// | Output column data type | <xref:Microsoft.ML.Data.MLImage> |
     /// | Required NuGet in addition to Microsoft.ML | Microsoft.ML.ImageAnalytics |
     /// | Exportable to ONNX | No |
     ///
     /// The resulting <xref:Microsoft.ML.Data.ImageLoadingTransformer> creates a new column, named as specified in the output column name parameters, and
     /// loads in it images specified in the input column.
     /// Loading is the first step of almost every pipeline that does image processing, and further analysis on images.
-    /// The images to load need to be in the formats supported by <xref:Microsoft.ML.Data.Imager> implementation.
+    /// The images to load need to be in the formats supported by <xref:Microsoft.ML.Data.MLImage> implementation.
     /// For end-to-end image processing pipelines, and scenarios in your applications, see the
     /// [examples](https://github.com/dotnet/machinelearning-samples/tree/main/samples/csharp/getting-started) in the machinelearning-samples github repository.</a>
     ///
diff --git a/src/Microsoft.ML.ImageAnalytics/ImagePixelExtractor.cs b/src/Microsoft.ML.ImageAnalytics/ImagePixelExtractor.cs
index 7b60560d74..82d0c43c6e 100644
--- a/src/Microsoft.ML.ImageAnalytics/ImagePixelExtractor.cs
+++ b/src/Microsoft.ML.ImageAnalytics/ImagePixelExtractor.cs
@@ -320,8 +320,8 @@ private ValueGetter<VBuffer<TValue>> GetGetterCore<TValue>(DataViewRow input, in
                 Contracts.Assert(size == planes * height * width);
                 int cpix = height * width;
 
-                var getSrc = input.GetGetter<Imager>(input.Schema[ColMapNewToOld[iinfo]]);
-                var src = default(Imager);
+                var getSrc = input.GetGetter<MLImage>(input.Schema[ColMapNewToOld[iinfo]]);
+                var src = default(MLImage);
 
                 disposer =
                     () =>
@@ -362,7 +362,13 @@ private ValueGetter<VBuffer<TValue>> GetGetterCore<TValue>(DataViewRow input, in
 
                         ImagePixelExtractingEstimator.GetOrder(ex.OrderOfExtraction, ex.ColorsToExtract, out int a, out int r, out int b, out int g);
 
-                        ReadOnlySpan<byte> pixelData = src.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
+                        ReadOnlySpan<byte> pixelData = src.Pixels;
+                        (int alphaIndex, int redIndex, int greenIndex, int blueIndex) = src.PixelFormat switch
+                        {
+                            MLPixelFormat.Bgra32 => (3, 2, 1, 0),
+                            MLPixelFormat.Rgba32 => (3, 0, 1, 2),
+                            _ => throw new InvalidOperationException($"Image pixel format is not supported")
+                        };
 
                         int h = height;
                         int w = width;
@@ -480,7 +486,7 @@ private VectorDataViewType[] ConstructTypes()
     /// |  |  |
     /// | -- | -- |
     /// | Does this estimator need to look at the data to train its parameters? | No |
-    /// | Input column data type | <xref:Microsoft.ML.Data.Imager> |
+    /// | Input column data type | <xref:Microsoft.ML.Data.MLImage> |
     /// | Output column data type | Known-sized vector of <xref:System.Single> or <xref:System.Byte> |
     /// | Required NuGet in addition to Microsoft.ML | Microsoft.ML.ImageAnalytics |
     /// | Exportable to ONNX | No |
diff --git a/src/Microsoft.ML.ImageAnalytics/ImageResizer.cs b/src/Microsoft.ML.ImageAnalytics/ImageResizer.cs
index 48456f5feb..bd893a56df 100644
--- a/src/Microsoft.ML.ImageAnalytics/ImageResizer.cs
+++ b/src/Microsoft.ML.ImageAnalytics/ImageResizer.cs
@@ -274,8 +274,8 @@ protected override Delegate MakeGetter(DataViewRow input, int iinfo, Func<int, b
                 Contracts.AssertValue(input);
                 Contracts.Assert(0 <= iinfo && iinfo < _parent._columns.Length);
 
-                var src = default(Imager);
-                var getSrc = input.GetGetter<Imager>(input.Schema[ColMapNewToOld[iinfo]]);
+                var src = default(MLImage);
+                var getSrc = input.GetGetter<MLImage>(input.Schema[ColMapNewToOld[iinfo]]);
                 var info = _parent._columns[iinfo];
 
                 disposer =
@@ -287,8 +287,8 @@ protected override Delegate MakeGetter(DataViewRow input, int iinfo, Func<int, b
                         }
                     };
 
-                ValueGetter<Imager> del =
-                    (ref Imager dst) =>
+                ValueGetter<MLImage> del =
+                    (ref MLImage dst) =>
                     {
                         if (dst != null)
                         {
@@ -342,8 +342,8 @@ protected override Delegate MakeGetter(DataViewRow input, int iinfo, Func<int, b
     /// |  |  |
     /// | -- | -- |
     /// | Does this estimator need to look at the data to train its parameters? | No |
-    /// | Input column data type | <xref:Microsoft.ML.Data.Imager> |
-    /// | Output column data type | <xref:Microsoft.ML.Data.Imager> |
+    /// | Input column data type | <xref:Microsoft.ML.Data.MLImage> |
+    /// | Output column data type | <xref:Microsoft.ML.Data.MLImage> |
     /// | Required NuGet in addition to Microsoft.ML | Microsoft.ML.ImageAnalytics |
     /// | Exportable to ONNX | No |
     ///
diff --git a/src/Microsoft.ML.ImageAnalytics/ImageType.cs b/src/Microsoft.ML.ImageAnalytics/ImageType.cs
index 05d2792bae..190f1b8d0f 100644
--- a/src/Microsoft.ML.ImageAnalytics/ImageType.cs
+++ b/src/Microsoft.ML.ImageAnalytics/ImageType.cs
@@ -63,7 +63,7 @@ public override int GetHashCode()
 
         public override void Register()
         {
-            DataViewTypeManager.Register(new ImageDataViewType(Height, Width), typeof(Imager), this);
+            DataViewTypeManager.Register(new ImageDataViewType(Height, Width), typeof(MLImage), this);
         }
     }
 
@@ -73,7 +73,7 @@ public sealed class ImageDataViewType : StructuredDataViewType
         public readonly int Width;
 
         public ImageDataViewType(int height, int width)
-           : base(typeof(Imager))
+           : base(typeof(MLImage))
         {
             Contracts.CheckParam(height > 0, nameof(height), "Must be positive.");
             Contracts.CheckParam(width > 0, nameof(width), " Must be positive.");
@@ -83,7 +83,7 @@ public ImageDataViewType(int height, int width)
             Width = width;
         }
 
-        public ImageDataViewType() : base(typeof(Imager))
+        public ImageDataViewType() : base(typeof(MLImage))
         {
         }
 
diff --git a/src/Microsoft.ML.ImageAnalytics/Imager.cs b/src/Microsoft.ML.ImageAnalytics/MLImage.cs
similarity index 78%
rename from src/Microsoft.ML.ImageAnalytics/Imager.cs
rename to src/Microsoft.ML.ImageAnalytics/MLImage.cs
index fa92cd1bbf..bea879213b 100644
--- a/src/Microsoft.ML.ImageAnalytics/Imager.cs
+++ b/src/Microsoft.ML.ImageAnalytics/MLImage.cs
@@ -15,132 +15,203 @@ namespace Microsoft.ML.Data
     /// <summary>
     /// Provide interfaces for imaging operations.
     /// </summary>
-    public class Imager : IDisposable
+    public class MLImage : IDisposable
     {
         private SKBitmap _image;
 
         /// <summary>
-        /// Create a new imager instance from a stream.
+        /// Create a new MLImage instance from a stream.
         /// </summary>
         /// <param name="imageStream">The stream to create the image from.</param>
-        public Imager(Stream imageStream)
+        /// <returns>MLImage object.</returns>
+        public static MLImage CreateFromStream(Stream imageStream)
         {
             if (imageStream is null)
             {
                 throw new ArgumentNullException(nameof(imageStream));
             }
 
-            _image = SKBitmap.Decode(imageStream);
-            if (_image is null)
+            SKBitmap image = SKBitmap.Decode(imageStream);
+            if (image is null)
             {
                 throw new ArgumentException($"Invalid input stream contents", nameof(imageStream));
             }
 
-            EnsureSupportedPixelFormat();
+            return new MLImage(image);
         }
 
         /// <summary>
-        /// Create a new imager instance from image file.
+        /// Create a new MLImage instance from a stream.
         /// </summary>
         /// <param name="imagePath">The image file path to create the image from.</param>
-        public Imager(string imagePath)
+        /// <returns>MLImage object.</returns>
+        public static MLImage CreateFromFile(string imagePath)
         {
             if (imagePath is null)
             {
                 throw new ArgumentNullException(nameof(imagePath));
             }
 
-            _image = SKBitmap.Decode(imagePath);
-            if (_image is null)
+            SKBitmap image = SKBitmap.Decode(imagePath);
+            if (image is null)
             {
                 throw new ArgumentException($"Invalid path", nameof(imagePath));
             }
 
-            EnsureSupportedPixelFormat();
-        }
-
-        private Imager(SKBitmap image)
-        {
-            _image = image;
-            EnsureSupportedPixelFormat();
-        }
-
-        private void EnsureSupportedPixelFormat()
-        {
-            Debug.Assert(_image is not null);
-
-            // Most of the time SkiaSharp create images with Bgra8888 or Rgba8888 pixel format.
-            if (_image.Info.ColorType != SKColorType.Bgra8888 && _image.Info.ColorType != SKColorType.Rgba8888)
-            {
-                if (!_image.CanCopyTo(SKColorType.Bgra8888))
-                {
-                    throw new InvalidOperationException("Unsupported image format.");
-                }
-
-                SKBitmap image1 = _image.Copy(SKColorType.Bgra8888);
-                _image.Dispose();
-                _image = image1;
-            }
+            return new MLImage(image);
         }
 
         /// <summary>
-        /// Create BRGA32 pixel format imager object from the pixel data buffer span.
+        /// Creates MLImage object from the pixel data span.
         /// </summary>
         /// <param name="width">The width of the image in pixels.</param>
         /// <param name="height">The height of the image in pixels.</param>
+        /// <param name="pixelFormat">The pixel format to create the image with.</param>
         /// <param name="imagePixelData">The pixels data to create the image from.</param>
-        /// <returns>Imager object.</returns>
-        public static unsafe Imager CreateFromBgra32PixelData(int width, int height, Span<byte> imagePixelData)
+        /// <returns>MLImage object.</returns>
+        public static unsafe MLImage CreateFromPixels(int width, int height, MLPixelFormat pixelFormat, ReadOnlySpan<byte> imagePixelData)
         {
+            if (pixelFormat != MLPixelFormat.Bgra32 && pixelFormat != MLPixelFormat.Rgba32)
+            {
+                throw new ArgumentException($"Unsupported pixel format", nameof(pixelFormat));
+            }
+
             if (imagePixelData.Length != width * height * 4)
             {
                 throw new ArgumentException($"Invalid {nameof(imagePixelData)} buffer size.");
             }
 
-            SKBitmap image = new SKBitmap(new SKImageInfo(width, height, SKColorType.Bgra8888));
+            SKBitmap image = new SKBitmap(new SKImageInfo(width, height, pixelFormat == MLPixelFormat.Bgra32 ? SKColorType.Bgra8888 : SKColorType.Rgba8888));
 
             Debug.Assert(image.Info.BitsPerPixel == 32);
             Debug.Assert(image.RowBytes * image.Height == width * height * 4);
 
             imagePixelData.CopyTo(new Span<byte>(image.GetPixels().ToPointer(), image.Width * image.Height * 4));
 
-            return new Imager(image);
+            return new MLImage(image);
+        }
+
+        /// <summary>
+        /// Gets the pixel format for this Image.
+        /// </summary>
+        public MLPixelFormat PixelFormat { get; private set; }
+
+        /// <summary>
+        /// Gets the image pixel data.
+        /// </summary>
+        public ReadOnlySpan<byte> Pixels
+        {
+            get
+            {
+                ThrowInvalidOperationExceptionIfDisposed();
+                Debug.Assert(_image.Info.BytesPerPixel == 4);
+
+                return _image.GetPixelSpan();
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the image tag.
+        /// </summary>
+        public string Tag { get; set; }
+
+        /// <summary>
+        /// Gets the image width in pixels.
+        /// </summary>
+        public int Width
+        {
+            get
+            {
+                ThrowInvalidOperationExceptionIfDisposed();
+                return _image.Width;
+            }
+        }
+
+        /// <summary>
+        /// Gets the image height in pixels.
+        /// </summary>
+        public int Height
+        {
+            get
+            {
+                ThrowInvalidOperationExceptionIfDisposed();
+                return _image.Height;
+            }
         }
 
         /// <summary>
-        /// Gets the image pixel data and how the colors are ordered in the used pixel format.
+        /// Gets how many bits per pixel used by current image object.
         /// </summary>
-        /// <param name="alphaIndex">The index of the alpha in the pixel format.</param>
-        /// <param name="redIndex">The index of the red color in the pixel format.</param>
-        /// <param name="greenIndex">The index of the green color in the pixel format.</param>
-        /// <param name="blueIndex">The index of the blue color in the pixel format.</param>
-        /// <returns>The byte span containing the image pixel data.</returns>
-        public ReadOnlySpan<byte> Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex)
+        public int BitsPerPixel
+        {
+            get
+            {
+                ThrowInvalidOperationExceptionIfDisposed();
+                Debug.Assert(_image.Info.BitsPerPixel == 32);
+                return _image.Info.BitsPerPixel;
+            }
+        }
+
+        /// <summary>
+        /// Save the current image to a file.
+        /// </summary>
+        /// <param name="imagePath">The path of the file to save the image to.</param>
+        /// <remarks>The saved image encoding will be detected from the file extension.</remarks>
+        public void Save(string imagePath)
         {
             ThrowInvalidOperationExceptionIfDisposed();
+            string ext = Path.GetExtension(imagePath);
 
-            if (_image.Info.ColorType == SKColorType.Rgba8888)
+            if (!_extensionToEncodingFormat.TryGetValue(ext, out SKEncodedImageFormat encodingFormat))
             {
-                redIndex = 0;
-                greenIndex = 1;
-                blueIndex = 2;
-                alphaIndex = 3;
+                throw new ArgumentException($"Path with invalid image file extension.", nameof(imagePath));
             }
-            else
+
+            using var stream = new FileStream(imagePath, FileMode.Create, FileAccess.Write);
+            SKData data = _image.Encode(encodingFormat, 100);
+            data.SaveTo(stream);
+        }
+
+        /// <summary>
+        /// Disposes the image.
+        /// </summary>
+        public void Dispose()
+        {
+            if (_image != null)
             {
-                Debug.Assert(_image.Info.ColorType == SKColorType.Bgra8888);
-                blueIndex = 0;
-                greenIndex = 1;
-                redIndex = 2;
-                alphaIndex = 3;
+                _image.Dispose();
+                _image = null;
             }
+        }
 
-            Debug.Assert(_image.Info.BytesPerPixel == 4);
+        private MLImage(SKBitmap image)
+        {
+            _image = image;
 
-            return _image.GetPixelSpan();
+            PixelFormat = _image.Info.ColorType switch
+            {
+                SKColorType.Bgra8888 => MLPixelFormat.Bgra32,
+                SKColorType.Rgba8888 => MLPixelFormat.Rgba32,
+                _ => CloneImageToSupportedFormat()
+            };
         }
 
-        internal Imager CloneWithResizing(int width, int height, ImageResizeMode mode)
+        private MLPixelFormat CloneImageToSupportedFormat()
+        {
+            Debug.Assert(_image.Info.ColorType != SKColorType.Bgra8888 && _image.Info.ColorType != SKColorType.Rgba8888);
+
+            if (!_image.CanCopyTo(SKColorType.Bgra8888))
+            {
+                throw new InvalidOperationException("Unsupported image format.");
+            }
+
+            SKBitmap image1 = _image.Copy(SKColorType.Bgra8888);
+            _image.Dispose();
+            _image = image1;
+            return MLPixelFormat.Bgra32;
+        }
+
+        internal MLImage CloneWithResizing(int width, int height, ImageResizeMode mode)
         {
             ThrowInvalidOperationExceptionIfDisposed();
 
@@ -157,7 +228,7 @@ internal Imager CloneWithResizing(int width, int height, ImageResizeMode mode)
                 throw new InvalidOperationException($"Couldn't resize the image");
             }
 
-            return new Imager(image);
+            return new MLImage(image);
         }
 
         private SKBitmap ResizeFull(int width, int height) => _image.Resize(new SKSizeI(width, height), SKFilterQuality.None);
@@ -262,7 +333,7 @@ private SKBitmap ResizeWithCrop(int width, int height, ImageResizeMode mode)
                                                                             0,    0,     0,     1, 0
                                                                         });
 
-        internal Imager CloneWithGrayscale()
+        internal MLImage CloneWithGrayscale()
         {
             ThrowInvalidOperationExceptionIfDisposed();
 
@@ -276,49 +347,7 @@ internal Imager CloneWithGrayscale()
             SKBitmap destBitmap = new SKBitmap(_image.Width, _image.Height, isOpaque: true);
             using SKCanvas canvas = new SKCanvas(destBitmap);
             canvas.DrawBitmap(_image, 0f, 0f, paint: paint);
-            return new Imager(destBitmap);
-        }
-
-        /// <summary>
-        /// Gets or sets the image tag.
-        /// </summary>
-        public string Tag { get; set; }
-
-        /// <summary>
-        /// Gets the image width in pixels.
-        /// </summary>
-        public int Width
-        {
-            get
-            {
-                ThrowInvalidOperationExceptionIfDisposed();
-                return _image.Width;
-            }
-        }
-
-        /// <summary>
-        /// Gets the image height in pixels.
-        /// </summary>
-        public int Height
-        {
-            get
-            {
-                ThrowInvalidOperationExceptionIfDisposed();
-                return _image.Height;
-            }
-        }
-
-        /// <summary>
-        /// Gets how many bits per pixel used by current image object.
-        /// </summary>
-        public int BitsPerPixel
-        {
-            get
-            {
-                ThrowInvalidOperationExceptionIfDisposed();
-                Debug.Assert(_image.Info.BitsPerPixel == 32);
-                return _image.Info.BitsPerPixel;
-            }
+            return new MLImage(destBitmap);
         }
 
         private static readonly Dictionary<string, SKEncodedImageFormat> _extensionToEncodingFormat = new Dictionary<string, SKEncodedImageFormat>(StringComparer.OrdinalIgnoreCase)
@@ -339,35 +368,6 @@ public int BitsPerPixel
             { ".webp", SKEncodedImageFormat.Webp }
         };
 
-        /// <summary>
-        /// Save the current image to a file.
-        /// </summary>
-        /// <param name="imagePath">The path of the file to save the image to.</param>
-        /// <remarks>The saved image encoding will be detected from the file extension.</remarks>
-        public void Save(string imagePath)
-        {
-            ThrowInvalidOperationExceptionIfDisposed();
-            string ext = Path.GetExtension(imagePath);
-
-            if (!_extensionToEncodingFormat.TryGetValue(ext, out SKEncodedImageFormat encodingFormat))
-            {
-                throw new ArgumentException($"Path with invalid image file extension.", nameof(imagePath));
-            }
-
-            using var stream = new FileStream(imagePath, FileMode.Create, FileAccess.Write);
-            SKData data = _image.Encode(encodingFormat, 100);
-            data.SaveTo(stream);
-        }
-
-        public void Dispose()
-        {
-            if (_image != null)
-            {
-                _image.Dispose();
-                _image = null;
-            }
-        }
-
         private void ThrowInvalidOperationExceptionIfDisposed()
         {
             if (_image is null)
@@ -377,6 +377,27 @@ private void ThrowInvalidOperationExceptionIfDisposed()
         }
     }
 
+    /// <summary>
+    /// The mode to decide how the image should be resized.
+    /// </summary>
+    public enum MLPixelFormat
+    {
+        /// <summary>
+        /// Pads the resized image to fit the bounds of its container.
+        /// </summary>
+        Unknown,
+
+        /// <summary>
+        /// Specifies that the format is 32 bits per pixel; 8 bits each are used for the blue, green, red, and alpha components.
+        /// </summary>
+        Bgra32,
+
+        /// <summary>
+        /// Specifies that the format is 32 bits per pixel; 8 bits each are used for the red, green, blue, and alpha components.
+        /// </summary>
+        Rgba32
+    }
+
     /// <summary>
     /// The mode to decide how the image should be resized.
     /// </summary>
diff --git a/src/Microsoft.ML.ImageAnalytics/VectorToImageTransform.cs b/src/Microsoft.ML.ImageAnalytics/VectorToImageTransform.cs
index addf61d45c..66a19db10d 100644
--- a/src/Microsoft.ML.ImageAnalytics/VectorToImageTransform.cs
+++ b/src/Microsoft.ML.ImageAnalytics/VectorToImageTransform.cs
@@ -341,7 +341,7 @@ protected override Delegate MakeGetter(DataViewRow input, int iinfo, Func<int, b
                     throw Contracts.Except("We only support float, double or byte arrays");
             }
 
-            private ValueGetter<Imager> GetterFromType<TValue>(PrimitiveDataViewType srcType, DataViewRow input, int iinfo,
+            private ValueGetter<MLImage> GetterFromType<TValue>(PrimitiveDataViewType srcType, DataViewRow input, int iinfo,
                 VectorToImageConvertingEstimator.ColumnOptions ex, bool needScale) where TValue : IConvertible
             {
                 Contracts.Assert(typeof(TValue) == srcType.RawType);
@@ -353,7 +353,7 @@ private ValueGetter<Imager> GetterFromType<TValue>(PrimitiveDataViewType srcType
                 float scale = ex.ScaleImage;
 
                 return
-                    (ref Imager dst) =>
+                    (ref MLImage dst) =>
                     {
                         getSrc(ref src);
                         if (src.GetValues().Length == 0)
@@ -415,7 +415,7 @@ private ValueGetter<Imager> GetterFromType<TValue>(PrimitiveDataViewType srcType
                             }
                         }
 
-                        dst = Imager.CreateFromBgra32PixelData(width, height, imageData);
+                        dst = MLImage.CreateFromPixels(width, height, MLPixelFormat.Bgra32, imageData);
                         dst.Tag = nameof(VectorToImageConvertingTransformer);
                     };
             }
@@ -438,7 +438,7 @@ private static ImageDataViewType[] ConstructTypes(VectorToImageConvertingEstimat
     /// | -- | -- |
     /// | Does this estimator need to look at the data to train its parameters? | No |
     /// | Input column data type | Known-sized vector of <xref:System.Single>, <xref:System.Double> or <xref:System.Byte>. |
-    /// | Output column data type | <xref:Microsoft.ML.Data.Imager> |
+    /// | Output column data type | <xref:Microsoft.ML.Data.MLImage> |
     /// | Required NuGet in addition to Microsoft.ML | Microsoft.ML.ImageAnalytics |
     /// | Exportable to ONNX | No |
     ///
diff --git a/test/Microsoft.ML.OnnxTransformerTest/OnnxTransformTests.cs b/test/Microsoft.ML.OnnxTransformerTest/OnnxTransformTests.cs
index 0796aeb9bc..97236cfec3 100644
--- a/test/Microsoft.ML.OnnxTransformerTest/OnnxTransformTests.cs
+++ b/test/Microsoft.ML.OnnxTransformerTest/OnnxTransformTests.cs
@@ -500,7 +500,7 @@ private class ImageDataPoint
             /// Image will be consumed by ONNX image multiclass classification model.
             /// </summary>
             [ImageType(Height, Width)]
-            public Imager Image { get; set; }
+            public MLImage Image { get; set; }
 
             /// <summary>
             /// Output of ONNX model. It contains probabilities of all classes.
@@ -525,7 +525,7 @@ public ImageDataPoint(byte red, byte green, byte blue)
                     imageData[i + 3] = 255;
                 }
 
-                Image = Imager.CreateFromBgra32PixelData(Width, Height, imageData);
+                Image = MLImage.CreateFromPixels(Width, Height, MLPixelFormat.Bgra32, imageData);
             }
         }
 
diff --git a/test/Microsoft.ML.Tests/ImagesTests.cs b/test/Microsoft.ML.Tests/ImagesTests.cs
index c4dbb35e21..5b916a8836 100644
--- a/test/Microsoft.ML.Tests/ImagesTests.cs
+++ b/test/Microsoft.ML.Tests/ImagesTests.cs
@@ -152,8 +152,8 @@ public void TestSaveImages()
             {
                 var pathGetter = cursor.GetGetter<ReadOnlyMemory<char>>(cropped.Schema["ImagePath"]);
                 ReadOnlyMemory<char> path = default;
-                var imageCropGetter = cursor.GetGetter<Imager>(cropped.Schema["ImageCropped"]);
-                Imager image = default;
+                var imageCropGetter = cursor.GetGetter<MLImage>(cropped.Schema["ImageCropped"]);
+                MLImage image = default;
                 while (cursor.MoveNext())
                 {
                     pathGetter(ref path);
@@ -199,14 +199,20 @@ public void TestGrayscaleTransformImages()
             grey.Schema.TryGetColumnIndex("ImageGrey", out int greyColumn);
             using (var cursor = grey.GetRowCursorForAllColumns())
             {
-                var imageGetter = cursor.GetGetter<Imager>(grey.Schema["ImageGrey"]);
-                Imager image = default;
+                var imageGetter = cursor.GetGetter<MLImage>(grey.Schema["ImageGrey"]);
+                MLImage image = default;
                 while (cursor.MoveNext())
                 {
                     imageGetter(ref image);
                     Assert.NotNull(image);
 
-                    ReadOnlySpan<byte> imageData = image.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
+                    ReadOnlySpan<byte> imageData = image.Pixels;
+                    (int alphaIndex, int redIndex, int greenIndex, int blueIndex) = image.PixelFormat switch
+                    {
+                        MLPixelFormat.Bgra32 => (3, 2, 1, 0),
+                        MLPixelFormat.Rgba32 => (3, 0, 1, 2),
+                        _ => throw new InvalidOperationException($"Image pixel format is not supported")
+                    };
                     int pixelSize = image.BitsPerPixel / 8;
 
                     for (int i = 0; i < imageData.Length; i += pixelSize)
@@ -250,7 +256,13 @@ public void TestGrayScaleInMemory()
                 Assert.Equal(image.Width, grayImage.Width);
                 Assert.Equal(image.Height, grayImage.Height);
 
-                ReadOnlySpan<byte> imageData = grayImage.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
+                ReadOnlySpan<byte> imageData = grayImage.Pixels;
+                (int alphaIndex, int redIndex, int greenIndex, int blueIndex) = grayImage.PixelFormat switch
+                {
+                    MLPixelFormat.Bgra32 => (3, 2, 1, 0),
+                    MLPixelFormat.Rgba32 => (3, 0, 1, 2),
+                    _ => throw new InvalidOperationException($"Image pixel format is not supported")
+                };
                 int pixelSize = grayImage.BitsPerPixel / 8;
 
                 for (int i = 0; i < imageData.Length; i += pixelSize)
@@ -267,7 +279,13 @@ public void TestGrayScaleInMemory()
             Assert.Equal(singleImage.Image.Height, transformedSingleImage.GrayImage.Height);
             Assert.Equal(singleImage.Image.Width, transformedSingleImage.GrayImage.Width);
 
-            ReadOnlySpan<byte> imageData1 = transformedSingleImage.GrayImage.Get32bbpImageData(out int alphaIndex1, out int redIndex1, out int greenIndex1, out int blueIndex1);
+            ReadOnlySpan<byte> imageData1 = transformedSingleImage.GrayImage.Pixels;
+            (int alphaIndex1, int redIndex1, int greenIndex1, int blueIndex1) = transformedSingleImage.GrayImage.PixelFormat switch
+            {
+                MLPixelFormat.Bgra32 => (3, 2, 1, 0),
+                MLPixelFormat.Rgba32 => (3, 0, 1, 2),
+                _ => throw new InvalidOperationException($"Image pixel format is not supported")
+            };
             int pixelSize1 = transformedSingleImage.GrayImage.BitsPerPixel / 8;
 
             for (int i = 0; i < imageData1.Length; i += pixelSize1)
@@ -280,10 +298,10 @@ public void TestGrayScaleInMemory()
         private class ImageDataPoint
         {
             [ImageType(10, 10)]
-            public Imager Image { get; set; }
+            public MLImage Image { get; set; }
 
             [ImageType(10, 10)]
-            public Imager GrayImage { get; set; }
+            public MLImage GrayImage { get; set; }
 
             public ImageDataPoint()
             {
@@ -303,7 +321,7 @@ public ImageDataPoint(int width, int height, byte red, byte green, byte blue)
                     imageData[i + 3] = 255;
                 }
 
-                Image = Imager.CreateFromBgra32PixelData(width, height, imageData);
+                Image = MLImage.CreateFromPixels(width, height, MLPixelFormat.Bgra32, imageData);
             }
         }
 
@@ -341,11 +359,11 @@ public void TestBackAndForthConversionWithAlphaInterleave()
 
             using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var imageGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageRestored"]);
-                Imager restoredImage = default;
+                var imageGetter = cursor.GetGetter<MLImage>(backToImages.Schema["ImageRestored"]);
+                MLImage restoredImage = default;
 
-                var imageCropGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageCropped"]);
-                Imager croppedImage = default;
+                var imageCropGetter = cursor.GetGetter<MLImage>(backToImages.Schema["ImageCropped"]);
+                MLImage croppedImage = default;
                 while (cursor.MoveNext())
                 {
                     imageGetter(ref restoredImage);
@@ -353,8 +371,21 @@ public void TestBackAndForthConversionWithAlphaInterleave()
                     imageCropGetter(ref croppedImage);
                     Assert.NotNull(croppedImage);
 
-                    ReadOnlySpan<byte> restoredImageData = restoredImage.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
-                    ReadOnlySpan<byte> croppedImageData = croppedImage.Get32bbpImageData(out int alphaIndex1, out int redIndex1, out int greenIndex1, out int blueIndex1);
+                    ReadOnlySpan<byte> restoredImageData = restoredImage.Pixels;
+                    (int alphaIndex, int redIndex, int greenIndex, int blueIndex) = restoredImage.PixelFormat switch
+                    {
+                        MLPixelFormat.Bgra32 => (3, 2, 1, 0),
+                        MLPixelFormat.Rgba32 => (3, 0, 1, 2),
+                        _ => throw new InvalidOperationException($"Image pixel format is not supported")
+                    };
+
+                    ReadOnlySpan<byte> croppedImageData = croppedImage.Pixels;
+                    (int alphaIndex1, int redIndex1, int greenIndex1, int blueIndex1) = croppedImage.PixelFormat switch
+                    {
+                        MLPixelFormat.Bgra32 => (3, 2, 1, 0),
+                        MLPixelFormat.Rgba32 => (3, 0, 1, 2),
+                        _ => throw new InvalidOperationException($"Image pixel format is not supported")
+                    };
 
                     int pixelSize = restoredImage.BitsPerPixel / 8;
 
@@ -403,11 +434,11 @@ public void TestBackAndForthConversionWithoutAlphaInterleave()
 
             using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var imageGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageRestored"]);
-                Imager restoredImage = default;
+                var imageGetter = cursor.GetGetter<MLImage>(backToImages.Schema["ImageRestored"]);
+                MLImage restoredImage = default;
 
-                var imageCropGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageCropped"]);
-                Imager croppedImage = default;
+                var imageCropGetter = cursor.GetGetter<MLImage>(backToImages.Schema["ImageCropped"]);
+                MLImage croppedImage = default;
                 while (cursor.MoveNext())
                 {
                     imageGetter(ref restoredImage);
@@ -415,8 +446,21 @@ public void TestBackAndForthConversionWithoutAlphaInterleave()
                     imageCropGetter(ref croppedImage);
                     Assert.NotNull(croppedImage);
 
-                    ReadOnlySpan<byte> restoredImageData = restoredImage.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
-                    ReadOnlySpan<byte> croppedImageData = croppedImage.Get32bbpImageData(out int alphaIndex1, out int redIndex1, out int greenIndex1, out int blueIndex1);
+                    ReadOnlySpan<byte> restoredImageData = restoredImage.Pixels;
+                    (int alphaIndex, int redIndex, int greenIndex, int blueIndex) = restoredImage.PixelFormat switch
+                    {
+                        MLPixelFormat.Bgra32 => (3, 2, 1, 0),
+                        MLPixelFormat.Rgba32 => (3, 0, 1, 2),
+                        _ => throw new InvalidOperationException($"Image pixel format is not supported")
+                    };
+
+                    ReadOnlySpan<byte> croppedImageData = croppedImage.Pixels;
+                    (int alphaIndex1, int redIndex1, int greenIndex1, int blueIndex1) = croppedImage.PixelFormat switch
+                    {
+                        MLPixelFormat.Bgra32 => (3, 2, 1, 0),
+                        MLPixelFormat.Rgba32 => (3, 0, 1, 2),
+                        _ => throw new InvalidOperationException($"Image pixel format is not supported")
+                    };
 
                     int pixelSize = restoredImage.BitsPerPixel / 8;
 
@@ -466,11 +510,11 @@ public void TestBackAndForthConversionWithDifferentOrder()
 
             using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var imageGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageRestored"]);
-                Imager restoredImage = default;
+                var imageGetter = cursor.GetGetter<MLImage>(backToImages.Schema["ImageRestored"]);
+                MLImage restoredImage = default;
 
-                var imageCropGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageCropped"]);
-                Imager croppedImage = default;
+                var imageCropGetter = cursor.GetGetter<MLImage>(backToImages.Schema["ImageCropped"]);
+                MLImage croppedImage = default;
                 while (cursor.MoveNext())
                 {
                     imageGetter(ref restoredImage);
@@ -478,8 +522,21 @@ public void TestBackAndForthConversionWithDifferentOrder()
                     imageCropGetter(ref croppedImage);
                     Assert.NotNull(croppedImage);
 
-                    ReadOnlySpan<byte> restoredImageData = restoredImage.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
-                    ReadOnlySpan<byte> croppedImageData = croppedImage.Get32bbpImageData(out int alphaIndex1, out int redIndex1, out int greenIndex1, out int blueIndex1);
+                    ReadOnlySpan<byte> restoredImageData = restoredImage.Pixels;
+                    (int alphaIndex, int redIndex, int greenIndex, int blueIndex) = restoredImage.PixelFormat switch
+                    {
+                        MLPixelFormat.Bgra32 => (3, 2, 1, 0),
+                        MLPixelFormat.Rgba32 => (3, 0, 1, 2),
+                        _ => throw new InvalidOperationException($"Image pixel format is not supported")
+                    };
+
+                    ReadOnlySpan<byte> croppedImageData = croppedImage.Pixels;
+                    (int alphaIndex1, int redIndex1, int greenIndex1, int blueIndex1) = croppedImage.PixelFormat switch
+                    {
+                        MLPixelFormat.Bgra32 => (3, 2, 1, 0),
+                        MLPixelFormat.Rgba32 => (3, 0, 1, 2),
+                        _ => throw new InvalidOperationException($"Image pixel format is not supported")
+                    };
 
                     int pixelSize = restoredImage.BitsPerPixel / 8;
 
@@ -528,11 +585,11 @@ public void TestBackAndForthConversionWithAlphaNoInterleave()
 
             using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var imageGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageRestored"]);
-                Imager restoredImage = default;
+                var imageGetter = cursor.GetGetter<MLImage>(backToImages.Schema["ImageRestored"]);
+                MLImage restoredImage = default;
 
-                var imageCropGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageCropped"]);
-                Imager croppedImage = default;
+                var imageCropGetter = cursor.GetGetter<MLImage>(backToImages.Schema["ImageCropped"]);
+                MLImage croppedImage = default;
                 while (cursor.MoveNext())
                 {
                     imageGetter(ref restoredImage);
@@ -540,8 +597,21 @@ public void TestBackAndForthConversionWithAlphaNoInterleave()
                     imageCropGetter(ref croppedImage);
                     Assert.NotNull(croppedImage);
 
-                    ReadOnlySpan<byte> restoredImageData = restoredImage.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
-                    ReadOnlySpan<byte> croppedImageData = croppedImage.Get32bbpImageData(out int alphaIndex1, out int redIndex1, out int greenIndex1, out int blueIndex1);
+                    ReadOnlySpan<byte> restoredImageData = restoredImage.Pixels;
+                    (int alphaIndex, int redIndex, int greenIndex, int blueIndex) = restoredImage.PixelFormat switch
+                    {
+                        MLPixelFormat.Bgra32 => (3, 2, 1, 0),
+                        MLPixelFormat.Rgba32 => (3, 0, 1, 2),
+                        _ => throw new InvalidOperationException($"Image pixel format is not supported")
+                    };
+
+                    ReadOnlySpan<byte> croppedImageData = croppedImage.Pixels;
+                    (int alphaIndex1, int redIndex1, int greenIndex1, int blueIndex1) = croppedImage.PixelFormat switch
+                    {
+                        MLPixelFormat.Bgra32 => (3, 2, 1, 0),
+                        MLPixelFormat.Rgba32 => (3, 0, 1, 2),
+                        _ => throw new InvalidOperationException($"Image pixel format is not supported")
+                    };
 
                     int pixelSize = restoredImage.BitsPerPixel / 8;
 
@@ -590,11 +660,11 @@ public void TestBackAndForthConversionWithoutAlphaNoInterleave()
 
             using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var imageGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageRestored"]);
-                Imager restoredImage = default;
+                var imageGetter = cursor.GetGetter<MLImage>(backToImages.Schema["ImageRestored"]);
+                MLImage restoredImage = default;
 
-                var imageCropGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageCropped"]);
-                Imager croppedImage = default;
+                var imageCropGetter = cursor.GetGetter<MLImage>(backToImages.Schema["ImageCropped"]);
+                MLImage croppedImage = default;
                 while (cursor.MoveNext())
                 {
                     imageGetter(ref restoredImage);
@@ -602,8 +672,21 @@ public void TestBackAndForthConversionWithoutAlphaNoInterleave()
                     imageCropGetter(ref croppedImage);
                     Assert.NotNull(croppedImage);
 
-                    ReadOnlySpan<byte> restoredImageData = restoredImage.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
-                    ReadOnlySpan<byte> croppedImageData = croppedImage.Get32bbpImageData(out int alphaIndex1, out int redIndex1, out int greenIndex1, out int blueIndex1);
+                    ReadOnlySpan<byte> restoredImageData = restoredImage.Pixels;
+                    (int alphaIndex, int redIndex, int greenIndex, int blueIndex) = restoredImage.PixelFormat switch
+                    {
+                        MLPixelFormat.Bgra32 => (3, 2, 1, 0),
+                        MLPixelFormat.Rgba32 => (3, 0, 1, 2),
+                        _ => throw new InvalidOperationException($"Image pixel format is not supported")
+                    };
+
+                    ReadOnlySpan<byte> croppedImageData = croppedImage.Pixels;
+                    (int alphaIndex1, int redIndex1, int greenIndex1, int blueIndex1) = croppedImage.PixelFormat switch
+                    {
+                        MLPixelFormat.Bgra32 => (3, 2, 1, 0),
+                        MLPixelFormat.Rgba32 => (3, 0, 1, 2),
+                        _ => throw new InvalidOperationException($"Image pixel format is not supported")
+                    };
 
                     int pixelSize = restoredImage.BitsPerPixel / 8;
 
@@ -653,11 +736,11 @@ public void TestBackAndForthConversionWithAlphaInterleaveNoOffset()
 
             using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var imageGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageRestored"]);
-                Imager restoredImage = default;
+                var imageGetter = cursor.GetGetter<MLImage>(backToImages.Schema["ImageRestored"]);
+                MLImage restoredImage = default;
 
-                var imageCropGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageCropped"]);
-                Imager croppedImage = default;
+                var imageCropGetter = cursor.GetGetter<MLImage>(backToImages.Schema["ImageCropped"]);
+                MLImage croppedImage = default;
                 while (cursor.MoveNext())
                 {
                     imageGetter(ref restoredImage);
@@ -665,8 +748,21 @@ public void TestBackAndForthConversionWithAlphaInterleaveNoOffset()
                     imageCropGetter(ref croppedImage);
                     Assert.NotNull(croppedImage);
 
-                    ReadOnlySpan<byte> restoredImageData = restoredImage.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
-                    ReadOnlySpan<byte> croppedImageData = croppedImage.Get32bbpImageData(out int alphaIndex1, out int redIndex1, out int greenIndex1, out int blueIndex1);
+                    ReadOnlySpan<byte> restoredImageData = restoredImage.Pixels;
+                    (int alphaIndex, int redIndex, int greenIndex, int blueIndex) = restoredImage.PixelFormat switch
+                    {
+                        MLPixelFormat.Bgra32 => (3, 2, 1, 0),
+                        MLPixelFormat.Rgba32 => (3, 0, 1, 2),
+                        _ => throw new InvalidOperationException($"Image pixel format is not supported")
+                    };
+
+                    ReadOnlySpan<byte> croppedImageData = croppedImage.Pixels;
+                    (int alphaIndex1, int redIndex1, int greenIndex1, int blueIndex1) = croppedImage.PixelFormat switch
+                    {
+                        MLPixelFormat.Bgra32 => (3, 2, 1, 0),
+                        MLPixelFormat.Rgba32 => (3, 0, 1, 2),
+                        _ => throw new InvalidOperationException($"Image pixel format is not supported")
+                    };
 
                     int pixelSize = restoredImage.BitsPerPixel / 8;
 
@@ -715,11 +811,11 @@ public void TestBackAndForthConversionWithoutAlphaInterleaveNoOffset()
 
             using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var imageGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageRestored"]);
-                Imager restoredImage = default;
+                var imageGetter = cursor.GetGetter<MLImage>(backToImages.Schema["ImageRestored"]);
+                MLImage restoredImage = default;
 
-                var imageCropGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageCropped"]);
-                Imager croppedImage = default;
+                var imageCropGetter = cursor.GetGetter<MLImage>(backToImages.Schema["ImageCropped"]);
+                MLImage croppedImage = default;
                 while (cursor.MoveNext())
                 {
                     imageGetter(ref restoredImage);
@@ -727,8 +823,21 @@ public void TestBackAndForthConversionWithoutAlphaInterleaveNoOffset()
                     imageCropGetter(ref croppedImage);
                     Assert.NotNull(croppedImage);
 
-                    ReadOnlySpan<byte> restoredImageData = restoredImage.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
-                    ReadOnlySpan<byte> croppedImageData = croppedImage.Get32bbpImageData(out int alphaIndex1, out int redIndex1, out int greenIndex1, out int blueIndex1);
+                    ReadOnlySpan<byte> restoredImageData = restoredImage.Pixels;
+                    (int alphaIndex, int redIndex, int greenIndex, int blueIndex) = restoredImage.PixelFormat switch
+                    {
+                        MLPixelFormat.Bgra32 => (3, 2, 1, 0),
+                        MLPixelFormat.Rgba32 => (3, 0, 1, 2),
+                        _ => throw new InvalidOperationException($"Image pixel format is not supported")
+                    };
+
+                    ReadOnlySpan<byte> croppedImageData = croppedImage.Pixels;
+                    (int alphaIndex1, int redIndex1, int greenIndex1, int blueIndex1) = croppedImage.PixelFormat switch
+                    {
+                        MLPixelFormat.Bgra32 => (3, 2, 1, 0),
+                        MLPixelFormat.Rgba32 => (3, 0, 1, 2),
+                        _ => throw new InvalidOperationException($"Image pixel format is not supported")
+                    };
 
                     int pixelSize = restoredImage.BitsPerPixel / 8;
 
@@ -778,11 +887,11 @@ public void TestBackAndForthConversionWithAlphaNoInterleaveNoOffset()
 
             using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var imageGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageRestored"]);
-                Imager restoredImage = default;
+                var imageGetter = cursor.GetGetter<MLImage>(backToImages.Schema["ImageRestored"]);
+                MLImage restoredImage = default;
 
-                var imageCropGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageCropped"]);
-                Imager croppedImage = default;
+                var imageCropGetter = cursor.GetGetter<MLImage>(backToImages.Schema["ImageCropped"]);
+                MLImage croppedImage = default;
                 while (cursor.MoveNext())
                 {
                     imageGetter(ref restoredImage);
@@ -790,8 +899,21 @@ public void TestBackAndForthConversionWithAlphaNoInterleaveNoOffset()
                     imageCropGetter(ref croppedImage);
                     Assert.NotNull(croppedImage);
 
-                    ReadOnlySpan<byte> restoredImageData = restoredImage.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
-                    ReadOnlySpan<byte> croppedImageData = croppedImage.Get32bbpImageData(out int alphaIndex1, out int redIndex1, out int greenIndex1, out int blueIndex1);
+                    ReadOnlySpan<byte> restoredImageData = restoredImage.Pixels;
+                    (int alphaIndex, int redIndex, int greenIndex, int blueIndex) = restoredImage.PixelFormat switch
+                    {
+                        MLPixelFormat.Bgra32 => (3, 2, 1, 0),
+                        MLPixelFormat.Rgba32 => (3, 0, 1, 2),
+                        _ => throw new InvalidOperationException($"Image pixel format is not supported")
+                    };
+
+                    ReadOnlySpan<byte> croppedImageData = croppedImage.Pixels;
+                    (int alphaIndex1, int redIndex1, int greenIndex1, int blueIndex1) = croppedImage.PixelFormat switch
+                    {
+                        MLPixelFormat.Bgra32 => (3, 2, 1, 0),
+                        MLPixelFormat.Rgba32 => (3, 0, 1, 2),
+                        _ => throw new InvalidOperationException($"Image pixel format is not supported")
+                    };
 
                     int pixelSize = restoredImage.BitsPerPixel / 8;
 
@@ -839,11 +961,11 @@ public void TestBackAndForthConversionWithoutAlphaNoInterleaveNoOffset()
 
             using (var cursor = backToImages.GetRowCursorForAllColumns())
             {
-                var imageGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageRestored"]);
-                Imager restoredImage = default;
+                var imageGetter = cursor.GetGetter<MLImage>(backToImages.Schema["ImageRestored"]);
+                MLImage restoredImage = default;
 
-                var imageCropGetter = cursor.GetGetter<Imager>(backToImages.Schema["ImageCropped"]);
-                Imager croppedImage = default;
+                var imageCropGetter = cursor.GetGetter<MLImage>(backToImages.Schema["ImageCropped"]);
+                MLImage croppedImage = default;
                 while (cursor.MoveNext())
                 {
                     imageGetter(ref restoredImage);
@@ -851,8 +973,21 @@ public void TestBackAndForthConversionWithoutAlphaNoInterleaveNoOffset()
                     imageCropGetter(ref croppedImage);
                     Assert.NotNull(croppedImage);
 
-                    ReadOnlySpan<byte> restoredImageData = restoredImage.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
-                    ReadOnlySpan<byte> croppedImageData = croppedImage.Get32bbpImageData(out int alphaIndex1, out int redIndex1, out int greenIndex1, out int blueIndex1);
+                    ReadOnlySpan<byte> restoredImageData = restoredImage.Pixels;
+                    (int alphaIndex, int redIndex, int greenIndex, int blueIndex) = restoredImage.PixelFormat switch
+                    {
+                        MLPixelFormat.Bgra32 => (3, 2, 1, 0),
+                        MLPixelFormat.Rgba32 => (3, 0, 1, 2),
+                        _ => throw new InvalidOperationException($"Image pixel format is not supported")
+                    };
+
+                    ReadOnlySpan<byte> croppedImageData = croppedImage.Pixels;
+                    (int alphaIndex1, int redIndex1, int greenIndex1, int blueIndex1) = croppedImage.PixelFormat switch
+                    {
+                        MLPixelFormat.Bgra32 => (3, 2, 1, 0),
+                        MLPixelFormat.Rgba32 => (3, 0, 1, 2),
+                        _ => throw new InvalidOperationException($"Image pixel format is not supported")
+                    };
 
                     int pixelSize = restoredImage.BitsPerPixel / 8;
 
@@ -889,9 +1024,15 @@ public void ImageResizerTransformResizingModeFill()
             var rowView = pipe.Preview(data).RowView;
             Assert.Single(rowView);
 
-            using (var image = (Imager)rowView.First().Values.Last().Value)
+            using (var image = (MLImage)rowView.First().Values.Last().Value)
             {
-                ReadOnlySpan<byte> imageData = image.Get32bbpImageData(out int alphaIndex, out int redIndex, out int greenIndex, out int blueIndex);
+                ReadOnlySpan<byte> imageData = image.Pixels;
+                (int alphaIndex, int redIndex, int greenIndex, int blueIndex) = image.PixelFormat switch
+                {
+                    MLPixelFormat.Bgra32 => (3, 2, 1, 0),
+                    MLPixelFormat.Rgba32 => (3, 0, 1, 2),
+                    _ => throw new InvalidOperationException($"Image pixel format is not supported")
+                };
                 int pixelSize = image.BitsPerPixel / 8;
 
                 // these points must be white
@@ -978,7 +1119,7 @@ private class DataPoint
         public class InMemoryImage
         {
             [ImageType(229, 299)]
-            public Imager LoadedImage;
+            public MLImage LoadedImage;
             public string Label;
 
             public static List<InMemoryImage> LoadFromTsv(MLContext mlContext, string tsvPath, string imageFolder)
@@ -1020,17 +1161,13 @@ public static List<InMemoryImage> LoadFromTsv(MLContext mlContext, string tsvPat
 
             }
 
-            private static Imager LoadImageFromFile(string imagePath)
-            {
-                using Stream stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read, FileShare.Read);
-                return new Imager(stream);
-            }
+            private static MLImage LoadImageFromFile(string imagePath) => MLImage.CreateFromFile(imagePath);
         }
 
         public class InMemoryImageOutput : InMemoryImage
         {
             [ImageType(100, 100)]
-            public Imager ResizedImage;
+            public MLImage ResizedImage;
         }
 
         [Fact]
@@ -1048,7 +1185,7 @@ public void ResizeInMemoryImages()
             var model = pipeline.Fit(dataView);
             var resizedDV = model.Transform(dataView);
             var rowView = resizedDV.Preview().RowView;
-            var resizedImage = (Imager)rowView.First().Values.Last().Value;
+            var resizedImage = (MLImage)rowView.First().Values.Last().Value;
             Assert.Equal(100, resizedImage.Height);
             Assert.NotEqual(100, dataObjects[0].LoadedImage.Height);
 

From 3c579b6481fea94963ef9f74c426761138ed620f Mon Sep 17 00:00:00 2001
From: Tarek Mahmoud Sayed <tarekms@microsoft.com>
Date: Wed, 19 Oct 2022 16:43:10 -0700
Subject: [PATCH 5/7] Addressing more feedback

---
 src/Microsoft.ML.ImageAnalytics/MLImage.cs    |  64 ++++++-
 .../OnnxTransformTests.cs                     |   2 +-
 test/Microsoft.ML.Tests/ImagesTests.cs        | 178 ++++++++++++++++++
 3 files changed, 236 insertions(+), 8 deletions(-)

diff --git a/src/Microsoft.ML.ImageAnalytics/MLImage.cs b/src/Microsoft.ML.ImageAnalytics/MLImage.cs
index bea879213b..a7d37582b7 100644
--- a/src/Microsoft.ML.ImageAnalytics/MLImage.cs
+++ b/src/Microsoft.ML.ImageAnalytics/MLImage.cs
@@ -15,9 +15,16 @@ namespace Microsoft.ML.Data
     /// <summary>
     /// Provide interfaces for imaging operations.
     /// </summary>
-    public class MLImage : IDisposable
+    public sealed class MLImage : IDisposable
     {
         private SKBitmap _image;
+        private MLPixelFormat _pixelFormat;
+        private string _tag;
+
+        // disallow instantiating image object from the default constructor
+        private MLImage()
+        {
+        }
 
         /// <summary>
         /// Create a new MLImage instance from a stream.
@@ -76,6 +83,16 @@ public static unsafe MLImage CreateFromPixels(int width, int height, MLPixelForm
                 throw new ArgumentException($"Unsupported pixel format", nameof(pixelFormat));
             }
 
+            if (width <= 0)
+            {
+                throw new ArgumentException($"Invalid width value.", nameof(width));
+            }
+
+            if (height <= 0)
+            {
+                throw new ArgumentException($"Invalid height value.", nameof(height));
+            }
+
             if (imagePixelData.Length != width * height * 4)
             {
                 throw new ArgumentException($"Invalid {nameof(imagePixelData)} buffer size.");
@@ -94,7 +111,20 @@ public static unsafe MLImage CreateFromPixels(int width, int height, MLPixelForm
         /// <summary>
         /// Gets the pixel format for this Image.
         /// </summary>
-        public MLPixelFormat PixelFormat { get; private set; }
+        public MLPixelFormat PixelFormat
+        {
+            get
+            {
+                ThrowInvalidOperationExceptionIfDisposed();
+                return _pixelFormat;
+            }
+
+            private set
+            {
+                Debug.Assert(_image is not null);
+                _pixelFormat = value;
+            }
+        }
 
         /// <summary>
         /// Gets the image pixel data.
@@ -113,7 +143,20 @@ public ReadOnlySpan<byte> Pixels
         /// <summary>
         /// Gets or sets the image tag.
         /// </summary>
-        public string Tag { get; set; }
+        public string Tag
+        {
+            get
+            {
+                ThrowInvalidOperationExceptionIfDisposed();
+                return _tag;
+            }
+
+            set
+            {
+                ThrowInvalidOperationExceptionIfDisposed();
+                _tag = value;
+            }
+        }
 
         /// <summary>
         /// Gets the image width in pixels.
@@ -164,11 +207,16 @@ public void Save(string imagePath)
 
             if (!_extensionToEncodingFormat.TryGetValue(ext, out SKEncodedImageFormat encodingFormat))
             {
-                throw new ArgumentException($"Path with invalid image file extension.", nameof(imagePath));
+                throw new ArgumentException($"Path has invalid image file extension.", nameof(imagePath));
+            }
+
+            using SKData data = _image.Encode(encodingFormat, 100);
+            if (data is null)
+            {
+                throw new ArgumentException($"Saving image with the format '{ext}' is not supported. Try save it with `Jpeg`, `Png`, or `Webp` format.", nameof(imagePath));
             }
 
             using var stream = new FileStream(imagePath, FileMode.Create, FileAccess.Write);
-            SKData data = _image.Encode(encodingFormat, 100);
             data.SaveTo(stream);
         }
 
@@ -378,22 +426,24 @@ private void ThrowInvalidOperationExceptionIfDisposed()
     }
 
     /// <summary>
-    /// The mode to decide how the image should be resized.
+    /// Specifies the format of the color data for each pixel in the image.
     /// </summary>
     public enum MLPixelFormat
     {
         /// <summary>
-        /// Pads the resized image to fit the bounds of its container.
+        /// The pixel format is unknown.
         /// </summary>
         Unknown,
 
         /// <summary>
         /// Specifies that the format is 32 bits per pixel; 8 bits each are used for the blue, green, red, and alpha components.
+        /// The color components are stored in blue, green, red, and alpha order
         /// </summary>
         Bgra32,
 
         /// <summary>
         /// Specifies that the format is 32 bits per pixel; 8 bits each are used for the red, green, blue, and alpha components.
+        /// The color components are stored in red, green, blue, and alpha order
         /// </summary>
         Rgba32
     }
diff --git a/test/Microsoft.ML.OnnxTransformerTest/OnnxTransformTests.cs b/test/Microsoft.ML.OnnxTransformerTest/OnnxTransformTests.cs
index 97236cfec3..e69fffb63a 100644
--- a/test/Microsoft.ML.OnnxTransformerTest/OnnxTransformTests.cs
+++ b/test/Microsoft.ML.OnnxTransformerTest/OnnxTransformTests.cs
@@ -542,7 +542,7 @@ public void OnnxModelInMemoryImage()
             var dataPoints = new ImageDataPoint[]
             {
                 new ImageDataPoint(red: 255, green: 0, blue: 0),
-                new ImageDataPoint(red: 0, green: 255, blue: 0),
+                new ImageDataPoint(red: 0, green: 128, blue: 0),
             };
 
             // Convert training data to IDataView, the general data type used in ML.NET.
diff --git a/test/Microsoft.ML.Tests/ImagesTests.cs b/test/Microsoft.ML.Tests/ImagesTests.cs
index 5b916a8836..68973381d3 100644
--- a/test/Microsoft.ML.Tests/ImagesTests.cs
+++ b/test/Microsoft.ML.Tests/ImagesTests.cs
@@ -4,6 +4,7 @@
 
 using System;
 using System.Collections.Generic;
+using System.Globalization;
 using System.IO;
 using System.Linq;
 using System.Runtime.InteropServices;
@@ -1215,5 +1216,182 @@ public void ResizeInMemoryImages()
 
             Assert.False(disposed, "The last in memory image had been disposed by running ResizeImageTransformer");
         }
+
+        public static IEnumerable<object[]> ImageListData()
+        {
+            yield return new object[] { "tomato.bmp" };
+            yield return new object[] { "hotdog.jpg" };
+            yield return new object[] { "banana.jpg" };
+            yield return new object[] { "tomato.jpg" };
+        }
+
+        [Theory]
+        [MemberData(nameof(ImageListData))]
+        public void MLImageCreationTests(string imageName)
+        {
+            var dataFile = GetDataPath($"images/{imageName}");
+
+            using MLImage image1 = MLImage.CreateFromFile(dataFile);
+            using FileStream imageStream = new FileStream(dataFile, FileMode.Open, FileAccess.Read);
+            using MLImage image2 = MLImage.CreateFromStream(imageStream);
+
+            Assert.Equal(image1.Tag, image2.Tag);
+            Assert.Equal(image1.Width, image2.Width);
+            Assert.Equal(image1.Height, image2.Height);
+            Assert.Equal(32, image1.BitsPerPixel);
+            Assert.Equal(image1.BitsPerPixel, image2.BitsPerPixel);
+            Assert.Equal(image1.PixelFormat, image2.PixelFormat);
+            Assert.Equal(image1.Pixels.ToArray(), image2.Pixels.ToArray());
+            Assert.Equal(image1.Width * image1.Height * (image1.BitsPerPixel / 8), image1.Pixels.Length);
+            Assert.True(image1.PixelFormat == MLPixelFormat.Rgba32 || image1.PixelFormat == MLPixelFormat.Bgra32);
+
+            image1.Tag = "image1";
+            Assert.Equal("image1", image1.Tag);
+            image2.Tag = "image2";
+            Assert.Equal("image2", image2.Tag);
+
+            using MLImage image3 = MLImage.CreateFromPixels(image1.Width, image1.Height, image1.PixelFormat, image1.Pixels);
+            Assert.Equal(image1.Width, image3.Width);
+            Assert.Equal(image1.Height, image3.Height);
+            Assert.Equal(image1.BitsPerPixel, image3.BitsPerPixel);
+            Assert.Equal(image1.PixelFormat, image3.PixelFormat);
+            Assert.Equal(image1.Pixels.ToArray(), image3.Pixels.ToArray());
+        }
+
+        [Fact]
+        public void MLImageCreateThrowingTest()
+        {
+            Assert.Throws<ArgumentNullException>(() => MLImage.CreateFromFile(null));
+            Assert.Throws<ArgumentException>(() => MLImage.CreateFromFile("This is Invalid Path"));
+            Assert.Throws<ArgumentNullException>(() => MLImage.CreateFromStream(null));
+            Assert.Throws<ArgumentException>(() => MLImage.CreateFromStream(new MemoryStream(new byte[10])));
+            Assert.Throws<ArgumentException>(() => MLImage.CreateFromPixels(10, 10, MLPixelFormat.Unknown, Array.Empty<byte>()));
+            Assert.Throws<ArgumentException>(() => MLImage.CreateFromPixels(10, 10, MLPixelFormat.Bgra32, Array.Empty<byte>()));
+            Assert.Throws<ArgumentException>(() => MLImage.CreateFromPixels(0, 10, MLPixelFormat.Bgra32, new byte[10]));
+            Assert.Throws<ArgumentException>(() => MLImage.CreateFromPixels(10, 0, MLPixelFormat.Bgra32, new byte[10]));
+            Assert.Throws<ArgumentException>(() => MLImage.CreateFromPixels(10, 10, MLPixelFormat.Bgra32, new byte[401]));
+        }
+
+        [Theory]
+        [MemberData(nameof(ImageListData))]
+        public void MLImageSaveTests(string imageName)
+        {
+            var dataFile = GetDataPath($"images/{imageName}");
+            using MLImage image1 = MLImage.CreateFromFile(dataFile);
+            string extension = Path.GetExtension(imageName);
+            string imageTempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + extension);
+
+            if (extension.Equals(".jpeg", StringComparison.OrdinalIgnoreCase) ||
+                extension.Equals(".jpg", StringComparison.OrdinalIgnoreCase) ||
+                extension.Equals(".png", StringComparison.OrdinalIgnoreCase) ||
+                extension.Equals(".webp", StringComparison.OrdinalIgnoreCase))
+            {
+                image1.Save(imageTempPath);
+                using MLImage image2 = MLImage.CreateFromFile(imageTempPath);
+
+                Assert.Equal(image1.Width, image2.Width);
+                Assert.Equal(image1.Height, image2.Height);
+                Assert.Equal(image1.BitsPerPixel, image2.BitsPerPixel);
+                Assert.Equal(image1.PixelFormat, image2.PixelFormat);
+
+                // When saving the image with specific encoding, the image decoder can manipulate the color
+                // and don't have to keep the exact original colors. 
+            }
+            else
+            {
+                Assert.Throws<ArgumentException>(() => image1.Save(imageTempPath));
+            }
+        }
+
+        [Fact]
+        public void MLImageDisposingTest()
+        {
+            MLImage image = MLImage.CreateFromPixels(10, 10, MLPixelFormat.Bgra32, new byte[10 * 10 * 4]);
+            image.Tag = "Blank";
+
+            Assert.Equal(10, image.Width);
+            Assert.Equal(10, image.Height);
+            Assert.Equal(32, image.BitsPerPixel);
+            Assert.Equal(MLPixelFormat.Bgra32, image.PixelFormat);
+
+            image.Dispose();
+
+            Assert.Throws<InvalidOperationException>(() => image.Tag);
+            Assert.Throws<InvalidOperationException>(() => image.Tag = "Something");
+            Assert.Throws<InvalidOperationException>(() => image.Width);
+            Assert.Throws<InvalidOperationException>(() => image.Height);
+            Assert.Throws<InvalidOperationException>(() => image.PixelFormat);
+            Assert.Throws<InvalidOperationException>(() => image.BitsPerPixel);
+            Assert.Throws<InvalidOperationException>(() => image.Pixels[0]);
+        }
+
+        [Theory]
+        [MemberData(nameof(ImageListData))]
+        public void MLImageSourceDisposingTest(string imageName)
+        {
+            var imageFile = GetDataPath($"images/{imageName}");
+            using MLImage image1 = MLImage.CreateFromFile(imageFile);
+
+            // Create image from stream then close the stream and then try to access the image data
+            FileStream stream = new FileStream(imageFile, FileMode.Open, FileAccess.Read, FileShare.None);
+            MLImage image2 = MLImage.CreateFromStream(stream);
+            stream.Dispose();
+            Assert.Equal(image1.Pixels.ToArray(), image2.Pixels.ToArray());
+            image2.Dispose();
+
+            // Create image from non-seekable stream
+            stream = new FileStream(imageFile, FileMode.Open, FileAccess.Read, FileShare.None);
+            NonSeekableStream nonSeekableStream = new NonSeekableStream(stream);
+            image2 = MLImage.CreateFromStream(nonSeekableStream);
+            Assert.Equal(image1.Pixels.ToArray(), image2.Pixels.ToArray());
+            stream.Close();
+            Assert.Equal(image1.Pixels.ToArray(), image2.Pixels.ToArray());
+            image2.Dispose();
+
+            // Now test image stream starts with image data and appended with extra unrelated data.
+            stream = new FileStream(imageFile, FileMode.Open, FileAccess.Read, FileShare.None);
+            MemoryStream ms = new MemoryStream((int)stream.Length);
+            stream.CopyTo(ms);
+            for (int i = 0; i < stream.Length; i++)
+            {
+                ms.WriteByte((byte)(i % 255));
+            }
+
+            ms.Seek(0, SeekOrigin.Begin);
+            image2 = MLImage.CreateFromStream(ms);
+            stream.Close();
+            ms.Close();
+            Assert.Equal(image1.Width, image2.Width);
+            Assert.Equal(image1.Height, image2.Height);
+            Assert.Equal(image1.Pixels.ToArray(), image2.Pixels.ToArray());
+            image2.Dispose();
+        }
+
+        private class NonSeekableStream : Stream
+        {
+            private Stream _stream;
+
+            public NonSeekableStream(Stream stream) => _stream = stream;
+
+            public override bool CanRead => _stream.CanRead;
+
+            public override bool CanSeek => false;
+
+            public override bool CanWrite => _stream.CanWrite;
+
+            public override long Length => _stream.Length;
+
+            public override long Position { get => _stream.Position; set => throw new InvalidOperationException($"The stream is not seekable"); }
+
+            public override void Flush() => _stream.Flush();
+
+            public override int Read(byte[] buffer, int offset, int count) => _stream.Read(buffer, offset, count);
+
+            public override long Seek(long offset, SeekOrigin origin) => throw new InvalidOperationException($"The stream is not seekable");
+
+            public override void SetLength(long value) => throw new InvalidOperationException($"The stream is not seekable");
+
+            public override void Write(byte[] buffer, int offset, int count) => _stream.Write(buffer, offset, count);
+        }
     }
 }

From ce4622c73d7dfdc88300ea18b1d81defc38714fe Mon Sep 17 00:00:00 2001
From: Tarek Mahmoud Sayed <tarekms@microsoft.com>
Date: Wed, 19 Oct 2022 16:55:07 -0700
Subject: [PATCH 6/7] Small test modification

---
 test/Microsoft.ML.Tests/ImagesTests.cs | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/test/Microsoft.ML.Tests/ImagesTests.cs b/test/Microsoft.ML.Tests/ImagesTests.cs
index 68973381d3..60505755ee 100644
--- a/test/Microsoft.ML.Tests/ImagesTests.cs
+++ b/test/Microsoft.ML.Tests/ImagesTests.cs
@@ -1341,7 +1341,7 @@ public void MLImageSourceDisposingTest(string imageName)
 
             // Create image from non-seekable stream
             stream = new FileStream(imageFile, FileMode.Open, FileAccess.Read, FileShare.None);
-            NonSeekableStream nonSeekableStream = new NonSeekableStream(stream);
+            ReadOnlyNonSeekableStream nonSeekableStream = new ReadOnlyNonSeekableStream(stream);
             image2 = MLImage.CreateFromStream(nonSeekableStream);
             Assert.Equal(image1.Pixels.ToArray(), image2.Pixels.ToArray());
             stream.Close();
@@ -1367,17 +1367,17 @@ public void MLImageSourceDisposingTest(string imageName)
             image2.Dispose();
         }
 
-        private class NonSeekableStream : Stream
+        private class ReadOnlyNonSeekableStream : Stream
         {
             private Stream _stream;
 
-            public NonSeekableStream(Stream stream) => _stream = stream;
+            public ReadOnlyNonSeekableStream(Stream stream) => _stream = stream;
 
             public override bool CanRead => _stream.CanRead;
 
             public override bool CanSeek => false;
 
-            public override bool CanWrite => _stream.CanWrite;
+            public override bool CanWrite => false;
 
             public override long Length => _stream.Length;
 
@@ -1391,7 +1391,7 @@ private class NonSeekableStream : Stream
 
             public override void SetLength(long value) => throw new InvalidOperationException($"The stream is not seekable");
 
-            public override void Write(byte[] buffer, int offset, int count) => _stream.Write(buffer, offset, count);
+            public override void Write(byte[] buffer, int offset, int count) => throw new InvalidOperationException($"The stream is not writable");
         }
     }
 }

From 152e065b024ea8bb112f8ad0aa641b02a952c39b Mon Sep 17 00:00:00 2001
From: Tarek Mahmoud Sayed <tarekms@microsoft.com>
Date: Wed, 19 Oct 2022 17:04:47 -0700
Subject: [PATCH 7/7] more test

---
 test/Microsoft.ML.Tests/ImagesTests.cs | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/test/Microsoft.ML.Tests/ImagesTests.cs b/test/Microsoft.ML.Tests/ImagesTests.cs
index 60505755ee..757bdb563a 100644
--- a/test/Microsoft.ML.Tests/ImagesTests.cs
+++ b/test/Microsoft.ML.Tests/ImagesTests.cs
@@ -1295,7 +1295,7 @@ public void MLImageSaveTests(string imageName)
                 Assert.Equal(image1.PixelFormat, image2.PixelFormat);
 
                 // When saving the image with specific encoding, the image decoder can manipulate the color
-                // and don't have to keep the exact original colors. 
+                // and don't have to keep the exact original colors.
             }
             else
             {
@@ -1348,16 +1348,23 @@ public void MLImageSourceDisposingTest(string imageName)
             Assert.Equal(image1.Pixels.ToArray(), image2.Pixels.ToArray());
             image2.Dispose();
 
-            // Now test image stream starts with image data and appended with extra unrelated data.
+            // Now test image stream contains image data prepended and appended with extra unrelated data.
             stream = new FileStream(imageFile, FileMode.Open, FileAccess.Read, FileShare.None);
             MemoryStream ms = new MemoryStream((int)stream.Length);
+            for (int i = 0; i < stream.Length; i++)
+            {
+                ms.WriteByte((byte)(i % 255));
+            }
+
+            long position = ms.Position;
+
             stream.CopyTo(ms);
             for (int i = 0; i < stream.Length; i++)
             {
                 ms.WriteByte((byte)(i % 255));
             }
 
-            ms.Seek(0, SeekOrigin.Begin);
+            ms.Seek(position, SeekOrigin.Begin);
             image2 = MLImage.CreateFromStream(ms);
             stream.Close();
             ms.Close();