I have got .png HD (1024x768) backgroud image. My users could have different screen sizes, but it should look always the same, so:
Is that possible to automatically scale it size to the background size in xna?
First, you have to render whole scene into RenderTarget and then draw it using rectangle for position and size.
Rectangle dest = new Rectangle (0, 0, graphics.ViewPort.Width, graphics.ViewPort.Height);
spriteBatch.Draw(RenderTarget, dest, Color.White);
it could looks stretched/squeezed if you work in 16:9 and user have 4:3 then:
int height = (int)(graphics.ViewPort.Width * (16.0/9.0));
Rectangle dest = new Rectangle (0, graphics.ViewPort.Height -- (int)(height / 2.0), graphics.ViewPort.Width, graphics.ViewPort.Height);
spriteBatch.Draw(RenderTarget, dest, Color.White);
or in general where width >= height:
double aspectratio = ((double)graphics.ViewPort.Width / (double)graphics.Viewport.Height);
int height = (int)(graphics.ViewPort.Width * aspectratio;
Rectangle dest = new Rectangle(0, graphics.ViewPort.Height - (int)(height / 2.0), graphics.ViewPort.Width, height);
and here is some nice example of independed screen resolution:
http://www.david-amador.com/2010/03/xna-2d-independent-resolution-rendering/
Related
When I draw an image using Graphics.DrawImage and draw it at a bigger size than the original image, it ends up being a bit too small. You can see this in the following picture:
The green lines shouldn't be visible and are not part of the image. Rather they get drawn behind the image and the image should cover them.
How can I draw an image with the exact right size?
EDIT: I draw the green part with the same rectangle I pass into the DrawImage call, with the exact dimensions of how big the image should be. So no flaw in my values (I think).
EDIT 2: I draw the green rectangle using FillRectangle, so no pen calculations need to be done. Also, I logged the values that I pass into the rectangle for both the image and the green fill, and the values are correct. It's just the image that's off. I will post code later, as I'm not at my computer at the moment.
EDIT 3: This is the code I use to render the images:
// This is for zooming
public readonly float[] SCALES = { 0.05f, 0.1f, 0.125f, 0.25f, 0.333f, 0.5f, 0.667f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 2.5f, 3.0f, 3.5f, 4.0f, 4.5f, 5.0f, 6.0f, 7.0f, 8.0f, 10.0f, 12.0f, 15.0f, 20.0f, 30.0f, 36.0f };
private int scaleIndex = 8;
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
float ScaleFactor = SCALES[scaleIndex];
e.Graphics.InterpolationMode = ScaleFactor < 1 ? InterpolationMode.Bicubic : InterpolationMode.NearestNeighbor;
Image im = Properties.Resources.TSprite0;
for (int y = 0; y < TilesVertical; y++)
{
for (int x = 0; x < TilesHorizontal; x++)
{
float sx = im.Width * ScaleFactor;
float sy = im.Height * ScaleFactor;
Point p = new Point((int)(-scrollPosition.X + sx * x), (int)(-scrollPosition.Y + sy * y));
Size s = new Size((int)Math.Floor(sx), (int)Math.Floor(sy));
// The green rectangle in the background should be the same size as the image
e.Graphics.FillRectangle(Brushes.Lime, new Rectangle(p, s));
e.Graphics.DrawImage(im, new Rectangle(p, s), 0, 0, 16, 16, GraphicsUnit.Pixel);
}
}
im.Dispose();
}
EDIT 4: Also note that the image seems to be cropped on the left and top instead of resized. Take a look at this comparison of the original image upscaled in Photoshop and then how GDI+ renders it:
The issue happens when scaling to 2x or larger.
Looks like the whole problem is caused by the wrong default PixelOffsetMode.
By offsetting pixels during rendering, you can improve render quality
at the cost of render speed.
Setting it to
g.PixelOffsetMode = PixelOffsetMode.Half;
makes it go away for me.
Setting it to
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
also works fine.
Default, None and HighSpeed cause the image to be rendered a little to the left and up.
Often you will also want to set InterpolationMode.NearestNeighbor.
Under Windows Forms, I take a full size screenshot of a specific window that has a specific window size, I save it into a Bitmap object, then, I declared a Rectangle structure to crop a region of that Bitmap, because later I need to manipulate only a very specific part/region of the screenshot...
To make things simpler for this question, lets say the window and bitmap size is 640x480, the Rectangle's X,Y is: 436,150 and the Width,Height is: 146,170, and what I crop from the screenshot (the bitmap) is a balloon image. The window is a videogame.
The problem is that when the window size increase, the balloon image increase too, as obvious, so the x,y and width/height of my rectangle for a window size of 640x480 will not properly capture/crop the entire balloon image when the window of the game has a bigger size...
I need to know how can I calculate the x,y width/height that my rectangle should have to properly crop the balloon image when the window size changes. I need to adapt the rectangle.
So, if this is the predefined size and rectangle I have:
{ new Size(640, 480), new Rectangle(436, 150, 146, 170) }
From that, the approximated adapted values that the rectangle should have to properly crop the same equivalent area in a window size of 800x600 and 1280x768 it would be more or less these:
{ new Size(800, 600), new Rectangle(546, 186, 186, 212) }
{ new Size(1280, 768), new Rectangle(830, 232, 240, 274) }
...are just approximated values, but not perfect, because I did it manually since Im not sure which is the way to calculate and automate this math operation.
I hope my question and problem was understood. Thankyou in advance.
Maybe you're over-thinking it, but all you need to do is capture the percentage change between the original size and the new size (for both X and Y), and then apply that percentage to the properties of the original rectangle to get the new rectangle.
For example:
public static Rectangle GetNewRectangle(Size oldSize, Rectangle oldRectangle,
Size newSize)
{
var percentChangeX = (double)newSize.Width / oldSize.Width;
var percentChangeY = (double)newSize.Height / oldSize.Height;
return new Rectangle
{
X = (int)(oldRectangle.X * percentChangeX),
Y = (int)(oldRectangle.Y * percentChangeY),
Width = (int)(oldRectangle.Width * percentChangeX),
Height = (int)(oldRectangle.Height * percentChangeY)
};
}
Example usage:
// Helper method to display size and rectangle properties
private static string GetDisplayValues(Size size, Rectangle rect)
{
return $" - size: {size.Width} x {size.Height}\n" +
$" - rect: {rect.X}, {rect.Y} : {rect.Width} x {rect.Height}\n";
}
private static void Main()
{
var size = new Size(640, 480);
var rect = new Rectangle(436, 150, 146, 170);
Console.WriteLine($"Original:\n{GetDisplayValues(size, rect)}");
var newSize = new Size(800, 600);
var newRect = GetNewRectangle(size, rect, newSize);
Console.WriteLine($"Resized:\n{GetDisplayValues(newSize, newRect)}");
GetKeyFromUser("\nDone! Press any key to exit...");
}
Output
Try this:
if width 640:
X = 436 / 640 = 0.68125 (68.125%)
W = 146 / 640 = 0.22125 (22.125%)
if heigth 480:
Y = 150 / 480 = 0.3125 (31.25%)
H = 170 / 480 = 0.3541666666666666666666666667 (35.41666666666666666666666667%)
Considering the size of the form as this.Width, and the height as this.Height:
decimal pX = 0.68125;
decimal pW = 0.22125;
decimal pY = 0.3125;
decimal pH = 0.3541666666666666666666666667;
Rectangle rect = new Rectangle(this.Width * pX, this.Height * pY, this.Width * pW, this.Height * pH);
Given a source Bitmap and a selection Rectangle inside its boundaries:
RectangleF SourceRect = new Rectangle(Point.Empty, SourceBitmap.Size);
Rectangle SelectionRect = new Rectangle([Point], [Size]);
When the SourceBitmap changes its size, the new size of the selection rectangle is calculated using the scale factor given by the relation between the old size and the new size of the SourceBitmap:
RectangleF DestinationRect = new RectangleF(Point.Empty, InflatedBitmap.Size);
SizeF ScaleFactor = new SizeF(DestinationRect.Width / SourceRect.Width,
DestinationRect.Height / SourceRect.Height);
PointF NewPosition = new PointF(SelectionRect.X * ScaleFactor.Width, SelectionRect.Y * ScaleFactor.Height);
SizeF NewSize = new SizeF(SelectionRect.Width * ScaleFactor.Width, SelectionRect.Height * ScaleFactor.Height);
RectangleF InflatedSelection = new RectangleF(NewPosition, NewSize);
With a SourceBitmap and a selection rectangle sized as:
RectangleF SourceRect = new RectangleF(0, 0, 640, 480);
RectangleF SelectionRect = new RectangleF(436, 150, 146, 170);
If the inflated bitmaps are sized as:
RectangleF DestinationRect1 = new RectangleF(0, 0, 800, 600);
RectangleF DestinationRect2 = new RectangleF(0, 0, 1280, 768);
The Inflated selection with a scale factor of (1.25, 1.25) and (2, 1.6) will be (rounded down):
RectangleF InflatedSelection1 = new RectangleF(545, 187, 182, 212);
RectangleF InflatedSelection2 = new RectangleF(872, 240, 292, 272);
I am writing a Kinect application, where I use the color image from the sensor. I get a 640 x 480 color image, I copy the data from the sensor to a WriteableBitmap, with the WritePixels method. When I use the whole color image I have no issues. But I would like to use only the middle part of the image. But I can't get stride and or offset right?
To copy the whole image I do the following:
_colorImageWritableBitmap.WritePixels(
new Int32Rect(0, 0, colorImageFrame.Width, colorImageFrame.Height),
_colorImageData,
colorImageFrame.Width * Bgr32BytesPerPixel,
0);
As I mention I only want the middle part of the image. I would like to start at a width at 185px and take the next 270px, and stop there. And I use the the whole height.
My PixelFormat is bgr32, so to calculate the byte pr. pixel I use:
var bytesPrPixel = (PixelFormats.Bgr32.BitsPerPixel + 7)/8;
And my stride:
var stride = bytesPrPixel*width;
The writepixel method:
_colorImageWritableBitmap.WritePixels(
new Int32Rect(0, 0, colorImageFrame.Width, colorImageFrame.Height),
_colorImageData, stride, offset);
But when I change the width to other than 640, the image gets wrong (hidden in noise).
Can someone help me, to understand what I am doing wrong here?
You have to properly copy the pixels from the source bitmap. Assuming that the source colorImageFrame is also a BitmapSource, you would do it this way:
var width = 270;
var height = 480;
var x = (colorImageFrame.PixelWidth - width) / 2;
var y = 0;
var stride = (width * colorImageFrame.Format.BitsPerPixel + 7) / 8;
var pixels = new byte[height * stride];
colorImageFrame.CopyPixels(new Int32Rect(x, y, width, height), pixels, stride, 0);
Now you could write the pixel buffer to your WriteableBitmap by:
colorImageWritableBitmap.WritePixels(
new Int32Rect(0, 0, width, height), pixels, stride, 0);
Or instead of using WriteableBitmap, you just create a new BitmapSource, like:
var targetBitmap = BitmapSource.Create(
width, height, 96, 96, colorImageFrame.Format, null, pixels, stride);
However, the easiest way to create a crop of the source bitmap might be to used a CroppedBitmap like this:
var targetBitmap = new CroppedBitmap(
colorImageFrame, new Int32Rect(x, y, width, height));
I have a method that takes in a bitmap, width, height, and a list of strings, overlays it on top of a black background, and adds y-axis text onto it (as demonstrated in the picture below).
In my attempt to align the text so they occupy the exact vertical space of my graph, I took the height and divided it by the number of values in the list:
float verticalDistance = height / times.Count;
However, perhaps of alignment issues, the space between each time value on the y-axis doesn't fix exactly into the height of the original bitmap, which is the variable height.
I thought it was an issue with int and it rounding numbers up or down, but changing it verticalDistance to a float did not ameliorate the issue.
private Bitmap overlayBitmap(Bitmap sourceBMP, int width, int height, List<String> times) {
int newWidth = width + (width / 3);
int newHeight = height + (height / 3);
Bitmap result = new Bitmap(newWidth, newHeight);
using (Graphics g = Graphics.FromImage(result)) {
g.FillRectangle(Brushes.Black, 0, 0, newWidth, newHeight);
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
StringFormat stringFormatX = new StringFormat();
stringFormatX.LineAlignment = StringAlignment.Center;
Font drawFontX = new Font("Whitney", 10);
float verticalDistance = height / times.Count;
for (int i = 0; i < times.Count; i++) {
if (i % 5 == 0) {
g.DrawString(times[i], drawFontX, Brushes.White, 5, ((newHeight - height)/2) + (verticalDistance * i), stringFormatX);
}
}
g.DrawImage(sourceBMP, width / 6, height / 6, width, height);
}
return result;
}
What could be the issue here?
Reduce your problem to the simplest case: What if you had just two values to display on your Y axis? Your vertical distance would be the length of the axis. Your vertical distance formula would not work in that case. Therefore
float verticalDistance = height / (times.Count - 1.0);
In the code below:
void f13(Graphics g)
{
g.FillRectangle(new SolidBrush(Color.Black), pictureBox1.ClientRectangle);
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
var zf = .0143;
const int w = 6000, h = 10, margin = 40;
var bmp = new Bitmap(w + 2 * margin, h + 2 * margin);
var bmpG = Graphics.FromImage(bmp);
bmpG.FillRectangle(new SolidBrush(Color.White), 0, 0, bmp.Width, bmp.Height);
var srcRect = new RectangleF(margin - .5f, margin - .5f, w, h);
zf = (float)Convert.ToInt32(w * zf) / w;
var destRect = new Rectangle(0, 0, Convert.ToInt32(w * zf), Convert.ToInt32(w * zf));
g.DrawImage(bmp, destRect, srcRect, GraphicsUnit.Pixel);
destRect.X += destRect.Width;
g.DrawImage(bmp, destRect, srcRect, GraphicsUnit.Pixel);
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
f13(e.Graphics);
}
I get a gap between two rectangles:
micro http://www.uploadup.com/di-0HXM.png
macro http://www.uploadup.com/di-G1O5.png
why is that?
If the gap line is not so clear, you may decrease margin. if you set it to 10 you'll get:
macro, less margin http://www.uploadup.com/di-P2ZT.png
That'll happen if your rectangles' boundaries aren't integers. Gradient has nothing to do with it.
Consider: Let's say you're drawing a rectangle whose right side is at X=100.5, and you're filling it with white (with the existing background being black). So the graphics library (this isn't specific to GDI+) will "half-fill" those rightmost pixels (at X=100) with white, meaning they blend the existing black with a 50% mix of white, for a result of gray.
Then you draw another rectangle whose left side is at X=100.5. Now you're once again filling the pixels at X=100 halfway with white, so the graphics library will take the existing color (gray) and blend it with a 50% white, leaving you with 75% white.
If you don't want this kind of seam, you have to either (a) make sure your rectangles overlap a little bit, or (b) manually round your coordinates to the nearest pixel, so all the pixels are getting completely written instead of blended with what's already there.