Galaxy Nexus slow with animations - android

I've edited this code to move the Rect instantiations out of the onDraw method. I've tested it on several devices.
public class BallBouncesActivity extends Activity {
BallBounces ball;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ball = new BallBounces(this);
setContentView(ball);
}
}
class BallBounces extends SurfaceView implements SurfaceHolder.Callback {
GameThread thread;
int screenW; //Device's screen width.
int screenH; //Devices's screen height.
int ballX; //Ball x position.
int ballY; //Ball y position.
int initialY ;
float dY; //Ball vertical speed.
int ballW;
int ballH;
int bgrW;
int bgrH;
int angle;
int bgrScroll;
int dBgrY; //Background scroll speed.
float acc;
Bitmap ball, bgr, bgrReverse;
boolean reverseBackroundFirst;
boolean ballFingerMove;
Rect toRect1, fromRect1;
Rect toRect2, fromRect2;
//Measure frames per second.
long now;
int framesCount=0;
int framesCountAvg=0;
long framesTimer=0;
Paint fpsPaint=new Paint();
//Frame speed
long timeNow;
long timePrev = 0;
long timePrevFrame = 0;
long timeDelta;
public BallBounces(Context context) {
super(context);
ball = BitmapFactory.decodeResource(getResources(), R.drawable.rocket); //Load a ball image.
bgr = BitmapFactory.decodeResource(getResources(), R.drawable.sky_bgr); //Load a background.
ballW = ball.getWidth();
ballH = ball.getHeight();
toRect1 = new Rect(0, 0, bgr.getWidth(), bgr.getHeight());
fromRect1 = new Rect(0, 0, bgr.getWidth(), bgr.getHeight());
toRect2 = new Rect(0, 0, bgr.getWidth(), bgr.getHeight());
fromRect2 = new Rect(0, 0, bgr.getWidth(), bgr.getHeight());
//Create a flag for the onDraw method to alternate background with its mirror image.
reverseBackroundFirst = false;
//Initialise animation variables.
acc = 0.2f; //Acceleration
dY = 0; //vertical speed
initialY = 100; //Initial vertical position
angle = 0; //Start value for the rotation angle
bgrScroll = 0; //Background scroll position
dBgrY = 1; //Scrolling background speed
fpsPaint.setTextSize(30);
//Set thread
getHolder().addCallback(this);
setFocusable(true);
}
#Override
public void onSizeChanged (int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//This event-method provides the real dimensions of this custom view.
screenW = w;
screenH = h;
bgr = Bitmap.createScaledBitmap(bgr, w, h, true); //Scale background to fit the screen.
bgrW = bgr.getWidth();
bgrH = bgr.getHeight();
//Create a mirror image of the background (horizontal flip) - for a more circular background.
Matrix matrix = new Matrix(); //Like a frame or mould for an image.
matrix.setScale(-1, 1); //Horizontal mirror effect.
bgrReverse = Bitmap.createBitmap(bgr, 0, 0, bgrW, bgrH, matrix, true); //Create a new mirrored bitmap by applying the matrix.
ballX = (int) (screenW /2) - (ballW / 2) ; //Centre ball X into the centre of the screen.
ballY = -50; //Centre ball height above the screen.
}
//***************************************
//************* TOUCH *****************
//***************************************
#Override
public synchronized boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {
ballX = (int) ev.getX() - ballW/2;
ballY = (int) ev.getY() - ballH/2;
ballFingerMove = true;
break;
}
case MotionEvent.ACTION_MOVE: {
ballX = (int) ev.getX() - ballW/2;
ballY = (int) ev.getY() - ballH/2;
break;
}
case MotionEvent.ACTION_UP:
ballFingerMove = false;
dY = 0;
break;
}
return true;
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
//Draw scrolling background.
fromRect1.set(0, 0, bgrW - bgrScroll, bgrH);
toRect1.set(bgrScroll, 0, bgrW, bgrH);
fromRect2.set(bgrW - bgrScroll, 0, bgrW, bgrH);
toRect2.set(0, 0, bgrScroll, bgrH);
// Rect fromRect1 = new Rect(0, 0, bgrW - bgrScroll, bgrH);
// Rect toRect1 = new Rect(bgrScroll, 0, bgrW, bgrH);
//
// Rect fromRect2 = new Rect(bgrW - bgrScroll, 0, bgrW, bgrH);
// Rect toRect2 = new Rect(0, 0, bgrScroll, bgrH);
if (!reverseBackroundFirst) {
canvas.drawBitmap(bgr, fromRect1, toRect1, null);
canvas.drawBitmap(bgrReverse, fromRect2, toRect2, null);
}
else{
canvas.drawBitmap(bgr, fromRect2, toRect2, null);
canvas.drawBitmap(bgrReverse, fromRect1, toRect1, null);
}
//Next value for the background's position.
if ( (bgrScroll += dBgrY) >= bgrW) {
bgrScroll = 0;
reverseBackroundFirst = !reverseBackroundFirst;
}
//Compute roughly the ball's speed and location.
if (!ballFingerMove) {
ballY += (int) dY; //Increase or decrease vertical position.
if (ballY > (screenH - ballH)) {
dY=(-1)*dY; //Reverse speed when bottom hit.
}
dY+= acc; //Increase or decrease speed.
}
//Increase rotating angle
if (angle++ >360)
angle =0;
//DRAW BALL
//Rotate method one
/*
Matrix matrix = new Matrix();
matrix.postRotate(angle, (ballW / 2), (ballH / 2)); //Rotate it.
matrix.postTranslate(ballX, ballY); //Move it into x, y position.
canvas.drawBitmap(ball, matrix, null); //Draw the ball with applied matrix.
*/// Rotate method two
canvas.save(); //Save the position of the canvas matrix.
canvas.rotate(angle, ballX + (ballW / 2), ballY + (ballH / 2)); //Rotate the canvas matrix.
canvas.drawBitmap(ball, ballX, ballY, null); //Draw the ball by applying the canvas rotated matrix.
canvas.restore(); //Rotate the canvas matrix back to its saved position - only the ball bitmap was rotated not all canvas.
//*/
//Measure frame rate (unit: frames per second).
now=System.currentTimeMillis();
canvas.drawText(framesCountAvg+" fps", 40, 70, fpsPaint);
framesCount++;
if(now-framesTimer>1000) {
framesTimer=now;
framesCountAvg=framesCount;
framesCount=0;
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
thread = new GameThread(getHolder(), this);
thread.setRunning(true);
thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
thread.setRunning(false);
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
class GameThread extends Thread {
private SurfaceHolder surfaceHolder;
private BallBounces gameView;
private boolean run = false;
public GameThread(SurfaceHolder surfaceHolder, BallBounces gameView) {
this.surfaceHolder = surfaceHolder;
this.gameView = gameView;
}
public void setRunning(boolean run) {
this.run = run;
}
public SurfaceHolder getSurfaceHolder() {
return surfaceHolder;
}
#Override
public void run() {
Canvas c;
while (run) {
c = null;
//limit frame rate to max 60fps
timeNow = System.currentTimeMillis();
timeDelta = timeNow - timePrevFrame;
if ( timeDelta < 16) {
try {
Thread.sleep(16 - timeDelta);
}
catch(InterruptedException e) {
}
}
timePrevFrame = System.currentTimeMillis();
try {
c = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder) {
//call methods to draw and process next fame
gameView.onDraw(c);
}
} finally {
if (c != null) {
surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
}
If you notice, there's code to measure the frame rate:
now=System.currentTimeMillis();
canvas.drawText(framesCountAvg+" fps", 40, 70, fpsPaint);
framesCount++;
if(now-framesTimer>1000) {
framesTimer=now;
framesCountAvg=framesCount;
framesCount=0;
}
I'm seeing that on both of my Galaxy Nexus devices, running Android 4.0 and 4.2, it's around 22-24fps. On my HTC Desire, running Android 2.2, it's more like 60fps.
You'll also notice that I'm not allocating anything in the onDraw method. I'm not creating new Paint objects either. I really don't see how this is running so, so, so much slower on the my Galaxy Nexus devices. There's a lot of stuttering and the ball moves very slowly.
Does anyone know whether there is a setting I can undo or a known issue with re-drawing on the Galaxy Nexus? This is happening to my Galaxy Nexus that's running 4.0 and the one that's running 4.2, so I'm not sure it's OS-specific. I have turned off the window and transition animations in Developer Options. It doesn't make a difference whether I force 2D acceleration.

Have you tested android:hardwareAccelerated="true|false" flag in application?
Android Dev Doc: http://developer.android.com/guide/topics/graphics/hardware-accel.html
You can add before setContentView:
if (android.os.Build.VERSION.SDK_INT >= 11) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
}

I've been thinking about this, as I'm still having performance issues on my Nexus 10 with surfaceview, like I said previously, I increased my performance a lot my removing the use of paint objects, it's a long shot as you are only using one, but you could try to remove the text drawing section from your onDraw() just to see if it makes any difference to your drawing speed.
Other than that, I think it's really a case of trying to pin down the problem.
I've noticed that even if I remove my onDraw and Logic updating methods completely from the equation, it's still taking sometimes up to 25ms just to lock and unlock/post the canvas! So it might be that the problem actually doesn't lie in onDraw method - give it a go, comment onDraw() out from your Run() method and see what speeds you get (Use logging to Logcat to see the figures, remember that ironically, displaying a frame / time count on the screen could affect the very thing you're measuring). :-)

What's your targetSdk set to?
If targetSdk is set to 8 (2.2), but you're running it on a 4.x device (15+), the 4.x device will run in compatibility mode, meaning that it will virtualize all the calls so that they return exactly the same as if they're running on a version 8 device.
This virtualization might explain some of the slowness. Try changing targetSdk to 17 (for your 4.2 device) and see if it makes a difference.

I has experienced the same thing on Kindle Fire, Sony Xperia Z and Samsung S4 (all with android 4.2).
The fix is: at App Manifest file remove "android:supportsRtl="true"".
Hope it will save your time. I spend 4 hours on tests and merging before i got it.

Related

Draw Simple Horizontal Line On GL Surface View

Good day. I am pretty much frustrated as what I want to achieve is the simplest thing in world but nowhere I could get anything. I just want to draw an simple HORIZONTAL line in the centre of the GL surface view (the idea is to make audio wave animation based on the amplitude of the recording) so I did achieve that simply and easy with canvas, but with GL surface view there are not even one simple example or one simple question on how to exactly draw that line on the surface of gl.
Anyway here is what I got and have no clue where I should write the draw code and what should I write to achieve above desired function.
GL Render class
public class MyGLRenderer implements GLSurfaceView.Renderer {
GL10 unused;
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
// Set the background frame color
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}
public void onDrawFrame(GL10 unused) {
// Redraw background color
// GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
this.unused = unused;
}
public void onSurfaceChanged(GL10 unused, int width, int height) {
GLES20.glViewport(0, 0, width, height);
}
}
GL View class (this class is copy pasted from another one which extends SurfaceView so do not take a notice of the canvas and etc.They will be deleted)
public class GLView extends GLSurfaceView {
// The number of buffer frames to keep around (for a nice fade-out visualization).
private static final int HISTORY_SIZE = 1;
private MyGLRenderer mRenderer;
Handler handler = new Handler();
private boolean isFinished=true;
private Runnable runnable = new Runnable() {
#Override
public void run() {
int colorDelta = 255 / (HISTORY_SIZE + 1);
int brightness = colorDelta;
final Random rnd = new Random();
mPaint.setColor(Color.argb(brightness, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256)));
isFinished = true;
brightness += colorDelta;
}
};
// To make quieter sounds still show up well on the display, we use +/- 8192 as the amplitude
// that reaches the top/bottom of the view instead of +/- 32767. Any samples that have
// magnitude higher than this limit will simply be clipped during drawing.
private static final float MAX_AMPLITUDE_TO_DRAW = 10000.0f;
// The queue that will hold historical audio data.
private final LinkedList<short[]> mAudioData;
private final Paint mPaint;
public GLView(Context context) {
super(context);
// Create an OpenGL ES 2.0 context
setEGLContextClientVersion(2);
mAudioData = new LinkedList<short[]>();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.WHITE);
mPaint.setStrokeWidth(5);
mPaint.setAntiAlias(true);
mRenderer = new MyGLRenderer();
// Set the Renderer for drawing on the GLSurfaceView
setRenderer(mRenderer);
}
public GLView(Context context, AttributeSet attrs) {
super(context, attrs);
mAudioData = new LinkedList<short[]>();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.WHITE);
mPaint.setStrokeWidth(5);
mPaint.setAntiAlias(true);
mRenderer = new MyGLRenderer();
// Set the Renderer for drawing on the GLSurfaceView
setRenderer(mRenderer);
}
/**
* Updates the waveform view with a new "frame" of samples and renders it. The new frame gets
* added to the front of the rendering queue, pushing the previous frames back, causing them to
* be faded out visually.
*
* #param buffer the most recent buffer of audio samples
*/
public synchronized void updateAudioData(short[] buffer) {
short[] newBuffer;
// We want to keep a small amount of history in the view to provide a nice fading effect.
// We use a linked list that we treat as a queue for this.
if (mAudioData.size() == HISTORY_SIZE) {
newBuffer = mAudioData.removeFirst();
System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
} else {
newBuffer = buffer.clone();
}
mAudioData.addLast(newBuffer);
final Canvas canvas = getHolder().lockCanvas();
if (canvas != null) {
drawWaveform(canvas);
getHolder().unlockCanvasAndPost(canvas);
}
}
/**
* Repaints the view's surface.
*
* #param canvas the {#link Canvas} object on which to draw
*/
private void drawWaveform(Canvas canvas) {
// Clear the screen each time because SurfaceView won't do this for us.
canvas.drawColor(Color.BLACK);
float width = getWidth();
float height = getHeight();
float centerY = height / 2;
// We draw the history from oldest to newest so that the older audio data is further back
// and darker than the most recent data.
int colorDelta = 255 / (HISTORY_SIZE + 1);
int brightness = colorDelta;
final Random rnd = new Random();
if(isFinished){
isFinished = false;
handler.postDelayed(runnable, 500);
}
for (short[] buffer : mAudioData) {
// mPaint.setColor(Color.argb(brightness, 128, 255, 192));
float lastX = -1;
float lastY = -1;
// For efficiency, we don't draw all of the samples in the buffer, but only the ones
// that align with pixel boundaries.
for (int x = 0; x < width; x++) {
int index = (int) ((x / width) * buffer.length);
short sample = buffer[index];
float y = (sample / MAX_AMPLITUDE_TO_DRAW) * centerY + centerY;
if (lastX != -1) {
canvas.drawLine(lastX, lastY, x, y, mPaint);
}
lastX = x;
lastY = y;
}
brightness += colorDelta;
}
}
}
Please...can you help me and tell me how to draw horizontal centered line on this gl surface view?

