How can I convert user input from millimeter to pixels so that it's printed on the right position of the page?
I use the following code:
private void document_PrintPage(object sender, PrintPageEventArgs e)
{
float dpiX = e.Graphics.DpiX;
float dpiY = e.Graphics.DpiY;
Point p = new Point(mmToPixel(float.Parse(edtBorderLeft.Text), dpiX),
mmToPixel(float.Parse(edtBorderTop.Text), dpiY));
e.Graphics.DrawImage(testImage, p);
}
private int mmToPixel(float mm, float dpi)
{
return (int)Math.Round((mm / 25.4) * dpi);
}
edtBorderLeft.Text got the value of "9.5" and edtBorderTop.Text the value of "21,5". These values are millimeters. If I check the output with this code:
private void printPage()
{
PrintDialog dialog = new PrintDialog();
dialog.Document = document;
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
PrintPreviewDialog preview = new PrintPreviewDialog();
preview.Document = document;
preview.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
preview.Show();
//document.Print();
}
}
It displays the image nearly in the center of the page. A calculation example:
mmToPixel(float.Parse(edtBorderLeft.Text), dpiX)
edtBorderLeft.Text = "9.5"
dpiX = 600;
returns: 224
How can I calculate the right point for the printed image?
I found a solution. You can change the page unit with the following code. So I don't need a conversion:
e.Graphics.PageUnit = GraphicsUnit.Millimeter;
or
e.Graphics.PageUnit = GraphicsUnit.Pixel;
and I can use the code above.
Just to add a little explanation. By default Graphics.PageUhit is set to "Display". For a screen display this usually means 96 pixels per inch, for a printer it is 100 dots per inch. This info is buried in MSDN somehwere but is hard to find.
Therefore for a printer, instead of using dpiX/dpiY you could assume a value of 100, but it is probably safer to set the units to millimeters.
Related
What is the unit of the coordinate system used in Windows Forms printing using the PrintDocument class? This information is needed in order to print something at a specific position and with a specific size.
In the PrintPage event, the PrintPageEventArgs instance has the properties Graphics and PageBounds. They seem to use the same coordinate system.
For an A4 portrait sheet, PageBounds returns a size of 827 by 1169. Given an A4 sheet is 210mm by 297mm, the unit Graphics / PageBounds unit seems to be pixels/points with 100dpi. (827 / 210 * 25.4 = 100.0278, 1169 / 297 * 25.4 = 99.9751).
Using 100dpi to scale and position the objects, the drawing result is correct. But is it always 100dpi? Or how can I query the unit?
(Querying Graphics.DpiX does not work. It returns 600dpi, which is the printer DPI but not the coordinate system DPI.)
private void PrintButton_Click(object sender, EventArgs e)
{
PrintDocument pd = new PrintDocument();
pd.PrintPage += new PrintPageEventHandler(PrintDocument_PrintPage);
pd.Print();
}
private void PrintDocument_PrintPage(object sender, PrintPageEventArgs e)
{
Rectangle bounds = e.PageBounds; // For A4 portrait sheet: {X = 0 Y = 0 Width = 827 Height = 1169}
float dpi = e.Graphics.DpiX; // 600
DrawIt(e.Graphics);
}
Thanks to Jimi who pointed out that the unit is Display. The short answer is: It's always 100dpi for printing.
The Graphics instance uses GraphicsUnit.Display as the PageUnit. And for printers, this is 1/100 inch for printers, i.e. 100dpi. The documentation says "typically" but this probably refers to the video displays.
It also coincides with PrinterUnit.Display, which is always 0.01in.
As the Graphics measurements are also consistent with PageBounds, I can probably safely assume that PageBounds and other PrintPageEventArgs properties also use display units for printers with 100dpi. It's not documented though.
I have a PictureBox inside a Panel, and I have implemented the zoom function with a TrackBar.
When I increase (or decrease) the PictureBox, the position of the image remain fixed on the left side of the PictureBox.
See the example to better understand the problem.
What I want is the possibility to relocate the image compared to the center of the Panel. See the following example
For example I tried to define the PictureBox X origin in this way:
before the zoom I calculate the distance (Δdx) between the origin of the PictureBox (x0) and the center of the Panel (x1).
I increase the distance with the zoom factor (Δdx').
I calculate the new origin of the image (x0') as x1 - Δdx'
I do the same with Y and I define the new PictureBox location with x0' and y0'.
Here the code:
// new image width after the zoom
double width = pbImg.Image.Width + (pbImg.Image.Width * trackbar.Value / 100);
// new image height after the zoom
double height = pbImg.Image.Height + (pbImg.Image.Height * trackbar.Value / 100);
// panel center
int cX = panel.Width / 2;
int cY = panel.Height / 2;
// actual origin for the picturebox
int imgX = pbImg.Location.X;
int imgY = pbImg.Location.Y;
// distance the panel center and the picturebox origin
int distFromXc = cX - imgX;
int distFromYc = cY - imgY;
// new distance with zoom factor
distFromXc = distFromXc + (distFromXc * trackbar.Value / 100);
distFromYc = distFromYc + (distFromYc * trackbar.Value / 100);
// new origin point for the picturebox
int pbX = (cX - distFromXc);
int pbY = (cY - distFromYc);
// new dimension for the picturebox
pbImg.Size = new Size(Convert.ToInt32(width), Convert.ToInt32(height));
// relocate picturebox
Point p = new Point(pbX, pbY);
pbImg.Location = p;
I tried to modify this C# code, but I’m not familiar with it.
In my case I want to manage the Picturebox and the image inside it as the same object (if it’s possible).
What I want is the possibility to increase (or decrease) the Picturebox (and the image inside) but I want the Picturebox to stay centered where it currently is.
The SizeMode of the picture is StretchImage.
The Trackbar has 0% as he minimum value and 100% as the maximum.
The size of the Picturebox, and the image isnside, can be variable, I receive the images from another software.
A zoomed Picturebox can be bigger than Panel, but it’s not a problem, because I can move it.
The problems are the following:
1. If i use the code I wrote above, the reposition seems to work, but the Picturebox isn’t resized.
2. If I use a fixed value for the origin of the Picturebox (for example Point p = new Point(50, 50)), the resize works but obviously the position of the Picturebox is fixed.
This is because you are changing the size of the picturebox and not the size of the image within it. To ensure an image matches the size of the picture box ensure you set the stretchimage sizemode
pbImg.SizeMode = PictureBoxSizeMode.StretchImage
to get it working you could add this line just before you change the size of the picture box, however i recommend setting this on picturebox during its creation.
Refer to : Fit Image into PictureBox
If you want the PictureBox to stay centered where it currently is, and only expand or deflate "in place", then try something like this:
double width = pbImg.Width * trackbar.Value / 100;
double height = pbImg.Height * trackbar.Value / 100;
Rectangle rc = pbImg.Bounds;
rc.Inflate((int)((width - pbImg.Width) / 2), (int)((height - pbImg.Height) / 2));
pbImg.Bounds = rc;
Note that this is all based on the size of the PictureBox itself, NOT the Image within. Not sure what SizeMode you have set for the PB...
---------- EDIT ----------
I'm using StretchImage as SizeMode. Is possible to have the same behavior but without the button? When I move the cursor from left to right the Pb increase and decrease from right to left – Scarj
Of course. Put my code into the ValueChanged() and/or Scroll() events. - Idle_Mind
The original post works off the current size of the PictureBox. You might want to store the original Bounds() of the PB (maybe in the Tag() property) and then always compute the new size based on that instead.
Here's an example of that:
private void Form1_Load(object sender, EventArgs e)
{
pbImg.Tag = pbImg.Bounds;
}
private void button1_Click(object sender, EventArgs e)
{
}
private void trackBar1_Scroll(object sender, EventArgs e)
{
ZoomPB();
}
private void trackBar1_ValueChanged(object sender, EventArgs e)
{
ZoomPB();
}
private void ZoomPB()
{
Rectangle rc = (Rectangle)pbImg.Tag;
double width = rc.Width * trackbar.Value / 100;
double height = rc.Height * trackbar.Value / 100;
rc.Inflate((int)((width - rc.Width) / 2), (int)((height - rc.Height) / 2));
pbImg.Bounds = rc;
}
I've searched a bit around the discussions\forums/StackOverflow/Official documentation, but i couldn't find much information about how to achieve what i'm trying. Most of the official documentation covers the command-line version of ImageMagick.
I'll describe what i'm trying to do:
I have a image loaded that i would like to paste into a larger one.
Ex: the image i loaded has 9920 width, 7085 height. I would like to place it in the middle of a larger one (10594 width, 7387 height). I do have all border calculation ready ([larger width - original width / 2] , same goes for height).
But i don't know how to do it using MagickImage. Here's the max i got:
private void drawInkzone(MagickImage loadedImage, List<string>inkzoneAreaInformation, string filePath)
{
unitConversion converter = new unitConversion();
List<double> inkZoneInfo = inkZoneListFill(inkzoneAreaInformation);
float DPI = getImageDPI(filePath);
double zoneAreaWidth_Pixels = converter.mmToPixel(inkZoneInfo.ElementAt(4), DPI);
double zoneAreaHeight_Pixels = converter.mmToPixel(inkZoneInfo.ElementAt(5), DPI);
using (MagickImage image = new MagickImage(MagickColor.FromRgb(255, 255, 255), Convert.ToInt32(zoneAreaWidth_Pixels), Convert.ToInt32(zoneAreaHeight_Pixels)))
{
//first: defining the larger image, with a white background (must be transparent, but for now its okay)
using (MagickImage original = loadedImage.Clone())
{
//Cloned the original image (already passed as parameter)
}
}
Here's the max i got. In order to achieve this, i used the following post:
How to process only one part of image by ImageMagick?
And i'm not using GDI+ because i'll be always working with larger TIFF files (big resolutions), and GDI+ tends to throw exceptions (Parameter not valid, out of memory) when it can't handle everything (i loaded three images with an resolution like that, and got out of memory).
Any help will be kindly appreciate, thanks.
Pablo.
You could either Composite the image on top of a new image with the required background or you could Clone and Extent if with the required background. In the answer from #Pablo Costa there is an example for Compositing the image so here is an example on how you could extent the image:
private void drawInkzone(MagickImage loadedImage, List<string> inkzoneAreaInformation, string filePath)
{
unitConversion converter = new unitConversion();
List<double> inkZoneInfo = inkZoneListFill(inkzoneAreaInformation);
float DPI = getImageDPI(filePath);
double zoneAreaWidth_Pixels = converter.mmToPixel(inkZoneInfo.ElementAt(4), DPI);
double zoneAreaHeight_Pixels = converter.mmToPixel(inkZoneInfo.ElementAt(5), DPI);
using (MagickImage image = loadedImage.Clone())
{
MagickColor background = MagickColors.Black;
int width = (int)zoneAreaWidth_Pixels;
int height = (int)zoneAreaHeight_Pixels;
image.Extent(width, height, Gravity.Center, background);
image.Write(#"C:\DI_PLOT\whatever.png");
}
}
I managed to accomplish what i needed.
Cool that i didn't had to calculate borders.
Here's the code:
private void drawInkzone(MagickImage loadedImage, List<string>inkzoneAreaInformation, string filePath)
{
unitConversion converter = new unitConversion();
List<double> inkZoneInfo = inkZoneListFill(inkzoneAreaInformation); //Larger image information
float DPI = getImageDPI(filePath);
double zoneAreaWidth_Pixels = converter.mmToPixel(inkZoneInfo.ElementAt(4), DPI); //Width and height for the larger image are in mm , converted them to pixel
double zoneAreaHeight_Pixels = converter.mmToPixel(inkZoneInfo.ElementAt(5), DPI);//Formula (is: mm * imageDPI) / 25.4
using (MagickImage image = new MagickImage(MagickColor.FromRgb(0, 0, 0), Convert.ToInt32(zoneAreaWidth_Pixels), Convert.ToInt32(zoneAreaHeight_Pixels)))
{
//first: defining the larger image, with a white background (must be transparent, but for now its okay)
using (MagickImage original = loadedImage.Clone())
{
//Cloned the original image (already passed as parameter)
image.Composite(loadedImage, Gravity.Center);
image.Write(#"C:\DI_PLOT\whatever.png");
}
}
Hope this helps someone :)
I searched for a lot in google and did not find what i actually wanted. I got following code, which will print the variable name. I got a Epson Dot Matrix Printer and Roll Paper (Endless continuous paper).
My problem is that, after printing name paper feeds up to size of A4. I don`t want the paper feed. This application is intended to do print receipts which will have unlimited data which need to be printed flawless ( with out page break).
Can you, the smart folk out there to point me in the correct direction with these codes?
edited this code and changed scenario .. please move down further
private void pd_PrintPage(object sender, PrintPageEventArgs e)
{
Font Heading2 = new Font("Times New Roman", 13);
StringFormat sf = new StringFormat();
sf.LineAlignment = StringAlignment.Near;
sf.Alignment = StringAlignment.Center;
//e.HasMorePages = false;
PaperSize pkCustomSize1 = new PaperSize("First custom size", 100, 200);
pd.DefaultPageSettings.PaperSize = pkCustomSize1;
e.Graphics.DrawString(name.ToString(), Heading1, Brushes.Black, e.MarginBounds.Left + (e.MarginBounds.Width / 2), e.MarginBounds.Top, sf);
}
Edit 1:- #Adriano Repetti suggested this is a duplicate with Form feed in c# printing. What i learned from the above question is that he want to add the form feed. But i want to remove the form feed.
Edit 2:- I got an another hint by googling that setting the page Height equal to line height will make stop feeding which sounds promising. I am trying to figure that out too.
Edit 3:- #Adriano Repetti suggested me with Raw Printing (Directly printing binary data) with KB link. I googled around about it, and found out its c# better equivalent paste bin or pastie.org (provided because it is a handy one) . At first it sounded good and it worked nicely stopping form feeding. But eventually i struck some ice berg.
On my code i had to align some printing quotes to center or align to left`. for which i got only option of using space and tabs. But there will be no guaranty that it will be well formatted as we can not certain about built in fonts with printer.( Refer:SO Question by #syncis )
And secondly, i will have to move my application to unicode(local language support) capable one, at least with in a month or so. In that scenario, raw printing wont help and i will have to go through theses faces again. SO, For avoiding that its better for me to stay with Graphics DrawString. And for this i changed my code as.
//---------
// start button click
//---------
PrintDocument pdoc = new PrintDocument();
pdoc.DefaultPageSettings.PaperSize.Height = 300;
pdoc.Print();
//---------
// end button click
//---------
private void pd_PrintPage(object sender, PrintPageEventArgs e)
{
Font Heading2 = new Font("Times New Roman", 13);
// changed following statement to met with new **unicode** criteria
//e.Graphics.DrawString(name.ToString(), Heading1, Brushes.Black, e.MarginBounds.Left + (e.MarginBounds.Width / 2), e.MarginBounds.Top, sf);
TextRenderer.DrawText(e.Graphics, "My name in local language is വിനീത്", Heading2, new Point(0, 0), Color.Black);
}
With current problems, i am redefining the Question extending tag to unicode as.
How can print with TextRenderer.DrawText with unicode support without form feeding ? I think setting paper height to line height will solve my problem. If so how or suggest me a better way to stop paper feeding. It really eats a lot of my valuable time...
EDIT 4: Today I found out a very interesting thing about my printer. I cant even set custom paper size manually (Not by coding.. I mean control panel->printers and faxes ->Epson LX-300+ ->properties -> printing preference-> paper/quality -> advanced -> paper size -> BOOOOOM not showing my custom paper size). I am using Epson LX-300+ printer. Do guys think it wont support custom paper sizes? is that causing me problems?
I found the solution by my self ( sorry for my english ) As Hans Passant says ( PrintDocument is page based. end of story ) You must use ( e.HasMorePages = true; )
float cordenadaX;
float cordenadaY;
int totalPages;
int paginaAtual;
int indiceItem;
List<string> items;
public void ImprimeDanfeNFCe()
{
totalPages = 1;
paginaAtual = 1;
indiceItem = 0;
cordenadaX = 0;
cordenadaY = 0;
items = new List<string>();
items.Add("Item1");
items.Add("Item2");
items.Add("Item3");
(............)
PrintDocument recordDoc = new PrintDocument();
recordDoc.DocumentName = "xMarket danfe";
recordDoc.PrintPage += new PrintPageEventHandler(imprimeDanfeReceipt);
PrinterSettings ps = new PrinterSettings();
ps.PrinterName = "My printer";
recordDoc.PrinterSettings = ps;
recordDoc.Print();
recordDoc.Dispose();
}
void imprimeDanfeReceipt(PrintPageEventArgs e)
{
float pageHeight = e.MarginBounds.Height;
string text = "";
if (paginaAtual == 1)
{
text = "Cupom header";
e.Graphics.DrawString(text, drawFontDanfeTitulo, drawBrush, new
RectangleF(cordenadaX, cordenadaY, width, height),
drawFormatCenter);
cordenadaY += e.Graphics.MeasureString(text, drawFontDanfeTitulo).Height;
}
for (int i = indiceItem; i < items.Count; i++)
{
int indice = i + 1;
//items[i] Is very important to not print same items again while print next page
e.Graphics.DrawString(items[i], drawFontDanfeItems, drawBrush,
new RectangleF(cordenadaX, cordenadaY, width, height), drawFormatLeft);
cordenadaY += e.Graphics.MeasureString(text, drawFontDanfeTitulo).Height;
indiceItem++;
//cordenadaY+100 is for the size of the footer
if (cordenadaY+100 >= pageHeight)
{
paginaAtual++;
e.HasMorePages = true;
return;
}
}
e.Graphics.DrawString("page footer", drawFontDanfeItems, drawBrush,
new RectangleF(cordenadaX, cordenadaY, width, height), drawFormatLeft);
cordenadaY += e.Graphics.MeasureString(text, drawFontDanfeTitulo).Height;
}
Using System.Drawing.Printing, I want to print a set of lines on print document.
But the problem is it prints every line on the very first page, what ever the coordinates are, also if I draw an image, It prints that on a single page no matter how big it is.
Following is what I have done for printing text on multiple pages:
protected void ThePrintDocument_PrintPage (object sender, System.Drawing.Printing.PrintPageEventArgs ev)
{
float linesPerPage = 0;
float yPosition = 0;
int count = 0;
float leftMargin = ev.MarginBounds.Left;
float topMargin = ev.MarginBounds.Top;
string line = null;
Font printFont = this.richTextBox1.Font;
SolidBrush myBrush = new SolidBrush(Color.Black);
// Work out the number of lines per page, using the MarginBounds.
linesPerPage = ev.MarginBounds.Height / printFont.GetHeight(ev.Graphics);
// Iterate over the string using the StringReader, printing each line.
while(count < linesPerPage && ((line=myReader.ReadLine()) != null))
{
// calculate the next line position based on
// the height of the font according to the printing device
yPosition = topMargin + (count * printFont.GetHeight(ev.Graphics));
// draw the next line in the rich edit control
ev.Graphics.DrawString(line, printFont, myBrush, leftMargin, yPosition, new StringFormat());
count++;
}
// If there are more lines, print another page.
if(line != null)
ev.HasMorePages = true;
else
ev.HasMorePages = false;
myBrush.Dispose();
}
What I should do in order to print line on multiple pages, i.e the following code should print on two pages as the length of the line is 1400 and print doc length is 1100, so remaining 300 of the line should be printed on the next page
protected void ThePrintDocument_PrintPage (object sender, System.Drawing.Printing.PrintPageEventArgs ev)
{
Pen P1 = new Pen(Brushes.Violet, 5);
ev.Graphics.DrawLine(P1, new Point(0,0), new Point(500,1400));
}
This is not how printing works in .NET. It does not create a new page just because you print outside the coordinates of the current page. There are events and event arguments which .NET uses to ask from you how many pages your document will contain. It will then invoke the event for printing a page for every page.
See here for an example.
EDIT
Ok, replying to your comment, I can think of two possible solutions: The first solution would involve clipping: intersect your graphic objects with the page's rectangle and print only what's common to both of them. If there were parts outside the clipping region, take that part as the new graphic objects and clip again to print on new page. Repeat this until the remaining graphics fit the page rectangle. However, I can't think of a way of doing that easily.
My second idea is as follows:
Calculate how many pages you would need to print all graphic objects by dividing the bounding rectangle of the graphic objects by the height of the "visual rectangle" of one page (increase number of pages by one if there's a remainder in this division).
Loop the following until reaching the last page
Print all graphic objects to the current page
Decrease all y-coordinates by the "visual height" of a page (effectively moving the graphic objects upwards outside the "bounds of the paper")
While the overhead of printing the entire list of graphic objects for every page may be high, you're leaving the clipping part to the printer driver/the printer itself.