I'm coming from Winforms trying to rewrite a program in WPF, and I want to display a certain portion of the whole image, depending on an Id I use for a list, that I load each portion of the whole image in. I was able to do it successfully in Winforms, but I want to perform the same task in WPF using Controls.Image. Heres what I did in Winforms.
PictureBox picBox;
List<Image> tileImageList;
Image FullImage;
public TileFrame(PictureBox pbox)
{
picBox = pbox;
FullImage = picBox.Image; //The source of the picBox is set to the full image on init
tileImageList = new List<Image>();
PopTileList();
}
void PopTileList()
{
const int SIZE = 32;
Bitmap bitFullImage = new Bitmap(FullImage);
for (int y = 0; y < 48; y++)
{
for (int x = 0; x < 64; x++)
{
var portion = bitFullImage.Clone(new Rectangle((x * SIZE), (y * SIZE), SIZE, SIZE), bitFullImage.PixelFormat);
tileImageList.Add(portion);
}
}
picBox.Image = tileImageList[10];//The first image that shows when this is done
}
public void ShowTilePic(int selectedId)
{
picBox.Image = tileImageList[--selectedId];
}
Since the image being displayed will change based on the selected item of a listbox, the tileImageList is crucial for relating the list box selected index and the tileImageList index. Other answers I've searched for seemed to make it much more complicated than what I've done here. Is there a simple way to do this in WPF and in code?
Nvm I figured it out.
List<CroppedBitmap> tileImageList;
Image imageBox;
public TileFrame(MainWindow mWindow)
{
tileImageList = new List<CroppedBitmap>();
imageBox = mWindow.ImageBox;
PopTileList();
}
void PopTileList()
{
const int SIZE = 32;
var bitmapImage = (BitmapSource)imageBox.Source;
for (int y = 0; y < 48; y++)
{
for (int x = 0; x < 64; x++)
{
var portion = new CroppedBitmap(bitmapImage, new Int32Rect((x * SIZE), (y * SIZE), SIZE, SIZE));
tileImageList.Add(portion);
}
}
}
public void ShowTilePic(int selectedId)
{
imageBox.Source = tileImageList[selectedId];
}
Related
I'd like to figure out how I can add a 1 pixel border in between each frame that my spritesheet creator generates, can anyone help?
The function below takes an array of images and turns them into a table filled with bitmaps.
The program is supposed to take a gif and turn it into multiple spritesheets, so that it can be displayed in a game engine. That part is working fine, but I'd like to add a 1 pixel border in between each frame.
public Bitmap[] combineFrames(Image[] images, int columns)
{
int rows = columns;
int imagesPerSheet = rows * columns;
int sheets = (int)Math.Ceiling((double)images.Length / imagesPerSheet);
Bitmap[] mapTable = new Bitmap[55]; //55 being the max #sheets
for (int x = 0; x < sheets; x++)
{
Bitmap bitmap = new Bitmap(images[0].Width * columns, images[0].Height * rows);
Graphics graphics = Graphics.FromImage(bitmap);
int L = 0;
int remainingNFrames = (images.Length - (imagesPerSheet * x));
if (remainingNFrames < imagesPerSheet)
{
L = remainingNFrames;
}
else
{
L = imagesPerSheet;
}
for (int i = 0; i <= L; i++)
{
int formulizedProduct = imagesPerSheet * (x) + (i);
if (formulizedProduct == images.Length) { break; } //should always be length - 1
Image image = images[formulizedProduct]; //16*x+i
int X = (image.Width * ((i - 1) % columns));
int Y = (image.Height * (int)((double)(i - 1) / columns));
graphics.DrawImage(
image,
X, //I feel like I'd have to adjust this part and the part below, but I'm not really sure.
Y
);
}
mapTable[x] = bitmap;
}
return mapTable;
}
Thanks.
I'm currently working on a project which is digitalization of analog radar. I have a PCI card which acquires data that I use. I need to do a PPI indicator, like on the picture radar PPI
But I have a problem: The data display is very slow. I'm currently displaying data point-by-point (if signal is detected it'll draw a small rectangle at the calculated position). The azimuth discrete is 0,1° so that's 3600 discretes of angles and the radius is 500 discretes, so that makes it 3600*500 = 1 800 000 points max. Which is a lot.
Here is the part of the code where display of data is done:
private void kreslenie()
{
for (int i = 0; i < bufferCH1.Length - 1; i++)
{
if (bufferCH0[i] > 4000)
{
if (azimutR >= 0 && azimutR <= Math.PI)
{
surX = (int)(sx + (i) * azimutX);
surY = (int)(sy - (i) * azimutY);
}
else
{
surX = (int)(sx - (i) * azimutX);
surY = (int)(sy - (i) * azimutY);
}
this.CreateGraphics().DrawRectangle(p, surX, surY, 1, 1);
}
}
}
Is there any other way to display such big amount of points in real-time (or near real-time)
One thing you could try is using a PictureBox control, and dynamically generating a Bitmap to display. You can do this by maintaining an array of the raw pixel data and updating it when information you want to display changes. Use the Bitmap.LockBits method to get a pointer to the raw image data, and then use Marshal.Copy to update it with the array of image data you are maintaining. Here is rough example that just randomly generates an image with points filled at a given location.
Edited:
To draw shapes you could just create a second array of image data for the background. You can create a Bitmap the same size as the other image, and draw to it using GDI. Next, save the background image as a byte array. When you generate your whole image, you first set it to the background image, and then draw your points over the background.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace PointDrawTest
{
public partial class Form1 : Form
{
public struct BoundingArea
{
public int x;
public int y;
public int width;
public int height;
}
public struct Point
{
public int x;
public int y;
}
int bytesPerPixel;
byte[] backgroundImageRgbData;
byte[] imageRgbData;
private Bitmap displayImage;
private BoundingArea area;
private List<Point> points = new List<Point>();
public Form1()
{
InitializeComponent();
LoadPoints();
}
private void LoadPoints()
{
this.points = new List<Point>();
this.area = new BoundingArea();
this.area.x = 0;
this.area.y = 0;
this.area.width = 1200;
this.area.height = 800;
displayImage = new Bitmap(this.area.width, this.area.height, PixelFormat.Format24bppRgb);
//There are three bytes per pixel in format PixelFormat.Format24bppRgb
bytesPerPixel = 3;
//Rgb byte data for the image
int rgbDataSize = this.area.width * this.area.height * bytesPerPixel;
imageRgbData = new byte[rgbDataSize];
GenerateRandomPoints();
GenerateBackgroundImage();
UpdateBitmap();
}
private void GenerateBackgroundImage()
{
Bitmap backgroundImage = new Bitmap(this.area.width, this.area.height, PixelFormat.Format24bppRgb);
Graphics g = Graphics.FromImage(backgroundImage);
int gridSize = 40;
int rowCount = this.area.height / gridSize;
int columnCount = this.area.width / gridSize;
//Set background color to white
g.Clear(System.Drawing.Color.White);
int penWidth = 1;
Pen linePen = new Pen(System.Drawing.Color.Gray, penWidth);
//Draw horizontal lines
for (int i = 0; i < rowCount; i++)
{
float y = i * gridSize;
g.DrawLine(linePen, this.area.x, y, this.area.x + this.area.width, y);
}
//Draw vertical lines
for (int i = 0; i < columnCount; i++)
{
float x = i * gridSize;
g.DrawLine(linePen, x, this.area.y, x, this.area.y + this.area.height);
}
//Get rgb data from drawn background image and save it to array
var backgroundData = backgroundImage.LockBits(new Rectangle(this.area.x, this.area.y, this.area.width, this.area.height),
ImageLockMode.ReadWrite,
backgroundImage.PixelFormat);
IntPtr ptrFirstPixel = backgroundData.Scan0;
int rgbDataSize = this.area.width * this.area.height * bytesPerPixel;
backgroundImageRgbData = new byte[rgbDataSize];
Marshal.Copy(ptrFirstPixel, backgroundImageRgbData, 0, backgroundImageRgbData.Length);
backgroundImage.UnlockBits(backgroundData);
}
private void GenerateRandomPoints()
{
int pointCount = 100000;
var r = new Random();
for (int i = 0; i < pointCount; i++)
{
int pointX = r.Next(this.area.x, this.area.x + this.area.width);
int pointY = r.Next(this.area.y, this.area.y + this.area.height);
points.Add(new Point() { x = pointX, y = pointY });
}
}
private void UpdateBitmap()
{
var bmpData = this.displayImage.LockBits(new Rectangle(this.area.x, this.area.y, this.area.width, this.area.height),
ImageLockMode.ReadWrite,
this.displayImage.PixelFormat);
IntPtr ptrFirstPixel = bmpData.Scan0;
//Set image array to default background image
for (int i = 0; i < imageRgbData.Length; i++)
{
imageRgbData[i] = backgroundImageRgbData[i];
}
Color pixelColor = System.Drawing.Color.LightGreen;
for (int i = 0; i < this.points.Count; i++)
{
Point p = this.points[i];
int bitmapRgbIndex = (p.y * this.area.width + p.x) * bytesPerPixel;
//Apply color at a specific pixel
imageRgbData[bitmapRgbIndex] = pixelColor.B;
imageRgbData[bitmapRgbIndex + 1] = pixelColor.G;
imageRgbData[bitmapRgbIndex + 2] = pixelColor.R;
}
//Copy your bitmap byte array to the image
Marshal.Copy(imageRgbData, 0, ptrFirstPixel, imageRgbData.Length);
this.displayImage.UnlockBits(bmpData);
pbDisplay.Image = this.displayImage;
}
}
}
So what I'm trying to do is create like a random image from panels of different colors. The user can choose how many panels (i.e. pixels) he wants to have and the number of different colors and then the program automatically generates that image. I'd really like to use panels for this because I will need this picture later on and need to modify every single pixel. As I'm comfortable with panels, I'd like to keep them and not use anything else.
So here's the code I'm using to create this panels:
//Creates two lists of panels
//Add items to list so that these places in the list can be used later.
//nudSizeX.Value is the user-chosen number of panels in x-direction
for (int a = 0; a < nudSizeX.Value; a++)
{
horizontalRows.Add(null);
}
//nudSizeY.Value is the user-chosen number of panels in y-direction
for (int b = 0; b < nudSizeY.Value; b++)
{
allRows.Add(null);
}
for (int i = 0; i < nudSizeY.Value; i++)
{
for (int j = 0; j < nudSizeX.Value; j++)
{
// new panel is created, random values for background color are assigned, position and size is calculated
//pnlBack is a panel used as a canvas on whoch the other panels are shown
Panel pnl = new Panel();
pnl.Size = new System.Drawing.Size((Convert.ToInt32(pnlBack.Size.Width)) / Convert.ToInt32(nudSizeX.Value), (Convert.ToInt32(pnlBack.Size.Height) / Convert.ToInt32(nudSizeY.Value)));
pnl.Location = new Point(Convert.ToInt32((j * pnl.Size.Width)), (Convert.ToInt32((i * pnl.Size.Height))));
//There are different types of panels that vary in color. nudTypesNumber iis the user-chosen value for howmany types there should be.
int z = r.Next(0, Convert.ToInt32(nudTypesNumber.Value));
//A user given percentage of the panels shall be free, i.e. white.
int w = r.Next(0, 100);
if (w < nudPercentFree.Value)
{
pnl.BackColor = Color.White;
}
//If a panel is not free/white, another rendom color is assigned to it. The random number determinig the Color is storede in int z.
else
{
switch (z)
{
case 0:
pnl.BackColor = Color.Red;
break;
case 1:
pnl.BackColor = Color.Blue;
break;
case 2:
pnl.BackColor = Color.Lime;
break;
case 3:
pnl.BackColor = Color.Yellow;
break;
}
}
//Every panel has to be added to a list called horizontal rows. This list is later added to a List<List<Panel>> calles allRows.
horizontalRows[j] = (pnl);
//The panel has also to be added to the "canvas-panel" pnl back. The advantage of using the canvas panel is that it is easier to determine the coordinates on this panel then on the whole form.
pnlBack.Controls.Add(pnl);
}
allRows[i] = horizontalRows;
}
As you might imagine, this is very slow when creating a checkerboard of 99x99 because the program has to loop through the process nearly 10000 times.
What would you to to improve performance? I said I'd like to keep doing it with panels because I'm comfortable with them, but if using panels is even more dumb than I thought, I'm open to other options. The program gets slower and slower the more panels it has already created. I guess that's because of the adding to the list that grows larger and larger?
This is how the output looks right now:
This is what I want to do with my "picture" later: I basically want to do Schellings model. That model shows how different groups of people (i.e. different colors) segregate when they want to have a certain percentage of people around them that belong to their group. That means that later on I have to be able to check for each of the panels/pixels what the neighbours are and have to be able to be able to change color of each pixel individually.
I don't want a ready solution, I'm just hoping for tips how to improve the speed of the picture-creating process.
Thank you very much
Instead of using Panels use a matrix to store your colors and other information you need.
In OnPaint event, use this matrix to draw the rectangles using GDI+.
Here is an example on how to draw 10x10 "pixels" if you have a matrix that contains colors:
private void myPanel_Paint(object sender, PaintEventArgs e)
{
for (var y=0; y < matrix.GetUpperBound(0); y++)
for (var x=0; x < matrix.GetUpperBound(1); x++)
{
var Brush = new SolidBrush(matrix[y,x]);
e.Graphics.FillRectangle(Brush, new Rectangle(x*10, y*10, 10, 10));
}
}
Use a picturebox to do your drawing. You've already got the code to see where each panel should be, just change it to draw a rectangle at each position. This way, you'll just be drawing a few rectangles on a board instead of working with 10.000 GUI objects.
Oh, keep your model/logic and view separated. Keep one matrix with all your information and just use a "Paint method" to draw it.
Your model could look something like this:
MyPanel[,] panels;
class MyPanel
{
Color color;
}
This way it's easy to check all neighbours of a panel, just check in the panels matrix.
And your view should just do something like this:
class View
{
Paint(MyPanel[,] panels)
{
//Draw
}
}
I think your best approach here is to write a custom Control class to draw the squares, and a custom collection class to hold the squares.
Your square collection class could look like this:
public sealed class ColouredSquareCollection
{
readonly int _width;
readonly int _height;
readonly Color[,] _colours;
public ColouredSquareCollection(int width, int height)
{
_width = width;
_height = height;
_colours = new Color[_width, _height];
intialiseColours();
}
public Color this[int x, int y]
{
get { return _colours[x, y]; }
set { _colours[x, y] = value; }
}
public int Width
{
get { return _width; }
}
public int Height
{
get { return _height; }
}
void intialiseColours()
{
for (int y = 0; y < _height; ++y)
for (int x = 0; x < _width; ++x)
_colours[x, y] = Color.White;
}
}
Then you write a custom control. To do so, add a new Custom control via Add new item -> Windows Forms -> Custom Control, and call it ColouredSquareHolder.
Then change the code to look like this. Notice how it is responsible for drawing all the squares:
public sealed partial class ColouredSquareHolder: Control
{
ColouredSquareCollection _squares;
public ColouredSquareHolder()
{
ResizeRedraw = true;
DoubleBuffered = true;
InitializeComponent();
}
public ColouredSquareCollection Squares
{
get
{
return _squares;
}
set
{
_squares = value;
Invalidate(); // Redraw after squares change.
}
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
if (_squares == null)
return;
int w = Width;
int h = Height;
int nx = _squares.Width;
int ny = _squares.Height;
var canvas = pe.Graphics;
for (int yi = 0; yi < ny; ++yi)
{
for (int xi = 0; xi < nx; ++xi)
{
int x1 = (xi*w)/nx;
int dx = ((xi + 1)*w)/nx - x1;
int y1 = (yi*h)/ny;
int dy = ((yi+1)*h)/ny - y1;
using (var brush = new SolidBrush(_squares[xi, yi]))
canvas.FillRectangle(brush, x1, y1, dx, dy);
}
}
}
}
Now you'll need to set up the square collection, add it to a ColouredSquareHolder and then add that to a form.
Firstly, add the ColouredSquareHolder to your test program and compile it so that it will show up in the Toolbox for the Windows Forms Editor.
Then create a new default Form called Form1, and from the Toolbox add a ColouredSquareHolder to it, and set the ColouredSquareHolder to Dock->Fill. Leave it called the default colouredSquareHolder1 for this demonstration.
Then change your Form1 class to look like this:
public partial class Form1: Form
{
readonly ColouredSquareCollection _squares;
readonly Random _rng = new Random();
public Form1()
{
InitializeComponent();
_squares = new ColouredSquareCollection(100, 100);
for (int x = 0; x < _squares.Width; ++x)
for (int y = 0; y < _squares.Height; ++y)
_squares[x, y] = randomColour();
colouredSquareHolder1.Squares = _squares;
}
Color randomColour()
{
return Color.FromArgb(_rng.Next(256), _rng.Next(256), _rng.Next(256));
}
}
Run your program and see how much faster it is at drawing the squares.
Hopefully this will give you the basis for something that you can build on.
Note: If you change the colours in the square collection, you will need to call .Invalidate() on the control in the form to make it redraw with the new colours.
well I suggest you using GDI+ instead , you can store your colors in a 2 dimensional array so you can draw the desired image based on that and also you can loop through them for further process , take a look at this code and also the demo project :
as you mentioned that you're not familiar with gdi+ , there is a demo project included so you can check it yourself and see how It's done in gdi+ :
demo project : ColorsTableDemoProject
Color[,] colorsTable;
Bitmap b;
Graphics g;
int size = 80; // size of table
int pixelWidth = 5; // size of each pixel
Random r = new Random();
int rand;
// CMDDraw is my Form button which draws the image
private void CMDDraw_Click(object sender, EventArgs e)
{
colorsTable = new Color[size, size];
pictureBox1.Size = new Size(size * pixelWidth, size * pixelWidth);
b = new Bitmap(size * pixelWidth, size * pixelWidth);
g = Graphics.FromImage(b);
for (int y = 0; y < size; y++)
{
for (int x = 0; x < size; x++)
{
rand = r.Next(0, 4);
switch (rand)
{
case 0: colorsTable[x, y] = Color.White; break;
case 1: colorsTable[x, y] = Color.Red; break;
case 2: colorsTable[x, y] = Color.Blue; break;
case 3: colorsTable[x, y] = Color.Lime; break;
default: break;
}
g.FillRectangle(new SolidBrush(colorsTable[x, y]), x * pixelWidth, y * pixelWidth, pixelWidth, pixelWidth);
}
}
pictureBox1.Image = b;
}
In C#, I need to convert an image that I have already converted to Bitmap in to a matrix of the size of the image's width and height that consists of the uint8 of the Bitmap data. In another word placing the Bitmap data inside of a matrix and converting them to uint8, so I can do the calculations that I am intended to do on the matrix rows and column.
Try something like this:
public Color[][] GetBitMapColorMatrix(string bitmapFilePath)
{
bitmapFilePath = #"C:\9673780.jpg";
Bitmap b1 = new Bitmap(bitmapFilePath);
int hight = b1.Height;
int width = b1.Width;
Color[][] colorMatrix = new Color[width][];
for (int i = 0; i < width; i++)
{
colorMatrix[i] = new Color[hight];
for (int j = 0; j < hight; j++)
{
colorMatrix[i][j] = b1.GetPixel(i, j);
}
}
return colorMatrix;
}
What I am trying to do is check an image row or column, and if it contains all white pixels then trim that row or column.
I am not sure why, but when I run this code snippet, img.Width in TrimLeft is 1, before subtracting 1 from it.
The actual image width is 175. I end up with an ArgumentException. I have a similar method for trimming the right side, and that works fine.
class ImageHandler
{
public Bitmap img;
public List<int[]> pixels = new List<int[]>();
public ImageHandler(String path)
{
img = new Bitmap(path);
GetPixels();
}
public void TrimLeft()
{
while (CheckColIfWhite(0, 0))
{
Rectangle cropBox = new Rectangle(1, 0, (img.Width-1), img.Height);
Bitmap cropdImg = CropImage(img, cropBox);
img = cropdImg;
}
}
public bool CheckColIfWhite(int colx, int starty)
{
bool allPixelsWhite = false;
int whitePixels = 0;
for (int y = starty; y < img.Height; y++)
{
if (pixels[y][colx] >= 200) { whitePixels++; }
else { return false; }
if (whitePixels == img.Height) { allPixelsWhite = true; }
}
return allPixelsWhite;
}
public void GetPixels()
{
for (int y = 0; y < img.Height; y++)
{
int[] line = new int[img.Width];
for (int x = 0; x < img.Width; x++)
{
line[x] = (int) img.GetPixel(x, y).R;
}
pixels.Add(line);
}
}
public Bitmap CropImage(Bitmap tImg, Rectangle area)
{
Bitmap bmp = new Bitmap(tImg);
Bitmap bmpCrop = bmp.Clone(area, bmp.PixelFormat);
return bmpCrop;
}
}
Your method seems like it will be remarkably inefficient - if the image is 175 pixels wide, and entirely white, you're going to create 175 copies of it, before (probably) failing when trying to create a 0 pixel wide image.
Why not examine each column in turn until you find a non-white column, and then perform a single crop at that time. Untested code, and with other changes hopefully obvious:
public Bitmap CropImage (Bitmap image)
{
int top = 0;
int bottom = image.Height-1;
int left = 0;
int right = image.Width-1;
while(left < right && CheckColIfWhite(image,left))
left++;
if(left==right) return null; //Entirely white
while(CheckColIfWhite(image,right)) //Because left stopped, we know right will also
right--;
while(CheckRowIfWhite(image,top))
top++;
while(CheckRowIfWhite(image,bottom))
bottom--;
return CropImage(image,new Rectangle(left,top,right-left+1,bottom-top+1));
}
(E.g. I'm now passing the image around, I've modified CheckColIfWhite and CheckRowIfWhite to take the image also, and to assume that one parameter is always fixed at 0)
Also, not sure why you're extracting the pixel array beforehand, so I'm putting my re-written CheckColIfWhite too:
public bool CheckColIfWhite(Bitmap image,int colx)
{
for (int y = 0; y < image.Height; y++)
{
if (image.GetPixel(colx,y).R < 200)
return false;
}
return true;
}