I'm having some troubles printing images and texts that have a 8 bit alpha channel.
It looks like most printer drivers will incorrectly render the alpha channel, adding some dithering patterns instead of blending the various layers.
For instance, the code at the end of this question produces something like this (notice the dithering in the left square): Virtual PDF printer - Laser printer
So far only the XPS virtual printer works fine.
What I've always done to avoid this is printing on an intermediate bitmap, but for a standard 11x8.5'' page at 1200 DPI it would take approx 400 MB of RAM just for storing this bitmap and I'm now wondering which is the correct way to print such objects.
Thanks
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Printing;
using System.Windows.Forms;
class Program
{
static void Main(string[] args)
{
PrintDocument printDoc = new PrintDocument();
printDoc.PrintPage += new PrintPageEventHandler(printDoc_PrintPage);
PrintDialog print = new PrintDialog();
print.Document = printDoc;
if (print.ShowDialog() == DialogResult.OK)
printDoc.Print();
}
static void printDoc_PrintPage(object sender, PrintPageEventArgs e)
{
//draw directly on the print graphics
e.Graphics.TranslateTransform(50, 50);
drawStuff(e.Graphics);
//draw on an intermediate bitmap
e.Graphics.ResetTransform();
using (Bitmap bmp = new Bitmap((int)e.Graphics.DpiX, (int)e.Graphics.DpiY))
{
bmp.SetResolution(e.Graphics.DpiX, e.Graphics.DpiY);
using (Graphics g = Graphics.FromImage(bmp))
{
g.ScaleTransform(e.Graphics.DpiX / 100, e.Graphics.DpiY / 100);
drawStuff(g);
}
e.Graphics.DrawImageUnscaled(bmp, new Point(175, 50));
}
}
private static void drawStuff(Graphics graphics)
{
Brush b1 = new SolidBrush(Color.LightGray);
Brush b2 = new SolidBrush(Color.FromArgb(50, Color.Black));
graphics.FillRectangle(b1, new Rectangle(0, 0, 100, 100));
graphics.FillRectangle(b2, new Rectangle(25, 25, 50, 50));
}
}
Could you use a library such as iTextSharp? The library has a bunch of different classes that will handle images and fonts.
Related
I am capturing desktop screen and drawing on picturebox.
Below is a sample code for testing.Basically i am updating small portion of images on picture box coming from socket. so for performance prospective i am not invalidate whole image always, instead of just invalidate area which need to repaint considering scaling.
On paint event using below DrawImage overload to draw only specific region of picture box
if you try to run below code it not drawing properly.
Text areas are not readable.What is wrong i am doing here.I search extensively but did not got any solution.
Here is my code.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApp2
{
public partial class Form1 : Form
{
private Bitmap initial = new Bitmap(Screen.PrimaryScreen.Bounds.Width,
Screen.PrimaryScreen.Bounds.Height,
PixelFormat.Format32bppArgb);
private Rectangle GetViewRect() { return pictureBox1.ClientRectangle; }
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
initial = CaptureDesktop();
Rectangle imageRect = new Rectangle(0, 0, initial.Width, initial.Height);
var viewRect = GetViewRect();
var scaleX = (float)viewRect.Width / initial.Width;
var scaleY = (float)viewRect.Height / initial.Height;
// Make sure the target rectangle includes the new block
var targetRect = Rectangle.FromLTRB(
(int)Math.Truncate(imageRect.X * scaleX),
(int)Math.Truncate(imageRect.Y * scaleY),
(int)Math.Ceiling(imageRect.Right * scaleX),
(int)Math.Ceiling(imageRect.Bottom * scaleY));
pictureBox1.Invalidate(targetRect);
pictureBox1.Update();
}
private Bitmap CaptureDesktop()
{
var bmpScreenshot = new Bitmap(Screen.PrimaryScreen.Bounds.Width,
Screen.PrimaryScreen.Bounds.Height,
PixelFormat.Format32bppArgb);
// Create a graphics object from the bitmap.
var gfxScreenshot = Graphics.FromImage(bmpScreenshot);
// Take the screenshot from the upper left corner to the right bottom corner.
gfxScreenshot.CopyFromScreen(Screen.PrimaryScreen.Bounds.X,
Screen.PrimaryScreen.Bounds.Y,
0,
0,
Screen.PrimaryScreen.Bounds.Size,
CopyPixelOperation.SourceCopy);
return bmpScreenshot;
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
lock (initial)
{
e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half;
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighSpeed;
e.Graphics.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
var viewRect = GetViewRect();
var scaleX = (float)initial.Width / viewRect.Width;
var scaleY = (float)initial.Height / viewRect.Height;
var targetRect = e.ClipRectangle;
var imageRect = new RectangleF(targetRect.X * scaleX, targetRect.Y * scaleY, targetRect.Width * scaleX, targetRect.Height * scaleY);
e.Graphics.DrawImage(initial, targetRect, imageRect, GraphicsUnit.Pixel);
}
}
}
}
`
Edit : if i change to below code. it works fine.
e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half;
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
e.Graphics.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High
;
I am using DevExpress 18.1 on Windows 10 with VS 2017 for a WPF application. Additionally I am using the DevExpress BarCode Class. I am trying to create a QR Code that is 1 inch in size but am unable to do it without using something like Photoshop to shrink the output. I think I must be missing something in the process. Below is the code being used:
using System.Diagnostics;
using System.Drawing;
using System.Text;
using System.Windows;
using DevExpress.BarCodes;
namespace WpfBarcode01
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Btn_1_Click(object sender, RoutedEventArgs e)
{
BarCode barCode = new BarCode();
barCode.Symbology = Symbology.QRCode;
barCode.CodeText =
"Alexander Johnathon Stevenson JR;Senior Software Developer;alexanderjohnathonstevensonjr#somesamplewebsite.com;20180709-08:00:00;9993334444;Los Angeles;CA;USA;ABC Company";
barCode.BackColor = Color.White;
barCode.ForeColor = Color.Black;
barCode.RotationAngle = 0;
barCode.CodeBinaryData = Encoding.Default.GetBytes(barCode.CodeText);
barCode.Options.QRCode.Version = QRCodeVersion.Version5;
barCode.Options.QRCode.CompactionMode = QRCodeCompactionMode.Byte;
barCode.Options.QRCode.ErrorLevel = QRCodeErrorLevel.H;
barCode.Options.QRCode.ShowCodeText = false;
barCode.DpiX = 100;
barCode.DpiY = 100;
barCode.AutoSize = false;
barCode.Unit = GraphicsUnit.Millimeter;
barCode.ImageWidth = (float)70;
barCode.ImageHeight = (float)70;
barCode.BarCodeImage.Save("d1.png", System.Drawing.Imaging.ImageFormat.Png);
Process.Start("d1.png");
}
}
}
When this runs, a QR Code is created which a hand held scanner is able to scan both on paper and screen. The problem is it is about 2.76 inches in size. I want one about 1 inch so I end up importing the .png file to Photoshop and reducing the image size to 1 inch. This works as the images now becomes small enough for label or document printing. This workflow though seems too time consuming if someone has to do this for a few hundred QR Codes.
I tried different values for the ImageWidth and ImageHeight as well as different values for DpiX and DPiY but no luck. And I tried to change the GraphicsUnit to Inches but that option does not seem to work as I always get an image of very irregular size. So I ended up using the Millimeter option for GraphicsUnit with a basis that 1 inch = 25.4 millimeters. If I use an ImageWidth or ImageHeight value lower than 65 the QR Code box gets clipped and becomes invalid for scanning.
Is there something else I can do to make the output be 1 inch and still valid? Or perhaps some graphic library call in DevExpress I can call to reduce the .png file to 1 inch like Photoshop does? Thanks in advance.
=====================================
Update July 9, 2018
Based on PepitoSH's suggested link below I was able to find a solution which I have added here in the code update. This code produces a 1 inch .png QRCode file which is a resize from the original that was 2.76 inches.
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Text;
using System.Windows;
using DevExpress.BarCodes;
namespace WpfBarcode01
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Btn_1_Click(object sender, RoutedEventArgs e)
{
BarCode barCode = new BarCode();
barCode.Symbology = Symbology.QRCode;
barCode.CodeText =
"Alexander Johnathon Stevenson JR;Senior Software Developer;alexanderjohnathonstevensonjr#somesamplewebsite.com;20180709-08:00:00;9993334444;Los Angeles;CA;USA;ABC Company";
barCode.BackColor = Color.White;
barCode.ForeColor = Color.Black;
barCode.RotationAngle = 0;
barCode.CodeBinaryData = Encoding.Default.GetBytes(barCode.CodeText);
barCode.Options.QRCode.Version = QRCodeVersion.Version5;
barCode.Options.QRCode.CompactionMode = QRCodeCompactionMode.Byte;
barCode.Options.QRCode.ErrorLevel = QRCodeErrorLevel.H;
barCode.Options.QRCode.ShowCodeText = false;
barCode.Dpi = 200;
barCode.AutoSize = false; //needs to be off if specifying unit and widths
barCode.Unit = GraphicsUnit.Millimeter; // Note: 1 inch = 25.4 Millimeters
barCode.ImageWidth = 70F;
barCode.ImageHeight = 70F;
Bitmap bitmap = ResizeImage(barCode.BarCodeImage, 200, 200);
bitmap.Save("QRCode.png");
Process.Start("QRCode.png");
}
public static Bitmap ResizeImage(Image originalImage, int newWidthInPixels, int newHeightInPixels)
{
var destRect = new Rectangle(0, 0, newWidthInPixels, newHeightInPixels);
var destImage = new Bitmap(newWidthInPixels, newHeightInPixels);
destImage.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution);
using (var graphics = Graphics.FromImage(destImage))
{
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
using (var wrapMode = new ImageAttributes())
{
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(originalImage, destRect, 0, 0, originalImage.Width, originalImage.Height, GraphicsUnit.Pixel, wrapMode);
}
}
return destImage;
}
}
}
Update July 9, 2018
Based on PepitoSH's suggested link I was able to find a solution which I have added here in the code update. This code produces a 1 inch .png QRCode file which is a resize from the original that was 2.76 inches.
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Text;
using System.Windows;
using DevExpress.BarCodes;
namespace WpfBarcode01
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Btn_1_Click(object sender, RoutedEventArgs e)
{
BarCode barCode = new BarCode();
barCode.Symbology = Symbology.QRCode;
barCode.CodeText =
"Alexander Johnathon Stevenson JR;Senior Software Developer;alexanderjohnathonstevensonjr#somesamplewebsite.com;20180709-08:00:00;9993334444;Los Angeles;CA;USA;ABC Company";
barCode.BackColor = Color.White;
barCode.ForeColor = Color.Black;
barCode.RotationAngle = 0;
barCode.CodeBinaryData = Encoding.Default.GetBytes(barCode.CodeText);
barCode.Options.QRCode.Version = QRCodeVersion.Version5;
barCode.Options.QRCode.CompactionMode = QRCodeCompactionMode.Byte;
barCode.Options.QRCode.ErrorLevel = QRCodeErrorLevel.H;
barCode.Options.QRCode.ShowCodeText = false;
barCode.Dpi = 200;
barCode.AutoSize = false; //needs to be off if specifying unit and widths
barCode.Unit = GraphicsUnit.Millimeter; // Note: 1 inch = 25.4 Millimeters
barCode.ImageWidth = 70F;
barCode.ImageHeight = 70F;
Bitmap bitmap = ResizeImage(barCode.BarCodeImage, 200, 200);
bitmap.Save("QRCode.png");
Process.Start("QRCode.png");
}
public static Bitmap ResizeImage(Image originalImage, int newWidthInPixels, int newHeightInPixels)
{
var destRect = new Rectangle(0, 0, newWidthInPixels, newHeightInPixels);
var destImage = new Bitmap(newWidthInPixels, newHeightInPixels);
destImage.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution);
using (var graphics = Graphics.FromImage(destImage))
{
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
using (var wrapMode = new ImageAttributes())
{
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(originalImage, destRect, 0, 0, originalImage.Width, originalImage.Height, GraphicsUnit.Pixel, wrapMode);
}
}
return destImage;
}
}
}
Going through the steps mentioned here
and using IDAutomationCode39, I am getting the barcode image, however they are very blurr and only scans bigger size images. My barcode id will be upto 30 characters long, which is causing a very wide barcode image. Where could the problem lie? Is it the IDAutomationCode39 or my setting in my button click event below?
private void button1_Click(object sender, EventArgs e)
{
string abbre = GenerateProdCodeFromProductName();
txt2.Text = abbre;
string barcode = txt1.Text;
Bitmap bitm = new Bitmap(barcode.Length * 45, 160);
bitm.SetResolution(240, 240);
using (Graphics graphic = Graphics.FromImage(bitm))
{
Font newfont = new Font("IDAutomationHC39M", 6);
PointF point = new PointF(5f, 5f);
SolidBrush black = new SolidBrush(Color.Black);
SolidBrush white = new SolidBrush(Color.White);
graphic.FillRectangle(white, 0, 0, bitm.Width, bitm.Height);
graphic.DrawString("*" + barcode + "*", newfont, black, point);
}
using (MemoryStream Mmst = new MemoryStream())
{
bitm.Save("ms", ImageFormat.Jpeg);
pictureBox1.Image = bitm;
pictureBox1.Width = bitm.Width;
pictureBox1.Height = bitm.Height;
}
}
Thank you.
I don't see any PlugIn reference in your code, you are just using a Code39 ASCII font to print a Barcode on a Bitmap.
The problem I see is that the size of the resulting Bitmap is unscaled.
What I mean is, you let the size of the Barcode determine the size of the final graphic image.
It is usually the opposite. You have some defined dimensions for a Bitmap, because of layout constraints: you have to print the barcode to a label, for example.
If the generated Barcode is wider than its container, you have to normalize it (scale it to fit).
Here is what I propose. The size of the Bitmap if fixed (300, 150). When the Barcode is generated, its size is scaled to fit the Bitmap size.
The quality of the image is preserved, using an Bicubic Interpolation in case of down scaling. The resulting graphics is also Anti-Aliased.
(Anti-Alias has a good visual render. May not be the best choice for printing. It also depends on the printer.)
The generated Bitmap is then passed to a PictureBox for visualization and saved to disk as a PNG image (this format because of its loss-less compression).
Here is the result:
string Barcode = "*8457QK3P9*";
using (Bitmap bitmap = new Bitmap(300, 150))
{
bitmap.SetResolution(240, 240);
using (Graphics graphics = Graphics.FromImage(bitmap))
{
Font font = new Font("IDAutomationSHC39M", 10, FontStyle.Regular, GraphicsUnit.Point);
graphics.Clear(Color.White);
StringFormat stringformat = new StringFormat(StringFormatFlags.NoWrap);
graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
graphics.TextContrast = 10;
PointF TextPosition = new PointF(10F, 10F);
SizeF TextSize = graphics.MeasureString(Barcode, font, TextPosition, stringformat);
if (TextSize.Width > bitmap.Width)
{
float ScaleFactor = (bitmap.Width - (TextPosition.X / 2)) / TextSize.Width;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.ScaleTransform(ScaleFactor, ScaleFactor);
}
graphics.DrawString(Barcode, font, new SolidBrush(Color.Black), TextPosition, StringFormat.GenericTypographic);
bitmap.Save(#"[SomePath]\[SomeName].png", ImageFormat.Png);
this.pictureBox1.Image = (Bitmap)bitmap.Clone();
font.Dispose();
}
}
I'm creating a program that uses the flycapture camera. I've created a class that extends the pictureBox class in order to draw a crosshair, consisting of two lines, onto the screen. I want to be able to move the crosshair from the center to any other location on the screen.
The problem is when the form is resized, the crosshair moves to a different location as shown here. I want the crosshair to be pointing at the same part of the image as before it was resized (in the example it is no longer pointing at the grey mesh). I'm drawing the crosshair in relation to the height and width of the pictureBox. I want to be able to draw the lines on the image but the image height and width are always the same regardless of the image's size.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace FlyCapture2SimpleGUI_CSharp
{
class IMSPictureBox : PictureBox
{
private Color colorSetting = Color.Black;
private float width = 1.0f;
public IMSPictureBox()
{
this.Paint += IMSPictureBox_Paint;
}
private void IMSPictureBox_Paint(object sender, PaintEventArgs e)
{
//Draw if image has loaded
if (this.Image != null)
{
//Draw horizontal line
e.Graphics.DrawLine(
new Pen(this.colorSetting, this.width),
new Point(0, this.Size.Height / 2 + 100),
new Point(this.Size.Width, this.Size.Height / 2 + 100));
//Draw vertical line
e.Graphics.DrawLine(
new Pen(this.colorSetting, this.width),
new Point(this.Size.Width / 2 + 100, 0),
new Point(this.Size.Width / 2 + 100, this.Size.Height));
}
}
}
}
Edit:
As DiskJunky suggested, I'm now drawing on the image itself and not using the Paint function above.
Here is the image getting set:
private void UpdateUI(object sender, ProgressChangedEventArgs e)
{
UpdateStatusBar();
pictureBox1.SetImage = m_processedImage.bitmap;
pictureBox1.Invalidate();
}
Here are the lines drawing on the image:
public System.Drawing.Image SetImage
{
set
{
using (Graphics g = Graphics.FromImage(value))
{
g.DrawLine(new Pen(Color.Red, 3.0f), new Point(0, 0), new Point(value.Width, value.Height));
g.Dispose();
}
this.Image = value;
}
get
{
return this.Image;
}
}
I now have a line that scales with the image, but now it's constantly flickering.
This isn't exact but modifying to something like the following will keep the position static as the picture box resizes;
class IMSPictureBox : PictureBox
{
private Color colorSetting = Color.Black;
private float width = 1.0f;
private Tuple<Point, Point> _verticalLine;
private Tuple<Point, Point> _horizontalLine;
public IMSPictureBox()
{
this.Paint += IMSPictureBox_Paint;
}
private void IMSPictureBox_Paint(object sender, PaintEventArgs e)
{
//Draw if image has loaded
if (this.Image != null)
{
//Draw vertical line
if (_verticalLine == null)
{
_verticalLine = new Tuple<Point, Point>(new Point(100, 0), new Point(100, this.Size.Height);
}
e.Graphics.DrawLine(
new Pen(this.colorSetting, this.width),
_verticalLine.Item1,
_verticalLine.Item2);
//Draw horizontal line
if (_horizontalLine == null)
{
_horizontalLine = new Tuple<Point, Point>(new Point(0, 100), new Point(this.Size.Width, 100);
}
e.Graphics.DrawLine(
new Pen(this.colorSetting, this.width),
_horizontalLine.Item1,
_horizontalLine.Item2);
}
}
}
Edit
The above solution outlines the concept of preserving the line position. As per discussion in comments below, this developed into a more involved solution for the OP as additional requirements came out of investigating and testing the solution above for the original intent - to cleanly draw a a coordinate marker on an image.
To that end a manual double buffering mechanism is recommended in that scenario as the PictureBox control supports limited drawing capability itself. An example of manually implementing a double buffering solution can be found here; https://learn.microsoft.com/en-us/dotnet/framework/winforms/advanced/how-to-manually-render-buffered-graphics
Rather than calling DrawEllipse() there'll be calls to DrawLine() to display the coordinate marker. One caveat is that if the image is still being displayed in the PictureBox then the PictureBoxSizeMode.Zoom value may still have to be accounted for.
Printer resolutions are generally 5-6 times greater than a screen's resolution. A printer's resolution can be around 6600 x 5100 as opposed to a full HD screen's resolution: 1920 x 1080.
An 1920 x 1080 image looks great on a screen but to avoid pixelation, one should ideally render a much higher resolution image to the printer, for example, a 6600 x 5100 image.
I am trying to print a high definition image (6600 x 5100) to my high definition printer (600 dpi), but I find that the available print area is only 850 x 1100 as specified by e.PageBounds; see the code below:
Bitmap bitmapToPrint;
public void printImage()
{
bitmapToPrint = new Bitmap(1700,2200);
Font font = new Font(FontFamily.GenericSansSerif, 60, FontStyle.Regular);
string alphabet = "abcdefghijklmnopqrstuvwxyz";
Graphics graphics = Graphics.FromImage(bitmapToPrint);
graphics.DrawString(alphabet, font, System.Drawing.Brushes.Black, 0, 0);
graphics.DrawString(alphabet, font, System.Drawing.Brushes.Black, 0, 1000);
PrintDocument pd = new PrintDocument();
pd.PrinterSettings.PrinterName = "Microsoft XPS Document Writer";
pd.PrinterSettings.PrintToFile = true;
pd.PrintPage += new PrintPageEventHandler(pd_PrintPage);
pd.Print();
}
void pd_PrintPage(object sender, PrintPageEventArgs e)
{
e.Graphics.DrawImage(bitmapToPrint, new PointF(0, 0));
//Have a look at e.PageBounds, the dimensions are only 850x1100
}