Similar to many programs that take a tiled map, like that in the game Terraria, and turn the map into a single picture of the entire map, I am trying to do something similar. The problem is, my block textures are in a single large texture atlas and are referenced by index, and I am having trouble taking the color data from a single block and placing it into the correct place in the larger texture.
This is my code so far.
Getting the source from the index (this code works):
public static Rectangle GetSourceForIndex(int index, Texture2D tex)
{
int dim = tex.Width / TEXTURE_MAP_DIM;
int startx = index % TEXTURE_MAP_DIM;
int starty = index / TEXTURE_MAP_DIM;
return new Rectangle(startx * dim, starty * dim, dim, dim);
}
Getting the texture at the index (Where the problems start):
public static Texture2D GetTextureAtIndex(int index, Texture2D tex)
{
Rectangle source = GetSourceForIndex(index, tex);
Texture2D texture = new Texture2D(_device, source.Width, source.Height);
Color[] colors = new Color[tex.Width * tex.Height];
tex.GetData<Color>(colors);
Color[] colorData = new Color[source.Width * source.Height];
for (int x = 0; x < source.Width; x++)
{
for (int y = 0; y < source.Height; y++)
{
colorData[x + y * source.Width] = colors[x + source.X + (y + source.Y) * tex.Width];
}
}
texture.SetData<Color>(colorData);
return texture;
}
Putting the texture into the larger picture (this is completely wrong I'm sure):
private void doSave()
{
int texWidth = this._rWidth * Region.REGION_DIM * 16;
int texHeight = this._rHeight * Region.REGION_DIM * 16;
Texture2D picture = new Texture2D(Game.GraphicsDevice, texWidth, texHeight);
Color[] pictureData = new Color[picture.Width * picture.Height];
for (int blockX = 0; blockX < texWidth / 16; blockX++)
{
for (int blockY = 0; blockY < texHeight / 16; blockY++)
{
Block b = this.GetBlockAt(blockX, blockY);
Texture2D toCopy = TextureManager.GetTextureAtIndex(b.GetIndexBasedOnMetadata(b.GetMetadataForSurroundings(this, blockX, blockY)), b.GetTextureFile());
Color[] copyData = new Color[toCopy.Width * toCopy.Height];
Rectangle source = new Rectangle(blockX * 16, blockY * 16, 16, 16);
toCopy.GetData<Color>(copyData);
for (int x = 0; x < source.Width; x++)
{
for (int y = 0; y < source.Height; y++)
{
pictureData[x + source.X + (y + source.Y) * picture.Width] = copyData[x + y * source.Width];
}
}
}
}
picture.SetData<Color>(pictureData);
string fileName = "picture" + DateTime.Now.ToString(#"MM\-dd\-yyyy-h\-mm-tt");
FileStream stream = File.Open(this.GetSavePath() + #"Pictures\" + fileName, FileMode.OpenOrCreate);
picture.SaveAsPng(stream, picture.Width, picture.Height);
I can't find any good descriptions on how to properly convert between the texture and a one dimensional color array. It would be much easier if I knew how to easily and properly place a square of colors into a larger two dimensional texture.
TL;DR: How do you put a smaller Texture into a larger texture?
Create a RenderTarget2D the size of you largest texture and set it to active. Draw the large texture then draw the smaller one. Set the reference to the original texture to the RenderTarget2D you just drew to.
int texWidth = this._rWidth * Region.REGION_DIM * 16;
int texHeight = this._rHeight * Region.REGION_DIM * 16;
_renderTarget = new RenderTarget2D(GraphicsDevice, texWidth, texHeight);
GraphicsDevice.SetRenderTarget(_renderTarget);
GraphicsDevice.Clear(Color.Transparent);
_spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque);
for (int blockX = 0; blockX < texWidth / 16; blockX++)
{
for (int blockY = 0; blockY < texHeight / 16; blockY++)
{
_spriteBatch.Draw(
TextureManager.GetTextureAtIndex(b.GetIndexBasedOnMetadata(b.GetMetadataForSurroundings(this, blockX, blockY)), b.GetTextureFile()),
new Rectangle(blockX * 16, blockY * 16, 16, 16),
Color.White);
}
}
_spriteBatch.End()
GraphicsDevice.SetRenderTarget(null);
var picture = _renderTarget;
Related
I'm trying to implement an Image Edge Detection into a WPF program.
I already have it working, but the converting of the image is quite slow.
The code is not using the slow GetPixel and SetPixel functions. But instead I'm looping through the image in some unsafe code so that I can directly access the value's using a pointer.
Before starting the Edge detection I'm also converting the image to a greyscale image to improve the edge detection speed.
But still it takes the program around 1600ms to convert an image with a size of 1920x1440 pixels, which I think could be much faster.
This is the original image:
Which is converted to this (Snapshot of the application):
This is how I'm converting the image, I'm wondering what I can do to get to some other speed improvements?
Loading the image and create a Greyscale WriteableBitmap:
private void imageData_Loaded(object sender, RoutedEventArgs e)
{
if (imageData.Source != null)
{
BitmapSource BitmapSrc = new FormatConvertedBitmap(imageData.Source as BitmapSource, PixelFormats.Gray8 /* Convert to greyscale image */, null, 0);
writeableOriginalBitmap = new WriteableBitmap(BitmapSrc);
writeableBitmap = writeableOriginalBitmap.Clone();
imageData.Source = writeableBitmap;
EdgeDetection();
}
}
Converting the Image:
private const int TOLERANCE = 20;
private void EdgeDetection()
{
DateTime startTime = DateTime.Now; //Save starting time
writeableOriginalBitmap.Lock();
writeableBitmap.Lock();
unsafe
{
byte* pBuffer = (byte*)writeableBitmap.BackBuffer.ToPointer();
byte* pOriginalBuffer = (byte*)writeableOriginalBitmap.BackBuffer.ToPointer();
for (int row = 0; row < writeableOriginalBitmap.PixelHeight; row++)
{
for (int column = 0; column < writeableOriginalBitmap.PixelWidth; column++)
{
byte edgeColor = getEdgeColor(column, row, pOriginalBuffer); //Get pixel color based on edge value
pBuffer[column + (row * writeableBitmap.BackBufferStride)] = (byte)(255 - edgeColor);
}
}
}
//Refresh image
writeableBitmap.AddDirtyRect(new Int32Rect(0, 0, writeableBitmap.PixelWidth, writeableBitmap.PixelHeight));
writeableBitmap.Unlock();
writeableOriginalBitmap.Unlock();
//Calculate converting time
TimeSpan diff = DateTime.Now - startTime;
Debug.WriteLine("Loading Time: " + (int)diff.TotalMilliseconds);
}
private unsafe byte getEdgeColor(int xPos, int yPos, byte* pOriginalBuffer)
{
byte Color;
byte maxColor = 0;
byte minColor = 255;
int difference;
//Calculate max and min value of surrounding pixels
for (int y = yPos - 1; y <= yPos + 1; y++)
{
for (int x = xPos - 1; x <= xPos + 1; x++)
{
if (x >= 0 && x < writeableOriginalBitmap.PixelWidth && y >= 0 && y < writeableOriginalBitmap.PixelHeight)
{
Color = pOriginalBuffer[x + (y * writeableOriginalBitmap.BackBufferStride)];
if (Color > maxColor) //If current pixel has higher value as previous max pixel
maxColor = Color; //Save current pixel value as max
if (Color < minColor) //If current pixel has lower value as previous min pixel
minColor = Color; //Save current pixel value as min
}
}
}
//Difference of minimum and maximum pixel with tollerance
difference = maxColor - minColor - TOLERANCE;
if (difference < 0)
difference = 0;
return (byte)difference;
}
Console Output:
Loading Time: 1599
The following code runs your algorithm on a byte array instead of the BackBuffer of a WriteableBitmap. It completes in less than 300 ms with a 1900x1200 image on my PC.
private static BitmapSource EdgeDetection(BitmapSource source)
{
var stopwatch = Stopwatch.StartNew();
var bitmap = new FormatConvertedBitmap(source, PixelFormats.Gray8, null, 0);
var width = bitmap.PixelWidth;
var height = bitmap.PixelHeight;
var originalBuffer = new byte[width * height];
var buffer = new byte[width * height];
bitmap.CopyPixels(originalBuffer, width, 0);
for (var y = 0; y < height; y++)
{
for (var x = 0; x < width; x++)
{
byte edgeColor = GetEdgeColor(originalBuffer, width, height, x, y);
buffer[width * y + x] = (byte)(255 - edgeColor);
}
}
Debug.WriteLine(stopwatch.ElapsedMilliseconds);
return BitmapSource.Create(
width, height, 96, 96, PixelFormats.Gray8, null, buffer, width);
}
private static byte GetEdgeColor(byte[] buffer, int width, int height, int x, int y)
{
const int tolerance = 20;
byte minColor = 255;
byte maxColor = 0;
var xStart = Math.Max(0, x - 1);
var xEnd = Math.Min(width - 1, x + 1);
var yStart = Math.Max(0, y - 1);
var yEnd = Math.Min(height - 1, y + 1);
for (var j = yStart; j <= yEnd; j++)
{
for (var i = xStart; i <= xEnd; i++)
{
var color = buffer[width * j + i];
minColor = Math.Min(minColor, color);
maxColor = Math.Max(maxColor, color);
}
}
return (byte)Math.Max(0, maxColor - minColor - tolerance);
}
I am writing a .Net wrapper for Tesseract Ocr and if I use a grayscale image instead of rgb image as an input file to it then results are pretty good.
So I was searching the web for C# solution to convert a Rgb image to grayscale image and I found this code.
This performs 3 operations to increase the accuracy of tesseract.
Resize the image
then convert into grayscale image and remove noise from image
Now this converted image gives almost 90% accurate results.
//Resize
public Bitmap Resize(Bitmap bmp, int newWidth, int newHeight)
{
Bitmap temp = (Bitmap)bmp;
Bitmap bmap = new Bitmap(newWidth, newHeight, temp.PixelFormat);
double nWidthFactor = (double)temp.Width / (double)newWidth;
double nHeightFactor = (double)temp.Height / (double)newHeight;
double fx, fy, nx, ny;
int cx, cy, fr_x, fr_y;
Color color1 = new Color();
Color color2 = new Color();
Color color3 = new Color();
Color color4 = new Color();
byte nRed, nGreen, nBlue;
byte bp1, bp2;
for (int x = 0; x < bmap.Width; ++x)
{
for (int y = 0; y < bmap.Height; ++y)
{
fr_x = (int)Math.Floor(x * nWidthFactor);
fr_y = (int)Math.Floor(y * nHeightFactor);
cx = fr_x + 1;
if (cx >= temp.Width)
cx = fr_x;
cy = fr_y + 1;
if (cy >= temp.Height)
cy = fr_y;
fx = x * nWidthFactor - fr_x;
fy = y * nHeightFactor - fr_y;
nx = 1.0 - fx;
ny = 1.0 - fy;
color1 = temp.GetPixel(fr_x, fr_y);
color2 = temp.GetPixel(cx, fr_y);
color3 = temp.GetPixel(fr_x, cy);
color4 = temp.GetPixel(cx, cy);
// Blue
bp1 = (byte)(nx * color1.B + fx * color2.B);
bp2 = (byte)(nx * color3.B + fx * color4.B);
nBlue = (byte)(ny * (double)(bp1) + fy * (double)(bp2));
// Green
bp1 = (byte)(nx * color1.G + fx * color2.G);
bp2 = (byte)(nx * color3.G + fx * color4.G);
nGreen = (byte)(ny * (double)(bp1) + fy * (double)(bp2));
// Red
bp1 = (byte)(nx * color1.R + fx * color2.R);
bp2 = (byte)(nx * color3.R + fx * color4.R);
nRed = (byte)(ny * (double)(bp1) + fy * (double)(bp2));
bmap.SetPixel(x, y, System.Drawing.Color.FromArgb(255, nRed, nGreen, nBlue));
}
}
//here i included the below to functions logic without the for loop to remove repetitive use of for loop but it did not work and taking the same time.
bmap = SetGrayscale(bmap);
bmap = RemoveNoise(bmap);
return bmap;
}
//SetGrayscale
public Bitmap SetGrayscale(Bitmap img)
{
Bitmap temp = (Bitmap)img;
Bitmap bmap = (Bitmap)temp.Clone();
Color c;
for (int i = 0; i < bmap.Width; i++)
{
for (int j = 0; j < bmap.Height; j++)
{
c = bmap.GetPixel(i, j);
byte gray = (byte)(.299 * c.R + .587 * c.G + .114 * c.B);
bmap.SetPixel(i, j, Color.FromArgb(gray, gray, gray));
}
}
return (Bitmap)bmap.Clone();
}
//RemoveNoise
public Bitmap RemoveNoise(Bitmap bmap)
{
for (var x = 0; x < bmap.Width; x++)
{
for (var y = 0; y < bmap.Height; y++)
{
var pixel = bmap.GetPixel(x, y);
if (pixel.R < 162 && pixel.G < 162 && pixel.B < 162)
bmap.SetPixel(x, y, Color.Black);
}
}
for (var x = 0; x < bmap.Width; x++)
{
for (var y = 0; y < bmap.Height; y++)
{
var pixel = bmap.GetPixel(x, y);
if (pixel.R > 162 && pixel.G > 162 && pixel.B > 162)
bmap.SetPixel(x, y, Color.White);
}
}
return bmap;
}
But the problem is it takes lot of time to convert it
So I included SetGrayscale(Bitmap bmap)
RemoveNoise(Bitmap bmap) function logic inside the Resize() method to remove repetitive use of for loop
but it did not solve my problem.
The Bitmap class's GetPixel() and SetPixel() methods are notoriously slow for multiple read/writes. A much faster way to access and set individual pixels in a bitmap is to lock it first.
There's a good example here on how to do that, with a nice class LockedBitmap to wrap around the stranger Marshaling code.
Essentially what it does is use the LockBits() method in the Bitmap class, passing a rectangle for the region of the bitmap you want to lock, and then copy those pixels from its unmanaged memory location to a managed one for easier access.
Here's an example on how you would use that example class with your SetGrayscale() method:
public Bitmap SetGrayscale(Bitmap img)
{
LockedBitmap lockedBmp = new LockedBitmap(img.Clone());
lockedBmp.LockBits(); // lock the bits for faster access
Color c;
for (int i = 0; i < lockedBmp.Width; i++)
{
for (int j = 0; j < lockedBmp.Height; j++)
{
c = lockedBmp.GetPixel(i, j);
byte gray = (byte)(.299 * c.R + .587 * c.G + .114 * c.B);
lockedBmp.SetPixel(i, j, Color.FromArgb(gray, gray, gray));
}
}
lockedBmp.UnlockBits(); // remember to release resources
return lockedBmp.Bitmap; // return the bitmap (you don't need to clone it again, that's already been done).
}
This wrapper class has saved me a ridiculous amount of time in bitmap processing. Once you've implemented this in all your methods, preferably only calling LockBits() once, then I'm sure your application's performance will improve tremendously.
I also see that you're cloning the images a lot. This probably doesn't take up as much time as the SetPixel()/GetPixel() thing, but its time can still be significant especially with larger images.
The easiest way would be to redraw the image onto itself using DrawImage and passing a suitable ColorMatrix. Google for ColorMatrix and gray scale and you'll find a ton of examples, this one for example: http://www.codeproject.com/Articles/3772/ColorMatrix-Basics-Simple-Image-Color-Adjustment
I could use just a little help. I am loading a png into a Texture2D, and have managed to flip it over the y axis using the following script I found. I need to flip it over the x axis now. I know a small modification should do it, but I have not managed to get the results I want.
Texture2D FlipTexture(Texture2D original){
Texture2D flipped = new Texture2D(original.width,original.height);
int xN = original.width;
int yN = original.height;
for(int i=0;i<xN;i++){
for(int j=0;j<yN;j++){
flipped.SetPixel(xN-i-1, j, original.GetPixel(i,j));
}
}
flipped.Apply();
return flipped;
}
say "pix" is a png,
Texture2D photo;
Color[] pix = photo.GetPixels(startAcross,0, 256,256);
// (256 is just an example size)
this ENTIRELY ROTATES a png 180 degrees
System.Array.Reverse(pix, 0, pix.Length);
this mirrors a PNG just around the upright axis
for(int row=0;row<256;++row)
System.Array.Reverse(pix, row*256, 256);
Texture2D FlipTexture(Texture2D original, bool upSideDown = true)
{
Texture2D flipped = new Texture2D(original.width, original.height);
int xN = original.width;
int yN = original.height;
for (int i = 0; i < xN; i++)
{
for (int j = 0; j < yN; j++)
{
if (upSideDown)
{
flipped.SetPixel(j, xN - i - 1, original.GetPixel(j, i));
}
else
{
flipped.SetPixel(xN - i - 1, j, original.GetPixel(i, j));
}
}
}
flipped.Apply();
return flipped;
}
To call it:
FlipTexture(camTexture, true); //Upside down
FlipTexture(camTexture, false); //Sideways
This flips the texture upside down:
int width = texture.width;
int height = texture.height;
Texture2D snap = new Texture2D(width, height);
Color[] pixels = texture.GetPixels();
Color[] pixelsFlipped = new Color[pixels.Length];
for (int i = 0; i < height; i++)
{
Array.Copy(pixels, i*width, pixelsFlipped, (height-i-1) * width , width);
}
snap.SetPixels(pixelsFlipped);
snap.Apply();
I'm trying to create a collision detection method for a simple XNA racing game, for which I am using this tutorial on how to extract texture data. What I'm trying to do is to check if any of the colors in that area of the texture are blue (which is the color of the walls on my racing track). However, I keep getting the error in the title. Can anyone explain to me why this happens?
code:
public bool Collision()
{
int width = arrow.Width; //arrow is the name of my "car" texture (it's an arrow)
int height = arrow.Height;
int xr = (int)x; // x is the x position of my arrow
int yr = (int)y; // y is the y position of my arrow
Color[] rawData = new Color[width * height];
Rectangle extractRegion = new Rectangle(xr, yr, width, height);
track.GetData<Color>(0, extractRegion, rawData, 0, width * height); //error occurs here
Color[,] rawDataAsGrid = new Color[height, width];
for (int row = 0; row < height; row++)
{
for (int column = 0; column < width; column++)
{
rawDataAsGrid[row, column] = rawData[row * width + column];
}
}
for (int x1 = (int)x; x1 < width; x1++)
{
for (int y1 = (int)y; y1 < height; y1++)
{
if (rawDataAsGrid[x1, y1] == Color.Blue)
{
return true;
}
}
}
return false;
}
edit: I got it working!
Your rawData is not of sufficient length to receive the data you attempt to get with the GetData() method.
Change this line:
Color[] rawData = new Color[width * height];
into:
Color[] rawData = new Color[track.Width * track.Height];
And that should do it. Hope it helps!
I have a Texture2D that I'm loading from the Content Pipeline. That's working fine, but as soon as I try to use SetData on a completely different Texture2D all of the textures in my game go completely black:
This is in my HUDMeter class, the class that I want to be just red
Texture2D colorGrad = Content.Load<Texture2D>(GradientAsset);
Color[,] pixels = new Color[colorGrad.Width, colorGrad.Height];
Color[] pixels1D = new Color[colorGrad.Width * colorGrad.Height];
pixels = GetRedChannel(colorGrad);
pixels1D = Color2DToColor1D(pixels, colorGrad.Width);
System.Diagnostics.Debug.WriteLine(pixels[32,32]);
Gradient = colorGrad;
Gradient.SetData<Color>(pixels1D);
These are using Riemers tutorial
protected Color[,] GetRedChannel(Texture2D texture)
{
Color[,] pixels = TextureTo2DArray(texture);
Color[,] output = new Color[texture.Width, texture.Height];
for (int x = 0; x < texture.Width; x++)
{
for (int y = 0; y < texture.Height; y++)
{
output[x,y] = new Color(pixels[x,y].G, 0, 0);
}
}
return output;
}
protected Color[,] TextureTo2DArray(Texture2D texture)
{
Color[] colors1D = new Color[texture.Width * texture.Height];
texture.GetData(colors1D);
Color[,] colors2D = new Color[texture.Width, texture.Height];
for (int x = 0; x < texture.Width; x++)
for (int y = 0; y < texture.Height; y++)
colors2D[x, y] = colors1D[x + y * texture.Width];
return colors2D;
}
private Color[] Color2DToColor1D (Color[,] colors, int width)
{
Color[] output = new Color[colors.Length];
for (int x = 0; x < width; x++)
{
for (int y = 0; y < colors.Length / width; y++)
{
output[x + y * width] = colors[x % width, y % (colors.Length/width)];
}
}
return output;
}
And here is the code to draw the sprites, this works fine and is how I always draw sprites:
batch.Draw(meter.Gradient, new Vector2(X, Y), Color.White);
Update:
I've actually found that the sprites that don't use the same file are not black. Does Texture2D.SetData<>() actually change the file itself? what is the use of that?
Update:
I just tried to use the Alpha as well as RGB and it's working. I'm thinking that there's something wrong with one of the conversion methods.
If you do this:
Texture2D textureA = Content.Load<Texture2D>("MyTexture");
Texture2D textureB = Content.Load<Texture2D>("MyTexture");
Both textureA and textureB refer to the same object. So if you call SetData on one of them, it will affect both of them. This is because ContentManager keeps an internal list of resources already loaded, so it doesn't have to keep reloading the same resource.
The solution would be to create a new Texture2D object of the same size, call GetData on the one loaded by ContentManager, and then SetData on the new texture.
Example (not tested):
Color[] buffer = new Color[textureA.Width * textureA.Height];
Texture2D textureB = new Texture2D(textureA.GraphicsDevice,
textureA.Width,
textureA.Height);
textureA.GetData(buffer);
textureB.SetData(buffer);
Dispose() of the new texture when you are finished with it (eg: in your Game.UnloadContent method). But never dispose of the one loaded by ContentManager (because, like I said, it is a shared object; use ContentManager.Unload instead).