Evenly positioning of multiple images on bitmap - c#

I'm trying to position a number of images onto a single fixed size Image. The size of the fixed Image is 200 x 200 (pixels). Lets assume that the my List<Image> contains 3 images. The positioning of the first 2 images should be next to each other and positioned at the top of the fixed Image. The third Image should be rendered on the "second" line and centered underneath the first 2 images. This pattern need to repeat itself for any number of images in the list of images. Assuming the list contains 4 images, the first 2 are rendered next to each other on the first line, and the second 2 are rendered next to each other on the second line, and so on.
Here is what I have attempted so far, but the positioning is all over the place:
Bitmap finalIcon = new Bitmap(200, 200);
Image imgFinalIcon = (Image)finalIcon;
using (Graphics g = Graphics.FromImage(imgFinalIcon))
{
int xOffSet = 0;
int item = 1;
foreach (Image icon in iconList)
{
int yOffset = 0;
if (item > 2 && (iconList.Count() % 2 != 0))
{
yOffset = imgFinalIcon.Height / 2;
}
else
{
yOffset = (imgFinalIcon.Height / 2) / 2;
}
g.DrawImage(icon, xOffSet, yOffset);
xOffSet += icon.Width;
item++;
}
}
iconList is my list of images.
Any help please?

As far as I see you don't increase the y-Offset.
Try:
yOffset += icon.height;
after you displayed the 2 images on the top or when you displayed the centered image below.
And your yOffset is local and just exists in the loop, and it all time set to 0, when a new loop interation starts. But it outside the loop.

Related

bitmap merge different amount of images together every time c#

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.

Crop white space around black image based on color

