Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions Sources/Imaging/Microsoft.Psi.Imaging/ImageExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1375,6 +1375,142 @@ public static void DrawCircle(this Image image, Point p0, int radius, Color colo
drawFormat.FormatFlags = 0;
graphics.DrawString(str, drawFont, drawBrush, p0.X, p0.Y, drawFormat);
}

/// <summary>
/// Renders text on the image at the specified pixel (p0).
/// </summary>
/// <param name="image">Image to draw on.</param>
/// <param name="str">Text to render.</param>
/// <param name="p0">Pixel coordinates for center of circle.</param>
/// <param name="backgroundColor">Background color to use when drawing text.</param>
/// <param name="textColor">Color to use to draw the text.</param>
/// <param name="font">Name of font to use. Optional.</param>
/// <param name="fontSize">Size of font. Optional.</param>
public static void DrawText(this Image image, string str, Point p0, Color backgroundColor, Color textColor, string font = "Arial", float fontSize = 24.0f)
{
if (image.PixelFormat == PixelFormat.Gray_16bpp || image.PixelFormat == PixelFormat.RGBA_64bpp)
{
throw new InvalidOperationException(
"Drawing on 16bpp and 64bpp images is not currently supported. " +
"Convert to a supported format such as 8bpp grayscale or 24/32bpp color first.");
}

// If our image is 8bpp we won't be able to call Graphics.FromImage because
// that call doesn't support the 8bpp pixel format. See:
// https://docs.microsoft.com/en-us/dotnet/api/system.drawing.graphics.fromimage?view=dotnet-plat-ext-3.1
// for details.
//
// Additionally, there is no corresponding System pixel format for RGB 24bpp.
//
// To work around these issues, we will convert the image to 24bpp, perform the operation,
// and then convert back to the original format.
if (image.PixelFormat == PixelFormat.Gray_8bpp || image.PixelFormat == PixelFormat.RGB_24bpp)
{
int stride = 4 * ((image.Width * 3 + 3) / 2); // Rounding to nearest word boundary
using var tmpImage = new Image(image.Width, image.Height, stride, PixelFormat.BGR_24bpp);
image.CopyTo(tmpImage);
tmpImage.DrawText(str, p0, backgroundColor, textColor, font, fontSize);
image.CopyFrom(tmpImage);
return;
}

font ??= "Arial";
using Bitmap bm = image.ToBitmap(false);
using var graphics = Graphics.FromImage(bm);
using Font drawFont = new Font(font, fontSize);
using SolidBrush textBrush = new SolidBrush(textColor);
using SolidBrush backgroundBrush = new SolidBrush(backgroundColor);
using StringFormat drawFormat = new StringFormat();
drawFormat.FormatFlags = 0;

SizeF textSize = graphics.MeasureString(str, drawFont);

// Drawing the background before drawing the text
var bg = new RectangleF(p0.X, p0.Y, textSize.Width, textSize.Height);
graphics.FillRectangle(backgroundBrush, bg);
graphics.DrawString(str, drawFont, textBrush, p0.X, p0.Y, drawFormat);
}

