Drawing Image wrong size WPF - c#

I'm drawing a cell wall on a map and it is the wrong size. I checked the height of the image in debugger and it was the same size as the cellSize which is correct, but visually it appears shorter and the start of it is not drawn in the correct place. Like it is being drawn over top of at both ends. When I change the Height property of the image it does not make the image bigger. Is that not how to set the image size, with Height and Width?
Here is the code.
private void calculateWall(int wall, int cell)
{
int[] mapData = this.getMapData(cell);
int startOfCol = mapData[0];
int endOfCol = mapData[1];
int startOfRow = mapData[2];
int endOfRow = mapData[3];
CellSide rightSide = this.getCells()[cell].myRightWall;
CellSide bottomSide = this.getCells()[cell].myBottomWall;
float thickness = myMap.myCellSize * (float)0.1;
Math.Round(thickness, 0);
int newThickness = Convert.ToInt32(thickness);
float height = myMap.myCellSize * (float)0.2;
Math.Round(height, 0);
int newHeight = Convert.ToInt32(height);
if (rightSide.hasWall == 1)
{
Image verticalWall = new Image();
// Create source.
BitmapImage bi = new BitmapImage();
// BitmapImage.UriSource must be in a BeginInit/EndInit block.
bi.BeginInit();
bi.UriSource = new Uri("verticalWall.jpg", UriKind.RelativeOrAbsolute);
bi.EndInit();
// Set the image source.
verticalWall.Source = bi;
verticalWall.Width = newThickness;
verticalWall.Height = myMap.myCellSize;
verticalWall.SetValue(Canvas.TopProperty, Convert.ToDouble(startOfRow));
verticalWall.SetValue(Canvas.LeftProperty, Convert.ToDouble(endOfCol - (newThickness / 2)));
verticalWall.IsHitTestVisible = false;
this.view.pbxMap.Children.Add(verticalWall);
}

Regarding your last questions in the comments.
Ok, here's a screenshot of how I structured your project: Screenshot
Now, wherever you use any of the pictures,you'll have to add the folder to that path:
Source="exit.png"
Will become:
Source="/Images/exit.png"
This:
<Page.Background>
<ImageBrush ImageSource="marbleBackground.jpg"/>
</Page.Background>
Will become:
<Page.Background>
<ImageBrush ImageSource="/Images/marbleBackground.jpg"/>
</Page.Background>
And so on ... problem solved :)

When you programmatically draw an image in WPF, its stretch property is set to uniform by default. Add this line:
verticalWall.Stretch = Stretch.Fill;
Now it will size correctly even if it does not stretch uniformly.

Related

ImageLayout.Center does not center background image

I want to center a background image. However the background image is larger than my control(which is a flat style checkbox).
A picture to make my problem clear
Usually if the background image is smaller than the control, it will be shown like the black box(which is properly centered); but in my case it will show partial image in the green box(left top corner), but the end result I want is the orange box(the center of the image), or zoomed proportionally to fill the control with extra part cut off(ImageLayout.Zoom will show whole image with blank space).
Update: Code used:
Image img = Image.FromFile("xxxx.png");
mycheckbox.BackgroundImage = img;
mycheckbox.BackgroundImageLayout = ImageLayout.Center;
Image can be used instead of BackgroundImage to get the part of the image as the orange rectangle of the example.
If the image doesn't fill the entire checkbox and if it's acceptable to alter the image being set (should work without problems, but would not be dynamic in case the checkbox size changes during runtime), the image could be resized first according to the largest ratio:
var img = Image.FromFile("xxxx.png");
float ratio = Math.Max(mycheckbox.Height / (float)img.Height,mycheckbox.Width / (float)img.Width);
if (ratio > 1)
{
Func<float, int> calc = f => (int)Math.Ceiling(f * ratio);
var bmp = new Bitmap(img, calc(img.Width ), calc(img.Height ));
img.Dispose();
img = bmp;
}
mycheckbox.ImageAlign = ContentAlignment.MiddleCenter;
mycheckbox.Image = img;
The line float ratio = Math.Max(mycheckbox.Height / (float)img.Height,mycheckbox.Width / (float)img.Width); simply calculates both the height ratio and the width ratio. If either is larger than 1, it means the checkbox height or width is larger. It doesn't matter which one, only which is larger, hence the Math.Max. The check of larger than 1 is performed on the largest ratio and if needed the image is enlarged with said ratio.
Edit A more generic approach, that scales and cuts the image so that if fills the control size and the BackGroundImage property can be used:
public static void SetImage(this Control ctrl, Image img)
{
var cs = ctrl.Size;
if (img.Size != cs)
{
float ratio = Math.Max(cs.Height / (float)img.Height, cs.Width / (float)img.Width);
if (ratio > 1)
{
Func<float, int> calc = f => (int)Math.Ceiling(f * ratio);
img = new Bitmap(img, calc(img.Width), calc(img.Height));
}
var part = new Bitmap(cs.Width, cs.Height);
using (var g = Graphics.FromImage(part))
{
g.DrawImageUnscaled(img, (cs.Width - img.Width) /2, (cs.Height - img.Height) / 2);
}
img = part;
}
ctrl.BackgroundImageLayout = ImageLayout.Center;
ctrl.BackgroundImage = img;
}

