Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

h2d.Graphics accuracy #776

Open
ncannasse opened this issue Mar 28, 2020 · 8 comments
Open

h2d.Graphics accuracy #776

ncannasse opened this issue Mar 28, 2020 · 8 comments

Comments

@ncannasse
Copy link
Member

Using the following sample, I was about to see that h2d.Graphics was not very accurate when drawing lines using various primitives:

  • drawRect is filling correctly (0,0,w,h) with fill but lines are (-1,-1,w,h)
  • drawRoundRect is missing some lines, must be an issue with line calculus
  • when we have a small offset <1 (O key) the lines are getting outside of the filled shape (green pixels)
  • circle/ellipsis seems ok
  • scaling seems ok
  • didn't check on JS (only HL)
class Main extends hxd.App {

	var tex : h3d.mat.Texture;
	var kind : Int;
	var evenSize = true;
	var pixelOffset = false;
	var MAX = 3;
	var scale = 0;

	override function init() {
		tex = new h3d.mat.Texture(255,255,[Target]);
		var bmp = new h2d.Bitmap(h2d.Tile.fromTexture(tex), s2d);
		bmp.x = 222;
		bmp.y = 111;
		bmp.scale(4);
		draw();
	}

	function draw() {
		var group = new h2d.Object();
		group.x = group.y = 4;
		group.scale(Math.pow(1.4,scale));

		if( pixelOffset ) {
			group.x += 0.5;
			group.y += 0.3;
		}

		var width = 63;
		var height = 17;

		if( evenSize ) {
			width++;
			height++;
		}

		if( kind == 2 ) height = width;

		var bmp = new h2d.Bitmap(h2d.Tile.fromColor(0x0000FF,width,height),group);

		var g = new h2d.Graphics(group);
		g.lineStyle(1 / group.scaleX, 0x00A000);
		g.beginFill(0xFF0000);
		switch( kind ) {
		case 0:
			g.drawRect(0,0,width,height);
		case 1:
			g.drawRoundedRect(0,0,width,height,4);
		case 2:
			g.drawCircle(width*0.5,height*0.5,width*0.5);
		case 3:
			g.drawEllipse(width*0.5,height*0.5,width*0.5,height*0.5);
		}
		g.blendMode = Add;

		tex.clear(0);
		group.drawTo(tex);
	}

	override function update(dt:Float) {
		super.update(dt);
		if( hxd.Key.isPressed(hxd.Key.LEFT) && kind > 0 ) {
			kind--;
			draw();
		}
		if( hxd.Key.isPressed(hxd.Key.RIGHT) && kind < MAX ) {
			kind++;
			draw();
		}
		if( hxd.Key.isPressed(hxd.Key.SPACE) ) {
			evenSize = !evenSize;
			draw();
		}
		if( hxd.Key.isPressed("O".code) ) {
			pixelOffset = !pixelOffset;
			draw();
		}
		if( hxd.Key.isPressed(hxd.Key.NUMPAD_ADD) ) {
			scale++;
			draw();
		}
		if( hxd.Key.isPressed(hxd.Key.NUMPAD_SUB) ) {
			scale--;
			draw();
		}
	}

	static function main() {
		new Main();
	}

}
ncannasse added a commit that referenced this issue Mar 28, 2020
saem pushed a commit to saem/heaps that referenced this issue May 24, 2020
saem pushed a commit to saem/heaps that referenced this issue May 24, 2020
@zommerfelds
Copy link
Contributor

Why do we add 0.01 to the vertices in a rect?

heaps/h2d/Graphics.hx

Lines 467 to 473 in 50e0707

var e = 0.01; // see #776
tmpPoints[0].x += e;
tmpPoints[0].y += e;
tmpPoints[1].y += e;
tmpPoints[3].x += e;
tmpPoints[4].x += e;
tmpPoints[4].y += e;

In my case it seems more accurate if I remove this code.

@ncannasse
Copy link
Member Author

Try to compile & run the example, then take a screenshot and look at the overlap between Graphics rect and Bitmap rect. Both should cove exactly the same zone

@zommerfelds
Copy link
Contributor

I compiled your example. If you multiply the line size by 3, it seems like the problem is that the line width (3 pixels) can't possibly be centered around the rectangle boundaries:

image

If I run your example (line size = 1) with my #969, I get:

image

Now my question is: is it actually bad to have the lines not be drawn inside the rectangle? I doesn't seem much worse to me than only fixing the issues when the object offset aligns exactly with a pixel.

What makes the problem a bit hard is that in Graphics.hx it's impossible to know the pixel alignment (also given viewport transformations, e.g. using Camera).

What do you think of the following idea:

Make shape borders be calculated inside (or outside) the shape. We don't need to know pixel alignment for this and the borders can be calculated perfectly. This would require calculating separate line offsets for each shape (easier) or creating a generic line drawing algorithm (maybe with modes Center, InsideClockwise, OutsideClockwise)

image

@ncannasse
Copy link
Member Author

ncannasse commented Jun 28, 2021 via email

@zommerfelds
Copy link
Contributor

Well unless x=55 is the rightmost point of a horizontal line, then the pixel at 55 should be empty and start at 54, right?

So what do you think of the inset vs outset idea? This would allow pixel perfect rendering.

@zommerfelds
Copy link
Contributor

I just updated #969.

It now draws all the examples nicely.

image

The only thing is that it now insets all lines as opposed to centering. This is good for pixel perfect rendering, but maybe some users would like to draw lines with their size centered. If you are interested I can add some enum or boolean to allow choosing between those modes.

@ncannasse
Copy link
Member Author

Yes I think we should have a default of "centered". When drawing lines with 1 pixel size, the default should be to match exactly what would happen if you draw on CPU in a bitmap, hence my comment earlier about x=55. A vertical line there should draw the pixel at x=55 and 54 would be empty. For odd line sizes there should be not be any problem as it's adding pixels around this line (3,5,9, etc.) on both sides equally.
For even numbers I think it should use the round up, so a line of size = 2 at x = 55 would cover 55/56

@zommerfelds
Copy link
Contributor

Hi Nicolas, one way to interpret what you are saying is that lines should start and end in the middle of pixels (+0.5/0.5). Adding a 0.5/0.5 offset would work if the Graphics object is pixel aligned, but won't if you can flexibly move the camera around, for example. I don't know how to solve this without adding more complexity to the Base2D shader or generic object rendering.

Would you be interested if I updated my PR to support a default "centered" alignment but use inset (align right of line) for drawing shapes? If I understand the requirements correctly this would solve the problem here. It would also solve the problem of the arbitrary offset that is visible at higher zoom levels.

Personally I find that having the border inside the shape is intuitive (like CSS). But we could leave the option open to the user by having a lineStyle(..., align) parameter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants