How do I print clear text from a windows form? - c#

I have successfully printed a windows form, but all the text is slightly blurry. I have concluded that this is a result of the resolution of the screen being much less than the resolution the printer uses. Is there a fundamental flaw in my approach or is there a way to reformat the text prior to printing so that it comes out crisp?
void PrintImage(object o, PrintPageEventArgs e)
{
int x = SystemInformation.WorkingArea.X;
int y = SystemInformation.WorkingArea.Y;
int width = panel1.Width;
int height = panel1.Height;
Rectangle bounds = new Rectangle(x, y, width, height);
Bitmap img = new Bitmap(width, height);
this.DrawToBitmap(img, bounds);
Point p = new Point(100, 100);
e.Graphics.DrawImage(img, p);
}
private void BtnPrint_Click(object sender, EventArgs e)
{
btnPrint.Visible = false;
btnCancel.Visible = false;
if(txtNotes.Text == "Notes:" || txtNotes.Text == "")
{
txtNotes.Visible = false;
}
PrintDocument pd = new PrintDocument();
pd.PrintPage += new PrintPageEventHandler(PrintImage);
pd.Print();
}

Is there a fundamental flaw in my approach [...] ?
Yes.
You take the size of panel1 to calculate the size of the image. Later, you let this draw to the image, but this is the form, not the panel.
What makes you think that SystemInformation.WorkingArea is related to the window you want to print?
You should take a bit more care of disposable objects.
[...] is there a way to reformat the text prior to printing so that it comes out crisp?
There's not a general way which would allow you to scale all other controls as well.
However, instead of blurry text, you can get crisp pixelated text by scaling the bitmap up by a certain factor using the NearestNeighbor mechanism.
Here's the difference in a PDF generated without scaling (left) and a factor of 3 scaling (right) at the same zoom level in Acrobat Reader (click to enlarge):
Here's the scaling code, also without fixing any disposable issues:
this.DrawToBitmap(img, bounds);
Point p = new Point(100, 100);
img = ResizeBitmap(img, 3);
e.Graphics.DrawImage(img, p);
}
private static Bitmap ResizeBitmap(Bitmap source, int factor)
{
Bitmap result = new Bitmap(source.Width*factor, source.Height*factor);
result.SetResolution(source.HorizontalResolution*factor, source.VerticalResolution*factor);
using (Graphics g = Graphics.FromImage(result))
{
g.InterpolationMode = InterpolationMode.NearestNeighbor;
g.DrawImage(source, 0, 0, source.Width*factor, source.Height*factor);
}
return result;
}

Related

c# system.drawing how to print pdf to whole page size without blurr and losing quality

I made a windows service that's runs in background service and listening for web app to order to print.
the web app send a pdf file and width and height of the printer paper with count of copies
I wrote the code this way
convert pdf to image
public Image ConverPdfToImage(IFormFile pdfFile)
{
if (pdfFile == null || pdfFile.Length==0)
{
return null;
}
var documemt = new Spire.Pdf.PdfDocument();
documemt.LoadFromStream(pdfFile.OpenReadStream());
Image image = documemt.SaveAsImage(0,PdfImageType.Bitmap);
return image;
}
then the event handler
private void PrintPage(object sender, PrintPageEventArgs ev,Image img)
{
Rectangle pageBounds = ev.PageBounds;
int x = pageBounds.Left - (int) ev.PageSettings.HardMarginX;
int y = pageBounds.Top - (int) ev.PageSettings.HardMarginY;
int width = pageBounds.Width;
int height = pageBounds.Height;
ev.Graphics.CompositingQuality = CompositingQuality.HighQuality;
ev.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
ev.Graphics.SmoothingMode = SmoothingMode.HighQuality;
ev.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
local = new Rectangle(x, y, width, height);
ev.Graphics.FillRectangle(Brushes.Transparent, local);
ev.Graphics.DrawImage(img, local);
}
and the final function that prints
PrintDocument printDocument = new PrintDocument();
printDocument.PrinterSettings.PrinterName = form.Printer;
printDocument.DefaultPageSettings.Landscape = form.Landscape;
printDocument.DefaultPageSettings.PrinterSettings.Copies = form.Count;
printDocument.DefaultPageSettings.PaperSize =GetPaperSize(form.Width, form.Height);
printDocument.DefaultPageSettings.Margins = new Margins(0, 0, 0, 0);
Image img = ConverPdfToImage(form.PrintFile);
printDocument.PrintPage += (sender, args) => this.PrintPage(sender,args,img);
printDocument.Print();
the printDocument is from System.Drawing
It makes correct size in printer
printer are for lables and they have label papers with custom size
but the printed paper is blur and hard to read
some of it is so messed up and dirty
glad to help me make print sharp and good quality

