How to get rid of weird OpenTK render? - c#

I am fully aware that I can load textures in OpenTK. But when I tried to render a picture using only points it shows really weird lines when ClientSize.Width is EXACTLY equal to the width of the picture being rendered. Like this:
And if I resize (enlarge) the ClientSize.Width of the window lines become kinda normal and there is actual reason they appear (they cover areas that can't be rendered):
This seems to occur regardless of the picture I open. Can somebody explain why there are lines in the first picture?
using System;
using System.Drawing;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
namespace Miracle
{
class Program
{
public static void Main()
{
using (Window w = new Window())
w.Run(30);
}
}
class Window : GameWindow
{
private Color[,] pixels;
private int width, height;
private bool blink = true;
private int blinkcounter;
public Window() : base(1337, 666, GraphicsMode.Default, "Miracle", GameWindowFlags.Default) { }
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
Bitmap pic = new Bitmap("sample.png");
width = pic.Width;
height = pic.Height;
ClientSize = new Size(width, height);
pixels = new Color[width, height];
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
pixels[x, y] = pic.GetPixel(x, y);
GL.ClearColor(Color.FromArgb(0, 255, 0));
GL.Ortho(0, width, height, 0, -1, 1);
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
Title = ClientSize.Width + "x" + ClientSize.Height + (ClientSize.Width == width && ClientSize.Height == height ? " (original)" : "");
GL.Viewport(ClientSize);
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
if (blinkcounter == 6)
{
GL.ClearColor(blink ? Color.FromArgb(255, 0, 0) : Color.FromArgb(0, 255, 0));
blink = !blink;
blinkcounter = 0;
}
blinkcounter++;
}
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
GL.Clear(ClearBufferMask.ColorBufferBit);
GL.MatrixMode(MatrixMode.Projection);
GL.Begin(PrimitiveType.Points);
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
{
GL.Color4(pixels[x, y]);
GL.Vertex2(x, y);
}
GL.End();
SwapBuffers();
}
}
}

I will describe a solution to your problem and also why I think you're getting the problem.
First, why I think you're getting the problem:
The rasterization of points is weird and hardware dependent. This problem looks to be an error of floating point accuracy in the rasterization stage for points. The projected point is on the boundary of choosing between 2 pixels, and it chooses the wrong one due to FP limitations.
Solution:
Rasterizing an image using points is NOT recommended. That's not what point rasterization is used for. Instead, rasterize a full screen quad, give each vertex of the quad a texture coordinate, and in the fragment shader use the interpolated texture coordinate to fetch from the texture storing the image you want to render. This avoids the problem you were experiencing with the GL_POINTS because it ensures every pixel is drawn to once.

Related

Get pixel value of image draw through onPaint methode

