I have another problem with XNA. I have a Texture2D containing many tiles. Then i have a list of rectangles which are supposed to be the bounds of each 50x50 pixels tile.
Now I would like to be able to save any tile as a small PNG-file.
Here is my code:
//declare the rectangles and spritesets
Texture2D tileSheet;
List<Rectangle> tileSet;
//load every tile, also works great
tileSheet = Content.Load<Texture2D>(#"Tiles/Object");
int noOfTilesX = (int)tileSheet.Width / 50;
int noOfTilesY = (int)tileSheet.Height / 50;
tileSet = new List<Rectangle>(noOfTilesX * noOfTilesY);
for (int j = 0; j < noOfTilesY; j++)
{
for (int i = 0; i < noOfTilesX; i++)
{
bounds = new Rectangle(i * 50, j * 50, 50, 50);
tileSet.Add(bounds);
}
}
//save as pngs if they do not exist
if (!File.Exists(#"C:\Test.png"))
{
Stream stream = File.Create(#"C:\Test.png");
//works, but it is only the complete file, i'd like to save a single tile
tileSheet.SaveAsPng(stream, tileSheet.Width, tileSheet.Height);
}
The problem is: I cannot save a single tile, it always saves my whole Texture2D and stretches it to the resolution i pass in the parameters. Searching in the internet didn't tell me anything, it just told me how to use the stream for a complete texture2D.
I just answered a question where someone wanted a new texture by taking a part of a larger texture, much like your tile-problem.
You can use this extension method and then save the result like you would do normally:
public static class TextureExtension
{
/// <summary>
/// Creates a new texture from an area of the texture.
/// </summary>
/// <param name="graphics">The current GraphicsDevice</param>
/// <param name="rect">The dimension you want to have</param>
/// <returns>The partial Texture.</returns>
public static Texture2D CreateTexture(this Texture2D src, GraphicsDevice graphics, Rectangle rect)
{
Texture2D tex = new Texture2D(graphics, rect.Width, rect.Height);
int count = rect.Width * rect.Height;
Color[] data = new Color[count];
src.GetData(0, rect, data, 0, count);
tex.SetData(data);
return tex;
}
}
Suggested usage:
using (FileStream stream = File.OpenWrite("path"))
{
tileTexture.CreateTexture(GraphicsDevice, new Rectangle(50, 50, 100, 100)).SaveAsJpeg(stream, 100, 100);
}
Try something like this. I haven't tested it so there might be some errors, but you just need to create a separate texture for each tile and then save each of them.
//declare the rectangles and spritesets
Texture2D tileSheet;
List<Rectangle> tileSet;
//load every tile, also works great
tileSheet = Content.Load<Texture2D>(#"Tiles/Object");
int noOfTilesX = (int)tileSheet.Width / 50;
int noOfTilesY = (int)tileSheet.Height / 50;
tileSet = new List<Rectangle>(noOfTilesX * noOfTilesY);
// Gets the color data of the tile sheet.
Color[] tileSheetPixels = new Color[tileSheet.Width * tileSheet.Height];
tileSheetPixels.GetData<Color>(tileSheetPixels);
for (int j = 0; j < noOfTilesY; j++)
{
for (int i = 0; i < noOfTilesX; i++)
{
bounds = new Rectangle(i * 50, j * 50, 50, 50);
tileSet.Add(bounds);
// Creates a new texture for a single tile.
Texture2D singleTile = new Texture2D(graphics, 50, 50);
Color[] pixels = new Color[50 * 50];
// Gets the pixels that correspond to the single tile and saves them in another
// color array.
for (int k = 0; k < pixels.Length; k++)
{
pixels[k] = new Color();
int x = bounds.X;
x += (k % 50);
int y = bounds.Y;
y += (k / 50);
pixels[k] = tileSheetPixels[y * 50 + x];
}
// Sets the color data of the single tile texture to the color array
// created above.
singleTile.SetData<Color>(pixels);
//save as pngs if they do not exist
if (!File.Exists(#"C:\tile_" + i + "_" + j + ".png"))
{
Stream stream = File.Create(#"C:\tile_" + i + "_" + j + ".png");
singleTile.SaveAsPng(stream, 50, 50);
}
}
}
Related
I'm new to Unity 3D and trying to split a texture2D sprite that contains an audio waveform in a Scroll Rect. The waveform comes from an audio source imported by the user and added to a scroll rect horizontally like a timeline. The script that creates the waveform works but the variable of the width (that came from another script, but this is not the problem) exceeds the limits of a Texture2D, only if I put manually a width less than 16000 the waveform appear but not to the maximum of the scroll rect. Usually, a song with 3-4min has a width of 55000-60000 width, and this can't be rendered. I need to split that waveform texture2D sprite horizontally into multiple parts (or Childs) together and render them only when appearing on the screen. How can I do that? Thank you in advance.
This creates the Waveform Sprite, and should split the sprite into multiple sprites and put together horizontally, render them only when appear on the screen):
public void LoadWaveform(AudioClip clip)
{
Texture2D texwav = waveformSprite.GetWaveform(clip);
Rect rect = new Rect(Vector2.zero, new Vector2(Realwidth, 180));
waveformImage.sprite = Sprite.Create(texwav, rect, Vector2.zero);
waveformImage.SetNativeSize();
}
This creates the waveform from an audio clip (getting from the internet and modifying for my project) :
public class WaveformSprite : MonoBehaviour
{
private int width = 16000; //This should be the variable from another script
private int height = 180;
public Color background = Color.black;
public Color foreground = Color.yellow;
private int samplesize;
private float[] samples = null;
private float[] waveform = null;
private float arrowoffsetx;
public Texture2D GetWaveform(AudioClip clip)
{
int halfheight = height / 2;
float heightscale = (float)height * 0.75f;
// get the sound data
Texture2D tex = new Texture2D(width, height, TextureFormat.RGBA32, false);
waveform = new float[width];
Debug.Log("NUMERO DE SAMPLES: " + clip.samples);
var clipSamples = clip.samples;
samplesize = clipSamples * clip.channels;
samples = new float[samplesize];
clip.GetData(samples, 0);
int packsize = (samplesize / width);
for (int w = 0; w < width; w++)
{
waveform[w] = Mathf.Abs(samples[w * packsize]);
}
// map the sound data to texture
// 1 - clear
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
tex.SetPixel(x, y, background);
}
}
// 2 - plot
for (int x = 0; x < width; x++)
{
for (int y = 0; y < waveform[x] * heightscale; y++)
{
tex.SetPixel(x, halfheight + y, foreground);
tex.SetPixel(x, halfheight - y, foreground);
}
}
tex.Apply();
return tex;
}
}
Instead of reading all the samples in one loop to populate waveform[], read only the amount needed for the current texture (utilizing an offset to track position in the array).
Calculate the number of textures your function will output.
var textureCount = Mathf.CeilToInt(totalWidth / maxTextureWidth); // max texture width 16,000
Create an outer loop to generate each texture.
for (int i = 0; i < textureCount; i++)
Calculate the current textures width (used for the waveform array and drawing loops).
var textureWidth = Mathf.CeilToInt(Mathf.Min(totalWidth - (maxTextureWidth * i), maxWidth));
Utilize an offset for populating the waveform array.
for (int w = 0; w < textureWidth; w++)
{
waveform[w] = Mathf.Abs(samples[(w + offset) * packSize]);
}
With offset increasing at the end of the textures loop by the number of samples used for that texture (ie texture width).
offset += textureWidth;
In the end the function will return an array of Texture2d instead of one.
I'm trying to build a 3D object model. But my code just has rendered a 3D model with a specific colour in the image.
How can I create a 3D object with 6 images for each surface like a Rubik cube?
This is my code, using Aspose 3D lib and C#:
private void Form1_Load(object sender, EventArgs e)
{
//Create a FBX file with embedded textures
Scene scene = new Scene();
scene.Open("BetterShirt.obj");
//Create an embedded texture
Texture tex = new Texture()
{
Content = CreateTextureContent(),
FileName = "face.png",
WrapModeU = Aspose.ThreeD.Shading.WrapMode.Wrap,
};
tex.SetProperty("TexProp", "value");
//create a material with custom property
//Aspose.ThreeD.Shading.
Material mat = scene.RootNode.ChildNodes[0].Material;
mat.SetTexture(Material.MapDiffuse, tex);
mat.SetProperty("MyProp", 1.0);
scene.RootNode.ChildNodes[0].Material = mat;
//save this to file
scene.Save("exported.obj", FileFormat.WavefrontOBJ);
}
private static byte[] CreateTextureContent()
{
using (var bitmap = new Bitmap(256, 256))
{
using (var g = Graphics.FromImage(bitmap))
{
g.Clear(Color.White);
LinearGradientBrush brush = new LinearGradientBrush(new Rectangle(0, 0, 128, 128),
Color.Moccasin, Color.Blue, 45);
using (var font = new Font(FontFamily.GenericSerif, 40))
{
g.DrawString("Aspose.3D", font, brush, Point.Empty);
}
}
using (var ms = new MemoryStream())
{
//bitmap.Save(ms, ImageFormat.Png);
return ms.ToArray();
}
}
}
build an 3D object model with 6 images
We have devised below code based on your requirements. Comments have also been added for your reference. Please try using it in your environment and then share your kind feedback with us.
private static void RubikCube()
{
Bitmap[] bitmaps = CreateRubikBitmaps();
Scene scene = new Scene();
//create a box and convert it to mesh, so we can manually specify the material per face
var box = (new Box()).ToMesh();
//create a material mapping, the box mesh generated from Box primitive class contains 6 polygons, then we can reference the material of polygon(specified by MappingMode.Polygon) by index(ReferenceMode.Index)
var materials = (VertexElementMaterial)box.CreateElement(VertexElementType.Material, MappingMode.Polygon, ReferenceMode.Index);
//and each polygon uses different materials, the indices of these materials are specified below
materials.SetIndices(new int[] {0, 1, 2, 3, 4, 5});
//create the node and materials(referenced above)
var boxNode = scene.RootNode.CreateChildNode(box);
for (int i = 0; i < bitmaps.Length; i++)
{
//create material with texture
var material = new LambertMaterial();
var tex = new Texture();
using (var ms = new MemoryStream())
{
bitmaps[i].Save(ms, ImageFormat.Png);
var bytes = ms.ToArray();
//Save it to Texture.Content as embedded texture, thus the scene with textures can be exported into a single FBX file.
tex.Content = bytes;
//Give it a name and save it to disk so it can be opened with .obj file
tex.FileName = string.Format("cube_{0}.png", i);
File.WriteAllBytes(tex.FileName, bytes);
//Dispose the bitmap since we're no longer need it.
bitmaps[i].Dispose();
}
//the texture is used as diffuse
material.SetTexture(Material.MapDiffuse, tex);
//attach it to the node where contains the box mesh
boxNode.Materials.Add(material);
}
//save it to file
//3D viewer of Windows 10 does not support multiple materials, you'll see same textures in each face, but the tools from Autodesk does
scene.Save("test.fbx", FileFormat.FBX7500ASCII);
//NOTE: Multiple materials of mesh in Aspose.3D's OBJ Exporter is not supported yet.
//But we can split the mesh with multiple materials into different meshes by using PolygonModifier.SplitMesh
PolygonModifier.SplitMesh(scene, SplitMeshPolicy.CloneData);
//following code will also generate a material library file(test.mtl) which uses the textures exported in above code.
scene.Save("test.obj", FileFormat.WavefrontOBJ);
}
private static Bitmap[] CreateRubikBitmaps()
{
Brush[] colors = { Brushes.White, Brushes.Red, Brushes.Blue, Brushes.Yellow, Brushes.Orange, Brushes.Green};
Bitmap[] bitmaps = new Bitmap[6];
//initialize the cell colors
int[] cells = new int[6 * 9];
for (int i = 0; i < cells.Length; i++)
{
cells[i] = i / 9;
}
//shuffle the cells
Random random = new Random();
Array.Sort(cells, (a, b) => random.Next(-1, 2));
//paint each face
// size of each face is 256px
const int size = 256;
// size of cell is 80x80
const int cellSize = 80;
// calculate padding size between each cell
const int paddingSize = (size - cellSize * 3) / 4;
int cellId = 0;
for (int i = 0; i < 6; i++)
{
bitmaps[i] = new Bitmap(size, size, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
using (Graphics g = Graphics.FromImage(bitmaps[i]))
{
g.Clear(Color.Black);
for (int j = 0; j < 9; j++)
{
//calculate the cell's position
int row = j / 3;
int column = j % 3;
int y = row * (cellSize + paddingSize) + paddingSize;
int x = column * (cellSize + paddingSize) + paddingSize;
Brush cellBrush = colors[cells[cellId++]];
//paint cell
g.FillRectangle(cellBrush, x, y, cellSize, cellSize);
}
}
}
return bitmaps;
}
PS: I work with Aspose as Developer Evangelist.
I am trying to add a watermark on my image, and this is the code I have for taking a screenshot. Can someone teach me how to implement watermark into my image? I want a small logo at the top right hand side of the image.
I am trying to research on maybe if I could implement what I have in the canvas to stay when a screenshot is taken ( real life ). But to no luck. Would really appreciate if someone could help me out here !
public string MakePhoto(bool openIt)
{
int resWidth = Screen.width;
int resHeight = Screen.height;
Texture2D screenShot = new Texture2D(resWidth, resHeight, TextureFormat.RGB24, false); //Create new texture
RenderTexture rt = new RenderTexture(resWidth, resHeight, 24);
// hide the info-text, if any
if (infoText)
{
infoText.text = string.Empty;
}
// render background and foreground cameras
if (backroundCamera && backroundCamera.enabled)
{
backroundCamera.targetTexture = rt;
backroundCamera.Render();
backroundCamera.targetTexture = null;
}
if (backroundCamera2 && backroundCamera2.enabled)
{
backroundCamera2.targetTexture = rt;
backroundCamera2.Render();
backroundCamera2.targetTexture = null;
}
if (foreroundCamera && foreroundCamera.enabled)
{
foreroundCamera.targetTexture = rt;
foreroundCamera.Render();
foreroundCamera.targetTexture = null;
}
// get the screenshot
RenderTexture prevActiveTex = RenderTexture.active;
RenderTexture.active = rt;
screenShot.ReadPixels(new Rect(0, 0, resWidth, resHeight), 0, 0);
// clean-up
RenderTexture.active = prevActiveTex;
Destroy(rt);
byte[] btScreenShot = screenShot.EncodeToJPG();
Destroy(screenShot);
#if !UNITY_WSA
// save the screenshot as jpeg file
string sDirName = Application.persistentDataPath + "/Screenshots";
if (!Directory.Exists(sDirName))
Directory.CreateDirectory (sDirName);
string sFileName = sDirName + "/" + string.Format ("{0:F0}", Time.realtimeSinceStartup * 10f) + ".jpg";
File.WriteAllBytes(sFileName, btScreenShot);
Debug.Log("Photo saved to: " + sFileName);
if (infoText)
{
infoText.text = "Saved to: " + sFileName;
}
// open file
if(openIt)
{
System.Diagnostics.Process.Start(sFileName);
}
return sFileName;
PS: I found this which might be useful?
public Texture2D AddWatermark(Texture2D background, Texture2D watermark)
{
int startX = 0;
int startY = background.height - watermark.height;
for (int x = startX; x < background.width; x++)
{
for (int y = startY; y < background.height; y++)
{
Color bgColor = background.GetPixel(x, y);
Color wmColor = watermark.GetPixel(x - startX, y - startY);
Color final_color = Color.Lerp(bgColor, wmColor, wmColor.a / 1.0f);
background.SetPixel(x, y, final_color);
}
}
background.Apply();
return background;
}
Select the imported image in the ProjectsView and in the inspector set TextureType to Sprite (2D and UI) (see Sprites Manual) and hit Apply
add a Sprite field for it to your class like
public Texture2D watermark;
Reference the watermark in the Inspector
You could simply add the watermark as overlay by adding the Color values from both textures for each pixel (assuming here they have the same size!)
If you want a watermark only in a certain rect of the texture you either have to scale it accordingly and use Texture2D.SetPixels(int x, int y, int blockWidth, int blockHeight, Color[] colors) (This assumes the watermark image is smaller in pixels than the screenShot!) like
private static void AddWaterMark(Texture2D texture, Texture2D watermarkTexture)
{
int watermarkWidth = watermarkTexture.width;
int watermarkHeight = watermarkTexture.height;
// In Unity differrent to most expectations the pixel corrdinate
// 0,0 is not the top-left corner but instead the bottom-left
// so since you want the whatermark in the top-right corner do
int startx = texture.width - watermarkWidth;
// optionally you could also still leave a border of e.g. 10 pixels by using
// int startx = texture.width - watermarkWidth - 10;
// same for the y axis
int starty = texture.height - watermarkHeight;
Color[] watermarkPixels = watermarkTexture.GetPixels();
// get the texture pixels for the given rect
Color[] originalPixels = texture.GetPixels(startx, starty, watermarkWidth, watermarkHeight);
for(int i = 0; i < watermarkPixels.Length; i++)
{
var pixel = watermarkPixels[i];
// adjust the alpha value of the whatermark
pixel.a *= 0.5f;
// add watermark pixel to original pixel
originalPixels[i] += pixel;
}
// write back the changed texture data
texture.SetPixels(startx, starty, watermarkWidth, watermarkHeight, originalPixels);
texture.Apply();
}
call it like
screenShot.ReadPixels(new Rect(0, 0, resWidth, resHeight), 0, 0);
AddWaterMark(screenShot, watermark);
My goal is to have a robot use this grid to create a map base off the information it collects from its surroundings. When the robot detects an object the square in front of it turns red. Currently I am stuck on how I can give each square an x and Y value for location purposes. Also when I scroll the screen the block sizes change, can someone provide help with that as well?
Rectangle rect = new Rectangle(700, 350, 50, 50);
g.DrawRectangle(myPen, rect); // Draws the Rectangle to the screen
e.Graphics.FillEllipse(myBrush, 700,350,50,50);`
for (int i = 0; i < 9900; i = i + 50)
{
rect = new Rectangle(0 + i, 0, 50, 50);
g.DrawRectangle(myPen, rect);
for (int j = 0; j < 9900; j = j + 50)
{
rect = new Rectangle(0 + i, 0 + j, 50, 50);
g.DrawRectangle(myPen, rect);
}
}
Here is a very quick example of how to do this using a 2d array. It was written in LINQPad, so it may look a little odd, but it should give you some leads. It allows you to store a map and look up values using x and y coordinates. You can use the CellInfo class to add any extra information about the cell that you need, beyond if it is blocking or not.
Ideally, you would want to wrap the entire array up in your own Map class, that abstracts away the details, and gives you a lot of helpful utility functions. For instance, if your map is extremely large, you may run out of memory. You could have the Map class only load smaller blocks of the map from files on disk as needed, or even make the map wrap around its self easily.
void Main()
{
var map = new CellInfo[10, 10];
for (int x = 0; x < 10; x++)
{
for (int y = 0; y < 10; y++)
{
map[x, y] = new CellInfo();
}
}
var rnd = new Random();
for (int i = 0; i < 20; i++)
{
map[rnd.Next(0, 10), rnd.Next(0, 10)].IsBlocked = true;
}
DrawMap(map).Dump();
}
public Bitmap DrawMap(CellInfo[,] map)
{
var img = new Bitmap(320, 320, PixelFormat.Format32bppArgb);
using (var g = Graphics.FromImage(img))
{
for (int x = 0; x < 10; x++)
{
for (int y = 0; y < 10; y++)
{
var cell = map[x, y];
Brush brush = cell.IsBlocked ? Brushes.Red : Brushes.White;
g.FillRectangle(brush, x * 32, y * 32, 31, 31);
g.DrawRectangle(Pens.Black, x * 32, y * 32, 31, 31);
}
}
}
return img;
}
public class CellInfo
{
public bool IsBlocked { get; set; } = false;
}
It produces the following output (varies each time it is run):
Im a C#/XNA student and I've recently been working on an isometric tile engine and so far it works fairly well. But im having problem trying to figure out on how to do collision, this is what my tile engine does at the moment:
Draws the world from an image and place a tile depending on what color is on my image. For instance color red would draw a grass tile. (Tiles are 64x32)
Camera following player, and my draw loop only draws what the camera sees.
This is how my game looks if that would be of any help:
I don't know what sort of collision would work best. Should i do collision points, or intersects or any other sort of collision. I've read somewhere that you could do Worldtoscreen/Screentoworld but im far to inexperienced and don't know how that works nor how the code would look like.
Here is my code drawing tiles etc:
class MapRow
{
public List<MapCell> Columns = new List<MapCell>();
}
class TileMap
{
public List<MapRow> Rows = new List<MapRow>();
public static Texture2D image;
Texture2D tileset;
TileInfo[,] tileMap;
Color[] pixelColor;
public TileMap(string TextureImage, string Tileset)
{
tileset = Game1.Instance.Content.Load<Texture2D>(Tileset);
image = Game1.Instance.Content.Load<Texture2D>(TextureImage);
pixelColor = new Color[image.Width * image.Height]; // pixelColor array that is holding all pixel in the image
image.GetData<Color>(pixelColor); // Save all the pixels in image to the array pixelColor
tileMap = new TileInfo[image.Height, image.Width];
int counter = 0;
for (int y = 0; y < image.Height; y++)
{
MapRow thisRow = new MapRow();
for (int x = 0; x < image.Width; x++)
{
tileMap[y, x] = new TileInfo();
if (pixelColor[counter] == new Color(0, 166, 81))
{
tileMap[y, x].cellValue = 1;//grass
}
if (pixelColor[counter] == new Color(0, 74, 128))
{
tileMap[y, x].cellValue = 2;//water
}
if (pixelColor[counter] == new Color(255, 255, 0))
{
tileMap[y, x].cellValue = 3;//Sand
}
tileMap[y, x].LoadInfoFromCellValue();//determine what tile it should draw depending on cellvalue
thisRow.Columns.Add(new MapCell(tileMap[y, x]));
counter++;
}
Rows.Add(thisRow);
}
}
public static int printx;
public static int printy;
public static int squaresAcross = Settings.screen.X / Tile.TileWidth;
public static int squaresDown = Settings.screen.Y / Tile.TileHeight;
int baseOffsetX = -32;
int baseOffsetY = -64;
public void draw(SpriteBatch spriteBatch)
{
printx = (int)Camera.Location.X / Tile.TileWidth;
printy = (int)Camera.Location.Y / Tile.TileHeight;
squaresAcross = (int)Camera.Location.X / Tile.TileWidth + Settings.screen.X / Tile.TileWidth;
squaresDown = 2*(int)Camera.Location.Y / Tile.TileHeight + Settings.screen.Y / Tile.TileHeight + 7;
for (printy = (int)Camera.Location.Y / Tile.TileHeight; printy < squaresDown; printy++)
{
int rowOffset = 0;
if ((printy) % 2 == 1)
rowOffset = Tile.OddRowXOffset;
for (printx = (int)Camera.Location.X / Tile.TileWidth; printx < squaresAcross; printx++)
{
if (tileMap[printy, printx].Collides(MouseCursor.mousePosition))
Console.WriteLine(tileMap[printy, printx].tileRect);
foreach (TileInfo tileID in Rows[printy].Columns[printx].BaseTiles)
{
spriteBatch.Draw(
tileset,
tileMap[printy, printx].tileRect = new Rectangle(
(printx * Tile.TileStepX) + rowOffset + baseOffsetX,
(printy * Tile.TileStepY) + baseOffsetY,
Tile.TileWidth, Tile.TileHeight),
Tile.GetSourceRectangle(tileID.cellValue),
Color.White,
0.0f,
Vector2.Zero,
SpriteEffects.None,
tileID.drawDepth);
}
}
}
}
}
Why don't you just draw stuff just like in normal tile based games, and then rotate the camera with a 45degree? Of course then you'd need to make your graphics a bit odd, but would be easier to handle the tiles.
But if you prefer your way, then I'd suggest using simple math to calculate the "tile to the right", "tile to the left" , "tile to the up" and "tile to the down" ones, you know, the tiles around the player(or another tile). You can simply work with your lists, and with some math, basic math, like getting the next tile, is quite simple.
Edit:
You could get the player's next position's tile value with a code something like this:
tileMap[Math.Floor((player.y+playerVelociy.Y)/tileHeight)]
[Math.Floor((player.x+playerVelocity.X)/tileWidth)]
In this code, I assume that the first tile is at 0,0 and you're drawing to right and down. (If not, then just change the Math.Floor to Math.Ceil)
THIS link could help you get the idea, however it's in AS3.0, only the syntax is different.