C# How to nextline in e.Drawing when printing images - c#

I'm generating a barcode depending on how many inputs that the user set in the numericUpDown control. The problem is when generating a lot of barcodes, the other barcodes cannot be seen in the printpreviewdialog because it I cannot apply a nextline or \n every 4-5 Images.
int x = 0, y = 10;
for (int i = 1; i <= int.Parse(txtCount.Text); i++)
{
idcount++;
connection.Close();
Zen.Barcode.Code128BarcodeDraw barcode = Zen.Barcode.BarcodeDrawFactory.Code128WithChecksum;
Random random = new Random();
string randomtext = "MLQ-";
int j;
for (j = 1; j <= 6; j++)
{
randomtext += random.Next(0, 9).ToString();
Image barcodeimg = barcode.Draw(randomtext, 50);
resultimage = new Bitmap(barcodeimg.Width, barcodeimg.Height + 20);
using (var graphics = Graphics.FromImage(resultimage))
using (var font = new Font("Arial", 11)) // Any font you want
using (var brush = new SolidBrush(Color.Black))
using (var format = new StringFormat() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Far}) // Also, horizontally centered text, as in your example of the expected output
{
graphics.Clear(Color.White);
graphics.DrawImage(barcodeimg, 0, 0);
graphics.DrawString(randomtext, font, brush, resultimage.Width / 2, resultimage.Height, format);
}
x += 25;
}
e.Graphics.DrawImage(resultimage, x, y);
}

There's no "new lines" in rasterized graphics. There's pixels. You've got the right idea, every n number of images, add a new line. But since you're working with pixels, let's say every 4 images you're going to need to add a vertical offset by modifying the y coordinate of all your graphics draw calls. This offset, combined with a row height in pixels could look something like this:
var rowHeight = 250; // pixels
var maxColumns = 4;
var verticalOffset = (i % maxColums) * rowHeight;
Then, when you can supply a y coordinate, starting at or near 0, add the vertical offset to it.

Related

Custom Measure String in C# without Graphics.MeasureString

I am trying to create an Image with a Caption/Text on it for Youtube-Thumbnails.
Following Rules are defined:
The Text is the Title of the Video and always changes from Thumbnail to Thumbnail.
The Porgram uses a pre-defined Text-Width which must not be touched by the Text on the Image.
The Text should be as close to the pre-defined with as possible.
So my thoughts were that I would use Graphics.MeasureString to be able to track the Width of the String on the Image and increase the Font-Size and repeat this until the pre-defined Width is closely reached but not touched.
But I have tested it with MeasureString and found out that it isn't that accurate. And also found confirmation here: Graphics.MeasureCharacterRanges giving wrong size calculations
I have tried the things they have recommended but with no success as the final width of my string always overflooded the image borders. Even if my pre-defined Width was way smaller than the Image Width. (Image Width: 1920; Pre-Defined Width: 1600)
So I came up with the Idea to create a custom Measurement Method and to write the String as I want it on a new Bitmap and to count the maximum Pixels of the String in Height and Width. (The Height is just for future stuff)
My current Code is:
public static SizeF MeasuredStringSize(string text, Bitmap originBitmap, FontFamily fontFamily, StringFormat strformat)
{
int currentFontSize = 10;
SizeF measuredSize = new();
var highestWidth = 0;
var highestHeight = 0;
while (highestWidth < maximumTextWidth)
{
Bitmap bitmap = new(originBitmap);
Bitmap _bitmap = new(bitmap.Width, bitmap.Height);
using Graphics graphics = Graphics.FromImage(bitmap);
if (graphics != null)
{
graphics.TranslateTransform(bitmap.Width / 2, bitmap.Height / 2);
currentFontSize++;
graphics.Clear(Color.White);
using GraphicsPath path = new();
using SolidBrush brush = new(Color.Red);
using Pen pen = new(Color.Red, 6)
{
LineJoin = LineJoin.Round
};
path.AddString(text, fontFamily, (int)fontStyle, currentFontSize, new Point(0, 0), strformat);
graphics.DrawPath(pen, path);
graphics.FillPath(brush, path);
Dictionary<int, List<int>> redPixelMatrix = new();
for (int i = 0; i < bitmap.Width; i++)
{
for (int j = 0; j < bitmap.Height; j++)
{
var currentPixelColor = bitmap.GetPixel(i, j);
if (currentPixelColor.B != 255 && currentPixelColor.G != 255 && currentPixelColor.R == 255)
{
if (!redPixelMatrix.ContainsKey(i))
{
redPixelMatrix.Add(i, new());
}
redPixelMatrix[i].Add(j);
}
}
}
highestWidth = redPixelMatrix.Keys.Count;
highestHeight = redPixelMatrix.Aggregate((l, r) => l.Value.Count > r.Value.Count ? l : r).Value.Count;
Console.WriteLine($"X:{highestWidth};Y:{highestHeight}");
//Debugging the final Image with Text to see the Result
bitmap.Save(ResultPath);
}
}
measuredSize = new SizeF(highestWidth, highestHeight);
return measuredSize;
}
The Resulting Image from bitmap.Save(ResultPath); as the String reaches the Image borders looks like this:
But the exact String width is 1742 instead of the width of my Image 1920 which should be more or less the same at this moment.
So, why is the Text nearly as wide as the Image but doesn't have the same width?
highestWidth = redPixelMatrix.Keys.Count; This will just count the number of columns containing red pixels, excluding any spaces in the text. You presumably want the minimum and maximum indices.
I.e.
var minX = int.MaxValue;
var maxX = int.MinValue;
// Loops over rows & columns
// Check if pixel is red
if(i > maxX) maxX = i;
if(i < minX) minX = i;
If you only want the text width and not the bounds you can just do maxX - minX.

