Passing Bitmap variable to method in extended class - java

My code implements TFlite and I have two classes:
1) Deals with camera activity and processing of image
2) Deals with specifics of the model and detector.
I have a method in the extended class which runs the model.
I am trying to call that method from within the main class. I am new to Java so I don't quite know why I keep getting java.lang.NullPointerException error.
See code below (I will leave lots of white-space and comment around the pieces that are relevant):
Method which makes the call:
// Class was called
Classifier classifier;
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// Called when image was captured from camera
// ...
// Obtains bitmap image from camera and processes it to a new Bitmap variable: rotatedBitmap
// ...
/* Here is where the issue begins.
I can obtain the processed image and set it to my ImageView no problem, so the variable rotatedBitmap
is NOT null. But when I try to pass it to classifier.recognizeImage() it throws the null pointer error
and crashes the app
*/
if (resultCode == RESULT_OK) {
// Set the image captured to our ImageView
mImageView.setImageBitmap(rotatedBitmap);
if (rotatedBitmap != null) {
float[][] result = classifier.recognizeImage(rotatedBitmap); // Says that rotatedBitmap is null
// Display results
String message = Arrays.toString(result[0]);
Snackbar mySnackbar = Snackbar.make(findViewById(R.id.myCoordinatorLayout), message,
Snackbar.LENGTH_SHORT);
mySnackbar.show();
}
}
}
Full Code:
public class CameraActivity extends AppCompatActivity {
private static final int PERMISSION_CODE = 1000;
private static final int IMAGE_CAPTURE_CODE = 1001;
Classifier classifier;
Button mCaptureBtn;
ImageView mImageView;
Uri image_uri;
public Bitmap rotatedBitmap;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImageView = findViewById(R.id.image_view);
mCaptureBtn = findViewById(R.id.capture_image_btn);
mCaptureBtn.setOnClickListener(new View.OnClickListener() {
// 1: Create the button
// 2: Create an instance of OnClickListener to wait for the click
// 3: Override the onClick method
#Override
public void onClick(View v) {
// If the operating system is newer or equal to Marshmello
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Check for permissions
if (checkSelfPermission(Manifest.permission.CAMERA) ==
PackageManager.PERMISSION_DENIED ||
checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) ==
PackageManager.PERMISSION_DENIED) {
// Permission not enables so request it
String[] permission = {Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE};
requestPermissions(permission, PERMISSION_CODE);
} else {
// Permission already granted
openCamera();
}
} else {
openCamera();
}
}
});
}
private void openCamera() {
// ContentValues creates a set-type object that can store values that ContentResolver can access
ContentValues values = new ContentValues();
// Store values
values.put(MediaStore.Images.Media.TITLE, "New Picture");
values.put(MediaStore.Images.Media.DESCRIPTION, "From the camera");
// Obtain the uri(uniform resource identifier) using the ContentValues previously made
image_uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
// Camera intent
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// The EXTRA_OUTPUT constraint outputs the full-sized image data to the uri
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, image_uri);
if (cameraIntent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(cameraIntent, IMAGE_CAPTURE_CODE);
}
}
// Handling permission request
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
// THis method is called whenever the user presses allow or deny from the Perm Req prompt
switch (requestCode){
case PERMISSION_CODE:{
if (grantResults.length > 0 && grantResults[0] ==
PackageManager.PERMISSION_GRANTED) {
// permission from popup was granted
openCamera();
}
else {
// permission from popup was denied
Toast.makeText(this, "Permission denied...", Toast.LENGTH_SHORT).show();
}
}
}
}
#RequiresApi(api = Build.VERSION_CODES.N)
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// Called when image was captured from camera
// Obtain the image from the uri
Bitmap bitmap = null;
int orientation;
// Make sure we have an image_uri
try {
// Convert uri to an InputStream
InputStream in = getContentResolver().openInputStream(image_uri);
// Obtain Exif info from the InputStream
ExifInterface ei = new ExifInterface(in);
// Get bitmap depending on version
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
try {
bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(getContentResolver(), image_uri));
} catch (IOException e) {
e.printStackTrace();
}
} else {
try {
bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), image_uri);
} catch (IOException e) {
e.printStackTrace();
}
}
// Obtain orientation information from image
orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_UNDEFINED);
// Rotate the image (if needed) to portrait mode
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
rotatedBitmap = rotateImage(bitmap, 90);
break;
case ExifInterface.ORIENTATION_ROTATE_180:
rotatedBitmap = rotateImage(bitmap, 180);
break;
case ExifInterface.ORIENTATION_ROTATE_270:
rotatedBitmap = rotateImage(bitmap, 270);
break;
case ExifInterface.ORIENTATION_NORMAL:
default:
rotatedBitmap = bitmap;
}
}
catch (IOException e) {
e.printStackTrace();
}
/* Here is where the issue begins.
I can obtain the processed image and set it to my ImageView no problem, so the variable rotatedBitmap
is NOT null. But when I try to pass it to classifier.recognizeImage() it throws the null pointer error
and crashes the app
*/
if (resultCode == RESULT_OK) {
// Set the image captured to our ImageView
//mImageView.setImageURI(image_uri);
mImageView.setImageBitmap(rotatedBitmap);
if (rotatedBitmap != null) {
float[][] result = classifier.recognizeImage(rotatedBitmap);
String message = Arrays.toString(result[0]);
Snackbar mySnackbar = Snackbar.make(findViewById(R.id.myCoordinatorLayout), message,
Snackbar.LENGTH_SHORT);
mySnackbar.show();
}
}
}
public static Bitmap rotateImage(Bitmap source, float angle) {
Matrix matrix = new Matrix();
matrix.postRotate(angle);
return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(),
matrix, true);
}
}
public class Classifier extends CameraActivity {
private int inputSize = 300;
private int pixelSize = 3;
private int imageMean = 0;
private float imageStd = 255.0f;
private int maxResult = 3;
private float threshHold = 0.4f;
private List<String> labelList;
private Interpreter interpreter;
public static final String PREFIX = "stream2file";
public static final String SUFFIX = ".tmp";
public File stream2file (InputStream in) throws IOException {
final File tempFile = File.createTempFile(PREFIX, SUFFIX);
tempFile.deleteOnExit();
try (FileOutputStream out = new FileOutputStream(tempFile)) {
IOUtils.copy(in, out);
}
return tempFile;
}
public void init() {
Interpreter.Options options = new Interpreter.Options();
options.setNumThreads(5);
options.setUseNNAPI(true);
// Obtain the model from assets folder
final AssetManager assets = getApplicationContext().getAssets();
try {
InputStream in = assets.open("detect.tflite");
File file = stream2file(in);
interpreter = new Interpreter(file, options);
labelList = loadLabels("labelmap.txt", assets);
} catch (IOException e) {
e.printStackTrace();
}
}
public List loadLabels(String labelPath, AssetManager assetManager) throws IOException {
InputStream in = assetManager.open("labelmap.txt");
BufferedReader br = new BufferedReader(new InputStreamReader(in));
List outList = new ArrayList();
String line;
while( (line = br.readLine()) != null)
{
outList.add(line);
}
return outList;
}
/*
Here is the recognizeImage method that I wish to call from the CameraActivity class.
-
-
-
*/
public float[][] recognizeImage(final Bitmap bitmap) {
// Scale the bitmap to the appropriate shape
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, inputSize, inputSize, false);
ByteBuffer byteBuffer = convertBitmapToByteBuffer(scaledBitmap);
final float[][] result = new float[1][labelList.size()];
interpreter.run(byteBuffer, result);
return result;
}
public ByteBuffer convertBitmapToByteBuffer(Bitmap bitmap) {
//bitmap = Bit
// Preallocate memory for bytebuffer
ByteBuffer byteBuffer = ByteBuffer.allocate(4*inputSize*inputSize*pixelSize);
byteBuffer.order(ByteOrder.nativeOrder());
// Initialize pixel data array and populate from bitmap
int [] intArray = new int[inputSize*inputSize];
bitmap.getPixels(intArray, 0, bitmap.getWidth(), 0 , 0,
bitmap.getWidth(), bitmap.getHeight());
int pixel = 0; // pixel indexer
for (int i=0; i<inputSize; i++) {
for (int j=0; j<inputSize; j++) {
int input = intArray[pixel++];
byteBuffer.putFloat((((input >> 16 & 0x000000FF) - imageMean) / imageStd));
byteBuffer.putFloat((((input >> 8 & 0x000000FF) - imageMean) / imageStd));
byteBuffer.putFloat((((input & 0x000000FF) - imageMean) / imageStd));
}
}
return byteBuffer;
}
}

