What is the replacement of Graphics and Pen class in uwp app? - c#

This is a WinForm code
private EasyScript eScript;
/// <summary>
/// Graphics object we'll draw on to in order to produce a signature
/// image.
/// </summary>
private Graphics graphics;
/// <summary>
/// Raster backing the graphics object.
/// </summary>
private Bitmap raster;
/// <summary>
/// Pen we'll use to create strokes on our graphics object.
/// </summary>
private Pen pen;
/// <summary>
/// The last point we captured.
/// </summary>
private Coordinate lastPoint = null;
/// <summary>
/// Whether or not the next event we receive should clear the signature.
/// </summary>
private bool clearOnNext = false;
/// <summary>
/// The current stroke count.
/// </summary>
private int strokeCount = 0;
/// <summary>
/// The amount to scale the coordinates by.
/// </summary>
private double scaleFactor = 1;
/// <summary>
/// Initializes a new instance of the <see cref="ExampleForm"/> class.
/// </summary>
public ExampleForm()
{
//Create a new EasyScript object.
this.eScript = new EasyScript();
//Register ourselves as a signature listener.
eScript.AddListener(this);
//Initialize our form.
this.InitializeComponent();
//Initialize our drawing components.
raster = new Bitmap(signaturePictureBox.Width, signaturePictureBox.Height);
graphics = Graphics.FromImage(raster);
//Enable high quality drawing.
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.SmoothingMode = SmoothingMode.AntiAlias;
pen = new Pen(Brushes.Black);
//Calculate our scale factor based on the size of the picture box.
scaleFactor = signaturePictureBox.Width / eScript.GetSignatureProtocol().GetWidth();
//Clear the picture box.
ClearSignatureBox();
// this allows the form to preview all keyboard events before other parts of the form are allowed
// to get them. If a particular keyboard event is from a ScripTouch device,
// we can prevent the event from propogating to other form elements, such as a TextBox.
this.KeyPreview = true;
this.cardSwipeInfoTextBox.ReadOnly = true;
this.signatureInfoTextBox.ReadOnly = true;
}
I need to convert this in my UWP app. However i can use WriteableBitmap in place of Bitmap and SolidColorBrush in place of Pen. But what should be for Graphics class.
Anyhow everything is solved if i consider graphics as a WriteableBitmap apart from these below lines
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.CompositingQuality = CompositingQuality.HighQuality; graphics.SmoothingMode = SmoothingMode.AntiAlias;
and
graphics.FillRectangle(Colors.White, 0, 0, signature.Width, signature.Height);
and
graphics.DrawLine(pen, (float)(lastPoint.X * scaleFactor), (float)(lastPoint.Y * scaleFactor), (float)(coordinate.X * scaleFactor), (float)(coordinate.Y * scaleFactor));
signature is my image object.
Thanks in advance.

The Graphics class under System.Drawing namespace is more like CanvasDrawingSession class of Win2D. Win2D is an easy-to-use Windows Runtime API for immediate mode 2D graphics rendering with GPU acceleration which is available for UWP app.
For example, for graphics.InterpolationMode property you may try CanvasImageInterpolation instead. The Antialiasing property of CanvasDrawingSession defined similar features as SmoothingMode has. CanvasDrawingSession also has FillRectangle and Drawline methods as you showed above from Graphics.
So you can try to use Win2D library in UWP app to implement the same features. For more details about how to use Win2D please reference the README.md of the official site and for samples please reference the official samples.

Try using the WritableBitmapEx library
https://www.nuget.org/packages/WriteableBitmapEx
You can add this package to your project using nuget.
With WritableBitmapEx, it is really easy to Draw polygons, shapes, lines etc, directly on images.
E.g.
bmp.DrawRectangle(cornerPoints[0].X, cornerPoints[0].Y, cornerPoints[2].X, cornerPoints[2].Y, Colors.Red);
You can find tons of drawing examples with WritableBitmapEx on github
https://github.com/reneschulte/WriteableBitmapEx

Related

Unity GUI.Label/GUI.Box blackout images

I draw images from GUI.Label/GUI.Box but Unity driwed use "blackout" (or "red filter") before render images.
https://i.gyazo.com/255e4947a7ca95f835104a1f8ffa2e61.png
In screen right original apple - bright. In left apple be drawed unity, but this dark.
I used GUI.Color = new Color(1f,1f,1f,1f) but it didn't help me
Next, a write editor (editor windows) and draw this:
https://i.gyazo.com/06908720a2b00c538558c61092a98329.png
Apples were dark. It's my ItemDrawService:
public class ItemDrawService {
private GUIStyle labelStyle = null;
public ItemDrawService(GUIStyle labelStyle){
this.labelStyle=labelStyle;
}
/// <summary>
/// Отрисовка предмета в инвентаре
/// </summary>
/// <param name="item">Рисуемый предмет</param>
/// <param name="offsetX">Смещение (позиция) инвентаря по x</param>
/// <param name="offsetY">Смещение (позиция) инвентаря по y</param>
/// <param name="fixWebPosition">при fixWebPosition=true предмет рисуется "в сетке"</param>
/// <param name="drawIcon">при drawIcon=false, предмет рисуется без иконки</param>
public void DrawItem(ItemSlot item, float offsetX, float offsetY, bool fixWebPosition = true, bool drawIcon = true){
Rect cellRectangle;
if (fixWebPosition) {
cellRectangle = new Rect(offsetX + CellSettings.cellPaddingX + (item.position.X - 1) * CellSettings.cellWidth,
offsetY + CellSettings.cellPaddingY + (item.position.Y - 1) * CellSettings.cellHeight,
CellSettings.cellWidth,
CellSettings.cellHeight);
} else {
cellRectangle = new Rect(offsetX,
offsetY,
CellSettings.cellWidth,
CellSettings.cellHeight);
}
if (drawIcon)
labelStyle.normal.background = item.item.resource.icon;
string description = item.item.getCount()>1? item.item.getCount().ToString()+CLang.getInstance().get(Dictionary.K_COUNT) : "";
GUI.color = new Color(1f,1f,1f,1f);
GUI.Label(cellRectangle, description, labelStyle);
}
}
How I do the drawing intact?
I find this in documentation:
Legacy GUI and Linear Authored Textures
Rendering elements of the Legacy GUI System is always done in gamma
space. This means that, for the legacy GUI system, GUI textures should
not have their gamma removed on read. This can be achieved in two
ways:
Set the texture type to GUI in the texture importer
Check the ‘Bypass sRGB Sampling’ checkbox in the advanced texture importer

How to Convert a WPF inch unit to Winforms pixels and vice versa?

