I am using a GLES20 texture in my app, which I am loading with texImage2D function. Sometimes I have to modify small area of the loaded texture with bitmap that I create on the fly. Bitmap contains transparency (some pixels have alpha < 255). When I call function texSubImage2D the pixels with alpha values < 255 get replaced with black pixels.
My code (I have removed non-relevant parts) looks like this:
// Set blending function
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
// Create bitmap
Bitmap bitmap = Bitmap.createBitmap(20, 20, Config.ARGB_8888);
// Draw on the bitmap
Canvas c = new Canvas(bitmp);
Paint paint = new Paint();
paint.setStyle(Style.FILL_AND_STROKE);
paint.setColor(Color.argb(100, 255, 0, 0));
c.drawRect(0, 0, 20, 20, paint);
// Bind to the texture I want to modify
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
// Draw on the texture
GLUtils.texSubImage2D(GLES20.GL_TEXTURE_2D, 0, myX, myY, bitmap);
// Free memory
bitmap.recycle();
If I use 255 alpha (change paint.setColor(Color.argb(100, 255, 0, 0)); to paint.setColor(Color.argb(255, 255, 0, 0));) then everything is OK, but alpha is lost of course.
How can I draw a bitmap with transparent pixels on the GLES20 texture without loosing the transparency?
I think the easiest way is to:
Keep a copy of the whole texture around as a Bitmap and draw onto that using whatever mode you want.
Make a copy of the section of the bitmap that you changed (or update an existing bitmap to avoid allocating memory each time).
Upload that smaller bitmap using glTexSubImage2D with the appropriate x and y offset.
The reason that you will have to copy into a smaller bitmap is that glTexSubImage2D doesn't support uploading a section (rectangle) of a bitmap. It does however allow you to update a section of the texture using the offset parameters.
Related
I try to resize an image like this:
aPaint.setAntiAlias(true); // Enabling this flag will cause all draw operations that support antialiasing to use it.
aPaint.setFilterBitmap(True); // enable bilinear sampling on scaled bitmaps. If cleared, scaled bitmaps will be drawn with nearest neighbor sampling, likely resulting in artifacts.
apaint.setDither(true); // Enabling this flag applies a dither to any blit operation where the target's colour space is more constrained than the source.
aCanvas.drawBitmap(aBitmap, aJSrcRect, aJDestRectf, apaint);
But I don't have a very good antialised image (it's antialiased, but not very good). Here is an image showing my problem
What can i do to increase the quality of the antialias under android ?
For antialias effect, you can try to create a Paint like this:
Paint paint= new Paint();
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
Finally apply paint on canvas with:
canvas.drawBitmap(mBitmap, 0.f, 0.f, paint)
I want to be able to slowly make a bitmap image more and more transparent on my canvas.
Currently I am drawing the .png file (stored in drawables) like this:
//I setup the Bitmap in my constructor.
heartSymb = BitmapFactory.decodeResource(getResources(),R.drawable.heartsymbol);
//This is in on draw.
canvas.drawBitmap(heartSymb,0,0,null);
How would I be able to slowly change the bitmap's transparency until it becomes fully transparent?
You can use a Paint object to modify the alpha of the bitmap to be drawn:
Paint alphaPaint = new Paint();
alphaPaint.setAlpha(alpha);
canvas.drawBitmap(heartSymb, 0, 0, alphaPaint);
Then you just have to modify alpha value and perform update periodically, maybe by using a Handler.
I'm trying to learn how to make an animated sprite in android and couldn't figure out how to go about organising my bitmaps. I have a sprite sheet of my character walking to the Right: a bitmap of five copies of a character, equally spaced (every 45px), in a walk cycle.
I planned to draw each frame by drawing a tiny section of my sprite sheet bitmap at a time by going:
Rect sourceRect = new Rect(0, 0, 45, 75);
canvas.drawBitmap(spriteSheetBitmap, sourceRect, new Rect(0, 0, 45, 75), null);
Then for the next frames, increment "sourceRect.x" by 45, then redraw and so forth.
However, I'm now not sure how to go about making my sprite walk to the Left. I had initially thought I could just mirror my rectangle that I am drawing from to get a flipped picture. Something like:
sourceRect = new Rect(45, 0, 0, 75);
which doesn't seem to work (not sure what actually happens here, but nothing gets drawn to my surface).
Searching online, it seems I should make a copy of my original bitmap, mirror it with a transform matrix, then use that bitmap for drawing when walking to the left. However I've also found implementations where many smaller bitmap objects get created out of the original sprite sheet, stored (and transformed for the mirrored motion), then used as needed.
So I'm wondering what would be the best in this case or if there is really any difference (performance/memory):
Method 1: Load in my original sprite sheet, make a new bitmap instance, mirror it,, then calculate all the rectangles and use those + two entire sheets to draw from (admittedly there is some extra bitmap space where the sprite sheet is unused).
Method 2: Load in my original sprite sheet, for every frame create a new two bitmap objects (1 mirrored, 1 normal) and store those to draw from.
Method 3: Other better ways?
Method 2 would be way too expensive, and you don't need a canvas to flip a bitmap. Simply create another bitmap with a Matrix applied, like so:
BitmapDrawable flip(BitmapDrawable d)
{
Matrix m = new Matrix();
m.preScale(-1, 1);
Bitmap src = d.getBitmap();
Bitmap dst = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), m, false);
dst.setDensity(DisplayMetrics.DENSITY_DEFAULT);
return new BitmapDrawable(dst);
}
To mirror your sprite simply apply the following transform on the Canvas: scale(-1, 1). You will have to offset the sprite by its width too.
To draw a vertical mirrored bitmap bmp on a canvas:
Matrix m = new Matrix();
// Mirror is basically a rotation
m.setScale( -1 , 1 );
// so you got to move your bitmap back to it's place. otherwise you will not see it
m.postTranslate(canvas.getWidth(), 0);
canvas.drawBitmap(bmp, m, p);
am creating temp Bitmap to draw a Text on it, and the i want to get it Pixels so i can manipulate these pixels (i don't show this image on screen).
this is the code
Bitmap tempBitmap=Bitmap.createBitmap(200, 400, Bitmap.Config.ARGB_8888);//i've tested all Configs
Canvas tempCanvas=new Canvas(tempBitmap);
tempCanvas.drawColor(Color.WHITE);
tempCanvas.drawText("Hello", 0, 0, mPaint);//mPaint color set to Black
int[] pixels=new int[tempBitmap.getWidth() * tempBitmap.getHeight()];
tempBitmap.getPixels(pixels, 0, tempBitmap.getWidth(), 0, 0, tempBitmap.getWidth(), tempBitmap.getHeight());
but when i print all pixels they all -1 value !! why?
You're positioning the baseline of the text at (0,0), so you're drawing it just off the top of the bitmap. Move it down a bit. You can use Paint.getTextBounds to measure the text size, and then use the returned height to move your text downwards.
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