I'd like to load a huge bitmap as a texture into the graphics card's memory on Android, in OpenGL ES 2.0, to be used as a texture atlas, in the biggest size possible. My device has a maximum texture size of 8192x8192.
I know that I can load a bitmap as a texture the following way:
// create bitmap
Bitmap bitmap = Bitmap.createBitmap(8192, 8192, Bitmap.Config.ARGB_8888);
{ // draw something
Canvas c = new Canvas(bitmap);
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setColor(0xFFFF0000);
c.drawCircle(4096, 4096, 4096, p);
}
// load as a texture
GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
However, (not surprisingly) I get a java.lang.OutOfMemoryError when trying to create a bitmap of this size.
Is it possible to load it in parts? As it's a texture atlas, it could be assembled from smaller bitmaps. I looked at the texSubImage2D function, but I don't understand where you would initialize the full-sized texture, or provide the size of the full texture beforehand.
On the GL side you need to allocate the full storage, and then patch it.
Allocate storage using glTexImage2D() with a null value for the data parameter. Upload patches using glTexSubImage2D().
Note that this still requires 256MB of memory, so on many budget devices you'll still get an OOM ...
Based on solidpixel's answer, this is a code that does the job:
GLES20.glTexImage2D ( GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, 8192, 8192, 0,
GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
Bitmap bitmap = Bitmap.createBitmap(1024, 1024, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bitmap);
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
for (int i = 0; i < 8; ++i) {
for (int j = 0; j < 8; ++j) {
// clear the bitmap
c.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
{ // draw something
p.setARGB(255, 32 * i, 32 * j, 255 - 32 * j);
c.drawCircle(512, 512, 512, p);
}
// load as part of a texture
GLUtils.texSubImage2D(GL_TEXTURE_2D, 0, i * 1024, j * 1024, bitmap);
}
}
Here the texture is assembled from 64, 1024x1024-sized bitmaps.
Related
I have a bitmap(which can be converted to a ByteBuffer). I want to upload all of its 6 faces by offsets to the GPU in OpenGL. When I do the following, the app crashes with OpenGL giving a memory violation.
Here bitmap is a byte array byte[]
for (int i=0 ; i<6 ; i++) {
GLES20.glTexImage2D(
GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
0,
GLES20.GL_RGBA,
side,
side,
0,
GLES20.GL_RGBA,
GLES20.GL_UNSIGNED_BYTE,
ByteBuffer.wrap(bitmap, length / 6 * i, side * side * 4));
}
But when I copy the array and then upload to the GPU like this(Here bitmap is of type Bitmap):
int numBytes = bitmap.getByteCount();
ByteBuffer pixels = ByteBuffer.allocate(numBytes);
bitmap.copyPixelsToBuffer(pixels);
for (int i=0 ; i<6 ; i++) {
Log.d("aakash", String.valueOf(numBytes / 6 * i));
byte[] arr = Arrays.copyOfRange(pixels.array(), numBytes / 6 * i, numBytes / 6 * (i+1));
GLES20.glTexImage2D(
GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
0,
GLES20.GL_RGBA,
bitmap.getWidth(),
bitmap.getHeight() / 6,
0,
GLES20.GL_RGBA,
GLES20.GL_UNSIGNED_BYTE,
ByteBuffer.wrap(arr));
}
I get the cubemap correctly rendered.
What am I doing wrong in the first one? I want to avoid copying the array to upload parts of it to the GPU.
I can assure that the size and the mathematical calculations are correct.
To avoid memory violation just replace
ByteBuffer pixels = ByteBuffer.allocate(numBytes);
with
ByteBuffer pixels = ByteBuffer.allocateDirect(numBytes);
But you don't need ByteBuffer for simple side texture loading
loadSideTexture(context, GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X, R.raw.lake2_rt);
loadSideTexture(context, GLES20.GL_TEXTURE_CUBE_MAP_NEGATIVE_X, R.raw.lake2_lf);
loadSideTexture(context, GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_Y, R.raw.lake2_up);
loadSideTexture(context, GLES20.GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, R.raw.lake2_dn);
loadSideTexture(context, GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_Z, R.raw.lake2_bk);
loadSideTexture(context, GLES20.GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, R.raw.lake2_ft);
private void loadSideTexture(Context context, int target, #RawRes int resID) {
final Bitmap bitmap = BitmapFactory.decodeStream(context.getResources().openRawResource(resID));
GLUtils.texImage2D(target, 0, bitmap, 0);
bitmap.recycle();
}
I am processing a camera frame using OpenCV. The functionality is working fine. However, it takes between 40~60 ms to process each frame. I am wondering if there is a possibility to improve the execution time. Following are the steps I take before I pass the mat to OpenCV cascade classifier.
mPixelBuf.rewind();
GLES20.glReadPixels(0, 0, mWidth, mHeight, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPixelBuf);
Bitmap bmp = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
mPixelBuf.rewind();
bmp.copyPixelsFromBuffer(mPixelBuf);
// received inverted(upside-down) bitmap. Fixing it.
android.graphics.Matrix m = new android.graphics.Matrix();
m.preScale(1, -1);
Bitmap flipped = Bitmap.createBitmap(bmp, 0, 0, mWidth, mHeight, m, false);
Mat mat = new Mat();
// convert bitmap to mat
Utils.bitmapToMat(flipped, mat);
// apply gray scale
Imgproc.cvtColor(mat, mat, Imgproc.COLOR_BGR2GRAY);
// pass the mat to cascade classifier...
I guess there could be some smart ways to avoid this extra processing or perhaps reduce the image resolution for faster image processing.
I have a list of bitmaps.
What I want is to take these bitmaps and, with the help of canvas, create a new bitmap with scaled down images (meaning, make them quite tiny) from the list I have of bitmaps.
I've manage to do this but, the image looks quite horrible due to the down-scaling.
I've tried many things, settings, creating new canvases etc.
The simple first solution looks like this (code below), but, as I said, the images looks awful.
public static Bitmap folderBitmap(Bitmap bitmap[]) {
Bitmap b = Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565);
Canvas c = new Canvas(b);
c.drawARGB(255, 255, 255, 255);
Paint paint = new Paint();
paint.setAntiAlias(false);
paint.setFilterBitmap(false);
paint.setDither(true);
c.drawBitmap(getBit(bitmap), 4, 4, paint);
c.drawBitmap(getBit(bitmap), 35, 4, paint);
c.drawBitmap(getBit(bitmap), 67, 4, paint);
c.drawBitmap(getBit(bitmap), 4, 35, null);
c.drawBitmap(getBit(bitmap), 35, 35, null);
c.drawBitmap(getBit(bitmap), 67, 35, null);
c.drawBitmap(getBit(bitmap), 4, 67, null);
c.drawBitmap(getBit(bitmap), 35, 67, null);
c.drawBitmap(getBit(bitmap), 67, 67, null);
return b;
}
private static Bitmap getBit(Bitmap[] b) {
Bitmap newBitmap = Bitmap.createScaledBitmap(b[getR()], 28, 28, false);
return newBitmap;
}
private static int getR() {
Random r = new Random();
int rint = r.nextInt(8);
return rint;
}
By awful I mean, they look pixelated and un-sharp.
Use inSampleSize to scale a Bitmap. From the documentation
If set to a value > 1, requests the decoder to subsample the original
image, returning a smaller image to save memory. The sample size is
the number of pixels in either dimension that correspond to a single
pixel in the decoded bitmap. For example, inSampleSize == 4 returns an
image that is 1/4 the width/height of the original, and 1/16 the
number of pixels. Any value <= 1 is treated the same as 1. Note: the
decoder uses a final value based on powers of 2, any other value will
be rounded down to the nearest power of 2.
For instance:
BitmapFactory.Options opts = new BitmapFactory.Options();
opt.inSampleSize = 4;
Bitmap newBitmap = BitmapFactory.decodeFile(filePath, opts);
You can make image thumbnail :
Bitmap thumbBitmap = ThumbnailUtils.extractThumbnail(BitmapFactory.decodeFile(filePath), thumbWidth, thumbHeight);
Using your code, if you want to increase quality according to documentation for each of these settings you should:
Bitmap b = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);: Each pixel is stored on 4 bytes. Each channel (RGB and alpha for translucency) is stored with 8 bits of precision (256 possible values.) This configuration is very flexible and offers the best
quality. It should be used whenever possible.
paint.setAntiAlias(true);: AntiAliasing smooths out the edges of what is being drawn
paint.setFilterBitmap(true);: Filtering affects the sampling of bitmaps when they are transformed.
I get a runtime error if I try to convert camera preview YUV byte array
to a RGB(A) byte array with Imgproc.cvtColor( mYUV_Mat, mRgba_Mat, Imgproc.COLOR_YUV420sp2RGBA, 4 )
in onPreviewFrame(byte[] data, Camera camera):
Preview.java:
mCamera.setPreviewCallback(new PreviewCallback() {
public void onPreviewFrame(byte[] data, Camera camera)
{
// Pass YUV data to draw-on-top companion
System.arraycopy(data, 0, mDrawOnTop.mYUVData, 0, data.length);
mDrawOnTop.invalidate();
}
});
DrawOnTop.java:
public class DrawOnTop extends View {
Bitmap mBitmap;
Mat mYUV_Mat;
protected void onDraw(Canvas canvas) {
if (mBitmap != null)
{
canvasWidth = canvas.getWidth();
canvasHeight = canvas.getHeight();
int newImageWidth = 640;
int newImageHeight = 480;
marginWidth = (canvasWidth - newImageWidth)/2;
if( mYUV_Mat != null ) mYUV_Mat.release();
//mYUV_Mat = new Mat( newImageWidth, newImageHeight, CvType.CV_8UC1 );
mYUV_Mat = new Mat( newImageWidth, newImageHeight, CvType.CV_8UC4 );
mYUV_Mat.put( 0, 0, mYUVData );
//Mat mRgba_Mat = new Mat();
Mat mRgba_Mat = new Mat(newImageWidth,newImageHeight,CvType.CV_8UC4);
//Mat mRgba_Mat = mYUV_Mat;
//Imgproc.cvtColor( mYUV_Mat, mRgba_Mat, Imgproc.COLOR_YUV2RGBA_NV21, 4 );
//Imgproc.cvtColor( mYUV_Mat, mRgba_Mat, Imgproc.COLOR_YUV420sp2RGB, 4 );
Imgproc.cvtColor( mYUV_Mat, mRgba_Mat, Imgproc.COLOR_YUV420sp2RGBA, 4 );
// Draw Bitmap New:
Bitmap mBitmap = Bitmap.createBitmap( newImageWidth, newImageHeight, Bitmap.Config.ARGB_8888 );
Utils.matToBitmap( mRgba_Mat, mBitmap );
mRgba_Mat.release();
}
}
The conversion mYUV_Mat.put( 0, 0, mYUVData ) runs correctly.
But the attempts to convert mYUV_Mat to mRgb_Mat using Imgproc.cvtColor
lead all to runtime errors ("Source not found." with Eclipse).
What is the correct Imgproc.cvtColor command for my goal?
(I don't want to use a Java YUV2RGB(A) decode method because it's to slow
for image processing. I want to use the OpenCV Imgproc.cvtColor method
because I can do native calls)
Maybe the Imgproc library isn't properly included in your project, but other OpenCV libraries are? The line that crashes is the first line where you use a method from Imgproc, which would explain why earlier parts of the code run correctly.
Your code looks fine, except you can use the no-argument constructor for mRgba_Mat (since most OpenCV4Android functions, including cvtColor, can infer the required size of the destination matrix), and you're allocating a lot of wasted space for mYUV_Mat. You don't need a full 4 channels if you just allocate YUV matrices 50% more space than their RGB counterparts:
mYUV_Mat = new Mat( newImageHeight + newImageHeight / 2, newImageWidth, CvType.CV_8UC1 );
I want to XOR two images in android as iam working on image encryption app iam bringing images from SD card and loading them in image view now as i have loaded two images i want to XOR both of them
Another alternative would be to draw on a Canvas both of your bitmaps. One bitmap does not specify any settings, but the other should specify a PorterDuffXfermode to Mode.XOR, in his Paint object.
Ex:
ImageView compositeImageView = (ImageView) findViewById(R.id.imageView);
Bitmap bitmap1=BitmapFactory.decodeResource(getResources(), R.drawable.batman_ad);
Bitmap bitmap2=BitmapFactory.decodeResource(getResources(), R.drawable.logo);
Bitmap resultingImage=Bitmap.createBitmap(bitmap1.getWidth(), bitmap1.getHeight(), bitmap1.getConfig());
Canvas canvas = new Canvas(resultingImage);
// Drawing first image on Canvas
Paint paint = new Paint();
canvas.drawBitmap(bitmap1, 0, 0, paint);
// Drawing second image on the Canvas, with Xfermode set to XOR
paint.setXfermode(new PorterDuffXfermode(Mode.XOR));
canvas.drawBitmap(bitmap2, 0, 0, paint);
compositeImageView.setImageBitmap(resultingImage);
It depends what you want to xor, the pixels or data itself.
any way an easy way to do it is to convert the to images to array of all the pixels, XOR them together and then convert it back to a bitmap. NOTE that this example will only work to bitmap with identical resolutions.
//convert the first bitmap to array of ints
int[] buffer1 = new int[bmp1.getWidth()*bmp1.getHeight()];
bmp1.getPixels(buffer1,0,bmp1.getWidth(),0,0,bmp1.getWidth(),bmp1.getHeight() );
//convert the seconds bitmap to array of ints
int[] buffer2 = new int[bmp2.getWidth()*bmp2.getHeight()];
bmp2.getPixels(buffer2,0,bmp2.getWidth(),0,0,bmp2.getWidth(),bmp2.getHeight() );
//XOR all the elements
for( int i = 0 ; i < bmp1.getWidth()*bmp1.getHeight() ; i++ )
buffer1[i] = buffer1[i] ^ buffer2[i];
//convert it back to a bitmap, you could also create a new bitmap in case you need them
//for some thing else
bmp1.setPixels(buffer1,0,bmp1.getWidth(),0,0,bmp2.getWidth(),bmp2.getHeight() );
see:
http://developer.android.com/reference/android/graphics/Bitmap.html