/// <summary>
/// Fills a rectangle at the specified pixel coordinates on the image.
/// </summary>
/// <param name="image">Image to draw on.</param>
/// <param name="rect">Pixel coordinates for rectangle.</param>
/// <param name="color">Color to use for drawing.</param>
public static void FillRectangle(this Image image, Rectangle rect, Color color)
{
if (image.PixelFormat == PixelFormat.Gray_16bpp || image.PixelFormat == PixelFormat.RGBA_64bpp)
{
throw new InvalidOperationException(
"Drawing on 16bpp and 64bpp images is not currently supported. " +
"Convert to a supported format such as 8bpp grayscale or 24/32bpp color first.");
}

// If our image is 8bpp we won't be able to call Graphics.FromImage because
// that call doesn't support the 8bpp pixel format. See:
// https://docs.microsoft.com/en-us/dotnet/api/system.drawing.graphics.fromimage?view=dotnet-plat-ext-3.1
// for details.
//
// Additionally, there is no corresponding System pixel format for RGB 24bpp.
//
// To work around these issues, we will convert the image to 24bpp, perform the operation,
// and then convert back to the original format.
if (image.PixelFormat == PixelFormat.Gray_8bpp || image.PixelFormat == PixelFormat.RGB_24bpp)
{
int stride = 4 * ((image.Width * 3 + 3) / 2); // Rounding to nearest word boundary
using var tmpImage = new Image(image.Width, image.Height, stride, PixelFormat.BGR_24bpp);
image.CopyTo(tmpImage);
tmpImage.FillRectangle(rect, color);
image.CopyFrom(tmpImage);
return;
}

using Bitmap bm = image.ToBitmap(false);
using var graphics = Graphics.FromImage(bm);
using var drawingBrush = new SolidBrush(color);
graphics.FillRectangle(drawingBrush, rect);
}

/// <summary>
/// Fills a circle centered at the specified pixel (p0) with the specified radius.
/// </summary>
/// <param name="image">Image to draw on.</param>
/// <param name="p0">Pixel coordinates for center of circle.</param>
/// <param name="radius">Radius of the circle.</param>
/// <param name="color">Color to use for drawing.</param>
public static void FillCircle(this Image image, Point p0, int radius, Color color)
{
if (image.PixelFormat == PixelFormat.Gray_16bpp || image.PixelFormat == PixelFormat.RGBA_64bpp)
{
throw new InvalidOperationException(
"Drawing on 16bpp and 64bpp images is not currently supported. " +
"Convert to a supported format such as 8bpp grayscale or 24/32bpp color first.");
}

// If our image is 8bpp we won't be able to call Graphics.FromImage because
// that call doesn't support the 8bpp pixel format. See:
// https://docs.microsoft.com/en-us/dotnet/api/system.drawing.graphics.fromimage?view=dotnet-plat-ext-3.1
// for details.
//
// Additionally, there is no corresponding System pixel format for RGB 24bpp.
//
// To work around these issues, we will convert the image to 24bpp, perform the operation,
// and then convert back to the original format.
if (image.PixelFormat == PixelFormat.Gray_8bpp || image.PixelFormat == PixelFormat.RGB_24bpp)
{
int stride = 4 * ((image.Width * 3 + 3) / 2); // Rounding to nearest word boundary
using var tmpImage = new Image(image.Width, image.Height, stride, PixelFormat.BGR_24bpp);
image.CopyTo(tmpImage);
tmpImage.FillCircle(p0, radius, color);
image.CopyFrom(tmpImage);
return;
}

using Bitmap bm = image.ToBitmap(false);
using var graphics = Graphics.FromImage(bm);
using var drawingBrush = new SolidBrush(color);
graphics.FillEllipse(drawingBrush, p0.X - radius, p0.Y - radius, 2 * radius, 2 * radius);
}
}

/// <summary>
Expand Down
71 changes: 71 additions & 0 deletions Sources/Imaging/Microsoft.Psi.Imaging/StreamOperators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,77 @@ public static IProducer<Shared<Image>> DrawText(this IProducer<Shared<Image>> so
drawTextSharedImage.Resource.DrawText(text, p0, color, font, fontSize);
emitter.Post(drawTextSharedImage, envelope.OriginatingTime);
}, deliveryPolicy);
}

