I'm using the following code to produce a vignette effect on an image. As you can see below it works quite well. I'd like to be able to adjust the inner spread of the vignette (i.e make the middle brighter and shorten the gradient) however the maths have got the better of me. Could anyone please give me some pointers with an explanation?
protected override void Apply(ImageBase target,
ImageBase source,
Rectangle targetRectangle,
Rectangle sourceRectangle,
int startY, int endY)
{
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
Color color = this.Color;
Vector2 centre = Rectangle.Center(targetRectangle);
float rX = this.RadiusX > 0 ? this.RadiusX : targetRectangle.Width / 2f;
float rY = this.RadiusY > 0 ? this.RadiusY : targetRectangle.Height / 2f;
float maxDistance = (float)Math.Sqrt(rX * rX + rY * rY);
Parallel.For(
startY,
endY,
y =>
{
for (int x = startX; x < endX; x++)
{
float distance = Vector2.Distance(centre, new Vector2(x, y));
Color sourceColor = target[x, y];
target[x, y] = Color.Lerp(sourceColor,
color, .9f * distance / maxDistance);
}
});
}
Original Image
Vignette Effect
Related
I am trying to do a digging tool for my game, I have x and y coordinates of point A and B, what I want to do is create a curve between these points, nothing graphical I just need loop through the coordinates (float x, float y).
I am not good at explaining so here is a visual example;
The first image is what's happen if I just use a for loop to decrease the y value until middle and then increase it from the middle to end.
//Very specific code for my example
//I wrote it just for this example so I am not sure if it works
float y;
float x;
public void Example(float startX, float endX, float startY, float endY, float depth)
{
y = startY;
x = startX;
float changeAmountOfY = depth / (endX - startX);
for (int i = (int)startX; i < (startX + endX) / 2; i++)
{
x++;
y -= changeAmountOfY;
}
for (int i = (int)(startX + endX) / 2; i < endX; i++)
{
x++;
y += changeAmountOfY;
}
}
public void ChangeCoordinates()
{
Example(100f, 200f, 100f, 100f, 50f);
}
The second image is what I need.
I am developing the game on unity and I am using Vector2 for the coordinates but it is not important.
Pure C# or even C++ is welcome.
It is also fine if someone can just explain the math behind what I am trying to do.
Maybe this can help:
// Calculate radius
int radius = (B.X - A.X) / 2;
// Calculate middle
int middle_x = A.X + radius;
int middle_y = A.Y;
// or
int middle_y = (A.Y + B.Y) / 2;
// Coordinates for a semicircle
// 0 to 180 degree
for (int i = 0; i <= 180; i++)
{
double x_coordinate = middle_x + radius * Math.Cos(i * Math.PI / 180);
// Opened to bottom
double y_coordinate = middle_y + radius * Math.Sin(i * Math.PI / 180);
// or opened to top
double y_coordinate = middle_y - radius * Math.Sin(i * Math.PI / 180);
}
Take a look at unit circle.
I'm attempting to write a matrix transform to convert chart points to device pixels in SkiaSharp. I have it functional as long as I use 0,0 as my minimum chart coordinates but if I need to to step up from a negative number, it causes the drawing to shift left and down. That is to say that the X Axis is shifted to the left off the window and the Y Axis is shift down off the window.
This is intended to be a typical line chart (minimum chart point at the lower left while minimum device point at the upper left). I have accounted for that already in the transform.
While stepping through code I can see that the coordinates returned from the Matrix are not what I expect them to be, so I believe the issue to be with my transform but I haven't been able to pinpoint it.
UPDATE: After further examination, I believe I was mistaken, it is not shifted, it's just not scaling properly to the max end of the screen. There is a bigger margin at the top and right side of the chart than there should be, but the bottom and left side are fine. I've been undable to determine why the scaling doesn't fill the canvas.
Below are my matrix methods:
private SKMatrix ChartToDeviceMatrix, DeviceToChartMatrix;
private void ConfigureTransforms(SKPoint ChartMin,
SKPoint ChartMax, SKPoint DeviceMin, SKPoint DeviceMax)
{
this.ChartToDeviceMatrix = SKMatrix.MakeIdentity();
float xScale = (DeviceMax.X - DeviceMin.X) / (ChartMax.X - ChartMin.X);
float yScale = (DeviceMin.Y - DeviceMax.Y) / (ChartMax.Y - ChartMin.Y);
this.ChartToDeviceMatrix.SetScaleTranslate(xScale, yScale, DeviceMin.X, DeviceMax.Y);
this.ChartToDeviceMatrix.TryInvert(out this.DeviceToChartMatrix);
}
// Transform a point from chart to device coordinates.
private SKPoint ChartToDevice(SKPoint point)
{
return this.ChartToDeviceMatrix.MapPoint(point);
}
The code invoking this is:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
float strokeWidth = 1;
float margin = 10;
// SKPaint definitions omitted for brevity.
var ChartMin = new SKPoint(-10, -1); // Works fine if I change this to 0,0
var ChartMax = new SKPoint(110, 11);
var DeviceMin = new SKPoint(margin, margin);
var DeviceMax = new SKPoint(info.Width - margin, info.Height - margin);
const float stepX = 10;
const float stepY = 1;
const float tickX = 0.5;
const float tickY = 0.075F;
// Prepare the transformation matrices.
this.ConfigureTransforms(ChartMin, ChartMax, DeviceMin, DeviceMax);
// Draw the X axis.
var lineStart = new SKPoint(ChartMin.X, 0);
var lineEnd = new SKPoint(ChartMax.X, 0);
canvas.DrawLine(this.ChartToDevice(lineStart), this.ChartToDevice(lineEnd), axisPaint);
// X Axis Tick Marks
for (float x = stepX; x <= ChartMax.X - stepX; x += stepX)
{
var tickMin = new SKPoint(x, -tickY);
var tickMax = new SKPoint(x, tickY);
canvas.DrawLine(this.ChartToDevice(tickMin), this.ChartToDevice(tickMax), axisPaint);
}
// Draw the Y axis.
// The inversion of above, basically the same.
I was able to discover my own problem with enough time. I wasn't calculating the offset correct.
this.ChartToDeviceMatrix.SetScaleTranslate(xScale, yScale, DeviceMin.X, DeviceMax.X);
Should have been:
this.ChartToDeviceMatrix.SetScaleTranslate(xScale, yScale, -ChartMin.X * xScale + DeviceMin.Y, -ChartMin.Y * yScale + DeviceMax.Y);
Final Matrix method was:
private SKMatrix ChartToDeviceMatrix, DeviceToChartMatrix;
private void ConfigureTransforms(SKPoint ChartMin, SKPoint ChartMax, SKPoint DeviceMin, SKPoint DeviceMax)
{
this.ChartToDeviceMatrix = SKMatrix.MakeIdentity();
float xScale = (DeviceMax.X - DeviceMin.X) / (ChartMax.X - ChartMin.X);
float yScale = (DeviceMin.Y - DeviceMax.Y) / (ChartMax.Y - ChartMin.Y);
float xOffset = -ChartMin.X * xScale + DeviceMin.X;
float yOffset = -ChartMin.Y * yScale + DeviceMax.Y;
this.ChartToDeviceMatrix.SetScaleTranslate(xScale, yScale, xOffset, yOffset);
this.ChartToDeviceMatrix.TryInvert(out this.DeviceToChartMatrix);
}
I'm drawing a lot of shapes on WritableBitmap with help of WritableBitmapEx in my WPF application.
Unfortunately there is no ready to go function to draw an arc on it.
How can I:
1. Draw an arc on WritableBitmap?
2. Draw an anti-aliased arc with variable thickness on WritableBitmap?
I just need to draw circular arcs.
There is possibility to draw a nice, anti-aliased arc with variable thickness (System.Windows.Media.ArcSegment) on Canvas - but with thousands of shapes the performance of Canvas is poor - that's why I'm using WritableBitmap.
If it would be needed by some algorithms I have already calculated arc parameters like:
CenterPoint, Radius, StartPoint, EndPoint, StartAngle, EndAngle, ArcLength, IsLarge or Direction
I was trying to draw it manually with code similar to this:
int number_of_points = 1000;
for(int i=0; i<=number_of_points; i++){
double progress=(double)i/number_of_points;
double theta = (StartAngle + ArcLength * progress) * Math.PI / 180.0;
draw_pixel(
Center.X + Radius * Math.Cos(theta),
Center.Y + Radius * Math.Sin(theta)
);
}
but with varying resolution of picture, varying size of arc (how to calculate optimum number_of_points?), varying thickness of arc and with anti aliasing it starts to be a little tricky.
1. Draw an arc on WritableBitmap?
After analyzing mono libgdiplus sources on github I found that they are drawing an arc using Bezier curve.
I have ported some of their functions to c#.
DrawArc extension function can be used (with help of DrawBezier from WritableBitmapEx) to draw an simple arc.
There is no anti-aliased version of DrawBezier in WritableBitmapEx so this solution answers (only) my first question:
namespace System.Windows.Media.Imaging
{
public static partial class WriteableBitmapArcExtensions
{
//port of mono libgdiplus function
//append_arcs (GpPath *path, float x, float y, float width, float height, float startAngle, float sweepAngle)
//from: https://github.com/mono/libgdiplus/blob/master/src/graphics-path.c
public static void DrawArc(this WriteableBitmap bmp, float x, float y, float width, float height, float startAngle, float sweepAngle, Color color)
{
int i;
float drawn = 0;
int increment;
float endAngle;
bool enough = false;
if (Math.Abs(sweepAngle) >= 360)
{
bmp.DrawEllipse((int)x, (int)y, (int)width, (int)height, color);
return;
}
endAngle = startAngle + sweepAngle;
increment = (endAngle < startAngle) ? -90 : 90;
/* i is the number of sub-arcs drawn, each sub-arc can be at most 90 degrees.*/
/* there can be no more then 4 subarcs, ie. 90 + 90 + 90 + (something less than 90) */
for (i = 0; i < 4; i++)
{
float current = startAngle + drawn;
float additional;
if (enough)
return;
additional = endAngle - current; /* otherwise, add the remainder */
if (Math.Abs(additional) > 90)
{
additional = increment;
}
else
{
/* a near zero value will introduce bad artefact in the drawing */
if ((additional >= -0.0001f) && (additional <= 0.0001f))
return;
enough = true;
}
bmp._DrawArc(
x, y,
width, height, /* bounding rectangle */
current, current + additional, color);
drawn += additional;
}
}
//port of mono libgdiplus function
//append_arc (GpPath *path, BOOL start, float x, float y, float width, float height, float startAngle, float endAngle)
//from: https://github.com/mono/libgdiplus/blob/master/src/graphics-path.c
private static void _DrawArc(this WriteableBitmap bmp, float x, float y, float width, float height, float startAngle, float endAngle, Color color)
{
double sin_alpha, sin_beta, cos_alpha, cos_beta;
var rx = width / 2;
var ry = height / 2;
/* center */
var cx = x + rx;
var cy = y + ry;
/* angles in radians */
var alpha = startAngle * Math.PI / 180;
var beta = endAngle * Math.PI / 180;
/* adjust angles for ellipses */
alpha = Math.Atan2(rx * Math.Sin(alpha), ry * Math.Cos(alpha));
beta = Math.Atan2(rx * Math.Sin(beta), ry * Math.Cos(beta));
if (Math.Abs(beta - alpha) > Math.PI)
{
if (beta > alpha)
beta -= 2 * Math.PI;
else
alpha -= 2 * Math.PI;
}
var delta = beta - alpha;
// http://www.stillhq.com/ctpfaq/2001/comp.text.pdf-faq-2001-04.txt (section 2.13)
var bcp = 4.0 / 3 * (1 - Math.Cos(delta / 2)) / Math.Sin(delta / 2);
sin_alpha = Math.Sin(alpha);
sin_beta = Math.Sin(beta);
cos_alpha = Math.Cos(alpha);
cos_beta = Math.Cos(beta);
/* starting point */
double sx = cx + rx * cos_alpha;
double sy = cy + ry * sin_alpha;
//DrawBezier comes from WritableBitmapEx library
bmp.DrawBezier(
(int)(sx),
(int)(sy),
(int)(cx + rx * (cos_alpha - bcp * sin_alpha)),
(int)(cy + ry * (sin_alpha + bcp * cos_alpha)),
(int)(cx + rx * (cos_beta + bcp * sin_beta)),
(int)(cy + ry * (sin_beta - bcp * cos_beta)),
(int)(cx + rx * cos_beta),
(int)(cy + ry * sin_beta),
color
);
}
}
}
I have commented an issue on WritableBitmapEx site: I would like to draw arcs - so maybe part of this code would be included in WritableBitmapEx library.
2. Draw an anti-aliased arc with variable thickness on WritableBitmap?
After reading comment from ForeverZer0 I have made some experiments with System.Drawing.Graphics and WritableBitmap. With help of getting a DrawingContext for a wpf WriteableBitmap I have done it with such code:
WritableBitmap ret = BitmapFactory.New(img_width, img_height);
ret.Lock();
var bmp = new System.Drawing.Bitmap(
ret.PixelWidth,
ret.PixelHeight,
ret.BackBufferStride,
System.Drawing.Imaging.PixelFormat.Format32bppPArgb,
ret.BackBuffer
);
System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bmp);
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
g.DrawArc(...); //<-- draws an antialiased arc with variable thickness
g.Dispose();
bmp.Dispose();
ret.AddDirtyRect(new Int32Rect(0, 0, ret.PixelWidth, ret.PixelHeight));
ret.Unlock();
return ret; //<-- WritableBitmap with beautifull arc on it;
Im playing around with the Platformer Starter Kit and so far I've added in horizontal and vertical "camera" movement and Im trying to add inn a parallaxing background. The problem is that after two background layers it stops showing the rest of them. Im very new to XNA and need a little help :). Heres a pic of the problem:
Heres the code. Please tell me if you need some more :)
Layer classes:
class Layer
{
public Texture2D[] Textures { get; private set; }
public float ScrollRate { get; private set; }
public Layer(ContentManager content, string basePath, float scrollRate)
{
// Assumes each layer only has 3 segments.
Textures = new Texture2D[3];
for (int i = 0; i < 3; ++i)
Textures[i] = content.Load<Texture2D>(basePath + "_" + i);
ScrollRate = scrollRate;
}
public void Draw(SpriteBatch spriteBatch, float cameraPosition, float cameraPositionYAxis)
{
// Assume each segment is the same width.
int segmentWidth = Textures[0].Width;
// Calculate which segments to draw and how much to offset them.
float x = cameraPosition * ScrollRate;
float y = ScrollRate;
int leftSegment = (int)Math.Floor(x / segmentWidth);
int rightSegment = leftSegment + 1;
x = (x / segmentWidth - leftSegment) * -segmentWidth;
spriteBatch.Draw(Textures[leftSegment % Textures.Length], new Vector2(x, -y), Color.White);
spriteBatch.Draw(Textures[rightSegment % Textures.Length], new Vector2(x + segmentWidth, -y), Color.White);
}
}
Heres the draw method in my Level.cs with my ScrollCamera (dont know if ScrollCamera has anything to do with it)
public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{
ScrollCamera(spriteBatch.GraphicsDevice.Viewport);
Matrix cameraTransformYAxis = Matrix.CreateTranslation(-cameraPosition, -cameraPositionYAxis, 0.0f);
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp,
DepthStencilState.Default, RasterizerState.CullCounterClockwise, null, cameraTransformYAxis);
//added this foreach loop
foreach (var layer in layers)
{
layer.Draw(spriteBatch, cameraPosition, cameraPositionYAxis);
}
DrawTiles(spriteBatch);
Player.Draw(gameTime, spriteBatch);
foreach (Enemy enemy in enemies)
{
enemy.Draw(gameTime, spriteBatch);
}
spriteBatch.End();
}
private void ScrollCamera(Viewport viewport)
{
#if ZUNE
const float ViewMargin = 0.4f;
#else
const float ViewMargin = 0.5f;
#endif
float marginWidth = viewport.Width * ViewMargin;
float marginLeft = cameraPosition + marginWidth;
float marginRight = cameraPosition + viewport.Width - marginWidth;
const float TopMargin = 0.4f;
const float BottomMargin = 0.4f;
float marginTop = cameraPositionYAxis + viewport.Height * TopMargin;
float marginBottom = cameraPositionYAxis + viewport.Height - viewport.Height * BottomMargin;
// float maxCameraPositionYOffset = Tile.Height * Height - viewport.Height;
float CameraMovement = 0.0f;
if (Player.Position.X < marginLeft)
CameraMovement = Player.Position.X - marginLeft;
else if (Player.Position.X > marginRight)
CameraMovement = Player.Position.X - marginRight;
//Aktualizuj przesuwanie ekranu, ale zapobiegnij wyjściu poza mape
float maxCameraPosition = Tile.Width * Width - viewport.Width;
cameraPosition = MathHelper.Clamp(cameraPosition + CameraMovement, 0.0f, maxCameraPosition);
float cameraMovementY = 0.0f;
if (Player.Position.Y < marginTop) //above the top margin
cameraMovementY = Player.Position.Y - marginTop;
else if (Player.Position.Y > marginBottom) //below the bottom margin
cameraMovementY = Player.Position.Y - marginBottom;
float maxCameraPositionYOffset = Tile.Height * Height - viewport.Height;
cameraPositionYAxis = MathHelper.Clamp(cameraPositionYAxis + cameraMovementY, 0.0f, maxCameraPositionYOffset);
}
And I think thats it. Please tell me if you need some more code :)
You want to use Linear Wrapping. There's an excellent blog post on it right here. This assumes of course that your texture tiles perfect. You just simply need to to set your linear wrapping mode, code example below:
// Use this one instead!
spriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.LinearWrap, null, null);
spriteBatch.Draw(texture, position, new Rectangle(-scrollX, -scrollY, texture.Width, texture.Height), Color.White);
spriteBatch.End();
I am using the following to create a circle using VertexPositionTexture:
public static ObjectData Circle(Vector2 origin, float radius, int slices)
{
/// See below
}
The texture that is applied to it doesn't look right, it spirals out from the center. I have tried some other things but nothing does it how I want. I would like for it to kind-of just fan around the circle, or start in the top-left end finish in the bottom-right. Basically wanting it to be easier to create textures for it.
I know that are MUCH easier ways to do this without using meshes, but that is not what I am trying to accomplish right now.
This is the code that ended up working thanks to Pinckerman:
public static ObjectData Circle(Vector2 origin, float radius, int slices)
{
VertexPositionTexture[] vertices = new VertexPositionTexture[slices + 2];
int[] indices = new int[slices * 3];
float x = origin.X;
float y = origin.Y;
float deltaRad = MathHelper.ToRadians(360) / slices;
float delta = 0;
float thetaInc = (((float)Math.PI * 2) / vertices.Length);
vertices[0] = new VertexPositionTexture(new Vector3(x, y, 0), new Vector2(.5f, .5f));
float sliceSize = 1f / slices;
for (int i = 1; i < slices + 2; i++)
{
float newX = (float)Math.Cos(delta) * radius + x;
float newY = (float)Math.Sin(delta) * radius + y;
float textX = 0.5f + ((radius * (float)Math.Cos(delta)) / (radius * 2));
float textY = 0.5f + ((radius * (float)Math.Sin(delta)) /(radius * 2));
vertices[i] = new VertexPositionTexture(new Vector3(newX, newY, 0), new Vector2(textX, textY));
delta += deltaRad;
}
indices[0] = 0;
indices[1] = 1;
for (int i = 0; i < slices; i++)
{
indices[3 * i] = 0;
indices[(3 * i) + 1] = i + 1;
indices[(3 * i) + 2] = i + 2;
}
ObjectData thisData = new ObjectData()
{
Vertices = vertices,
Indices = indices
};
return thisData;
}
public static ObjectData Ellipse()
{
ObjectData thisData = new ObjectData()
{
};
return thisData;
}
ObjectData is just a structure that contains an array of vertices & an array of indices.
Hope this helps others that may be trying to accomplish something similar.
It looks like a spiral because you've set the upper-left point for the texture Vector2(0,0) in the center of your "circle" and it's wrong. You need to set it on the top-left vertex of the top-left slice of you circle, because 0,0 of your UV map is the upper left corner of your texture.
I think you need to set (0.5, 0) for the upper vertex, (1, 0.5) for the right, (0.5, 1) for the lower and (0, 0.5) for the left, or something like this, and for the others use some trigonometry.
The center of your circle has to be Vector2(0.5, 0.5).
Regarding the trigonometry, I think you should do something like this.
The center of your circle has UV value of Vector2(0.5, 0.5), and for the others (supposing the second point of the sequence is just right to the center, having UV value of Vector2(1, 0.5)) try something like this:
vertices[i] = new VertexPositionTexture(new Vector3(newX, newY, 0), new Vector2(0.5f + radius * (float)Math.Cos(delta), 0.5f - radius * (float)Math.Sin(delta)));
I've just edited your third line in the for-loop. This should give you the UV coordinates you need for each point. I hope so.