I am developping a custom C# UserControl WinForm to display an image on background and display scrollbars when I zoom with the mouse. For this, I overrided the OnPaint method. In it, if I have an image loaded, according some parameters I know the source and destination rectangle sizes. In the same way, I know what scale and translation apply to always keeping the top left corner on screen when zooming. And for the zoom, I use the scrollmouse event to update the zoom factory.
Here is my code related to this override method.
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// Draw image
if(image != null)
{
//
Rectangle srcRect, destRect;
Point pt = new Point((int)(hScrollBar1.Value/zoom), (int)(vScrollBar1.Value/zoom));
if (canvasSize.Width * zoom < viewRectWidth && canvasSize.Height * zoom < viewRectHeight)
srcRect = new Rectangle(0, 0, canvasSize.Width, canvasSize.Height); // view all image
else if (canvasSize.Width * zoom < viewRectWidth)
srcRect = new Rectangle(0, pt.Y, canvasSize.Width, (int)(viewRectHeight / zoom)); // view a portion of image but center on width
else if (canvasSize.Height * zoom < viewRectHeight)
srcRect = new Rectangle(pt.X, 0, (int)(viewRectWidth / zoom), canvasSize.Height); // view a portion of image but center on height
else
srcRect = new Rectangle(pt, new Size((int)(viewRectWidth / zoom), (int)(viewRectHeight / zoom))); // view a portion of image
destRect = new Rectangle((int)(-srcRect.Width/2),
(int)-srcRect.Height/2,
srcRect.Width,
srcRect.Height); // the center of apparent image is on origin
Matrix mx = new Matrix(); // create an identity matrix
mx.Scale(zoom, zoom); // zoom image
// Move image to view window center
mx.Translate(viewRectWidth / 2.0f, viewRectHeight / 2.0f, MatrixOrder.Append);
// Display image on widget
Graphics g = e.Graphics;
g.InterpolationMode = interMode;
g.Transform = mx;
g.DrawImage(image, destRect, srcRect, GraphicsUnit.Pixel);
}
}
My question is how to get the pixel value when I am on the MouseMove override method of this WinForm ?
I think understand that it is possible only in method with PaintEventArgs but I am not sure how to deal with it. I tried a lot of things but for now the better I got is use the mouse position on the screen and find the pixel value in the original bitmap with these "wrong" coordinates. I can't link this relative position on the screen with the real coordinates of the pixel of the image display at this place. Maybe there is method to "just" get the pixel value not passing through the image bitmap I use for the paint method ? Or maybe not.
Thank you in advance for your help. Best regards.
I couldn't completely understand your drawing code but you can do an inverse transformation on mouse coordinate. So, you can translate the mouse coordinate back to the origin and scale it by 1/zoom. This simple process gives you the image space coordinate.
I provide an example code with its own drawing code (not your code/algorithm) but that can still give you the idea of inverse transformation. It is pretty simple so look at the example code.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace GetPixelFromZoomedImage
{
public partial class MainForm : Form
{
public Form1()
{
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint | ControlStyles.ResizeRedraw, true);
InitializeComponent();
}
private float m_zoom = 1.0f;
private Bitmap m_image;
private Point m_origin = Point.Empty;
private Point m_delta = Point.Empty;
private SolidBrush m_brush = new SolidBrush(Color.Transparent);
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
g.TranslateTransform(m_origin.X, m_origin.Y);
g.ScaleTransform(m_zoom, m_zoom);
g.DrawImageUnscaled(m_image, Point.Empty);
g.ResetTransform();
g.FillRectangle(m_brush, ClientSize.Width - 50, 0, 50, 50);
base.OnPaint(e);
}
protected override void OnHandleCreated(EventArgs e)
{
m_image = (Bitmap)Image.FromFile("test.png");
base.OnHandleCreated(e);
}
protected override void OnMouseDown(MouseEventArgs e)
{
if(e.Button == MouseButtons.Left)
{
m_delta = new Point(m_origin.X - e.X, m_origin.Y - e.Y);
}
base.OnMouseDown(e);
}
protected override void OnMouseMove(MouseEventArgs e)
{
if(e.Button == MouseButtons.Left)
{
m_origin = new Point(e.X + m_delta.X, e.Y + m_delta.Y);
Invalidate();
}
int x = (int)((e.X - m_origin.X) / m_zoom);
int y = (int)((e.Y - m_origin.Y) / m_zoom);
if (x < 0 || x >= m_image.Width || y < 0 || y >= m_image.Height)
return;
m_brush.Color = m_image.GetPixel(x, y);
Invalidate();
base.OnMouseMove(e);
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
}
protected override void OnMouseWheel(MouseEventArgs e)
{
float scaleFactor = 1.6f * (float)Math.Abs(e.Delta) / 120;
if(e.Delta > 0)
m_zoom *= scaleFactor;
else
m_zoom /= scaleFactor;
m_zoom = m_zoom > 64.0f ? 64.0f : m_zoom;
m_zoom = m_zoom < 0.1f ? 0.1f : m_zoom;
Invalidate();
base.OnMouseWheel(e);
}
}
}

Initiate panel with drawings