/// <summary>
/// Draws a piece of text with background over a shared image.
/// </summary>
/// <param name="source">Image to draw text on.</param>
/// <param name="text">Text to render.</param>
/// <param name="p0">Coordinates for start of text (in pixels).</param>
/// <param name="backgroundColor">Background color to use when drawing text.</param>
/// <param name="textColor">Color to use to draw the text.</param>
/// <param name="font">Name of font to use. Optional.</param>
/// <param name="fontSize">Size of font. Optional.</param>
/// <param name="deliveryPolicy">An optional delivery policy.</param>
/// <param name="sharedImageAllocator">Optional image allocator to create new shared image.</param>
/// <returns>Returns a producer that generates images overdrawn with text.</returns>
public static IProducer<Shared<Image>> DrawText(this IProducer<Shared<Image>> source, string text, Point p0, Color backgroundColor, Color textColor, string font = null, float fontSize = 24.0f, DeliveryPolicy<Shared<Image>> deliveryPolicy = null, Func<int, int, PixelFormat, Shared<Image>> sharedImageAllocator = null)
{
sharedImageAllocator ??= ImagePool.GetOrCreate;
return source.Process<Shared<Image>, Shared<Image>>(
(sharedImage, envelope, emitter) =>
{
using var drawTextSharedImage = sharedImageAllocator(sharedImage.Resource.Width, sharedImage.Resource.Height, sharedImage.Resource.PixelFormat);
drawTextSharedImage.Resource.CopyFrom(sharedImage.Resource);
drawTextSharedImage.Resource.DrawText(text, p0, backgroundColor, textColor, font, fontSize);
emitter.Post(drawTextSharedImage, envelope.OriginatingTime);
}, deliveryPolicy);
}

/// <summary>
/// Fills a rectangle over a shared image.
/// </summary>
/// <param name="source">Image to draw rectangle on.</param>
/// <param name="rect">Pixel coordinates for rectangle.</param>
/// <param name="color">Color to use when drawing the rectangle.</param>
/// <param name="deliveryPolicy">An optional delivery policy.</param>
/// <param name="sharedImageAllocator">Optional image allocator to create new shared image.</param>
/// <returns>Returns a producer that generates images overdrawn with a rectangle.</returns>
public static IProducer<Shared<Image>> FillRectangle(this IProducer<Shared<Image>> source, Rectangle rect, Color color, DeliveryPolicy<Shared<Image>> deliveryPolicy = null, Func<int, int, PixelFormat, Shared<Image>> sharedImageAllocator = null)
{
sharedImageAllocator ??= ImagePool.GetOrCreate;
return source.Process<Shared<Image>, Shared<Image>>(
(sharedImage, envelope, emitter) =>
{
using var drawRectSharedImage = sharedImageAllocator(sharedImage.Resource.Width, sharedImage.Resource.Height, sharedImage.Resource.PixelFormat);
drawRectSharedImage.Resource.CopyFrom(sharedImage.Resource);
drawRectSharedImage.Resource.FillRectangle(rect, color);
emitter.Post(drawRectSharedImage, envelope.OriginatingTime);
}, deliveryPolicy);
}

/// <summary>
/// Fills a circle over a shared image.
/// </summary>
/// <param name="source">Image to draw circle on.</param>
/// <param name="p0">Center of circle (in pixels).</param>
/// <param name="radius">Radius of circle (in pixels).</param>
/// <param name="color">Color to use when drawing the circle.</param>
/// <param name="deliveryPolicy">An optional delivery policy.</param>
/// <param name="sharedImageAllocator">Optional image allocator to create new shared image.</param>
/// <returns>Returns a producer that generates images overdrawn with a circle.</returns>
public static IProducer<Shared<Image>> FillCircle(this IProducer<Shared<Image>> source, Point p0, int radius, Color color, DeliveryPolicy<Shared<Image>> deliveryPolicy = null, Func<int, int, PixelFormat, Shared<Image>> sharedImageAllocator = null)
{
sharedImageAllocator ??= ImagePool.GetOrCreate;
return source.Process<Shared<Image>, Shared<Image>>(
(sharedImage, envelope, emitter) =>
{
using var drawCircleSharedImage = sharedImageAllocator(sharedImage.Resource.Width, sharedImage.Resource.Height, sharedImage.Resource.PixelFormat);
drawCircleSharedImage.Resource.CopyFrom(sharedImage.Resource);
drawCircleSharedImage.Resource.FillCircle(p0, radius, color);
emitter.Post(drawCircleSharedImage, envelope.OriginatingTime);
}, deliveryPolicy);
}

/// <summary>
Expand Down
Loading