Erase Part of Bitmap with another Bitmap

Let me preface this with a real life product; You may remember in Elementary school, they had scratch paper which basically consisted of a rainbow-colored sheet of paper with a black film on top. You would take a sharp object and peel away the black film to expose the colored paper.
I am attempting to do the same thing using images in a picture box.
My idea consists of these things:
A textured image.
A black rectangle the size of the picture box.
A circle image.
What I am trying to achieve is to open a program, have an image drawn to a picture box with the black rectangle on top of it. Upon clicking the picture box it uses the circle to invert the alpha of the rectangle where I click using the circle as a reference.
My Problem-
I cannot figure out any way to erase (set the transparency of) a part of the black rectangle where I click.
For the life of me, I do not know of any method to cut a window in an image. It is almost like a reverse crop, where I keep the exterior elements rather than the interior, exposing the textured image below.
Can WinForms not do this? Am I crazy? Should I just give up?
I should mention that I prefer not to have to change alpha on a pixel per pixel basis. It would slow the program down far too much to be used as a pseudo-painter. If that is the only way, however, feel free to show.
Here is an image of what I'm trying to achieve:
This is not really hard:
Set the colored image as a PictureBox's BackgroundImage.
Set a black image as its Image.
And draw into the image using the normal mouse events and a transparent Pen..
We need a point list to use DrawCurve:
List<Point> currentLine = new List<Point>();
We need to prepare and clear the the black layer:
private void ClearSheet()
{
if (pictureBox1.Image != null) pictureBox1.Image.Dispose();
Bitmap bmp = new Bitmap(pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height);
using (Graphics G = Graphics.FromImage(bmp)) G.Clear(Color.Black);
pictureBox1.Image = bmp;
currentLine.Clear();
}
private void cb_clear_Click(object sender, EventArgs e)
{
ClearSheet();
}
To draw into the Image we need to use an associated Graphics object..:
void drawIntoImage()
{
using (Graphics G = Graphics.FromImage(pictureBox1.Image))
{
// we want the tranparency to copy over the black pixels
G.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
G.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
G.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
using (Pen somePen = new Pen(Color.Transparent, penWidth))
{
somePen.MiterLimit = penWidth / 2;
somePen.EndCap = System.Drawing.Drawing2D.LineCap.Round;
somePen.LineJoin = System.Drawing.Drawing2D.LineJoin.Round;
somePen.StartCap = System.Drawing.Drawing2D.LineCap.Round;
if (currentLine.Count > 1)
G.DrawCurve(somePen, currentLine.ToArray());
}
}
// enforce the display:
pictureBox1.Image = pictureBox1.Image;
}
The usual mouse events:
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
currentLine.Add(e.Location);
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
currentLine.Add(e.Location);
drawIntoImage();
}
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
currentLine.Clear();
}
That's all that's needed. Make sure to keep the PB's SizeMode = Normal or else the pixels won't match..!
Note that there are a few challenges when you want to get soft edges, more painting tools, letting a simple click paint a dot or an undo or other finer details to work. But the basics are not hard at all..
Btw, changing Alpha is not any different from changing the color channels.
As an alternative you may want to play with a TextureBrush:
TextureBrush brush = new TextureBrush(pictureBox1.BackgroundImage);
using (Pen somePen = new Pen(brush) )
{
// basically
// the same drawing code..
}
But I found this to be rather slow.
Update:
Using a png-file as a custom tip is a little harder; the main reason is that the drawing is reversed: We don't want to draw the pixels, we want to clear them. GDI+ doesn't support any such composition modes, so we need to do it in code.
To be fast we use two tricks: LockBits will be as fast as it gets and restricting the area to our custom brush tip will prevent wasting time.
Let's assume you have a file to use and load it into a bitmap:
string stampFile = #"yourStampFile.png";
Bitmap stamp = null;
private void Form1_Load(object sender, EventArgs e)
{
stamp = (Bitmap) Bitmap.FromFile(stampFile);
}
Now we need a new function to draw it into our Image; instead of DrawCurve we need to use DrawImage:
void stampIntoImage(Point pt)
{
Point point = new Point(pt.X - stamp.Width / 2, pt.Y - stamp.Height / 2);
using (Bitmap stamped = new Bitmap(stamp.Width, stamp.Height) )
{
using (Graphics G = Graphics.FromImage(stamped))
{
stamp.SetResolution(stamped.HorizontalResolution, stamped.VerticalResolution);
G.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
G.DrawImage(pictureBox1.Image, 0, 0,
new Rectangle(point, stamped.Size), GraphicsUnit.Pixel);
writeAlpha(stamped, stamp);
}
using (Graphics G = Graphics.FromImage(pictureBox1.Image))
{
G.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
G.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
G.CompositingQuality =
System.Drawing.Drawing2D.CompositingQuality.HighQuality;
G.DrawImage(stamped, point);
}
}
pictureBox1.Image = pictureBox1.Image;
}
A few notes: I found that I hat to do an explicit SetResolution since the stamp file I photoshopped was 72dpi and the default Bitmaps in my program were 120dpi. Watch out for these differences!
I start the Bitmap to be drawn by copying the right part of the current Image.
Then I call a fast routine that applies the alpha of the stamp to it:
void writeAlpha(Bitmap target, Bitmap source)
{
// this method assumes the bitmaps both are 32bpp and have the same size
int Bpp = 4;
var bmpData0 = target.LockBits(
new Rectangle(0, 0, target.Width, target.Height),
ImageLockMode.ReadWrite, target.PixelFormat);
var bmpData1 = source.LockBits(
new Rectangle(0, 0, source.Width, source.Height),
ImageLockMode.ReadOnly, source.PixelFormat);
int len = bmpData0.Height * bmpData0.Stride;
byte[] data0 = new byte[len];
byte[] data1 = new byte[len];
Marshal.Copy(bmpData0.Scan0, data0, 0, len);
Marshal.Copy(bmpData1.Scan0, data1, 0, len);
for (int i = 0; i < len; i += Bpp)
{
int tgtA = data0[i+3]; // opacity
int srcA = 255 - data1[i+3]; // transparency
if (srcA > 0) data0[i + 3] = (byte)(tgtA < srcA ? 0 : tgtA - srcA);
}
Marshal.Copy(data0, 0, bmpData0.Scan0, len);
target.UnlockBits(bmpData0);
source.UnlockBits(bmpData1);
}
I use a simple rule: Reduce target opacity by the source transparency and make sure we don't get negative.. You may want to play around with it.
Now all we need is to adapt the MouseMove; for my tests I have added two RadioButtons to switch between the original round pen and the custom stamp tip:
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
if (rb_pen.Checked)
{
currentLine.Add(e.Location);
drawIntoImage();
}
else if (rb_stamp.Checked) { stampIntoImage(e.Location); };
}
}
I didn't use a fish but you can see the soft edges:
Update 2: Here is a MouseDown that allows for simple clicks:
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (rb_pen.Checked) currentLine.Add(e.Location);
else if (rb_stamp.Checked)
{
{ stampIntoImage(e.Location); };
}
}