Android - adding a custom view to scroll view where the custom view is an inner class

I've been trying to enable my application to scroll my custom view (both horizontally and vertically) to enable the user to draw on more than just the initial screen size. The application is to be used for basic house plan generation e.g. a user draws connected rectangles representing their home.
I am using a custom View which I have set up (as an inner class) inside the activity. After extensive searching and seeing the issues people are having with scroll views, this is as far as I've managed to get. Here's my XML:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<com.example.HomerProject1stDraft.DrawNewPlans.HomerView
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</com.example.HomerProject1stDraft.DrawNewPlans.HomerView>
</LinearLayout>
As you can see I'm trying to add my HomerView class which is inside the DrawNewPlans class (the activity).
Here is the onCreate() method:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.drawnew);
}
Here's the code for my custom view (bearing in mind this is contained in another class):
class HomerView extends View { // the custom View for drawing on
// set up Bitmap, canvas, path and paint
private Bitmap myBitmap; // the initial image we turn into our canvas
private Canvas myCanvas; // the canvas we are drawing on
private Rect myRect; // the mathematical path of the lines we draw
private Paint myBitmapPaint; // the paint we use to draw the bitmap
// get the width of the entire tablet screen
private int screenWidth = getContext().getResources()
.getDisplayMetrics().widthPixels;
// get the height of the entire tablet screen
private int screenHeight = (getContext().getResources()
.getDisplayMetrics().heightPixels);
private int mX, mY, iX, iY; // current x,y and in
public HomerView(Context context) { // constructor of HomerView
super(context);
System.out.println("Screen Width = "+screenWidth+" and screen height = "+screenHeight);
myBitmap = Bitmap.createBitmap(screenWidth, screenHeight,
Bitmap.Config.ARGB_8888); // set our drawable space - the bitmap which becomes the canvas we draw on
myCanvas = new Canvas(myBitmap); // set our canvas to our bitmap which we just set up
myRect = new Rect(); // make a new rect
myBitmapPaint = new Paint(Paint.DITHER_FLAG); // set dither to ON in our saved drawing - gives better color interaction
}
protected void onDraw(Canvas canvas) { // method used when we want to draw something to our canvas
super.onDraw(canvas);
if (addObjectMode == true || addApplianceMode == true) {
canvas.drawColor(Color.TRANSPARENT); // sets canvas colour
canvas.drawBitmap(myBitmap, 0, 0, myBitmapPaint); // save the canvas to bitmap - the numbers are the x, y coords we are drawing from
for (int i = 0; i < rectList.size(); i++) {
canvas.drawRect(rectList.get(i), myPaint);
}
if (addApplianceMode == true) {
canvas.drawBitmap(bmp, iX-50, iY-50, myBitmapPaint);
}
canvas.drawRect(myRect, myPaint); // draw the rectangle that the user has drawn using the paint we set up
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) { // if screen size changes, alter the bitmap size
super.onSizeChanged(w, h, oldw, oldh);
}
private void touch_Start(float x, float y) { // on finger touchdown
// check touch mode
iX = (int) (Math.round(x));
iY = (int) (Math.round(y));
mX = (int) (Math.round(x));
mY = (int) (Math.round(y));
if (addObjectMode == true) {
myRect.set(iX, iY, mX, mY);
} else if (addApplianceMode == true) {
// code to draw an appliance icon at mX, mY (with offset so icon is centered)
if (isLamp == true) {
Resources res = getResources();
bmp = BitmapFactory.decodeResource(res, R.drawable.lamp);
myCanvas.drawBitmap(bmp, iX - 50, iY - 50, myBitmapPaint);
} else if (isPC == true) {
Resources res = getResources();
bmp = BitmapFactory.decodeResource(res, R.drawable.pc);
myCanvas.drawBitmap(bmp, iX - 50, iY - 50, myBitmapPaint);
} else if (isKettle == true) {
Resources res = getResources();
bmp = BitmapFactory.decodeResource(res, R.drawable.kettle);
myCanvas.drawBitmap(bmp, iX - 50, iY - 50, myBitmapPaint);
} else if (isOven == true) {
Resources res = getResources();
bmp = BitmapFactory.decodeResource(res, R.drawable.oven);
myCanvas.drawBitmap(bmp, iX - 50, iY - 50, myBitmapPaint);
} else if (isTV == true) {
Resources res = getResources();
bmp = BitmapFactory.decodeResource(res, R.drawable.tv);
myCanvas.drawBitmap(bmp, iX - 50, iY - 50, myBitmapPaint);
}
}
}
private void touch_Move(float x, float y) { // on finger movement
float dX = Math.abs(x - mX); // get difference between x and my X
float dY = Math.abs(y - mY);
if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) { // if coordinates are outside screen? if touching hard enough?
mX = (int) (Math.round(x));
mY = (int) (Math.round(y));
if (addObjectMode == true) {
myRect.set(iX, iY, mX, mY);
}
}
}
#SuppressWarnings("deprecation")
private void touch_Up() { // on finger release
if (addObjectMode == true) {
myRect.set(iX, iY, mX, mY);
System.out.println("MyRect coords = "+iX+","+iY+","+mX+","+mY);
rectList.add(myRect);
myCanvas.drawRect(iX, iY, mX, mY, myPaint); // if this isnt present - no lasting copy is drawn to screen. It is erased every draw.
if (eraseMode == false) {
dialogStarter();
}
} else if (addApplianceMode == true) {
showDialog(DIALOG_DEVICE_ENTRY);
}
}
public boolean onTouchEvent(MotionEvent event) { // on any touch event
if (addObjectMode == true || addApplianceMode == true) {
float x = event.getX(); // get current X
float y = event.getY(); // get current Y
switch (event.getAction()) { // what action is the user performing?
case MotionEvent.ACTION_DOWN: // if user is touching down
touch_Start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE: // if user is moving finger while touched down
touch_Move(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP: // if user has released finger
touch_Up();
invalidate();
break;
}
return true;
} else {
return false;
}
}
}
I realise there are many things I need to add - but what are they? awakenScrollBars()?
Also: at the moment I get this error Error inflating class com.example.HomerProject1stDraft.DrawNewPlans.HomerView in the XML file. Is there a mistake in the way I'm creating the custom view? Can you even create a view from an inner class?
Many Thanks.
Firstly , i would suggest not to wrap your custom scrollview in another scrollview.If it is avoidable, avoid it.Also, you can check below link where i have implemented a custom scrollview.
https://stackoverflow.com/questions/15188842/customization-of-imageview-and-layouts
ScrollView only supports verticall scrolling. You need scrolling on both sides horizontal and vertical for that you will need a special class known as TwoDScrollView.
more information about this class and source code is available at:
http://blog.gorges.us/2010/06/android-two-dimensional-scrollview/
In your xml use this class instead of ScrollView and you should be able to scroll both horizontally and vertically.
For your other error please share the source code and state where you get the problem.

Scale & rotate Bitmap using Matrix in Android

I'm trying to scale and rotate in single operation before creting the final bitmap but the preRotate, postConcat doesn't seem to work.
Bitmap bmp = ... original image ...
Matrix m = new Matrix()
m.setScale(x, y);
m.preRotate(degrees, (float) width / 2, (float) height / 2);
Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), m, true);
It only applies the scale and not rotation.
The answer was given, but to make things more clear to anyone reading this:
1) if you wish to perform ONE transformation in your bitmap, you CAN use SET (setRotate, setScale etc).
But note that any call to a "set" method OVERRIDES other transformations. It's like a new matrix. That's why OP's rotation was not working. These calls are not performed line by line. It's like they are scheduled to be done at runtime by the GPU when the new bitmap is being drawn. It's like when resolving your matrix, GPU rotated it, but then, created a scaled new one, ignoring previous matrix.
2) if you wish to perform more then one transformation, then you MUST use "pre" or "post" methods.
And what is the difference between a postRotate and a preRotate, for example? Well, this matrix math stuff is not my strength, but what I know is that the graphic cards make these transformations using matrix multiplication. It seems to be way more efficient. And as far as I remember from school, when multiplicating matrices the order IS important. A X B != B X A. So, scale a matrix and then rotate it is different from rotate and then scale it.
BUUUUT, as far as the final result in the screen is the same, we high level programmers usually do not need to know these differences. The GPU does.
Well, in that rare cases when you are performing really complicated matrix operations, and results are not what you expected or the performance is terrible and you need to deeply understand these methods to fix your code, well, then android documentation can not be of much help anyway. Instead, a good Linear Algebra book would be your best friend. ;)
This is the code
public class Bitmaptest extends Activity {
#Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
LinearLayout linLayout = new LinearLayout(this);
// load the origial BitMap (500 x 500 px)
Bitmap bitmapOrg = BitmapFactory.decodeResource(getResources(),
R.drawable.android);
int width = bitmapOrg.getWidth();
int height = bitmapOrg.getHeight();
int newWidth = 200;
int newHeight = 200;
// calculate the scale - in this case = 0.4f
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// createa matrix for the manipulation
Matrix matrix = new Matrix();
// resize the bit map
matrix.postScale(scaleWidth, scaleHeight);
// rotate the Bitmap
matrix.postRotate(45);
// recreate the new Bitmap
Bitmap resizedBitmap = Bitmap.createBitmap(bitmapOrg, 0, 0,
newWidth, newHeight, matrix, true);
// make a Drawable from Bitmap to allow to set the BitMap
// to the ImageView, ImageButton or what ever
BitmapDrawable bmd = new BitmapDrawable(resizedBitmap);
ImageView imageView = new ImageView(this);
// set the Drawable on the ImageView
imageView.setImageDrawable(bmd);
// center the Image
imageView.setScaleType(ScaleType.CENTER);
// add ImageView to the Layout
linLayout.addView(imageView,
new LinearLayout.LayoutParams(
LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT
)
);
// set LinearLayout as ContentView
setContentView(linLayout);
}
}
If you face the issue of OutOfMemory with above answers, than use below:
Bitmap MyFinalBitmap = Bitmap.createBitmap(CurrentBitmap, 0, 0,CurrentBitmap.getWidth()/2, CurrentBitmap.getHeight()/2,matrix, true);
Canvas holds a matrix stack und you can use it with the methods:
Canvas.save()
Doc:
/**
* Saves the current matrix and clip onto a private stack.
*
* Subsequent calls to translate,scale,rotate,skew,concat or clipRect,
* clipPath will all operate as usual, but when the balancing call to
* restore() is made, those calls will be forgotten, and the settings that
* existed before the save() will be reinstated.
*
* #return The value to pass to restoreToCount() to balance this save()
*/
Canvas.restore()
Doc:
/**
* This call balances a previous call to save(), and is used to remove all
* modifications to the matrix/clip state since the last save call. It is
* an error to call restore() more times than save() was called.
*/
example:
A custom View(Android) which looks like a rotary knob(e.g. potentiometer)
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
viewX = getWidth(); //views width
viewY = getHeight(); //views height
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); //a must call for every custom view
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
double tempAngel = 3.6 * barValue;
int deltaX = bitmap.getWidth() / 2;
int deltaY = bitmap.getHeight() / 2;
...
canvas.save();
canvas.translate(viewX / 2, viewY / 2); //translate drawing point to center
canvas.rotate((float) tempAngel); //rotate matrix
canvas.save(); //save matrix. your drawing point is still at (viewX / 2, viewY / 2)
canvas.translate(deltaX * -1, deltaY * -1); //translate drawing point a bit up and left to draw the bitmap in the middle
canvas.drawBitmap(bitmap, 0,0, bitmapPaint); // draw bitmap to the tranlated point at 0,0
canvas.restore(); //must calls...
canvas.restore();
}
All of the previous answer assume that this change to the bitmap is being made in a view. However in my case I was making the change to be saved out. Figured I would answer it for those in a similar boat.
There are two ways to do translation. Below dx is the translation in the X axis, and dy is the translation in the Y axis. The other variables should be self explanatory.
1 - Translation within the image (without rotation)
val newBitmap = Bitmap.createBitmap(originalBitmap, dx, dy, newWidth, newHeight, matrix, false)
2 - Complex matrix
matrix.postTranslate(dx, dy)
val newBitmap = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(newBitmap)
canvas.drawBitmap(originalBitmap, matrix, null)
Matrix rotateMatrix = new Matrix();
rotateMatrix.postRotate(rotation);
rotatedBitmap = Bitmap.createBitmap(loadedImage, 0, 0,loadedImage.getWidth(), loadedImage.getHeight(),rotateMatrix, false);
Refer to the following code, seems to work. In your code you are defining Matrix as m but referring to it as matrix
public class FourthActivity extends Activity {
private static final int WIDTH = 50;
private static final int HEIGHT = 50;
private static final int STRIDE = 64;
private static int[] createColors() {
int[] colors = new int[STRIDE * HEIGHT];
for (int y = 0; y < HEIGHT; y++) {
for (int x = 0; x < WIDTH; x++) {
int r = x * 255 / (WIDTH - 1);
int g = y * 255 / (HEIGHT - 1);
int b = 255 - Math.min(r, g);
int a = Math.max(r, g);
colors[y * STRIDE + x] = (a << 24) | (r << 16) | (g << 8) | b;
}
}
return colors;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main2);
final ImageView view1 = (ImageView) findViewById(R.id.imageView1);
int[] colors = createColors();
final Bitmap bmp1 = Bitmap.createBitmap(colors, 0, STRIDE, WIDTH, HEIGHT,
Bitmap.Config.ARGB_8888);
view1.setImageBitmap(bmp1);
Button button = (Button) findViewById(R.id.button1);
button.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v) {
Matrix matrix = new Matrix();
matrix.setScale(2, 2);
matrix.preRotate(45, (float) WIDTH / 2, (float) HEIGHT / 2);
Bitmap bmp2 = Bitmap.createBitmap(bmp1, 0, 0,
bmp1.getWidth(), bmp1.getHeight(), matrix, true);
ImageView view2 = (ImageView) findViewById(R.id.imageView2);
view2.setImageBitmap(bmp2);
}
});
}
}
Use matrix to scale area of original bitmap to 50% and compress bitmap until it's size < 200k
Compress bitmap to a specific byte size in Android