I am now drawing to a panel some dots to indicate a sort of dotted grid with 1% of margin of total panel width.
This is what I am doing now:
private void panel1_Paint(object sender, PaintEventArgs e)
{
Pen my_pen = new Pen(Color.Gray);
int x,y;
int k = 1 ,t = 1;
int onePercentWidth = panel1.Width / 100;
for (y = onePercentWidth; y < panel1.Height-1; y += onePercentWidth)
{
for (x = onePercentWidth; x < panel1.Width-1; x += onePercentWidth)
{
e.Graphics.DrawEllipse(my_pen, x, y, 1, 1);
}
}
}
What is bothering me is that when the app starts I can see the dots being drawn on the panel. Even if it is very quick it still bothers me a lot.
Is it possible to draw the dots on the panel and load it directly drawn?
Thank you for the help
You could create a bitmap and draw it instead.
But before you do that: DrawEllipse is a little expensive. Use DrawLine with a Pen that has a dotted linestyle instead:
int onePercentWidth = panel1.ClientSize.Width / 100;
using (Pen my_pen = new Pen(Color.Gray, 1f))
{
my_pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Custom;
my_pen.DashPattern = new float[] { 1F, onePercentWidth -1 };
for (int y = onePercentWidth; y < panel1.ClientSize.Height - 1; y += onePercentWidth)
e.Graphics.DrawLine(my_pen, 0, y, panel1.ClientSize.Width, y);
}
Note that I am using using so I don't leak the Pen and ClientSize so I use only the inner width. Also note the exaplanation about the custom DashPattern on MSDN

Issue with mouse clicking a rectangle in an array

So, I'm attempting to create a grid on the screen, and to do so, I've implemented a multidimensional array of Rectangles.
When the program starts, I use a for loop to increase the x and y coordinates to form the grid.
public Form1() {
InitializeComponent();
for (int x = 0; x < 12; x++) {
for (int y = 0; y < 12; y++) {
recArray[x, y] = new Rectangle(y * 50, x * 50, 100, 100);
}
Application.DoEvents();
}
}
My issue is trying to figure out when the user has clicked on a rectangle, and furthermore, which rectangle in the array that he/she has clicked on. As I will change the border to red when given the correct rectangle.
I'm using Visual Studio 2008, and here is my code so far.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace Quoridor {
public partial class Form1 : Form {
private Pen pen = Pens.Black;
Rectangle[,] recArray = new Rectangle[12, 12];
public Form1() {
InitializeComponent();
for (int x = 0; x < 12; x++) {
for (int y = 0; y < 12; y++) {
recArray[x, y] = new Rectangle(y * 50, x * 50, 100, 100);
}
Application.DoEvents();
}
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
for (int x = 0; x < 12; x++) {
for (int y = 0; y < 12; y++) {
e.Graphics.DrawRectangle(pen, recArray[x, y]);
Application.DoEvents();
}
}
}
private void Form1_Click(object sender, EventArgs e) {
Point cursor = this.PointToClient(Cursor.Position);
Refresh();
}
}
}
I'm making this into a real game, with classes and all. But keep in mind, this is my second month programming, so don't be harsh ^_^
First, a couple of pointers:
You are storing your calculated y coordinate as the x position of your rectangle and vice versa. Switch the first two arguments of the Rectangle constructor to resolve this.
Use the MouseClick event instead of the Click event. The former provides you with a MouseEventArgs that contains the coordinate of the click relative to your Form.
Your rectangles are currently overlapping, since they are 100 x 100 but are positioned 50 pixels apart. This appears to be unintended (as a click would then usually land on 2 rectangles). This is also why your grid appears to be 13x13 instead of 12x12. To resolve this, pass 50 instead of 100 as the width and height of your Rectangles.
You could then determine the clicked Rectangle as follows:
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
Rectangle clickedRectangle = FindClickedRectangle(e.Location);
if (!clickedRectangle.IsEmpty)
Console.WriteLine("X: {0} Y: {1}", clickedRectangle.X, clickedRectangle.Y);
}
private Rectangle FindClickedRectangle(Point point)
{
// Calculate the x and y indices in the grid the user clicked
int x = point.X / 50;
int y = point.Y / 50;
// Check if the x and y indices are valid
if (x < recArray.GetLength(0) && y < recArray.GetLength(1))
return recArray[x, y];
return Rectangle.Empty;
}
Notice that I determine the clicked rectangle using a straightforward calculation, which is possible because your rectangles are positioned in a grid. If you intend to depart from the grid layout, looping over all rectangles and hit testing them using Rectangle.Contains(Point) is an alternative solution.