Change Classifier so that it does not extend anything.
Initialize this object with:
Classifier classifier = new Classifier();
classifier.init();
before you call the method you need.

Related

How can I fix my app crash after take a photo in android [duplicate]

This question already has answers here:
What is a NullPointerException, and how do I fix it?
(12 answers)
Closed 3 years ago.
How can I fix my app crash after take a photo in android, I am currently making an application to report to the streets via photos, but I have a problem when the user has taken a picture through the camera, when I press the application button to crash, the following reports of crashes that occur
java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=0, result=-1, data=Intent { act=inline-data (has extras) }} to activity {com.dbothxrpsc119.soppeng/com.dbothxrpsc119.soppeng.LaporanActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'int android.graphics.Bitmap.getWidth()' on a null object reference
at android.app.ActivityThread.deliverResults(ActivityThread.java:4332)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:4376)
at android.app.ActivityThread.-wrap19(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1670)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:176)
at android.app.ActivityThread.main(ActivityThread.java:6635)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:823)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'int android.graphics.Bitmap.getWidth()' on a null object reference
at com.dbothxrpsc119.soppeng.LaporanActivity.setPic(LaporanActivity.java:262)
at com.dbothxrpsc119.soppeng.LaporanActivity.onCaptureImageResult(LaporanActivity.java:271)
at com.dbothxrpsc119.soppeng.LaporanActivity.onActivityResult(LaporanActivity.java:206)
at android.app.Activity.dispatchActivityResult(Activity.java:7351)
at android.app.ActivityThread.deliverResults(ActivityThread.java:4328)
... 9 more
for the reportactivity code snippet as follows
public class LaporanActivity extends AppCompatActivity {
private int REQUEST_CAMERA = 0, SELECT_FILE = 1;
private Button btnSelect;
private EditText edPesanKirim;
private Button btnUpload;
private ImageView ivImage;
private String userChoosenTask;
String mCurrentPhotoPath;
Uri photoURI;
public static final String UPLOAD_KEY = "image";
public static final String TAG = "Laporan";
private Bitmap bitmap;
private SQLiteDatabase db;
private Cursor c;
String pesan,nama,ktpsim,alamat,hp;
Boolean sudahUpload = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_laporan);
db=openOrCreateDatabase("User", Context.MODE_PRIVATE, null);
ivImage = (ImageView) findViewById(R.id.ivImage);
btnSelect = (Button) findViewById(R.id.btnSelectPhoto);
btnSelect.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
selectImage();
}
});
edPesanKirim = (EditText) findViewById(R.id.edPesanKirim);
btnUpload = (Button) findViewById(R.id.btnUpload);
btnUpload.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//check jika data belum lengkap
c = db.rawQuery("SELECT * FROM users",null);
boolean exists = (c.getCount() > 0);
if(exists) {
c.moveToFirst();
pesan = edPesanKirim.getText().toString();
nama = c.getString(1);
ktpsim = c.getString(2);
alamat = c.getString(3);
hp = c.getString(4);
if (!c.getString(0).equals("") || !c.getString(1).equals("") || c.getString(2).equals("")
|| c.getString(3).equals("") || c.getString(4).equals("")) {
if (!pesan.equals("")) {
if (sudahUpload.equals(true)) {
uploadImage("gambar");
} else {
uploadImage("pesan");
}
} else {
Toast.makeText(LaporanActivity.this,"Silahkan isi gambar dan pesan terlebih dahulu",Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(LaporanActivity.this,"Silahkan Lengkapi Profil user anda terlebih dahulu",Toast.LENGTH_SHORT).show();
Intent intent = new Intent(LaporanActivity.this, ProfileDataActivity.class);
startActivity(intent);
}
} else {
Toast.makeText(LaporanActivity.this,"Silahkan Lengkapi Profil user anda terlebih dahulu",Toast.LENGTH_SHORT).show();
Intent intent = new Intent(LaporanActivity.this, ProfileDataActivity.class);
startActivity(intent);
}
c.close();
}
});
}
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case Utility.MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if(userChoosenTask.equals("Ambil Photo"))
cameraIntent();
else if(userChoosenTask.equals("Pilih dari Galery"))
galleryIntent();
} else {
//code for deny
}
break;
}
}
private void selectImage() {
final CharSequence[] items = { "Ambil Photo", "Pilih dari Galery", "Cancel" };
AlertDialog.Builder builder = new AlertDialog.Builder(LaporanActivity.this);
builder.setTitle("Tambah Photo!");
builder.setItems(items, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int item) {
boolean result=Utility.checkPermission(LaporanActivity.this);
if (items[item].equals("Ambil Photo")) {
userChoosenTask ="Ambil Photo";
if(result)
cameraIntent();
} else if (items[item].equals("Pilih dari Galery")) {
userChoosenTask ="Pilih dari Galery";
if(result)
galleryIntent();
} else if (items[item].equals("Cancel")) {
dialog.dismiss();
}
}
});
builder.show();
}
private void galleryIntent()
{
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);//
startActivityForResult(Intent.createChooser(intent, "Pilih File"),SELECT_FILE);
}
private void cameraIntent()
{
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Ensure that there's a camera activity to handle the intent
if (intent.resolveActivity(getPackageManager()) != null) {
// Create the File where the photo should go
File photoFile = null;
try {
photoFile = createImageFile();
photoFile.delete();
} catch (IOException ex) {
// Error occurred while creating the File
Log.d("Photo",ex.toString());
}
// Continue only if the File was successfully created
if (photoFile != null) {
Uri photoURI = FileProvider.getUriForFile(this,
"com.dbothxrpsc119.soppeng.fileprovider",
photoFile);
startActivityForResult(intent, REQUEST_CAMERA);
}
}
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == Activity.RESULT_OK) {
if (requestCode == SELECT_FILE)
onSelectFromGalleryResult(data);
else if (requestCode == REQUEST_CAMERA) {
onCaptureImageResult();
}
}
}
private File createImageFile() throws IOException {
// Create an image file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "DebotHaxor_" + timeStamp + "_";
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
storageDir /* directory */
);
// Save a file: path for use with ACTION_VIEW intents
//mCurrentPhotoPath = "file:" + image.getAbsolutePath();
mCurrentPhotoPath = image.getAbsolutePath();
//mCurrentPhotoPath = image;
return image;
}
private void galleryAddPic() {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri contentUri = photoURI;
mediaScanIntent.setData(contentUri);
this.sendBroadcast(mediaScanIntent);
}
private void setPic() {
// Get the dimensions of the View
int targetW = ivImage.getWidth();
int targetH = ivImage.getHeight();
//Log.d("Photo","Set Pic "+mCurrentPhotoPath);
Bitmap real_bitmap = BitmapFactory.decodeFile(mCurrentPhotoPath);
// Get the dimensions of the bitmap
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
int photoW = bmOptions.outWidth;
int photoH = bmOptions.outHeight;
// Determine how much to scale down the image
int scaleFactor = Math.min(photoW/targetW, photoH/targetH);
// Decode the image file into a Bitmap sized to fill the View
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = scaleFactor;
bmOptions.inPurgeable = true;
Bitmap bitmap_view = BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
ivImage.setImageBitmap(bitmap_view);
Log.d("Photo","Tampilkan Foto "+ mCurrentPhotoPath);
//resize
Bitmap resized;
resized = Bitmap.createScaledBitmap(bitmap_view,(int)(real_bitmap.getWidth()*0.4), (int)(real_bitmap.getHeight()*0.4), true);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
resized.compress(Bitmap.CompressFormat.JPEG, 60, bytes);
bitmap = resized;
}
private void onCaptureImageResult() {
galleryAddPic();
setPic();
sudahUpload = true;
}
#SuppressWarnings("deprecation")
private void onSelectFromGalleryResult(Intent data) {
Bitmap bm=null;
if (data != null) {
try {
bm = MediaStore.Images.Media.getBitmap(getApplicationContext().getContentResolver(), data.getData());
} catch (IOException e) {
e.printStackTrace();
}
}
ivImage.setImageBitmap(bm);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.JPEG, 60, bytes);
bitmap = bm;
sudahUpload = true;
}
public String getStringImage(Bitmap bmp){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bmp.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] imageBytes = baos.toByteArray();
String encodedImage = Base64.encodeToString(imageBytes, Base64.DEFAULT);
return encodedImage;
}
private void uploadImage(String mode){
class UploadImage extends AsyncTask<Bitmap,Void,String> {
ProgressDialog loading;
RequestHandler rh = new RequestHandler();
#Override
protected void onPreExecute() {
super.onPreExecute();
loading = ProgressDialog.show(LaporanActivity.this, "Kirim Pesan", "Mohon tunggu...",true,true);
loading.setCanceledOnTouchOutside(false);
}
#Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
loading.dismiss();
String msg = "";
if (s.equals("ok")) {
msg = "Pesan sukses terkirim";
clearForm();
} else {
msg = getString(R.string.api_error);
}
Toast.makeText(getApplicationContext(),msg,Toast.LENGTH_LONG).show();
}
#Override
protected String doInBackground(Bitmap... params) {
HashMap<String,String> data = new HashMap<>();
if (params.length > 0) {
Bitmap bitmap = params[0];
String uploadImage = getStringImage(bitmap);
data.put(UPLOAD_KEY, uploadImage);
}
data.put("pesan", pesan);
data.put("device_id", Utility.uniqDevice(LaporanActivity.this));
String uri = "https://"+ getString(R.string.api_url) + "/path/kirim_pesan";
String result = rh.sendPostRequest(uri,data);
return result;
}
}
UploadImage ui = new UploadImage();
if (mode.equals("gambar"))
ui.execute(bitmap);
else
ui.execute();
}
private void clearForm() {
ivImage.setImageResource(R.drawable.ic_menu_gallery);
edPesanKirim.setText("");
btnSelect.setFocusable(true);
}
}
I believe that the problem is that you create uri for a file to store photo but didn't put it in intent. Please, try following:
// Continue only if the File was successfully created
if (photoFile != null) {
Uri photoURI = FileProvider.getUriForFile(this,
"com.dbothxrpsc119.soppeng.fileprovider",
photoFile);
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
startActivityForResult(intent, REQUEST_CAMERA);
}

