ImageLayout.Center does not center background image - c#

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

Related

ImageSharp and Font Height

I have a task to create an image that will be printed. On the picture, I need to put a single uppercase letter (Upper case, [A-Z]).
The printed image size can vary between 15cm height, and 30cm height (including any size in between).
The letter needs to span the full height of the printed image.
When setting the font size, I see you can get the size of the text.
using (Image<Rgba32> img = new Image<Rgba32>(imageWidth, imageHeight))
{
img.Mutate(x => x.Fill(Rgba32.White));
img.MetaData.HorizontalResolution = 96;
img.MetaData.VerticalResolution = 96;
var fo = SystemFonts.Find("Arial");
var font = new Font(fo, 1350, FontStyle.Regular);
I can get the size of my text here:
SizeF size = TextMeasurer.Measure(group.Text, new RendererOptions(font));
However, as you can see, I hard coded the size for my font here. The height needs to be matched to the height of the image.
Is there any way to specify this, without stretching and losing quality? Is there a way I can specify the height, in pixels? Maybe there's coloration to the font size that I can use safely?
When I set the Font size to the pixel height of my Image, I am seeing this:
I'm not sure why the circled parts have gaps. I am setting my top left position of the left hand text, to 0,0.... and the top right hand point of the 'QWW' group to the width of the image, and 0 as Y. But I'd expect them to be flush against the size, and the bottom.
TextMeasurer is designed for measurer text in the context of line and words not on individual characters because it doesn't look at individual glyph forms instead looks at the font as a whole to measure against line spacing etc.
Instead you will want to render the glyph directly to a vector using the nuget package SixLabors.Shapes.Text. This will allow you to accurately measure the final glyph + apply scaling and transforms to guarantee the glyph lines up with the edges of your image. It also saves you having to perform any expensive pixel level operations except the final drawing of the glyph to the image.
/// <param name="text">one or more characters to scale to fill as much of the target image size as required.</param>
/// <param name="targetSize">the size in pixels to generate the image</param>
/// <param name="outputFileName">path/filename where to save the image to</param>
private static void GenerateImage(string text, Primitives.Size targetSize, string outputFileName)
{
FontFamily fam = SystemFonts.Find("Arial");
Font font = new Font(fam, 100); // size doesn't matter too much as we will be scaling shortly anyway
RendererOptions style = new RendererOptions(font, 72); // again dpi doesn't overlay matter as this code genreates a vector
// this is the important line, where we render the glyphs to a vector instead of directly to the image
// this allows further vector manipulation (scaling, translating) etc without the expensive pixel operations.
IPathCollection glyphs = SixLabors.Shapes.TextBuilder.GenerateGlyphs(text, style);
var widthScale = (targetSize.Width / glyphs.Bounds.Width);
var heightScale = (targetSize.Height / glyphs.Bounds.Height);
var minScale = Math.Min(widthScale, heightScale);
// scale so that it will fit exactly in image shape once rendered
glyphs = glyphs.Scale(minScale);
// move the vectorised glyph so that it touchs top and left edges
// could be tweeked to center horizontaly & vertically here
glyphs = glyphs.Translate(-glyphs.Bounds.Location);
using (Image<Rgba32> img = new Image<Rgba32>(targetSize.Width, targetSize.Height))
{
img.Mutate(i => i.Fill(new GraphicsOptions(true), Rgba32.Black, glyphs));
img.Save(outputFileName);
}
}
I split up your question into 3 parts:
dynamic font size, rather than hard coded font size
the glyph should use the full height of the image
the glyph should be aligned to the left
Dynamically scale the text to fill the height of the image
After measuring the text size, calculate the factor by which the font needs to be scaled up or down to match the height of the image:
SizeF size = TextMeasurer.Measure(text, new RendererOptions(font));
float scalingFactor = finalImage.Height / size.Height;
var scaledFont = new Font(font, scalingFactor * font.Size);
This way the initially set font size is largely ignored. Now we can draw the text with the dynamically scaled font, depending on the height of the image:
Inflate text to use the entire height of the image
Depending on each glyph, we might now have a gap in between the top/bottom side of the image and the top/bottom side of the text. How a glyph is rendered or drawn depends heavily on the font in use. I am not an expert in typography, but AFAIK every font has it's own margin/padding, and has custom heights around the baseline.
In order for our glyph to align with the top and bottom of the image, we have to further scale up the font. To calculate this factor, we can determine the top and bottom edge of the currently drawn text by searching for the height (y) of the top-most and bottom-most pixels, and scale up the font with this difference. Additionally, we need to offset the glyph by the distance from the top of the image to the top edge of the glyph:
int top = GetTopPixel(initialImage, Rgba32.White);
int bottom = GetBottomPixel(initialImage, Rgba32.White);
int offset = top + (initialImage.Height - bottom);
SizeF inflatedSize = TextMeasurer.Measure(text, new RendererOptions(scaledFont));
float inflatingFactor = (inflatedSize.Height + offset) / inflatedSize.Height;
var inflatedFont = new Font(font, inflatingFactor * scaledFont.Size);
location.Offset(0.0f, -top);
Now we can draw the text with the top and the bottom snapping to the top and bottom edges of the image:
Move glyph to the very left
Lastly, depending on the glyph, the left side of the glyph might not snap with the left side of the image. Similar to the previous step, we can determine the left-most pixel of the text within the current image containing the inflated glyph, and move the text accordingly to the left to remove the gap in between:
int left = GetLeftPixel(intermediateImage, Rgba32.White);
location.Offset(-left, 0.0f);
Now we can draw the text aligning with the left side of the image:
This final image now has the font dynamically scaled depending on the size of the image, has been further scaled up and moved to fill up the entire height of the image, and has been further moved to have no gap to the left.
Note
When drawing the text, the DPI of the TextGraphicsOptions should match the DPI of the image:
var textGraphicOptions = new TextGraphicsOptions(true)
{
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
DpiX = (float)finalImage.MetaData.HorizontalResolution,
DpiY = (float)finalImage.MetaData.VerticalResolution
};
Code
private static void CreateImageFiles()
{
Directory.CreateDirectory("output");
string text = "J";
Rgba32 backgroundColor = Rgba32.White;
Rgba32 foregroundColor = Rgba32.Black;
int imageWidth = 256;
int imageHeight = 256;
using (var finalImage = new Image<Rgba32>(imageWidth, imageHeight))
{
finalImage.Mutate(context => context.Fill(backgroundColor));
finalImage.MetaData.HorizontalResolution = 96;
finalImage.MetaData.VerticalResolution = 96;
FontFamily fontFamily = SystemFonts.Find("Arial");
var font = new Font(fontFamily, 10, FontStyle.Regular);
var textGraphicOptions = new TextGraphicsOptions(true)
{
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
DpiX = (float)finalImage.MetaData.HorizontalResolution,
DpiY = (float)finalImage.MetaData.VerticalResolution
};
SizeF size = TextMeasurer.Measure(text, new RendererOptions(font));
float scalingFactor = finalImage.Height / size.Height;
var scaledFont = new Font(font, scalingFactor * font.Size);
PointF location = new PointF();
using (Image<Rgba32> initialImage = finalImage.Clone(context => context.DrawText(textGraphicOptions, text, scaledFont, foregroundColor, location)))
{
initialImage.Save("output/initial.png");
int top = GetTopPixel(initialImage, backgroundColor);
int bottom = GetBottomPixel(initialImage, backgroundColor);
int offset = top + (initialImage.Height - bottom);
SizeF inflatedSize = TextMeasurer.Measure(text, new RendererOptions(scaledFont));
float inflatingFactor = (inflatedSize.Height + offset) / inflatedSize.Height;
var inflatedFont = new Font(font, inflatingFactor * scaledFont.Size);
location.Offset(0.0f, -top);
using (Image<Rgba32> intermediateImage = finalImage.Clone(context => context.DrawText(textGraphicOptions, text, inflatedFont, foregroundColor, location)))
{
intermediateImage.Save("output/intermediate.png");
int left = GetLeftPixel(intermediateImage, backgroundColor);
location.Offset(-left, 0.0f);
finalImage.Mutate(context => context.DrawText(textGraphicOptions, text, inflatedFont, foregroundColor, location));
finalImage.Save("output/final.png");
}
}
}
}
private static int GetTopPixel(Image<Rgba32> image, Rgba32 backgroundColor)
{
for (int y = 0; y < image.Height; y++)
{
for (int x = 0; x < image.Width; x++)
{
Rgba32 pixel = image[x, y];
if (pixel != backgroundColor)
{
return y;
}
}
}
throw new InvalidOperationException("Top pixel not found.");
}
private static int GetBottomPixel(Image<Rgba32> image, Rgba32 backgroundColor)
{
for (int y = image.Height - 1; y >= 0; y--)
{
for (int x = image.Width - 1; x >= 0; x--)
{
Rgba32 pixel = image[x, y];
if (pixel != backgroundColor)
{
return y;
}
}
}
throw new InvalidOperationException("Bottom pixel not found.");
}
private static int GetLeftPixel(Image<Rgba32> image, Rgba32 backgroundColor)
{
for (int x = 0; x < image.Width; x++)
{
for (int y = 0; y < image.Height; y++)
{
Rgba32 pixel = image[x, y];
if (pixel != backgroundColor)
{
return x;
}
}
}
throw new InvalidOperationException("Left pixel not found.");
}
We don't need to save all 3 images, however we do need to create all 3 images and inflate and move the text step by step in order to fill up the entire height of the image and start at the very left of the image.
This solution works independently of the used font. Also, for a production application avoid finding a font via SystemFonts, because the font in question might not be available at the target machine. To have an stable stand-alone solution, deploy a TTF font with the application and install the font via FontCollection manually.

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.

ListView Larget Icon vertical alignment of the images

I have ListView with the View type LargeIcon. The ListView has assigned LargeImageList. The assigned ImageList has ImageSize 200x200.
Images that will be added to the ImageList have size that doesn't match to 200x200 ImageList size. They can have a lesser width or height. In both cases I want images to be aligned by center, i.e. MiddleCenter property for Winforms.Label class
ImageList will resize the image to fit the ImageSize. To get the image at its original size, and centered, you'll need to create a new image with the desired properties. Sample code to do this (untested):
public static void AddCenteredImage(ImageList list, Image image) {
using (var bmp = new Bitmap(list.ImageSize.Width, list.ImageSize.Height))
using (var gr = Graphics.FromImage(bmp)) {
gr.Clear(Color.Transparent); // Change background if necessary
var size = image.Size;
if (size.Width > list.ImageSize.Width || size.Height > list.ImageSize.Height) {
// Image too large, rescale to fit the image list
double wratio = list.ImageSize.Width / size.Width;
double hratio = list.ImageSize.Height / size.Height;
double ratio = Math.Min(wratio, hratio);
size = new Size((int)(ratio * size.Width), (int)(ratio * size.Height));
}
var rc = new Rectangle(
(list.ImageSize.Width - size.Width) / 2,
(list.ImageSize.Height - size.Height) / 2,
size.Width, size.Height);
gr.DrawImage(image, rc);
list.Images.Add(bmp);
}
}

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.

Resize image proportionally with MaxHeight and MaxWidth constraints

Using System.Drawing.Image.
If an image width or height exceed the maximum, it need to be resized proportionally .
After resized it need to make sure that neither width or height still exceed the limit.
The Width and Height will be resized until it is not exceed to maximum and minimum automatically (biggest size possible) and also maintain the ratio.
Like this?
public static void Test()
{
using (var image = Image.FromFile(#"c:\logo.png"))
using (var newImage = ScaleImage(image, 300, 400))
{
newImage.Save(#"c:\test.png", ImageFormat.Png);
}
}
public static Image ScaleImage(Image image, int maxWidth, int maxHeight)
{
var ratioX = (double)maxWidth / image.Width;
var ratioY = (double)maxHeight / image.Height;
var ratio = Math.Min(ratioX, ratioY);
var newWidth = (int)(image.Width * ratio);
var newHeight = (int)(image.Height * ratio);
var newImage = new Bitmap(newWidth, newHeight);
using (var graphics = Graphics.FromImage(newImage))
graphics.DrawImage(image, 0, 0, newWidth, newHeight);
return newImage;
}
Much longer solution, but accounts for the following scenarios:
Is the image smaller than the bounding box?
Is the Image and the Bounding Box square?
Is the Image square and the bounding box isn't
Is the image wider and taller than the bounding box
Is the image wider than the bounding box
Is the image taller than the bounding box
private Image ResizePhoto(FileInfo sourceImage, int desiredWidth, int desiredHeight)
{
//throw error if bouning box is to small
if (desiredWidth < 4 || desiredHeight < 4)
throw new InvalidOperationException("Bounding Box of Resize Photo must be larger than 4X4 pixels.");
var original = Bitmap.FromFile(sourceImage.FullName);
//store image widths in variable for easier use
var oW = (decimal)original.Width;
var oH = (decimal)original.Height;
var dW = (decimal)desiredWidth;
var dH = (decimal)desiredHeight;
//check if image already fits
if (oW < dW && oH < dH)
return original; //image fits in bounding box, keep size (center with css) If we made it bigger it would stretch the image resulting in loss of quality.
//check for double squares
if (oW == oH && dW == dH)
{
//image and bounding box are square, no need to calculate aspects, just downsize it with the bounding box
Bitmap square = new Bitmap(original, (int)dW, (int)dH);
original.Dispose();
return square;
}
//check original image is square
if (oW == oH)
{
//image is square, bounding box isn't. Get smallest side of bounding box and resize to a square of that center the image vertically and horizontally with Css there will be space on one side.
int smallSide = (int)Math.Min(dW, dH);
Bitmap square = new Bitmap(original, smallSide, smallSide);
original.Dispose();
return square;
}
//not dealing with squares, figure out resizing within aspect ratios
if (oW > dW && oH > dH) //image is wider and taller than bounding box
{
var r = Math.Min(dW, dH) / Math.Min(oW, oH); //two dimensions so figure out which bounding box dimension is the smallest and which original image dimension is the smallest, already know original image is larger than bounding box
var nH = oH * r; //will downscale the original image by an aspect ratio to fit in the bounding box at the maximum size within aspect ratio.
var nW = oW * r;
var resized = new Bitmap(original, (int)nW, (int)nH);
original.Dispose();
return resized;
}
else
{
if (oW > dW) //image is wider than bounding box
{
var r = dW / oW; //one dimension (width) so calculate the aspect ratio between the bounding box width and original image width
var nW = oW * r; //downscale image by r to fit in the bounding box...
var nH = oH * r;
var resized = new Bitmap(original, (int)nW, (int)nH);
original.Dispose();
return resized;
}
else
{
//original image is taller than bounding box
var r = dH / oH;
var nH = oH * r;
var nW = oW * r;
var resized = new Bitmap(original, (int)nW, (int)nH);
original.Dispose();
return resized;
}
}
}
Working Solution :
For Resize image with size lower then 100Kb
WriteableBitmap bitmap = new WriteableBitmap(140,140);
bitmap.SetSource(dlg.File.OpenRead());
image1.Source = bitmap;
Image img = new Image();
img.Source = bitmap;
WriteableBitmap i;
do
{
ScaleTransform st = new ScaleTransform();
st.ScaleX = 0.3;
st.ScaleY = 0.3;
i = new WriteableBitmap(img, st);
img.Source = i;
} while (i.Pixels.Length / 1024 > 100);
More Reference at http://net4attack.blogspot.com/

Categories