My printer could handle only B3 and A4 sizes, I am working on an application to concatenate images. These images are of different sizes. I would like to know how to neatly arrange all of them into one single image file before printing it in A4 or B3.
For concatenation, I have got the working code but still am stuck at how the images should be arranged to fit the output paper size above. Thank you for any ideas
public Image DrawLstOfItem(List<string> imgPath)
{
string path = imgPath.First();
Image img = Image.FromFile(path);
Bitmap bmp = new Bitmap(img.Width, (int)(img.Height*imgPath.Count+(5.0f*(imgPath.Count-1))));
using (Graphics g = Graphics.FromImage(bmp))
{
g.DrawImage(img, 0, 0);
float nY = img.Height + 5.0f;
float nX = 0.0f;
for (int i = 1; i < imgPath.Count; i++)
{
Image nextImg = Image.FromFile(imgPath[i]);
g.DrawImage(nextImg, nX, nY, nextImg.Width, nextImg.Height);
nY += nextImg.Height + 0.5f;
}
}
return bmp;
}
I need to detect input image sizes, calculate the possible outcomes and arrange them when the requirement is B3 or A4 respectively. I am messed up with all different ideas and really need someone to enlighten me so I can find a likely optimal path to move on.
Related
When i run the program, i get a different amount of images(from 20 all the way up to 2000) and i would like to merge all of these images into one image which would preferably be a square.
This is the code i have for getting the file images(the images are in URL format)
int ximg = 1;
int totalImgs = richTextBox1.Lines.Count();
while (ximg < totalImgs)
{
System.Net.WebRequest request = System.Net.WebRequest.Create(richTextBox1.Lines[ximg]);
System.Net.WebResponse response = request.GetResponse();
System.IO.Stream responseStream =
response.GetResponseStream();
Bitmap image = new Bitmap(responseStream);
List<Image> fileList = new List<Image>();
fileList.Add(image);
ximg++;
}
Also every single image has a title in a different richtextbox which i would like to know if it is possible to add a title under the image (richtextbox1.lines[1] (image) = richtextbox2.lines[1] (title)). Is it possible to add a picture as a background picture when merging(to the square image I want to generate)? Is it possible to add a border on every single image picture and merge them with the border? How can the code calculate when its time to change line and start adding images in the next row?
I've tried this code, but it works only if you know the amount of images you want to merge.
Bitmap bitmap = new Bitmap(image1.Width + image2.Width, Math.Max(image1.Height, image2.Height));
using (Graphics g = Graphics.FromImage(bitmap))
{
g.DrawImage(image1, 0, 0);
g.DrawImage(image2, image1.Width, 0);
}
bitmap.Save("merged.bmp");
I would do it with photoshop but when there are 2000 images to merge together, I just do not have the time.
Is there any way to accomplish such task? Any references would be appreciated!
I have mentioned it above :) !
This solution will create a mosaic of the images from left to right, top to bottom. It does not do anything to maximize the available space. It also assumes that you have a max width and height for the finished image, since it's not realistic to support an arbitrary size.
// where we store the finished mosaic
var mosaic = new Bitmap(maxWidth, maxHeight);
// track the location where we are drawing each image
var imageCorner = new Point(0,0);
// track the height of the current row
var currentRowHeight = 0;
// track the width and height of the mosaic as it grows.
var mosaicWidth = 0;
var mosaicHeight = 0;
using (var g = Graphics.FromImage(bitmap))
{
var borderPen = new Pen(Brushes.Black) { Width = 2 };
var labelFont = new Font("Arial", 10);
var labelBrush = new SolidBrush(Color.Black);
foreach (var image in imageList)
{
if (imageCorner.X + image.Width > maxWidth)
{
// if adding the image to the current row would make it too wide,
// move to the next row by resetting X to zero and adding the
// height of the tallest image to Y
imageCorner.X = 0;
imageCorner.Y += currentRowHeight;
// since this is a new row, it's current height is zero
currentRowHeight = 0;
}
// if adding this image would put us past the
// height of the image, then we're out of room.
if (imageCorner.Y + image.Height > maxHeight)
{
// this skips images if there's no room for them in
// the mosaic, you may want to do something different
Trace.WriteLine($"Image is {image.Height} pixels tall, but only {maxHeight - mosaicHeight} pixels available.");
continue;
}
// draw the image
g.DrawImage(image, imageCorner);
// draw the border
g.DrawRectangle(borderPen,
imageCorner.X, imageCorner.Y,
image.Width, image.Height)
// draw the label
g.DrawText("Image Label", labelBrush, imageCorner.X, imageCorner.Y)
// now that we've drawn the image, we need to shift to the right
imageCorner.X += image.Width;
// row height is the height of the tallest image so far in this row
currentRowHeight = Math.Max(image.Height, currentRowHeight);
// track the total height of the mosaic
mosaicHeight = imageCorner.Y + currentRowHeight;
// mosaic width is just the widest row in the mosaic
mosaicWidth = Math.Max(imageCorner.X, widthOfWidestRow);
}
}
// trim off the parts of the mosaic we didn't fill
mosaic = mosaic.Clone(new Rectangle(0, 0, mosaicWidth, mosaicHeight);
mosaic.Save("merged.bmp");
If you wanted to minimize wasted space, you could sort your list of images in different ways, or calculate ahead of time what a good width and height for the mosaic would be.
I need to cut a .jpg image from top of 20 pixels.
I have this code to associate the image to the object:
iTextSharp.text.Rectangle rect = pdf.AcroFields.GetFieldPositions("BarCode")[0].position;
iTextSharp.text.Image img = iTextSharp.text.Image.GetInstance(Application.StartupPath + segnacollo.BarCode);
img.ScaleAbsoluteHeight(rect.Height);
img.ScaleAbsoluteWidth(rect.Width);
img.SetAbsolutePosition(rect.Left, rect.Bottom);
pdf.GetOverContent(1).AddImage(img);
Thanks
Please take a look at the answer to the question How to give an image rounded corners?
In this example, we clip an image so that it gets rounded corners:
Image img = Image.GetInstance(some_path_to_an_image);
float w = img.ScaledWidth;
float h = img.ScaledHeight;
PdfTemplate t = writer.DirectContent.CreateTemplate(w, h);
t.Ellipse(0, 0, w, h);
t.Clip();
t.NewPath();
t.AddImage(img, w, 0, 0, h, 0, -600);
Image clipped = Image.GetInstance(t);
You want to remove part of the top of the image, you can make your code even simpler:
Image img = Image.GetInstance(some_path_to_an_image);
float w = img.ScaledWidth;
float h = img.ScaledHeight;
PdfTemplate t = writer.DirectContent.CreateTemplate(w, h - 20);
t.AddImage(img, w, 0, 0, h, 0, 0);
Image clipped = Image.GetInstance(t);
In this example, I created a PdfTemplate that is 20 user units smaller than the original image. I add the original image to that template, and I create a new image from that template.
Important: this visually clips the image in the sense that you won't see the top strip (20 user units high) in the PDF. However, if you extract the image from the PDF, you'll see that the complete image is present in the document.
I try to draw a string (single character) in C# into a Bitmap at an exact position with:
Bitmap bmp = new Bitmap(64, 64);
Graphics g = Graphics.FromImage(bmp);
g.DrawString("W", font1, new SolidBrush(myColor), new Point(32,32);
There is so much empty space rendered around a single letter, that I can not guess the "needed" position to draw the character to have it at the correct position at the end.
By now I have the pixel exact dimension of the character (looking at bits in a separately rendered bitmap). But this information is useless, if I cannot draw the character at an exact position (e.g. center or top right corner or ....).
Are there other methods to draw text in C# on a bitmap? Or are there any converting methods to convert the real pixel position in something DrawString needs?
No need to look at the pixels or start working with your own font..
You can use a GraphicsPath instead of DrawString or TextRenderer, as it will let you know its net bounds rectangle with GraphicsPath.GetBounds() .
When you know it, you can calculate how to move the Graphics object using TranslateTransform:
private void button1_Click(object sender, EventArgs e)
{
string text = "Y"; // whatever
Bitmap bmp = new Bitmap(64, 64); // whatever
bmp.SetResolution(96, 96); // whatever
float fontSize = 32f; // whatever
using ( Graphics g = Graphics.FromImage(bmp))
using ( GraphicsPath GP = new GraphicsPath())
using ( FontFamily fontF = new FontFamily("Arial"))
{
testPattern(g, bmp.Size); // optional
GP.AddString(text, fontF, 0, fontSize, Point.Empty,
StringFormat.GenericTypographic);
// this is the net bounds without any whitespace:
Rectangle br = Rectangle.Round(GP.GetBounds());
g.DrawRectangle(Pens.Red,br); // just for testing
// now we center:
g.TranslateTransform( (bmp.Width - br.Width ) / 2 - br.X,
(bmp.Height - br.Height )/ 2 - br.Y);
// and fill
g.FillPath(Brushes.Black, GP);
g.ResetTransform();
}
// whatever you want to do..
pictureBox1.Image = bmp;
bmp.Save("D:\\__test.png", ImageFormat.Png);
}
A small test routine to let us see the centering better:
void testPattern(Graphics g, Size sz)
{
List<Brush> brushes = new List<Brush>()
{ Brushes.SlateBlue, Brushes.Yellow,
Brushes.DarkGoldenrod, Brushes.Lavender };
int bw2 = sz.Width / 2;
int bh2 = sz.Height / 2;
for (int i = bw2; i > 0; i--)
g.FillRectangle(brushes[i%4],bw2 - i, bh2 - i, i + i, i + i );
}
The GetBounds method returns a RectangleF; in my example it is {X=0.09375, Y=6.0625, Width=21, Height=22.90625}. Do note that due to rounding things can always be off by one..
You may or may not want to change the Graphics setting to special Smoothingmodes etc..
Also it should be noted that this will do automatic ie mechanical centering by the bounds rectangle. This may be quite different from 'optical or visual centering', which is rather hard to code and to some extent a matter of personal taste. But typography is as much an art as a profession..
What I'm After:
I'm trying to create an extra 1/4 inch of white space to be appended to the TOP of the image during the scanning process.
Using the Kofax Image Controls Toolkit is it possible within one of the following events to add extra white space to the top of the image when scanning?
_PageStart
_PageEnd
_PageAnnotate
_PageDone
Most of the properties available are read only... I know I can set the scan size in the beginning to say 14 inches and when scanning an 11 inch document I will get my extra 3 inches at the bottom of the image. I want to achieve the same principle but at the top of the document and only about a quarter of an inch tall.
So in all the research I did trying to make this possible I came to the conclusion it is in fact not possible...
The only drawbacks about this method were the speed issues after the fact. It did end up slowing down things a good bit.. Hope this helps someone else! Upvote if it helps you please. ;)
What I ended up doing is not using the _PageAnnotate event raised by the Kofax Image Controls and in turn I ended up turning off that event by setting .hDCCreate = false on the Kofax Scan control. Not creating a DC when scanning speeds up the process a little bit not bogging down the processor with all the uncompressing and compressing of the image.
Instead of using the built in Kofax Image Controls event _PageAnnotate I used the _PageDone event and from there used native c# objects/functions to achieve what I was after. Below is the code I ended up with.
if (!Imaging.AnnotateImage(sImageFileName, "This is my annotation..."))
{
Debug.WriteLine("Scan Error! Could not annotate the image.");
}
The code for the class I made called 'Imaging.cs'...
static class Imaging
{
public static bool AnnotateImage(string sFilePath, string sText)
{
try
{
// Get an ImageCodecInfo object that represents the TIFF codec.
ImageCodecInfo myImageCodecInfo = GetEncoderInfo("image/tiff");
Encoder myEncoder = Encoder.Compression;
EncoderParameters myEncoderParameters = new EncoderParameters(1);
EncoderParameter myEncoderParameter = new EncoderParameter(myEncoder, (long)EncoderValue.CompressionCCITT4);
myEncoderParameters.Param[0] = myEncoderParameter;
//Create some global variables
Point pointPosition = new Point(0, 0);
PointF pPos = new PointF((float)pointPosition.X, (float)pointPosition.Y);
Bitmap newBmp;
Graphics g;
Font fNewFont;
SizeF sizeTextSize;
Rectangle rectTextSize;
//Set inital width and height variables
int iW;
int iH;
int iAnnotationH = 44;
using(Image iSource = Image.FromFile(sFilePath))
{
iW = iSource.Width;
iH = iSource.Height;
//Increase the height of the image
iH += iAnnotationH;
//Create the new bitmap object
newBmp = new Bitmap(iW, iH);
newBmp.SetResolution(200.0F, 200.0F);
g = Graphics.FromImage(newBmp);
g.Clear(Color.White);
g.DrawImageUnscaled(iSource, 0, iAnnotationH, iW, iH);
//Create the font object to draw the annotation text
fNewFont = new Font("Verdana", 12);
//Get the size of the area to be drawn then convert it to a rect
sizeTextSize = g.MeasureString(sText, fNewFont);
rectTextSize = new Rectangle(pointPosition.X, pointPosition.Y, (int)sizeTextSize.Width, (int)sizeTextSize.Height);
//Draw a white rect
g.FillRectangle(Brushes.White, rectTextSize);
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
g.DrawString(sText, fNewFont, Brushes.Black, pPos);
}
//Save the changed image
newBmp.Save(sFilePath, myImageCodecInfo, myEncoderParameters);
}
catch
{
return false;
}
return true;
}
static ImageCodecInfo GetEncoderInfo(String mimeType)
{
int j;
ImageCodecInfo[] encoders;
encoders = ImageCodecInfo.GetImageEncoders();
for (j = 0; j < encoders.Length; ++j)
{
if (encoders[j].MimeType == mimeType)
return encoders[j];
}
return null;
}
}
How can I fill the holes in binary image in emgu cv?
In Aforge.net it's easy, use Fillholes class.
Thought the question is a little bit old, I'd like to contribute an alternative solution to the problem.
You can obtain the same result as Chris' without memory problem if you use the following:
private Image<Gray,byte> FillHoles(Image<Gray,byte> image)
{
var resultImage = image.CopyBlank();
Gray gray = new Gray(255);
using (var mem = new MemStorage())
{
for (var contour = image.FindContours(
CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE,
RETR_TYPE.CV_RETR_CCOMP,
mem); contour!= null; contour = contour.HNext)
{
resultImage.Draw(contour, gray, -1);
}
}
return resultImage;
}
The good thing about the method above is that you can selectively fill holes that meets your criteria. For example, you may want to fill holes whose pixel count (count of black pixels inside the blob) is below 50, etc.
private Image<Gray,byte> FillHoles(Image<Gray,byte> image, int minArea, int maxArea)
{
var resultImage = image.CopyBlank();
Gray gray = new Gray(255);
using (var mem = new MemStorage())
{
for (var contour = image.FindContours(
CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE,
RETR_TYPE.CV_RETR_CCOMP,
mem); contour!= null; contour = contour.HNext)
{
if ( (contour.Area < maxArea) && (contour.Area > minArea) )
resultImage.Draw(contour, gray, -1);
}
}
return resultImage;
}
Yes there is a method but it's a bit messy as its based on cvFloodFill operation. Now all this algorithm is designed to do is fill an area with a colour until it reaches an edge similar to a region growing algorithm. To use this effectively you need to use a little inventive coding but I warn you this code is only to get you started it may require re-factoring to speed things up . As it stands the loop goes through each of your pixels that are less then 255 applies cvFloodFill checks what size the area is and then if it is under a certain area fill it in.
It is important to note that a copy of the image is made of the original image to be supplied to the cvFloodFill operation as a pointer is used. If the direct image is supplied then you will end up with a white image.
OpenFileDialog OpenFile = new OpenFileDialog();
if (OpenFileDialog.ShowDialog() == DialogResult.OK)
{
Image<Bgr, byte> image = new Image<Bgr, byte>(OpenFile.FileName);
for (int i = 0; i < image.Width; i++)
{
for (int j = 0; j < image.Height; j++)
{
if (image.Data[j, i, 0] != 255)
{
Image<Bgr, byte> image_copy = image.Copy();
Image<Gray, byte> mask = new Image<Gray, byte>(image.Width + 2, image.Height + 2);
MCvConnectedComp comp = new MCvConnectedComp();
Point point1 = new Point(i, j);
//CvInvoke.cvFloodFill(
CvInvoke.cvFloodFill(image_copy.Ptr, point1, new MCvScalar(255, 255, 255, 255),
new MCvScalar(0, 0, 0),
new MCvScalar(0, 0, 0), out comp,
Emgu.CV.CvEnum.CONNECTIVITY.EIGHT_CONNECTED,
Emgu.CV.CvEnum.FLOODFILL_FLAG.DEFAULT, mask.Ptr);
if (comp.area < 10000)
{
image = image_copy.Copy();
}
}
}
}
}
The "new MCvScalar(0, 0, 0), new MCvScalar(0, 0, 0)," are not really important in this case as you are only filling in results of a binary image. YOu could play around with other settings to see what results you can achieve. "if (comp.area < 10000)" is the key constant to change is you want to change what size hole the method will fill.
These are the results that you can expect:
Original
Results
The problem with this method is it's extremely memory intensive and it managed to eat up 6GB of ram on a 200x200 image and when I tried 200x300 it ate all 8GB of my RAM and brought everything to a crashing halt. Unless a majority of your image is white and you want to fill in tiny gaps or you can minimise where you apply the method I would avoid it. I would suggest writing you own class to examine each pixel that is not 255 and add the number of pixels surrounding it. You can then record the position of each pixel that was not 255 (in a simple list) and if your count was bellow a threshold set these positions to 255 in your images (by iterating though the list).
I would stick with the Aforge FillHoles class if you do not wish to write your own as it is designed for this purpose.
Cheers
Chris
you can use FillConvexPoly
image.FillConvexPoly(externalContours.ToArray(), new Gray(255));