Table of Contents

Migrating from System.Drawing to Egami

Egami.SystemDrawing runs System.Drawing code unchanged; this guide moves that code to Egami's own API one piece at a time, with the four escape hatches that join the two: Bitmap.AsRaster, Bitmap.FromRaster, Graphics.Canvas and Font.ToEgamiFont. At every step the program builds, runs and gives the same pictures, so the move can stop anywhere.

Step 0: the drop-in

Replace the System.Drawing.Common package reference with Egami.SystemDrawing, as the drop-in guide describes: System.Drawing and Egami.SystemDrawing come in as implicit usings, and files that name System.Drawing.Imaging, System.Drawing.Drawing2D or System.Drawing.Text change those lines to Egami.SystemDrawing.Imaging, .Drawing2D and .Text. Members that need Windows handles compile with a warning, EGSD0001 to EGSD0004, and throw when called: each warning marks code that has to change anyway (an HBITMAP becomes LockBits or AsRaster, CopyFromScreen has no replacement, an Icon comes from a file). The drop-in guide's list names every member.

The strategy

Keep Bitmap, Image and Graphics at the edges of the program, where they meet other code and public APIs, and move the hot spots first: pixel loops, resizing, filters, batch encoding. An escape hatch turns a bitmap into an Egami raster for one part of a routine and back for the rest, so each change is small and tested on its own. When no Bitmap crosses a boundary any more, drop the package.

Pixels: Bitmap.AsRaster

AsRaster gives a bitmap's pixels as a Raster<TPixel> without copying them for 32-bit ARGB (Raster<Bgra8>), premultiplied ARGB (Raster<PremulBgra8>), 24-bit RGB (Raster<Bgr8>) and the 16-bit formats; other formats give an 8-bit RGBA copy. Egami's operations (Apply changes a raster, Derive makes a new one) and its row spans then work on the bitmap itself, in place of LockBits loops, ImageAttributes color matrices and hand-written filters.

The view stays valid as long as you hold it, but RotateFlip, MakeTransparent, a format change (ConvertFormat) and SelectActiveFrame give the bitmap new pixels, which an old view no longer shows: fetch the view again after them.

// A 32-bit or 24-bit bitmap's pixels as an Egami raster, no copy: Egami's operations change the bitmap itself.
using (Bitmap argb = bitmap.Clone(new Rectangle(0, 0, bitmap.Width, bitmap.Height), PixelFormat.Format32bppArgb))
{
    var view = (Raster<Bgra8>)argb.AsRaster();
    view.Apply(ops => ops.Grayscale().Sharpen(0.5f));
    Color gray = argb.GetPixel(10, 10);  // R, G and B are equal now

    // RotateFlip, a format change or SelectActiveFrame give the bitmap new pixels: fetch the view again after them.
    argb.RotateFlip(RotateFlipType.Rotate90FlipNone);
    view = (Raster<Bgra8>)argb.AsRaster();
    Assert.Equal(gray.R, gray.B);
    Assert.Equal(argb.Width, view.Width);
}

Results back: Bitmap.FromRaster

Bitmap.FromRaster copies any Egami raster into a bitmap of the matching format (Bgra8 into 32-bit ARGB, Bgr8 into 24-bit RGB, other pixel types through 8-bit RGBA), for code that still takes a Bitmap or Image.

// An Egami result handed back to code that still takes a Bitmap: FromRaster copies it into the matching pixel format.
using Raster<Rgba8> badge = Raster.Create(64, 64, new Rgba8(30, 110, 200, 255));
badge.Apply(ops => ops.Vignette());
using Bitmap forOldCode = Bitmap.FromRaster(badge);  // 32-bit ARGB
forOldCode.Save(System.IO.Path.Combine(System.IO.Path.GetTempPath(), $"badge-{Guid.NewGuid():N}.png"), ImageFormat.Png);

Drawing: Graphics.Canvas

Graphics.Canvas is an Egami.Drawing canvas over a 32-bit bitmap, set to the graphics' world transform and clip, so Egami paths, paints, gradients, images and text can be drawn between Graphics calls. A routine moves call by call; once it only uses the canvas, draw on raster.CreateCanvas() instead.

// Egami.Drawing calls in the middle of an existing Graphics: the canvas draws in the graphics' world coordinates and clip.
using Bitmap bitmap = new(120, 80);
using (Graphics g = Graphics.FromImage(bitmap))
{
    g.Clear(Color.White);
    g.TranslateTransform(10, 10);
    g.DrawRectangle(Pens.Black, 0, 0, 60, 40);                       // System.Drawing, as before
    Egami.Drawing.Canvas canvas = g.Canvas;                          // Egami, at the same place
    canvas.FillPath(Egami.Drawing.Path.Star(new Vector2(30, 20), 18, 8, 5), new SolidPaint(new Rgba8(240, 160, 0, 255)) { Antialias = true });
}

Text: Font.ToEgamiFont

Font.ToEgamiFont gives the same family, style and size as an Egami.Fonts font, sized in pixels at 96 dpi by default, with synthetic bold or italic where the family lacks the face. Egami shapes text (kerning, ligatures, Arabic, Indic and the other complex scripts), lays out paragraphs with TextLayout and draws with Canvas.DrawText; Font.MeasureText replaces MeasureString.

// The same font in Egami.Fonts: shaped text (kerning, ligatures, complex scripts), measured and drawn by Egami.Drawing.
EgamiFonts.Font font = caption.ToEgamiFont();  // 16 pixels per em at 96 dpi, synthetic bold or italic as needed
using Raster<Rgba8> label = Raster.Create(200, 30, new Rgba8(255, 255, 255, 255));
using (RasterCanvas<Rgba8> canvas = label.CreateCanvas())
{
    canvas.DrawText("Shaped by Egami", new Vector2(4, 20), font, new Rgba8(0, 0, 0, 255));
}

