I'm using a TransformedBitmap class to draw scaled images to a Bitmap using TransformedBitmap.CopyPixels. Is there a way to specify the scaling mode that is used? RenderOptions.SetBitmapScalingMode doesn't seem to affect anything. I would like to use nearest neighbor but it appears to use a bi linear filter of some sort.
It is not possible to specify the scaling algorithm, it is by design.
The RenderOptions.SetBitmapScalingMode applies to rendering only, e.g. you have a 32*32 icon and want to show it at 256*256 but still in a blocky way (nearest neighbour)
Update
A few ways on how you could overcome this issue :
Do it by yourself :
http://tech-algorithm.com/articles/nearest-neighbor-image-scaling/
Using Forms :
https://stackoverflow.com/a/1856362/361899
Custom drawing :
How to specify the image scaling algorithm used by a WPF Image?
There is AForge too but that might be overkill for your needs.
Update 2
WriteableBitmapEx will probably do the job easily for you : http://writeablebitmapex.codeplex.com/
You can resize a WriteableBitmap, specify interpolation mode and there is nearest neighbor.
Both TransformedBitmap and WriteableBitmapEx inherit from BitmapSource, likely you'll have no change to make at all to your existing code.
public static class Extensions
{
public static BitmapFrame Resize(this
BitmapSource photo, int width, int height,
BitmapScalingMode scalingMode)
{
var group = new DrawingGroup();
RenderOptions.SetBitmapScalingMode(
group, scalingMode);
group.Children.Add(
new ImageDrawing(photo,
new Rect(0, 0, width, height)));
var targetVisual = new DrawingVisual();
var targetContext = targetVisual.RenderOpen();
targetContext.DrawDrawing(group);
var target = new RenderTargetBitmap(
width, height, 96, 96, PixelFormats.Default);
targetContext.Close();
target.Render(targetVisual);
var targetFrame = BitmapFrame.Create(target);
return targetFrame;
}
}
Took from http://weblogs.asp.net/bleroy/resizing-images-from-the-server-using-wpf-wic-instead-of-gdi
Related
What I'm trying to do:
Since in my bitmaps there are some unwanted white edges around the picture that result from anti-aliasing as pointed out from another user from stackoverflow.
I'm trying to convert an image that's inputted into a bitmap, convert bitmap into a Graphics object so that I can set the Smooth Mode to none, and then finally convert that Graphics object to a bitmap so that it can be copied by the user after setting it to the clipboard. I'm not sure if this is a good way of getting rid anti-aliasing in bitmaps but I'm definitely interested in improvements and suggestions.
The issue I'm facing:
The result of the image after is completely blank and does not contain any of the pixels that are previously found in the original bitmap. Here's the result:
This issue applies to all pictures no matter what their format is.
My code:
public PicGen(PictureBox pictureBox)
{
Clipboard.Clear();
Bitmap firstImage = new(pictureBox.Image, pictureBox.Width, pictureBox.Height);
RectangleF cloneRect = new RectangleF(0, 0, firstImage.Width, firstImage.Height);
System.Drawing.Imaging.PixelFormat format = firstImage.PixelFormat;
Bitmap cloneBitmap = firstImage.Clone(cloneRect, format);
Graphics AntiARemover = Graphics.FromImage(cloneBitmap);
AntiARemover.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
Bitmap finalImg = new(52, 52, AntiARemover);
Clipboard.SetImage(finalImg);
Color backColorBottom = firstImage.GetPixel(0, 0);
firstImage.ReplaceColor(backColorBottom, Color.FromArgb(54, 57, 63));
Bitmap finalImg = new(52, 52, AntiARemover);
From the documentation for this bitmap constructor:
The new Bitmap that this method creates takes its horizontal and vertical resolution from the DpiX and DpiY properties of g, respectively.
If you want create a new image with the content from another you need to call one of the DrawImage methods. You should also dispose your graphics object, and any temporary bitmaps you may use.
using var finalImg = new Bitmap(52,52);
using var graphics = Graphics.FromImage(finalImg);
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
graphics.DrawImage(cloneBitmap)
However, edge artifacts typically occur when combining two images using an alpha channel, see Premultiplied alpha. In your example I can only see one input image, so I'm really not sure what it is you are actually trying to do. If you need to convert to premultiplied alpha you can use the following code to convert the color for each pixel
premultiplied.R = (byte)(straight.R * straight.A / 255);
premultiplied.G = (byte)(straight.G * straight.A / 255);
premultiplied.B = (byte)(straight.B * straight.A / 255);
premultiplied.A = straight.A;
Bitmap image = ReadBitmap("image.png");
Bitmap imageCopy = new Bitmap(image);
Bitmap canvas = new Bitmap(imageCopy.Width+100, imageCopy.Height);
// From this bitmap, the graphics can be obtained, because it has the right PixelFormat
using(Graphics g = Graphics.FromImage(canvas))
{
// Draw the original bitmap onto the graphics of the new bitmap
g.DrawImage(image, 0, 0);
}
// Use tempBitmap as you would have used originalBmp
InputPictureBox.Image = image;
OutputPictureBox.Image = canvas;
I haven't understood the output of this c# code.
The original image is not placed at the correct position. It should have been at (0, 0).
Also, I need a black background.
So, what is going on and how to correct this?
You are loading an Image, then a copy of this source is created using:
Bitmap bitmap = new Bitmap();
When you create a copy of an Image this way, you sacrifice/alter some details:
Dpi Resolution: if not otherwise specified, the resolution is set to the UI resolution. 96 Dpi, as a standard; it might be different with different screen resolutions and scaling. The System in use also affects this value (Windows 7 and Windows 10 will probably/possibly provide different values)
PixelFormat: If not directly copied from the Image source or explicitly specified, the PixelFormat is set to PixelFormat.Format32bppArgb.
From what you were saying, you probably wanted something like this:
var imageSource = Image.FromStream(new MemoryStream(File.ReadAllBytes(#"[SomeImageOfLena]"))), true, false)
var imageCopy = new Bitmap(imageSource.Width + 100, imageSource.Height, imageSource.PixelFormat))
imageCopy.SetResolution(imageSource.HorizontalResolution, imageSource.VerticalResolution);
using (var g = Graphics.FromImage(imageCopy)) {
g.Clear(Color.Black);
g.CompositingMode = CompositingMode.SourceCopy;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(imageSource, (imageCopy.Width - imageSource.Width) / 2, 0);
pictureBox1.Image?.Dispose();
pictureBox2.Image?.Dispose();
pictureBox1.Image = imageSource;
pictureBox2.Image = imageCopy;
}
This is the result:
(The upper/lower frame black color is actually the Picturebox background color)
When the original Image Dpi Resolution is different from the base Dpi Resolution used when creating an Image copy with new Bitmap(), your results may be different from what is expected.
This is what happens with a source Image of 150, 96 and 72 Dpi in the same scenario:
Another important detail is the IDisposable nature of the Image object.
When you create one, you have to Dispose() of it; explicitly, calling the Dispose method, or implicitly, enclosing the Image contructor in a Using statement.
Also, possibly, don't assign an Image object directly loaded from a FileStream.
GDI+ will lock the file, and you will not be able to copy, move or delete it.
With the file, all resources tied to the Images will also be locked.
Make a copy with new Bitmap() (if you don't care of the above mentioned details), or with Image.Clone(), which will preserve the Image Dpi Resolution and PixelFormat.
I am not completely clear on what you are actually needing to do. But anyway, here is a WPF-friendly example of how to draw an image at a specific position inside another image.
Note if all you want to do is display the image in different size and/or put a black border around it, there are much simpler ways to do simply that, without having to create a second image, such as just laying out the image inside a panel that already has the border style you want.
Notice that I am using classes from the System.Windows.Media namespace because that is what WPF uses. These don't mix easily with the older classes from System.Drawing namespace (some of the class names conflict, and Microsoft's .Net framework lacks built-in methods for converting objects between those types), so normally one needs to simply decide whether to use one or the other sets of drawing tools. I assume you have been trying to use System.Drawing. Each has its own pros and cons that would take too long to explain here.
// using System.Windows.Media;
// using System.Windows.Media.Imaging;
private void DrawTwoImages()
{
// For InputPictureBox
var file = new Uri("C:\\image.png");
var inputImage = new BitmapImage(file);
// If your image is stored in a Resource Dictionary, instead use:
// var inputImage = (BitmapImage) Resources["image.png"];
InputPicture.Source = inputImage;
// imageCopy isn't actually needed for this example.
// But since you had it in yours, here is how it's done, anyway.
var imageCopy = inputImage.Clone();
// Parameters for setting up our output picture
int leftMargin = 50;
int topMargin = 5;
int rightMargin = 50;
int bottomMargin = 5;
int width = inputImage.PixelWidth + leftMargin + rightMargin;
int height = inputImage.PixelHeight + topMargin + bottomMargin;
var backgroundColor = Brushes.Black;
var borderColor = (Pen) null;
// Use a DrawingVisual and DrawingContext for drawing
DrawingVisual dv = new DrawingVisual();
using (DrawingContext dc = dv.RenderOpen())
{
// Draw the black background
dc.DrawRectangle(backgroundColor, borderColor, new Rect(0, 0, width, height));
// Copy input image onto output image at desired position
dc.DrawImage(inputImage, new Rect(leftMargin, topMargin,
inputImage.PixelWidth, inputImage.PixelHeight));
}
// For displaying output image
var rtb = new RenderTargetBitmap( width, height, 96, 96, PixelFormats.Pbgra32 );
rtb.Render(dv);
OutputPicture.Source = rtb;
}
I need to shrink multiple images that contain text. Because of the text they need to be shrunk in such a way as to retain the sharp edges of the text and not smoothed. My first attempt was the following:
RenderOptions.SetBitmapScalingMode(upgradeCard, BitmapScalingMode.HighQuality);
upgradeCard.Height(resizedHeight);
upgradeCard.Width(resizedWidth);
The result was too blurry, the text was hard to read. It was, however, really really fast. I then tried this:
public static class ImageResizer
{
public static Image Resize(Image image, Size size)
{
if (image == null || size.IsEmpty)
return null;
var resizedImage = new Bitmap(size.Width, size.Height, image.PixelFormat);
resizedImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (var graphics = Graphics.FromImage(resizedImage))
{
var location = new Point(0, 0);
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.None;
graphics.DrawImage(image, new Rectangle(location, size),
new Rectangle(location, image.Size), GraphicsUnit.Pixel);
}
return resizedImage;
}
}
This worked really well, almost as good as Photoshop Bicubic Sharper. Unfortunately it was also very slow. Way too slow for what I need.
Is there any other way of doing this that produces the results of the second method but does so fairly quickly?
Without examples of your images it's hard to give reliable advice.
For example, how much contrast is in your images already? By what factor are you reducing them?
You could try nearest neighbour scaling, which can be very fast, and then try blurring the output slightly with a Gaussian filter, or similar. If that's too aliased, you could also try linear scaling with a soft blur.
I am trying to make a retro game engine in C#. I want to use a resolution of 320x200, but the screen does not natively support that, so I'm trying to decide what is the most efficient method of emulating that. Do I create a Bitmap object and then use SetPixel and create methods for drawing basic shapes? Then scale the image to the size of the screen. Should I draw little Rectangle objects instead to mimic the pixels? What do you think would be the most efficient? Also any other ideas?
You could just use the DrawImage of the Graphics object to paint your 320x200 bitmap on a rectangle of any size.
By setting the interpolation mode on the graphics object first, you can control the way the image is painted when resized. Different interpolation modes should give different visual results and chances are you will be satisfied with one of built-in modes so that you don't have to provide any custom implementation of the streching algorithm.
On the other hand, have you considered OpenGL/DirectX rather than GDI+?
If you must use GDI+ then let it handle the scaling using this method can leverage hardware acceleration if available as opposed to drawing into bitmaps. But agreed with other poster there are better frameworks for this have a look at XNA.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var gameTick = new Timer {Interval = 10};
gameTick.Tick += (s, e) => BeginInvoke((Action)(Invalidate));
gameTick.Start();
}
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
//calculate the scale ratio to fit a 320x200 box in the form
var width = g.VisibleClipBounds.Width;
var height = g.VisibleClipBounds.Height;
var widthRatio = width / 320f;
var heightRatio = height / 200f;
var scaleRatio = Math.Min(widthRatio, heightRatio);
g.ScaleTransform(scaleRatio, scaleRatio);
//draw a 320x200 rectangle (because of scale transform this always fills form)
g.FillRectangle(Brushes.Gray, 0, 0, 320, 200);
}
}
I am using WPF to generate tile images for my WinRT app. I have a tile UserControl which is converted to PNG and this works well (It does, please don't tell me otherwise). WinRT passes me a scale property 100% (320x150), 140% (434x210) and 180% (558x270) which I use to generate the correct size of image.
For when I want to use images in my tile. I have replicated the image selection functionality of WinRT (You can supply thee scales of images and WinRT apps automatically select the correct scale) in my tile UserControl's code behind. So depending on the scale I select a bigger or smaller image source. However, on the larger scales my font size remains the same size and looks really small. I don't think I need to change the font size based on the scale as this is not what happens in WinRT, so it must be the code I'm using to convert my UserControl to a PNG and something to do with DPI. Here is my conversion Code:
// I pass in 320x150 or 434x210 or 558x270 depending on the scale.
public static MemoryStream ToPng(
this FrameworkElement frameworkElement,
double width,
double height)
{
BitmapSource bitmapSource = ToBitmapSource(frameworkElement, width, height);
PngBitmapEncoder pngBitmapEncoder = new PngBitmapEncoder();
pngBitmapEncoder.Frames.Add(BitmapFrame.Create(bitmapSource));
MemoryStream memoryStream = new MemoryStream();
pngBitmapEncoder.Save(memoryStream);
memoryStream.Position = 0;
return memoryStream;
}
public static BitmapSource ToBitmapSource(
this FrameworkElement frameworkElement,
double width,
double height)
{
Size renderingSize = new Size(width, height);
frameworkElement.Measure(renderingSize);
Rect renderingRectangle = new Rect(new Point(0, 0), renderingSize);
frameworkElement.Arrange(renderingRectangle);
frameworkElement.UpdateLayout();
Rect bounds = VisualTreeHelper.GetDescendantBounds(frameworkElement);
RenderTargetBitmap renderBitmap = new RenderTargetBitmap(
(int)frameworkElement.ActualWidth,
(int)frameworkElement.ActualHeight,
96,
96,
PixelFormats.Pbgra32);
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
VisualBrush visualBrush = new VisualBrush(frameworkElement);
drawingContext.DrawRectangle(visualBrush, null, new Rect(new Point(), bounds.Size));
}
renderBitmap.Render(drawingVisual);
return renderBitmap;
}
Thanks for any help. Much appreciated.
I needed to change the above code so that I measure and arrange the frameworkElement as 310x150. I then render it to the final scale size e.g. 558x270 and set the DPI to (96/100)*scale where the scale is 180 in this case.