How to resize camera or gallery photo

I choose a file from gallery or camera. Then I'll upload them to the server. But I can't reduce their size. Image quality does not matter. Can you tell me the best method? I'm a beginner and I don't know how to use the code. So please give details.
private File createImageFile() throws IOException {
// Create an image file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
storageDir /* directory */
);
// Save a file: path for use with ACTION_VIEW intents
currentPhotoPath = image.getAbsolutePath();
return image;
}
private void dispatchTakePictureIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Ensure that there's a camera activity to handle the intent
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
// Create the File where the photo should go
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
// Error occurred while creating the File
}
// Continue only if the File was successfully created
if (photoFile != null) {
Uri photoURI = FileProvider.getUriForFile(this,
"com.example.myapplication.fileprovider",
photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
}
}
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_TAKE_PHOTO && resultCode == RESULT_OK) {
Glide.with(this).load(currentPhotoPath).into(iv);
} else if (requestCode == SELECT_A_PHOTO && resultCode == RESULT_OK){
selectedPhoto = data.getData();
Glide.with(this).load(selectedPhoto).into(iv);
}
private void galleryIntent()
{
Intent i = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(i,SELECT_A_PHOTO);
}
My file sizes look like this
I found the answer.This method allows you to reduce the size.
// Get the data from an ImageView as bytes
imageView.setDrawingCacheEnabled(true);
imageView.buildDrawingCache();
Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] data = baos.toByteArray();
New image size
This function takes in the image path and converts into a bitmap. 700 is the basic threshold for height/width set by me here. You can change it accordingly and create a scaled bitmap (The lower the number, lower is the image size). Each iteration of the while loop reduces the image to half. You can remodify it as per your requirement.
private Bitmap reduce_image_to_bitmap(String file_path){
Bitmap bit_map = BitmapFactory.decodeFile(file_path);
int h = bit_map.getHeight();
int w = bit_map.getWidth();
while(h > 700 || w > 700){
h = h/2;
w = w/2;
}
Bitmap out = Bitmap.createScaledBitmap(bit_map, w, h, false);
return out;
}
Make sure to convert the bitmap to a file and then proceed to send the file to your server.
Use this library: Compressor
Compressor is a lightweight and powerful android image compression library. Compressor will allow you to compress large photos into smaller sized photos with very less or negligible loss in quality of the image.
First of all you need to process this image so that you can reduce the size but quality you can maintain. You need to run a background task so that during big image process device doesn't fizz.
Then you can show a progress dialog during this process just add this code into your onCreate activity.
public ProgressDialog progressDialog;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
progressDialog = new ProgressDialog(MyProfileEidtActivity.this);
progressDialog.setMessage("Loading ...");
// just execute this process
new ImageProcessing().execute("YOUR IMAGE PATH");
}
public class ImageProcessing extends AsyncTask<String, Void, String> {
#Override
protected void onPreExecute() {
super.onPreExecute();
progressDialog.setMessage("Image Processing");
progressDialog.setCancelable(false);
progressDialog.show();
}
#Override
protected String doInBackground(String... strings) {
Bitmap mainImage = null;
Bitmap converetdImage = null;
ByteArrayOutputStream bos = null;
byte[] bt = null;
String encodeString = null;
try {
mainImage = BitmapFactory.decodeFile(strings[0]);
/// 500 means image size will be maximum 500 kb
converetdImage = getResizedBitmap(mainImage, 500);
bos = new ByteArrayOutputStream();
converetdImage.compress(Bitmap.CompressFormat.JPEG, 50, bos);
bt = bos.toByteArray();
encodeString = Base64.encodeToString(bt, Base64.DEFAULT);
} catch (Exception e) {
e.printStackTrace();
}
return encodeString;
}
#Override
protected void onPostExecute(String image) {
super.onPostExecute(s);
progressDialog.dismiss();
// this image will be your reduced image path
}
}
public Bitmap getResizedBitmap(Bitmap image, int maxSize) {
int width = image.getWidth();
int height = image.getHeight();
float bitmapRatio = (float) width / (float) height;
if (bitmapRatio > 1) {
width = maxSize;
height = (int) (width / bitmapRatio);
} else {
height = maxSize;
width = (int) (height * bitmapRatio);
}
return Bitmap.createScaledBitmap(image, width, height, true);
}