I have a window that designed at WPF and I used that on center of a WinForms owner.
Now, I want to move the owner form and at the moment my WPF window has also to be moved in the center of the form!
But I have a problem, Only when window is in the center of the form that form in the center of the screen. And otherwise act in a different form than the Windows coordinates.
I just add the displacement values of the form to window location.
Now I have come to the conclusion that the coordinates of the pixels on WPF Windows are different by WinForms!
How to convert WPF window location to WinForms base location and vice versa ?
Owner Form codes is:
public partial class Form1 : Form
{
private WPF_Window.WPF win;
public Form1()
{
InitializeComponent();
win = new WPF();
win.Show();
CenterToParent(win);
}
private void CenterToParent(System.Windows.Window win)
{
win.Left = this.Left + (this.Width - win.Width) / 2;
win.Top = this.Top + (this.Height - win.Height) / 2;
}
protected override void OnMove(EventArgs e)
{
base.OnMove(e);
CenterToParent(win);
}
}
Best way to get DPI value in WPF
Method 1
It’s the same way you did that in Windows Forms. System.Drawing.Graphics object provides convenient properties to get horizontal and vertical DPI. Let’s sketch up a helper method:
/// <summary>
/// Transforms device independent units (1/96 of an inch)
/// to pixels
/// </summary>
/// <param name="unitX">a device independent unit value X</param>
/// <param name="unitY">a device independent unit value Y</param>
/// <param name="pixelX">returns the X value in pixels</param>
/// <param name="pixelY">returns the Y value in pixels</param>
public void TransformToPixels(double unitX,
double unitY,
out int pixelX,
out int pixelY)
{
using (Graphics g = Graphics.FromHwnd(IntPtr.Zero))
{
pixelX = (int)((g.DpiX / 96) * unitX);
pixelY = (int)((g.DpiY / 96) * unitY);
}
// alternative:
// using (Graphics g = Graphics.FromHdc(IntPtr.Zero)) { }
}
You can use it transforms both coordinates as well as size values. It’s pretty simple and robust and completely in managed code (at least as far as you, the consumer, is concerned). Passing IntPtr.Zero as HWND or HDC parameter results in a Graphics object that wraps a device context of the entire screen.
There is one problem with this approach though. It has a dependency on Windows Forms/GDI+ infrastructure. You are going to have to add a reference to System.Drawing assembly. Big deal? Not sure about you, but for me this is an issue to avoid.
Method 2
Let’s take it one step deeper and do it the Win API way. GetDeviceCaps function retrieves various information for the specified device and is able to retrieve horizontal and vertical DPI’s when we pass it LOGPIXELSX and LOGPIXELSY parameters respectively.
GetDeviceCaps function is defined in gdi32.dll and is probably what System.Drawing.Graphics uses under the hood.
Let’s have a look at what our helper has become:
[DllImport("gdi32.dll")]
public static extern int GetDeviceCaps(IntPtr hDc, int nIndex);
[DllImport("user32.dll")]
public static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDc);
public const int LOGPIXELSX = 88;
public const int LOGPIXELSY = 90;
/// <summary>
/// Transforms device independent units (1/96 of an inch)
/// to pixels
/// </summary>
/// <param name="unitX">a device independent unit value X</param>
/// <param name="unitY">a device independent unit value Y</param>
/// <param name="pixelX">returns the X value in pixels</param>
/// <param name="pixelY">returns the Y value in pixels</param>
public void TransformToPixels(double unitX,
double unitY,
out int pixelX,
out int pixelY)
{
IntPtr hDc = GetDC(IntPtr.Zero);
if (hDc != IntPtr.Zero)
{
int dpiX = GetDeviceCaps(hDc, LOGPIXELSX);
int dpiY = GetDeviceCaps(hDc, LOGPIXELSY);
ReleaseDC(IntPtr.Zero, hDc);
pixelX = (int)(((double)dpiX / 96) * unitX);
pixelY = (int)(((double)dpiY / 96) * unitY);
}
else
throw new ArgumentNullException("Failed to get DC.");
}
So we have exchanged a dependency on managed GDI+ for the dependency on fancy Win API calls. Is that an improvement? In my opinion yes, as long as we run on Windows Win API is a least common denominator. It is lightweight. On other platforms we wouldn’t probably have this dilemma in the first place.
And don’t get fooled by that ArgumentNullException. This solution is as robust as the first one. System.Drawing.Graphics will throw this same exception if it can’t obtain a device context too.
Method 3
As officially documented here there is a special key in the registry: HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontDPI. It stores a DWORD value which is exactly what the user chooses for DPI in the display settings dialog (it’s called a font size there).
Reading it is a no-brainer but I wouldn’t recommend it. You see there is a difference between an official API and a storage for various settings. The API is a public contract that stays the same even if the internal logic is totally rewritten (If it doesn’t the whole platform sucks, doesn’t it?).
But nobody guarantees that the internal storage will remain the same. It may have lasted for a couple of decades but a crucial design document that describes its relocation may already be pending an approval. You never know.
Always stick to API (whatever it is, native, Windows Forms, WPF, etc). Even if the underlying code reads the value from the location you know.
Method 4
This is a pretty elegant WPF approach that I’ve found documented in this blog post. It is based on the functionality provided by System.Windows.Media.CompositionTarget class that ultimately represents the display surface on which the WPF application is drawn. The class provides 2 useful methods:
TransformFromDevice
TransformToDevice
The names are self-explanatory and in both cases we get a System.Windows.Media.Matrix object that contains the mapping coefficients between device units (pixels) and independent units. M11 will contain a coefficient for the X axis and M22 – for the Y axis.
As we have been considering a units->pixels direction so far let’s re-write our helper with CompositionTarget.TransformToDevice. When calling this method M11 and M22 will contain values that we calculated as:
dpiX / 96
dpiY / 96
So on a machine with DPI set to 120 the coefficients will be 1.25.
Here’s the new helper:
/// <summary>
/// Transforms device independent units (1/96 of an inch)
/// to pixels
/// </summary>
/// <param name="visual">a visual object</param>
/// <param name="unitX">a device independent unit value X</param>
/// <param name="unitY">a device independent unit value Y</param>
/// <param name="pixelX">returns the X value in pixels</param>
/// <param name="pixelY">returns the Y value in pixels</param>
public void TransformToPixels(Visual visual,
double unitX,
double unitY,
out int pixelX,
out int pixelY)
{
Matrix matrix;
var source = PresentationSource.FromVisual(visual);
if (source != null)
{
matrix = source.CompositionTarget.TransformToDevice;
}
else
{
using (var src = new HwndSource(new HwndSourceParameters()))
{
matrix = src.CompositionTarget.TransformToDevice;
}
}
pixelX = (int)(matrix.M11 * unitX);
pixelY = (int)(matrix.M22 * unitY);
}
I had to add one more parameter to the method, the Visual. We need it as a base for calculations (previous samples used the device context of the entire screen for that). I don’t think it’s a big issue as you are more than likely to have a Visual at hand when running your WPF application (otherwise, why would you need to translate pixel coordinates?). However, if your visual hasn't been attached to a presentation source (that is, it hasn't been shown yet) you can't get the presentation source (thus, we have a check for NULL and construct a new HwndSource).
Reference
I've just uncovered and tested this (in VB):
formVal = Me.LogicalToDeviceUnits(WPFval)
formVal and WPFval can be either Integers or Sizes.

