I've run into an issue where the SpriteBatch doesn't draw with modified Alpha of specified "Trail".
What I'm trying to do is a "fade effect" where the alpha of "Item" decreases so that it gets more transparent until it eventually gets destroyed. However it doesn't change the alpha on it?
The alpha does decrease but the alpha value of the color doesn't get modified, it stays the same color and then dissapears
Here's what happens:
http://dl.dropbox.com/u/14970061/Untitled.jpg
And this is what I'm trying to do http://dl.dropbox.com/u/14970061/Untitled2.jpg
Here's a cutout of the related code I'm using at the moment.
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend);
for (int i = 0; i < Trails.Count; i++)
{
Trail Item = Trails[i];
if (Item.alpha < 1)
{
Trails.RemoveAt(i);
i--;
continue;
}
Item.alpha -= 255 * (float)gameTime.ElapsedGameTime.TotalSeconds;
Color color = new Color(255, 0, 0, Item.alpha);
spriteBatch.Draw(simpleBullet, Item.position, color);
}
spriteBatch.End();
Don't use NonPremultiplied if you don't have to! Leave it as AlphaBlend. Read up on Premultiplied Alpha and how it was added in XNA 4.0.
The correct solution to your problem is to use the multiply operator on your colour:
Color color = Color.Red * Item.alpha/255f;
Or use the equivalent Lerp function to interpolate it to transparency:
Color color = Color.Lerp(Color.Red, Color.Transparent, Item.alpha/255f);
(Also, if you did change your blend state to non-premultiplied, to be correct you'd have to change your content import to not premultiply your textures, and ensure your content has blendable data around its transparent edges.)
Make sure that your call to spriteBatch.Begin() includes the necessary parameters:
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend);
Alpha range is between 1 (fully opaque) and 0 (fully transparent), also its a float I believe. So you are going out of bounds of its range.
edit: try decreasing it by 0.1 and if its less or equal to zero, delete it
Turns out it did work, I just used the wrong BlendState, I switched to BlendState.NonPremultiplied and now it works.
Related
I'm planning to create a square inside UI image using line renderer but size is too small that you need to zoom In. but if its outside the UI image its working. Please see attached imaged below
the line renderer component is attached to redkey1spawn object.
Tried derHugo code it works but somehow it overshoots in the screen
Your problem is that the LineRenderer works with coordinates in Unity Units.
A Screenspace Overlay canvas has pixel size scaling so the width and height (in Unity units) match up with the width and height (in Pixels) of the Window.
→ since you add 4 points
0, 0, 0
2, 0, 0
2, -2, 0
0, -2, 0
in worldspace it means they actually on the canvas will use e.g. 2px, -2px, 0px → very small.
You could e.g. multiply the sizes by the height or width of the image/canvas.
private void Start()
{
var lineRenderer = GetComponent<LineRenderer>();
var image = GetComponentInParent<RectTransform>();
// get the Unity worldspace coordinates of the images corners
// note: to get the scales like that ofcourse only works
// if the image is never rotated!
var worlsCorners = new Vector3[4];
image.GetWorldCorners(worlsCorners);
var imageWorldSize = new Vector2(Mathf.Abs(worlsCorners[0].x - worlsCorners[2].x), Mathf.Abs(worlsCorners[1].y - worlsCorners[3].y));
var positions = new Vector3[lineRenderer.positionCount];
var pointnum = lineRenderer.GetPositions(positions);
for (var i = 0; i < pointnum; i++)
{
positions[i] = positions[i] * imageWorldSize.x;
}
lineRenderer.SetPositions(positions);
}
Note, however, I'm actually not even sure you will see this LineRenderer since it is not a UI component I'm pretty sure the ScreenSpace Overlay will make every Image etc always render on top of it.
I want to create a linear gradient with 7 step colors and custom size - from black, blue, cyan, green, yellow, red to white. My problem is that the final bitmap has a black stripe on the right side. Anyone have an idea what's the matter?
public static List<Color> interpolateColorScheme(int size)
{
// create result list with for interpolated colors
List<Color> colorList = new List<Color>();
// use Bitmap and Graphics from bitmap
using (Bitmap bmp = new Bitmap(size, 200))
using (Graphics G = Graphics.FromImage(bmp))
{
// create empty rectangle canvas
Rectangle rect = new Rectangle(Point.Empty, bmp.Size);
// use LinearGradientBrush class for gradient computation
LinearGradientBrush brush = new LinearGradientBrush
(rect, Color.Empty, Color.Empty, 0, false);
// setup ColorBlend object
ColorBlend colorBlend = new ColorBlend();
colorBlend.Positions = new float[7];
colorBlend.Positions[0] = 0;
colorBlend.Positions[1] = 1 / 6f;
colorBlend.Positions[2] = 2 / 6f;
colorBlend.Positions[3] = 3 / 6f;
colorBlend.Positions[4] = 4 / 6f;
colorBlend.Positions[5] = 5 / 6f;
colorBlend.Positions[6] = 1;
// blend colors and copy them to result color list
colorBlend.Colors = new Color[7];
colorBlend.Colors[0] = Color.Black;
colorBlend.Colors[1] = Color.Blue;
colorBlend.Colors[2] = Color.Cyan;
colorBlend.Colors[3] = Color.Green;
colorBlend.Colors[4] = Color.Yellow;
colorBlend.Colors[5] = Color.Red;
colorBlend.Colors[6] = Color.White;
brush.InterpolationColors = colorBlend;
G.FillRectangle(brush, rect);
bmp.Save("gradient_debug_image_sarcus.png", ImageFormat.Png);
for (int i = 0; i < size; i++) colorList.Add(bmp.GetPixel(i, 0));
brush.Dispose();
}
// return interpolated colors
return colorList;
}
Here is my gradient:
I took your code and tried every size from 2 to ushort.MaxValue, generating the gradient and scanning from the right edge to determine how many black pixels there were.
For many sizes, there are no black pixels. However, for certain consecutive runs of sizes, as the size increases, the number of black pixels also increases. There are approximately 2140 such runs in the tested range. This implies that there is a rounding error in the gradient drawing.
This bug has been encountered before (http://www.pcreview.co.uk/threads/error-on-lineargradientbrush.2165794/). The two solutions that link recommends are to
draw the gradient larger than you need it or
use WrapMode.TileFlipX.
What that link gets wrong is that the rounding error is not just 1 pixel at all times; at large image sizes it can be as large as 127 pixels (in the range I tested). Drawing the gradient larger than you need it requires you to know (or estimate) how much bigger you need to make the gradient. You could try scaling by (size + Math.Ceiling(size / 512.0)) / size, which is an upper bound on the error for the range of image sizes I have tested.
If you're looking for a simpler solution, specifying brush.WrapMode = WrapMode.TileFlipX will cause the brush to draw normally up to the (incorrect) edge of the gradient, then repeat the gradient in reverse until the actual edge of the specified rectangle. Since the rounding error is small compared to the size of the rectangle, this will look like the final color of the gradient has been extended to the edge of the rectangle. Visually, it looks good, but it may be unsuitable if you require very precise results.
I just play around with XNA and when I wanted to click on a sprite and something happen, I put this code:
if(Mouse.GetState().LeftButton == ButtonState.Pressed)
{
if (sprite.Bounds.Contains(Mouse.GetState().X, Mouse.GetState().Y))
{
this.Exit();
}
}
how ever when I hover over the sprite with my mouse and click nothing happens, why?
And how do I fix this?
If this helps I wrote my 2D sprite in a rectangle
Texture.Bounds does not put the rectangle in the texture's place, the values of X and Y are both equal to 0.
You would have create your own rectangle to do .Contains() on based on your SpriteBatch.Draw() inputs.
The texture "logoTexture" is somewhere near the bottom left corner of the screen.
Please confirm that Bounds is calculated as such:
public Rectangle Bounds
{
get
{
return new Rectangle(position.X - width / 2, position.Y - height / 2, width, height);
}
}
I also suggest getting a reference to Mouse.GetState() a single time per update instead of calling it as needed.
I have a folder containing about 2500 PNG images, with no transparency. Every image is about 500 x 500 (some are 491 x 433, others 511 x 499 etc).
I want to programatically downsize every image to 10% of its original size, and to set the white background of every image as the transparent color.
To test the functionality of my application without resizing 2500 images every time, I used 15 images of billiard balls as a "test" folder.
Now my problem is with the following code, I get a resized and cropped PNG, whith a almost transparent background. The problem is that a white border on the left and top appears in every image viewer (Irfan View, Paint.Net and GIMP)
How can I avoid this border?
Here is the code I used for this:
void ResizeI(string[] Paths, string OutPut, Methods m, PointF Values, bool TwoCheck, bool Overwrite, float[] CropVals)
{
for (int i = 0; i < Paths.Length; i++)//Paths is the array of all images
{
string Path = Paths[i];//current image
Bitmap Oimg = (Bitmap)Bitmap.FromFile(Path);//original image
Bitmap img = new Bitmap((int)(Oimg.Width - CropVals[0] - CropVals[1]), (int)(Oimg.Height - CropVals[2] - CropVals[3]));//cropped image
Graphics ggg = Graphics.FromImage(img);
ggg.DrawImage(Oimg, new RectangleF(((float)-CropVals[0]), ((float)-CropVals[2]), Oimg.Width - CropVals[1], Oimg.Height - CropVals[3]));
ggg.Flush(System.Drawing.Drawing2D.FlushIntention.Flush);
ggg.Dispose();
PointF scalefactor = GetScaleFactor(img, Values, TwoCheck);//the scale factor equals 0.1 for 10%
Bitmap newimg = new Bitmap((int)(Math.Ceiling(((float)img.Width) * scalefactor.X)), (int)(Math.Ceiling(((float)img.Height) * scalefactor.Y)));
System.Drawing.Imaging.ImageFormat curform = img.RawFormat;
string OutPath = System.IO.Path.Combine(OutPut, System.IO.Path.GetFileName(Path));
OutPath = CheckPath(OutPath, Overwrite);//Delete if exsits
Graphics g = Graphics.FromImage(newimg);
g.InterpolationMode = GetModeFromMethod(m);//Bicubic interpolation
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
g.ScaleTransform(scalefactor.X, scalefactor.Y);
g.DrawImage(img, new Rectangle(0, 0, (int)Math.Ceiling(((float)newimg.Width) / scalefactor.X) + 1, (int)Math.Ceiling(((float)newimg.Height) / scalefactor.Y) + 1));
//g.Flush(System.Drawing.Drawing2D.FlushIntention.Flush);
newimg.MakeTransparent(Color.White);
newimg.Save(OutPath, curform);
g.Dispose();
img.Dispose();
}
}
And here is a example of the white border I mentioned. Download the image or drag it around and put a black background under it to see the border:
-- EDIT --
I managed to write this function instead of newimg.MakeTransparent(...):
void SetTransparent(ref Bitmap b)
{
for (int i = 0; i < b.Width; i++)
{
for (int ii = 0; ii < b.Height; ii++)
{
Color cc = b.GetPixel(i, ii);
int tog = cc.R + cc.G + cc.B;
float durch = 255f - (((float)tog) / 3f);
b.SetPixel(i, ii, Color.FromArgb((int)durch, cc.R, cc.G, cc.B));
}
}
}
the problem is that my billiard ball now look like this:
I can't help with the specific code, but maybe can explain what's happening.
newimg.MakeTransparent(Color.White);
This will take one color, and make it transparent. The catch is that, there's a spectrum of colors between the edge of your billiard ball (orange) and the pure white background. This is the antialiasing of the edge (which will be a blend of colors from the pure orange of the ball to the pure white of the background).
By turning only pure white transparent, you are still left with this 'halo' of white-ish colors around the object.
There's perhaps a better way to handle this using white values as an alpha mask but I'm not sure if .net's image library can handle that (I'll have to defer to someone with more .net experience comes along).
In the interim, though, what may help is if you set the transparency before you do the resize. It won't be a true fix, but might reduce the halo effect some.
UPDATE:
So, I've been thinking about this some more, and I'm not entirely sure there's a programmatic solution for creating alpha channel transparency automatically, as I have a hunch there's a lot of subjectivity involved.
Off the top of my head, this is what I came up with:
assuming the top left pixel is your 100% transparent color (we'll say pixel X).
assuming your background that you want transparent is one solid color (vs. a pattern)
assume a roughly 3px anti-aliasing
you could then...
check for neighboring pixels to X. For each neighboring pixel to X that matches the color of X, we set that 100% transparent.
if a pixel next to x is NOT the same, we could check it's relative hue.
branch from that pixel and check it's surrounding pixels.
do this marking each pixel (a, b, c, etc) until the relative hue changes a certain percentage and/or the pixel color is the same as it's neighbor (with a certain margin of variability). If it does, we'll assume we're well into the interior of the object.
now step backwards through the pixels you marked, adjusting the transparency...say c=0% b=33% a=66%
But still, that's a large oversimplification of what would really have to happen. It's making a lot of assumptions, not taking into account a patterned background, and completely ignores interior areas that need to also be transparent (such as a donut hole).
Normally in a graphics editing app, this is done via selecting blocks of the background color, feathering the edges of said selection, then turning that into an alpha max.
It's a really interesting question/problem. I, alas, don't have the answer for you but will be watching this thread with curiosity!
Your edited SetTransparent function is on the right direction, and you're almost there.
Just a slight modification you can try this:
void SetTransparent(ref Bitmap b)
{
const float selectivity = 20f; // set it to some number much larger than 1 but less than 255
for (int i = 0; i < b.Width; i++)
{
for (int ii = 0; ii < b.Height; ii++)
{
Color cc = b.GetPixel(i, ii);
float avgg = (cc.R + cc.G + cc.B) / 3f;
float durch = Math.Min(255f, (255f - avgg) * selectivity);
b.SetPixel(i, ii, Color.FromArgb((int)durch, cc.R, cc.G, cc.B));
}
}
}
The idea is that to avoid affecting the alpha value of the billard ball, you will only want to reduce the alpha for colors that are very close to zero. In other words, it is a function that rises rapidly from 0 to 255 as the color moves away from white.
This will not produce the ideal result, as #DA said, because there is some information lost (transparent pixels and non-transparent pixels being blended together near the object's edges) that is unrecoverable. To make perfectly alias-free alpha edges, the source image itself must be generated with transparency.
I load my textures using
Texture2D.FromFile()
then draw them using
spriteBatch.Draw()
But here's the point: I want to change some colors of the image to another ones. So my questions:
How to change single color of the image to another single color (eg. blue to red).
In fact, what I really want to do is changing group of colors to another group of colors. For example red and similar hues to red to blue and similar hues to blue. You can do this for example in Corel PHOTO-PAINT ("Replace Color").
Please have in mind, that I'm a beginner in XNA.
Best regards,
Jack
EDIT:
Thank you very much for help, guys. Callum's answer is very helpful indeed. But I'm wondering is there a built-in function to solve my second problem, because writing your own may be time-consuming. And I think, that kind of function may be very useful. Something like:
color.SetNewColor(Color color_from, Color color_to, int range)
That kind of function, as I've said before, is built in Corel PHOTO-PAINT. To explain it better, here is the example of what I'm talking about:
link text
So, I only set color_from, color_to and range. I think it works like that: it checks every color of the image, if it is in range of color_from, it is changed to adequate color in hue of color_to.
I assume you mean change individual pixels? In that case use the GetData() and SetData() methods of the Texture2D class.
For example, you can get an array containing the colours of the individual pixels by doing this:
// Assume you have a Texture2D called texture
Color[] data = new Color[texture.Width * texture.Height];
texutre.GetData(data);
// You now have a packed array of Colors.
// So, change the 3rd pixel from the right which is the 4th pixel from the top do:
data[4*texture.Width+3] = Color.Red;
// Once you have finished changing data, set it back to the texture:
texture.SetData(data);
Note you can use the other overloads of GetData() to select only a section.
So, to replace each pixel of a specified colour to another colour:
// Assume you have a Texture2D called texture, Colors called colorFrom, colorTo
Color[] data = new Color[texture.Width * texture.Height];
texutre.GetData(data);
for(int i = 0; i < data.Length; i++)
if(data[i] == colorFrom)
data[i] = colorTo;
texture.SetData(data);
To see if hues are similar, try this method:
private bool IsSimilar(Color original, Color test, int redDelta, int blueDelta, int greenDelta)
{
return Math.Abs(original.R - test.R) < redDelta && Math.Abs(original.G - test.G) < greenDelta && Math.Abs(original.B - test.B) < blueDelta;
}
where *delta is the tolerance of change for each colour channel that you want to accept.
To answer your edit, no there is a built in function, but you can just use a mixture of ideas from the two sections above:
Color[] data = new Color[texture.Width * texture.Height];
texutre.GetData(data);
for(int i = 0; i < data.Length; i++)
if(IsSimilar(data[i], colorFrom, range, range, range))
data[i] = colorTo;
texture.SetData(data);
Moving data between the GPU and CPU by using GetData and SetData is an expensive operation. If there are a limited number of colors, you could use a pixel shader effect when rendering to the screen. You can pass an effect to SpriteBatch.Begin:
sampler2D input : register(s0);
/// <summary>The color used to tint the input.</summary>
/// <defaultValue>White</defaultValue>
float4 FromColor : register(C0);
/// <summary>The color used to tint the input.</summary>
/// <defaultValue>Red</defaultValue>
float4 ToColor : register(C1);
/// <summary>Explain the purpose of this variable.</summary>
/// <minValue>05/minValue>
/// <maxValue>10</maxValue>
/// <defaultValue>3.5</defaultValue>
float4 main(float2 uv : TEXCOORD) : COLOR
{
float4 Color;
Color= tex2D(input , uv.xy);
if (Color.r == FromColor.r && Color.g == FromColor.g && Color.b == FromColor.b)
return ToColor;
return Color;
}
technique Technique1
{
pass Pass1
{
PixelShader = compile ps_2_0 main();
}
}
Create your effect in your LoadContent method:
colorSwapEffect = Content.Load<Effect>(#"Effects\ColorSwap");
colorSwapEffect.Parameters["FromColor"].SetValue(Color.White);
colorSwapEffect.Parameters["ToColor"].SetValue(Color.Red);
And pass the effect to your call to SpriteBatch.Begin():
sprite.Begin(0, BlendState.Opaque, SamplerState.PointWrap,
DepthStencilState.Default, RasterizerState.CullNone, colorSwapEffect);
For what you really want to do, you can swap the red and blue channels even more easily. Change your pixel shader's main() function to this, which swaps b (blue) and r (red):
float4 main(float2 uv : TEXCOORD) : COLOR
{
float4 Color;
Color= tex2D(input , uv.xy);
return float4(Color.b, Color.g, Color.r, Color.a);
}
Callum's solution is powerful and flexible.
A more limited solution that is slightly easier to implement is to leverage the spriteBatch color parameter.
The variables
Texture2D sprite; //Assuming you have loaded this somewhere
Color color = Color.Red; //The color you want to use
Vector2 position = new Vector2(0f, 0f); //the position to draw the sprite
The drawing code
//Start the spriteBatch segment, enable alpha blending for transparency
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
//Draw our sprite at the specified position using a specified color
spriteBatch.Draw(sprite, position, color);
//end the spritebatch
spriteBatch.End();
If your sprite is all white, then using this method will turn your sprite red. Also, make sure you are using a file format with transparency in it, PNG is a favorite.
Callum hit it on the head if you are changing the color of 2D images as it seems you are - but as you can see you actually need to determine the actual pixel you want to modify and edit it rather than "replace yellow with green" for example.
The same logic could be used to do this replacement (simply loop through the pixels of the image and check the color - I can say that be wary when editing textures like this though as they seemed to cause some pretty serious spikes in performance depending on what was done and how often. I didn't fully investigate but I think it was causing quite a bit of garbage collection.
this works for me:
protected override void Initialize()
{
sprite = Content.Load<Texture2D>("Parado");
Color[] data = new Color[sprite.Width * sprite.Height];
sprite.GetData(data);
// new color
Color novaCor =Color.Blue;
for (int i = 0; i < data.Length; i++)
{
// cor roxa no desenho
if (data[i].R == 142
&& data[i].G == 24
&& data[i].B == 115)
{
data[i] = novaCor;
}
}
sprite.SetData<Color>(data);
posicaoNinja = new Vector2(0, 200);
base.Initialize();
}