Why can't I read files produced by phone main camera, while front camera works

I'm trying to load pictures taken with build-in camera in LG G8 phone.
Code works for front camera, but throws Null Pointer Exception if I switch it to back.
static final int DESIRED_WIDTH = 640;
static final int DESIRED_HIGH = 480;
private Bitmap retrieveBitmap(){
// Get the dimensions of the bitmap
BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
//decode only size
bitmapOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(currentPhotoPath, bitmapOptions);
//returns 0 x 0
int photoW = bitmapOptions.outWidth;
int photoH = bitmapOptions.outHeight;
// Determine how much to scale down the image
float scaleFactor = Math.min( (float) photoW/ (float) DESIRED_WIDTH,
(float) photoH/ (float) DESIRED_HIGH);
// Decode the image file into a Bitmap of given size
bitmapOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
bitmapOptions.inJustDecodeBounds = false;
bitmapOptions.inSampleSize = (int) scaleFactor;
//returns null
Bitmap bitmap = BitmapFactory.decodeFile(currentPhotoPath, bitmapOptions);
return bitmap;
}
Camera app is invoked as in this example using "Save the full-size photo" method. Android reports NullPointerException after first call to BitmapFactory.decodeFile() as if file produced from main camera didn't exist.
E/BitmapFactory: Unable to decode stream: java.lang.NullPointerException
A while ago i too used the guide that you mentioned to be able to use the phone camera to take photos and save them.
The code below activates the phone camera on a button click and allowes both front and back camara to take a photo, then procede to save them. It also displays the photo taken in a ImageView. Hope it helps.
public class MainActivity extends AppCompatActivity {
static final int REQUEST_IMAGE_CAPTURE = 1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportActionBar().hide();
setContentView(R.layout.activity_main);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
galleryAddPic();
ImageView img = findViewById(R.id.img);
Bitmap bitm = BitmapFactory.decodeFile(mCurrentPhotoPath);
img.setImageBitmap(bitm);
}
private void dispatchTakePictureIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
ex.printStackTrace();
}
if (photoFile != null) {
Uri photoURI = FileProvider.getUriForFile(this,
"com.example.android.fileprovider", photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
}
}
}
String mCurrentPhotoPath;
private File createImageFile() throws IOException {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(imageFileName, ".jpg", storageDir);
mCurrentPhotoPath = image.getAbsolutePath();
return image;
}
private void galleryAddPic() {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
File f = new File(mCurrentPhotoPath);
Uri contentUri = Uri.fromFile(f);
mediaScanIntent.setData(contentUri);
this.sendBroadcast(mediaScanIntent);
}
public void cameraClick(View v){
dispatchTakePictureIntent();
}
}
Answering my own question:
turns out phone needs some time before large pictures are accessible. Adding a wait loop makes it work:
private Bitmap retrieveBitmap(){
// Get the dimensions of the bitmap
BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
//decode only size
bitmapOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(currentPhotoPath, bitmapOptions);
int i = 0;
while( bitmapOptions.outWidth == 0 && bitmapOptions.outHeight == 0){
//wait for 4 seconds for resource to be available, otherwise fail
try{
wait(1000);
}catch (Exception ex){
ex.printStackTrace();
return null;
}
BitmapFactory.decodeFile(currentPhotoPath, bitmapOptions);
i++;
//give up trying
if( i == 4) break;
}
//returns 0 x 0
int photoW = bitmapOptions.outWidth;
int photoH = bitmapOptions.outHeight;
// Determine how much to scale down the image
float scaleFactor = Math.min( (float) photoW/ (float) DESIRED_WIDTH,
(float) photoH/ (float) DESIRED_HIGH);
// Decode the image file into a Bitmap of given size
bitmapOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
bitmapOptions.inJustDecodeBounds = false;
bitmapOptions.inSampleSize = (int) scaleFactor;
return BitmapFactory.decodeFile(currentPhotoPath, bitmapOptions);
}

