Draw a colour bitmap as greyscale in WPF - c#

I'm looking for a way to draw a bitmap to a WPF DrawingContext as greyscale. I'd like to be able to draw it at the given x,y location and scale it to a given width and height. 256 levels of grey (8-bit greyscale) is good enough. I have a colour bitmap file on disk which will be either bmp, png or jpg format.

using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
void DrawBitmapGreyscale(DrawingContext dc, string filename, int x, int y, int width, int height)
{
// Load the bitmap into a bitmap image object
BitmapImage bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.UriSource = new Uri(filename);
bitmap.EndInit();
// Convert the bitmap to greyscale, and draw it.
FormatConvertedBitmap bitmapGreyscale = new FormatConvertedBitmap(bitmap, PixelFormats.Gray8, BitmapPalettes.Gray256, 0.0);
dc.DrawImage(bitmapGreyscale, new Rect(x, y, width, height));
}

You can do this using an effect - possibly not as performant as pure code, but offers flexibility.
<Image Source="../Images/ChartSample.png" Stretch="Uniform" Margin="5">
<Image.Effect>
<ee:ColorToneEffect DarkColor="Black" LightColor="White" ToneAmount="0" Desaturation="1" />
</Image.Effect>
Where you reference namespace as
xmlns:ee=”http://schemas.microsoft.com/expression/2010/effects”

Related

WPF thumbnail image is blurry

I have a WPF app which saves out a thumbnail image as png. The code works well enough but when I open the image is very blurry. The image that it grabs comes from the canvas itself. The canvas changes its width and height depending on the image I'm loading. The desired thumbnail size will be 200 x 200 (pixels).
Here is my code
public void CreateThumbail(Canvas canvas, string filename)
{
RenderTargetBitmap rtb = new RenderTargetBitmap(
(int)canvas.ActualWidth,
(int)canvas.ActualHeight,
96, //dip X
96, //dpi Y
PixelFormats.Pbgra32);
rtb.Render(canvas);
PngBitmapEncoder pngImage = new PngBitmapEncoder();
pngImage.Frames.Add(CreateResizedImage(rtb, 200, 200, 0));
using (var filestream = System.IO.File.Create(filename))
{
pngImage.Save(filestream);
}
}
private static BitmapFrame CreateResizedImage(ImageSource source, int width, int height, int margin)
{
var rect = new Rect(margin, margin, width, height);
var group = new DrawingGroup();
RenderOptions.SetBitmapScalingMode(group, BitmapScalingMode.HighQuality);
group.Children.Add(new ImageDrawing(source, rect));
var drawingVisual = new DrawingVisual();
using (var drawingContext = drawingVisual.RenderOpen())
drawingContext.DrawDrawing(group);
var resizedImage = new RenderTargetBitmap(
(int)rect.Width, (int)rect.Height,// Resized dimensions
96, 96, // Default DPI values
PixelFormats.Pbgra32); // Default pixel format
resizedImage.Render(drawingVisual);
return BitmapFrame.Create(resizedImage);
}
I saved out the image before I resize it and it looks crisp and sharp. yet when I save out the thumbnail it's ugly and blurry. What am I doing wrong? Am I over-engineering this? Many thanks in advance.
Probably you need to respect the original dimensions of the image. For instance an image that is 400x400 will downscale to 200x200 quite nicely, but an image that is 235x235 will not.
This doesn't consider images that are not square to begin with.
You might try reducing the image height and width by a good factor (I would start by halving) repeatedly until the image is smaller than 200x200 and then padding it with white or transparent.
Image processing can be quite hard. It's not something I'm an expert in either so I'd probably try a 3rd party library like this one I just found on google: https://imageprocessor.org/

Cropping a bitonal image in C#