.NET and Direct2D rapid draw crash

General purpose
I am developing a .NET user control that can be used for multipurpose graphics rendering using Direct2D. This control is based on the ID2D1HwndRenderTarget interface with a ID2D1BitmapRenderTarget back-buffer/double-buffer.
Background
I have written a wrapper using C++/CLI that exposes a set of managed wrapper objects for Direct2D, and I have a designed a Singleton resource manager for Direct2D bitmaps that code can send to the binary data to, and get a resource key back from, which code can then pass in a message to the user control to render a bitmap.
Usage so far
I have been able to load bitmaps, JPEGs, custom imaging formats and the like and send them to the Direct2D render control all with no issue. I've written a custom video decoder that could load frames from a 15-fps video (decoding in a background thread) and render them from events raised by a Win32 multimedia timer, no problem.
Issue
My issue is that when I attempted to expand out from a single multimedia format into something more flexible (specifically connecting to LibAV/ffmpeg), the Direct2D render control starts to crash display drivers. This does not happen when frames are rendered sequentially (using a button to render the next frame, rather than a timer). It also does not happen straightaway. It also does not happen when I block inside the Win32 timer callback, but does when I use a Mutex to raise a thread that will free the timer callback up and let the interim thread instruct the control to render.
Symptoms
If I start a timer and attach to its elapsed event methods that will cause the next render, it will typically play fine for around 2-20 seconds. Using smaller video, I can get longer playback before the issue starts. If I play back 1080p video, I can get the crash usually within 5 seconds without contest. Playback will start fine, maybe have a hiccup here or there before catching up.
Eventually, I will start to see flickering, as if the background color is rendered but the frame is not. Additional frames may or may not later render, but this is very short-lived.
After the flicker, if I stop the timer quickly enough there will be no crash.
If I do not stop in time:
If I'm lucky (maybe 10% of the time), frames will stop rendering, be stuck on one frame until I resize the control, at which point only black is drawn to the entire control. (I've read that this may indicate a lost device context for DirectX rendering, but have not seen much more.)
If I'm unlucky, the display driver will crash, Windows recovers and restarts the driver, after which point the EndDraw will finally tell me that an error has occurred and return D2DERR_RECREATE_TARGET.
If I am really unlucky, the display will start to look like a kaleidoscope and I'll have to power off my machine, and I just wasted 5 minutes booting, logging on, loading Visual Studio and my solution, loading the video and lost all my debugging data.
I want to think that there is some sort of race condition that I am missing, but every time I run through the rendering code, it appears that it should be properly locked.
Render Control's Code
using System;
using System.Drawing;
using System.Windows.Forms;
using Direct2D = Bardez.Projects.DirectX.Direct2D;
using ExternalPixelEnums = Bardez.Projects.FileFormats.MediaBase.Video.Pixels.Enums;
using Bardez.Projects.DirectX.Direct2D;
using Bardez.Projects.FileFormats.MediaBase.Video;
using Bardez.Projects.FileFormats.MediaBase.Video.Enums;
using Bardez.Projects.Win32;
namespace Bardez.Projects.Output.Visual
{
/// <summary>Represents a rendering target for Direct2D.</summary>
/// <remarks>
/// To use this control, an external component will need to supply bitmap data. This does not need to be a GDI+ Bitmap class.
/// However, the container for this control will need to push data into this control.
/// So, in the case of a movie player, we'd see the following model:
/// * decompress a frame.
/// * push frame to control
/// * invoke Invalidate
/// * control will render the bitmap
/// * sleep just a little bit
/// * go back to first step
/// </remarks>
public class Direct2dRenderControl : VisualRenderControl
{
/*
* Locking orientation:
* There are two rendering targets: a GDI display and a bitmap back buffer.
* There are 5 'real' locking operations:
* Rendering to the GDI display (OnPaint)
* Rendering to the back buffer (DrawBitmapToBuffer, DiscardCurrentBuffer)
* Setting the current displayed frame (SetRenderFrame)
* Resource Freeing (FreeFrameResource)
* Resizing (OnResize)
*
* Briefly, the overarching effects of these five are:
* Rendering
* Utilizes the buffer and the display
* Back Buffer
* Utilizes the buffer and if mid-render could affect this display
* Set Frame
* Sets the buffer's displayed image
* Resource Freeing
* Affects the buffer if a resource is refernced
* Resizing
* Resizes the render control and the bitmap, uses the frame set by set
*
* Locking plan:
* Resize should block set, free and back buffer
* Render should block back buffer, control
* Set frame should block resize and back buffer
* Free should block back buffer,
* Back buffer should block rendering, setting, resizing, freeing
*
* Basically, lock everything at the process level, and not at the access level.
*/
#region Fields
/// <summary>Represents a Win32 HWND render target for Direct2D</summary>
private ControlRenderTarget ctrlRenderTarget;
/// <summary>Represents a drawing buffer</summary>
private BitmapRenderTarget bmpRenderTarget;
/// <summary>Represents a drawing buffer, used solely to create bitmaps</summary>
private BitmapRenderTarget resourceBmpRenderTarget;
/// <summary>Represents a buffer drawing command lock</summary>
private Object controlBufferLock;
/// <summary>Represents a paint/render drawing command lock</summary>
private Object controlPaintRenderLock;
/// <summary>Locking object reference for resource management (memory bitmaps, etc.)</summary>
private Object resourceLock;
/// <summary>Represents the key to accessing the currently set key</summary>
protected Int32 currentFrameKey;
#endregion
#region Properties
/// <summary>Indicates whether there is a frame set for this control</summary>
protected Boolean HasFrameSet
{
get { return currentFrameKey > -1; }
}
/// <summary>Exposes a wrapper for the bitmap render target</summary>
protected BitmapRenderTarget BmpRenderTarget
{
get { return this.bmpRenderTarget; }
set
{
lock (this.controlBufferLock)
{
if (this.bmpRenderTarget != null)
this.bmpRenderTarget.Dispose();
this.bmpRenderTarget = value;
}
}
}
/// <summary>Exposes a wrapper for the bitmap render target</summary>
protected BitmapRenderTarget ResourceBmpRenderTarget
{
get { return this.resourceBmpRenderTarget; }
set
{
lock (this.resourceLock)
{
if (this.resourceBmpRenderTarget != null)
this.resourceBmpRenderTarget.Dispose();
this.resourceBmpRenderTarget = value;
}
}
}
/// <summary>Represents a Win32 HWND render target for Direct2D</summary>
protected ControlRenderTarget CtrlRenderTarget
{
get { return this.ctrlRenderTarget; }
set
{
lock (this.controlPaintRenderLock)
{
if (this.ctrlRenderTarget != null)
this.ctrlRenderTarget.Dispose();
this.ctrlRenderTarget = value;
}
}
}
#endregion
#region Construction
/// <summary>Default constructor</summary>
public Direct2dRenderControl() : base()
{
this.controlBufferLock = new Object();
this.controlPaintRenderLock = new Object();
this.resourceLock = new Object();
this.currentFrameKey = -1;
this.InitializeControlDirect2D();
}
/// <summary>Initializes the Direct2D</summary>
protected void InitializeControlDirect2D()
{
//disable Windows background draw
this.SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
// Build options on the Control render target
PixelFormat format = new PixelFormat(DXGI_ChannelFormat.FORMAT_B8G8R8A8_UNORM, AlphaMode.Unknown); //32-bit color, pure alpha
DpiResolution res = Direct2dResourceManager.Instance.Factory.GetDesktopDpi();
RenderTargetProperties rtProp = new RenderTargetProperties(RenderTargetType.Default, format, res, RenderTargetUsage.GdiCompatible, DirectXVersion.DirectX9);
//Build out control render target properties
HwndRenderTargetProperties hwndProp = new HwndRenderTargetProperties(this.Handle, new SizeU(this.Size), PresentationOptions.RetainContents);
lock (this.controlPaintRenderLock)
{
// populate the Control rendering target
ResultCode result = Direct2dResourceManager.Instance.Factory.CreateHwndRenderTarget(rtProp, hwndProp, out this.ctrlRenderTarget);
lock (this.controlBufferLock)
{
// create a bitmap rendering targets
this.CtrlRenderTarget.CreateCompatibleRenderTarget(out this.bmpRenderTarget);
lock (this.resourceLock)
this.CtrlRenderTarget.CreateCompatibleRenderTarget(out this.resourceBmpRenderTarget);
}
}
}
#endregion
#region Destruction
/// <summary>Disposal code; releases unmanaged resources</summary>
/// <param name="disposing">True indicates to dispose managed resources</param>
protected override void Dispose(Boolean disposing)
{
this.ResourceBmpRenderTarget = null; //property disposes
this.BmpRenderTarget = null; //property disposes
this.CtrlRenderTarget = null; //property disposes
base.Dispose(disposing);
}
/// <summary>Disposal</summary>
~Direct2dRenderControl()
{
this.Dispose();
}
#endregion
#region Event Raising
/// <summary>Draws the output, then raises the paint event</summary>
/// <param name="e">Painting Event arguments</param>
protected override void OnPaint(PaintEventArgs e)
{
lock (this.controlPaintRenderLock)
{
lock (this.controlBufferLock)
{
this.SuspendLayout();
this.OnPaintBackground(e);
Direct2D.Bitmap bmp;
ResultCode result;
result = this.BmpRenderTarget.GetBitmap(out bmp);
Direct2D.RectangleF rect = new Direct2D.RectangleF(e.ClipRectangle);
this.CtrlRenderTarget.BeginDraw();
this.CtrlRenderTarget.DrawBitmap(bmp, rect, 1.0F, BitmapInterpolationMode.Linear, rect);
result = this.CtrlRenderTarget.EndDraw();
bmp.Dispose();
if (result != ResultCode.Success_OK)
throw new ApplicationException(String.Format("Error encountered during draw: '{0}'", result.ToString()));
base.OnPaint(e);
this.ResumeLayout();
}
}
}
/// <summary>Overides the resize method</summary>
/// <param name="e">Parameters for the resize event</param>
protected override void OnResize(EventArgs e)
{
lock (this.controlPaintRenderLock)
{
lock (this.controlBufferLock)
{
//Null check since resize fires before it is constructed, with the size event during parent's InitializeComponent
if (this.CtrlRenderTarget != null)
{
this.SuspendLayout();
//resize the existing control rendering target
this.CtrlRenderTarget.Resize(new SizeU(this.Size));
//I also need to resize the buffer, but can't. Instead, create a new one, then copy the existing one. Kind of lame.
this.ResizeBitmapRenderTarget();
base.OnResize(e);
//cause another draw
this.Invalidate(new Rectangle(new Point(0, 0), this.Size));
this.ResumeLayout();
}
}
}
}
/// <summary>Overridden Paint background method</summary>
/// <param name="e">Paint event arguments</param>
/// <remarks>
/// Made empty to avoid GDI and Direct2D writing to the same control; when moving the control around
/// or rendering at high speeds, the dreaded WinForms flicker was introduced.
/// The background painting can be found in the <see cref="FillBufferRenderTarget"/> method,
/// which fills the bitmap back buffer with the control's background color.
/// </remarks>
protected override void OnPaintBackground(PaintEventArgs e) { }
#endregion
#region Drawing
/// <summary>Resizes the bitmap rendering target buffer</summary>
/// <remarks>Does not lock the GDI render target. The OnResize or other callers lock instead.</remarks>
protected void ResizeBitmapRenderTarget()
{
lock (this.controlPaintRenderLock)
{
lock (this.controlBufferLock)
{
using (BitmapRenderTarget bmpCurr = this.BmpRenderTarget)
{
BitmapRenderTarget bmpNew;
ResultCode result = this.CtrlRenderTarget.CreateCompatibleRenderTarget(out bmpNew);
this.DuplicateDoubleBufferContents(bmpNew);
//Property disposes and locks
this.BmpRenderTarget = bmpNew;
}
}
}
}
/// <summary>Draws a Bitmap to the render buffer</summary>
/// <param name="bmp">Direct2D bitmap to draw to the buffer.</param>
protected void DrawBitmapToBuffer(Direct2D.Bitmap bmp, Point2dF origin)
{
lock (this.controlBufferLock)
{
lock (this.resourceLock)
{
Direct2D.SizeF bmpSize = bmp.GetSize();
Single width = bmpSize.Width > this.BmpRenderTarget.Size.Width ? this.BmpRenderTarget.Size.Width : bmpSize.Width;
Single height = bmpSize.Height > this.BmpRenderTarget.Size.Height ? this.BmpRenderTarget.Size.Height : bmpSize.Height;
Direct2D.RectangleF destRect = new Direct2D.RectangleF(origin.X, origin.X + width, origin.Y, origin.Y + height);
Direct2D.RectangleF srcRect = new Direct2D.RectangleF(0.0F, width, 0.0F, height);
this.BmpRenderTarget.BeginDraw();
this.FillBufferRenderTarget(this.BmpRenderTarget);
// do the actual draw
this.BmpRenderTarget.DrawBitmap(bmp, destRect, 1.0F, BitmapInterpolationMode.Linear, srcRect);
//tell Direct2D that a paint operation is ending
ResultCode result = this.BmpRenderTarget.EndDraw();
}
}
}
/// <summary>Draws a Bitmap to the render buffer</summary>
/// <param name="bmp">Direct2D bitmap to draw to the buffer.</param>
protected void DrawBitmapToBuffer(Direct2D.Bitmap bmp)
{
this.DrawBitmapToBuffer(bmp, new Point2dF(0.0F, 0.0F));
}
/// <summary>Duplicates the bitmap behind the existing rendering target, and drawing it to a new one, discarding the current and setting the new.</summary>
/// <remarks>Does not lock any references, as the outside method locks</remarks>
protected void DuplicateDoubleBufferContents(BitmapRenderTarget bmpNew)
{
Direct2D.Bitmap bmp = null;
ResultCode result = ResultCode.Success_OK;
if (this.HasFrameSet)
bmp = Direct2dResourceManager.Instance.GetBitmapResource(this.currentFrameKey);
else
result = this.BmpRenderTarget.GetBitmap(out bmp);
bmpNew.BeginDraw();
this.FillBufferRenderTarget(bmpNew);
//calculate the size to copy
Direct2D.SizeF bmpSize = bmp.GetSize();
Single width = bmpSize.Width > this.CtrlRenderTarget.Size.Width ? this.CtrlRenderTarget.Size.Width : bmpSize.Width;
Single height = bmpSize.Height > this.CtrlRenderTarget.Size.Height ? this.CtrlRenderTarget.Size.Height : bmpSize.Height;
//Determine the copy rectangle
Direct2D.RectangleF rect = new Direct2D.RectangleF(0, width, 0, height);
//Copy
bmpNew.DrawBitmap(bmp, rect, 1.0F, BitmapInterpolationMode.Linear, rect);
//conditionally disose the bitmap, don't if it is in the manager
if (!this.HasFrameSet)
bmp.Dispose();
result = bmpNew.EndDraw();
}
/// <summary>Discards the current buffer and replaces it with a blank one.</summary>
protected void DiscardCurrentBuffer()
{
lock (this.controlBufferLock)
{
BitmapRenderTarget bmpNew;
// create a bitmap rendering target
ResultCode result = this.CtrlRenderTarget.CreateCompatibleRenderTarget(out bmpNew);
bmpNew.BeginDraw();
this.FillBufferRenderTarget(bmpNew);
result = bmpNew.EndDraw();
//property locks, so no lock here
this.BmpRenderTarget = bmpNew; //replace the old buffer
}
}
/// <summary>Fills the buffer render target with the background color</summary>
/// <param name="renderTarget">Bitmap render target to fill</param>
protected void FillBufferRenderTarget(BitmapRenderTarget renderTarget)
{
SolidColorBrush brush = null;
ResultCode result = renderTarget.CreateSolidColorBrush(new ColorF(this.BackColor), out brush);
renderTarget.FillRectangle(new Direct2D.RectangleF(new Rectangle(new Point(0, 0), this.Size)), brush);
brush.Dispose();
}
#endregion
#region Frame Resources
/// <summary>Posts a Frame resource to the resource manager and returns a unique key to access it.</summary>
/// <param name="resource">Frame to be posted.</param>
/// <returns>A unique Int32 key</returns>
public override Int32 AddFrameResource(Frame resource)
{
lock (this.resourceLock)
{
//create the bitmap
BitmapProperties properties = new BitmapProperties(new PixelFormat(DXGI_ChannelFormat.FORMAT_B8G8R8A8_UNORM, AlphaMode.PreMultiplied), Direct2dResourceManager.Instance.Factory.GetDesktopDpi());
SizeU dimensions = new SizeU(Convert.ToUInt32(resource.Pixels.Metadata.Width), Convert.ToUInt32(resource.Pixels.Metadata.Height));
Direct2D.Bitmap bmp = null;
ResultCode result = this.ResourceBmpRenderTarget.CreateBitmap(dimensions, properties, out bmp);
Byte[] data = resource.Pixels.GetPixelData(ExternalPixelEnums.PixelFormat.RGBA_B8G8R8A8, ScanLineOrder.TopDown, 0, 0);
result = bmp.CopyFromMemory(new RectangleU(dimensions), data, Convert.ToUInt32(resource.Pixels.Metadata.Width * 4));
return Direct2dResourceManager.Instance.AddFrameResource(bmp);
}
}
/// <summary>Frees a Bitmap resource in the resource manager and Disposes of it.</summary>
/// <param name="frameKey">Direct2D Bitmap key to be Disposed.</param>
public override void FreeFrameResource(Int32 frameKey)
{
lock (this.resourceLock)
{
if (frameKey > -1)
Direct2dResourceManager.Instance.FreeFrameResource(frameKey);
}
}
#endregion
#region Frame Setting
/// <summary>Sets the frame to be rendered to the User Control</summary>
/// <param name="key">Frame key to set as current image</param>
public override void SetRenderFrame(Int32 key)
{
this.SetRenderFrame(key, 0, 0);
}
/// <summary>Sets the frame to be rendered to the User Control</summary>
/// <param name="key">Frame key to set as current image</param>
/// <param name="originX">X coordinate to start drawing from</param>
/// <param name="originY">Y coordinate to start drawing from</param>
public override void SetRenderFrame(Int32 key, Int64 originX, Int64 originY)
{
lock (this.controlBufferLock)
{
if (key > -1)
this.DrawBitmapToBuffer(Direct2dResourceManager.Instance.GetBitmapResource(key), new Point2dF(Convert.ToSingle(originX), Convert.ToSingle(originY)));
else
this.DiscardCurrentBuffer();
this.currentFrameKey = key;
}
}
/// <summary>This method invokes the rendering. For use by the appliation to tell the control to change images on demand.</summary>
public void Render()
{
this.Invalidate();
}
/// <summary>Sets the frame to be rendered to the User Control and then renders it</summary>
/// <param name="key">Frame key to set as current image</param>
public virtual void SetRenderFrameAndRender(Int32 key)
{
this.SetRenderFrameAndRender(key, false);
}
/// <summary>Sets the frame to be rendered to the User Control and then renders it</summary>
/// <param name="key">Frame key to set as current image</param>
/// <param name="freePreviousFrame">
/// Flag indicating whether to dispose of the previous image set
/// (in the case of transient images, such as composite images for a game or high-frame video playback)
/// </param>
public virtual void SetRenderFrameAndRender(Int32 key, Boolean freePreviousFrame)
{
this.SetRenderFrameAndRender(key, 0, 0, freePreviousFrame);
}
/// <summary>Sets the frame to be rendered to the User Control and then renders it</summary>
/// <param name="key">Frame key to set as current image</param>
/// <param name="originX">X coordinate to start drawing from</param>
/// <param name="originY">Y coordinate to start drawing from</param>
public virtual void SetRenderFrameAndRender(Int32 key, Int64 originX, Int64 originY)
{
this.SetRenderFrameAndRender(key, originX, originY, false);
}
/// <summary>Sets the frame to be rendered to the User Control and then renders it</summary>
/// <param name="key">Frame key to set as current image</param>
/// <param name="originX">X coordinate to start drawing from</param>
/// <param name="originY">Y coordinate to start drawing from</param>
/// <param name="freePreviousFrame">
/// Flag indicating whether to dispose of the previous image set
/// </param>
public virtual void SetRenderFrameAndRender(Int32 key, Int64 originX, Int64 originY, Boolean freePreviousFrame)
{
lock (this.controlBufferLock)
{
Int32 previousFrameKey = this.currentFrameKey;
this.SetRenderFrame(key, originX, originY);
this.Render();
if (freePreviousFrame)
this.FreeFrameResource(previousFrameKey);
}
}
#endregion
}
}
Win32 Multimedia Timer Callback's Code
Excluded for size, but here is the relevant code, linking to "winmm.dll", EntryPoint = "timeSetEvent"
/// <summary>Callback method for WIn32 API</summary>
protected virtual void Win32Callback(UInt32 timerId, UInt32 message, IntPtr user, IntPtr param1, IntPtr param2)
{
TimeSpan raise;
lock (this.timerLock)
{
//get system time for start time
UInt32 uptime = Win32MultimediaTimeFunctions.GetEnvironmentUptime();
Int32 upTimeSec = (Int32)(uptime / 1000);
Int32 upTimeMilliSec = (Int32)(uptime % 1000);
TimeSpan WinUpTime = new TimeSpan(0, 0, 0, upTimeSec, upTimeMilliSec);
raise = WinUpTime - this.StartTime;
//this.elapsed(raise);
this.RaiseTimer(raise);
}
//Note: outside the lock, this.elapse(raise) kills the display driver
//inside, without threading, it is fine.
//inside, with threading, the display driver will crash after a bit of playback
}
protected void RaiseTimer(TimeSpan elapsedTime)
{
Thread timerEvent = new Thread(() => { this.RunThread(elapsedTime); });
timerEvent.IsBackground = true;
timerEvent.Name = "Timer callback";
timerEvent.Start();
}
/// <summary>Raises the timer on a separate thread</summary>
protected void RunThread(TimeSpan elapsedTime)
{
if (this.threadLock.WaitOne(0))
{
this.elapsed(elapsedTime);
this.threadLock.ReleaseMutex();
}
}
Conclusion
After 3 weeks, I'm not ashamed to say I'm baffled by this behavior, and looking for any sort of input for identifying a race condition, something that I might be missing, architecturally speaking, about Direc2D or where I'm just showing my ignorance.
All locks statements in the code seems to be an over complicated solution. Also creating COM objects (like Direct2D1) from constructors and using them from thread UI methods is usually not a good practice.
I would simplify your application by performing all device/objects creation and rendering on the same thread, also by keeping your rendering code outside the form. Ideally, this should be totally independent, as you could render to any kind of windows/controls via the HWND or to a DXGI surface, or to a Windows 8 Metro surface.
Also, as Aren suggest, instead of using your own Direct2D1 wrapper, you should rely on an existing robust wrapper like SharpDX. It will save you to maintain a costly layer and help you to focus on your specific application.

