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.
Related
At the moment I develope a ChartControl and it works just pretty well in my opinion,
but now I'm at a point where it would be nice to have the ability to zoom the drawed signal for better analyzing.
At the moment I calculate the needed points like this:
for (int i = 0; i < PointsCount; i++){
xAxisPoint = xAxisOP.X + i * (xAxisWidth / PointsCount);
yAxisPoint = yAxisHeight * data[i].Point / Divisor;
if(yAxisPoint > yAxisHeight){
yAxisPoint = yAxisHeight;
}
if(yAxisPoint < -yAxisHeight){
yAxisPoint = -yAxisHeight;
}
Points[i] = new PointF(xAxisPoint, yAxisOP.Y + yAxisPoint);
}
if(zoom){
graphics.ScaleTransform(0.2f*ZoomFactor, 0.2f*ZoomFactor);
}
using (Pen plotPen = new Pen(plotColor, 1)){
graphics.DrawLines(plotPen, Points);
}
But the problem is: When it zooms in, the zoom is way too big and is drawn outside the bounds of my control.
Is there a way to specify an area in which it should be Scaled (zoomed)?
For the final question: Is there a way to specify an area in which it should be scaled/zoomed? you need a combination of SetClip, TranslateTransform and ScaleTransform.
Here is an example.
It uses a
target rectangle zoomTgtArea where the zoomed graphics are displayed,
a mouse location zoomOrigin where the zoom origin is,
a float zoomFactor, a positive float.
Initial values:
Rectangle zoomTgtArea = new Rectangle(300, 500, 200, 200);
Point zoomOrigin = Point.Empty; // updated in MouseMove when button is pressed
float zoomFactor = 2f;
The trick to zoom in on only a part of the graphics is to display the graphics twice, once normally and once with the transformations of the Graphics object.
Let's try:
private void pictureBox_Paint(object sender, PaintEventArgs e)
{
// normal drawing
DrawStuff(e.Graphics);
// for the movable zoom we want a small correction
Rectangle cr = pictureBox.ClientRectangle;
float pcw = cr.Width / (cr.Width - ZoomTgtArea.Width / 2f) ;
float pch = cr.Height / (cr.Height - ZoomTgtArea.Height / 2f) ;
// now we prepare the graphics object; note: order matters!
e.Graphics.SetClip(zoomTgtArea );
// we can either follow the mouse or keep the output area fixed:
if (cbx_fixed.Checked)
e.Graphics.TranslateTransform( ZoomTgtArea.X - zoomCenter.X * zoomFactor,
ZoomTgtArea.Y - zoomCenter.Y * zoomFactor);
else
e.Graphics.TranslateTransform( - zoomCenter.X * zoomFactor * pcw,
- zoomCenter.Y * zoomFactor * pch);
// finally zoom
e.Graphics.ScaleTransform(zoomFactor, zoomFactor);
// and display zoomed
DrawStuff(e.Graphics);
}
The DrawStuff I used is simple:
void DrawStuff(Graphics g)
{
bool isZoomed = g.Transform.Elements[0]!= 1
|| g.Transform.OffsetX != 0 | g.Transform.OffsetY != 0;
if (isZoomed) g.Clear(Color.Gainsboro); // pick your back color
// all your drawing here!
Rectangle r = new Rectangle(10, 10, 500, 800); // some size
using (Font f = new Font("Tahoma", 11f))
g.DrawString(text, f, Brushes.DarkSlateBlue, r);
}
Its only extra is clearing the background so the normal drawing won't shine through the zoomed version..
Let's see:
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/
What brush should i use to draw rectangles with white interior of the line and lines for the perimeter of the rectangle like the elevations below.
The form1 winform is what i am working on and the image behind the winform is how i need to the rectangles to look in my winform.
To make the question easier, how can i fill the interior portion of the rectangles with white?
How do i fill the LINES of the rectangle with white? I do not need to fill the inside of the rectangle, I need to fill a portion of the 4 lines that make up the rectangle with white.
void BuildShopDrawing(ElevationResponse elevation)
{
float penWidth = (float)((2f / 12f) * PIXELS_PER_FOOT);
Pen blackPen = new Pen(Color.FromArgb(40, 84, 149), penWidth);
Bitmap canvas = new Bitmap((((int)elevation.TotalWidthFeet) * PIXELS_PER_FOOT) + 55, (((int)elevation.TotalHeightFeet) * PIXELS_PER_FOOT) + 25);
Graphics dc = Graphics.FromImage(canvas);
RectangleF[] bays = new RectangleF[elevation.Bays.Count];
float x = 10F;
float width = 0F;
float height = 0F;
for (int i = 0; i < elevation.Bays.Count; i++)
{
if (i > 0)
{
x += (float)((elevation.Bays[i - 1].WidthInches / 12) * PIXELS_PER_FOOT);
}
width = (float)(elevation.Bays[i].WidthInches / 12) * PIXELS_PER_FOOT;
height = (float)(elevation.Bays[i].HeightInches / 12) * PIXELS_PER_FOOT;
bays[i] =
new RectangleF(new PointF(x, 10),
new SizeF(width, height));
}
dc.DrawRectangles(blackPen, bays);
this.picBx.Image = canvas;
this.Size = new System.Drawing.Size(canvas.Width + 10, canvas.Height + 50);
}
You need to look a bit more thoroughly at the Pen Class more specifically the CompoundArray Property, it will give you something like you are wanting, You will need to play around some other of the Pen Class properties to get your transitions right. And as a side note when you post example code that depends on external custom classes you make it harder for someone to help, it is always best to make sure that the code can run by itself.
Try adding this after you declare your pen.
float[] cmpArray = new float[4]{0.0F, 0.2F, 0.7F, 1.0F};
blackPen.CompoundArray = cmpArray;
It looks something like this:
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.
I would like to 60 pie slices with of 5 degree and each with 1 degree of separation. However, with the code below, the outcome looks like the image below with a big black centre, is there anyway that I can modify the code to avoid that?
Graphics g = panel1.CreateGraphics();
// Create pen.
Pen blackPen = new Pen(Color.Black, 1);
// Create rectangle for ellipse.
//RectangleF rect = new RectangleF(150.0F, 10.0F, 200F, 300.0F);
// Create start and sweep angles.
float startAngle = 270F;
float sweepAngle = 4F;
for (int i = 0; i < 60; i++ )
{
RectangleF rect = new RectangleF(50F, 10.0F, 200F, 200F);
startAngle = startAngle + 6F;
g.DrawPie(blackPen, rect, startAngle, sweepAngle);
//start = start + 1f;
}
Regards
This effect is called the Moiré pattern. http://en.wikipedia.org/wiki/Moir%C3%A9_pattern
To avoid it make the distance between slices bigger. The amount depends on the size of the pie and the display resolution. To make the distance bigger you could draw a bigger pie but you'll find that the slices will be fighting over the pixels. You could just live with this effect or you could push the slices outward.