The buttons in project delete the squares?

I have problem with my C# winform project.
I have function that draw squares:
public void DrawingSquares(int x, int y)
{
System.Drawing.Graphics graphicsObj;
graphicsObj = this.CreateGraphics();
Pen myPen = new Pen(System.Drawing.Color.Black, 5);
Rectangle myRectangle = new Rectangle(x, y, 100, 100);
graphicsObj.DrawRectangle(myPen, myRectangle);
}
private void button1_Click(object sender, EventArgs e)
{
z = Convert.ToInt16(textBox1.Text)-1;
k = Convert.ToInt16(textBox2.Text)-1;
DrawAllSquares();
}
private void DrawAllSquares()
{
int tempy = y;
for (int i = 0; i < z; i++)
{
DrawingSquares(x, y);
for (int j = 0; j < k - 1; j++)
{
tempy += 50;
DrawingSquares(x, tempy);
}
x += 50;
tempy = y;
}
}
In my project, I have a function that I use to move button around the form at runtime, but when the button is moved onto the drawing the drawing is deleted.
What can I do to make the drawing permanent?
If you need permanently (in terms of application life time), by any means, you need to use it inside you Control's (the Control where rectangle is have to be drawn), OnPaint method.
If you need an animation too: it could be resolved by using a timer and changing coordinates that you pass like a parameters to your DrawSquares.
Hope this helps.
EDIT
A pseudocode:
public class MyControl : Control
{
public override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
DrawingSquares(e.Graphics, valueX, valueY);
}
public void DrawingSquares(Graphics graphicsObj, int x, int y)
{
Pen myPen = new Pen(System.Drawing.Color.Black, 5);
Rectangle myRectangle = new Rectangle(x, y, 100, 100);
graphicsObj.DrawRectangle(myPen, myRectangle);
}
}
valueX and valueY are relative X and Y coordinates where you want the rectangle to be drawn.
These coordinates can be constant values, or you can change them from some timer and call Invalidate() on MyControl, so paint will be executed.

Adding a background graphic to http handler image