Android Issue with 'createBitmap' and Sub-pixel Data

I'm currently developing a small Android application to familiarize myself with the API. However, I have come across a problem regarding sub-pixel data while using createBitmap. I currently have these blocks of code. (NOTE: Image being read in is a 128x128 RGB565 jpeg):
public class MainMenu extends View {
private int vWidth; // Width of our view
private int vHeight; // Height of our view
private Bitmap imgButtons;
private Rect rect;
private MenuItem testButton;
private MenuItem testButton2;
public MainMenu(Context context) {
super(context);
// Get our view dimensions
vWidth = getWidth();
vHeight = getHeight();
// Load our menu buttons' image
BitmapFactory.Options imgOptions = new BitmapFactory.Options();
imgButtons = BitmapFactory.decodeResource(getResources(), R.drawable.mainmenubuttons, imgOptions);
rect = new Rect(100, 400, 228, 528);
// Create our menu buttons and load their specific images
testButton = new MenuItem(100, 50);
testButton2 = new MenuItem(100, 200);
testButton.LoadImage(imgButtons, 128, 64, 0, 0);
testButton2.LoadImage(imgButtons, 128, 64, 0, 64);
}
#Override
public void onDraw(Canvas canvas) {
canvas.drawBitmap(imgButtons, null, rect, null);
testButton.Draw(canvas);
testButton2.Draw(canvas);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() != MotionEvent.ACTION_DOWN)
super.onTouchEvent(event);
return true;
}
}
class MenuItem {
private int xPos; // Center x-coordinate
private int yPos; // Center y-coordinate
private int width; // Button width
private int height; // Button height
private Rect rect;
private Bitmap buttonImage;
public MenuItem(int withXPos, int withYPos) {
xPos = withXPos;
yPos = withYPos;
}
public void LoadImage(Bitmap image, int imageWidth, int imageHeight, int xOffset, int yOffset) {
width = imageWidth;
height = imageHeight;
buttonImage = Bitmap.createBitmap(image, xOffset, yOffset, width, height);
rect = new Rect(xPos - (width >> 1), yPos - (height >> 1),
xPos + (width >> 1), yPos + (height >> 1));
}
public void Draw(Canvas canvas) {
canvas.drawBitmap(buttonImage, null, rect, null);
}
}
Below is an image displaying the issue:
I lied. I'm not allowed to post images yet, so here is a link to the image:
createBitmap issues
So, why is that I can display the original image correctly, but cannot correctly display images created from its sub-pixel data? I have run the application using versions 2.1 through 3.0, and 3.0 has no issues displaying the image correctly. Is it just an issue with devices before version 3.0, or am I missing something? I found an article on stacked describing issues between solid images used(RBG565), and images that contain alpha(ARGB8888). Where, my method wouldn't work if the image was of type ARGB8888, and if so I needed to physically handle the pixel data and copy the sub-pixel data myself and create my own pixel buffer for storing that data. I tried this on a whim and it lead me down to the same issue. I just figured why not try it? I have also tried other variations of Bitmap.createBitmap to no avail. At this point I'm a little stumped and figured I'd ask the community.