Lines drawn on a large Bitmap are not visible when the image is saved

I have created a program to draw square grids on a selected image. It works fine for images that has small resolution, but it doesn't work properly on large images.
The all grid lines are not visible seem when the image is saved as file.
The image I am testing has resolution 3600x4320 and can be shown in the link.
How can I fix this problem?
My code:
Image drawGrid(int n, string imgPath)
{
Image img = Image.FromFile(imgPath);
Graphics grp = Graphics.FromImage(img);
grp.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
float m = img.Width * 1f / n;
for (int i = 1; i < n; i++)
{
var x = new PointF(i * m, 0);
var y = new PointF(i * m, img.Height);
grp.DrawLine(Pens.Red, x, y);
}
for (int i = 1; i <= (int)(this.Height / m); i++)
{
var x = new PointF(0, i * m);
var y = new PointF(img.Width, i * m);
grp.DrawLine(new Pen(Color.Red, 5f), x, y);
}
return img;
}
void BtnExportClick(object sender, EventArgs e)
{
if(saveFileDialog1.ShowDialog() == DialogResult.OK)
{
int n = (int)numericUpDown1.Value;
drawGrid(n, txtImagePath.Text).Save(saveFileDialog1.FileName, System.Drawing.Imaging.ImageFormat.Jpeg);
MessageBox.Show("Done");
}
}
The result image is below (resolution reduced to upload)
The grid lines not shown correctly.
The major problem is in this line:
for (int i = 1; i <= (int)(this.Height / m); i++)
▶ this.Height is clearly not what you wanted to write, let's replace it with the Image.Height
▶ grp.DrawLine(Pens.Red, x, y); and grp.DrawLine(new Pen(Color.Red, 5f), x, y); will draw lines of different size (1 and 5 pixels). In the sample code, the two methods accept Color and float arguments that define the Pen color and size.
▶ grp.SmoothingMode: we don't want any smoothing mode here, not needed to draw straight lines and it will add anti-alias which will be clearly visible, especially when saving the Image in JPEG format (it will anti-alias - sort of, it actually mangles the colors - these lines by itself).
▶ You're not disposing of any of the Graphics object you create. This is quite important with both frequent graphics operations and when working with large Bitmaps.
The Pen and Graphics objects needs to be disposed.
Since it's not exactly clear if you want to generate a grid that has the same number of lines in both dimensions - hence, a grid with Cells in which the Width is not equal to the Height, most probably - or a grid with squared Cells (this is what the sample Image seems to show, not the code), I posted two method that draw both grid types:
First method, same number of lines in both width and height:
var gridSizeX = (float)image.Width / lines;
var gridSizeY = (float)image.Height / lines;
private Image DrawGridLines(int lines, string imgPath, Color penColor, float penSize)
{
var image = Image.FromStream(new MemoryStream(File.ReadAllBytes(imgPath)), true);
using (var g = Graphics.FromImage(image)) {
g.PixelOffsetMode = PixelOffsetMode.Half;
var gridSizeX = (float)image.Width / lines;
var gridSizeY = (float)image.Height / lines;
for (int i = 1; i < lines; i++) {
var pointX1 = new PointF(0, i * gridSizeY);
var pointX2 = new PointF(image.Width, i * gridSizeY);
var pointY1 = new PointF(i * gridSizeX, 0);
var pointY2 = new PointF(i * gridSizeX, image.Height);
using (var pen = new Pen(penColor, penSize)) {
g.DrawLine(pen, pointX1, pointX2);
g.DrawLine(pen, pointY1, pointY2);
}
}
return image;
}
}
Second method, drawing a squared grid. The integer value, gridSection, is used to define a grid Cell based on the minimum dimension of the Bitmap.
This dimension is then used to determine how many lines to draw in the other dimension.
The grid size is calculated on the minimum dimension:
var gridSize = (float)Math.Min(image.Width, image.Height) / gridSection;
And the Cell are determined as a consequence:
var gridStepMin = Math.Min(image.Width, image.Height) / gridSize;
var gridStepMax = Math.Max(image.Width, image.Height) / gridSize;
private Image DrawSquaredGrid(int gridSection, string imgPath, Color penColor, float penSize)
{
var image = Image.FromStream(new MemoryStream(File.ReadAllBytes(imgPath)), true);
using (var g = Graphics.FromImage(image)) {
g.PixelOffsetMode = PixelOffsetMode.Half;
var gridSize = (float)Math.Min(image.Width, image.Height) / gridSection;
var gridStepMin = Math.Min(image.Width, image.Height) / gridSize;
var gridStepMax = Math.Max(image.Width, image.Height) / gridSize;
for (int i = 1; i < gridStepMin; i++) {
var pointY1 = new PointF(i * gridSize, 0);
var pointY2 = new PointF(i * gridSize, image.Height);
using (var pen = new Pen(penColor, penSize)) {
g.DrawLine(pen, pointY1, pointY2);
}
}
for (int i = 1; i < gridStepMax; i++) {
var pointX1 = new PointF(0, i * gridSize);
var pointX2 = new PointF(image.Width, i * gridSize);
using (var pen = new Pen(penColor, penSize)) {
g.DrawLine(pen, pointX1, pointX2);
}
}
return image;
}
}
The SaveFileDialog is refactored to allow multiple Image formats. and to call one of the drawing methods based on a selection (in the sample code, a CheckBox (chkSquared) is used select one of the Grid types).
You can add more formats, the ImageFormatFromFileName() methods selects the ImageFormat type based on the SaveFileDialog.FielName extension.
private void BtnExportClick(object sender, EventArgs e)
{
string imagePath = [Some Path];
using (var sfd = new SaveFileDialog()) {
sfd.Filter = "PNG Image (*.png)|*.png|TIFF Image (*.tif)|*.tif|JPEG Image (*.jpg)|*.jpg";
sfd.RestoreDirectory = true;
sfd.AddExtension = true;
if (sfd.ShowDialog() == DialogResult.OK) {
Image image = null;
if (chkSquared.Checked) {
image = DrawSquaredGrid((int)numericUpDown1.Value, imagePath, Color.Red, 5.0f);
}
else {
image = DrawGridLines((int)numericUpDown1.Value, imagePath, Color.Red, 5.0f);
}
image.Save(sfd.FileName, ImageFormatFromFileName(sfd.FileName));
MessageBox.Show("Done");
image.Dispose();
}
}
}
private ImageFormat ImageFormatFromFileName(string fileName)
{
string fileType = Path.GetExtension(fileName).Remove(0, 1);
if (fileType.Equals("tif")) fileType = "tiff";
if (fileType.Equals("jpg")) fileType = "jpeg";
return (ImageFormat)new ImageFormatConverter().ConvertFromString(fileType);
}

