I'm successfully generating my textures using GLUtils.texImage2D,
but when I use the textures generated I get problems with my alpha: they are darker than wanted.
after having checked several things I finally got the the conclusions that the problem comes from GLUtils.texImage2D(GL10.GL_TEXTURE_2D, level, bmp, 0);
I created a second function that uses gl.glTexImage2D(GL10.GL_TEXTURE_2D, level, GL10.GL_RGBA, width, height, 0, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, pixels2);
but it is costly in processing to create pixels2 which is a bytebuffer in which I have to recopy the bytes while changing the values from the bitmap ARGB to texture RGBA.
Has anybody noticed that ? and if so how did you solve this...
jason
Thank you for your answer,
I'm already using
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
and I'm getting this problem
my problem is that the alpha generated by GLUtils isn't the one of the texture, its darker.
the difference is like looking at a color in the sun and in the shade (if that makes any sence).
I already tried gl.gltextimage2d but the creating the buffer takes too long, unless there is a tool to convert a bitmap to a byte buffer that I don't know of...
GLUtils.texImage2D generates a premultiplied-alpha texture. In this generated texture, all pixels are multiplied by alpha value, so you don't need to multiply alpha value once again.
Let's try
gl.glBlendFunc(GL10.GL_ONE, GL10.GL_ONE_MINUS_SRC_ALPHA);
The alpha channel is the poor mistreated stepchild of a huge number of programmers is all I can say... but the upload works fairly efficient if you do that:
Estimate your largest texture (like 1024x1024) and create an int array of that size (1024*1024) that you store in some static variable or somewhere where you can access it so that you don't need to recreate that array (allocation time is precious here)
then do this:
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, GL10.GL_RGBA, width, height,
0, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, IntBuffer.wrap(pixels));
I am sorry not having found a different solution... the original problem is, that the implementor of the GLUtils.texImage2D function has mistreated the alpha channel somehow resulting in black borders when the image is displayed larger than it is (the bilinear filter calculates the color between four pixels and when the rgb values of the pixels with transparent alphas have been mangled (like set to black), the result is, that there's some kind of a "color bleeding" happening over the transparent border that is forming there. Very ugly. Maybe it was done to improve the compression ratio as the RGB values in alpha transparent areas in PSDs contain a lot of junk that when eliminated yield a lot of room of improvement for compression algorithms)
Edit: Sadly, this approach was only working for grey images correctly as the red and blue channel is swapped when fetching the pixels from the bitmap. At least on MY device. I am not sure how correctly this is for other devices, but in my case, this here did the trick:
for (int i=0;i<pixels.length;i+=1) {
int argb = pixels[i];
pixels[i] = argb&0xff00ff00 | ((argb&0xff)<<16) | ((argb>>16)&0xff);
}
Solution is found here. It is related as is stated by others with premultiplied alpha.
In the surfaceView Constructor
setEGLConfigChooser(8, 8, 8, 8, 0, 0);
getHolder().setFormat(PixelFormat.RGBA_8888);
In the View.Renderer onSurfaceCreated
gl.glEnable(GL10.GL_BLEND);
gl.glBlendFunc(GL10.GL_ONE, GL10.GL_ONE_MINUS_SRC_ALPHA);
Android Bitmap stores images loaded from PNG with premultiplied colors. GLUtils.texImage2D also uses colors premultiplied by alpha, so you can't get original colours this way.
In order to load PNG images without RGB channels being premultiplied I use 3rd party PNGDecoder and load texture with glTexImage2D. You can get PNGDecoder library to decode PNG from here: http://twl.l33tlabs.org/#downloads
There is an issues in GLUtils with premultiplied alpha. The only workaround that I can propose is to use:
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA)
In case you need other blend functions you will have to use gl.glTexImage2D.
Android's BitmapFactory.decode() premultiplies alpha by default on loading.
So if you don't want to load premultiplied bitmaps, use Bitmap.Options with inPremultiplied set to true when loading the bitmap for texture:
//kotlin
val options = BitmapFactory.Options()
options.inPremultiplied = false
val bitmap = BitmapFactory.decodeStream(inputStream, null, options)
Then pass this bitmap to GLUtils.texImage2D
P.S.
Nice video for understanding premultiplied alpha:
https://www.youtube.com/watch?v=wVkLeaWQOlQ
Related
I would like to create an interactive 2D effect that I can put on anything. So I want to take a mostly transparent effect, render it to a texture, and put it wherever I want simply by putting it on a square.
The problem I encountered is that I can't get rid of the background color. When I put the effect over an object, the background color of the effect blocks out the object that I want to put the effect over.
Here is my code. Can anybody tell me what I'm missing?
Drawing:
GLES20.glClearColor(0.6f, 0.34f, 0.14f, 0.0f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
GLES20.glDisable(GLES20.GL_DEPTH_TEST);
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
ball.draw(); //This is the object a simple square draw with 2 triangles
particleSystem.renderToTexture(); //effect rendering to texture and drawing it
The render to texture code code:
public void renderToTexture(){
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fb[0]);
GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, renderTex[0], 0);
GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT, GLES20.GL_RENDERBUFFER, depthRb[0]);
int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
drawRender(); //draws the effect on a fbo and saving the texture to renderTex[0]
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, renderTex[0]);
drawEffect(); //draws a square with the effect texture on it
The problem should be here somewhere, but I have no idea what to do. I tried everything I could think of. I even triend to dispose of the background color in the shader.
The posted code looks perfectly fine. You will need to make sure that the texture you use for the color render target (renderTex[0]) has an alpha component.
Note that the number of texture formats in ES 2.0 that are guaranteed to be color-renderable is very limited. The only two with an alpha component (see table 4.5. in the spec) are GL_RGBA4 and GL_RGB5_A1. Most notably, this does not include GL_RGBA with 8 bits per component.
So for defining a color-renderable texture with alpha component that is guaranteed to work across all ES 2.0 implementations, you will have to use GL_RGBA for the internal format, and GL_UNSIGNED_SHORT_4_4_4_4 or GL_UNSIGNED_SHORT_5_5_5_1 for the type argument of glTexImage2D().
Most common devices (at least all the ones I have seen) do support the OES_rgb8_rgba8 extension, which adds support for rendering to 8 bit component textures. But if you want to be completely portable, you should check for the presence of this extension before using render targets with those formats.
I know this question is old, but i also encountered it right now and the solution for me was to do something with the GLSurfaceView. Just add these lines after setting the context
glSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 0, 0);
glSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
I was already using GL_RGBA format and was really annoyed that nothing else was working, but this worked like magic.
I have a bitmap and want to be able to change all black pixels in that bitmap to blue. I know you can do this via Bitmap.setPixel but that process is extremely slow (believe me, I tried it...even doing the setPixels instead of setPixel).
Researching this is see where people recommend using PorterDuff Xor, but there isn't any posts on how this was successfully done. Lots of people asking...no one spelling out the answer.
So using, paint, bitmap, and canvas, how do you change every black pixel to all blue ones?
Thanks!
you just pull the pixels of the bitmap
myBitmap.getPixels(myPixels, 0 0, 0, 0, myBitmap.getWidth(), myBitmap.getHeight())
and loop over myPixels looking for whatever color you wish and modifying that pixel to whatever color you prefer.
For various reasons I'm implementing a class that can be used similarly to Bitmap in Android, holding pixel data for when an actual Bitmap needs to be resized before getting passed into OpenGL (Power of 2 size thing)
I am using an int[] to hold pixel data, so basically, I my code is like:
width2 = nearestPowerOf2(width);
height2 = nearestPowerOf2(height);
int[] pixels = new int[width2 * height2];
bitmap.getPixels(pixels, 0, width2, 0, 0, bitmap.getWidth(), bitmap.getHeight());
Now, taking a step back for a moment, if I create a canvas and use canvas.draw() to put the smaller bitmap onto a larger, properly sized image, and bind it in OpenGL, it draws perfectly.
If, however, I take my pixel data (wrapped in an IntBuffer) and bind it, the alpha values, are wrong. For completeness, this is how I'm binding:
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width2, height2, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, pixelsBuffer);
I don't understand why that should happen.
I've checked the format of the bitmaps and they are all ARGB_8888. And, again, if I draw the bitmap directly, or use Bitmap.createBitmap(), it works, but using getPixels doesn't?
(The reason I can't use a Bitmap & Canvas, as one would usually use is that, if I do, we are running out of native on certain devices, which are, apparently, very lazy about freeing native memory. Yes, I was using Bitmap.recycle(), I was setting the bitmap to null, and I was ensuring the canvas was also set to null. Only a few devices had the problem, but enough that I needed a work around.)
Why do you think alpha is incorrect? I think only red and blue components are swapped. That's because android Bitmap stores pixels in RGBA order, but OpenGL ES expects them in BGRA. See here: http://developer.android.com/reference/android/graphics/Color.html
The components are stored as follows (alpha << 24) | (red << 16) | (green << 8) | blue
OpenGL expects color as (alpha << 24) | (blue << 16) | (green << 8) | red.
So just swap blue and red component, and you'll be OK.
Alternative way would be to use GL_EXT_texture_format_BGRA888 extension to specify texture format as GL_BGRA_EXT.
Pixels coming out of Android's Bitmap will be ARGB as you have noted, then you pass them into the glTexImage2D, which expects pixels to be in RGBA. I am not sure that OpenGL supports ARGB type input, otherwise you could do something like:
GLES20.glTexImage2D( GLES20.GL_TEXTURE_2D,
0,
GLES20.GL_RGBA,
width2,
height2,
0,
GLES20.GL_ARGB, /** THIS ONE **/
GLES20.GL_UNSIGNED_BYTE,
pixelsBuffer );
Otherwise, you will have to rearrange what you got from Bitmap into RGBA before feeding it into GL.
I have an app that I've been repeatedly playing with in android, it uses opengl-es.
Currently I load textures from a bitmap like so:
//Load up and flip the texture - then dispose the temp
Bitmap temp = BitmapFactory.decodeResource(Deflecticon.getContext().getResources(), resourceID);
Bitmap bmp = Bitmap.createBitmap(temp, 0, 0, temp.getWidth(), temp.getHeight(), flip, true);
temp.recycle();
//Bind the texture in memory
gl.glBindTexture(GL10.GL_TEXTURE_2D, id);
//Set the parameters of the texture.
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
//On to the GPU
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bmp, 0);
The obvious issue is that the texture I'm using has to be a power of 2. At the moment I'm pre-editing the textures in photoshop to be a power of 2 and simply have empty borders. However this is a little tedious and I want to be able to load them as they are .. recognise they aren't a power of 2 and load them into a texture that is.
I know I could scale the bitmap to become a power of 2 size and simply stretch the texture but I do not wish to stretch the texture and in some cases may want to put several textures into one "atlas".
I know I can use glTexSubImage2D() to paste into the texture the data I want at the origin I want. This is great!
However I do not know how in Android to initialise a texture with no data?
In this question previously asked the suggestion was to call glTexImage2D() with no data and to then fill it.
However in android when you call "GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bmp, 0);" you do not specify a width / height. It reads this from the bitmap I assume.
What is the best way to do this? Can I create a new bitmap of the right power of 2 size only blank and not filled with any data and use this to initialise the texture then paste into it using subImage? Or should I make a new bitmap somehow copy the pixels I want (not sure if you can do this easily) into this new bitmap (leaving borders) and then just use this?
Edit: clarified that I'm using opengl.
I think if you tried creating a bitmap with the power of 2 axis sizes and then add your bitmap it should work just fine. maybe something like
Bitmap.createBitmap(notPowerOf2Bitmap, offx, offy, xsize, ysize, bitmapFlag)
other than that, I would say suffer through the photoshop process. How many pictures you got?
Non-power-of-two (NPOT) bitmaps are supported on some GLES platforms, but you have to check to see if the appropriate extension exists. Note, however, that at least on PowerVR SGX, even though NPOT is supported, there are still some other fairly arbitrary restrictions; for example, your texture width must be a multiple of 2 (if not a power of 2). Also, NPOT rendering tends to be a bit slower on many GPUs.
One thing you can do is just create a wrapper texture which is a power-of-two size and then use glTexSubimage2D to upload the texture to cover only part of that, and then adjust your texture coordinates accordingly. The obvious drawback to this is that you can't use texture wrapping in that circumstance. If you absolutely must support wrapping, you could just scale your texture to the nearest power-of-two size before you call glTexImage2D, although this usually introduces sampling artifacts and makes things blurry, especially if you're trying to do pixel-precise 2D work.
Another thing you might consider, if you don't need to support wrapping, is to make a "texture atlas," in which you condense all of your textures into a few big textures, and have your polygons map to just some portions of the texture atlas(es). You have to be careful when generating MIPmaps, but other than that it usually provides a pretty nice performance benefit, as well as making more efficient use of texture memory since you're not wasting so much on padded or scaled images.
I have 2 solutions I have employed for this problem. I can be more specific if necessary, but conceptually you can: -
Make the image a power of 2, and the section to crop you fill with 100% alpha channel and load the images with alpha enabled.
Tweak your texture vector / buffer so it doesn't load that section. So instead of using the default
float texture[] = {
0.0f, 1.0f, //
1.0f, 1.0f, //
0.0f, 0.0f, //
1.0f, 0.0f, //
};
as the matrix (obviously this is for loading an image to a 2 triangled square), factor back by ratio the area to crop, eg.
float texture[] = {
0.0f, 0.75f, //
0.9f, 0.75f, //
0.0f, 0.0f, //
0.9f, 0.0f, //
};
of course, be precise with your math or the unwanted bit may bleed in, or you'll cut out some of the real image. Obviously this array is calculated on the fly and not hard-coded as I have demonstrated here.
Uh why don't you create two bitmaps. Load the first one as you're doing then use createBitmapScaled to turn that bitmap into a power of two. Performance-wise I don't know if it is the fastest method possible but it works.
YOu can use GLES20.glTexImage2D() to create a empty texture with specified width and height. The example code is
public static int genTexture(int texWidth, int texHeight) {
int[] textureIds = new int[1];
GLES20.glGenTextures(1, textureIds, 0);
assertNoError();
int textureId = textureIds[0];
texWidth = Utils.nextPowerOf2(texWidth);
texHeight = Utils.nextPowerOf2(texHeight);
GLES20.glBindTexture(GL11.GL_TEXTURE_2D, textureId);
GLES20.glTexParameteri(
GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(
GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA,
texWidth, texHeight, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
return textureId;
}
I would like to draw you attention to the following pictures
First the original image
then how it is rendered on the screen
as you can see on the original everything is nice and yellow, the edges have a slight transparency to look smooth.
but when I render I have some darkened pixels that appear.
To avoid the usual answer I get
I use
gl.glTexImage2D(GL10.GL_TEXTURE_2D, level, GL10.GL_RGBA, width, height, 0, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, pixelBuffer);
and not the glutil tool
also
gl.glEnable(GL10.GL_BLEND);
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
Anybody has a solution for that ?
Thanks
Jason
Edit
I did some digging. in the original image my background is already the same color as the coin and I use an alpha mask.
I've also checked, when I load the bitmap it seems that all the bitmap who have an alpha 0 are automatically set to 0,0,0,0. this might be the problem.
edit
actually it seems to be the issue. I checked the values of the bitmap I open, the values for transparent pixels are 0,0,0,0 instead of the color I set. Is this a problem with Bitmap or how I created my image?
// Load up, and flip the texture:
Bitmap temp = BitmapFactory.decodeResource(context.getResources(), resource, opts);
Bitmap bmp = Bitmap.createBitmap(temp, 0, 0, temp.getWidth(), temp.getHeight(), flip, true);
temp.recycle();
What is the background color of your image? i.e. what does it look like when you remove the alpha channel? I'll guess - it's black? Try giving the background color the same color as your logo (i.e. yellowish) and try again.
EDIT: May as well edit the answer based on the comments. Even if you properly set the RGB color of the surrounding pixels, some image compression formats might still "optimize" that out and turn all completely transparent pixels into (0,0,0,0), which will cause the problems you describe when using bilinear filtering. Try using a different file format, or do research to see if you can turn this feature off in that particular compression, or consider fixing up the image in code after loading.
Your image probably has premultipled alpha values. Try using gl.glBlendFunc(GL10.GL_ONE, GL10.GL_ONE_MINUS_SRC_ALPHA);
See this post for more details.