Android: Scrollable (bitmap) screen

I am currently implementing a view in Android that involves using a larger than the screen size bitmap as a background and then having drawables drawn ontop of this. This is so as to simulate a "map" that can be scrolled horizontally aswell as vertically.
Which is done by using a canvas and then drawing to this the full "map" bitmap, then putting the other images on top as an overlay and then drawing only the viewable bit of this to screen.
Overriding the touch events to redraw the screen on a scroll/fling.
I'm sure this probably has a huge ammount of overhead (by creating a canvas of the full image whilst using(drawing) only a fifth of it) and could be done in a different way as to the explained, but I was just wondering what people would do in this situation, and perhaps examples?
If you need more info just let me know,
Thanks,
Simon
I cooked up an example of how to do this using the BitmapRegionDecoder API. The example has a large (6000,4000) image of the world that the user can scroll around in full resolution. The initial tag is quite small and easy to understand.
I write this class to an project I'm developing.
public class ScrollableImage extends View {
private Bitmap bmLargeImage; // bitmap large enough to be scrolled
private Rect displayRect = null; // rect we display to
private Rect scrollRect = null; // rect we scroll over our bitmap with
private int scrollRectX = 0; // current left location of scroll rect
private int scrollRectY = 0; // current top location of scroll rect
private float scrollByX = 0; // x amount to scroll by
private float scrollByY = 0; // y amount to scroll by
private int width, height;
private Paint background;
public ScrollableImage(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ScrollableImage(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setSize(int width, int height) {
background = new Paint();
background.setColor(Color.WHITE);
this.width = width;
this.height = height;
// Destination rect for our main canvas draw. It never changes.
displayRect = new Rect(0, 0, width, height);
// Scroll rect: this will be used to 'scroll around' over the
// bitmap in memory. Initialize as above.
scrollRect = new Rect(0, 0, width, height);
// scrollRect = new Rect(0, 0, bmp.getWidth(), bmp.getHeight());
}
public void setImage(Bitmap bmp) {
if (bmLargeImage != null)
bmLargeImage.recycle();
bmLargeImage = bmp;
scrollRect = new Rect(0, 0, width, height);
scrollRectX = 0;
scrollRectY = 0;
scrollByX = 0;
scrollByY = 0;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
return true; // done with this event so consume it
}
public void notifyScroll(float distX, float distY) {
scrollByX = distX; // move update x increment
scrollByY = distY; // move update y increment
}
#Override
protected void onDraw(Canvas canvas) {
if (bmLargeImage == null)
return;
if (scrollByX != 0 || scrollByY != 0) {
// Our move updates are calculated in ACTION_MOVE in the opposite direction
// from how we want to move the scroll rect. Think of this as
// dragging to
// the left being the same as sliding the scroll rect to the right.
int newScrollRectX = scrollRectX - (int) scrollByX;
int newScrollRectY = scrollRectY - (int) scrollByY;
scrollByX = 0;
scrollByY = 0;
// Don't scroll off the left or right edges of the bitmap.
if (newScrollRectX < 0)
newScrollRectX = 0;
else if (newScrollRectX > (bmLargeImage.getWidth() - width))
newScrollRectX = (bmLargeImage.getWidth() - width);
// Don't scroll off the top or bottom edges of the bitmap.
if (newScrollRectY < 0)
newScrollRectY = 0;
else if (newScrollRectY > (bmLargeImage.getHeight() - height))
newScrollRectY = (bmLargeImage.getHeight() - height);
scrollRect.set(newScrollRectX, newScrollRectY, newScrollRectX
+ width, newScrollRectY + height);
scrollRectX = newScrollRectX;
scrollRectY = newScrollRectY;
}
canvas.drawRect(displayRect, background);
// We have our updated scroll rect coordinates, set them and draw.
canvas.drawBitmap(bmLargeImage, scrollRect, displayRect, background);
}
}
And in the gesture listener I has this implementation of onScroll
Where img is your ScrollableImage instance.
Remember to use the setImage with your large image.
Edit: Also use setSize to set the size of your display.
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
img.notifyScroll(-distanceX, -distanceY);
img.invalidate();
return true;
}
I would divide the huge image into tiles and then draw the appropriate tiles depending on which part of the image has to be shown. Pretty much what Google Maps does. You can check http://openzoom.org/. There is nothing in Android but I think you can follow the same approach.

Categories

Resources