How to make a simple magnifier in C#

I followed the following in post on"Creating a screen magnifier".
Therefore I have this code.
It is not copy & pasted from the post. I have also added a timer so the form is not blank.
However I have found some problems.
It doesn't zoom in very much. I would like to have a larger zoom. An adjustable zoom setting will be optimal, but I can make that myself if I know how to zoom in more.
The center of the form is not always the tip of the cursor like I want it would be. Is there anyway I can Fix this?
Here is the code I have got now.
Graphics g;
Bitmap bmp;
private void Timer1_Tick(object sender, EventArgs e)
{
bmp = new Bitmap(250, 200);
g = this.CreateGraphics();
g = Graphics.FromImage(bmp);
g.CopyFromScreen(MousePosition.X , MousePosition.Y , 0, 0, new Size(300, 300));
pictureBox1.Image = bmp;
}
The results seem to be exactly the same to this software that I found during my research.the link, It takes you to a Japanese webpage.
You're going to have to play around with the various numbers in the example in order to see what effect they have on the output. It'll help to turn them into variables so you can play with them more easily. Here is a good start, no promises that it works, but it'll give you a good place to start experimenting until you get what you want.
Graphics g;
Bitmap bmp;
private void Timer1_Tick(object sender, EventArgs e)
{
var endWidth = 300;
var endHeight = 300;
var scaleFactor = 2; //perhaps get this value from a const, or an on screen slider
var startWidth = endWidth / scaleFactor;
var startHeight = endHeight / scaleFactor;
bmp = new Bitmap(startWidth, startHeight);
g = this.CreateGraphics();
g = Graphics.FromImage(bmp);
var xPos = Math.Max(0, MousePosition.X - (startWidth/2)); // divide by two in order to center
var yPos = Math.Max(0, MousePosition.Y - (startHeight/2));
g.CopyFromScreen(xPos, yPos, 0, 0, new Size(endWidth, endWidth));
pictureBox1.Image = bmp;
}

