Expand canvas/transparent background in BitmapImage in a WPF app - c#

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;
}

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;
}

color changing while uploading image

I am using a method that i got from internet and I customized it a little bit.
it takes a HttpPostedFile from fileUpload and some not necessary parameters and then resize the image and comperise it then saving it in the hosting and returning the location
but after I uploaded the image I found it become a little bit gray and you can see the difference in the two pictures here.
Real Image:
Uploaded Image
how I can fix that in my method.
My Upload Method
public string ResizeImage(HttpPostedFile PostedFile, string destinationfile, int maxWidth, int maxHeight)
{
float ratio;
// Create variable to hold the image
System.Drawing.Image thisImage = System.Drawing.Image.FromStream(PostedFile.InputStream);
// Get height and width of current image
int width = (int)thisImage.Width;
int height = (int)thisImage.Height;
// Ratio and conversion for new size
if (width < maxWidth)
{
ratio = (float)width / (float)maxWidth;
width = (int)(width / ratio);
height = (int)(height / ratio);
}
// Ratio and conversion for new size
if (height < maxHeight)
{
ratio = (float)height / (float)maxHeight;
height = (int)(height / ratio);
width = (int)(width / ratio);
}
// Create "blank" image for drawing new image
System.Drawing.Bitmap outImage = new System.Drawing.Bitmap(width, height);
System.Drawing.Graphics outGraphics = System.Drawing.Graphics.FromImage(outImage);
System.Drawing.SolidBrush sb = new System.Drawing.SolidBrush(System.Drawing.Color.White);
outGraphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
outGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
outGraphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
// Fill "blank" with new sized image
outGraphics.FillRectangle(sb, 0, 0, outImage.Width, outImage.Height);
outGraphics.DrawImage(thisImage, 0, 0, outImage.Width, outImage.Height);
sb.Dispose();
outGraphics.Dispose();
thisImage.Dispose();
if (!destinationfile.EndsWith("/"))
destinationfile += "/";
if (!System.IO.Directory.Exists(Server.MapPath(destinationfile)))
System.IO.Directory.CreateDirectory(Server.MapPath(destinationfile));
// Save new image as jpg
string filename = Guid.NewGuid().ToString();
outImage.Save(Server.MapPath(destinationfile + filename + ".jpg"), System.Drawing.Imaging.ImageFormat.Jpeg);
outImage.Dispose();
return destinationfile + filename + ".jpg";
}
EDIT
I took a print screen so you can see the difference in color between the two picture
The main difference between the images when it comes to color, is that the original image doesn't have a color profile at all, while the processed image has the sRGB color profile.
Depending on the browser, the operating system, and how your screen is calibrated, the images will be shown with slightly different colors. For the image without a color profile, the browser can either assume a color profile for it, or it can display it without any color correction at all. When I view the images in Firefox on my computer that has a color calibrated screen, I actually can't see any color difference at all.
The JPEG encoder has assumed the sRGB color profile when you save the image, which is as good a guess as any other profile when there is no color profile information at all in the original image. You need to upload an image with a color profile to see if the JPEG encoder handles that correctly or not. As long as there is no color profile, there is no right or wrong when it comes to interpreting the color values in the image.

How to resize animated gif using Windows.Media.Imaging namespace

I have been using GDI+ implementation in Asp.Net hadler for resizing images. I've found article Resizing images from the server using WPF/WIC instead of GDI+ describing that resizing images using Windows.Media.Imaging is more effective (much faster and consume less system resources).
This approach has one more advantage, I can read individual frames from source image, as gif. Here is part of my code:
var photoDecoder = BitmapDecoder.Create(inputStream, BitmapCreateOptions.PreservePixelFormat,
BitmapCacheOption.None);
IEnumerable<BitmapFrame> bitmapFrames = from bitmapFrame in photoDecoder.Frames
select bitmapFrame;
var bitmapFramesList = new List<BitmapFrame>();
bitmapFramesList.AddRange(bitmapFrames);
var firstFrame = bitmapFramesList.First();
int inputWidth = Convert.ToInt32(firstFrame.Width);
int inputHeight = Convert.ToInt32(firstFrame.Height);
var firstFrameSize= new Size(inputWidth, inputHeight);
//if targetSize is bigger than image Size render image as is
bool targetSizeIsBiggerOrEmpty = targetSize.IsEmpty || (targetSize.Width > firstFrameSize.Width && targetSize.Height > firstFrameSize.Height);
if (targetSizeIsBiggerOrEmpty)
{
inputStream.Position = ZERO;
inputStream.CopyTo(catalogImage.Stream);
}
else
{
//calculate size of target image
Size imageSize = SizeCalculator.CalculateImageSize(new Size(Convert.ToInt32(firstFrame.Width),Convert.ToInt32(firstFrame.Height)), targetSize);
if (targetSize.Width == ZERO)
targetSize.Width = imageSize.Width;
if (targetSize.Height == ZERO)
targetSize.Height = imageSize.Height;
double scaleX = Convert.ToDouble(imageSize.Width) / Convert.ToDouble(firstFrame.Width);
double scaleY = Convert.ToDouble(imageSize.Height) / Convert.ToDouble(firstFrame.Height);
double scale = Math.Min(scaleX, scaleY);
var targetFrames= new Collection<BitmapFrame>();
foreach (var sourceFrame in bitmapFramesList)
{
var transform = new ScaleTransform {ScaleX = scale, ScaleY = scale};
var transformedBitmap = new TransformedBitmap(sourceFrame, transform);
BitmapFrame bitmapFrame = BitmapFrame.Create(transformedBitmap);
targetFrames.Add(bitmapFrame);
}
//my own method for saving image to response output stream
SaveCatalogImage(catalogImage, targetFrames);
}
But here is a small problem. Each frame has different size and I'm not able to find each frame position according to first frame size. I also need timing in target image as is in source.
Where I can find those information in source image?

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.

RenderTargetBitmap is blurry

Hi i'm creating an image in memory from a Canvas using a PngBitmapEncoder.
public void CaptureGraphic()
{
Canvas canvas = new Canvas();
canvas.SnapsToDevicePixels = true;
canvas.Height = IMAGEHEIGHT;
canvas.Width = IMAGEWIDTH;
Draw(canvas);
canvas.Arrange(new Rect(0, 0, IMAGEWIDTH, IMAGEHEIGHT));
member.MemberImage = GetPngFromUIElement(canvas);
}
public static System.Drawing.Image GetPngFromUIElement(Canvas source)
{
int width = (int)source.ActualWidth;
int height = (int)source.ActualHeight;
if (width == 0)
width = (int)source.Width;
if (height == 0)
height = (int)source.Height;
RenderTargetBitmap bitmap = new RenderTargetBitmap(width, height, 96d, 96d, PixelFormats.Pbgra32);
bitmap.Render(source);
PngBitmapEncoder enc = new PngBitmapEncoder();
enc.Interlace = PngInterlaceOption.Off;
enc.Frames.Add(BitmapFrame.Create(bitmap));
System.IO.MemoryStream ms = new System.IO.MemoryStream();
enc.Save(ms);
System.Drawing.Image image = System.Drawing.Image.FromStream(ms);
ms.Flush();
ms.Dispose();
return image;
}
Then i'm sending the image to the printer using the GDI+ DrawImage() method. However the printed result is blurry.
I've tried to match the original canvas size to the printed size to avoid any scaling, equally i've tried to make the original considerably bigger so the scaled image retains the quality however the final printed image is always blurred.
Can anyone offer any suggestions/alternatives. I have a considerable GDI+ print routine already setup and moving to wpf documents is not an option just yet.
Thanks
You're capturing the bitmap at 96 DPI. Instead of using 96 in the constructor of the RenderTargetBitmap, try to match the DPI of your printer output. Alternatively, you could do the math and calculate the difference in width/height and rescale the image on the report accordingly (the result is the image on the report will appear smaller).
I had the same blurry result and have come up with the following piece of code, which applies the same idea with the offset, but using the Offset property on the DrawingVisual (since I was using DrawDrawing, which doesn't have an overload with the offset argument):
public static Image ToBitmap(Image source)
{
var dv = new DrawingVisual();
// Blur workaround
dv.Offset = new Vector(0.5, 0.5);
using (var dc = dv.RenderOpen())
dc.DrawDrawing(((DrawingImage)source.Source).Drawing);
var bmp = new RenderTargetBitmap((int)source.Width, (int)source.Height, 96, 96, PixelFormats.Pbgra32);
bmp.Render(dv);
var bitMapImage = new Image();
bitMapImage.Source = bmp;
bitMapImage.Width = source.Width;
bitMapImage.Height = source.Height;
return bitMapImage;
}
Think i've found the answer.
http://www.charlespetzold.com/blog/2007/12/High-Resolution-Printing-of-WPF-3D-Visuals.html
I just needed to scale up the image size along with the dpi and voila, massively increased file size!
I had the same problem. To avoid blurry text and lines, I had to draw everything with an offset of 0.5 in X and Y direction. E.g, an horizontal line could be
drawingContext.DrawLine(pen, new Point(10.5,10.5), new Point(100.5,10.5));
In my case, I was rendering to a RenderTargetBitmap in a different thread to improve the UI performance. The rendered bitmap is then frozen and drawn onto the UI using
drawingContext.DrawImage(bitmap, new Rect(0.5, 0, bitmap.Width, bitmap.Height));
Here, I needed an additional offset of 0.5, but (strangely) only in X direction, so that the rendered image did not look Blurry any more.

Categories