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.
Related
I am trying to draw a pixel array, which contains alpha values (it is supposed to be a grayscale picture) on a texture in OpenGL ES on Android.
Background information
The pixel array is generated by FreeType 2, I have checked the contents of the array and it looks OK to me.
So for each letter FreeType 2 generates, I need to draw it on the screen. In case you want to know it more specifically, it is the bootanimation. And I want to draw a text over the animation.
Current result
This is what I get:
IMPORTANT: i have removed the white boxes as explained in Edit 4 but I still don't get any letters.
The white squares are supposed to be letters.
Code snippet for drawing the pixel buffers of the letters:
void BootAnimation::drawFtBitmap(FT_Bitmap *bitmap, int, int) {
int width = abs(bitmap->pitch);
int height = bitmap->rows;
/*// Round up to power of 2
width--;
width |= width >> 1;
width |= width >> 2;
width |= width >> 4;
width |= width >> 8;
width |= width >> 16;
width++;
height--;
height |= height >> 1;
height |= height >> 2;
height |= height >> 4;
height |= height >> 8;
height |= height >> 16;
height++;*/
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glEnable(GL_BLEND);
glEnable(GL_TEXTURE_2D);
glGenTextures(1, &mBootStatusTex.name);
glBindTexture(GL_TEXTURE_2D, mBootStatusTex.name);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA,
width, height,
0, GL_ALPHA, GL_UNSIGNED_BYTE, bitmap->buffer);
glBindTexture(GL_TEXTURE_2D, 0);
glDeleteTextures(1, &mBootStatusTex.name);
}
Additional information and explanation
mBootStatusTex is a Texture.
The array is a unsigned byte array.
The size of the array is set by bitmap->pitch * bitmap->rows.
bitmap->pitch can be seen as the width of the image, but using bitmap->width wouldn't make a difference in this case.
I am a beginner in OpenGL ES, I never did anything like this before. My goal is to draw text which has a specific font on top of the animation.
The problem is simply that instead of getting text, I just get those white boxes.
What I have tried
I have tried removing glDrawTexiOES, but then it doesn't show anything.
I tried using both GL_ALPHA and GL_LUMINANCE, both same result.
And much more...
If you need more information, please let me know. I hope the code above is enough.
Edit: The height and width is wrong. Because I round it up. I shiuld better use some kind of crop or something. But that doesn't fix the problem with the white boxes.
Edit 2: The problem is that it does not draw anything. I pass the array, it has various values but nothing is drawn on the screen. Am I doing something wrong?
Edit 3: I checked the error code for each and every call. No errors.
Edit 4: I updated the code above. Still not working. I removed the glDrawTexiOES call because the only thing it does is drawing solid boxes. glTexImage2D is not drawing anything yet. At least I can't see it. Even if I round the size up to power of 2, it doesn't work. In the updated code above you can see how I am trying to use mipmaps. That way it should handle it automatically. Also tried using glSubTexImage2D. Doesn't work.
Edit 5: I also don't know how to set the position of the texture. Would the following (pseudo-code like) snippet work?
glLoadIdentity();
glPushMatrix();
glTranslatef(x, y, 0);
glPopMatrix();
Edit 6: I also tried (just as an experiment) drawing each and every single pixel using glDrawTexiOES. While it works fine for a few drawings, it doesn't for the all the buffers. If i draw the complete text, it is distorted for the most part and the whole screen has random colors. So this isn't a way to go.
Also tried overlaying the surface with another, but transparent surface, but it doesn't work too. I discarded my changes since this isn't working how I wanted. I just wanted a simple way of drawing text on top of that bootanimation.
In my application I have a custom view that renders some bitmaps and draws them to the view's canvas using onDraw(). The canvas is filled with a color at first. Essentially I have the following code:
public static int COLOR = Color.rgb(200, 50, 50);
#Override
public void onDraw(Canvas canvas) {
canvas.drawColor(COLOR);
Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565);
Canvas c = new Canvas(bitmap);
c.drawColor(COLOR);
canvas.drawBitmap(bitmap, 0, 0, null);
}
I'm expecting the code to create a continuously red screen; the bitmap is rendered in a different shade of red though, so its position is visible. To analyze the colors I made a screenshot of it - the bitmap is drawn with (206,48,49) instead of (200,50,50).
Obviously this must have to do with the bitmap using RGB_565 instead of ARGB_8888 (which I don't want to use though). So my question is, how can I fill the view's canvas with a RGB_565 color in order to work around those color issues?
I tried converting (200,50,50) to RGB_565 by dropping the least significant bits (red >> 3, green >> 2, blue >> 3), but of course that doesn't make a difference here. What does Android do internally to get (206,48,49) from (200,50,50)? Where is my error in reasoning?
Finally figured this out myself...
In my onDraw() method, there are two implicit color space conversions:
Draw (200,50,50) (RGB_888) on RGB_565 bitmap.
Draw RGB_565 bitmap on RGB_888 canvas.
(200,50,50) is equal to (25,12,6) in RGB_565 (red >> 3, green >> 2, blue >> 3). Besides,
not surprisingly, (206,48,49) is also (25,12,6) in RGB_565.
Now when you convert (25,12,6) back to RGB_888, e.g. using this algorithm, you get (206,49,49) - close enough. I really can't explain why Android returns 48 instead of 49 for the green channel, though. Maybe it's a rounding error or floating point imprecision. A look at the Android source might help, but since this is not an earth-shattering problem, I'll give it a pass.
let's say i have this bitmap, which is a random shape painted all black, and say i want to be bale to change it color, does my bitmap have to be all painted white first or is there something else to it?
If you're using Canvas the way to alter the bitmap's color is to alter the bitmap itself. The steps involved are as follows:
Say you want to load an existing Bitmap you have somewhere and you want to tint it red somehow.
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bitmap);
After that you want to modify the bitmap's pixels before you paint it onto the Canvas. You create an int array that will holds all your pixels.
int[] pixels;
bitmap.getPixels(pixels, 0, 0, 0, 0, width, height);
After that you need to modify the array (say, adding to the red component). However, right now all we have is int values inside a pixel array. R,G and B are all packed inside. How to retrieve them?
int red = Color.red(pixels[n]);
int green = Color.green(pixels[n]);
int blue= Color.blue(pixels[n]);
Then you modify the pixel's value by whatever you want, you could put it in a loop or however you like, and then put it back to the pixels array. Also, RGB values go from 0-255 because they are 8-bit values.
Right after that you would put them back using exactly the opposite function.
bitmap.setPixels(pixels, 0, 0, 0, 0, width, height);
And then you're ready to go calling Canvas.drawBitmap();
Keep in mind that this process ought to be slow if you do it frequently, besides Canvas is a slow way of doing thing's if you're interested in real-time apps such as games.
Hope it helped!
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.
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