float width = font.MeasureText("Shaped by Egami").Advance;

Files: Raster.Load and Save

Image.FromFile and Image.FromStream become Raster.Load, which reads every format Egami knows; Raster.Probe reads the size, format and frames from the header alone. Save takes the format's own writer options (JpegWriterOptions, PngWriterOptions, WebpWriterOptions and the others) instead of ImageCodecInfo and EncoderParameters, and metadata stays in the raster's Metadata instead of PropertyItems. The formats overview and each format's page cover the options.

// Image.FromFile and Save become Raster.Load and Save; Probe reads the header alone, and each format has its own options.
Egami.Formats.RasterInfo info = Raster.Probe(input);             // size, format, frames, without decoding pixels
using Raster photo = Raster.Load(input);
photo.Save(output, new JpegWriterOptions { Quality = 85 });     // instead of EncoderParameters

What maps to what

System.Drawing Egami
Bitmap, Image Raster<TPixel>, such as Raster<Rgba8>; Raster when the pixel type does not matter
PixelFormat the pixel type: Rgba8, Bgra8, PremulBgra8, Rgb8, Gray8, Rgba16 and the others
GetPixel, SetPixel, LockBits Raster<TPixel>.GetPixel, SetPixel and Row(y) spans
Image.FromFile, FromStream, Save, EncoderParameters Raster.Load, Raster.Probe, Save with the format's writer options
PropertyItem, GetPropertyItem Raster.Metadata (EXIF, XMP, IPTC, ICC)
RotateFlip, GetThumbnailImage, Clone(rect) operations: Rotate, FlipHorizontal, FlipVertical, Fit, Resize, Crop
ImageAttributes, ColorMatrix, SetGamma operations: ColorMatrix, Gamma, Grayscale, Brightness and the rest
Graphics, Graphics.FromImage Canvas, from raster.CreateCanvas()
Pen, DashStyle, LineCap, LineJoin Stroke (width, dashes, caps, joins) with a Paint
SolidBrush, LinearGradientBrush, PathGradientBrush, TextureBrush SolidPaint, LinearGradient, RadialGradient, ImagePaint
GraphicsPath, Region Path, PathBuilder, Path.Union and the other path operations
Matrix, TranslateTransform, RotateTransform System.Numerics.Matrix3x2, Canvas.Concat, Translate, Rotate, Scale
SetClip, IntersectClip Canvas.ClipRect, ClipPath
Font, FontFamily, InstalledFontCollection Egami.Fonts.Font, Typeface, FontCollection.System
DrawString, MeasureString, StringFormat Canvas.DrawText, Font.MeasureText, TextLayout with a ParagraphStyle
Metafile Egami.Metafiles.Metafile
Icon ICO and CUR files through Raster.Load and Save

The last step

When no Bitmap, Image, Graphics or Font crosses a boundary of your code any more, remove the Egami.SystemDrawing package reference and its implicit usings. What is left uses Egami, Egami.Drawing and Egami.Fonts directly; the compiler lists anything that still names a drop-in type.

Recipes

Move a thumbnail with a watermark in three steps

A routine that scales a photo to 200 pixels wide, writes a caption on it and saves a JPEG, as System.Drawing code writes it. It runs unchanged on the drop-in:

// Step 1: the System.Drawing code as it was, compiled against Egami.SystemDrawing.
private static void Step1(string input, string output)
{
    using Image source = Image.FromFile(input);
    int height = source.Height * 200 / source.Width;
    using Bitmap thumb = new(200, height);
    using (Graphics g = Graphics.FromImage(thumb))
    {
        g.InterpolationMode = Egami.SystemDrawing.Drawing2D.InterpolationMode.HighQualityBicubic;
        g.DrawImage(source, new Rectangle(0, 0, 200, height));
        using Egami.SystemDrawing.Font font = new("Arial", 10);
        g.DrawString("© Egami", font, Brushes.White, 4, height - 20);
    }

    thumb.Save(output, ImageFormat.Jpeg);
}

The scaling moves to Egami first: the photo loads as a raster, Resize makes the thumbnail, and Bitmap.FromRaster hands it to the drawing and saving code, which does not change:

// Step 2: the hot spot (scaling) moves to Egami; the bitmap stays at the edges, so the watermark code is untouched.
private static void Step2(string input, string output)
{
    using Raster loaded = Raster.Load(input);
    using Raster<Rgba8> small = loaded.As<Rgba8>().Derive(ops => ops.Resize(200, 0));
    using Bitmap thumb = Bitmap.FromRaster(small);
    using (Graphics g = Graphics.FromImage(thumb))
    {
        using Egami.SystemDrawing.Font font = new("Arial", 10);
        g.DrawString("© Egami", font, Brushes.White, 4, thumb.Height - 20);
    }

    thumb.Save(output, ImageFormat.Jpeg);
}

Then the caption and the save move too, and the routine uses Egami alone:

// Step 3: all Egami: load, resize, draw the watermark on the raster's canvas, save with the JPEG options.
private static void Step3(string input, string output)
{
    using Raster loaded = Raster.Load(input);
    using Raster<Rgba8> thumb = loaded.As<Rgba8>().Derive(ops => ops.Resize(200, 0));
    EgamiFonts.Font font = new(EgamiFonts.Typeface.FromFile(FontFile), 13);
    using (RasterCanvas<Rgba8> canvas = thumb.CreateCanvas())
    {
        canvas.DrawText("© Egami", new Vector2(6, thumb.Height - 8), font, new Rgba8(255, 255, 255, 255));
    }

    thumb.Save(output, new JpegWriterOptions { Quality = 85 });
}