How to calculate FixedPage dimensions

This snippet is part of some code used to generate an XPS document. XPS document generation is no joke. I wish to avoid pasting any of that XPS code here if at all possible. Instead, this code focuses on the WPF portion of the problem.
The problem I am asking for you to help with is here. I have hard coded the dimensions to work for a test image:
double magicNumber_X = 3.5;//trial and error...3 too small 4 too big
fixedPage.Arrange(new Rect(new Point(magicNumber_X, 0), size));
Instead, how can I fix this code to calculate the coordinates?
Full Method:
private PageContent AddContentFromImage()
{
var pageContent = new PageContent();
var fixedPage = new FixedPage();
var bitmapImage = new BitmapImage(new Uri(hardCodedImageSampleFilePath, UriKind.RelativeOrAbsolute));
var image = new Image();
image.Source = bitmapImage;
fixedPage.Children.Add(image);
((IAddChild)pageContent).AddChild(fixedPage);
double pageWidth = 96 * 8.5;//XPS documents are 96 units per inch
double pageHeight = 96 * 11;
fixedPage.Width = pageWidth;
fixedPage.Height = pageHeight;
var size = new Size(8.5 * 96, 11 * 96);
fixedPage.Measure(size);
double magicNumber_X = 3.5;//trial and error...3 too small 4 too big
double magicNumber_Y = 0;
fixedPage.Arrange(new Rect(new Point(magicNumber_X, magicNumber_Y), size));
fixedPage.UpdateLayout();
return pageContent;
}
I'm a little surprised FixedPage.Measure(size) does not correct the issue by itself. I tried passing no params, e.g. fixedPage.Arrange(new Rect(), size)) still no go.
FWIW, this calculation worked fine when I was using PrintDocument.
private void pd_PrintPage(object sender, PrintPageEventArgs e)
{
Graphics g = e.Graphics;
RectangleF marginBounds = e.MarginBounds;
RectangleF printableArea = e.PageSettings.PrintableArea;
int availableWidth = (int)Math.Floor(printDocument.OriginAtMargins ? marginBounds.Width : (e.PageSettings.Landscape ? printableArea.Height : printableArea.Width));
int availableHeight = (int)Math.Floor(printDocument.OriginAtMargins ? marginBounds.Height : (e.PageSettings.Landscape ? printableArea.Width : printableArea.Height));
Rectangle rectangle = new Rectangle(0,0, availableWidth -1, availableHeight - 1);
g.DrawImage(_image, rectangle);
I hooked into FixedPage.Loaded event because FixedPage.ActualHeight is required in order to perform the calculation and will not be set until the control has loaded. This also means that with this mechanism FixedPage has to be displayed to correctly perform an automated print.
void fixedPage_Loaded(object sender, RoutedEventArgs e)
{
var fixedDocument = sender as FixedPage;
CalculateSize(fixedDocument);
}
private void CalculateSize(FixedPage fixedPage)
{
PrintQueue printQueue = LocalPrintServer.GetDefaultPrintQueue();
PrintCapabilities capabilities = printQueue.GetPrintCapabilities();
//get scale of the print wrt to screen of WPF visual
double scale = Math.Min(capabilities.PageImageableArea.ExtentWidth / fixedPage.ActualWidth, capabilities.PageImageableArea.ExtentHeight / fixedPage.ActualHeight);
//Transform the Visual to scale
fixedPage.LayoutTransform = new ScaleTransform(scale, scale);
//get the size of the printer page
var sz = new Size(capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight);
//update the layout of the visual to the printer page size.
fixedPage.Measure(sz);
double x = capabilities.PageImageableArea.OriginWidth;
double y = capabilities.PageImageableArea.OriginHeight;
fixedPage.Arrange(new Rect(new Point(x, y), sz));
fixedPage.UpdateLayout();
}

Image cropping in C#

I'am using PictureBox for displaying images. My images are direct from scanner so the resolutions are up to 4000*4000... Because my display area is a lot smaller I have to display the image with pictureBox1.SizeMode = PictureBoxSizeMode.Zoom; to preserve aspect ratio.
After that the image is in the middle of the screen.
How can I find the distance between the left side of the image control and the REAL left side of an actual image (see image bellow).
Is there any solution?
Btw. displaying the image on the left side of the screen would do the trick too.
var imageHeight = pictureBox1.Image.Height;
var imageWidth = pictureBox1.Image.Width;
var userSelection = rect.Rect;
var display = pictureBox1.DisplayRectangle;
var xFactor = (float)userSelection.Width / display.Width;
var yFactor = (float)userSelection.Height / display.Height;
var realCropSizeWidth = xFactor * imageWidth;
var realCropSizeHight = yFactor * imageHeight;
var realCropX = imageWidth / display.Width;
realCropX *= userSelection.X;
var realCropY = imageHeight / display.Height;
realCropY *= userSelection.Y;
var realCropRectangle = new Rectangle(realCropX, realCropY, (int)realCropSizeWidth,
(int)realCropSizeHight);
var image = CropImage(pictureBox1.Image, realCropRectangle);
pictureBox1.Image = image;
public Image CropImage(Image source, Rectangle rectangle)
{
var target = new Bitmap(rectangle.Width, rectangle.Height);
using (var g = Graphics.FromImage(target))
{
g.DrawImage(source, new Rectangle(0, 0, target.Width, target.Height),
rectangle,
GraphicsUnit.Pixel);
}
return target;
}
As far as I know there is no direct way of getting what you want, but some simple maths would suffice:
You know the aspect ratio of your original image which is preserved and you know the aspect ratio of your picturebox in which you are showing it. Based on that you can figure out which dimension (height or width) the image fits exactly. Once you know that you can obtain the scaling factor of the image and therefore you can calculate the other dimension of the shown image.
As the image will be centered on the dimension that is not fitted exactly to the picturebox, its straighforward to get the distance you are looking for.

Expand canvas/transparent background in BitmapImage in a WPF app

This is a follow up question on Save image to file keeping aspect ration in a WPF app
I know howto scale the image, but how do I expand the canvas size, to ensure the image still has the requested width and height. In this example its 250x250 but its dynamic.
I have created this illustration to show what I'm trying to accomplice.
I can't find any way of expanding the canvas of an BitmapImage, nor a way to create an in memory image in the correct size, with a transparent background, and then merging the two images together.
CroppedBitmap doesn't seem to support adding space around an image so instead you can create a transparent image the correct size using WriteableBitmap. If the input is smaller than the target size this method will enlarge it, but that is easy to alter.
public static BitmapSource FitImage(BitmapSource input, int width, int height)
{
if (input.PixelWidth == width && input.PixelHeight == height)
return input;
if(input.Format != PixelFormats.Bgra32 || input.Format != PixelFormats.Pbgra32)
input = new FormatConvertedBitmap(input, PixelFormats.Bgra32, null, 0);
//Use the same scale for x and y to keep aspect ratio.
double scale = Math.Min((double)width / input.PixelWidth, height / (double)input.PixelHeight);
int x = (int)Math.Round((width - (input.PixelWidth * scale))/2);
int y = (int)Math.Round((height - (input.PixelHeight * scale))/2);
var scaled = new TransformedBitmap(input, new ScaleTransform(scale, scale));
var stride = scaled.PixelWidth * (scaled.Format.BitsPerPixel / 8);
var result = new WriteableBitmap(width, height, input.DpiX, input.DpiY, input.Format,null);
var data = new byte[scaled.PixelHeight * stride];
scaled.CopyPixels(data, stride, 0);
result.WritePixels(new Int32Rect(0,0,scaled.PixelWidth,scaled.PixelHeight), data, stride,x,y);
return result;
}
If you are already rendering content using RenderTargetBitmap you could wrap it in a ViewBox to do the scaling but if you're just working with normal images I'd use the above method.
You should be able to set the Stretch property to Uniform.
Just some code in case others are trying to postprocess files from a form upload.
if (file.PostedFile != null)
{
//write the new file to disk
string cachePath = String.Format("{0}temp\\", Request.PhysicalApplicationPath);
string photoPath = String.Format("{0}temp.png", cachePath);
if (!Directory.Exists(cachePath))
{
Directory.CreateDirectory(cachePath);
}
file.PostedFile.SaveAs(photoPath);
//resize the new file and save it to disk
BitmapSource banana = FitImage(ReadBitmapFrame(file.PostedFile.InputStream), 640, 480);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(banana));
FileStream pngStream = new FileStream(cachePath + "test.png", FileMode.Create);
encoder.Save(pngStream);
pngStream.Close();
//set a couple images on page to the newly uploaded and newly processed files
image.Src = "temp/temp.png";
image.Visible = true;
image2.Src = "temp/test.png";
image2.Visible = true;
}

