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

GWT - pixmap.drawLine() produces "antialias like" artifacts #6019

Closed
1 of 7 tasks
lyze237 opened this issue Apr 24, 2020 · 6 comments
Closed
1 of 7 tasks

GWT - pixmap.drawLine() produces "antialias like" artifacts #6019

lyze237 opened this issue Apr 24, 2020 · 6 comments

Comments

@lyze237
Copy link
Contributor

lyze237 commented Apr 24, 2020

Please ensure you have given all the following requested information in your report.

Issue details

I have a basic pixmap (e.g. 8x8 px), when I draw a line from 0,0 to 8,8 the line produces half transparent pixels at positions where there should be no line.

image

I've tried it with with both blending modes (none, source over) as well with both filtering modes.

Also I've verified that this is a pixmap problem and not a texture problem since the color at 0,1 should be white but it isn't.

Other methods which work fine: pixmap.drawPixel(), pixmap.fillRect()
Other methods which don't work: pixmap.drawRect()

Reproduction steps/code

public class BeakselsTest extends ApplicationAdapter {
    private Pixmap pixmap;
    private Texture texture;

    private ScreenViewport viewport;
    private SpriteBatch batch;

    @Override
    public void create() {
        Gdx.app.setLogLevel(Application.LOG_INFO);

        pixmap = new Pixmap(8, 8, Pixmap.Format.RGBA8888);

        pixmap.setColor(Color.WHITE);
        pixmap.fill();
        pixmap.setColor(Color.BLACK);
        pixmap.drawLine(0, 0, 8, 8);

        texture = new Texture(pixmap);
        viewport = new ScreenViewport();
        batch = new SpriteBatch();

        Gdx.app.log("HEY", new Color(pixmap.getPixel(0, 1)).toString());
    }

    @Override
    public void render() {
        Gdx.gl.glClearColor(0, 0, 0.2f, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        batch.setProjectionMatrix(viewport.getCamera().combined);
        batch.begin();
        batch.draw(texture, 0, 0, 265, 265);
        batch.end();
    }

    @Override
    public void resize(int width, int height) {
        viewport.update(width, height, true);
    }

    @Override
    public void dispose() { }
}
public class HtmlLauncher extends GwtApplication {
    // PADDING is to avoid scrolling in iframes, set to 20 if you have problems
    private static final int PADDING = 0;

    @Override
    public GwtApplicationConfiguration getConfig() {
        GwtApplicationConfiguration cfg = new GwtApplicationConfiguration(Window.getClientWidth() - PADDING, Window.getClientHeight() - PADDING);
        Window.enableScrolling(false);
        Window.setMargin("0");
        Window.addResizeHandler(new ResizeListener());
        cfg.preferFlash = false;
        cfg.log = null;
        return cfg;
    }

    class ResizeListener implements ResizeHandler {
        @Override
        public void onResize(ResizeEvent event) {
            int width = event.getWidth() - PADDING;
            int height = event.getHeight() - PADDING;
            getRootPanel().setWidth("" + width + "px");
            getRootPanel().setHeight("" + height + "px");
            getApplicationListener().resize(width, height);
            Gdx.graphics.setWindowedMode(width, height);
        }
    }

    @Override
    public ApplicationListener createApplicationListener() {
        return new BeakselsTest();
    }
}

Version of LibGDX and/or relevant dependencies

  • libgdx version: 1.9.10
  • old edge
  • chromium edge 83.0.478.13

Please select the affected platforms

  • Android
  • iOS (robovm)
  • iOS (MOE)
  • HTML/GWT
  • Windows
  • Linux
  • MacOS
@Darkyenus
Copy link
Contributor

Darkyenus commented Apr 24, 2020

I have done some investigation and this seems to be a problem with the html canvas API, which is used under the hood and does not actually define how lines should be rendered. A quick search through Stack Overflow reveals, that the only reliable workaround would be to render the geometry pixel-by-pixel.

For anyone wondering: imageSmoothingEnabled indeed does not fix this, neither does CSS image-rendering: pixelated; (optimizeSpeed does change the output, but the line is no longer continuous, so this is not correct either, and it can't be relied upon anyway). (Tested on Firefox in jsfiddle.)

The correct (but slow) fix would be to port the C code to Java(Script) to do this kind of drawing pixel by pixel.

@lyze237
Copy link
Contributor Author

lyze237 commented Apr 24, 2020

Ah that's interesting and quite a bummer.

Since I'm luckily need to only draw really small lines (2-5px) I think I'll work around this by implementing a basic line drawer myself by setting each pixel manually.

Since imo it's only a problem in certain scenario (e.g. if you need pixel precise placement with a small pixmap) I could imagine that some sort of toggle could be done in the pixmap which switches algorithms?

@Darkyenus
Copy link
Contributor

some sort of toggle could be done in the pixmap which switches algorithms?

Yes, that would be one way to do it, but making it nicely compatible with all other platforms could be tricky.

@mgsx-dev
Copy link
Contributor

mgsx-dev commented Oct 2, 2020

another way is to use FBO (probably faster in most cases). FrameBuffer on WebGL 1.0 doesn't have antialiasing at all, and WebGL 2.0 has configurable antialiasing.
So, if you want pixel perfect drawing, maybe the general solution is not to use Pixmap at all but FrameBuffer and ShapeRenderer.
I'm not sure we should use FBO as Pixmap implementation for Web platform though. Since it's an issue for some edge cases (literally :-) ), maybe we could document it, advising to use FBO + ShapeRenderer instead?

@lyze237
Copy link
Contributor Author

lyze237 commented Oct 2, 2020

Since I'm luckily need to only draw really small lines (2-5px) I think I'll work around this by implementing a basic line drawer myself by setting each pixel manually.

Here's the code I wrote a couple months ago. If I remember correctly it's based on Bresenham's line algorithm.

public void drawLine(int x0, int y0, int x1, int y1) {
	boolean steep = Math.abs(y1 - y0) > Math.abs(x1 - x0);
	if (steep) {
		x0 += y0;
		y0 = x0 - y0;
		x0 -= y0;

		x1 += y1;
		y1 = x1 - y1;
		x1 -= y1;
	}
	if (x0 > x1) {
		x0 += x1;
		x1 = x0 - x1;
		x0 -= x1;

		y0 += y1;
		y1 = y0 - y1;
		y0 -= y1;
	}
	int dX = (x1 - x0),
			dY = Math.abs(y1 - y0),
			err = (dX / 2),
			ystep = (y0 < y1 ? 1 : -1),
			y = y0;

	for (int x = x0; x <= x1; ++x) {
		if (steep)
			drawPixel(y, x);
		else
			drawPixel(x, y);
		err = err - dY;
		if (err < 0) {
			y += ystep;
			err += dX;
		}
	}
}

@mgsx-dev
Copy link
Contributor

mgsx-dev commented Oct 2, 2020

Since there is no general solution about this (drawing pixel by pixel may have performance cost), i'm closing it.

I added a note about it in the wiki with a reference to last @lyze237 comment.

@mgsx-dev mgsx-dev closed this as completed Oct 2, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants