I'm trying to resize an image in .NET, but get a faint black border around the resized image. I found a post - http://social.msdn.microsoft.com/forums/en-US/csharpgeneral/thread/cf765094-c8c1-4991-a1f3-cecdbd07ee15/ which from someone who said making the destination rectangle larger than the canvas worked, but this doesn't work for me. It gets riid of the top and left borders, but the right and bottom are still there, and are a full 1px thick black.
Am I missing something? My code is below.
Image image = ... // this is a valid image loaded from the source
Rectangle srcRectangle = new Rectangle(0,0,width, height);
Size croppedFullSize = new Size(width+3,height+3);
Rectangle destRect = new Rectangle(new Point(-1,-1), croppedFullSize);
using(Bitmap newImage = new Bitmap(croppedFullSize.Width, croppedFullSize.Height, format))
using(Graphics Canvas = Graphics.FromImage(newImage)) {
Canvas.SmoothingMode = SmoothingMode.AntiAlias;
Canvas.InterpolationMode = InterpolationMode.HighQualityBicubic;
Canvas.PixelOffsetMode = PixelOffsetMode.HighQuality;
Canvas.FillRectangle(Brushes.Transparent, destRect);
Canvas.DrawImage(image, destRect, srcRectangle, GraphicsUnit.Pixel);
newImage.Save(filename, image.RawFormat);
}
Simply provide the DrawImage method with an ImageAttributes instance which has WrapMode set to TileFlipXY. This will prevent the edge from blending against the background color.
For sample code that doesn't leak memory like the other answers here, see this gist
Try it like this, i think i've never got a black border...
If you want to use System.Drawing libraries:
using (var sourceBmp = new Bitmap(sourcePath))
{
decimal aspect = (decimal)sourceBmp.Width / (decimal)sourceBmp.Height;
int newHeight = (int)(newWidth / aspect);
using (var destinationBmp = new Bitmap(newWidth, newHeight))
{
using (var destinationGfx = Graphics.FromImage(destinationBmp))
{
destinationGfx.InterpolationMode = InterpolationMode.HighQualityBicubic;
destinationGfx.DrawImage(sourceBmp, new Rectangle(0, 0, destinationBmp.Width, destinationBmp.Height));
destinationBmp.Save(destinationPath, ImageFormat.Jpeg);
}
}
}
or you can do the same with wpf, like this:
using (var output = new FileStream(outputPath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None))
{
var imageDecoder = BitmapDecoder.Create(inputStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None);
var imageFrame = imageDecoder.Frames[0];
decimal aspect = (decimal)imageFrame.Width / (decimal)imageFrame.Height;
var height = (int)(newWidth / aspect);
var imageResized = new TransformedBitmap(imageFrame,new ScaleTransform(
newWidth / imageFrame.Width * Dpi / imageFrame.DpiX,
height / imageFrame.Height * Dpi / imageFrame.DpiY, 0, 0));
var targetFrame = BitmapFrame.Create(imageResized);
var targetEncoder = new JpegBitmapEncoder();
targetEncoder.Frames.Add(targetFrame);
targetEncoder.QualityLevel = 80;
targetEncoder.Save(output);
}
I recommend the WPF way. The compression & quality seems better...
For me it was a bad Bitmap parameter. Instead of this:
new Bitmap(width, height, PixelFormat.Format32bppPArgb);
Just remove the PixelFormat to this:
new Bitmap(width, height);
And everything was ok then.
With the PixelFormat I had black border on the top and left border. Then I tried g.PixelOffsetMode = PixelOffsetMode.HighQuality; which seemed fine at first. But then I noticed light gray borders around the whole image.
Related
I'm facing an issue (ghost border) when an image is resized on the live server, but not on localhost. This is really weird. I did some research on web and found similar solutions:
https://mariusschulz.com/blog/preventing-ghost-borders-when-resizing-images-with-system-drawing
https://www.codeproject.com/Articles/11143/Image-Resizing-outperform-GDI
Ghost-borders ('ringing') when resizing in GDI+
Below is the code that I'm using, inside I have commented old code and new code.
protected byte[] ApplyResize(byte[] byteArray, int targetSize, Size originalSize = default(Size))
{
using (MemoryStream ms = new MemoryStream(byteArray))
{
if (targetSize <= 0)
{
targetSize = 800;
}
var image = Image.FromStream(ms);
var size = default(Size);
if (originalSize != default(Size))
{
size = CalculateDimensions(originalSize, targetSize);
}
else
{
size = new Size(targetSize, targetSize);
}
var resized = new Bitmap(size.Width, size.Height);
using (var graphics = Graphics.FromImage(resized))
{
//old code
//graphics.CompositingQuality = CompositingQuality.HighSpeed;
//graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
//graphics.CompositingMode = CompositingMode.SourceCopy;
//graphics.DrawImage(image, 0, 0, size.Width, size.Height);
//new code
graphics.CompositingQuality = CompositingQuality.HighSpeed;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.CompositingMode = CompositingMode.SourceCopy;
var attributes = new ImageAttributes();
attributes.SetWrapMode(WrapMode.TileFlipXY);
var destination = new Rectangle(0, 0, size.Width, size.Height);
graphics.DrawImage(image, destination, 0, 0, size.Width, size.Height, GraphicsUnit.Pixel, attributes);
}
using (var ms2 = new MemoryStream())
{
resized.Save(ms2, image.RawFormat);
return ms2.ToArray();
}
}
}
I tested on the live server, yes, the ghost border is gone, but the image has gotten a wrong position.
This image is the expected result after resize, all clear, no problem
This is what happens on localhost and live server, when using the new code, image gets cut with weird position
To me it looks like the DPI (resolution) of the resized image is different than the real image. This would make sense, as the resolution of the new bitmap is dependent of the resolution of the display. And your server might not really have a display...
To make sure you get the expected results you should call setResolution on it with the resolution of the original image, right after creating the new image and before drawing to it, like this:
var resized = new Bitmap(size.Width, size.Height);
resized.SetResolution(image.HorizontalResolution, image.VerticalResolution);
This should be a good fix in general, as you might also get issues when running locally with image with have a different DPI than the one you are testing with.
On top of that, as was mentioned above, you should change the call to DrawImage to the following, as you forgot to specify the source rectangle:
graphics.DrawImage(image, destination, 0, 0, originalSize.Width, originalSize.Height, GraphicsUnit.Pixel, attributes);
Additionally you might need to explain what CalculateDimensions does, as this might produce something which is wrong and as is not visible here we can only guess.
I solve my issue by using using (ImageAttributes wrapMode = new ImageAttributes()) inside and upload image size must more than 415x415 , not sure why, but upload lower than 415x415 border will still appear and draw in correct position
protected byte[] ApplyResize(byte[] byteArray, int targetSize, Size originalSize = default(Size))
{
using (MemoryStream ms = new MemoryStream(byteArray))
{
if (targetSize <= 0)
{
targetSize = 800;
}
var image = Image.FromStream(ms);
var size = default(Size);
if (originalSize != default(Size))
{
size = CalculateDimensions(originalSize, targetSize);
}
else
{
size = new Size(targetSize, targetSize);
}
var resized = new Bitmap(size.Width, size.Height);
using (var graphics = Graphics.FromImage(resized))
{
graphics.CompositingQuality = CompositingQuality.HighSpeed;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.CompositingMode = CompositingMode.SourceCopy;
var destination = new Rectangle(0, 0, size.Width, size.Height);
using (ImageAttributes wrapMode = new ImageAttributes())
{
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(image, destination, 0, 0, originalSize.Width, originalSize.Height, GraphicsUnit.Pixel, wrapMode);
}
}
using (var ms2 = new MemoryStream())
{
resized.Save(ms2, image.RawFormat);
return ms2.ToArray();
}
}
}
I am using Cyotek ImageBox to zoom a image, now if I zoom to a part of image and that part is visible in the ImageBox how can I save that part of image which is visible in ImageBox.
The GetSourceImageRegion method allows you to get a RectangleF that describes the part of the image that is visible in the current state of an ImageBox.
The example code below will create a new Bitmap based on the visible part of the image. This example is not zoomed.
Rectangle visibleImageRegion;
Bitmap result;
visibleImageRegion = Rectangle.Round(imageBox.GetSourceImageRegion());
result = new Bitmap(visibleImageRegion.Width, visibleImageRegion.Height);
using (Graphics g = Graphics.FromImage(result))
{
g.DrawImage(imageBox.Image, new Rectangle(Point.Empty, visibleImageRegion.Size), visibleImageRegion, GraphicsUnit.Pixel);
}
This next example does the same as above, but also scales the new image to match the ImageBox
RectangleF visibleImageRegion;
Bitmap result;
double zoomFactor;
int w;
int h;
visibleImageRegion = imageBox.GetSourceImageRegion();
zoomFactor = imageBox.ZoomFactor;
w = Convert.ToInt32(visibleImageRegion.Width * zoomFactor);
h = Convert.ToInt32(visibleImageRegion.Height * zoomFactor);
result = new Bitmap(w, h);
using (Graphics g = Graphics.FromImage(result))
{
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(imageBox.Image, new Rectangle(0, 0, w, h), visibleImageRegion, GraphicsUnit.Pixel);
}
You could hook into the Scroll or Zoomed events of the control to detect when you need to update the image based on user activity.
I'm trying to add some text to an Image programmatically and later I save it. But the end result is somewhat not convincing.
The text on the image appears quite blurry.
The snippet I use to render is as follows :
static void ModifyImage()
{
Image origImage;
if (File.Exists(path))
{
using (Stream s = new FileStream(path, FileMode.Open, FileAccess.Read))
{
origImage = Image.FromStream(s);
}
using (Image newImage = new Bitmap(path))
{
// Get the image's original width and height
int originalWidth = origImage.Width;
int originalHeight = origImage.Height;
// To preserve the aspect ratio
float ratio = Math.Min((float)originalWidth, (float)originalHeight);
Graphics graphics = Graphics.FromImage(newImage);
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
string strSoftwareVersion = "Version " + StrVersion;
var oVersionFont = new Font("Arial", 15f, FontStyle.Bold);
SizeF strSize = graphics.MeasureString(strSoftwareVersion, oVersionFont);
float mX = (float)((originalWidth * 0.85) - 5.0);
float mY = (float)((originalHeight * 0.85) - 5.0);
float mWidth = (float) (strSize.Width + 5.0);
float mHeight = (float)(strSize.Height + 10.0);
/*
* Alternate I tried.
*/
//GraphicsPath blackfont = new GraphicsPath();
//SolidBrush brsh = new SolidBrush(Color.White);
//blackfont.AddString("TEST APP", oVersionFont.FontFamily, (int)FontStyle.Bold, 15, new Point((int)(originalWidth * 0.85), (int)(originalHeight * 0.85)), StringFormat.GenericDefault);
//graphics.FillPath(brsh, blackfont);
/*
*Software Version String
*/
graphics.DrawString("Version " + StrVersion, new Font("Arial", 15f, FontStyle.Bold), Brushes.White, (int)(originalWidth * 0.85), (int)(originalHeight * 0.85));
/*
* Save processed image
*/
newImage.Save(newPath, ImageFormat.Jpeg);
}
origImage.Dispose();
}
}
As you can see from the image, the blurriness of the text and surrounding areas are quite prominent. I'm quite sure this is due to the Aliasing or TextRendering issue with the alpha levels, but just not able to point the exact issue.
The text looks fine to me.
To make it crispier you can turn off all antialiasing.
The 'surrounding areas' obviously show some jpeg artifacts.
Good text and jpeg don't go together well.
Either turn up jpeg quality settings up from the default (of around 75%) or go for png!
Note that 90-95% quality, while greatly improving text quality also greatly enlarges size and going for png may well save you space in addition to being lossless..
I had a similar issue with the text being quite badly aliased when drawn onto the image. This is my solution which produces a reasonable text quality.
"text" is a System.Windows.Media.FormattedText instance.
BitmapFrame originalImageSource = BitmapFrame.Create(new Uri(file.FullName), BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
var visual = new DrawingVisual();
using(DrawingContext drawingContext = visual.RenderOpen()) {
drawingContext.DrawImage(originalImageSource, new Rect(0, 0, originalImageSource.PixelWidth, originalImageSource.PixelHeight));
drawingContext.DrawText(text, topRight);
}
var renderTargetBitmap = new RenderTargetBitmap(originalImageSource.PixelWidth,
originalImageSource.PixelHeight,
originalImageSource.DpiX, originalImageSource.DpiY,
PixelFormats.Pbgra32);
renderTargetBitmap.Render(visual);
BitmapFrame bitmapFrame = BitmapFrame.Create(renderTargetBitmap);
BitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(bitmapFrame);
encoder.Save(stream);
Im using DrawToBitmap to save some labels as image. I would like to know how to change the resolution of these images, is there a way? Say I have a label with a text and I want to render it to an image file (not posting the full code):
this.label1 = new System.Windows.Forms.Label();
this.label1.AutoSize = true;
this.label1.Font = new System.Drawing.Font("Baskerville Old Face", 36F);
//...
this.label1.Size = new System.Drawing.Size(161, 54);
this.label1.Text = "Output";
//...
//save image:
Bitmap image = new Bitmap(161, 54);
this.label1.DrawToBitmap(image, this.label1.ClientRectangle);
image.Save(#"C:\image.jpg");
This is working fine, I will get something like this:
The resolution is ok, but is it possible to increase it? When I zoom into this image a little, I can see the individual pixels as large blocks:
I know that's normal, because its not a vector graphic and that's fine. I just would like to change it somehow, so that you can zoom in further before seeing individual pixels as large blocks. Any ideas?
Thank you.
Edit: If Im using only black/white images - is it maybe better to save the image as png or gif?
Something like this will do the job, just increase font size used from label:
Bitmap CreateBitmapImage(string text, Font textFont, SolidBrush textBrush)
{
Bitmap bitmap = new Bitmap(1, 1);
Graphics graphics = Graphics.FromImage(bitmap);
int intWidth = (int)graphics.MeasureString(text, textFont).Width;
int intHeight = (int)graphics.MeasureString(text, textFont).Height;
bitmap = new Bitmap(bitmap, new Size(intWidth, intHeight));
graphics = Graphics.FromImage(bitmap);
graphics.Clear(Color.White);
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
graphics.DrawString(text, textFont, textBrush,0,0);
graphics.Flush();
return (bitmap);
}
you can achieve this by increasing the value of second parameter of System.Drawing.Font.
this.label1.Font = new System.Drawing.Font("Baskerville Old Face", 1000F);
I'm really trying to nail out a little more performance out of this tidbit of code. It's not a heavly used bit of code but is used every time a new image is uploaded, and 4 times for each image (100px, 200px, 500px, 700px). So when there are any more than 2 or 3 images processing, it gets a little busy on the server. Also I'm trying to figure out how to make it correctly process images with a low resolution. Currently it just chops it off half way through, not plesent.
Examples: Original, large, xLarge
public static byte[] ResizeImageFile(byte[] imageFile, int targetSize)
{
using (System.Drawing.Image oldImage = System.Drawing.Image.FromStream(new MemoryStream(imageFile)))
{
Size newSize = CalculateDimensions(oldImage.Size, targetSize);
using (Bitmap newImage = new Bitmap(newSize.Width, newSize.Height, PixelFormat.Format32bppRgb))
{
newImage.SetResolution(oldImage.HorizontalResolution, oldImage.VerticalResolution);
using (Graphics canvas = Graphics.FromImage(newImage))
{
canvas.SmoothingMode = SmoothingMode.AntiAlias;
canvas.InterpolationMode = InterpolationMode.HighQualityBicubic;
canvas.PixelOffsetMode = PixelOffsetMode.HighQuality;
canvas.DrawImage(oldImage, new Rectangle(new Point(0, 0), newSize));
MemoryStream m = new MemoryStream();
newImage.Save(m, ImageFormat.Jpeg);
return m.GetBuffer();
}
}
}
}
private static Size CalculateDimensions(Size oldSize, int targetSize)
{
Size newSize = new Size();
if (oldSize.Width > oldSize.Height)
{
newSize.Width = targetSize;
newSize.Height = (int)(oldSize.Height * (float)targetSize / (float)oldSize.Width);
}
else
{
newSize.Width = (int)(oldSize.Width * (float)targetSize / (float)oldSize.Height);
newSize.Height = targetSize;
}
return newSize;
}
Thanks for and help!
The first thought that comes to mind is, have you thought about Multithreading it? i.e. calling this method for each image (or batch of images) in a separate thread? That way, if your server has a few cores you can get things done quicker. Just a thought...
(Threading is a great tip.)
Try to call your method with the smallest possible image as input each time, instead of the original image. If the original image is, say 2000px, then create the 700px image from it and then use your newly created 700px image to create the 500px, etc...
With the HighQualityBicubic setting I doubt that you'll notice any difference in the 100px image. (But it of course it needs to be verified.)
For completeness, here is the solution to the second part of the question which was never answered. When processing a low resolution image the image was being cut off. The solution now, seems obvious. The problem lies in this bit of code from above:
using (Bitmap newImage = new Bitmap(newSize.Width, newSize.Height,
PixelFormat.Format32bppRgb))
The problem being that I'm selecting the PixelFormat, not letting it be the format of the original image. The correct code is here:
public static byte[] ResizeImageFile(byte[] imageFile, int targetSize)
{
using (System.Drawing.Image oldImage = System.Drawing.Image.FromStream(new MemoryStream(imageFile)))
{
Size newSize = CalculateDimensions(oldImage.Size, targetSize);
using (Bitmap newImage = new Bitmap(newSize.Width, newSize.Height,
oldImage.PixelFormat))
{
newImage.SetResolution(oldImage.HorizontalResolution,
oldImage.VerticalResolution);
using (Graphics canvas = Graphics.FromImage(newImage))
{
canvas.SmoothingMode = SmoothingMode.AntiAlias;
canvas.InterpolationMode = InterpolationMode.HighQualityBicubic;
canvas.PixelOffsetMode = PixelOffsetMode.HighQuality;
canvas.DrawImage(oldImage, new Rectangle(new Point(0, 0), newSize));
MemoryStream m = new MemoryStream();
newImage.Save(m, ImageFormat.Jpeg);
return m.GetBuffer();
}
}
}
}