Canvas background for retrieving color

I have a canvas with a background set to be lineargradientbrush....how do I then extract the color from this background at a particular mouse point (x,y)?
I can do this with a BitmappedImage fine...as this deals with pixels, not sure about a canvas though...
Thanks greatly in advance,
U.
The code posted by Ray Burns didn't work for me but it did lead me down the right path. After some research and experimentation I located the problems to be the bitmap.Render(...) implementation and the Viewbox it uses.
Note: I'm using .Net 3.5 and WPF so maybe his code works in other versions of .Net.
The comments were left here intentionally to help explain the code.
As you can see the Viewbox needs to be normalized with respect to the source Visual Height and Width.
The DrawingVisual needs to be drawn using the DrawingContext before it can be rendered.
In the RenderTargetBitmap method I tried both PixelFormats.Default and PixelFormats.Pbgra32. My testing results were the same with both of them.
Here is the code.
public static Color GetPixelColor(Visual visual, Point pt)
{
Point ptDpi = getScreenDPI(visual);
Size srcSize = VisualTreeHelper.GetDescendantBounds(visual).Size;
//Viewbox uses values between 0 & 1 so normalize the Rect with respect to the visual's Height & Width
Rect percentSrcRec = new Rect(pt.X / srcSize.Width, pt.Y / srcSize.Height,
1 / srcSize.Width, 1 / srcSize.Height);
//var bmpOut = new RenderTargetBitmap(1, 1, 96d, 96d, PixelFormats.Pbgra32); //assumes 96 dpi
var bmpOut = new RenderTargetBitmap((int)(ptDpi.X / 96d),
(int)(ptDpi.Y / 96d),
ptDpi.X, ptDpi.Y, PixelFormats.Default); //generalized for monitors with different dpi
DrawingVisual dv = new DrawingVisual();
using (DrawingContext dc = dv.RenderOpen())
{
dc.DrawRectangle(new VisualBrush { Visual = visual, Viewbox = percentSrcRec },
null, //no Pen
new Rect(0, 0, 1d, 1d) );
}
bmpOut.Render(dv);
var bytes = new byte[4];
int iStride = 4; // = 4 * bmpOut.Width (for 32 bit graphics with 4 bytes per pixel -- 4 * 8 bits per byte = 32)
bmpOut.CopyPixels(bytes, iStride, 0);
return Color.FromArgb(bytes[0], bytes[1], bytes[2], bytes[3]);
}
If you are interested in the getScreenDPI() function the code is:
public static Point getScreenDPI(Visual v)
{
//System.Windows.SystemParameters
PresentationSource source = PresentationSource.FromVisual( v );
Point ptDpi;
if (source != null)
{
ptDpi = new Point( 96.0 * source.CompositionTarget.TransformToDevice.M11,
96.0 * source.CompositionTarget.TransformToDevice.M22 );
}
else
ptDpi = new Point(96d, 96d); //default value.
return ptDpi;
}
And the usage is similar to Ray's. I show it here for a MouseDown on a canvas.
private void cvsTest_MouseDown(object sender, MouseButtonEventArgs e)
{
Point ptClicked = e.GetPosition(cvsTest);
if (e.LeftButton.Equals(MouseButtonState.Pressed))
{
Color pxlColor = ImagingTools.GetPixelColor(cvsTest, ptClicked);
MessageBox.Show("Color String = " + pxlColor.ToString());
}
}
FYI, ImagingTools is the class where I keep static methods related to imaging.
WPF is vector based so it doesn't really have any concept of a "pixel" except within a bitmap data structure. However you can determine the average color of a rectangular area, including a 1x1 rectangular area (which generally comes out as a single pixel on the physical screen).
Here's how to do this:
public Color GetPixelColor(Visual visual, int x, int y)
{
return GetAverageColor(visual, new Rect(x,y,1,1));
}
public Color GetAverageColor(Visual visual, Rect area)
{
var bitmap = new RenderTargetBitmap(1,1,96,96,PixelFormats.Pbgra32);
bitmap.Render(
new Rectangle
{
Width = 1, Height = 1,
Fill = new VisualBrush { Visual = visual, Viewbox = area }
});
var bytes = new byte[4];
bitmap.CopyPixels(bytes, 1, 0);
return Color.FromArgb(bytes[0], bytes[1], bytes[2], bytes[3]);
}
Here is how you would use it:
Color pixelColor = GetPixelColor(canvas, x, y);
The way this code works is:
It fills a 1x1 Rectangle using a VisualBrush that shows the selected area of the canvas
It renders this Rectangle on to a 1-pixel bitmap
It gets the pixel color from the rendered bitmap
On Microsoft Support, there is this article about finding the color of the pixel at the mouse cursor:
http://support.microsoft.com/kb/892462

Categories