boundaries cut the image when rotated

I want to rotate an image in the picture box. Here is my code.
public static Bitmap RotateImage(Image image, PointF offset, float angle)
{
if (image == null)
{
throw new ArgumentNullException("image");
}
var rotatedBmp = new Bitmap(image.Width, image.Height);
rotatedBmp.SetResolution(image.HorizontalResolution, image.VerticalResolution);
var g = Graphics.FromImage(rotatedBmp);
g.TranslateTransform(offset.X, offset.Y);
g.RotateTransform(angle);
g.TranslateTransform(-offset.X, -offset.Y);
g.DrawImage(image, new PointF(0, 0));
return rotatedBmp;
}
private void button1_Click(object sender, EventArgs e)
{
Image image = new Bitmap(pictureBox1.Image);
pictureBox1.Image = (Bitmap)image.Clone();
var oldImage = pictureBox1.Image;
var p = new Point(image.Width / 2, image.Height);
pictureBox1.Image = null;
pictureBox1.Image = RotateImage(image, p, 1);
pictureBox1.SizeMode = PictureBoxSizeMode.CenterImage;
pictureBox1.Refresh();
if (oldImage != null)
{
oldImage.Dispose();
}
}
private void button2_Click(object sender, EventArgs e)
{
Image image = new Bitmap(pictureBox1.Image);
pictureBox1.Image = (Bitmap)image.Clone();
var oldImage = pictureBox1.Image;
var p = new Point(image.Width / 2, image.Height);
pictureBox1.Image = null;
pictureBox1.Image = RotateImage(image, p, -1);
pictureBox1.SizeMode = PictureBoxSizeMode.CenterImage;
pictureBox1.Refresh();
if (oldImage != null)
{
oldImage.Dispose();
}
}
Now the problem is that when I rotate the image it gets cut. Here is the situation.
I have stretched the picture box and changed the colour of form just for clear picture.
My question is when I have used the statement
pictureBox1.Image = RotateImage(image, p, 1);
Then why is the picture not getting right after postion as this is the same statement used for any situation where we have to assign some image to groupbox. Why is not it working here? I have searched it before but the most of the searches seem irrelevant to me because they use filip function which rotate through 90,180,270. But I want to rotate by some degree maximum upto 10 degree.
Rotating Controls is not something supported by default (links talking about this: link1, link2). The reason why the picture gets cut is because, after the rotation, its width is bigger than the pictureBox1 one; thus a quick solution would be updating its size after the rotation:
pictureBox1.SizeMode = PictureBoxSizeMode.AutoSize; //Adapts the size automatically
or
pictureBox1.Width = image.Width;
pictureBox1.Height = image.Height;
This should be an acceptable solution (there has to be enough free space to account for the new dimensions of the image after being rotated anyway). The other option would be affecting the PictureBox control directly (by affecting the rectangle defining its boundaries, for example) what would be much more difficult.
Well i have come to know that win Forms are not meant for any transformations and rotations.Changing the mode to AutoSize does not make a difference. The best thing for rotation and transformation is WPF.
WPF has a good transformation classes which rotate and transform objects without affecting the object. The object does not get blurred.
You can use This for rotations and transformations.

