question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Loading images on GWT is lossy (particularly visible when using premultiplied alpha)

See original GitHub issue

This is a follow-up to #3316, which was closed, I guess partly due to lack of information. However, I did some more digging and found the root cause of the alpha banding problems in GWT. The summary is: storing pixel data in Pixmap is lossy on GWT, and should be avoided during Texture loading when possible.

A JSFiddle to demonstrate the issue: https://jsfiddle.net/gg9tbejf/

In my answer to the corresponding StackOverflow post, I have described a workaround. Copying here for convenience:

public class GwtTextureLoader extends AsynchronousAssetLoader<Texture, TextureLoader.TextureParameter> {
    TextureData data;
    Texture texture;

    public GwtTextureLoader(FileHandleResolver resolver) {
        super(resolver);
    }

    @Override
    public void loadAsync(AssetManager manager, String fileName, FileHandle fileHandle, TextureLoader.TextureParameter parameter) {
        if (parameter == null || parameter.textureData == null) {
            Pixmap.Format format = null;
            boolean genMipMaps = false;
            texture = null;

            if (parameter != null) {
                format = parameter.format;
                genMipMaps = parameter.genMipMaps;
                texture = parameter.texture;
            }

            // Mostly these few lines changed w.r.t. TextureLoader:
            GwtFileHandle gwtFileHandle = (GwtFileHandle) fileHandle;
            ImageElement imageElement = gwtFileHandle.preloader.images.get(fileHandle.path());
            data = new ImageTextureData(imageElement, format, genMipMaps);
        } else {
            data = parameter.textureData;
            if (!data.isPrepared()) data.prepare();
            texture = parameter.texture;
        }
    }

    @Override
    public Texture loadSync(AssetManager manager, String fileName, FileHandle fileHandle, TextureLoader.TextureParameter parameter) {
        Texture texture = this.texture;
        if (texture != null) {
            texture.load(data);
        } else {
            texture = new Texture(data);
        }
        if (parameter != null) {
            texture.setFilter(parameter.minFilter, parameter.magFilter);
            texture.setWrap(parameter.wrapU, parameter.wrapV);
        }
        return texture;
    }

    @Override
    public Array<AssetDescriptor> getDependencies(String fileName, FileHandle fileHandle, TextureLoader.TextureParameter parameter) {
        return null;
    }
}
public class ImageTextureData implements TextureData {

    private final ImageElement imageElement;
    private final Pixmap.Format format;
    private final boolean useMipMaps;

    public ImageTextureData(ImageElement imageElement, Pixmap.Format format, boolean useMipMaps) {
        this.imageElement = imageElement;
        this.format = format;
        this.useMipMaps = useMipMaps;
    }

    @Override
    public TextureDataType getType() {
        return TextureDataType.Custom;
    }

    @Override
    public boolean isPrepared() {
        return true;
    }

    @Override
    public void prepare() {
    }

    @Override
    public Pixmap consumePixmap() {
        throw new GdxRuntimeException("This TextureData implementation does not use a Pixmap");
    }

    @Override
    public boolean disposePixmap() {
        throw new GdxRuntimeException("This TextureData implementation does not use a Pixmap");
    }

    @Override
    public void consumeCustomData(int target) {
        WebGLRenderingContext gl = ((GwtGL20) Gdx.gl20).gl;
        gl.texImage2D(target, 0, GL20.GL_RGBA, GL20.GL_RGBA, GL20.GL_UNSIGNED_BYTE, imageElement);
        if (useMipMaps) {
            gl.generateMipmap(target);
        }
    }

    @Override
    public int getWidth() {
        return imageElement.getWidth();
    }

    @Override
    public int getHeight() {
        return imageElement.getHeight();
    }

    @Override
    public Pixmap.Format getFormat() {
        return format;
    }

    @Override
    public boolean useMipMaps() {
        return useMipMaps;
    }

    @Override
    public boolean isManaged() {
        return false;
    }
}

This is likely to be more performant too, since less copying is going on. However, it does not attempt to convert images to power-of-two size (because that would of course be lossy again).

Would it make sense to replace the GWT version of TextureLoader with something like this implementation instead? Possibly fixed up to do the lossy power-of-two conversion when needed?

Issue Analytics

  • State:closed
  • Created 8 years ago
  • Comments:6 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
intriguscommented, Jun 16, 2016

I don’t think that anybody is working on a patch, feel free to send a PR 😃

0reactions
WillCalderwoodcommented, Mar 15, 2017

@intrigus @barkholt It looks like this is something to do with the way that Pixmap is handled by the TextureAtlas.

I’ve created a new issue here

Read more comments on GitHub >

github_iconTop Results From Across the Web

Images with an alpha gradient display banding when loaded ...
Issue details Images with an alpha gradient display banding when loaded as part of an atlas. The top image is the region from...
Read more >
Loading image onto MenuItem is losing transparency on pre ...
I am trying to load an image which I believe to be 32bpp with pre-multiplied alpha onto a MenuItem (I followed this guide...
Read more >
Lossless and Alpha Gallery | WebP - Google Developers
This gallery presents some sample images to showcase two new modes of WebP: WebP-lossless and WebP-lossy with alpha (transparency support).
Read more >
Compositing, alpha channels, and adjusting clip opacity
With straight (or unmatted) channels, transparency information is stored only in the alpha channel, not in any of the visible color channels.
Read more >
What is Premultiplied Alpha? A Primer for Artists. - Limnu
An alpha channel is an image mask that controls the visibility of a layer. Usually white means opaque and black means transparent. Masks...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found