32Bit Bitmaps slow on Google TV. Bad Pixel format? - android

I am wondering what I'm doing wrong here.
I create a bitmap with dimesions 1080x1080:
Bitmap level = Bitmap.createBitmap(1080, 1080, Config.ARGB_8888);
then I draw some lines and rects into it and put it on canvas in my SurfaceView:
c.drawBitmap(LevelBitmap.level, 0, 0, null);
and this operation takes 20ms on my google TV NSZ-GS7. much to long.
setting pixelformat in surfaceCreated to RGBX_8888 or RGBA_8888 makes things even worse, 30ms for drawing.
holder.setFormat(PixelFormat.RGBX_8888);
setting to RGB_888
holder.setFormat(PixelFormat.RGB_888);
leads to nullpointerexceptions when i draw something
only the combination of Config.RGB_565 for the bitmap and PixelFormat.RGB_565 for the window
draws the bitmap in acceptable 10ms to the canvas, but the quality of RGB_565 is horrilble.
am I missing something? is Google TV not capable of 32bit graphics? what is its "natural" pixel and bitmap format? Is there any documentation on this topic i missed on google?
here my requested onDraw method i use to measure the timings:
private static void doDraw(Canvas c) {
if (LevelBitmap.level == null){
LevelBitmap.createBitmap();
}
long time = System.currentTimeMillis();
c.drawBitmap(LevelBitmap.level, 0, 0, null);
Log.e("Timer:",""+(System.currentTimeMillis()-time) );
if(true) return;
and here the class creating the bitmap:
public final class LevelBitmap {
public static Bitmap level;
public static void createBitmap() {
level = Bitmap.createBitmap(GameViewThread.mCanvasHeight, GameViewThread.mCanvasHeight, Config.ARGB_8888);
Canvas canvas = new Canvas(level);
canvas.scale(GameViewThread.fieldScaleFactor, GameViewThread.fieldScaleFactor);
canvas.translate(500, 500);
// floor covering
int plankWidth = 50;
int plankSpace = 500;
short size = TronStatics.FIELDSIZE;
for (int i = plankSpace; i < size; i = i + plankSpace) {
canvas.drawRect(i, 0, i + plankWidth, size, GameViewThread.bgGrey);
canvas.drawRect(0, i, size, i + plankWidth, GameViewThread.bgGrey);
}
// draw field
canvas.drawRect(-10, -10, TronStatics.FIELDSIZE + 10, TronStatics.FIELDSIZE + 10, GameViewThread.wallPaint);
}
}

Related

screenshot of Video on surface view shows black screen [duplicate]

I am attempting to Take a Screenshot of my Game through code and Share it through an Intent. I able to do of those things, however the screenshot always appears black. Here is the Code Related to Sharing the Screenshot:
View view = MainActivity.getView();
view.setDrawingCacheEnabled(true);
Bitmap screen = Bitmap.createBitmap(view.getDrawingCache(true));
.. save Bitmap
This is in the MainActivity:
view = new GameView(this);
view.setLayoutParams(new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.FILL_PARENT,
RelativeLayout.LayoutParams.FILL_PARENT));
public static SurfaceView getView() {
return view;
}
And the View itself:
public class GameView extends SurfaceView implements SurfaceHolder.Callback {
private static SurfaceHolder surfaceHolder;
...etc
And this is how I am Drawing everything:
Canvas canvas = surfaceHolder.lockCanvas(null);
if (canvas != null) {
Game.draw(canvas);
...
Ok, based on some answers, i have constructed this:
public static void share() {
Bitmap screen = GameView.SavePixels(0, 0, Screen.width, Screen.height);
Calendar c = Calendar.getInstance();
Date d = c.getTime();
String path = Images.Media.insertImage(
Game.context.getContentResolver(), screen, "screenShotBJ" + d
+ ".png", null);
System.out.println(path + " PATH");
Uri screenshotUri = Uri.parse(path);
final Intent emailIntent = new Intent(
android.content.Intent.ACTION_SEND);
emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
emailIntent.putExtra(Intent.EXTRA_STREAM, screenshotUri);
emailIntent.setType("image/png");
Game.context.startActivity(Intent.createChooser(emailIntent,
"Share High Score:"));
}
The Gameview contains the Following Method:
public static Bitmap SavePixels(int x, int y, int w, int h) {
EGL10 egl = (EGL10) EGLContext.getEGL();
GL10 gl = (GL10) egl.eglGetCurrentContext().getGL();
int b[] = new int[w * (y + h)];
int bt[] = new int[w * h];
IntBuffer ib = IntBuffer.wrap(b);
ib.position(0);
gl.glReadPixels(x, 0, w, y + h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, ib);
for (int i = 0, k = 0; i < h; i++, k++) {
for (int j = 0; j < w; j++) {
int pix = b[i * w + j];
int pb = (pix >> 16) & 0xff;
int pr = (pix << 16) & 0x00ff0000;
int pix1 = (pix & 0xff00ff00) | pr | pb;
bt[(h - k - 1) * w + j] = pix1;
}
}
Bitmap sb = Bitmap.createBitmap(bt, w, h, Bitmap.Config.ARGB_8888);
return sb;
}
The Screenshot is still Black. Is there something wrong with the way I am saving it perhaps?
I have attempted several different methods to take the Screenshot, but none of them worked:
The one shown in the code above was the most commonly suggested one. But it does not seem to work.
Is this an Issue with using SurfaceView? And if so, why does view.getDrawingCache(true) even exist if I cant use it and how do I fix this?
My code:
public static void share() {
// GIVES BLACK SCREENSHOT:
Calendar c = Calendar.getInstance();
Date d = c.getTime();
Game.update();
Bitmap.Config conf = Bitmap.Config.RGB_565;
Bitmap image = Bitmap.createBitmap(Screen.width, Screen.height, conf);
Canvas canvas = GameThread.surfaceHolder.lockCanvas(null);
canvas.setBitmap(image);
Paint backgroundPaint = new Paint();
backgroundPaint.setARGB(255, 40, 40, 40);
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(),
backgroundPaint);
Game.draw(canvas);
Bitmap screen = Bitmap.createBitmap(image, 0, 0, Screen.width,
Screen.height);
canvas.setBitmap(null);
GameThread.surfaceHolder.unlockCanvasAndPost(canvas);
String path = Images.Media.insertImage(
Game.context.getContentResolver(), screen, "screenShotBJ" + d
+ ".png", null);
System.out.println(path + " PATH");
Uri screenshotUri = Uri.parse(path);
final Intent emailIntent = new Intent(
android.content.Intent.ACTION_SEND);
emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
emailIntent.putExtra(Intent.EXTRA_STREAM, screenshotUri);
emailIntent.setType("image/png");
Game.context.startActivity(Intent.createChooser(emailIntent,
"Share High Score:"));
}
Thank you.
There is a great deal of confusion about this, and a few correct answers.
Here's the deal:
A SurfaceView has two parts, the Surface and the View. The Surface is on a completely separate layer from all of the View UI elements. The getDrawingCache() approach works on the View layer only, so it doesn't capture anything on the Surface.
The buffer queue has a producer-consumer API, and it can have only one producer. Canvas is one producer, GLES is another. You can't draw with Canvas and read pixels with GLES. (Technically, you could if the Canvas were using GLES and the correct EGL context was current when you went to read the pixels, but that's not guaranteed. Canvas rendering to a Surface is not accelerated in any released version of Android, so right now there's no hope of it working.)
(Not relevant for your case, but I'll mention it for completeness:) A Surface is not a frame buffer, it is a queue of buffers. When you submit a buffer with GLES, it is gone, and you can no longer read from it. So if you were rendering with GLES and capturing with GLES, you would need to read the pixels back before calling eglSwapBuffers().
With Canvas rendering, the easiest way to "capture" the Surface contents is to simply draw it twice. Create a screen-sized Bitmap, create a Canvas from the Bitmap, and pass it to your draw() function.
With GLES rendering, you can use glReadPixels() before the buffer swap to grab the pixels. There's a (less-expensive than the code in the question) implementation of the grab code in Grafika; see saveFrame() in EglSurfaceBase.
If you were sending video directly to a Surface (via MediaPlayer) there would be no way to capture the frames, because your app never has access to them -- they go directly from mediaserver to the compositor (SurfaceFlinger). You can, however, route the incoming frames through a SurfaceTexture, and render them twice from your app, once for display and once for capture. See this question for more info.
One alternative is to replace the SurfaceView with a TextureView, which can be drawn on like any other Surface. You can then use one of the getBitmap() calls to capture a frame. TextureView is less efficient than SurfaceView, so this is not recommended for all situations, but it's straightforward to do.
If you were hoping to get a composite screen shot containing both the Surface contents and the View UI contents, you will need to capture the Canvas as above, capture the View with the usual drawing cache trick, and then composite the two manually. Note this won't pick up the system parts (status bar, nav bar).
Update: on Lollipop and later (API 21+) you can use the MediaProjection class to capture the entire screen with a virtual display. There are some trade-offs with this approach, e.g. you're capturing the rendered screen, not the frame that was sent to the Surface, so what you get may have been up- or down-scaled to fit the window. In addition, this approach involves an Activity switch since you have to create an intent (by calling createScreenCaptureIntent on the ProjectionManager object) and wait for its result.
If you want to learn more about how all this stuff works, see the Android System-Level Graphics Architecture doc.
I know its a late reply but for those who face the same problem,
we can use PixelCopy for fetching the snapshot. It's available in API level 24 and above
PixelCopy.request(surfaceViewObject,BitmapDest,listener,new Handler());
where,
surfaceViewObject is the object of surface view
BitmapDest is the bitmap object where the image will be saved and it cant be null
listener is OnPixelCopyFinishedListener
for more info refer - https://developer.android.com/reference/android/view/PixelCopy
Update 2020 view.setDrawingCacheEnabled(true) is deprecated in API 28
If you are using normal View then you can create a canvas with the specified bitmap to draw into. Then ask the view to draw over that canvas and return bitmap filled by Canvas
/**
* Copy View to Canvas and return bitMap
*/
fun getBitmapFromView(view: View): Bitmap? {
var bitmap =
Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
view.draw(canvas)
return bitmap
}
Or you can fill the canvas with a default color before drawing it with the view:
/**
* Copy View to Canvas and return bitMap and fill it with default color
*/
fun getBitmapFromView(view: View, defaultColor: Int): Bitmap? {
var bitmap =
Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
var canvas = Canvas(bitmap)
canvas.drawColor(defaultColor)
view.draw(canvas)
return bitmap
}
The above approach will not work for the surface view they will drawn as a hole in the screenshot.
For surface view since Android 24, you need to use Pixel Copy.
/**
* Pixel copy to copy SurfaceView/VideoView into BitMap
*/
fun usePixelCopy(videoView: SurfaceView, callback: (Bitmap?) -> Unit) {
val bitmap: Bitmap = Bitmap.createBitmap(
videoView.width,
videoView.height,
Bitmap.Config.ARGB_8888
);
try {
// Create a handler thread to offload the processing of the image.
val handlerThread = HandlerThread("PixelCopier");
handlerThread.start();
PixelCopy.request(
videoView, bitmap,
PixelCopy.OnPixelCopyFinishedListener { copyResult ->
if (copyResult == PixelCopy.SUCCESS) {
callback(bitmap)
}
handlerThread.quitSafely();
},
Handler(handlerThread.looper)
)
} catch (e: IllegalArgumentException) {
callback(null)
// PixelCopy may throw IllegalArgumentException, make sure to handle it
e.printStackTrace()
}
}
This approach can take a screenshot of any subclass of Surface Vie eg VideoView
Screenshot.usePixelCopy(videoView) { bitmap: Bitmap? ->
processBitMap(bitmap)
}
Here is the complete method to take screen shot of a surface view using PixelCopy. It requires API 24(Android N).
#RequiresApi(api = Build.VERSION_CODES.N)
private void capturePicture() {
Bitmap bmp = Bitmap.createBitmap(surfaceView.getWidth(), surfaceView.getHeight(), Bitmap.Config.ARGB_8888);
PixelCopy.request(surfaceView, bmp, i -> {
imageView.setImageBitmap(bmp); //"iv_Result" is the image view
}, new Handler(Looper.getMainLooper()));
}
This is because SurfaceView uses OpenGL thread for drawing and draws directly to a hardware buffer. You have to use glReadPixels (and probably a GLWrapper).
See the thread: Android OpenGL Screenshot

Android Take Screenshot of Surface View Shows Black Screen

I am attempting to Take a Screenshot of my Game through code and Share it through an Intent. I able to do of those things, however the screenshot always appears black. Here is the Code Related to Sharing the Screenshot:
View view = MainActivity.getView();
view.setDrawingCacheEnabled(true);
Bitmap screen = Bitmap.createBitmap(view.getDrawingCache(true));
.. save Bitmap
This is in the MainActivity:
view = new GameView(this);
view.setLayoutParams(new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.FILL_PARENT,
RelativeLayout.LayoutParams.FILL_PARENT));
public static SurfaceView getView() {
return view;
}
And the View itself:
public class GameView extends SurfaceView implements SurfaceHolder.Callback {
private static SurfaceHolder surfaceHolder;
...etc
And this is how I am Drawing everything:
Canvas canvas = surfaceHolder.lockCanvas(null);
if (canvas != null) {
Game.draw(canvas);
...
Ok, based on some answers, i have constructed this:
public static void share() {
Bitmap screen = GameView.SavePixels(0, 0, Screen.width, Screen.height);
Calendar c = Calendar.getInstance();
Date d = c.getTime();
String path = Images.Media.insertImage(
Game.context.getContentResolver(), screen, "screenShotBJ" + d
+ ".png", null);
System.out.println(path + " PATH");
Uri screenshotUri = Uri.parse(path);
final Intent emailIntent = new Intent(
android.content.Intent.ACTION_SEND);
emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
emailIntent.putExtra(Intent.EXTRA_STREAM, screenshotUri);
emailIntent.setType("image/png");
Game.context.startActivity(Intent.createChooser(emailIntent,
"Share High Score:"));
}
The Gameview contains the Following Method:
public static Bitmap SavePixels(int x, int y, int w, int h) {
EGL10 egl = (EGL10) EGLContext.getEGL();
GL10 gl = (GL10) egl.eglGetCurrentContext().getGL();
int b[] = new int[w * (y + h)];
int bt[] = new int[w * h];
IntBuffer ib = IntBuffer.wrap(b);
ib.position(0);
gl.glReadPixels(x, 0, w, y + h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, ib);
for (int i = 0, k = 0; i < h; i++, k++) {
for (int j = 0; j < w; j++) {
int pix = b[i * w + j];
int pb = (pix >> 16) & 0xff;
int pr = (pix << 16) & 0x00ff0000;
int pix1 = (pix & 0xff00ff00) | pr | pb;
bt[(h - k - 1) * w + j] = pix1;
}
}
Bitmap sb = Bitmap.createBitmap(bt, w, h, Bitmap.Config.ARGB_8888);
return sb;
}
The Screenshot is still Black. Is there something wrong with the way I am saving it perhaps?
I have attempted several different methods to take the Screenshot, but none of them worked:
The one shown in the code above was the most commonly suggested one. But it does not seem to work.
Is this an Issue with using SurfaceView? And if so, why does view.getDrawingCache(true) even exist if I cant use it and how do I fix this?
My code:
public static void share() {
// GIVES BLACK SCREENSHOT:
Calendar c = Calendar.getInstance();
Date d = c.getTime();
Game.update();
Bitmap.Config conf = Bitmap.Config.RGB_565;
Bitmap image = Bitmap.createBitmap(Screen.width, Screen.height, conf);
Canvas canvas = GameThread.surfaceHolder.lockCanvas(null);
canvas.setBitmap(image);
Paint backgroundPaint = new Paint();
backgroundPaint.setARGB(255, 40, 40, 40);
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(),
backgroundPaint);
Game.draw(canvas);
Bitmap screen = Bitmap.createBitmap(image, 0, 0, Screen.width,
Screen.height);
canvas.setBitmap(null);
GameThread.surfaceHolder.unlockCanvasAndPost(canvas);
String path = Images.Media.insertImage(
Game.context.getContentResolver(), screen, "screenShotBJ" + d
+ ".png", null);
System.out.println(path + " PATH");
Uri screenshotUri = Uri.parse(path);
final Intent emailIntent = new Intent(
android.content.Intent.ACTION_SEND);
emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
emailIntent.putExtra(Intent.EXTRA_STREAM, screenshotUri);
emailIntent.setType("image/png");
Game.context.startActivity(Intent.createChooser(emailIntent,
"Share High Score:"));
}
Thank you.
There is a great deal of confusion about this, and a few correct answers.
Here's the deal:
A SurfaceView has two parts, the Surface and the View. The Surface is on a completely separate layer from all of the View UI elements. The getDrawingCache() approach works on the View layer only, so it doesn't capture anything on the Surface.
The buffer queue has a producer-consumer API, and it can have only one producer. Canvas is one producer, GLES is another. You can't draw with Canvas and read pixels with GLES. (Technically, you could if the Canvas were using GLES and the correct EGL context was current when you went to read the pixels, but that's not guaranteed. Canvas rendering to a Surface is not accelerated in any released version of Android, so right now there's no hope of it working.)
(Not relevant for your case, but I'll mention it for completeness:) A Surface is not a frame buffer, it is a queue of buffers. When you submit a buffer with GLES, it is gone, and you can no longer read from it. So if you were rendering with GLES and capturing with GLES, you would need to read the pixels back before calling eglSwapBuffers().
With Canvas rendering, the easiest way to "capture" the Surface contents is to simply draw it twice. Create a screen-sized Bitmap, create a Canvas from the Bitmap, and pass it to your draw() function.
With GLES rendering, you can use glReadPixels() before the buffer swap to grab the pixels. There's a (less-expensive than the code in the question) implementation of the grab code in Grafika; see saveFrame() in EglSurfaceBase.
If you were sending video directly to a Surface (via MediaPlayer) there would be no way to capture the frames, because your app never has access to them -- they go directly from mediaserver to the compositor (SurfaceFlinger). You can, however, route the incoming frames through a SurfaceTexture, and render them twice from your app, once for display and once for capture. See this question for more info.
One alternative is to replace the SurfaceView with a TextureView, which can be drawn on like any other Surface. You can then use one of the getBitmap() calls to capture a frame. TextureView is less efficient than SurfaceView, so this is not recommended for all situations, but it's straightforward to do.
If you were hoping to get a composite screen shot containing both the Surface contents and the View UI contents, you will need to capture the Canvas as above, capture the View with the usual drawing cache trick, and then composite the two manually. Note this won't pick up the system parts (status bar, nav bar).
Update: on Lollipop and later (API 21+) you can use the MediaProjection class to capture the entire screen with a virtual display. There are some trade-offs with this approach, e.g. you're capturing the rendered screen, not the frame that was sent to the Surface, so what you get may have been up- or down-scaled to fit the window. In addition, this approach involves an Activity switch since you have to create an intent (by calling createScreenCaptureIntent on the ProjectionManager object) and wait for its result.
If you want to learn more about how all this stuff works, see the Android System-Level Graphics Architecture doc.
I know its a late reply but for those who face the same problem,
we can use PixelCopy for fetching the snapshot. It's available in API level 24 and above
PixelCopy.request(surfaceViewObject,BitmapDest,listener,new Handler());
where,
surfaceViewObject is the object of surface view
BitmapDest is the bitmap object where the image will be saved and it cant be null
listener is OnPixelCopyFinishedListener
for more info refer - https://developer.android.com/reference/android/view/PixelCopy
Update 2020 view.setDrawingCacheEnabled(true) is deprecated in API 28
If you are using normal View then you can create a canvas with the specified bitmap to draw into. Then ask the view to draw over that canvas and return bitmap filled by Canvas
/**
* Copy View to Canvas and return bitMap
*/
fun getBitmapFromView(view: View): Bitmap? {
var bitmap =
Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
view.draw(canvas)
return bitmap
}
Or you can fill the canvas with a default color before drawing it with the view:
/**
* Copy View to Canvas and return bitMap and fill it with default color
*/
fun getBitmapFromView(view: View, defaultColor: Int): Bitmap? {
var bitmap =
Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
var canvas = Canvas(bitmap)
canvas.drawColor(defaultColor)
view.draw(canvas)
return bitmap
}
The above approach will not work for the surface view they will drawn as a hole in the screenshot.
For surface view since Android 24, you need to use Pixel Copy.
/**
* Pixel copy to copy SurfaceView/VideoView into BitMap
*/
fun usePixelCopy(videoView: SurfaceView, callback: (Bitmap?) -> Unit) {
val bitmap: Bitmap = Bitmap.createBitmap(
videoView.width,
videoView.height,
Bitmap.Config.ARGB_8888
);
try {
// Create a handler thread to offload the processing of the image.
val handlerThread = HandlerThread("PixelCopier");
handlerThread.start();
PixelCopy.request(
videoView, bitmap,
PixelCopy.OnPixelCopyFinishedListener { copyResult ->
if (copyResult == PixelCopy.SUCCESS) {
callback(bitmap)
}
handlerThread.quitSafely();
},
Handler(handlerThread.looper)
)
} catch (e: IllegalArgumentException) {
callback(null)
// PixelCopy may throw IllegalArgumentException, make sure to handle it
e.printStackTrace()
}
}
This approach can take a screenshot of any subclass of Surface Vie eg VideoView
Screenshot.usePixelCopy(videoView) { bitmap: Bitmap? ->
processBitMap(bitmap)
}
Here is the complete method to take screen shot of a surface view using PixelCopy. It requires API 24(Android N).
#RequiresApi(api = Build.VERSION_CODES.N)
private void capturePicture() {
Bitmap bmp = Bitmap.createBitmap(surfaceView.getWidth(), surfaceView.getHeight(), Bitmap.Config.ARGB_8888);
PixelCopy.request(surfaceView, bmp, i -> {
imageView.setImageBitmap(bmp); //"iv_Result" is the image view
}, new Handler(Looper.getMainLooper()));
}
This is because SurfaceView uses OpenGL thread for drawing and draws directly to a hardware buffer. You have to use glReadPixels (and probably a GLWrapper).
See the thread: Android OpenGL Screenshot

Bitmap threshold quicker

I am trying to write a method that will take a Bitmap and force it to a strict black and white image (no shades of grey).
I first pass the bitmap to a method that makes it greyscale using colormatrix:
public Bitmap toGrayscale(Bitmap bmpOriginal)
{
int width, height;
height = bmpOriginal.getHeight();
width = bmpOriginal.getWidth();
Bitmap bmpGrayscale = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
Canvas c = new Canvas(bmpGrayscale);
Paint paint = new Paint();
ColorMatrix cm = new ColorMatrix();
cm.setSaturation(0);
ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm);
paint.setColorFilter(f);
c.drawBitmap(bmpOriginal, 0, 0, paint);
return bmpGrayscale;
}
that works nice and fast..
then i pass it to another method to force the greyscaled image to a 2 color image (black and white) this method works but obviously it is going through each pixel and that takes a long time:
public Bitmap toStrictBlackWhite(Bitmap bmp){
Bitmap imageOut = bmp;
int tempColorRed;
for(int y=0; y<bmp.getHeight(); y++){
for(int x=0; x<bmp.getWidth(); x++){
tempColorRed = Color.red(imageOut.getPixel(x,y));
Log.v(TAG, "COLOR: "+tempColorRed);
if(imageOut.getPixel(x,y) < 127){
imageOut.setPixel(x, y, 0xffffff);
}
else{
imageOut.setPixel(x, y, 0x000000);
}
}
}
return imageOut;
}
anyone know a faster more efficient way to do this?
Don't use getPixel() and setPixel().
Use getPixels() which will return a multidimensional array of all pixels. Do your operations locally on this array, then use setPixels() to set back the modified array. This will be significantly faster.
Have you tried something like converting it into a byte array (see the answer here)?
And, as I look into this, the Android reference for developers about Bitmap processing might help you as well.

Filter images android?

I'm using this algorithm to filter images in andriod.
http://xjaphx.wordpress.com/2011/06/22/image-processing-convolution-matrix/
But the images are not as expected, where I can find other ways to do this. You see that applications already do this, makes it fast, this algorithm is way too slow.
regards
I recently posted there a faster version of the code you tried, you should give it a try.
By the way, what do you mean with the sentence images are not as expected ? Maybe you're just using a wrong matrix; you can find some matrix example here.
Here is the sample you requested. If you don't need to scale / offset pixel colors, you should add different implementations of convolute without those parameters and the related unnecessary computations.
class Convolution {
private static Bitmap convolute(Bitmap bmp, Matrix mat, float factor, int offset) {
/* ... */
}
private static Matrix getEdgeEnhanceMatrix() {
Matrix m = new Matrix();
m.setValues(new float[] {
0, 0, 0,
-1, 1, 0,
0, 0, 0
});
return m;
}
// the simple way
public static Bitmap edgeEnhance1(Bitmap bmp) {
return convolute(bmp, getEdgeEnhanceMatrix(), 1f, 0);
}
// if you want to apply filter to border pixels
// warning: really memory consuming
public static Bitmap edgeEnhance2(Bitmap bmp, int bgColor) {
// create a bigger canvas
Bitmap bigger = Bitmap.createBitmap(bmp.getWidth() + 2, bmp.getHeight() + 2, bmp.getConfig());
Canvas cBigger = new Canvas(bigger);
// paint background
cBigger.drawColor(bgColor);
// draw the bmp you want to manipulate from (1,1)
cBigger.drawBitmap(bmp, 1, 1, null);
// compute convolution
bigger = convolute(bigger, getEdgeEnhanceMatrix(), 1f, 0);
// create the result and project the convolution at (-1,-1)
Bitmap rt = Bitmap.createBitmap(bmp.getWidth(), bmp.getHeight(), bmp.getConfig());
Canvas cRt = new Canvas(rt);
cRt.drawBitmap(bigger, -1, -1, null);
return rt;
}
}
I am using this formula to filter images as per their extension
class FileExtensionFilter implements FilenameFilter {
public boolean accept(File dir, String name) {
return (name.endsWith(".png") || name.endsWith(".PNG"));
}
If you are fetching it from the sd card let me know about this .I had code for it.

Blending pixels from Two Bitmaps

I'm beating my head against a wall here, and I'm fairly certain I'm doing something stupid, so time to make my stupidity public.
I'm trying to take two images, blend them together into a third image using standard blending algorithms (Hardlight, softlight, overlay, multiply, etc).
Because Android does not have such blend properties build in, I've gone down the path of taking each pixel and combine them using an algorithm. However, the results are garbage. Below is the results of a simple multiply blend (images used, and expected result).
BASE:
BLEND:
EXPECTED RESULT:
GARBAGE RESULT:
Any help would be appreciated. Below is the code, which I've tried to strip out all the "junk", but some may have made it through. I'll clean it up if something isn't clear.
ImageView imageView = (ImageView) findViewById(R.id.ImageView01);
Bitmap base = BitmapFactory.decodeResource(getResources(), R.drawable.base);
Bitmap result = base.copy(Bitmap.Config.RGB_565, true);
Bitmap blend = BitmapFactory.decodeResource(getResources(), R.drawable.blend);
IntBuffer buffBase = IntBuffer.allocate(base.getWidth() * base.getHeight());
base.copyPixelsToBuffer(buffBase);
buffBase.rewind();
IntBuffer buffBlend = IntBuffer.allocate(blend.getWidth() * blend.getHeight());
blend.copyPixelsToBuffer(buffBlend);
buffBlend.rewind();
IntBuffer buffOut = IntBuffer.allocate(base.getWidth() * base.getHeight());
buffOut.rewind();
while (buffOut.position() < buffOut.limit()) {
int filterInt = buffBlend.get();
int srcInt = buffBase.get();
int redValueFilter = Color.red(filterInt);
int greenValueFilter = Color.green(filterInt);
int blueValueFilter = Color.blue(filterInt);
int redValueSrc = Color.red(srcInt);
int greenValueSrc = Color.green(srcInt);
int blueValueSrc = Color.blue(srcInt);
int redValueFinal = multiply(redValueFilter, redValueSrc);
int greenValueFinal = multiply(greenValueFilter, greenValueSrc);
int blueValueFinal = multiply(blueValueFilter, blueValueSrc);
int pixel = Color.argb(255, redValueFinal, greenValueFinal, blueValueFinal);
buffOut.put(pixel);
}
buffOut.rewind();
result.copyPixelsFromBuffer(buffOut);
BitmapDrawable drawable = new BitmapDrawable(getResources(), result);
imageView.setImageDrawable(drawable);
}
int multiply(int in1, int in2) {
return in1 * in2 / 255;
}
After reproducing, I think your issue has to do with manipulating the images in RGB565 mode. As discussed in this post, Bitmaps apparently need to be in ARGB8888 mode to manipulate properly. I first got the expected result on a multiply blend by doing the following:
Resources res = getResources();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap base = BitmapFactory.decodeResource(res, R.drawable.base, options);
Bitmap blend = BitmapFactory.decodeResource(res, R.drawable.blend, options);
// now base and blend are in ARGB8888 mode, which is what you want
Bitmap result = base.copy(Config.ARGB_8888, true);
// Continue with IntBuffers as before...
Converting the Bitmaps to ARGB8888 mode did seem to work for me, at least with the gradient test patterns. However, it you only need to do Screen or Multiply, you might try this as well:
// Same image creation/reading as above, then:
Paint p = new Paint();
p.setXfermode(new PorterDuffXfermode(Mode.MULTIPLY));
p.setShader(new BitmapShader(blend, TileMode.CLAMP, TileMode.CLAMP));
Canvas c = new Canvas();
c.setBitmap(result);
c.drawBitmap(base, 0, 0, null);
c.drawRect(0, 0, base.getWidth(), base.getHeight(), p);
With that, you aren't doing the per-pixel calculations, but you are limited to the preset PorterDuff.Modes. In my quick (and dirty) testing, this was the only way I was able to get the blending to work on non-gradient images.
Simple overlay you can do this way (for simplicity it is supposed that bmp1 is equal or bigger than bmp2):
private Bitmap bitmapOverlay(Bitmap bmp1, Bitmap bmp2)
{
Bitmap bmOverlay = Bitmap.createBitmap(bmp1.getWidth(), bmp1.getHeight(), bmp1.getConfig());
Canvas canvas = new Canvas(bmOverlay);
canvas.drawBitmap(bmp1, 0, 0, null);
canvas.drawBitmap(bmp2, 0, 0, null);
return bmOverlay;
}
For more complex blending algorithms, maybe you can help yourself with some available Bitmap/Canvas functions.

Categories

Resources