Native: Could not initialize Tesseract API with language=eng

Please pardon any bad English as this is my first time posting question on stackoverflow.
I would like to create a OCR Android Application using tesseract OCR engine and faced the following error, I have tried to search around but however did not find any solution, would appreciate your help. Thanks.
Codes I am trying:
TessBaseAPI baseApi = new TessBaseAPI();
baseApi.init(Environment.getExternalStorageDirectory().toString()+"/", `"eng");`
I have already created a tessdata folder in my device root with the eng.traineddata file inside, but I was prompted the following error when I access the function.
Could not initialize Tesseract API with language=eng!
I am using Android 6.0.1, API 23
Would appreciate any help! Thanks in advance~
Try this code out . It allows you to take a picture and displays the text .There are minor bugs in this code .Try this code on letters typed in notepad
Ignore the various files being placed in the tessdata folder . I am trying to read maths equation hence i need those . I have commented out the other files, it shouldn't bother you. If you are willing to try , try Mobile Vision API.
Hope this helps :)
public class MainActivity extends AppCompatActivity {
String imgPath;
Bitmap imgBitmap;
Uri imgUri;
InputStream trainDataInputStream;
OutputStream trainDataOutputStream;
AssetManager assetManager;
String externalDataPath;
TextView t;
String[] fileToBeCopied = {"eng.cube.bigrams", "eng.cube.fold", "eng.cube.lm", "eng.cube.nn", "eng.cube.params", "eng.cube.size", "eng.cube.word-freq", "eng.tesseract_cube.nn", "eng.traineddata","equ.traineddata"};
ProgressDialog pDialog;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
t = (TextView) findViewById(R.id.text);
new CopyFile().execute();
//placeFileFromAssetsToExternalStorage();
takePicture();
}
class CopyFile extends AsyncTask {
#Override
protected void onPreExecute() {
pDialog = new ProgressDialog(MainActivity.this);
pDialog.setMessage("Fetching image...");
pDialog.setIndeterminate(false);
pDialog.setCancelable(false);
pDialog.show();
}
#Override
protected Object doInBackground(Object[] objects) {
//placeFileFromAssetsToExternalStorage(fileToBeCopied[0]);
//placeFileFromAssetsToExternalStorage(fileToBeCopied[1]);
//placeFileFromAssetsToExternalStorage(fileToBeCopied[2]);
//placeFileFromAssetsToExternalStorage(fileToBeCopied[3]);
//placeFileFromAssetsToExternalStorage(fileToBeCopied[4]);
//placeFileFromAssetsToExternalStorage(fileToBeCopied[5]);
//placeFileFromAssetsToExternalStorage(fileToBeCopied[6]);
//placeFileFromAssetsToExternalStorage(fileToBeCopied[7]);
placeFileFromAssetsToExternalStorage(fileToBeCopied[8]);
//placeFileFromAssetsToExternalStorage(fileToBeCopied[9]);
return null;
}
#Override
protected void onPostExecute(Object o) {
pDialog.dismiss();
}
}
private void takePicture() {
File photoFile = null;
Intent iPicture = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (iPicture.resolveActivity(getPackageManager()) != null) {
try {
photoFile = createImageFile();
} catch (Exception e) {
e.printStackTrace();
}
//if photo file is created
if (photoFile != null) {
Uri photoURI = FileProvider.getUriForFile(getApplicationContext(), "com.scorpio.fileprovider", photoFile);
System.out.println(imgPath);
iPicture.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(iPicture, 1);
}
}
}
private File createImageFile() {
File imgFile = null;
String fileStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
File storageDir = Environment.getExternalStorageDirectory();
try {
imgFile = File.createTempFile(fileStamp, ".jpeg", storageDir);
} catch (IOException e) {
e.printStackTrace();
}
imgPath = imgFile.getAbsolutePath();
return imgFile;
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 1 && resultCode == RESULT_OK) {
galleryAddPic();
}
}
private void galleryAddPic() {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
File f = new File(imgPath);
System.out.println("Image path ->" + imgPath);
Uri contentUri = Uri.fromFile(f);
imgUri = contentUri;
System.out.println("Image uri " + imgUri);
mediaScanIntent.setData(contentUri);
this.sendBroadcast(mediaScanIntent);
ocrImage();
}
public void ocrImage() {
try {
//getting image for ocr
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 4;
imgBitmap = BitmapFactory.decodeFile(imgPath, options);
} catch (Exception e) {
e.printStackTrace();
}
ExifInterface exif = null;
try {
exif = new ExifInterface(imgPath);
} catch (IOException e) {
e.printStackTrace();
}
int exifOrientation = exif.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
int rotate = 0;
switch (exifOrientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
rotate = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
rotate = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
rotate = 270;
break;
}
if (rotate != 0) {
int w = imgBitmap.getWidth();
int h = imgBitmap.getHeight();
// Setting pre rotate
Matrix mtx = new Matrix();
mtx.preRotate(rotate);
// Rotating Bitmap & convert to ARGB_8888, required by tess
imgBitmap = Bitmap.createBitmap(imgBitmap, 0, 0, w, h, mtx, false);
}
imgBitmap = imgBitmap.copy(Bitmap.Config.ARGB_8888, true);
TessBaseAPI baseApi = new TessBaseAPI();
baseApi.init(externalDataPath, "eng");
baseApi.setImage(imgBitmap);
String ocrResult = baseApi.getUTF8Text();
System.out.println(ocrResult);
baseApi.end();
t.setText(ocrResult);
}
public void placeFileFromAssetsToExternalStorage(String filename) {
System.out.println("Running DataRunnable class ");
assetManager = getResources().getAssets();
externalDataPath = Environment.getExternalStorageDirectory() + "/tessdata";
System.out.println("external data path " + externalDataPath);
//creating eng.trainedData
File f = new File(externalDataPath);
try {
if (!f.exists()) {
f.mkdir();
}
externalDataPath = externalDataPath + "/" + filename;
f = new File(externalDataPath);
if (!f.exists())
f.createNewFile();
externalDataPath = Environment.getExternalStorageDirectory().toString();
trainDataInputStream = assetManager.open(filename);
trainDataOutputStream = new FileOutputStream(f);
byte[] buffer = new byte[1024];
int read;
while ((read = trainDataInputStream.read(buffer)) != -1) {
trainDataOutputStream.write(buffer, 0, read);
}
trainDataOutputStream.flush();
trainDataOutputStream.close();
trainDataInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

Implementing fix on Out of Memory Error

In my widget, I have an imageView that collect an image from the user gallery and then sets that image up as the background in another activity. It works fine, but when I try to add an image file that is too big (such as an image from the camera) or upload many image files, I get an "out of memory" error. After looking around stackoverflow for a while, I noticed that everyone pretty much used the basic method of:
public static Bitmap decodeSampleImage(File f, int width, int height) {
try {
System.gc(); // First of all free some memory
// Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(f), null, o);
// The new size we want to scale to
final int requiredWidth = width;
final int requiredHeight = height;
// Find the scale value (as a power of 2)
int sampleScaleSize = 1;
while (o.outWidth / sampleScaleSize / 2 >= requiredWidth && o.outHeight / sampleScaleSize / 2 >= requiredHeight)
sampleScaleSize *= 2;
// Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = sampleScaleSize;
return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
} catch (Exception e) {
Log.d(TAG, e.getMessage()); // We don't want the application to just throw an exception
}
return null;
}
I also noticed that people have recycled unused bitmaps as well. I understand how this works but I don't know where I should put it in my coding.
Here are my two classes (Personalize.java where the imageView to collect the background is. It has the imageView and two buttons (one for choosing an image from the gallery where it then displays that image into the imageView and the other to then set that image as the background).
First here is Personalize.java:
package com.example.awesomefilebuilderwidget;
IMPORTS
public class Personalize extends Activity implements View.OnClickListener {
Button button;
ImageView image; //the imageview for setting the background
ImageView image2; //the imageview for setting the icon (not focusing on)
Button btnChangeImage;
Button btnChangeImageForIcon;
Button btnSetBackground;
private static final int SELECT_PICTURE = 1;
private static final int SELECT_PICTURE_2 = 2;
private String selectedImagePath;
Bitmap background;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.personalize);
image = (ImageView) findViewById(R.id.imageView1);
image2 = (ImageView) findViewById(R.id.imageView2Icon);
Button btnChangeImage = (Button) findViewById(R.id.btnChangeImage);
btnChangeImage.setOnClickListener(this);
Button btnChangeImageForIcon = (Button) findViewById(R.id.btnChangeImageForIcon);
btnChangeImageForIcon.setOnClickListener(this);
Button btnSetBackground = (Button) findViewById(R.id.btnSetBackground);
btnSetBackground.setOnClickListener(this);
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnChangeImage:
launchImageChooser();
break;
case R.id.btnChangeImageForIcon:
launchImageChooser();
break;
case R.id.btnSetBackground:
setBackgroundImageInDragAndDrop();
break;
}
}
private void setBackgroundImageInDragAndDrop() {
Log.d("Personalize", "setBackgroundImageInDragAndDrop() called");
Intent i = getIntent();
//Convert bitmap to byte array to send back to activity
// See: http://stackoverflow.com/questions/11010386/send-bitmap-using-intent-android
ByteArrayOutputStream stream = new ByteArrayOutputStream();
background.compress(Bitmap.CompressFormat.PNG, 100, stream);
byte[]byteArray = stream.toByteArray();
i.putExtra("myBackgroundBitmap", byteArray);
setResult(RESULT_OK, i);
finish();
}
private void launchImageChooser() {
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(intent, SELECT_PICTURE);
}
public String getPath(Uri uri) {
String[] projection = { MediaStore.Images.Media.DATA };
Cursor cursor = getContentResolver().query(uri, projection, null, null, null);
int column_index = cursor
.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
String imagePath = cursor.getString(column_index);
if(cursor != null) {
cursor.close();
}
return imagePath;
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (resultCode == RESULT_OK) {
if (requestCode == SELECT_PICTURE)
{
Uri selectedImageUri = data.getData();
selectedImagePath = getPath(selectedImageUri);
background = getAndDecodeImage(selectedImagePath);
if(background != null){
image.setImageBitmap(background);
}
} else if (requestCode == SELECT_PICTURE_2)
{
Uri selectedImageUri = data.getData();
selectedImagePath = getPath(selectedImageUri);
Bitmap b2 = getAndDecodeImage(selectedImagePath);
if(b2 != null){
image2.setImageBitmap(b2);
}
}
}
}
private Bitmap getAndDecodeImage(String selectedImagePath){
try {
Log.d("Personalize", "selectedImagePath: " + selectedImagePath);
FileInputStream fileis=new FileInputStream(selectedImagePath);
BufferedInputStream bufferedstream=new BufferedInputStream(fileis);
byte[] bMapArray= new byte[bufferedstream.available()];
bufferedstream.read(bMapArray);
Bitmap bMap = BitmapFactory.decodeByteArray(bMapArray, 0, bMapArray.length);
if (fileis != null)
{
fileis.close();
}
if (bufferedstream != null)
{
bufferedstream.close();
}
return bMap;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public boolean saveImageToInternalStorage(Bitmap image) {
try {
FileOutputStream fos = this.openFileOutput("desiredFilename.png", Context.MODE_PRIVATE);
image.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.close();
return true;
} catch (Exception e) {
return false;
}
}
}
Then here is my Drag_and_Drop_App.java (snippet of important parts -- collects bitmap and sets as background):
package com.example.awesomefilebuilderwidget;
IMPORTS
public class Drag_and_Drop_App extends Activity {
private static final int SET_BACKGROUND = 10;
private ListView mListAppInfo;
// Search EditText
EditText inputSearch;
public AppInfoAdapter adapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// set layout for the main screen
setContentView(R.layout.drag_and_drop_app);
// import buttons
Button btnLinkToFeedback = (Button) findViewById(R.id.btnLinkToFeedback);
Button btnLinkToPersonalize = (Button) findViewById(R.id.btnLinkToPersonalize);
// Link to Personalize Screen
btnLinkToPersonalize.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent i = new Intent(getApplicationContext(),
Personalize.class);
startActivityForResult(i, SET_BACKGROUND);
}
});
}
public Bitmap getThumbnail(String filename) {
Bitmap thumbnail = null;
try {
File filePath = this.getFileStreamPath(filename);
FileInputStream fi = new FileInputStream(filePath);
thumbnail = BitmapFactory.decodeStream(fi);
} catch (Exception ex) {
Log.e("getThumbnail() on internal storage", ex.getMessage());
}
return thumbnail;
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.i("Drag_and_Drop_App", "requestCode: " + requestCode + ", resultCode: " + resultCode);
if(requestCode == SET_BACKGROUND && resultCode == RESULT_OK){
byte[] byteArray = data.getByteArrayExtra("myBackgroundBitmap");
Bitmap myBackground = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length);
setBackgroundImage(myBackground);
}
}
#SuppressLint("NewApi")
private void setBackgroundImage(Bitmap bitmap) {
RelativeLayout yourBackgroundView = (RelativeLayout) findViewById(R.id.rl_drag_and_drop_app);
Drawable d = new BitmapDrawable(getResources(), bitmap);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
yourBackgroundView.setBackgroundDrawable(d);
} else {
yourBackgroundView.setBackground(d);
}
}
}
So where could I implement that coding and where could I also get rid of other unused bitmaps in memory? (recycle them?)

Categories