After Writing to a RenderTarget, How to Efficiently Clone the Output?

XNA noob here, learning every day. I just worked out how to composite multiple textures into one using a RenderTarget2D. However, while I can use the RenderTarget2D as a Texture2D for most purposes, there's a critical difference: these rendered textures are lost when the backbuffer is resized (and no doubt under other circumstances, like the graphics device running low on memory).
For the moment, I'm just copying the finished RenderTarget2D into a new non-volatile Texture2D object. My code to do so is pretty fugly, though. Is there a more graceful way to do this? Maybe I'm just tired but I can't find the answer on Google or SO.
Slightly simplified:
public static Texture2D MergeTextures(int width, int height, IEnumerable<Tuple<Texture2D, Color>> textures)
{
RenderTarget2D buffer = new RenderTarget2D(_device, width, height);
_device.SetRenderTarget(buffer);
_device.Clear(Color.Transparent);
SpriteBatch spriteBatch = new SpriteBatch(_device);
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied);
// Paint each texture over the one before, in the appropriate color
Rectangle rectangle = new Rectangle(0, 0, width, height);
foreach (Tuple<Texture2D, Color> texture in textures)
spriteBatch.Draw(texture.Item1, rectangle, texture.Item2);
spriteBatch.End();
_device.SetRenderTarget((RenderTarget2D)null);
// Write the merged texture to a Texture2D, so we don't lose it when resizing the back buffer
// This is POWERFUL ugly code, and probably terribly, terribly slow
Texture2D mergedTexture = new Texture2D(_device, width, height);
Color[] content = new Color[width * height];
buffer.GetData<Color>(content);
mergedTexture.SetData<Color>(content);
return mergedTexture;
}
I suppose I should check for IsContentLost and re-render as needed, but this happens in the middle of my main drawing loop, and of course you can't nest SpriteBatches. I could maintain a "render TODO" list, handle those after the main SpriteBatch ends, and then they'd be available for the next frame. Is that the preferred strategy?
This code is only called a few times, so performance isn't a concern, but I'd like to learn how to do things right.
Actually your code is not so bad if you're generating textures in a once-off process when you'd normally load content (game start, level change, room change, etc). You're transferring textures between CPU and GPU, same thing you'd be doing loading plain ol' textures. It's simple and it works!
If you're generating your textures more frequently, and it starts to become a per-frame cost, rather than a load-time cost, then you will want to worry about its performance and perhaps keeping them as render targets.
You shouldn't get ContentLost in the middle of drawing, so you can safely just respond to that event and recreate the render targets then. Or you can check for IsContentLost on each of them, ideally at the start of your frame before you render anything else. Either way everything should be checked before your SpriteBatch begins.
(Normally when using render targets you're regenerating them each frame anyway, so you don't need to check them in that case.)
Replace
Texture2D mergedTexture = new Texture2D(_device, width, height);
Color[] content = new Color[width * height];
buffer.GetData<Color>(content);
mergedTexture.SetData<Color>(content);
return mergedTexture;
with
return buffer;
Because RenderTarget2D extends Texture2D you will just get Texture2D class data returned. Also in case you are interested here's a class i made for building my GUI library's widgets out of multiple textures. In case you need to be doing this sort of thing a lot.
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.IO;
namespace Voodo.Utils {
/// <summary>
///
/// </summary>
public class TextureBaker {
private readonly SpriteBatch _batch;
private readonly RenderTarget2D _renderTarget;
private readonly GraphicsDevice _graphicsDevice;
/// <summary>
///
/// </summary>
public Rectangle Bounds {
get { return _renderTarget.Bounds; }
}
/// <summary>
///
/// </summary>
/// <param name="graphicsDevice"></param>
/// <param name="size"></param>
public TextureBaker(GraphicsDevice graphicsDevice, Vector2 size) {
_graphicsDevice = graphicsDevice;
_batch = new SpriteBatch(_graphicsDevice);
_renderTarget = new RenderTarget2D(
_graphicsDevice,
(int)size.X,
(int)size.Y);
_graphicsDevice.SetRenderTarget(_renderTarget);
_graphicsDevice.Clear(Color.Transparent);
_batch.Begin(
SpriteSortMode.Immediate,
BlendState.AlphaBlend,
SamplerState.LinearClamp,
DepthStencilState.Default,
RasterizerState.CullNone);
}
#region Texture2D baking
/// <summary>
///
/// </summary>
/// <param name="texture"></param>
public void BakeTexture(Texture2D texture) {
_batch.Draw(
texture,
new Rectangle(0, 0, Bounds.Width, Bounds.Height),
Color.White);
}
/// <summary>
///
/// </summary>
/// <param name="texture"></param>
/// <param name="destination"></param>
public void BakeTexture(Texture2D texture, Rectangle destination) {
_batch.Draw(
texture,
destination,
Color.White);
}
/// <summary>
///
/// </summary>
/// <param name="texture"></param>
/// <param name="destination"></param>
/// <param name="source"></param>
public void BakeTexture(Texture2D texture, Rectangle destination, Rectangle source) {
_batch.Draw(
texture,
destination,
source,
Color.White);
}
/// <summary>
///
/// </summary>
/// <param name="texture"></param>
/// <param name="sourceModification"></param>
/// <param name="destination"></param>
public void BakeTexture(Texture2D texture, System.Drawing.RotateFlipType sourceModification, Rectangle destination) {
Stream sourceBuffer = new MemoryStream();
texture.SaveAsPng(sourceBuffer, texture.Width, texture.Height);
System.Drawing.Image sourceImage = System.Drawing.Image.FromStream(sourceBuffer);
sourceBuffer = new MemoryStream();
sourceImage.RotateFlip(sourceModification);
sourceImage.Save(sourceBuffer, System.Drawing.Imaging.ImageFormat.Png);
_batch.Draw(
Texture2D.FromStream(_graphicsDevice, sourceBuffer),
destination,
Color.White);
}
/// <summary>
///
/// </summary>
/// <param name="texture"></param>
/// <param name="sourceModification"></param>
/// <param name="destination"></param>
/// <param name="source"></param>
public void BakeTexture(Texture2D texture, System.Drawing.RotateFlipType sourceModification, Rectangle destination, Rectangle source) {
Stream sourceBuffer = new MemoryStream();
texture.SaveAsPng(sourceBuffer, texture.Width, texture.Height);
System.Drawing.Image sourceImage = System.Drawing.Image.FromStream(sourceBuffer);
sourceBuffer = new MemoryStream();
sourceImage.RotateFlip(sourceModification);
sourceImage.Save(sourceBuffer, System.Drawing.Imaging.ImageFormat.Png);
_batch.Draw(
Texture2D.FromStream(_graphicsDevice, sourceBuffer),
destination,
source,
Color.White);
}
#endregion
#region SpriteFont baking
/// <summary>
///
/// </summary>
/// <param name="font"></param>
/// <param name="text"></param>
/// <param name="location"></param>
/// <param name="textColor"></param>
public void BakeText(SpriteFont font, string text, Vector2 location, Color textColor) {
_batch.DrawString(font, text, location, textColor);
}
/// <summary>
///
/// </summary>
/// <param name="font"></param>
/// <param name="text"></param>
/// <param name="location"></param>
public void BakeTextCentered(SpriteFont font, string text, Vector2 location, Color textColor) {
var shifted = new Vector2 {
X = location.X - font.MeasureString(text).X / 2,
Y = location.Y - font.MeasureString(text).Y / 2
};
_batch.DrawString(font, text, shifted, textColor);
}
#endregion
/// <summary>
///
/// </summary>
/// <returns></returns>
public Texture2D GetTexture() {
_batch.End();
_graphicsDevice.SetRenderTarget(null);
return _renderTarget;
}
}
}
if you are having problems with your rendertarget being dynamically resized when drawing it somewhere else, you could just have an off-screen rendertarget with a set size that you copy your finished RT to like this:
Rendertarget2D offscreenRT = new RenderTarget2D(_device, width, height);
_device.SetRenderTarget(offscreenRT);
_device.Clear(Color.Transparent);
SpriteBatch spriteBatch = new SpriteBatch(_device);
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied);
spriteBatch.Draw(buffer, Vector2.Zero, Color.White);
spriteBatch.End();
_device.SetRenderTarget(null);

