Using the onFrameAvailable() in Jacobi Google Tango API - google-project-tango

Question: Does anyone know how to get the Tango's color camera image buffer using the Tango Java (Jacobi) API onFrameAvailable() callback?
Background:
I have an augmented reality application that displays video in the background of the Tango. I've successfully created the video overlay example using the the Java API (Jacobi) following this example. My application works fine, and the video is rendered in the background properly.
As part of the application, I'd like to store a copy of the video backbuffer when the user presses a button. Therefore, I need access to the camera's RGB data.
According to the Jacobi release notes, any class desiring access to the camera RGB data should implement the new onFrameAvailable() method in the OnTangoUpdateListener. I did this, but I don't see any handle or arguments to actually get the pixels:
Java API
#Override
public void onFrameAvailable(int cameraId) {
//Log.w(TAG, "Frame available!");
if (cameraId == TangoCameraIntrinsics.TANGO_CAMERA_COLOR) {
tangoCameraPreview.onFrameAvailable();
}
}
as shown, onFrameAvailable only has one argument, and integer designating the id of the camera generating the view. Contrast this with the C-library call back, which provides access to the image buffer:
C API
TangoErrorType TangoService_connectOnFrameAvailable(
TangoCameraId id, void* context,
void (*onFrameAvailable)(void* context, TangoCameraId id,
const TangoImageBuffer* buffer));
I was expecting the Java method to have something similar to the buffer object in the C API call.
What I've Tried
I tried extending the TangoCameraPreview class and saving the image there, but I only get a black background.
public class CameraSurfaceView extends TangoCameraPreview {
private boolean takeSnapShot = false;
public void takeSnapShot() {
takeSnapShot = true;
}
/**
* Grabs a copy of the surface (which is rendering the Tango color camera)
* https://stackoverflow.com/questions/14620055/how-to-take-a-screenshot-of-androids-surface-view
*/
public void screenGrab2(){
int width = this.getWidth();
int height = this.getHeight();
long fileprefix = System.currentTimeMillis();
View v= getRootView();
v.setDrawingCacheEnabled(true);
// this is the important code :)
// Without it the view will have a dimension of 0,0 and the bitmap will be null
v.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
v.layout(0, 0, width, height);
v.buildDrawingCache(true);
Bitmap image = v.getDrawingCache();
//TODO: make seperate subdirctories for each exploitation sessions
String targetPath =Environment.getExternalStorageDirectory() + "/RavenEye/Photos/";
String imageFileName = fileprefix + ".jpg";
if(!(new File(targetPath)).exists()) {
new File(targetPath).mkdirs();
}
try {
File targetDirectory = new File(targetPath);
File photo=new File(targetDirectory, imageFileName);
FileOutputStream fos=new FileOutputStream(photo.getPath());
image.compress(CompressFormat.JPEG, 100, fos);
fos.flush();
fos.close();
Log.i(this.getClass().getCanonicalName(), "Grabbed an image in target path:" + targetPath);
} catch (FileNotFoundException e) {
Log.e(CameraPreview.class.getName(),"Exception " + e);
e.printStackTrace();
} catch (IOException e) {
Log.e(CameraPreview.class.getName(),"Exception " + e);
e.printStackTrace();
}
}
/**
* Grabs a copy of the surface (which is rendering the Tango color camera)
*/
public void screenGrab(){
int width = this.getWidth();
int height = this.getHeight();
long fileprefix = System.currentTimeMillis();
Bitmap image = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(image);
canvas.drawBitmap(image, 0, 0, null);
//TODO: make seperate subdirctories for each exploitation sessions
String targetPath =Environment.getExternalStorageDirectory() + "/RavenEye/Photos/";
String imageFileName = fileprefix + ".jpg";
if(!(new File(targetPath)).exists()) {
new File(targetPath).mkdirs();
}
try {
File targetDirectory = new File(targetPath);
File photo=new File(targetDirectory, imageFileName);
FileOutputStream fos=new FileOutputStream(photo.getPath());
image.compress(CompressFormat.JPEG, 100, fos);
fos.flush();
fos.close();
Log.i(this.getClass().getCanonicalName(), "Grabbed an image in target path:" + targetPath);
} catch (FileNotFoundException e) {
Log.e(CameraPreview.class.getName(),"Exception " + e);
e.printStackTrace();
} catch (IOException e) {
Log.e(CameraPreview.class.getName(),"Exception " + e);
e.printStackTrace();
}
}
#Override
public void onFrameAvailable() {
super.onFrameAvailable();
if(takeSnapShot) {
screenGrab();
takeSnapShot = false;
}
}
public CameraSurfaceView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
}
Where I'm Heading
I'm preparing to root the device, and then using the onFrameAvailable method to cue an external root process such as one of these:
post 23610900
post 10965409
post 4998527
I'm hoping I can find a way to avoid the root hack.
Thank you in advance!