Send Arabic text as Bitmap

I want to Send Arabic text as Bitmap to a POS printer since I could not print Arabic words directly to the printer. I used below code to convert a text to Bitmap :
Convert_ValueToImage("كيكه", "Simplified Arabic Fixed", 12)
public static Bitmap Convert_ValueToImage(string ValueText, string Fontname, int Fontsize)
{
//creating bitmap image
Bitmap ValueBitmap = new Bitmap(1, 1);
//FromImage method creates a new Graphics from the specified Image.
Graphics Graphics = Graphics.FromImage(ValueBitmap);
// Create the Font object for the image text drawing.
Font Font = new Font(Fontname, Fontsize);
// Instantiating object of Bitmap image again with the correct size for the text and font.
SizeF stringSize = Graphics.MeasureString(ValueText, Font);
ValueBitmap = new Bitmap(ValueBitmap, (int)stringSize.Width, (int)stringSize.Height);
Graphics = Graphics.FromImage(ValueBitmap);
//Draw Specified text with specified format
Graphics.DrawString(ValueText, Font, Brushes.Black, 0, 0);
Font.Dispose();
Graphics.Flush();
Graphics.Dispose();
return ValueBitmap; //return Bitmap Image
}
and when I assign it to pictureBox it works.
Now I want to send it to the printer. I used below method to convert the bitmap image to string with adding the image mode to the string:
public string GetArabic(Bitmap ArabicText)
{
string logo = "";
BitmapData data = GetArabicBitmapData(ArabicText);
BitArray dots = data.Dots;
byte[] width = BitConverter.GetBytes(data.Width);
int offset = 0;
MemoryStream stream = new MemoryStream();
BinaryWriter bw = new BinaryWriter(stream);
bw.Write((char)0x1B);
bw.Write('#');
bw.Write((char)0x1B);
bw.Write('3');
bw.Write((byte)24);
while (offset < data.Height)
{
bw.Write((char)0x1B);
bw.Write('*'); // bit-image mode
bw.Write((byte)33); // 24-dot double-density
bw.Write(width[0]); // width low byte
bw.Write(width[1]); // width high byte
for (int x = 0; x < data.Width; ++x)
{
for (int k = 0; k < 3; ++k)
{
byte slice = 0;
for (int b = 0; b < 8; ++b)
{
int y = (((offset / 8) + k) * 8) + b;
// Calculate the location of the pixel we want in the bit array.
// It'll be at (y * width) + x.
int i = (y * data.Width) + x;
// If the image is shorter than 24 dots, pad with zero.
bool v = false;
if (i < dots.Length)
{
v = dots[i];
}
slice |= (byte)((v ? 1 : 0) << (7 - b));
}
bw.Write(slice);
}
}
offset += 24;
bw.Write((char)0x0A);
}
// Restore the line spacing to the default of 30 dots.
bw.Write((char)0x1B);
bw.Write('3');
bw.Write((byte)30);
bw.Flush();
byte[] bytes = stream.ToArray();
return logo + Encoding.Default.GetString(bytes);
}
public BitmapData GetArabicBitmapData(Bitmap bmpFileName)
{
using (var bitmap = bmpFileName )
{
var threshold = 127;
var index = 0;
double multiplier = 570; // this depends on your printer model. for Beiyang you should use 1000
double scale = (double)(multiplier / (double)bitmap.Width);
int xheight = (int)(bitmap.Height * scale);
int xwidth = (int)(bitmap.Width * scale);
var dimensions = xwidth * xheight;
var dots = new BitArray(dimensions);
for (var y = 0; y < xheight; y++)
{
for (var x = 0; x < xwidth; x++)
{
var _x = (int)(x / scale);
var _y = (int)(y / scale);
var color = bitmap.GetPixel(_x, _y);
var luminance = (int)(color.R * 0.3 + color.G * 0.59 + color.B * 0.11);
dots[index] = (luminance < threshold);
index++;
}
}
return new BitmapData()
{
Dots = dots,
Height = (int)(bitmap.Height * scale),
Width = (int)(bitmap.Width * scale)
};
}
}
this code print a black Rectangle. what would help me is if I could print the text with white background and the size is small as the text size.