High Quality Image Scaling Library [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking us to recommend or find a book, tool, software library, tutorial or other off-site resource are off-topic for Stack Overflow as they tend to attract opinionated answers and spam. Instead, describe the problem and what has been done so far to solve it.
Closed 8 years ago.
Improve this question
I want to scale an image in C# with quality level as good as Photoshop does. Is there any C# image processing library available to do this thing?
Here's a nicely commented Image Manipulation helper class that you can look at and use. I wrote it as an example of how to perform certain image manipulation tasks in C#. You'll be interested in the ResizeImage function that takes a System.Drawing.Image, the width and the height as the arguments.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
namespace DoctaJonez.Drawing.Imaging
{
/// <summary>
/// Provides various image untilities, such as high quality resizing and the ability to save a JPEG.
/// </summary>
public static class ImageUtilities
{
/// <summary>
/// A quick lookup for getting image encoders
/// </summary>
private static Dictionary<string, ImageCodecInfo> encoders = null;
/// <summary>
/// A lock to prevent concurrency issues loading the encoders.
/// </summary>
private static object encodersLock = new object();
/// <summary>
/// A quick lookup for getting image encoders
/// </summary>
public static Dictionary<string, ImageCodecInfo> Encoders
{
//get accessor that creates the dictionary on demand
get
{
//if the quick lookup isn't initialised, initialise it
if (encoders == null)
{
//protect against concurrency issues
lock (encodersLock)
{
//check again, we might not have been the first person to acquire the lock (see the double checked lock pattern)
if (encoders == null)
{
encoders = new Dictionary<string, ImageCodecInfo>();
//get all the codecs
foreach (ImageCodecInfo codec in ImageCodecInfo.GetImageEncoders())
{
//add each codec to the quick lookup
encoders.Add(codec.MimeType.ToLower(), codec);
}
}
}
}
//return the lookup
return encoders;
}
}
/// <summary>
/// Resize the image to the specified width and height.
/// </summary>
/// <param name="image">The image to resize.</param>
/// <param name="width">The width to resize to.</param>
/// <param name="height">The height to resize to.</param>
/// <returns>The resized image.</returns>
public static System.Drawing.Bitmap ResizeImage(System.Drawing.Image image, int width, int height)
{
//a holder for the result
Bitmap result = new Bitmap(width, height);
//set the resolutions the same to avoid cropping due to resolution differences
result.SetResolution(image.HorizontalResolution, image.VerticalResolution);
//use a graphics object to draw the resized image into the bitmap
using (Graphics graphics = Graphics.FromImage(result))
{
//set the resize quality modes to high quality
graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
//draw the image into the target bitmap
graphics.DrawImage(image, 0, 0, result.Width, result.Height);
}
//return the resulting bitmap
return result;
}
/// <summary>
/// Saves an image as a jpeg image, with the given quality
/// </summary>
/// <param name="path">Path to which the image would be saved.</param>
/// <param name="quality">An integer from 0 to 100, with 100 being the
/// highest quality</param>
/// <exception cref="ArgumentOutOfRangeException">
/// An invalid value was entered for image quality.
/// </exception>
public static void SaveJpeg(string path, Image image, int quality)
{
//ensure the quality is within the correct range
if ((quality < 0) || (quality > 100))
{
//create the error message
string error = string.Format("Jpeg image quality must be between 0 and 100, with 100 being the highest quality. A value of {0} was specified.", quality);
//throw a helpful exception
throw new ArgumentOutOfRangeException(error);
}
//create an encoder parameter for the image quality
EncoderParameter qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
//get the jpeg codec
ImageCodecInfo jpegCodec = GetEncoderInfo("image/jpeg");
//create a collection of all parameters that we will pass to the encoder
EncoderParameters encoderParams = new EncoderParameters(1);
//set the quality parameter for the codec
encoderParams.Param[0] = qualityParam;
//save the image using the codec and the parameters
image.Save(path, jpegCodec, encoderParams);
}
/// <summary>
/// Returns the image codec with the given mime type
/// </summary>
public static ImageCodecInfo GetEncoderInfo(string mimeType)
{
//do a case insensitive search for the mime type
string lookupKey = mimeType.ToLower();
//the codec to return, default to null
ImageCodecInfo foundCodec = null;
//if we have the encoder, get it to return
if (Encoders.ContainsKey(lookupKey))
{
//pull the codec from the lookup
foundCodec = Encoders[lookupKey];
}
return foundCodec;
}
}
}
Update
A few people have been asking in the comments for samples of how to consume the ImageUtilities class, so here you go.
//resize the image to the specified height and width
using (var resized = ImageUtilities.ResizeImage(image, 50, 100))
{
//save the resized image as a jpeg with a quality of 90
ImageUtilities.SaveJpeg(#"C:\myimage.jpeg", resized, 90);
}
Note
Remember that images are disposable, so you need to assign the result of your resize to a using declaration (or you could use a try finally and make sure you call dispose in your finally).
When you draw the image using GDI+ it scales quite well in my opinion. You can use this to create a scaled image.
If you want to scale your image with GDI+ you can do something like this:
Bitmap original = ...
Bitmap scaled = new Bitmap(new Size(original.Width * 4, original.Height * 4));
using (Graphics graphics = Graphics.FromImage(scaled)) {
graphics.DrawImage(original, new Rectangle(0, 0, scaled.Width, scaled.Height));
}
Tested libraries like Imagemagick and GD are available for .NET
You could also read up on things like bicubic interpolation and write your own.
CodeProject articles discussing and sharing source code for scaling images:
Two Pass Scaling using Filters
Matrix Transformation of Images in C#, using .NET GDI+
Resizing a Photographic image with GDI+ for .NET
Fast Dyadic Image Scaling with Haar Transform
Use this library: http://imageresizing.net
Have a read of this article by the library author: 20 Image Sizing Pitfalls with .NET
Try the different values for Graphics.InterpolationMode. There are several typical scaling algorithms available in GDI+. If one of these is sufficient for your need, you can go this route instead of relying on an external library.
You can try dotImage, one of my company's products, which includes an object for resampling images that has 18 filter types for various levels of quality.
Typical usage is:
// BiCubic is one technique available in PhotoShop
ResampleCommand resampler = new ResampleCommand(newSize, ResampleMethod.BiCubic);
AtalaImage newImage = resampler.Apply(oldImage).Image;
in addition, dotImage includes 140 some odd image processing commands including many filters similar to those in PhotoShop, if that's what you're looking for.
This might help
public Image ResizeImage(Image source, RectangleF destinationBounds)
{
RectangleF sourceBounds = new RectangleF(0.0f,0.0f,(float)source.Width, (float)source.Height);
RectangleF scaleBounds = new RectangleF();
Image destinationImage = new Bitmap((int)destinationBounds.Width, (int)destinationBounds.Height);
Graphics graph = Graphics.FromImage(destinationImage);
graph.InterpolationMode =
System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
// Fill with background color
graph.FillRectangle(new SolidBrush(System.Drawing.Color.White), destinationBounds);
float resizeRatio, sourceRatio;
float scaleWidth, scaleHeight;
sourceRatio = (float)source.Width / (float)source.Height;
if (sourceRatio >= 1.0f)
{
//landscape
resizeRatio = destinationBounds.Width / sourceBounds.Width;
scaleWidth = destinationBounds.Width;
scaleHeight = sourceBounds.Height * resizeRatio;
float trimValue = destinationBounds.Height - scaleHeight;
graph.DrawImage(source, 0, (trimValue / 2), destinationBounds.Width, scaleHeight);
}
else
{
//portrait
resizeRatio = destinationBounds.Height/sourceBounds.Height;
scaleWidth = sourceBounds.Width * resizeRatio;
scaleHeight = destinationBounds.Height;
float trimValue = destinationBounds.Width - scaleWidth;
graph.DrawImage(source, (trimValue / 2), 0, scaleWidth, destinationBounds.Height);
}
return destinationImage;
}
Note the InterpolationMode.HighQualityBicubic -> this is generally a good tradeoff between performance and results.
Try This basic code snippet:
private static Bitmap ResizeBitmap(Bitmap srcbmp, int width, int height )
{
Bitmap newimage = new Bitmap(width, height);
using (Graphics g = Graphics.FromImage(newimage))
g.DrawImage(srcbmp, 0, 0, width, height);
return newimage;
}
There's an article on Code Project about using GDI+ for .NET to do photo resizing using, say, Bicubic interpolation.
There was also another article about this topic on another blog (MS employee, I think), but I can't find the link anywhere. :( Perhaps someone else can find it?
you could try this one if it's a lowres cgi
2D Image Filter
This is an article I spotted being referenced in Paint.NET's code for image resampling: Various Simple Image Processing Techniques by Paul Bourke.
You could try the magic kernel. It produces less pixelation artifacts than bicubic resample when upscaling and it also gives very good results when downscaling.
The source code is available in c# from the web site.
I have some improve for Doctor Jones's answer.
It works for who wanted to how to proportional resize the image. It tested and worked for me.
The methods of class I added:
public static System.Drawing.Bitmap ResizeImage(System.Drawing.Image image, Size size)
{
return ResizeImage(image, size.Width, size.Height);
}
public static Size GetProportionedSize(Image image, int maxWidth, int maxHeight, bool withProportion)
{
if (withProportion)
{
double sourceWidth = image.Width;
double sourceHeight = image.Height;
if (sourceWidth < maxWidth && sourceHeight < maxHeight)
{
maxWidth = (int)sourceWidth;
maxHeight = (int)sourceHeight;
}
else
{
double aspect = sourceHeight / sourceWidth;
if (sourceWidth < sourceHeight)
{
maxWidth = Convert.ToInt32(Math.Round((maxHeight / aspect), 0));
}
else
{
maxHeight = Convert.ToInt32(Math.Round((maxWidth * aspect), 0));
}
}
}
return new Size(maxWidth, maxHeight);
}
and new available using according to this codes:
using (var resized = ImageUtilities.ResizeImage(image, ImageUtilities.GetProportionedSize(image, 50, 100)))
{
ImageUtilities.SaveJpeg(#"C:\myimage.jpeg", resized, 90);
}

Categories