OK, I figured out a way to make it work.
Update: My working solution is here:
https://github.com/stevehenderson/GoogleTango_AR_VideoCapture
I essentially set up a "man (renderer) in the middle" attack on the rendering pipeline.
This approach intercepts the SetRenderer call from the TangoCameraPreview base class, and allows one to get access
to the base renderer's OnDraw() method and the GL context. I then add additional methods to this extended renderer that allow reading of the GL buffer.
General approach
1) Extend the TangoCameraPreview class (e.g. in my example ReadableTangoCameraPreview). Override the setRenderer(GLSurfaceView.Renderer renderer), keeping a reference to the base renderer, and replacing the renderer with your own "wrapped" GLSUrface.Renderer renderer that will add methods to render the backbuffer to an image on the device.
2) Create your own GLSurfaceView.Renderer Interface (e.g. my ScreenGrabRenderer class ) that implements all the GLSurfaceView.Renderer methods, passing them on to the base renderer captured in Step 1. Also, add a few new methods to "cue" when you want to grab the image.
3) Implement the ScreenGrabRenderer described in step 2 above.
4) Use a callback interface (my TangoCameraScreengrabCallback) to communicate when an image has been copied
It works pretty well, and allows one to grab the camera bits in an image without rooting the device.
Note: I haven't had the need to closely synchronize my captured images with the point cloud. So I haven't checked the latency. For best results, you may need to invoke the C methods proposed by Mark.
Here's what each of my classes looks like..
///Main Activity Class where bulk of Tango code is
.
.
.
// Create our Preview view and set it as the content of our activity.
mTangoCameraPreview = new ReadableTangoCameraPreview(getActivity());
RelativeLayout preview = (RelativeLayout) view.findViewById(R.id.camera_preview);
preview.addView(mTangoCameraPreview);
.
.
.
//When you want to take a snapshot, call the takeSnapShotMethod()
//(you can make this respond to a button)
mTangoCameraPreview.takeSnapShot();
.
.
.
.
.
//Main Tango Listeners
#Override
public void onFrameAvailable(final int cameraId) {
// Update the UI with TangoPose information
runOnUiThread(new Runnable() {
#Override
public void run() {
if (cameraId == TangoCameraIntrinsics.TANGO_CAMERA_COLOR) {
tangoCameraPreview.onFrameAvailable();
}
}
});
}
ReadableTangoCameraPreview Class
public class ReadableTangoCameraPreview extends TangoCameraPreview implements TangoCameraScreengrabCallback {
Activity mainActivity;
private static final String TAG = ReadableTangoCameraPreview.class.getSimpleName();
//An intercept renderer
ScreenGrabRenderer screenGrabRenderer;
private boolean takeSnapShot = false;
#Override
public void setRenderer(GLSurfaceView.Renderer renderer) {
//Create our "man in the middle"
screenGrabRenderer= new ScreenGrabRenderer(renderer);
//Set it's call back
screenGrabRenderer.setTangoCameraScreengrabCallback(this);
//Tell the TangoCameraPreview class to use this intermediate renderer
super.setRenderer(screenGrabRenderer);
Log.i(TAG,"Intercepted the renderer!!!");
}
/**
* Set a trigger for snapshot. Call this from main activity
* in response to a use input
*/
public void takeSnapShot() {
takeSnapShot = true;
}
#Override
public void onFrameAvailable() {
super.onFrameAvailable();
if(takeSnapShot) {
//screenGrabWithRoot();
screenGrabRenderer.grabNextScreen(0,0,this.getWidth(),this.getHeight());
takeSnapShot = false;
}
}
public ReadableTangoCameraPreview(Activity context) {
super(context);
mainActivity = context;
}
public void newPhoto(String aNewPhotoPath) {
//This gets called when a new photo was grabbed created in the renderer
Log.i(TAG,"New image available at" + aNewPhotoPath);
}
}
ScreenGrabRenderer Interface
(Overloads the TangoCameraPreview default Renderer)
/**
* This is an intermediate class that intercepts all calls to the TangoCameraPreview's
* default renderer.
*
* It simply passes all render calls through to the default renderer.
*
* When required, it can also use the renderer methods to dump a copy of the frame to a bitmap
*
* #author henderso
*
*/
public class ScreenGrabRenderer implements GLSurfaceView.Renderer {
TangoCameraScreengrabCallback mTangoCameraScreengrabCallback;
GLSurfaceView.Renderer tangoCameraRenderer;
private static final String TAG = ScreenGrabRenderer.class.getSimpleName();
private String lastFileName = "unset";
boolean grabNextScreen = false;
int grabX = 0;
int grabY = 0;
int grabWidth = 640;
int grabHeight = 320;
public void setTangoCameraScreengrabCallback(TangoCameraScreengrabCallback aTangoCameraScreengrabCallback) {
mTangoCameraScreengrabCallback = aTangoCameraScreengrabCallback;
}
/**
* Cue the renderer to grab the next screen. This is a signal that will
* be detected inside the onDrawFrame() method
*
* #param b
*/
public void grabNextScreen(int x, int y, int w, int h) {
grabNextScreen = true;
grabX=x;
grabY=y;
grabWidth=w;
grabHeight=h;
}
#Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
tangoCameraRenderer.onSurfaceCreated(gl, config);
}
#Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
tangoCameraRenderer.onSurfaceChanged(gl, width, height);
}
#Override
public void onDrawFrame(GL10 gl) {
tangoCameraRenderer.onDrawFrame(gl);
if(grabNextScreen) {
screenGrab(gl);
grabNextScreen=false;
}
}
/**
*
* Creates a bitmap given a certain dimension and an OpenGL context
*
* This code was lifted from here:
*
* http://stackoverflow.com/questions/5514149/capture-screen-of-glsurfaceview-to-bitmap
*/
private Bitmap createBitmapFromGLSurface(int x, int y, int w, int h, GL10 gl)
throws OutOfMemoryError {
int bitmapBuffer[] = new int[w * h];
int bitmapSource[] = new int[w * h];
IntBuffer intBuffer = IntBuffer.wrap(bitmapBuffer);
intBuffer.position(0);
try {
gl.glReadPixels(x, y, w, h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, intBuffer);
int offset1, offset2;
for (int i = 0; i < h; i++) {
offset1 = i * w;
offset2 = (h - i - 1) * w;
for (int j = 0; j < w; j++) {
int texturePixel = bitmapBuffer[offset1 + j];
int blue = (texturePixel >> 16) & 0xff;
int red = (texturePixel << 16) & 0x00ff0000;
int pixel = (texturePixel & 0xff00ff00) | red | blue;
bitmapSource[offset2 + j] = pixel;
}
}
} catch (GLException e) {
Log.e(TAG,e.toString());
return null;
}
return Bitmap.createBitmap(bitmapSource, w, h, Bitmap.Config.ARGB_8888);
}
/**
* Writes a copy of the GLSurface backbuffer to storage
*/
private void screenGrab(GL10 gl) {
long fileprefix = System.currentTimeMillis();
String targetPath =Environment.getExternalStorageDirectory() + "/RavenEye/Photos/";
String imageFileName = fileprefix + ".png";
String fullPath = "error";
Bitmap image = createBitmapFromGLSurface(grabX,grabY,grabWidth,grabHeight,gl);
if(!(new File(targetPath)).exists()) {
new File(targetPath).mkdirs();
}
try {
File targetDirectory = new File(targetPath);
File photo=new File(targetDirectory, imageFileName);
FileOutputStream fos=new FileOutputStream(photo.getPath());
image.compress(CompressFormat.PNG, 100, fos);
fos.flush();
fos.close();
fullPath =targetPath + imageFileName;
Log.i(TAG, "Grabbed an image in target path:" + fullPath);
///Notify the outer class(es)
if(mTangoCameraScreengrabCallback != null) {
mTangoCameraScreengrabCallback.newPhoto(fullPath);
} else {
Log.i(TAG, "Callback not set properly..");
}
} catch (FileNotFoundException e) {
Log.e(TAG,"Exception " + e);
e.printStackTrace();
} catch (IOException e) {
Log.e(TAG,"Exception " + e);
e.printStackTrace();
}
lastFileName = fullPath;
}
/**
* Constructor
* #param baseRenderer
*/
public ScreenGrabRenderer(GLSurfaceView.Renderer baseRenderer) {
tangoCameraRenderer = baseRenderer;
}
}
TangoCameraScreengrabCallback Interface
(not required unless you want to pass info back from the screen grab renderer)
/*
* The TangoCameraScreengrabCallback is a generic interface that provides callback mechanism
* to an implementing activity.
*
*/
interface TangoCameraScreengrabCallback {
public void newPhoto(String aNewPhotoPath);
}

I haven't tried on the latest release, but it was the absence of this functionality that drove me to the C API where I could get image data - a recent post, I think on the G+ page, seemed to indicate that the Unity API now returns image data as well - for a company that wants to keep scolding us when we don't use Java, it certainly is an odd lag :-)

Related

Dark Google Tango camera surface when using depth information

