I'm searching for a button control that will AutoSize its image. Normal button controls won't do this. I'm using C#.Net 2.0.
For example, I have a Button that is 200 x 50px and an image that is 800 x 100px. I want to resize the Image so that it is a little to the left, near the text of the button. With a PictureBox I can do this. But when I lay a PictureBox over the Button its very ugly because you can't click there.
You can do this as follows:
button.Image = Image.FromFile(path);
button.AutoSize = true;
E.g: Or, You can create a new Button type that will change the size of the image:
public class AutoSizeButton : Button
{
public new Image Image
{
get { return base.Image; }
set
{
Image newImage = new Bitmap(Width, Height);
using (Graphics g = Graphics.FromImage(newImage))
{
g.DrawImage(value, 0, 0, Width, Height);
}
base.Image = newImage;
}
}
}
Test:
AutoSizeButton button = new AutoSizeButton();
button.Location = new Point(27, 52);
button.Name = "button";
button.Size = new Size(75, 23);
button.Text = "Test";
button.UseVisualStyleBackColor = true;
button.Image = Image.FromFile(path);
Controls.Add(button);
I was looking for a version of this in vb.net, so I started with mykhaylo's answer and improved it a bit. This code resizes the image to fit proportionally, centers the image in the button, and provides inner padding for the image.
This button actually does not use the "Image" property of the button and exposes a property "AutoScaleImage" that should be set alternately.
Here is the C# version - VB.net at bottom. Enjoy!
[System.ComponentModel.DesignerCategory("")]
public class AutoScaleButton : Button
{
private Image _AutoScaleImage;
public Image AutoScaleImage {
get { return _AutoScaleImage; }
set {
_AutoScaleImage = value;
if (value != null)
this.Invalidate();
}
}
private int _AutoScaleBorder;
public int AutoScaleBorder {
get { return _AutoScaleBorder; }
set {
_AutoScaleBorder = value;
this.Invalidate();
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
DrawResizeImage(ref e.Graphics);
}
private void DrawResizeImage(Graphics g)
{
if (_AutoScaleImage == null)
return;
int iB = AutoScaleBorder;
int iOff = 0;
Rectangle rectLoc = default(Rectangle);
Rectangle rectSrc = default(Rectangle);
Size sizeDst = new Size(Math.Max(0, this.Width - 2 * iB),
Math.Max(0, this.Height - 2 * iB));
Size sizeSrc = new Size(_AutoScaleImage.Width,
_AutoScaleImage.Height);
double ratioDst = sizeDst.Height / sizeDst.Width;
double ratioSrc = sizeSrc.Height / sizeSrc.Width;
rectSrc = new Rectangle(0, 0, sizeSrc.Width, sizeSrc.Height);
if (ratioDst < ratioSrc) {
iOff = (sizeDst.Width -
(sizeDst.Height * sizeSrc.Width / sizeSrc.Height)) / 2;
rectLoc = new Rectangle(iB + iOff,
iB,
sizeDst.Height * sizeSrc.Width / sizeSrc.Height,
sizeDst.Height);
} else {
iOff = (sizeDst.Height - (sizeDst.Width * sizeSrc.Height / sizeSrc.Width)) / 2;
rectLoc = new Rectangle(iB,
iB + iOff,
sizeDst.Width,
sizeDst.Width * sizeSrc.Height / sizeSrc.Width);
}
g.DrawImage(_AutoScaleImage, rectLoc, rectSrc, GraphicsUnit.Pixel);
}
}
Or my original VB.NET Version.
<System.ComponentModel.DesignerCategory("")> _
Public Class AutoScaleButton
Inherits Button
Private _AutoScaleImage As Image
Public Property AutoScaleImage() As Image
Get
Return _AutoScaleImage
End Get
Set(value As Image)
_AutoScaleImage = value
If value IsNot Nothing Then Me.Invalidate()
End Set
End Property
Private _AutoScaleBorder As Integer
Public Property AutoScaleBorder() As Integer
Get
Return _AutoScaleBorder
End Get
Set(ByVal value As Integer)
_AutoScaleBorder = value
Me.Invalidate()
End Set
End Property
Protected Overrides Sub OnPaint(e As PaintEventArgs)
MyBase.OnPaint(e)
DrawResizeImage(e.Graphics)
End Sub
Private Sub DrawResizeImage(ByRef g As Graphics)
If _AutoScaleImage Is Nothing Then Exit Sub
Dim iB As Integer = AutoScaleBorder, iOff As Integer = 0
Dim rectLoc As Rectangle, rectSrc As Rectangle
Dim sizeDst As Size = New Size(Math.Max(0, Me.Width - 2 * iB), Math.Max(0, Me.Height - 2 * iB))
Dim sizeSrc As Size = New Size(_AutoScaleImage.Width, _AutoScaleImage.Height)
Dim ratioDst As Double = sizeDst.Height / sizeDst.Width
Dim ratioSrc As Double = sizeSrc.Height / sizeSrc.Width
rectSrc = New Rectangle(0, 0, sizeSrc.Width, sizeSrc.Height)
If ratioDst < ratioSrc Then
iOff = (sizeDst.Width - (sizeDst.Height * sizeSrc.Width / sizeSrc.Height)) / 2
rectLoc = New Rectangle(iB + iOff, iB, _
sizeDst.Height * sizeSrc.Width / sizeSrc.Height, _
sizeDst.Height)
Else
iOff = (sizeDst.Height - (sizeDst.Width * sizeSrc.Height / sizeSrc.Width)) / 2
rectLoc = New Rectangle(iB, iB + iOff, _
sizeDst.Width, _
sizeDst.Width * sizeSrc.Height / sizeSrc.Width)
End If
g.DrawImage(_AutoScaleImage, rectLoc, rectSrc, GraphicsUnit.Pixel)
End Sub
End Class
public class ExtButton : Button
{
public new Image Image
{
get { return base.Image; }
set {
base.Image = ScaleImage(value, this.Width, this.Height);
}
}
private 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);
Graphics.FromImage(newImage).DrawImage(image, 0, 0, newWidth, newHeight);
return newImage;
}
}
I originally proposed using a standard ImageButton but then read your comment that you are trying to size the button to the image. For that use a LinkButton:
<asp:LinkButton ID="foo" runat="server" OnClick="LinkButton1_Click">
<asp:Image ID="imagefoo" runat="server" ImageUrl="~/Foo.jpg" />
</asp:LinkButton>
Related
I have a label that shows the file name .. I had to set AutoSize of the label to False for designing.
So when the file name text got longer than label size.. it got cut like in the picture.
label1.Size = new Size(200, 32);
label1.AutoSize = false;
How do I re-size the text automatically to fit the label size, when the text is longer than the label size?
You can use my code snippet below. System needs some loops to calculate the label's font based on text size.
while(label1.Width < System.Windows.Forms.TextRenderer.MeasureText(label1.Text,
new Font(label1.Font.FontFamily, label1.Font.Size, label1.Font.Style)).Width)
{
label1.Font = new Font(label1.Font.FontFamily, label1.Font.Size - 0.5f, label1.Font.Style);
}
Label scaling
private void scaleFont(Label lab)
{
Image fakeImage = new Bitmap(1, 1); //As we cannot use CreateGraphics() in a class library, so the fake image is used to load the Graphics.
Graphics graphics = Graphics.FromImage(fakeImage);
SizeF extent = graphics.MeasureString(lab.Text, lab.Font);
float hRatio = lab.Height / extent.Height;
float wRatio = lab.Width / extent.Width;
float ratio = (hRatio < wRatio) ? hRatio : wRatio;
float newSize = lab.Font.Size * ratio;
lab.Font = new Font(lab.Font.FontFamily, newSize, lab.Font.Style);
}
TextRenderer Approach pointed out by #ToolmakerSteve in the comments
private void ScaleFont(Label lab)
{
SizeF extent = TextRenderer.MeasureText(lab.Text, lab.Font);
float hRatio = lab.Height / extent.Height;
float wRatio = lab.Width / extent.Width;
float ratio = (hRatio < wRatio) ? hRatio : wRatio;
float newSize = lab.Font.Size * ratio;
lab.Font = new Font(lab.Font.FontFamily, newSize, lab.Font.Style);
}
Based on the article provided by #brgerner, I'll provide the alternative implementation here, as that one marked as an answer is not so efficient nor complete as this one below:
public class FontWizard
{
public static Font FlexFont(Graphics g, float minFontSize, float maxFontSize, Size layoutSize, string s, Font f, out SizeF extent)
{
if (maxFontSize == minFontSize)
f = new Font(f.FontFamily, minFontSize, f.Style);
extent = g.MeasureString(s, f);
if (maxFontSize <= minFontSize)
return f;
float hRatio = layoutSize.Height / extent.Height;
float wRatio = layoutSize.Width / extent.Width;
float ratio = (hRatio < wRatio) ? hRatio : wRatio;
float newSize = f.Size * ratio;
if (newSize < minFontSize)
newSize = minFontSize;
else if (newSize > maxFontSize)
newSize = maxFontSize;
f = new Font(f.FontFamily, newSize, f.Style);
extent = g.MeasureString(s, f);
return f;
}
public static void OnPaint(object sender, PaintEventArgs e, string text)
{
var control = sender as Control;
if (control == null)
return;
control.Text = string.Empty; //delete old stuff
var rectangle = control.ClientRectangle;
using (Font f = new System.Drawing.Font("Microsoft Sans Serif", 20.25f, FontStyle.Bold))
{
SizeF size;
using (Font f2 = FontWizard.FlexFont(e.Graphics, 5, 50, rectangle.Size, text, f, out size))
{
PointF p = new PointF((rectangle.Width - size.Width) / 2, (rectangle.Height - size.Height) / 2);
e.Graphics.DrawString(text, f2, Brushes.Black, p);
}
}
}
}
and the usage:
val label = new Label();
label.Paint += (sender, e) => FontWizard.OnPaint(sender, e, text);
I use the following weighted scaling trick to provide a good fit, i.e. a weighted tradeoff is made between fitting the height and fitting the width. It's in VB .net, but I think you can translate to C# easily.
Function shrinkFontToFit(f As Font, text As String, requiredsize As SizeF) As Font
Dim actualsize As SizeF = TextRenderer.MeasureText(text, f)
Return New Font(f.FontFamily, f.Size * (requiredsize.Width + requiredsize.Height ) _
/ (actualsize.Width + actualsize.Height), f.Style, GraphicsUnit.Pixel)
End Function
With inspiration from #bnguyen82 i came up with something that works all the way.
public static void ScaleLabel(Label label, float stepSize = 0.5f)
{
//decrease font size if text is wider or higher than label
while (lblTextSize() is Size s && s.Width > label.Width || s.Height > label.Height)
{
label.Font = new Font(label.Font.FontFamily, label.Font.Size - stepSize, label.Font.Style);
}
//increase font size if label width is bigger than text size
while (label.Width > lblTextSize().Width)
{
var font = new Font(label.Font.FontFamily, label.Font.Size + stepSize, label.Font.Style);
var nextSize = TextRenderer.MeasureText(label.Text, font);
//dont make text width or hight bigger than label
if (nextSize.Width > label.Width || nextSize.Height > label.Height)
break;
label.Font = font;
}
Size lblTextSize() => TextRenderer.MeasureText(label.Text,
new Font(label.Font.FontFamily, label.Font.Size, label.Font.Style));
}
PS: In order for this to work the label needs to have AutoSize = false and either to be docked or anchored.
this method worked for me
simply you will reduce font size until it reach the width you want.
while (label1.Width >150 )
{
label1.Font = new Font(label1.Font.FontFamily, label1.Font.Size - 0.5f, label1.Font.Style);
}
First you need an event whenever text changes:
lbl.TextChanged += new EventHandler(Label_TextChanged);
Then you change the font inside the event to fit :
private void Label_TextChanged(object sender, EventArgs e)
{
Label lbl = (Label)sender;
if (lbl.Image != null) return;
using (Graphics cg = lbl.CreateGraphics())
{
SizeF lblsize = new SizeF(lbl.Width, lbl.Height);
SizeF textsize = cg.MeasureString(lbl.Text, lbl.Font, lblsize);
while (textsize.Width > lblsize.Width - (lblsize.Width * 0.1))
{
lbl.Font = new Font(lbl.Font.Name, lbl.Font.Size - 1, lbl.Font.Style);
textsize = cg.MeasureString(lbl.Text, lbl.Font, lblsize);
if (lbl.Font.Size < 6) break;
}
}
}
This way you have smaller fonts to fit.
I skip when image is involved.
I also stop to size 5.
I shrink at intervals of -1. -0.5 will also work.
I consider a 10% of control area as unusable 'border' - works well.
This technique will work but do not expect results on large texts into small labels.
If you do not have images you can use control instead of label and apply to buttons and textboxes as well.
I think the easiest way could be to check the render size and if it is greater than the actual label size, decrease the fontsize of the label.
private void label3_Paint(object sender, PaintEventArgs e)
{
Size sz = TextRenderer.MeasureText(label1.Text, label1.Font, label1.Size, TextFormatFlags.WordBreak);
if (sz.Width > label1.Size.Width || sz.Height > label1.Size.Height)
{
DecreaseFontSize(label1);
}
}
public void DecreaseFontSize(Label lbl)
{
lbl.Font = new System.Drawing.Font(lbl.Font.Name, lbl.Font.Size - 1, lbl.Font.Style);
}
Similar to Photoshop, where you can type in a ratio or in my case a certain dimention such as 800x600, I want to be able to force a Rectangle to be a fixed ratio/size when dragging the mouse..
At the moment I have this:
Which will crop an image using the Rectangle created from clicking and dragging on the PictureBox. The bounding box selects the area without any constraints. I want to be able to force the rectangle to be a certain ratio (prefereably from a set resolution) similar to the way Photoshop's cropping tool works.
My source if anyone needs more detail:
Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace CropResize
{
public partial class Form1 : Form
{
private static string path;
public Form1(string filePath)
{
InitializeComponent();
path = filePath;
}
private Image _originalImage;
private Image _newImage;
private bool _selecting;
private Rectangle _selection;
private void Form1_Load(object sender, System.EventArgs e)
{
pictureBox1.Image = Image.FromFile(path);
if (pictureBox1.Image.Height > Screen.PrimaryScreen.Bounds.Height - 50 || pictureBox1.Image.Width > Screen.PrimaryScreen.Bounds.Width - 50)
{
if (pictureBox1.Image.Height > Screen.PrimaryScreen.Bounds.Height - 50)
{
Height = Screen.PrimaryScreen.Bounds.Height - 50;
panel1.Height = Size.Height - statusStrip1.Height - buttonSave.Height - 60;
}
if (pictureBox1.Image.Width > Screen.PrimaryScreen.Bounds.Width - 50)
{
Size = new Size(Screen.PrimaryScreen.Bounds.Width - 50, Screen.PrimaryScreen.Bounds.Height - 50);
panel1.Width = Size.Width - statusStrip1.Height - buttonSave.Height - 60;
}
pictureBox1.Image = pictureBox1.Image.Fit2PictureBox(pictureBox1);
panel1.Size = new Size(pictureBox1.Image.Width, pictureBox1.Image.Height);
}
Size = new Size(panel1.Size.Width + 50, panel1.Size.Height + buttonSave.Height + statusStrip1.Height + 80);
// Create a copy of the original image for later use
_originalImage = pictureBox1.Image.Clone() as Image;
_newImage = pictureBox1.Image.Clone() as Image;
}
private void buttonOrig_Click(object sender, System.EventArgs e)
{
pictureBox1.Image = _originalImage.Clone() as Image;
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
// Starting point of the selection:
if (e.Button == MouseButtons.Left)
{
pictureBox1.Image.Dispose();
pictureBox1.Image = _originalImage.Clone() as Image;
_selecting = true;
_selection = new Rectangle(new Point(e.X, e.Y), new Size());
}
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
// Update the actual size of the selection:
if (_selecting)
{
_selection.Width = (e.X - _selection.X);
_selection.Height = (e.Y - _selection.Y);
//int nGCD = GetGreatestCommonDivisor(1920, 1080);
//_selection.Width = _selection.Width / nGCD;
//_selection.Height = _selection.Height / nGCD;
int widthRatio = 16;
int heightRatio = 9;
if (_selection.Height * widthRatio <= _selection.Width)
{
_selection.Width = _selection.Height * widthRatio;
}
else if (_selection.Width * heightRatio <= _selection.Width)
{
_selection.Height = _selection.Width * heightRatio;
}
// Redraw the picturebox:
pictureBox1.Refresh();
}
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (_selecting && _selection.Height != 0)
{
// Draw a rectangle displaying the current selection
e.Graphics.DrawRectangle(Pens.WhiteSmoke, _selection);
//e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(50, Color.Gray)), 0, pictureBox1.Height - pictureBox1.Image.Height, pictureBox1.Image.Width, pictureBox1.Image.Height);
e.Graphics.SetClip(_selection, CombineMode.Exclude);
e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(100, Color.Black)), 0, 0, pictureBox1.Width, pictureBox1.Height);
int nGCD = GetGreatestCommonDivisor(_selection.Width, _selection.Height);
string str = string.Format("{0}:{1}", _selection.Width / nGCD, _selection.Height / nGCD);
toolStripStatusLabel1.Text = "Image Size: " + _selection.Width + "x" + _selection.Height + "px. Aspect Ratio: " + str;
}
}
public static int GetGreatestCommonDivisor(int height, int width)
{
return width == 0 ? height : GetGreatestCommonDivisor(width, height % width);
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left &&
_selecting &&
_selection.Size != new Size())
{
// Create cropped image:
_newImage = pictureBox1.Image.Crop(_selection);
_selecting = false;
try
{
// Set new image to the picturebox:
//pictureBox1.Image = _newImage.Fit2PictureBox(pictureBox1);
pictureBox1.Image = _newImage;
//toolStripStatusLabel1.Text = "Image Cropped.";
}
catch (Exception)
{ }
}
else
{
_selecting = false;
}
}
private void buttonResize_Click(object sender, EventArgs e)
{
pictureBox1.Image = ResizeImage(pictureBox1.Image, new Size(800, 600));
int nGCD = GetGreatestCommonDivisor(pictureBox1.Image.Width, pictureBox1.Image.Height);
string str = string.Format("{0}:{1}", pictureBox1.Image.Width / nGCD, pictureBox1.Image.Height / nGCD);
toolStripStatusLabel1.Text = "Image Resized to " + pictureBox1.Image.Width + "x" + pictureBox1.Image.Height + "px. Aspect Ratio: " + str;
}
public static Image ResizeImage(Image image, Size size, bool preserveAspectRatio = true)
{
int newWidth;
int newHeight;
if (preserveAspectRatio)
{
int originalWidth = image.Width;
int originalHeight = image.Height;
float percentWidth = size.Width / originalWidth;
float percentHeight = size.Height / originalHeight;
float percent = percentHeight < percentWidth ? percentHeight : percentWidth;
newWidth = (int)(originalWidth * percent);
newHeight = (int)(originalHeight * percent);
}
else
{
newWidth = size.Width;
newHeight = size.Height;
}
Image newImage = new Bitmap(newWidth, newHeight);
using (Graphics graphicsHandle = Graphics.FromImage(newImage))
{
graphicsHandle.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphicsHandle.DrawImage(image, 0, 0, newWidth, newHeight);
}
return newImage;
}
private void buttonSave_Click(object sender, EventArgs e)
{
string filename = path.Substring(path.LastIndexOf("\\") + 1);
string newPath = path.Substring(0, path.LastIndexOf(".") - 1) + "NEW.png";
toolStripStatusLabel1.Text = "Saving " + filename + " to " + newPath;
pictureBox1.Image.Save(newPath, ImageFormat.Png);
toolStripStatusLabel1.Text = filename + " saved to " + newPath;
}
}
}
Program.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace CropResize
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1(args[0]));
}
public static Image SetImageWithinResolution(this Image image, PictureBox pictureBox)
{
//Bitmap bitmap = null;
if (image.Height > Screen.PrimaryScreen.Bounds.Height)
{
//ActiveForm.Size = new Size(100, 100);
Form.ActiveForm.Size = new Size(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
image = image.Fit2PictureBox(pictureBox);
//Bitmap bitmap = new Bitmap(image, );
}
if (image.Width > Screen.PrimaryScreen.Bounds.Width)
{
//ActiveForm.Size = new Size(100, 100);
Form.ActiveForm.Size = new Size(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
image = image.Fit2PictureBox(pictureBox);
//Bitmap bitmap = new Bitmap();
}
return image;
}
public static Image Crop(this Image image, Rectangle selection)
{
Bitmap bmp = image as Bitmap;
try
{
// Check if it is a bitmap:
if (bmp == null)
throw new ArgumentException("No valid bitmap");
// Crop the image:
Bitmap cropBmp = bmp.Clone(selection, bmp.PixelFormat);
// Release the resources:
image.Dispose();
return cropBmp;
}
catch (Exception)
{
return bmp;
}
}
public static Image Fit2PictureBox(this Image image, PictureBox picBox)
{
Bitmap bmp = null;
Graphics g;
// Scale:
double scaleY = (double)image.Width / picBox.Width;
double scaleX = (double)image.Height / picBox.Height;
double scale = scaleY < scaleX ? scaleX : scaleY;
// Create new bitmap:
bmp = new Bitmap(
(int)((double)image.Width / scale),
(int)((double)image.Height / scale));
// Set resolution of the new image:
bmp.SetResolution(
image.HorizontalResolution,
image.VerticalResolution);
// Create graphics:
g = Graphics.FromImage(bmp);
// Set interpolation mode:
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
// Draw the new image:
g.DrawImage(
image,
new Rectangle( // Destination
0, 0,
bmp.Width, bmp.Height),
new Rectangle( // Source
0, 0,
image.Width, image.Height),
GraphicsUnit.Pixel);
// Release the resources of the graphics:
g.Dispose();
// Release the resources of the origin image:
image.Dispose();
return bmp;
}
}
Maybe this example will help; is shows how to restrict a drawn rectangle to a given ratio:
float ratio = 0.33f;
Rectangle setRect()
{
int x = Math.Min(mDown.X, currPt.X);
int y = Math.Min(mDown.Y, currPt.Y);
int w = Math.Max(mDown.X, currPt.X) - x;
int h = Math.Max(mDown.Y, currPt.Y) - y;
if (ratio > 1) h = (int)(1f * w / ratio);
else w = (int)(1f * h * ratio);
return new Rectangle(x, y, w, h);
}
It uses two Points, one set in the MouseDown and one updated in the MouseMove.
It is up to you to integrate it with you programm; instead of painting all those pixels during MouseMove I would simply draw a rubberband rectangle on the surface of the control using the Paint event..
If you are scaling things you may want to switch to using all floats.
I have a method that takes in a bitmap object and overlays dates and times strings over it and returns that new bitmap. The code is below.
public static Bitmap overlayBitmap(Bitmap sourceBMP, int width, int height, List<String> times, List<String> dates, IEnumerable<Color> colors) {
// Determine the new width
float newWidth = width + (width / 3.0f);
float newHeight = height + (height / 3.0f);
// Intelligent vertical + horizontal text distance calculator
float verticalDistance = height / (times.Count - 1.0f);
float horizontalDistance = width / (dates.Count - 1.0f);
Bitmap result = new Bitmap((int)newWidth, (int)newHeight);
using (Graphics g = Graphics.FromImage(result)) {
// Background color
Brush brush = new SolidBrush(colors.First());
g.FillRectangle(brush, 0, 0, newWidth, newHeight);
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
// Times text configs
StringFormat stringFormatTimes = new StringFormat();
stringFormatTimes.LineAlignment = StringAlignment.Center;
stringFormatTimes.Alignment = StringAlignment.Center;
Font drawFontY = new Font("Whitney", newHeight / 70);
// Dates text configs
StringFormat stringFormatDates = new StringFormat();
stringFormatDates.LineAlignment = StringAlignment.Center;
stringFormatTimes.Alignment = StringAlignment.Center;
stringFormatDates.FormatFlags = StringFormatFlags.DirectionVertical;
Font drawFontX = new Font("Whitney", newHeight / 70);
// Location of times text
for (int i = 0; i < times.Count; i++) {
if (i % determineIncrementTimes(times.Count) == 0) {
g.DrawString(times[i], drawFontX, Brushes.White, (((newWidth - width) / 2) / 2), ((newHeight - height) / 2) + (verticalDistance * i), stringFormatTimes);
}
}
// Location of dates text
for (int i = 0; i < dates.Count; i++) {
if (i % determineIncrementDates(dates.Count) == 0) {
g.DrawString(dates[i], drawFontY, Brushes.White, ((newWidth - width) / 2) + (horizontalDistance * i), ((newHeight - height) / 2) + height, stringFormatDates);
}
}
// New X and Y Position of the sourceBMP within the new BMP.
int XPos = width / 6;
int YPos = height / 6;
// Int -> Float casting for the outline
float fXPos = width / 6.0f;
float fYPos = height / 6.0f;
float fWidth = width / 1.0f;
float fHeight = height / 1.0f;
// Draw new image at the position width/6 and height/6 with the size at width and height
g.DrawImage(sourceBMP, fXPos, fYPos, fWidth, fHeight);
g.DrawRectangle(Pens.White, fXPos, fYPos, fWidth, fHeight); // white outline
g.Dispose();
}
return result;
}
My concern is, I would like to be able, for the next developer, to easily access and set particular values that currently I've only "hardcoded" in. An example being the x-position of the time text calculated via this snippet of code:
(((newWidth - width) / 2) / 2)
Realistically I'd like to have the developer be able to access and/or set this value through simply typing in:
something.XPos = [someFloat];
How my method above is used (is pseudo-code) is as the following:
private readonly Bitmap _image;
private readonly Bitmap _overlayedImage;
public myConstructor(int someInputValues){
// some code that generates the first bitmap called _image
_newImage = overlayImage(_image, ....);
}
For reference this is the image drawn:
My question is - since some values need to be casted and initialized first, can I set my instance variables at the end of the method, before the closing brace?
public Bitmap overlayBitmap
{
get
{
// Build bitmap overlay
return overlayBitmapOutput;
}
...
}
[Edit: Answer Insufficient >> Wait]
I want to print one tall (long) image in many pages. So in every page, I take a suitable part from the image and I draw it in the page.
the problem is that I have got the image shrunk (its shape is compressed) in the page,so I added an scale that its value is 1500 .
I think that I can solve the problem if I knew the height of the page (e.Graphics) in pixels.
to convert Inches to Pixel, Do I have to multiply by (e.Graphics.DpiX = 600) or multiply by 96 .
void printdocument_PrintPage(object sender, PrintPageEventArgs e)
{
if (pageImage == null)
return;
e.Graphics.PageUnit = GraphicsUnit.Pixel;
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
float a = (e.MarginBounds.Width / 100) * e.Graphics.DpiX;
float b = ((e.MarginBounds.Height / 100) * e.Graphics.DpiY);
int scale = 1500;
scale = 0; //try to comment this
RectangleF srcRect = new RectangleF(0, startY, pageImage.Width, b - scale);
RectangleF destRect = new RectangleF(0, 0, a, b);
e.Graphics.DrawImage(pageImage, destRect, srcRect, GraphicsUnit.Pixel);
startY = Convert.ToInt32(startY + b - scale);
e.HasMorePages = (startY < pageImage.Height);
}
could you please make it works correctly.
you can download the source code from (here).
thanks in advanced.
I tried to complete your task.
Here you go. Hope it helps.
This method prints the image on several pages (or one if image is small).
private void printImage_Btn_Click(object sender, EventArgs e)
{
list = new List<Image>();
Graphics g = Graphics.FromImage(image_PctrBx.Image);
Brush redBrush = new SolidBrush(Color.Red);
Pen pen = new Pen(redBrush, 3);
decimal xdivider = image_PctrBx.Image.Width / 595m;
int xdiv = Convert.ToInt32(Math.Ceiling(xdivider));
decimal ydivider = image_PctrBx.Image.Height / 841m;
int ydiv = Convert.ToInt32(Math.Ceiling(ydivider));
/*int xdiv = image_PctrBx.Image.Width / 595; //This is the xsize in pt (A4)
int ydiv = image_PctrBx.Image.Height / 841; //This is the ysize in pt (A4)
// # 72 dots-per-inch - taken from Adobe Illustrator
if (xdiv >= 1 && ydiv >= 1)
{*/
for (int i = 0; i < xdiv; i++)
{
for (int y = 0; y < ydiv; y++)
{
Rectangle r;
try
{
r = new Rectangle(i * Convert.ToInt32(image_PctrBx.Image.Width / xdiv),
y * Convert.ToInt32(image_PctrBx.Image.Height / ydiv),
image_PctrBx.Image.Width / xdiv,
image_PctrBx.Image.Height / ydiv);
}
catch (Exception)
{
r = new Rectangle(i * Convert.ToInt32(image_PctrBx.Image.Width / xdiv),
y * Convert.ToInt32(image_PctrBx.Image.Height),
image_PctrBx.Image.Width / xdiv,
image_PctrBx.Image.Height);
}
g.DrawRectangle(pen, r);
list.Add(cropImage(image_PctrBx.Image, r));
}
}
g.Dispose();
image_PctrBx.Invalidate();
image_PctrBx.Image = list[0];
PrintDocument printDocument = new PrintDocument();
printDocument.PrintPage += PrintDocument_PrintPage;
PrintPreviewDialog previewDialog = new PrintPreviewDialog();
previewDialog.Document = printDocument;
pageIndex = 0;
previewDialog.ShowDialog();
// don't forget to detach the event handler when you are done
printDocument.PrintPage -= PrintDocument_PrintPage;
}
This method prints every picture in the List in the needed dimensions (A4 size):
private void PrintDocument_PrintPage(object sender, PrintPageEventArgs e)
{
// Draw the image for the current page index
e.Graphics.DrawImageUnscaled(list[pageIndex],
e.PageBounds.X,
e.PageBounds.Y);
// increment page index
pageIndex++;
// indicate whether there are more pages or not
e.HasMorePages = (pageIndex < list.Count);
}
This method crops the image and returns every part of the image:
private static Image cropImage(Image img, Rectangle cropArea)
{
Bitmap bmpImage = new Bitmap(img);
Bitmap bmpCrop = bmpImage.Clone(cropArea, System.Drawing.Imaging.PixelFormat.DontCare);
return (Image)(bmpCrop);
}
The Image gets loaded from the PictureBox, so make sure the image is loaded. (No exception handling yet).
internal string tempPath { get; set; }
private int pageIndex = 0;
internal List<Image> list { get; set; }
These variables are defined as global variables.
You can download a sample project here:
http://www.abouchleih.de/projects/PrintImage_multiplePages.zip // OLD Version
http://www.abouchleih.de/projects/PrintImage_multiplePages_v2.zip // NEW
I have Created a Class file for multiple page print a single large image.
Cls_PanelPrinting.Print Print =new Cls_PanelPrinting.Print(PnlContent/Image);
You have to Pass the panel or image.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Drawing.Printing;
namespace Cls_PanelPrinting
{
public class Print
{
readonly PrintDocument printdoc1 = new PrintDocument();
readonly PrintPreviewDialog previewdlg = new PrintPreviewDialog();
public int page = 1;
internal string tempPath { get; set; }
private int pageIndex = 0;
internal List<Image> list { get; set; }
private int _Line = 0;
private readonly Panel panel_;
public Print(Panel pnl)
{
panel_ = pnl;
printdoc1.PrintPage += (printdoc1_PrintPage);
PrintDoc();
}
private void printdoc1_PrintPage(object sender, PrintPageEventArgs e)
{
Font myFont = new Font("Cambria", 10, FontStyle.Italic, GraphicsUnit.Point);
float lineHeight = myFont.GetHeight(e.Graphics) + 4;
float yLineTop = 1000;
int x = e.MarginBounds.Left;
int y = 25; //e.MarginBounds.Top;
e.Graphics.DrawImageUnscaled(list[pageIndex],
x,y);
pageIndex++;
e.HasMorePages = (pageIndex < list.Count);
e.Graphics.DrawString("Page No: " + pageIndex, myFont, Brushes.Black,
new PointF(e.MarginBounds.Right, yLineTop));
}
public void PrintDoc()
{
try
{
Panel grd = panel_;
Bitmap bmp = new Bitmap(grd.Width, grd.Height, grd.CreateGraphics());
grd.DrawToBitmap(bmp, new Rectangle(0, 0, grd.Width, grd.Height));
Image objImage = (Image)bmp;
Bitmap objBitmap = new Bitmap(objImage, new Size(700, objImage.Height));
Image PrintImage = (Image)objBitmap;
list = new List<Image>();
Graphics g = Graphics.FromImage(PrintImage);
Brush redBrush = new SolidBrush(Color.Red);
Pen pen = new Pen(redBrush, 3);
decimal xdivider = panel_.Width / 595m;
// int xdiv = Convert.ToInt32(Math.Ceiling(xdivider));
decimal ydivider = panel_.Height / 900m;
int ydiv = Convert.ToInt32(Math.Ceiling(ydivider));
int xdiv = panel_.Width / 595; //This is the xsize in pt (A4)
for (int i = 0; i < xdiv; i++)
{
for (int y = 0; y < ydiv; y++)
{
Rectangle r;
if (panel_.Height > 900)
{
try
{
if (list.Count > 0)
{
r = new Rectangle(0, (900 * list.Count), PrintImage.Width, PrintImage.Height - (900 * list.Count));
}
else
{
r = new Rectangle(0, 0, PrintImage.Width, 900);
}
list.Add(cropImage(PrintImage, r));
}
catch (Exception)
{
list.Add(PrintImage);
}
}
else { list.Add(PrintImage); }
}
}
g.Dispose();
PrintImage = list[0];
PrintDocument printDocument = new PrintDocument();
printDocument.PrintPage += printdoc1_PrintPage;
PrintPreviewDialog previewDialog = new PrintPreviewDialog();
previewDialog.Document = printDocument;
pageIndex = 0;
printDocument.DefaultPageSettings.PrinterSettings.PrintToFile = true;
string path = "d:\\BillIng.xps";
if (File.Exists(path))
File.Delete(path);
printDocument.DefaultPageSettings.PrinterSettings.PrintFileName = "d:\\BillIng.xps";
printDocument.PrintController = new StandardPrintController();
printDocument.Print();
printDocument.PrintPage -= printdoc1_PrintPage;
}
catch { }
}
private static Image cropImage(Image img, Rectangle cropArea)
{
Bitmap bmpImage = new Bitmap(img);
Bitmap bmpCrop = bmpImage.Clone(cropArea, System.Drawing.Imaging.PixelFormat.DontCare);
return (Image)(bmpCrop);
}
}
}
I have a label that shows the file name .. I had to set AutoSize of the label to False for designing.
So when the file name text got longer than label size.. it got cut like in the picture.
label1.Size = new Size(200, 32);
label1.AutoSize = false;
How do I re-size the text automatically to fit the label size, when the text is longer than the label size?
You can use my code snippet below. System needs some loops to calculate the label's font based on text size.
while(label1.Width < System.Windows.Forms.TextRenderer.MeasureText(label1.Text,
new Font(label1.Font.FontFamily, label1.Font.Size, label1.Font.Style)).Width)
{
label1.Font = new Font(label1.Font.FontFamily, label1.Font.Size - 0.5f, label1.Font.Style);
}
Label scaling
private void scaleFont(Label lab)
{
Image fakeImage = new Bitmap(1, 1); //As we cannot use CreateGraphics() in a class library, so the fake image is used to load the Graphics.
Graphics graphics = Graphics.FromImage(fakeImage);
SizeF extent = graphics.MeasureString(lab.Text, lab.Font);
float hRatio = lab.Height / extent.Height;
float wRatio = lab.Width / extent.Width;
float ratio = (hRatio < wRatio) ? hRatio : wRatio;
float newSize = lab.Font.Size * ratio;
lab.Font = new Font(lab.Font.FontFamily, newSize, lab.Font.Style);
}
TextRenderer Approach pointed out by #ToolmakerSteve in the comments
private void ScaleFont(Label lab)
{
SizeF extent = TextRenderer.MeasureText(lab.Text, lab.Font);
float hRatio = lab.Height / extent.Height;
float wRatio = lab.Width / extent.Width;
float ratio = (hRatio < wRatio) ? hRatio : wRatio;
float newSize = lab.Font.Size * ratio;
lab.Font = new Font(lab.Font.FontFamily, newSize, lab.Font.Style);
}
Based on the article provided by #brgerner, I'll provide the alternative implementation here, as that one marked as an answer is not so efficient nor complete as this one below:
public class FontWizard
{
public static Font FlexFont(Graphics g, float minFontSize, float maxFontSize, Size layoutSize, string s, Font f, out SizeF extent)
{
if (maxFontSize == minFontSize)
f = new Font(f.FontFamily, minFontSize, f.Style);
extent = g.MeasureString(s, f);
if (maxFontSize <= minFontSize)
return f;
float hRatio = layoutSize.Height / extent.Height;
float wRatio = layoutSize.Width / extent.Width;
float ratio = (hRatio < wRatio) ? hRatio : wRatio;
float newSize = f.Size * ratio;
if (newSize < minFontSize)
newSize = minFontSize;
else if (newSize > maxFontSize)
newSize = maxFontSize;
f = new Font(f.FontFamily, newSize, f.Style);
extent = g.MeasureString(s, f);
return f;
}
public static void OnPaint(object sender, PaintEventArgs e, string text)
{
var control = sender as Control;
if (control == null)
return;
control.Text = string.Empty; //delete old stuff
var rectangle = control.ClientRectangle;
using (Font f = new System.Drawing.Font("Microsoft Sans Serif", 20.25f, FontStyle.Bold))
{
SizeF size;
using (Font f2 = FontWizard.FlexFont(e.Graphics, 5, 50, rectangle.Size, text, f, out size))
{
PointF p = new PointF((rectangle.Width - size.Width) / 2, (rectangle.Height - size.Height) / 2);
e.Graphics.DrawString(text, f2, Brushes.Black, p);
}
}
}
}
and the usage:
val label = new Label();
label.Paint += (sender, e) => FontWizard.OnPaint(sender, e, text);
I use the following weighted scaling trick to provide a good fit, i.e. a weighted tradeoff is made between fitting the height and fitting the width. It's in VB .net, but I think you can translate to C# easily.
Function shrinkFontToFit(f As Font, text As String, requiredsize As SizeF) As Font
Dim actualsize As SizeF = TextRenderer.MeasureText(text, f)
Return New Font(f.FontFamily, f.Size * (requiredsize.Width + requiredsize.Height ) _
/ (actualsize.Width + actualsize.Height), f.Style, GraphicsUnit.Pixel)
End Function
With inspiration from #bnguyen82 i came up with something that works all the way.
public static void ScaleLabel(Label label, float stepSize = 0.5f)
{
//decrease font size if text is wider or higher than label
while (lblTextSize() is Size s && s.Width > label.Width || s.Height > label.Height)
{
label.Font = new Font(label.Font.FontFamily, label.Font.Size - stepSize, label.Font.Style);
}
//increase font size if label width is bigger than text size
while (label.Width > lblTextSize().Width)
{
var font = new Font(label.Font.FontFamily, label.Font.Size + stepSize, label.Font.Style);
var nextSize = TextRenderer.MeasureText(label.Text, font);
//dont make text width or hight bigger than label
if (nextSize.Width > label.Width || nextSize.Height > label.Height)
break;
label.Font = font;
}
Size lblTextSize() => TextRenderer.MeasureText(label.Text,
new Font(label.Font.FontFamily, label.Font.Size, label.Font.Style));
}
PS: In order for this to work the label needs to have AutoSize = false and either to be docked or anchored.
this method worked for me
simply you will reduce font size until it reach the width you want.
while (label1.Width >150 )
{
label1.Font = new Font(label1.Font.FontFamily, label1.Font.Size - 0.5f, label1.Font.Style);
}
First you need an event whenever text changes:
lbl.TextChanged += new EventHandler(Label_TextChanged);
Then you change the font inside the event to fit :
private void Label_TextChanged(object sender, EventArgs e)
{
Label lbl = (Label)sender;
if (lbl.Image != null) return;
using (Graphics cg = lbl.CreateGraphics())
{
SizeF lblsize = new SizeF(lbl.Width, lbl.Height);
SizeF textsize = cg.MeasureString(lbl.Text, lbl.Font, lblsize);
while (textsize.Width > lblsize.Width - (lblsize.Width * 0.1))
{
lbl.Font = new Font(lbl.Font.Name, lbl.Font.Size - 1, lbl.Font.Style);
textsize = cg.MeasureString(lbl.Text, lbl.Font, lblsize);
if (lbl.Font.Size < 6) break;
}
}
}
This way you have smaller fonts to fit.
I skip when image is involved.
I also stop to size 5.
I shrink at intervals of -1. -0.5 will also work.
I consider a 10% of control area as unusable 'border' - works well.
This technique will work but do not expect results on large texts into small labels.
If you do not have images you can use control instead of label and apply to buttons and textboxes as well.
I think the easiest way could be to check the render size and if it is greater than the actual label size, decrease the fontsize of the label.
private void label3_Paint(object sender, PaintEventArgs e)
{
Size sz = TextRenderer.MeasureText(label1.Text, label1.Font, label1.Size, TextFormatFlags.WordBreak);
if (sz.Width > label1.Size.Width || sz.Height > label1.Size.Height)
{
DecreaseFontSize(label1);
}
}
public void DecreaseFontSize(Label lbl)
{
lbl.Font = new System.Drawing.Font(lbl.Font.Name, lbl.Font.Size - 1, lbl.Font.Style);
}