I have code which I use to crop an image to a specified size. It does the job apart from the fact that, when I crop a bitonal image, it converts it to 24-bit colour which I don't want. Here's a snippet from the code ...
using (var Bmp = new Bitmap(FileImage.Width, Height))
{
using (var Graphic = Graphics.FromImage(Bmp))
{
var MemoryStreamTemp = new MemoryStream();
Graphic.DrawImage(FileImage, new Rectangle(0, 0, FileImage.Width, Height), x, CheckY, FileImage.Width, Height, GraphicsUnit.Pixel);
Bmp.Save(MemoryStreamTemp, ImageFormat.Tiff);
When bmp is created, it defaults to 24-bit colour. I realise that I can specify 1bpp indexed but that causes an exception when the Graphic object is instantiated.
I could convert the bitmap back to bitonal after it is cropped but that seems an unnecessary step that I would prefer to avoid.
Incidentally, I have a similar problem with the file's compression and resolution but that's for another day.

Wpf partial image display

I need some tips for a little project.
i am NOT trying to make a game of some sort, but i probably need some game-making techniques.
I have to make a minimap viewer in wpf.
I have a set of map jpg files, but i only have to show a portion of them in a smaller rectangle that i draw on the app window.
The goal is to make a minimap like in most games where you can only see a set portion of the whole map.
I am fairly new in the C# wpf environment so please be specific and if you give some code please explain what is not obvious.
Thank you all.
If you have an Image you can crop it like this:
public static Bitmap CropImage(Image source, int x,int y,int width,int height)
{
Rectangle crop = new Rectangle(x, y, width, height);
var bmp = new Bitmap(crop.Width, crop.Height);
using (var gr = Graphics.FromImage(bmp))
{
gr.DrawImage(source, new Rectangle(0, 0, bmp.Width, bmp.Height), crop, GraphicsUnit.Pixel);
}
return bmp;
}

Is there a way to overlay or merge a Drawing.Bitmap and a DrawingImage

In a windows forms application, I have as input a Drawing.Bitmap and a DrawingImage. I need to overlay them and put there output in a Controls.Image. How can I do this?
It doesn't matter if you use Image object or Bitmap object, The Drawing.Image is abstract class and Drawing.Bitmap inherited from it. to
draw image over image, get the graphics object from the base image and then use Graphics.DrawImage which accept parameter of type Image.
So you have two images here, one should be printed "overlay" over the other image:
System.Drawing.Image primaryImage = Image.FromFile(#"Your file path");//or resource..
using (Graphics graphics = Graphics.FromImage(primaryImage))//get the underlying graphics object from the image.
{
using (Bitmap overlayImage = new Bitmap(primaryImage.Width, primaryImage.Hieght,
System.Drawing.Imaging.PixelFormat.Format32bppArgb)//or your overlay image from file or resource...
{
graphics.DrawImage(overlayImage, new Point(0, 0));//this will draw the overlay image over the base image at (0, 0) coordination.
}
}
Control.Image = primaryImage;
Not that if the overlay image doesn't have some transparent, and its size is equals or larger than the base image, it will overlap the other image completely, so you the overlay image must have some opacity.
I realize it has been awhile, but the answers here weren't quite working for me. A little tweaking, though made them work fine. For what it is worth, here is my final version.
SCENARIO:
background image is RGB 24
overlay image is ARGB 32 with alpha channel already set properly.
images created from a memory stream
PROBLEM:
Creating the overlay image from the memory stream assumed I meant: Format32bppRgb
But what is needed is Format32bppArgb since the transparency is already in place..
SOLUTION:
pictureBox1.Image = MergeImages( backgroundImage, overlayImage);
using System.Drawing;
using System.Drawing.Imaging;
// ...
private Image MergeImages(Image backgroundImage,
Image overlayImage)
{
Image theResult = backgroundImage;
if (null != overlayImage)
{
Image theOverlay = overlayImage;
if (PixelFormat.Format32bppArgb != overlayImage.PixelFormat)
{
theOverlay = new Bitmap(overlayImage.Width,
overlayImage.Height,
PixelFormat.Format32bppArgb);
using (Graphics graphics = Graphics.FromImage(theOverlay))
{
graphics.DrawImage(overlayImage,
new Rectangle(0, 0, theOverlay.Width, theOverlay.Height),
new Rectangle(0, 0, overlayImage.Width, overlayImage.Height),
GraphicsUnit.Pixel);
}
((Bitmap)theOverlay).MakeTransparent();
}
using (Graphics graphics = Graphics.FromImage(theResult))
{
graphics.DrawImage(theOverlay,
new Rectangle(0, 0, theResult.Width, theResult.Height),
new Rectangle(0, 0, theOverlay.Width, theOverlay.Height),
GraphicsUnit.Pixel);
}
}
return theResult;
}

System.Drawing and WPF Interop - CreateBitmapSourceFromHBitmap

I have a problem interoperating between system.drawing and WPF. To simplify this question I've build an example to illustrate the problem. I draw using System.Drawing and then I copy the result to the System.Windows.Controls.Image using CreateBitmapSourceFromHBitmap. And somehow the alpha information is lost as you can see in the screenshot.
Black text is drawn on the transparent Bitmap and then copied to the image component which is transparent itself. The parent grid itself has a black background. I would guess that the result is a completely black image, but it is not, the text seems to be drawn using white as background when doing the anti-aliasing.
Link to Screenshot
Here's the code for MainWindow.xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid Name="grid" Background="Black">
<Image Name="image" Stretch="Fill" Loaded="image_Loaded"></Image>
</Grid>
</Window>
And the code for MainWindow.xaml.cs:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void image_Loaded(object sender, RoutedEventArgs e)
{
//use system drawing to draw text
Bitmap bmp = new Bitmap ( (Int32) grid.ActualWidth, (Int32) grid.ActualHeight );
Graphics graphics = Graphics.FromImage ( bmp );
var brush = new SolidBrush(Color.FromArgb(255, 0, 0, 0));
var font = new Font(System.Drawing.FontFamily.GenericSansSerif, 30.0f, System.Drawing.FontStyle.Regular, GraphicsUnit.Pixel);
graphics.Clear(Color.Transparent);
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.CompositingMode = CompositingMode.SourceOver;
graphics.CompositingQuality = CompositingQuality.HighQuality;
//draw text
graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
graphics.TextContrast = 5;
graphics.DrawString("My Text", font, brush, 10, 10);
//try saving
bmp.Save("E:\\temp.png", System.Drawing.Imaging.ImageFormat.Png);
//back to wpf image control
image.Source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap ( bmp.GetHbitmap ( ), IntPtr.Zero, Int32Rect.Empty, System.Windows.Media.Imaging.BitmapSizeOptions.FromWidthAndHeight ( (Int32) grid.ActualWidth, (Int32) grid.ActualHeight ) );
}
}
}
Does anyone have experienced this? How can I have a smooth anti-aliasing effect which is not pre-multiplied with white?
Not the most glamorous solution, but the only way I can get it to work properly is by saving the bitmap with the Save() function and then decoding it and loading it as a BitmapSource:
bmp.Save("E:\\temp.png", System.Drawing.Imaging.ImageFormat.Png);
//A neater way to free up the file!
bmp.Dispose();
bmp = null;
//Load up the image.
BitmapFrame frm = new BitmapFrame(new Uri("E:\\temp.png"));
image.Source = frm as BitmapSource;
Now that's not the neatest solution, but it's all I can come across.

Categories

Resources