Situation: I'm trying to write a Google Tango application in Java that allows the user to see the tango's camera feed with virtual objects on top (i.e. a video see-through augmented reality view) AND uses Tango depth/point cloud information.
Problem: Whenever I try to enable the depth sensor on the Tango, the camera image get's very dark. When I disable the depth sensing, everything is OK. Here are some screen shots:
Google Tango with Depth information enabled:
mConfig.putBoolean(TangoConfig.KEY_BOOLEAN_DEPTH, true);
Same application with Depth information disabled:
mConfig.putBoolean(TangoConfig.KEY_BOOLEAN_DEPTH, false);
Question: How do I get a clean camera image AND enable the Tango's depth information? If pure color is not possible, can a get high contrast B/W? I suspect this is a synchronization issue, and perhaps the surface is drawn after the depth/point cloud algorithm perturbing the image. Or, the camera format is changed to support the depth sensing and is unsuitable for preview.
I'm using the Tango.setSurface technique suggested in this helpful and related post
I'm purposefully NOT using the Android's native camera APIs.
(EDIT: This post is based on Fermat update. Have not confirmed after Gauss update)
My main activity code is posted below. Full project is at this github repo
Thanks in advance!
/*
* Copyright 2014 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.digitalblacksmith.tango_ar_pointcloud;
import com.google.atap.tangoservice.Tango;
import com.google.atap.tangoservice.Tango.OnTangoUpdateListener;
import com.google.atap.tangoservice.TangoConfig;
import com.google.atap.tangoservice.TangoCoordinateFramePair;
import com.google.atap.tangoservice.TangoErrorException;
import com.google.atap.tangoservice.TangoEvent;
import com.google.atap.tangoservice.TangoInvalidException;
import com.google.atap.tangoservice.TangoOutOfDateException;
import com.google.atap.tangoservice.TangoPoseData;
import com.google.atap.tangoservice.TangoXyzIjData;
import android.app.Activity;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import java.io.FileInputStream;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;
/**
*
* Modified Main Activity class from the Original Google Tango SDK Motion Tracking API Sample.
*
* Creates a GLSurfaceView for the OpenGL scene, which displays a cube
* Then adds a SurfaceView for the camera image. The surface is connected
* to the Tango camera. This is necessary if one wants to get point cloud
* data from the Tango AND use the camera for video-see through Augmented Reality.
*
* Lessons learned: Ensure your onPause and onResume actions are handled correctly
* in terms of disconnecting and reconnecting the Tango!! If the Tango is not
* disconnected and reconnected properly, you will get a black background and
* may think the issue is something else.
*
* #author Steve Henderson #stevehenderson
*
*/
public class PointCloudActivity extends Activity implements View.OnClickListener, SurfaceHolder.Callback {
private static final String TAG = PointCloudActivity.class.getSimpleName();
private static final int SECS_TO_MILLISECS = 1000;
private Tango mTango;
private TangoConfig mConfig;
private TextView mDeltaTextView;
private TextView mPoseCountTextView;
private TextView mPoseTextView;
private TextView mQuatTextView;
private TextView mPoseStatusTextView;
private TextView mTangoServiceVersionTextView;
private TextView mApplicationVersionTextView;
private TextView mTangoEventTextView;
private TextView mPointCountTextView;
private TextView mAverageZTextView;
private TextView mFrequencyTextView;
private float mPreviousTimeStamp;
private int mPreviousPoseStatus;
private int count;
private float mDeltaTime;
private Button mMotionResetButton;
private Button mDropBoxButton;
//private boolean mIsAutoRecovery;
//private PCRenderer mOpenGL2Renderer;
private OpenGL2PointCloudRenderer mOpenGL2Renderer;
private DemoRenderer mDemoRenderer;
private GLSurfaceView mGLView;
private SurfaceView surfaceView;
private float mXyIjPreviousTimeStamp;
private float mCurrentTimeStamp;
boolean first_initialized = false;
Surface tangoSurface;
Vector3f lastPosition;
Vector3f dropBoxPosition;
/**
* Set up the activity using OpenGL 20
*/
#SuppressWarnings("deprecation")
private void setUpOpenGL20() {
///////////////////////
//Create GLSurface
///////////////////////
// OpenGL view where all of the graphics are drawn
mGLView = new GLSurfaceView(this);
mGLView.setEGLContextClientVersion(2);
mGLView.setEGLConfigChooser(8,8,8,8,16,0);
SurfaceHolder glSurfaceHolder = mGLView.getHolder();
glSurfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
////////////////////////////////////
// Instantiate the Tango service
///////////////////////////////////
mTango = new Tango(this);
// Create a new Tango Configuration and enable the MotionTrackingActivity API
mConfig = new TangoConfig();
mConfig = mTango.getConfig(TangoConfig.CONFIG_TYPE_CURRENT);
mConfig.putBoolean(TangoConfig.KEY_BOOLEAN_MOTIONTRACKING, true);
/// --->If the next property is false (disabled depth) then image ok <-------
mConfig.putBoolean(TangoConfig.KEY_BOOLEAN_DEPTH, true);
// Configure OpenGL renderer
//mRenderer = new GLClearRenderer();
int maxDepthPoints = mConfig.getInt("max_point_cloud_elements");
mOpenGL2Renderer = new OpenGL2PointCloudRenderer(maxDepthPoints);
mDemoRenderer = mOpenGL2Renderer;
mOpenGL2Renderer.setFirstPersonView();
mGLView.setRenderer(mOpenGL2Renderer);
mGLView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
//setContentView(mGLView);
try {
setTangoListeners();
} catch (TangoErrorException e) {
Toast.makeText(getApplicationContext(), R.string.TangoError, Toast.LENGTH_SHORT).show();
} catch (SecurityException e) {
Toast.makeText(getApplicationContext(), R.string.motiontrackingpermission,
Toast.LENGTH_SHORT).show();
}
//////////////////////////
// Create Camera Surface
//////////////////////////
surfaceView = new SurfaceView(this);
SurfaceHolder activitySurfaceHolder = surfaceView.getHolder();
activitySurfaceHolder.addCallback(this);
//mGLView.setZOrderOnTop(true);
setContentView(mGLView);
addContentView( surfaceView, new LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT ) );
/////////////////////////
//Create UI Objects
////////////////////////
LayoutInflater inflater = getLayoutInflater();
View tmpView;
tmpView = inflater.inflate(R.layout.activity_motion_tracking, null);
getWindow().addContentView(tmpView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.FILL_PARENT));
mApplicationVersionTextView = (TextView) findViewById(R.id.appversion);
mApplicationVersionTextView.setText("OpenGL 2.0");
// Button to reset motion tracking
mMotionResetButton = (Button) findViewById(R.id.resetmotion);
// Set up button click listeners
mMotionResetButton.setOnClickListener(this);
// Button to drop position box (breadcrumb cube)
mDropBoxButton = (Button) findViewById(R.id.dropbox);
// Set up button click listeners
mDropBoxButton.setOnClickListener(this);
//mOpenGL2Renderer.setFirstPersonView();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
setUpOpenGL20();
// Text views for displaying translation and rotation data
mPoseTextView = (TextView) findViewById(R.id.pose);
mQuatTextView = (TextView) findViewById(R.id.quat);
mPoseCountTextView = (TextView) findViewById(R.id.posecount);
mDeltaTextView = (TextView) findViewById(R.id.deltatime);
mTangoEventTextView = (TextView) findViewById(R.id.tangoevent);
mPointCountTextView = (TextView) findViewById(R.id.pointCount);
mAverageZTextView = (TextView) findViewById(R.id.averageZ);
mFrequencyTextView = (TextView) findViewById(R.id.frameDelta);
// Text views for the status of the pose data and Tango library versions
mPoseStatusTextView = (TextView) findViewById(R.id.status);
mTangoServiceVersionTextView = (TextView) findViewById(R.id.version);
// Display the library version for debug purposes
mTangoServiceVersionTextView.setText(mConfig.getString("tango_service_library_version"));
dropBoxPosition = new Vector3f();
lastPosition = new Vector3f();
}
private void motionReset() {
mTango.resetMotionTracking();
}
private void dropBox() {
dropBoxPosition.setTo(lastPosition);
}
#Override
protected void onPause() {
super.onPause();
Log.i(TAG, "OnPause");
try {
mTango.disconnect();
Log.i(TAG,"Pausing..TANGO disconnected");
} catch (TangoErrorException e) {
Toast.makeText(getApplicationContext(), R.string.TangoError, Toast.LENGTH_SHORT).show();
}
}
protected void onResume() {
super.onResume();
Log.i(TAG, "OnResume");
try {
//setTangoListeners();
} catch (TangoErrorException e) {
Log.e(TAG,e.toString());
} catch (SecurityException e) {
Log.e(TAG,e.toString());
}
try {
if(first_initialized)mTango.connect(mConfig);
} catch (TangoOutOfDateException e) {
Log.e(TAG,e.toString());
} catch (TangoErrorException e) {
Log.e(TAG,e.toString());
}
try {
//setUpExtrinsics();
} catch (TangoErrorException e) {
Log.e(TAG,e.toString());
} catch (SecurityException e) {
Log.e(TAG,e.toString());
}
}
#Override
protected void onDestroy() {
super.onDestroy();
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.resetmotion:
motionReset();
break;
case R.id.dropbox:
dropBox();
break;
default:
Log.w(TAG, "Unknown button click");
return;
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
return false;
}
/**
* Set up the TangoConfig and the listeners for the Tango service, then begin using the Motion
* Tracking API. This is called in response to the user clicking the 'Start' Button.
*/
private void setTangoListeners() {
// Lock configuration and connect to Tango
// Select coordinate frame pair
final ArrayList<TangoCoordinateFramePair> framePairs =
new ArrayList<TangoCoordinateFramePair>();
framePairs.add(new TangoCoordinateFramePair(
TangoPoseData.COORDINATE_FRAME_START_OF_SERVICE,
TangoPoseData.COORDINATE_FRAME_DEVICE));
// Listen for new Tango data
mTango.connectListener(framePairs, new OnTangoUpdateListener() {
#Override
public void onPoseAvailable(final TangoPoseData pose) {
// Log whenever Motion Tracking enters a n invalid state
if (pose.statusCode == TangoPoseData.POSE_INVALID) {
Log.w(TAG, "Invalid State");
}
if (mPreviousPoseStatus != pose.statusCode) {
count = 0;
}
count++;
mPreviousPoseStatus = pose.statusCode;
mDeltaTime = (float) (pose.timestamp - mPreviousTimeStamp) * SECS_TO_MILLISECS;
mPreviousTimeStamp = (float) pose.timestamp;
// Update the OpenGL renderable objects with the new Tango Pose
// data
float[] translation = pose.getTranslationAsFloats();
mGLView.requestRender();
// Update the UI with TangoPose information
runOnUiThread(new Runnable() {
#Override
public void run() {
DecimalFormat threeDec = new DecimalFormat("0.000");
String translationString = "[" + threeDec.format(pose.translation[0])
+ ", " + threeDec.format(pose.translation[1]) + ", "
+ threeDec.format(pose.translation[2]) + "] ";
String quaternionString = "[" + threeDec.format(pose.rotation[0]) + ", "
+ threeDec.format(pose.rotation[1]) + ", "
+ threeDec.format(pose.rotation[2]) + ", "
+ threeDec.format(pose.rotation[3]) + "] ";
float x = (float) pose.translation[0];
float y = (float) pose.translation[1];
float z = (float) pose.translation[2];
mDemoRenderer.setCameraPosition(x-dropBoxPosition.x, y-dropBoxPosition.y, z-dropBoxPosition.z);
lastPosition.setTo(x, y, z);
float qx = (float) pose.rotation[0];
float qy = (float) pose.rotation[1];
float qz = (float) pose.rotation[2];
float qw = (float) pose.rotation[3];
mDemoRenderer.setCameraAngles(qx, qy, qz, qw);
// Display pose data on screen in TextViews
//Log.i(TAG,translationString);
mPoseTextView.setText(translationString);
mQuatTextView.setText(quaternionString);
mPoseCountTextView.setText(Integer.toString(count));
mDeltaTextView.setText(threeDec.format(mDeltaTime));
if (pose.statusCode == TangoPoseData.POSE_VALID) {
mPoseStatusTextView.setText(R.string.pose_valid);
} else if (pose.statusCode == TangoPoseData.POSE_INVALID) {
mPoseStatusTextView.setText(R.string.pose_invalid);
} else if (pose.statusCode == TangoPoseData.POSE_INITIALIZING) {
mPoseStatusTextView.setText(R.string.pose_initializing);
} else if (pose.statusCode == TangoPoseData.POSE_UNKNOWN) {
mPoseStatusTextView.setText(R.string.pose_unknown);
}
}
});
}
#Override
public void onXyzIjAvailable(final TangoXyzIjData xyzIj) {
//Log.i(TAG,"xyzijAvailable!!!!!!!!");
mCurrentTimeStamp = (float) xyzIj.timestamp;
final float frameDelta = (mCurrentTimeStamp - mXyIjPreviousTimeStamp)
* SECS_TO_MILLISECS;
mXyIjPreviousTimeStamp = mCurrentTimeStamp;
byte[] buffer = new byte[xyzIj.xyzCount * 3 * 4];
//////mGLView.requestRender();
FileInputStream fileStream = new FileInputStream(
xyzIj.xyzParcelFileDescriptor.getFileDescriptor());
try {
fileStream.read(buffer,
xyzIj.xyzParcelFileDescriptorOffset, buffer.length);
fileStream.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
TangoPoseData pointCloudPose = mTango.getPoseAtTime(
mCurrentTimeStamp, framePairs.get(0));
mOpenGL2Renderer.getPointCloud().UpdatePoints(buffer,
xyzIj.xyzCount);
mOpenGL2Renderer.getModelMatCalculator()
.updatePointCloudModelMatrix(
pointCloudPose.getTranslationAsFloats(),
pointCloudPose.getRotationAsFloats());
mOpenGL2Renderer.getPointCloud().setModelMatrix(
mOpenGL2Renderer.getModelMatCalculator()
.getPointCloudModelMatrixCopy());
} catch (TangoErrorException e) {
Toast.makeText(getApplicationContext(),
R.string.TangoError, Toast.LENGTH_SHORT).show();
} catch (TangoInvalidException e) {
Toast.makeText(getApplicationContext(),
R.string.TangoError, Toast.LENGTH_SHORT).show();
}
// Must run UI changes on the UI thread. Running in the Tango
// service thread
// will result in an error.
runOnUiThread(new Runnable() {
DecimalFormat threeDec = new DecimalFormat("0.000");
#Override
public void run() {
// Display number of points in the point cloud
mPointCountTextView.setText(Integer
.toString(xyzIj.xyzCount));
mFrequencyTextView.setText(""
+ threeDec.format(frameDelta));
mAverageZTextView.setText(""
+ threeDec.format(mOpenGL2Renderer.getPointCloud()
.getAverageZ()));
}
});
}
#Override
public void onTangoEvent(final TangoEvent event) {
runOnUiThread(new Runnable() {
#Override
public void run() {
mTangoEventTextView.setText(event.eventKey + ": " + event.eventValue);
}
});
}
});
}
private void setUpExtrinsics() {
// Get device to imu matrix.
TangoPoseData device2IMUPose = new TangoPoseData();
TangoCoordinateFramePair framePair = new TangoCoordinateFramePair();
framePair.baseFrame = TangoPoseData.COORDINATE_FRAME_IMU;
framePair.targetFrame = TangoPoseData.COORDINATE_FRAME_DEVICE;
device2IMUPose = mTango.getPoseAtTime(0.0, framePair);
// mRenderer.getModelMatCalculator().SetDevice2IMUMatrix(
// device2IMUPose.getTranslationAsFloats(), device2IMUPose.getRotationAsFloats());
// Get color camera to imu matrix.
TangoPoseData color2IMUPose = new TangoPoseData();
framePair.baseFrame = TangoPoseData.COORDINATE_FRAME_IMU;
framePair.targetFrame = TangoPoseData.COORDINATE_FRAME_CAMERA_COLOR;
color2IMUPose = mTango.getPoseAtTime(0.0, framePair);
// mRenderer.getModelMatCalculator().SetColorCamera2IMUMatrix(
// color2IMUPose.getTranslationAsFloats(), color2IMUPose.getRotationAsFloats());
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
Surface surface = holder.getSurface();
if (surface.isValid()) {
mTango.connectSurface(0, surface);
first_initialized=true;
mTango.connect(mConfig);
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
mTango.disconnectSurface(0);
}
}
I discovered a solution of sorts, with the help of my friends at CGUI.
In my post above, you will note that the depth camera enabled image has an underexposure error. It seems that the Tango is doing some auto-exposure on the camera image.
When I tried it during the day time, with good natural light and some added flood lights, I received better results:
Depth enabled:
Depth disabled:
So, one possible workaround/consideration when using color and depth is to carefully manage the light in the environment..This makes sense given some of the calibration routines I've seen in the Tango demo apps that call for "daylight"
UPDATE You can also select the B/W fisheye camera which might be better for low-light situations if you don't mind the lack of color and distortion:
mTango.connectSurface(2, surface); //0-->color cam; 2--> B/W fisheye