I using Imaemagick and c# and wondering:
Is it possible to crop image to border without exactly sizes?
From first to second?
First image
Second
To remove the white space around the sudoku square you can loop over the pixels. Since the image is black and white it makes it a lot easier because we can check when any of the R, G or B values drop below a certain white threshold and become black.
In this example, I'm just using an arbitrary 200 value to check.
I'm walking in from the top left and bottom right corners. This will only work if your image is always a perfect square. but you can easily adjust this code to check coordinates more accurately to meet your purposes.
using (var image = new Bitmap(Image.FromFile("firstImage.jpg")))
{
int topX = 0, topY = 0;
int bottomX = image.Width - 1, bottomY = image.Height - 1;
var color = image.GetPixel(topX, topY);
while(color.R > 200)
color = image.GetPixel(++topX, ++topY);
color = image.GetPixel(bottomX, bottomY);
while(color.R > 200)
color = image.GetPixel(--bottomX, --bottomY);
Bitmap croppedImage = new Bitmap(image);
Rectangle cropRect = new Rectangle(topX, topY, bottomX - topX + 1, bottomY - topY + 1);
croppedImage = croppedImage.Clone(cropRect, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
croppedImage.Save("firstImageNoBorder.jpg");
}
Original:
Cropped:

Plotting 2D heat map

I have a chart on which I want to plot a heat map; the only data I have is humidity and temperature, which represent a point in the chart.
How do I get the rectangular type of heat map on the chart in c#?
What I want is similar to picture below :
What I really want is a rectangular region in the chart which is plotted in different color based on the point that i get from the list of points and form the colorful section in the chart.
You have a choice of at least three ways to create a chart with colored rectangles that make up a heat map.
Here is one example
that uses/abuses a DataGridView. While I would not suggest this, the post contains a useful function that creates nice color lists to use in your task.
Then there is the option to draw the chart using GDI+ methods, namely Graphics.FillRectangle. This not hard at all but once you want to get those nice extras a Chart control offers, like scaling, axes, tooltips etc the work adds up.. See below!
So let's have a look at option three: Using the Chart control from the DataVisualization namespace.
Let's first assume that you have created a list of colors:
List<Color> colorList = new List<Color>();
And that you have managed to project your data onto a 2D array of int indices that point into the color list:
int[,] coloredData = null;
Next you have to pick a ChartType for your Series S1 There really is only one I can think of that will help:
S1.ChartType = SeriesChartType.Point;
Points are displayed by Markers. We want the DataPoints not really displayed as one of the standard MarkerTypes.
Square would be ok, if we wanted to display squares; but for rectangles it will not work well: Even if we let them overlap there will still be points at the borders that have a different size because they don't fully overlap..
So we use a custom marker by setting the MarkerImage of each point to a bitmap of a suitable size and color.
Here is a loop that adds the DataPoints to our Series and sets each to have a MarkerImage:
for (int x = 1; x < coloredData.GetLength(0); x++)
for (int y = 1; y < coloredData.GetLength(1); y++)
{
int pt = S1.Points.AddXY(x, y);
S1.Points[pt].MarkerImage = "NI" + coloredData[x,y];
}
This takes some explaining: To set a MarkerImage that is not at a path on the disk, it has to reside in the Chart's Images collection. This means is needs to be of type NamedImage. Any image will do, but it has to have a unique name string added to identify it in the NamedImagesCollection . I chose the names to be 'NI1', 'NI2'..
Obviously we need to create all those images; here is a function to do that:
void createMarkers(Chart chart, int count)
{
// rough calculation:
int sw = chart.ClientSize.Width / coloredData.GetLength(0);
int sh = chart.ClientSize.Height / coloredData.GetLength(1);
// clean up previous images:
foreach(NamedImage ni in chart1.Images) ni.Dispose();
chart.Images.Clear();
// now create count images:
for (int i = 0; i < count; i++)
{
Bitmap bmp = new Bitmap(sw, sh);
using (Graphics G = Graphics.FromImage(bmp))
G.Clear(colorList[i]);
chart.Images.Add(new NamedImage("NI" + i, bmp));
}
}
We want all markers to have at least roughly the right size; so whenever that size changes we set it again:
void setMarkerSize(Chart chart)
{
int sx = chart1.ClientSize.Width / coloredData.GetLength(0);
int sy = chart1.ClientSize.Height / coloredData.GetLength(1);
chart1.Series["S1"].MarkerSize = (int)Math.Max(sx, sy);
}
This doesn't care much about details like the InnerPlotPosition, i.e. the actual area to draw to; so here is some room for refinement..!
We call this when we set up the chart but also upon resizing:
private void chart1_Resize(object sender, EventArgs e)
{
setMarkerSize(chart1);
createMarkers(chart1, 100);
}
Let's have a look at the result using some cheap testdata:
As you can see resizing works ok..
Here is the full code that set up my example:
private void button6_Click(object sender, EventArgs e)
{
List<Color> stopColors = new List<Color>()
{ Color.Blue, Color.Cyan, Color.YellowGreen, Color.Orange, Color.Red };
colorList = interpolateColors(stopColors, 100);
coloredData = getCData(32, 24);
// basic setup..
chart1.ChartAreas.Clear();
ChartArea CA = chart1.ChartAreas.Add("CA");
chart1.Series.Clear();
Series S1 = chart1.Series.Add("S1");
chart1.Legends.Clear();
// we choose a charttype that lets us add points freely:
S1.ChartType = SeriesChartType.Point;
Size sz = chart1.ClientSize;
// we need to make the markers large enough to fill the area completely:
setMarkerSize(chart1);
createMarkers(chart1, 100);
// now we fill in the datapoints
for (int x = 1; x < coloredData.GetLength(0); x++)
for (int y = 1; y < coloredData.GetLength(1); y++)
{
int pt = S1.Points.AddXY(x, y);
// S1.Points[pt].Color = coloredData[x, y];
S1.Points[pt].MarkerImage = "NI" + coloredData[x,y];
}
}
A few notes on limitations:
The point will always sit on top of any gridlines. If you really needs those you will have to draw them on top in one of the the Paint events.
The labels as shown are referring to the integers indices of the data array. If you want to show the original data, one way would be to add CustomLabels to the axes.. See here for an example!
This should give you an idea of what you can do with a Chart control; to complete your confusion here is how to draw those rectangles in GDI+ using the same colors and data:
Bitmap getChartImg(float[,] data, Size sz, Padding pad)
{
Bitmap bmp = new Bitmap(sz.Width , sz.Height);
using (Graphics G = Graphics.FromImage(bmp))
{
float w = 1f * (sz.Width - pad.Left - pad.Right) / coloredData.GetLength(0);
float h = 1f * (sz.Height - pad.Top - pad.Bottom) / coloredData.GetLength(1);
for (int x = 0; x < coloredData.GetLength(0); x++)
for (int y = 0; y < coloredData.GetLength(1); y++)
{
using (SolidBrush brush = new SolidBrush(colorList[coloredData[x,y]]))
G.FillRectangle(brush, pad.Left + x * w, y * h - pad.Bottom, w, h);
}
}
return bmp;
}
The resulting Bitmap looks familiar:
That was simple; but to add all the extras into the space reserved by the padding will not be so easy..

Print multipaged WPF control without page breaking on a line of data C#

I made an invoice that has a listview on the bottom to list all of the line items. This can run over the one page mark so I overloaded the DocumentPaginator class to allow for me to print multiple pages. However, sometimes it cuts the page in the middle of a line of the listview. I found an article that uses a bitmap of the wpf control, then checks the color of pixels of a line to determine if there is whitespace or data (code below). When I make my control into a bitmap though, the spacing and line height is different than when I print the wpf control as an xps or to the printer. Any other ideas out there on how to intelligently break pages or get the bitmap to match the xps?
private void GetGoodCut()
{
int goodCut = _lastCut;
int lastNumber = 0;
int numberCount = 1;
// At most, it will take 32 pixel lines to find a white space
for (int i = 0; i < 32; i++)
{
int number = rowPixelWhiteCount(_bitmap, goodCut);
goodCut--;
if (number == lastNumber)
{
numberCount++;
// White space count between LV lines is 12
if (numberCount == 12)
{
lastNumber = i - 6;
break;
}
}
else
{
// If we started inside a white space, can break if starting before/at middle of the white space
if (numberCount > 5)
{
lastNumber = i - 6;
break;
}
numberCount = 1;
}
lastNumber = number;
}
_lastCut -= lastNumber;
}
private int rowPixelWhiteCount(System.Drawing.Bitmap bmp, int row)
{
int colorCount = 0;
System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
int stride = bmpData.Stride;
IntPtr firstPixelInImage = bmpData.Scan0;
unsafe
{
byte* p = (byte*)(void*)firstPixelInImage;
p += stride * row; // find starting pixel of the specified row
for (int column = 0; column < bmp.Width; column++)
{
// Printing in black/white, look for non-black pixels
byte blue = p[0];
byte red = p[1];
byte green = p[3];
if (blue > 0 && red > 0 && green > 0)
colorCount++;
// go to next pixel
p += 3;
}
}
bmp.UnlockBits(bmpData);
count.Add(colorCount);
return colorCount;
}
I believe that the secret to printing multiple pages from a WPF Application is to use multiple FixedDocument elements... you can also use your DocumentPaginator element in this process. As this is well documented online, I won't go over it all again here. Instead, please refer to the WPF Printing Part 2 – The Fixed Document page on the NBD Tech website for the full story.
As additional help, you can also refer to the Advanced WPF Part 2 of 5: Printing in Windows Presentation Foundation page on Abhishek Shukla's website.
Finally, if all else fails, you might be interested in using the code from the WPF Print Engine: Part I page on Code Project.

How to create an outline of an image if I know the background color?

I want to create a simple program that you open any given image and select 2 colors: BackgroundColor and OutlineColor. Then make an outline around the "object".
Here is my code so far:
for (int y = 1; y < height - 1; y++) //iterate trough every pixel
{ //in my bitmap
for (int x = 1; x < width - 1; x++)
{
//i want to put a pixel only if the the curent pixel is background
if (bitmap.GetPixel(x, y) != BackgroundColor)
continue;
var right = bitmap.GetPixel(x + 1, y);
var down = bitmap.GetPixel(x, y + 1);
var up = bitmap.GetPixel(x, y - 1);
var left = bitmap.GetPixel(x - 1, y);
//get the nearby pixels
var neibours = new List<Color> {up, down, left, right};
var nonBackgroundPix = 0;
//then count how many are not outline nor background color
foreach (Color neibour in neibours)
{
if (neibour != BackgroundColor && neibour != OutlineColor)
{
nonBackgroundPix++;
}
}
//finaly put an outline pixel only if there are 1,2 or 3 non bg pixels
if (nonBackgroundPix > 0 && nonBackgroundPix < 4)
{
bitmap.SetPixel(x, y, OutlineColor);
}
}
}
And here comes the problem when I run my code and input
I get
And what I want is
If you find a problem in my code, know better algorithm for doing this or manage to do it in some way just tell me. Thanks in advance guys!
The problem: You are changing the background color into a non-background color which is then being picked up by subsequent pixel checks.
The solution:
I'd suggest storing a new array with pixels "to be changed later" and then once the full image is mapped go back and set those. (Also, you can change it immediately and then add a flag to the pixel you can check for, but this is more logic you'd need to implement such as checking against a boolean array)

Categories

Resources