Filling a big rectangle with a variable quantity of little rectangles

I'm trying to fill a rectangle with a variable quantity of little rectangles but adjusting the distance between them depending on the number of units (more units->the lesser the distance between).
I'm a newbie programming WPF in C# and i don´t know how to advance from this point.
How can I do it?
The code so far:
int units = 20;
int width = 10;
int height = 20;
int top = 200;
int left = 200;
int rectangleWidth = 300;
int rectangleHeight = 100;
for (int i = 0; i < units; i++)
{
Rectangle rec = new Rectangle()
{
Width = width,
Height = height,
Fill = Brushes.Black,
Stroke = Brushes.White,
StrokeThickness = 1,
RadiusX = 3,
RadiusY = 3,
};
cuadernodibujo.Children.Add(rec);
Canvas.SetTop(rec, top);
Canvas.SetLeft(rec, left + (i*50));
}
I have updated the code, but doesn´t work.
I don´t know what am i doing wrong.
The piece of code so far:
int rectangleWidth = 500;
int rectangleHeight = 100;
int units = 60;
int unitsX = 10;
int unitsY = 6;
var childWidht = (rectangleWidth - 2*Left) / unitsX;
var childHeigth = (rectangleHeight - 2*Top ) / unitsY;
int width = 10;
int height = 20;
double top = 100;
double left = 100;
for (int i = 0; i < units; i++)
{
Rectangle rec = new Rectangle()
{
Width = width,
Height = height,
Fill = Brushes.Black,
Stroke = Brushes.White,
StrokeThickness = 1,
RadiusX = 3,
RadiusY = 3,
};
cuadernodibujo.Children.Add(rec);
for (int j = 0; j < unitsY; j++)
{
Rectangle rec2 = new Rectangle()
{
Width = width,
Height = height,
Fill = Brushes.Black,
Stroke = Brushes.White,
StrokeThickness = 1,
RadiusX = 3,
RadiusY = 3,
};
cuadernodibujo.Children.Add(rec2);
Canvas.SetTop(rec, top + (j * childHeigth));
for (int k = 0; k < unitsX; k++)
{
Rectangle rec3 = new Rectangle()
{
Width = width,
Height = height,
Fill = Brushes.Black,
Stroke = Brushes.White,
StrokeThickness = 1,
RadiusX = 3,
RadiusY = 3,
};
cuadernodibujo.Children.Add(rec3);
Canvas.SetLeft(rec, left + (k * childWidht));
}
}
}
If I understand correctly, you want to spread the little rectangles uniformly over the width of the parent rectangle.
This is less a programming problem, then a maths problem.
Given the parent rectangle's width parentWidht and the number of child rectangles units each child rectangle has a width of:
var childWidht = parentWidht / units;
If you want to add a left and right margin (given your left variable), you need to subtract the margin from parentWidht.
var childWidht = (parentWidht - 2 * left) / units; // 2 times left, to add the margin on both sides.
This gives you the width of each child, you now only have to move each child rectangle according to the previously calculated childWidht.
...
var childWidht = (parentWidht - 2 * left) / units;
for (int i = 0; i < units; i++)
{
...
Canvas.SetLeft(rec, left + (i*childWidht));
}
Update to question in the comments
With that I can fill a single line, but how can I fill the rest of the lines (to fill the parent height as well)?
We can apply the same logic as for the horizontal filling.
First calculate the child rectangles height (parentHeight - 2 * top)
Then wrap the horizontal rectangles into a loop and move each line according to the calculated height.
Here the listing with horizontal and vertical filling.
...
var childWidht = (parentWidht - 2 * left) / unitsX;
var childHeigth = (parentHeigth - 2 * top) / unitsY;
for (int j = 0; j < unitsY; i++) // first loop for vertical filling
{
for (int i = 0; i < unitsX; i++) // second loop for horizontal
{
var rect = new Rectangle { ... } ;
Canvas.Children.Add(rect); // Only add once in the inner loop.
Canvas.SetTop(rec, top + (j * childHeigth)); // here we use j, the vertical index
Canvas.SetLeft(rec, left + (i*childWidht)); // here we use i, the horizontal index
}
}