Extract Images from PDF coordinates using iText

I found some examples for how to extract images from PDF using iText. But what I am looking for is to get the images from PDF by coordinates.
Is it possible? If yes then how it can be done.
Along the lines of the iText example ExtractImages you can extract code like this:
PdfReader reader = new PdfReader(resourceStream);
PdfReaderContentParser parser = new PdfReaderContentParser(reader);
ImageRenderListener listener = new ImageRenderListener("testpdf");
for (int i = 1; i <= reader.getNumberOfPages(); i++) {
parser.processContent(i, listener);
}
The ImageRenderListener is defined like this:
class ImageRenderListener implements RenderListener
{
final String name;
int counter = 100000;
public ImageRenderListener(String name)
{
this.name = name;
}
public void beginTextBlock() { }
public void renderText(TextRenderInfo renderInfo) { }
public void endTextBlock() { }
public void renderImage(ImageRenderInfo renderInfo)
{
try
{
PdfImageObject image = renderInfo.getImage();
if (image == null) return;
int number = renderInfo.getRef() != null ? renderInfo.getRef().getNumber() : counter++;
String filename = String.format("%s-%s.%s", name, number, image.getFileType());
FileOutputStream os = new FileOutputStream(filename);
os.write(image.getImageAsBytes());
os.flush();
os.close();
PdfDictionary imageDictionary = image.getDictionary();
PRStream maskStream = (PRStream) imageDictionary.getAsStream(PdfName.SMASK);
if (maskStream != null)
{
PdfImageObject maskImage = new PdfImageObject(maskStream);
filename = String.format("%s-%s-mask.%s", name, number, maskImage.getFileType());
os = new FileOutputStream(filename);
os.write(maskImage.getImageAsBytes());
os.flush();
os.close();
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
As you see the ImageRenderListener method renderImage retrieves an argument ImageRenderInfo. This arguments has methods
getStartPoint giving you a vector in User space representing the start point of the xobject and
getImageCTM giving you the coordinate transformation matrix active when this image was rendered. Coordinates are in User space.
The latter gives you the information which exact manipulation on a 1x1 user space unit square are used to actually draw the image. As you are aware, an image may be rotated, stretched, skewed, and moved (the former method actually extracts its result from the matrix from the "moved" information).

Failed to allocate timer 0: no slots left and unable to set dynamic row height

I have a screen which call a listfield.
public class Main_AllLatestNews extends MainScreen {
private Database_Webservice webservice;
private String[] title, category, date, imagepath = {"no picture", "no picture", "no picture", "no picture","no picture","no picture","no picture","no picture","no picture", "no picture"};
private int[] newsid;
private List_News newslist;
public Main_AllLatestNews(final boolean needdownload) {
super(USE_ALL_WIDTH);
webservice = new Database_Webservice();
add(new Custom_TopField(this, 0, -1, "", 1, 1));
add(new Custom_BottomField(this, 0));
add(new Custom_HeaderField(Config_GlobalFunction.latest));
if (needdownload){
Main.getUiApplication().pushScreen(
new Custom_LoadingScreen(30));
webservice.UpdateAllCatNews();
}else {
webservice.LoadtodayNews();
newsid = new int[webservice.news.size()];
title = new String[webservice.news.size()];
category = new String[webservice.news.size()];
date = new String[webservice.news.size()];
//imagepath = new String[webservice.news.size()];
for (int i = 0; i < webservice.news.size(); i++) {
newslist = (List_News) webservice.news.elementAt(i);
newsid[i] = newslist.getID();
title[i] = newslist.getNtitle();
category[i] = newslist.getNewCatName();
date[i] = newslist.getNArticalD();
//imagepath[i] = newslist.getImagePath();
}
add(new Custom_ListField(newsid, title, date, category, imagepath, true));
}
}
}
When I add custom_listfield then I get:
Failed to allocate timer 0: no slots left
Here is my listfield
public Custom_ListField(int newsid[], String title[], String date[],
String category[], String imagepath[], boolean islatest) {
super(0, ListField.MULTI_SELECT);
this.newsid = newsid;
setCallback(this);
setBackground(Config_GlobalFunction.loadbackground("background.png"));
this.islatest = islatest;
rows = new Vector();
for (int x = 0; x < title.length; x++) {
TableRowManager row = new TableRowManager();
titlelabel = new Custom_LabelField(title[x],
LabelField.USE_ALL_WIDTH | DrawStyle.LEFT);
titlelabel.setFont(Font.getDefault().derive(Font.BOLD, 23));
row.add(titlelabel);
datelabel = new Custom_LabelField(date[x], DrawStyle.ELLIPSIS
| LabelField.USE_ALL_WIDTH | DrawStyle.LEFT);
datelabel.setFont(Font.getDefault().derive(Font.BOLD, 18));
datelabel.setFontColor(Color.GRAY);
row.add(datelabel);
categorylabel = new Custom_LabelField(category[x],
DrawStyle.ELLIPSIS | LabelField.USE_ALL_WIDTH
| DrawStyle.LEFT);
categorylabel.setFont(Font.getDefault().derive(Font.BOLD, 18));
categorylabel.setFontColor(Color.RED);
row.add(categorylabel);
/*Bitmap imagebitmap = null;
if (!imagepath[x].toString().equals("no picture")) {
imagebitmap = Util_ImageLoader.loadImage(imagepath[x]);
} else {
imagepath[x] = "image_base.png";
imagebitmap = Bitmap.getBitmapResource(imagepath[x]);
}
image = new BitmapField(imagebitmap, Field.FIELD_HCENTER
| Field.FIELD_VCENTER);
row.add(image);*/
//setRowHeight(image.getBitmapHeight() + 10);
setRowHeight(70);
rows.addElement(row);
}
setSize(rows.size());
}
In this list, it will call 10 images or more. First I will check got link send to it else load local images. So the row height must be not same, however, it does not auto set row height for each row but set a same height to all row. I think out of memory because i call too many images? but I call in android also no problem.
This is my imageloader.
public class Util_ImageLoader {
public static Bitmap loadImage(String url) {
HttpConnection connection = null;
InputStream inputStream = null;
EncodedImage bitmap;
byte[] dataArray = null;
try {
// can use this for BlackBerry 5.0+ :
// connection = (HttpConnection) (new
// ConnectionFactory()).getConnection(url).getConnection();
connection = (HttpConnection) Connector
.open(url + Util_GetInternet.getConnParam(),
Connector.READ, true);
int responseCode = connection.getResponseCode();
if (responseCode == HttpConnection.HTTP_OK) {
inputStream = connection.openDataInputStream();
dataArray = IOUtilities.streamToBytes(inputStream);
}
} catch (Exception ex) {
} finally {
try {
inputStream.close();
connection.close();
} catch (Exception e) {
}
}
if (dataArray != null) {
bitmap = EncodedImage.createEncodedImage(dataArray, 0,
dataArray.length);
return bitmap.getBitmap();
} else {
return null;
}
}
}
1) What can I do to reduce the use of memory?
2) How to set different row height? I am set bitmap.getbitmapheight() but different bitmap will have different height.
//Updated//
I am running on simulator 9930 OS 7.0 and 8520 OS 5.0. Both also same result. Real Device cannot run because after signing the key also prompt the warning message try to Secure APi. I am completely commented all the images also same. I did not call neither online nor local image. I think is the data problem?
#AlanLai, can you tell us which device this is being run on, and which OS? Is it a simulator, or real hardware? Why don't you try commenting out the image completely. Don't show any images (network images, or local images). See if you still get the problem. Let's try to narrow down where exactly the code is that's causing your problem. Note: please post the information about which device you're testing on above, in the question, not as a comment response here. Thanks
How about to have only one TableRowManager and every drawRow set values with layout with specific values?
There's a lot of things you can do to reduce memory usage. For one, try to avoid keeping objects in memory longer than you really need them. One way this happens is if you keep member variables in your class, that could really be local variables in a method. Keeping member variables may lead to objects living longer than they need to, preventing the release of the memory they occupy.
Util_ImageLoader
For example, in Util_ImageLoader, you do almost all the work in the constructor. But then, you keep the result around (the Bitmap) in a static member variable (_bmap), which keeps it in memory. I know you do this so that you can call getBitmap(). But, you could change the class to be like this:
public class Util_ImageLoader {
public static Bitmap loadImage(String url) {
HttpConnection connection = null;
InputStream inputStream = null;
EncodedImage bitmap;
byte[] dataArray = null;
try {
// can use this for BlackBerry 5.0+ :
// connection = (HttpConnection) (new ConnectionFactory()).getConnection(url).getConnection();
connection = (HttpConnection) Connector.open(url + Util_GetInternet.getConnParam(), Connector.READ,
true);
int responseCode = connection.getResponseCode();
if (responseCode == HttpConnection.HTTP_OK) {
inputStream = connection.openDataInputStream();
dataArray = IOUtilities.streamToBytes(inputStream);
}
} catch (Exception ex) {
}
finally {
try {
inputStream.close();
connection.close();
} catch (Exception e) {
}
}
if (dataArray != null) {
bitmap = EncodedImage.createEncodedImage(dataArray, 0, dataArray.length);
return bitmap.getBitmap();
} else {
return null;
}
}
}
Because your Util_ImageLoader class doesn't really have any state associated with it, you can probably make it a class with just one static method. The static method does not require you to create an instance of Util_ImageLoader to use it. Just do this:
Bitmap img = Util_ImageLoader.loadImage("http://domain.com/path/image.png");
This allows the image that's loaded to be released as soon as the UI is done with it. The existing code keeps that image in memory for the life of the program.
Also, I replaced your custom code that uses a byte[] buffer, with the useful IOUtilities.streamtoBytes() method. Let the built-in libraries do the work of optimizing for you. Most of the time, they will do a pretty good job of that.
You also had some fixed point scaling code in your Util_ImageLoader class that wasn't doing anything. It was creating a scaled image of the same size as the original. So, I just removed that code. That can only help your memory usage. Image manipulation can be expensive.
Finally, I checked the web server return code (HTTP_OK) before I created any of the large objects needed for this method. If the network request fails, you certainly don't want to waste memory for no reason.
Custom_ListField
Again, you are keeping some objects around, possibly longer than needed. Let's go through your member variables:
private Bitmap bg = Bitmap.getBitmapResource("background.png"),
imagebitmap;
I don't know how many instances of Custom_ListField you will have in your app, but if you are going to assign bg to a constant app resource image, you should at least make it a static member variable, so that if there are 10 instances of Custom_ListField, you will only be keeping one bg variable in memory:
private static Bitmap bg = Bitmap.getBitmapResource("background.png"),
imagebitmap;
But, in your case, I don't think you need to keep that member variable at all. You can simply replace it where it's used, like this:
Background background = BackgroundFactory.createBitmapBackground(Bitmap.getBitmapResource("background.png"));
Then, the imagebitmap member can also be replaced with a local variable:
Bitmap imageBitmap = null;
if (!imagepath[x].toString().equals("no picture")) {
imageBitmap = Util_ImageLoader.loadImage(imagepath[x]);
imageBitmap = loader.getbitmap();
} else {
imagepath[x] = "image_base.png";
imageBitmap = Bitmap.getBitmapResource(imagepath[x]);
}
image = new BitmapField(imageBitmap, Field.FIELD_HCENTER | Field.FIELD_VCENTER);
imageBitmap only needs to be a local variable, not a member variable.
Debugging memory usage usually requires having the whole program, running, and profiling it. With only some of your code, I can't see all the other code that uses it. How many of each class is created is important? Which images are the large ones, and which are small? These are all questions you need to ask yourself to get your memory usage down.
But, hopefully, the general techniques I showed example of above can help you get started.
The problem was the Custom_ListField. This should extends listfield
instead of custom extends manager
public class Custom_ListField extends ListField {
private String[] title, category, date, imagepath;
private int[] newsid, catsid;
private List_News newslist;
private Bitmap imagebitmap[], localimage = Bitmap
.getBitmapResource("image_base.png");
private BrowserField webpage;
private Custom_BrowserFieldListener listener;
private boolean islatest;
private Vector content = null;
private ListCallback callback = null;
private int currentPosition = 0;
public Custom_ListField(Vector content, boolean islatest) {
this.content = content;
this.islatest = islatest;
newsid = new int[content.size()];
title = new String[content.size()];
category = new String[content.size()];
date = new String[content.size()];
imagepath = new String[content.size()];
catsid = new int[content.size()];
imagebitmap = new Bitmap[content.size()];
for (int i = 0; i < content.size(); i++) {
newslist = (List_News) content.elementAt(i);
newsid[i] = newslist.getID();
title[i] = newslist.getNtitle();
category[i] = newslist.getNewCatName();
date[i] = newslist.getNArticalD();
imagepath[i] = newslist.getImagePath();
if (!imagepath[i].toString().equals("no picture")) {
imagebitmap[i] = Util_ImageLoader.loadImage(imagepath[i]);
} else {
imagebitmap[i] = localimage;
}
catsid[i] = newslist.getCatID();
}
initCallbackListening();
this.setRowHeight(localimage.getHeight() + 10);
}
private void initCallbackListening() {
callback = new ListCallback();
this.setCallback(callback);
}
private class ListCallback implements ListFieldCallback {
public ListCallback() {
setBackground(Config_GlobalFunction
.loadbackground("background.png"));
}
public void drawListRow(ListField listField, Graphics graphics,
int index, int y, int width) {
currentPosition = index;
graphics.drawBitmap(
Display.getWidth() - imagebitmap[index].getWidth() - 5,
y + 3, imagebitmap[index].getWidth(),
imagebitmap[index].getHeight(), imagebitmap[index], 0, 0);
graphics.setColor(Color.WHITE);
graphics.drawRect(0, y, width, imagebitmap[index].getHeight() + 10);
graphics.setColor(Color.BLACK);
graphics.setFont(Font.getDefault().derive(Font.BOLD, 20));
graphics.drawText(title[index], 5, y + 3, 0, Display.getWidth()
- imagebitmap[index].getWidth() - 10);
System.out.println(Display.getWidth()
- imagebitmap[index].getWidth() - 10);
graphics.setColor(Color.GRAY);
graphics.setFont(Font.getDefault().derive(Font.BOLD, 15));
graphics.drawText(date[index], 5, y + 6
+ Font.getDefault().getHeight() + 3);
if (islatest) {
graphics.setColor(Color.RED);
graphics.setFont(Font.getDefault().derive(Font.BOLD, 15));
graphics.drawText(category[index], Font.getDefault()
.getAdvance(date[index]) + 3, y + 6
+ Font.getDefault().getHeight() + 3);
}
}
public Object get(ListField listField, int index) {
return content.elementAt(index);
}
public int getPreferredWidth(ListField listField) {
return Display.getWidth();
}
public int indexOfList(ListField listField, String prefix, int start) {
return content.indexOf(prefix, start);
}
}
public int getCurrentPosition() {
return currentPosition;
}
protected boolean navigationClick(int status, int time) {
int index = getCurrentPosition();
if (catsid[index] == 9) {
if (Config_GlobalFunction.isConnected()) {
webpage = new BrowserField();
listener = new Custom_BrowserFieldListener();
webpage.addListener(listener);
MainScreen aboutus = new Menu_Aboutus();
aboutus.add(webpage);
Main.getUiApplication().pushScreen(aboutus);
webpage.requestContent("http://www.orientaldaily.com.my/index.php?option=com_k2&view=item&id="
+ newsid[index] + ":&Itemid=223");
} else
Config_GlobalFunction.Message(Config_GlobalFunction.nowifi, 1);
} else
Main.getUiApplication().pushScreen(
new Main_NewsDetail(newsid[index]));
return true;
}
}

How to Load images from SD CARD and Run Animation using AnimationDrawable or AnimationUtils in Android

I am having Images stored in SD Card and using that images i wish to run an animation. I am using the following code for this but my animation is not working at all.
Code Snippet
playAnimation("xxx", medid, 25);//calling method
break;
public void playAnimation(String string, int medid2, int length) {
// TODO Auto-generated method stub
animation = new AnimationDrawable();
Bitmap bitMap;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2; //reduce quality
player = MediaPlayer.create(this.getApplicationContext(), medid2);
try {
for (int i = 0; i <= length; i++) {
System.out.println("File Name : - " + Environment.getExternalStorageDirectory().toString() + "/" + string + i);
bitMap = BitmapFactory.decodeFile(Environment.getExternalStorageDirectory().toString() + "/" + string + i);
Drawable bmp = new BitmapDrawable(bitMap);
animation.addFrame(bmp, DURATION);
}
animation.setOneShot(true);
animation.setVisible(true, true);
int frames = animation.getNumberOfFrames();
System.out.println("Number of Frames are - " + frames);
img.setBackgroundDrawable(animation);
img.post(new Starter());
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
class Starter implements Runnable {
public void run() {
try {
if(animation.isRunning()) {
animation.stop();
animation.start();
if (player.isPlaying()) {
player.stop();
player.start();
}
else {
player.start();
}
} else {
animation.start();
if (player.isPlaying()) {
player.stop();
player.start();
}
else {
player.start();
}
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
Using concept of Frame Animation i need to run my animation. I am able fetch images as i have done some debugging but when i click on button and this methods are called my screen is not displaying any animation. It just display black screen only. I am not getting any error in this. If anyone having idea please kindly let me know.
Thanks
An AnimationDrawable just shows black screen, may be caused by different reasons. For example, in the Android Dev Guide, Drawable Animation, the following code lets you load a series of Drawable resources.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ImageView rocketImage = (ImageView) findViewById(R.id.rocket_image);
rocketImage.setBackgroundResource(R.drawable.rocket_thrust);
rocketAnimation = (AnimationDrawable) rocketImage.getBackground();
}
However, if you set resource after getBackground() like the following code, the screen will keep black.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ImageView rocketImage = (ImageView) findViewById(R.id.rocket_image);
rocketAnimation = (AnimationDrawable) rocketImage.getBackground();
rocketImage.setBackgroundResource(R.drawable.rocket_thrust);
}
If you want to load images from SD card, and show them as animation, you can refer to the following code. I write and test on API 8 (2.3).
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
showedImage = (ImageView) findViewById(R.id.imageView_showedPic);
showedImage.setBackgroundResource(R.drawable.slides);
frameAnimation = (AnimationDrawable) showedImage.getBackground();
addPicturesOnExternalStorageIfExist();
}
#Override
public void onWindowFocusChanged (boolean hasFocus){
super.onWindowFocusChanged (hasFocus);
frameAnimation.start();
}
private void addPicturesOnExternalStorageIfExist() {
// check if external storage
String state = Environment.getExternalStorageState();
if ( !(Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) ) {
return;
}
// check if a directory named as this application
File rootPath = Environment.getExternalStorageDirectory();
// 'happyShow' is the name of directory
File pictureDirectory = new File(rootPath, "happyShow");
if ( !pictureDirectory.exists() ) {
Log.d("Activity", "NoFoundExternalDirectory");
return;
}
// check if there is any picture
//create a FilenameFilter and override its accept-method
FilenameFilter filefilter = new FilenameFilter() {
public boolean accept(File dir, String name) {
return (name.endsWith(".jpeg") ||
name.endsWith(".jpg") ||
name.endsWith(".png") );
}
};
String[] sNamelist = pictureDirectory.list(filefilter);
if (sNamelist.length == 0) {
Log.d("Activity", "No pictures in directory.");
return;
}
for (String filename : sNamelist) {
Log.d("Activity", pictureDirectory.getPath() + '/' + filename);
frameAnimation.addFrame(
Drawable.createFromPath(pictureDirectory.getPath() + '/' + filename),
DURATION);
}
return;
}

Blackberry - Loading/Wait screen with animation

Is there a way to show "Loading" screen with animation in blackberry?
Options:
PME animation content
multithreading + set of images + timer/counter
standard rim api
some other way
Any of this?
Thanks!
Fermin, Anthony +1. Thanks to all, you gave me the part of answer.
My final solution:
1.Create or generate (free Ajax loading gif generator) animation and add it to project.
2.Create ResponseCallback interface (see Coderholic - Blackberry WebBitmapField) to receive thread execution result:
public interface ResponseCallback {
public void callback(String data);
}
3.Create a class to handle your background thread job. In my case it was http request:
public class HttpConnector
{
static public void HttpGetStream(final String fileToGet,
final ResponseCallback msgs) {
Thread t = new Thread(new Runnable() {
public void run() {
HttpConnection hc = null;
DataInputStream din = null;
try {
hc = (HttpConnection) Connector.open("http://" + fileToGet);
hc.setRequestMethod(HttpsConnection.GET);
din = hc.openDataInputStream();
ByteVector bv = new ByteVector();
int i = din.read();
while (-1 != i) {
bv.addElement((byte) i);
i = din.read();
}
final String response = new String(bv.toArray(), "UTF-8");
UiApplication.getUiApplication().invokeLater(
new Runnable() {
public void run() {
msgs.callback(response);
}
});
}
catch (final Exception e) {
UiApplication.getUiApplication().invokeLater(
new Runnable() {
public void run() {
msgs.callback("Exception (" + e.getClass() + "): "
+ e.getMessage());
}
});
}
finally {
try {
din.close();
din = null;
hc.close();
hc = null;
}
catch (Exception e) {
}
}
}
});
t.start();
}
}
4.Create WaitScreen (a hybrid of FullScreen and AnimatedGIFField with ResponseCallback interface):
public class WaitScreen extends FullScreen implements ResponseCallback
{
StartScreen startScreen;
private GIFEncodedImage _image;
private int _currentFrame;
private int _width, _height, _xPos, _yPos;
private AnimatorThread _animatorThread;
public WaitScreen(StartScreen startScreen) {
super(new VerticalFieldManager(), Field.NON_FOCUSABLE);
setBackground(
BackgroundFactory.createSolidTransparentBackground(
Color.WHITE, 100));
this.startScreen = startScreen;
EncodedImage encImg =
GIFEncodedImage.getEncodedImageResource("ajax-loader.gif");
GIFEncodedImage img = (GIFEncodedImage) encImg;
// Store the image and it's dimensions.
_image = img;
_width = img.getWidth();
_height = img.getHeight();
_xPos = (Display.getWidth() - _width) >> 1;
_yPos = (Display.getHeight() - _height) >> 1;
// Start the animation thread.
_animatorThread = new AnimatorThread(this);
_animatorThread.start();
UiApplication.getUiApplication().pushScreen(this);
}
protected void paint(Graphics graphics) {
super.paint(graphics);
// Draw the animation frame.
graphics
.drawImage(_xPos, _yPos, _image
.getFrameWidth(_currentFrame), _image
.getFrameHeight(_currentFrame), _image,
_currentFrame, 0, 0);
}
protected void onUndisplay() {
_animatorThread.stop();
}
private class AnimatorThread extends Thread {
private WaitScreen _theField;
private boolean _keepGoing = true;
private int _totalFrames, _loopCount, _totalLoops;
public AnimatorThread(WaitScreen _theScreen) {
_theField = _theScreen;
_totalFrames = _image.getFrameCount();
_totalLoops = _image.getIterations();
}
public synchronized void stop() {
_keepGoing = false;
}
public void run() {
while (_keepGoing) {
// Invalidate the field so that it is redrawn.
UiApplication.getUiApplication().invokeAndWait(
new Runnable() {
public void run() {
_theField.invalidate();
}
});
try {
// Sleep for the current frame delay before
// the next frame is drawn.
sleep(_image.getFrameDelay(_currentFrame) * 10);
} catch (InterruptedException iex) {
} // Couldn't sleep.
// Increment the frame.
++_currentFrame;
if (_currentFrame == _totalFrames) {
// Reset back to frame 0
// if we have reached the end.
_currentFrame = 0;
++_loopCount;
// Check if the animation should continue.
if (_loopCount == _totalLoops) {
_keepGoing = false;
}
}
}
}
}
public void callback(String data) {
startScreen.updateScreen(data);
UiApplication.getUiApplication().popScreen(this);
}
}
5.In the end, create Start screen to call HttpConnector.HttpGetStream and to show WaitScreen:
public class StartScreen extends MainScreen
{
public RichTextField text;
WaitScreen msgs;
public StartScreen() {
text = new RichTextField();
this.add(text);
}
protected void makeMenu(Menu menu, int instance) {
menu.add(runWait);
super.makeMenu(menu, instance);
}
MenuItem runWait = new MenuItem("wait", 1, 1) {
public void run() {
UiApplication.getUiApplication().invokeLater(
new Runnable() {
public void run() {
getFile();
}
});
}
};
public void getFile() {
msgs = new WaitScreen(this);
HttpConnector.HttpGetStream(
"stackoverflow.com/faq", msgs);
}
//you should implement this method to use callback data on the screen.
public void updateScreen(String data)
{
text.setText(data);
}
}
UPDATE: another solution naviina.eu: A Web2.0/Ajax-style loading popup in a native BlackBerry application
The basic pattern for this kind of thing is:
Have a thread running a loop that updates a variable (such as the frame index of the animated image) and then calls invalidate on a Field which draws the image (and then sleeps for a period of time). The invalidate will queue a repaint of the field.
In the field's paint method, read the variable and draw the appropriate frame of the image.
Pseudo code (not totally complete, but to give you the idea):
public class AnimatedImageField extends Field implements Runnable {
private int currentFrame;
private Bitmap[] animationFrames;
public void run() {
while(true) {
currentFrame = (currentFrame + 1) % animationFrames.length;
invalidate();
Thread.sleep(100);
}
}
protected void paint(Graphics g) {
g.drawBitmap(0, 0, imageWidth, imageHeight, animationFrames[currentFrame], 0, 0);
}
}
Note also here I used an array of Bitmaps, but EncodedImage lets you treat an animated gif as one object, and includes methods to get specific frames.
EDIT: For completeness: Add this to a PopupScreen (as in Fermin's answer) or create your own dialog by overriding Screen directly. The separate thread is necessary because the RIM API is not thread-safe: you need to do everything UI related on the event thread (or while holding the event lock, see BlackBerry UI Threading - The Very Basics
This is simple code for loading screen ....
HorizontalFieldManager popHF = new HorizontalFieldManager();
popHF.add(new CustomLabelField("Pls wait..."));
final PopupScreen waitScreen = new PopupScreen(popHF);
new Thread()
{
public void run()
{
synchronized (UiApplication.getEventLock())
{
UiApplication.getUiApplication().pushScreen(waitScreen);
}
//Here Some Network Call
synchronized (UiApplication.getEventLock())
{
UiApplication.getUiApplication().popScreen(waitScreen);
}
}
}.start();
If it's just an animation could you show an animated gif on a popup and close it when loading operation is complete?
Easiest way is probably to use the standard GaugeField, setting style GaugeField.PERCENT. This will give you a progress bar. Add this to a PopupScreen and it will sit on top of your content. Something like..
private GaugeField _gaugeField;
private PopupScreen _popup;
public ProgressBar() {
DialogFieldManager manager = new DialogFieldManager();
_popup = new PopupScreen(manager);
_gaugeField = new GaugeField(null, 0, 100, 0, GaugeField.PERCENT);
manager.addCustomField(_gaugeField);
}
Then have an update method which will use _gaugeField.setValue(newValue); to update the progress bar.
I normally have this called from whichever thread is doing the work (loading in your case, everytime an operation is complete the progress bar is updated.
I would suggest to take a look at this simple implementation. I liked this but never used it. May be helpful to you.
link text
ActivityIndicator is a good option if you are working with at least BB OS 6.0.
http://www.brighthub.com/mobile/blackberry-platform/articles/94258.aspx
http://docs.blackberry.com/en/developers/deliverables/17966/Screen_APIs_1245069_11.jsp

Resources