the following code is a handler that takes in a Percent (percent of the graph to show as blue), Max (maximum value), and a Gallons value (number) to create a thermometer-style progress meter. It outputs a graph which is added to a web form as <img src="Thermometer.ashx" alt="" />.
Since this graph is intended to show progress as the number of gallons of water saved, it would be nice to have the blue color fill up a water barrel image. To do so I have attempted to add a barrel image with a transparent interior to the web form and tried to position it in front of the thermometer image but this has not worked, as it seems impossible to layer an image on top of one created using a handler.
My question is this: is it possible to added the barrel image through code so that is serves as the outline for the blue thermometer? Or does the barrel image need to be created from scratch in the code? I fear the latter since I am probably unable to code it.
Please see below and let me know if there is anything I can do.
Thank you!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Drawing;
using System.Net;
using System.Drawing.Imaging;
using System.Web.UI.HtmlControls;
namespace rainbarrel
{
public class Thermometer : IHttpHandler
{
public bool IsReusable
{
get { return false; }
}
public void ProcessRequest(HttpContext context)
{
int Percent = 25;
int Max = 20000;
bool Gallon = true;
float Pixels = (float)Percent * 2.15f;
Bitmap TempImage = new Bitmap(200, 250);
Graphics TempGraphics = Graphics.FromImage(TempImage);
TempGraphics.FillRectangle(Brushes.White, new Rectangle(0, 0, 200, 250));
Pen TempPen = new Pen(Color.Black);
BarrelFill(TempImage, Percent);
bool DrawText = true;
float Amount = Max;
for (float y = 20.0f; y < 235.0f; y += 10.75f)
{
TempGraphics.DrawLine(TempPen, 119, y, 125, y);
if (DrawText)
{
Font TempFont = new Font("Arial", 8.0f, FontStyle.Regular);
if (Gallon)
{
TempGraphics.DrawString(Amount.ToString() + " Gal", TempFont, Brushes.Black, 130, y);
}
else
{
TempGraphics.DrawString(Amount.ToString(), TempFont, Brushes.Black, 130, y);
}
DrawText = false;
}
else DrawText = true;
Amount -= ((Max / 100) * 5.0f);
}
string etag = "\"" + Percent.GetHashCode() + "\"";
string incomingEtag = context.Request.Headers["If-None-Match"];
context.Response.Cache.SetExpires(DateTime.Now.ToUniversalTime().AddDays(1));
context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.Cache.SetMaxAge(new TimeSpan(7, 0, 0, 0));
context.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
context.Response.Cache.SetETag(etag);
if (String.Compare(incomingEtag, etag) == 0)
{
context.Response.StatusCode = (int)HttpStatusCode.NotModified;
context.Response.End();
}
else
{
context.Response.ContentType = "image/Gif";
TempImage.Save(context.Response.OutputStream, ImageFormat.Gif);
TempImage.MakeTransparent();
}
}
private void BarrelFill(Bitmap TempImage, int Percent)
{
if (Percent == 100)
{
FillRectangle(TempImage, 60, 20, 235);
}
else
{
FillRectangle(TempImage, 60, (int)(235.0f - ((float)Percent * 2.15f)), 235);
}
}
private void FillRectangle(Bitmap TempImage, int x, int y1, int y2)
{
int MaxDistance = 50;
for (int i = x - MaxDistance; i < x + MaxDistance; ++i)
{
for (int j = y1; j < y2; ++j)
{
float Distance = (float)Math.Abs((i - x));
int BlueColor = (int)(255.0f * (1.0 - (Distance / (2.0f * MaxDistance))));
TempImage.SetPixel(i, j, Color.FromArgb(30, 144, BlueColor));
}
}
}
}
}
UPDATE:
I found this post here which showed me how to solve:
overlaying images with GDI+
Here is my solution:
Image Barrel = Image.FromFile("C:\\Inetpub\\wwwroot\\barrel\\barrel_trans.png");
TempGraphics.DrawImage(Barrel, -5, 0, 120, 240);
Barrel.Dispose();
However, The quality of the png layer is bad (slightly blurry), although the original is very sharp and clear. Is there a way to retain the image quality of the original?
Many thanks in advance for your help.
Changing TempImage.Save(context.Response.OutputStream, ImageFormat.gif); to Jpeg solved the quality issue.
I believe that the best way to accomplish this would be to create the entire image in C# and send that down. You can get exact positioning of the barrel over the 'thermometer', and if the barrel is already a (semi) transparent PNG, you can just do a .DrawImage right over the thermostat, add text, and do anything you want before sending back the final image directly into the ResponseStream.
A couple of other suggestions for you:
Since you are handing everything directly in your Handler, you should dispose of your own internal disposable resources. "Using" blocks are the easiest way. You will need at least these two:
using (Graphics TempGraphics = GetGraphicsFromBitmap(TempImage))
{
for (float y = 20.0f; y < 235.0f; y += 10.75f)
{
TempGraphics.DrawLine(TempPen, 119, y, 125, y);
...
}
}
and
using (Font TempFont = new Font("Arial", 8.0f, FontStyle.Regular))
{
...
}

Categories