c# bitmap question

Sorry for the previous post. I now show the full code here.
I need to know what the bitmap.Width and bitmap.Height - 1 for and also the bitmap.Scan0.
I search in the internet but it does not give any full explanation for that.
I will appreciate anyone who can briefly explain the whole thing. Thank you.
public static double[][] GetRgbProjections(Bitmap bitmap)
{
var width = bitmap.Width - 1;
var height = bitmap.Height - 1;
var horizontalProjection = new double[width];
var verticalProjection = new double[height];
var bitmapData1 = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
unsafe
{
var imagePointer1 = (byte*)bitmapData1.Scan0;
for (var y = 0; y < height; y++)
{
for (var x = 0; x < width; x++)
{
var blu = imagePointer1[0];
var green = imagePointer1[1];
var red = imagePointer1[2];
int luminosity = (byte)(((0.2126 * red) + (0.7152 * green)) + (0.0722 * blu));
horizontalProjection[x] += luminosity;
verticalProjection[y] += luminosity;
imagePointer1 += 4;
}
imagePointer1 += bitmapData1.Stride - (bitmapData1.Width * 4);
}
}
MaximizeScale(ref horizontalProjection, height);
MaximizeScale(ref verticalProjection, width);
var projections =
new[]
{
horizontalProjection,
verticalProjection
};
bitmap.UnlockBits(bitmapData1);
return projections;
}
Apparently it runs through every pixel of a RGBA bitmap and calculates the luminosity per pixel which its tracks inside two arrays, luminosity per horizontal line and luminosity per vertical line.
Unless I am mistaken, the -1 should not even be there. When you have a bitmap of 100x100 you want to create an array with 100 elements, not an array with 99 elements (width-1) since you want to track every horizontal and vertical line.

Categories