C# : GDI+ Image cropping

I have an image .I want to crop 10 px from left and 10px from right of the image.I used the below code to do so
string oldImagePath="D:\\RD\\dotnet\\Images\\photo1.jpg";
Bitmap myOriginalImage = (Bitmap)Bitmap.FromFile(oldImagePath);
int newWidth = myOriginalImage.Width;
int newHeight = myOriginalImage.Height;
Rectangle cropArea = new Rectangle(10,0, newWidth-10, newHeight);
Bitmap target = new Bitmap(cropArea.Width, cropArea.Height);
using (Graphics g = Graphics.FromImage(target))
{
g.DrawImage(myOriginalImage,cropArea);
}
target.Save("D:\\RD\\dotnet\\Images\\test.jpg");
But this is not giving me the results which i expect. This outputs an image which has 10 px cropped from the right and a resized image.Instead of cropiing it is resizing the width i think.So the image is shrinked(by width). Can any one correct me ? Thanks in advance
Your new width should be reduced by twice the crop margin, since you'll be chopping off that amount from both sides.
Next, when drawing the image into the new one, draw it at a negative offset. This causes the area that you aren't interested in to be clipped off.
int cropX = 10;
Bitmap target = new Bitmap(myOriginalImage.Width - 2*cropX, myOriginalImage.Height);
using (Graphics g = Graphics.FromImage(target))
{
g.DrawImage(myOriginalImage, -cropX, 0);
}
My guess is this line
Rectangle cropArea = new Rectangle(10,0, newWidth-10, newHeight);
should be
Rectangle cropArea = new Rectangle(10,0, newWidth-20, newHeight);
Set the width of the new rectangle to be 20 less than the original - 10 for each side.
Some indication what result it is giving you would be helpful in confirming this.
Corey Ross is correct. Alternately, you can translate along the negative x axis and render at 0.0, 0.0. Should produce identical results.
using (Graphics g = Graphics.FromImage(target))
{
g.TranslateTransform(-cropX, 0.0f);
g.DrawImage(myOriginalImage, 0.0f, 0.0f);
}
You need to use the overload that has you specify both Destination Rectangle, and Source Rectangle.
Below is an interactive form of this using a picture box on a form. It allows you to drag the image around. I suggest making the picture box 100 x 100 and have a much larger image such as a full screen window you've captured with alt-prtscr.
class Form1 : Form
{
// ...
Image i = new Bitmap(#"C:\Users\jasond\Pictures\foo.bmp");
Point lastLocation = Point.Empty;
Size delta = Size.Empty;
Point drawLocation = Point.Empty;
bool dragging = false;
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (!dragging)
{
lastLocation = e.Location;
dragging = true;
}
delta = new Size(lastLocation.X - e.Location.X, lastLocation.Y - e.Location.Y);
lastLocation = e.Location;
if (!delta.IsEmpty)
{
drawLocation.X += delta.Width;
drawLocation.Y += delta.Height;
pictureBox1.Invalidate();
}
}
else
{
dragging = false;
}
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
Rectangle source = new Rectangle(drawLocation,pictureBox1.ClientRectangle.Size);
e.Graphics.DrawImage(i,pictureBox1.ClientRectangle,source, GraphicsUnit.Pixel);
}
//...
Okay, I totally fail at explaining this, but hang on:
The DrawImage function requires the location of the image, as well as it's position. You need a second position for cropping as how the old relates to the new, not vice versa.
That was entirely incomprehensible, but here is the code.
g.DrawImage(myOriginalImage, -cropArea.X, -cropArea.Y);
I hope that explains it more then I did.

Categories

Resources