I am using some custom controls one of which is a tooltip controller that can display images, so I am using th ebelow code to instantiate it:
Image newImage = Image.FromFile(imagePath);
e.ToolTipImage = newImage;
obviously could inline it but just testing at the moment. The trouble is the image is sometimes the wrong size, is there a way to set the display size. The only way I can currently see is editing the image using GDI+ or something like that. Seems like a lot of extra processing when I am only wanting to adjust display size not affect the actual image.
Once you have an image object loaded from its source, the Height and Width (and Size, and all ancillary properties) are read-only. Therefore, you are stuck with GDI+ methods for resizing it in RAM and then displaying it accordingly.
There are a lot of approaches you can take, but if you were to encapsulate that out to a library which you could reuse should this problem occur again, you'll be set to go. This isn't exactly optimized (IE, may have some bugs), but should give you an idea of how to approach it:
Image newImage = Image.FromFile(myFilePath);
Size outputSize = new Size(200, 200);
Bitmap backgroundBitmap = new Bitmap(outputSize.Width, outputSize.Height);
using (Bitmap tempBitmap = new Bitmap(newImage))
{
using (Graphics g = Graphics.FromImage(backgroundBitmap))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
// Get the set of points that determine our rectangle for resizing.
Point[] corners = {
new Point(0, 0),
new Point(backgroundBitmap.Width, 0),
new Point(0, backgroundBitmap.Height)
};
g.DrawImage(tempBitmap, corners);
}
}
this.BackgroundImage = backgroundBitmap;
I did test this, and it worked. (It created a 200x200 resized version of one of my desktop wallpapers, then set that as the background image of the main form in a scratch WinForms project. You'll need using statements for System.Drawing and System.Drawing.Drawing2D.
In Winforms, if you contain the image inside a PictureBox control, the PictureBox control can be set to zoom to a particular height/width, and the image should conform.
At least that's what happened in my Head First C# book when I did the exercise.
Related
I am trying to render a figure to a PNG using WPF (rather than the old GDI API) and the result is always blank.
I am writing a small program that will produce images routes around a warehouse to various items. I am using WPF API for drawing rather than the old GDI API as the images need to be both saved to disk and displayed within a WPF application. The problem is that all of the png files that it produces are currently entirely black.
To reduce the scope of the problem and prove that it was not a problem with code for drawing the map I made a minimal test method that should simply render a red circle on a black background. However the result is still an entirely black.
[TestMethod]
public void DrawingTest()
{
//define the dimensions of the test image
int width = 100;
int height = 100;
int dpi = 100;
//define where the image should be saved
var path = #"C:\temp\DrawingTest.png";
//create a bitmap to render the image into
RenderTargetBitmap bitmap = new RenderTargetBitmap(
width, height, dpi, dpi, PixelFormats.Default);
//create a drawing context to draw the image onto
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext canvas = drawingVisual.RenderOpen())
{
//draw a circle
canvas.DrawEllipse(Brushes.Red, new Pen(Brushes.Red, 1), new System.Windows.Point(50, 50), 50, 50);
//render the image into a bitmap
bitmap.Render(drawingVisual);
}
//create the file encoder
var encoder = new PngBitmapEncoder();
//encode the bitmap in PNG format
encoder.Frames.Clear();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
//make sure that the file can be saved in this location
var directory = System.IO.Path.GetDirectoryName(path);
if (!System.IO.Directory.Exists(directory))
System.IO.Directory.CreateDirectory(directory);
if (System.IO.File.Exists(path))
System.IO.File.Delete(path);
//save the map to file
using (var stream = System.IO.File.Create(path))
encoder.Save(stream);
}
The test method runs without any errors and produces a png file at the location that I expected. However, this file is simply a black square rather than the red circle in a black square that I was expecting. Can anybody tell me why it is just rendering a black image, and what I can do to make it show the red circle.
You have to call bitmap.Render(drawingVisual) after the DrawingContext is closed.
From the Remarks on DrawingContext.Close:
A DrawingContext must be closed before its content can be rendered ...
So just move the Render call outside the using block:
using (DrawingContext dc = drawingVisual.RenderOpen())
{
dc.DrawEllipse(Brushes.Red, new Pen(Brushes.Red, 1),
new System.Windows.Point(50, 50), 50, 50);
}
bitmap.Render(drawingVisual);
Besides that, the red Pen seems redundant, since the circle is already filled red. So this should be ok:
dc.DrawEllipse(Brushes.Red, null, new System.Windows.Point(50, 50), 50, 50);
Also make sure to create the bitmap with a DPI value of 96, which corresponds to WPF device-independent units. Otherwise you would have to scale the drawing appropriately.
One and a half year later I faced the same problem - how to properly render a canvas without displaying it in the screen.
The solution was simpler than I thought and produces exactly the result one expects.
Long story short - to render the canvas without displaying it call Arrange(Rect finalRect) method on your canvas:
canvas.Arrange(new Rect(new Size(width, height)));
According to the documentation it:
Positions child elements and determines a size for a UIElement. Parent elements call this method from their ArrangeCore(Rect) implementation (or a WPF framework-level equivalent) to form a recursive layout update. This method constitutes the second pass of a layout update.
I'm making a program that's cropping images. I have two PictureBoxes and a Button named 'crop'. One picture box contains an image and when I select a rectangle in it and press 'Crop' the selected area appears in the other picture box; so the program is working when I press crop. The problem is: How can I get the image from crop area into picture box Image?
Rectangle rectCropArea;
Image srcImage = null;
TargetPicBox.Refresh();
//Prepare a new Bitmap on which the cropped image will be drawn
Bitmap sourceBitmap = new Bitmap(SrcPicBox.Image, SrcPicBox.Width, SrcPicBox.Height);
Graphics g = TargetPicBox.CreateGraphics();
g.DrawImage(sourceBitmap, new Rectangle(0, 0, TargetPicBox.Width, TargetPicBox.Height),
rectCropArea, GraphicsUnit.Pixel);
//Good practice to dispose the System.Drawing objects when not in use.
sourceBitmap.Dispose();
Image x = TargetPicBox.Image;
The problem is that x = null and the image is showing in the picture box so how can I get the Image from this picture box into the Image variable ?
A couple of issues:
First and most important: You are being confused about the relationship between PictureBox.Image (a Property) and the Graphics you associate with the PictureBox's surface.
The Graphics object you get from Control.CreateGraphics is only able to paint onto the surface of the control; usually not what you want; and even when you do, you usually want to do it in a Paint event using e.Graphics..
So, while your code seems to work, it only paints non-persistent pixels onto the surface. Minimize/maximize and you'll see what non-persistent means..!
To change a Bitmap bmp you need to associate it with a Grahics object like this:
Graphics g = Graphics.FromImage(bmp);
Now you can draw into it:
g.DrawImage(sourceBitmap, targetArea, sourceArea, GraphicsUnit.Pixel);
After that you can assign the Bitmap to the Image Property of the TargetPicBox..
Finally dispose of the Graphics, or better, put it into a using clause..
I am assuming that you have managed to give the rectCropArea meaningful values.
Also note that the way you copy the source bitmap has an error: If you want the full image, do use its Size (*), not the one of the PictureBox!!
And instead of creating a target rectangle, with the same error, simply use the TargetPicBox.ClientRectangle!
Here is an example code for the crop Button:
// a Rectangle for testing
Rectangle rectCropArea = new Rectangle(22,22,55,99);
// see the note below about the aspect ratios of the two rectangles!!
Rectangle targetRect = TargetPicBox.ClientRectangle;
Bitmap targetBitmap = new Bitmap(targetRect.Width, targetRect.Height);
using (Bitmap sourceBitmap = new Bitmap(SrcPicBox.Image,
SrcPicBox.Image.Width, SrcPicBox.Image.Height) )
using (Graphics g = Graphics.FromImage(targetBitmap))
g.DrawImage(sourceBitmap, targetRect, rectCropArea, GraphicsUnit.Pixel);
if (TargetPicBox.Image != null) TargetPicBox.Dispose();
TargetPicBox.Image = targetBitmap;
Of course you should have prepared the Rectangle in the proper mouse events!
Here you would want to decide on the aspect ratio of the result; you probably don't want to distort the result! So you need to decide whether to crop the source cropping rectangle or whether to expand the target rectangle..!
Unless you are sure about the dpi resolution you should use SetResolution to make sure the new image has the same!
Note that since I assign targetBitmap to TargetPicBox.Image I must not dipose of it! Instead, before assigning a new Image, I first Dispose the old one..
I have been working on creating a program similar to MS Paint. I have several of the features it has down but the one which is currently giving me trouble is the rectangular selection tool. My program currently draws everything on the panel and saves it all in an ArrayList so each shape can be redrawn in Paint().
Like MS paint I would like the user to be able to select a section of the drawing on the panel and either copy it, move it, re-size it, or even delete it. I was thinking about having the user draw a rectangle & saving the information for it. Then taking that information for the rectangle, passing them to create a new Bitmap. I would then paint a new rectangle in the background color to give the appearance that the selected area was "removed" when the selected portion is moved. It sounded okay until I realized that I couldn't use the Graphics.FromImage() on the PaintEventArgs variable passed to Paint() which made my idea useless. Not sure if that makes sense so my apologies if it's a confusing mess.
I've been searching the internet for some assistance and I haven't found much to help so either this is very easy to do, very difficult, or "rectangle selection tool" is not the proper term. Any help or pointers would be greatly appreciated!!! Thank you for your time! :)
I understand that you actually have the Rectangle and now would like to copy an area from your painted Panel.
This is possible, assuming you have, as you should, placed all the painting in the Paint event of the Panel.
Then you can, use DrawToBitmap to ask the Panel to draw itself onto a new Bitmap; from there you can DrawImage the Rectangle onto your Panel.
Note: For this to integrate with your list of 'Paint-Actions' you will have to either now store that Bitmap or store the Rectangle and redo the whole operation.
using (Graphics G = panelCanvas.CreateGraphics() )
{
Rectangle R0 = new Rectangle(22,22,55,55); // your Rectangle!
using (Bitmap bmp = new
Bitmap(panelCanvas.ClientSize.Width, panelCanvas.ClientSize.Height))
{ panelCanvas.DrawToBitmap(bmp, panelCanvas.ClientRectangle);
G.DrawImage(bmp, 111f, 111f, R0, GraphicsUnit.Pixel);
}
}
Aside: Please do replace the ArrayList, which is depracated by the new List<T>, e.g. a List<PaintAction> or whatever name your class has!
If you simply want to extract a rectanglular area from the Panel Control you can use thsi function:
public Bitmap getAreaFrom(Control ctl, Rectangle area)
{
Bitmap bmp2 = new Bitmap(area.Width, area.Height);
using (Graphics G = ctl.CreateGraphics())
using (Bitmap bmp = new Bitmap(ctl.ClientSize.Width, ctl.ClientSize.Height))
{
ctl.DrawToBitmap(bmp, ctl.ClientRectangle);
using (Graphics G2 = Graphics.FromImage(bmp2))
G2.DrawImage(bmp, 0f, 0f, area, GraphicsUnit.Pixel);
}
return bmp2;
}
I've got a situation where I need to resize a large number of images. These images are stored as .jpg files on the file system currently, but I expect to just have byte[] in memory later on in the project. The source image size is variable, but the output should be 3 different predetermined sizes. Aspect ratios should be preserved, padding the original image with white space (ie, a really tall image would be resized to fit within the square target image size, with large areas of white on the left and right).
I initially built the project targeting .NET 2.0, and using System.Drawing classes to perform the load/resize/save. Relevant code includes:
original = Image.FromFile(inputFile); //NOTE: Reused for each of the 3 target sizes
Bitmap resized = new Bitmap(size, size);
//Draw the image to a new image of the intended size
Graphics g = Graphics.FromImage(resized);
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.Clear(Color.White);
g.DrawImage(original, center - width / 2f, center - height / 2f, width, height);
g.Dispose();
//Save the new image to the output path
resized.Save(outputFile, ImageFormat.Jpeg);
I wanted to port this project to .NET 3.5, so tried using the System.Windows.Media classes to perform the same function. I got it working, however performance is terrible; processing time per image is about 50x longer. The vast majority of the time is spent loading the image. Relevant code includes:
BitmapImage original = new BitmapImage(); //Again, reused for each of the 3 target sizes
original.BeginInit();
original.StreamSource = new MemoryStream(imageData); //imageData is a byte[] of the data loaded from a FileStream
original.CreateOptions = BitmapCreateOptions.None;
original.CacheOption = BitmapCacheOption.Default;
original.EndInit(); //Here's where the vast majority of the time is spent
original.Freeze();
// Target Rect for the resize operation
Rect rect = new Rect(center - width / 2d, center - height / 2d, width, height);
// Create a DrawingVisual/Context to render with
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
drawingContext.DrawImage(original, rect);
}
// Use RenderTargetBitmap to resize the original image
RenderTargetBitmap resizedImage = new RenderTargetBitmap(
size, size, // Resized dimensions
96, 96, // Default DPI values
PixelFormats.Default); // Default pixel format
resizedImage.Render(drawingVisual);
// Encode the image using the original format and save the modified image
SaveImageData(resizedImage, outputFile);
Am I doing something wrong here, to take so much time? I've tried just using the constructor on BitmapImage that takes a URI, same performance issue there. Anyone done anything like this before, know if there's a more performance-minded way to do this? Or am I just going to need to use System.Drawing still? Thanks!
And after typing all that up, it occurred to me that I could load the symbols from MS for the System.Windows.Media classes, and step through where it was slow. Immediately found the cause, and the solution. The input images were saved with a color profile, and it was attempting to load that color profile (from the file system) of each image. By switching from BitmapCreateOptions.None to BitmapCreateOptions.IgnoreColorProfile in the code above, it no longer does that, and performs just as fast as System.Drawing did.
Hope this helps anyone else that runs into this problem!
You appear to be doing this the hard way. You can let WPF do the work for you by just setting DecodePixelHeight and DecodePixelWidth. This will cause the resize to happen during the image load:
BitmapImage resizedImage = new BitmapImage
{
StreamSource = new MemoryStream(imageData),
CreateOptions = BitmapCreateOptions.IgnoreColorProfile,
DecodePixelHeight = height,
DecodePixelWidth = width,
}
resizedImage.BeginInit(); // Needed only so we can call EndInit()
resizedImage.EndInit(); // This does the actual loading and resizing
imageSaveImageData(resizedImage, outputFile);
I also included the IgnoreColorProfile solution you found in my code.
Update I reread your question and realized the reason you're using DrawingVisual is that you need whitespace around your image to make it square. DecodePixelHeight and DecodePixelWidth would not accomplish that goal, so my solution does not answer your question.
I will leave my answer here in case someone who just needs a resize without whitespace comes across this question.
I think this from the System.Drawing page on MSDN might be relevant:
The System.Drawing namespace provides access to GDI+ basic graphics functionality. More advanced functionality is provided in the System.Drawing.Drawing2D, System.Drawing.Imaging, and System.Drawing.Text namespaces.
The Graphics class provides methods for drawing to the display device. Classes such as Rectangle and Point encapsulate GDI+ primitives. The Pen class is used to draw lines and curves, while classes derived from the abstract class Brush are used to fill the interiors of shapes.
By using System.Drawing you are closer to the actual basic graphics functionality than if you go via System.Windows.Media which:
Defines objects that enable integration of rich media, including drawings, text, and audio/video content within Windows Presentation Foundation (WPF) applications.
System.Drawing is still supported, so I'd stick with that.
I found an interesting situation in your code. Remove using from following line:
using(DrawingContext drawingContext = drawingVisual.RenderOpen())
I'm not sure why this speed up code, but you can give it a try.
I am trying to draw two images side-by-side using the C# Drawing namespace.
Here is a very simple example that assumes we have two images of the same height:
Image[] oldImages = GetOldImages();
var newImage = new Bitmap(oldImages[0].Width + oldImages[1].Width, 800);
using (var newImageGraphics = Graphics.FromImage(newImage))
{
newImageGraphics.DrawImage(oldImages[0], 0, 0);
newImageGraphics.DrawImage(oldImages[1], oldImage[0].Width, 0);
newImageGraphics.Save();
}
This works OK if the resolution of the two old images are the same.
However, if the resolutions are different then the image is resized, causing problems. For example, if the first image has a different resolution, then the second image will be positioned incorrectly.
Does anyone know how I can fix this problem easily? Ideally I want the original image's height and width to remain the same when they are drawn on to the new image.
Try this trick:
Bitmap picture_1 = new Bitmap(picture_1_path);
Graphics graphics = Graphics.FromImage(picture_1);
Bitmap picture_2 = new Bitmap(picture_2_path);
picture_2.SetResolution(graphics.DpiX, graphics.DpiY);
//Then do with pictures anything
Basically you'll need to resize the second image before adding to the new image.
Though as you say you want to retain the original height and width you'll need to change the canvas size of the second image. This increases the size of the image by adding empty space around the actual image. If the second image is larger than the first you'll need to do this to the first image instead.