Class Notes – Resize Camera Image – Out of Memory Issue

String imageFilePath = "myfavpict.jpg";
PImage cameraImage;

void setup() {
  orientation(LANDSCAPE);
  
  // Create a File object out of that
  File imageFile = new File(android.os.Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + imageFilePath);
  // Create a Uri out of that
  android.net.Uri imageFileUri = android.net.Uri.fromFile(imageFile);

  // Create the Intent that triggers the camera
  android.content.Intent i = new android.content.Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
  // Tell the camera application where we want the resulting image saved
  i.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, imageFileUri);
  // Start the Camera
  startActivityForResult(i, 0);
}

void draw() {
  if (cameraImage != null) {
    //image(cameraImage, 0, 0, width, height);
    cameraImage.loadPixels();
    loadPixels();
    
    for (int x = 0; x < cameraImage.width && x < width; x++) {
      for (int y = 0; y < cameraImage.height && y < height; y++) {
         int r = int(red(cameraImage.pixels[x+cameraImage.width*y]));
         color c = color(r,r,r);
         pixels[x+cameraImage.width*y] = c;
      }
    }
    updatePixels();
      filter(INVERT);
    
  }
}

protected void onActivityResult(int requestCode, int resultCode, android.content.Intent intent) {
  super.onActivityResult(requestCode, resultCode, intent);
  if (resultCode == RESULT_OK) {
    // We know the location via the imageFilePath String so load it into a standard Processing PImage
    println(imageFilePath);
    resizeImage(android.os.Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + imageFilePath, android.os.Environment.getExternalStorageDirectory().getAbsolutePath() + "/smallimage.jpg");
    cameraImage = loadImage(android.os.Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "smallimage.jpg");
    println("Should have loaded Image");
  }
}


// Loads an image from disk at screen resolution and saves it back out at the same size.
void resizeImage(String imageFilePath, String newImageFilePath) {
  try {
    // Load up the image's dimensions not the image itself
    android.graphics.BitmapFactory.Options bmpFactoryOptions = new android.graphics.BitmapFactory.Options();
    bmpFactoryOptions.inJustDecodeBounds = true;
    android.graphics.Bitmap bmp = android.graphics.BitmapFactory.decodeFile(imageFilePath, bmpFactoryOptions);
    int heightRatio = (int) Math.ceil(bmpFactoryOptions.outHeight / (float) height);
    int widthRatio = (int) Math.ceil(bmpFactoryOptions.outWidth / (float) width);
    println("HEIGHTRATIO: " + heightRatio);
    println("WIDTHRATIO: " + widthRatio);
    // If both of the ratios are greater than 1, one of the sides of // the image is greater than the screen
    if (heightRatio > 1 && widthRatio > 1) {
      if (heightRatio > widthRatio) {
        // Height ratio is larger, scale according to it
        bmpFactoryOptions.inSampleSize = heightRatio;
      } 
      else {
        // Width ratio is larger, scale according to it
        bmpFactoryOptions.inSampleSize = widthRatio;
      }
    }
    // Decode it for real
    bmpFactoryOptions.inJustDecodeBounds = false;
    bmp = android.graphics.BitmapFactory.decodeFile(imageFilePath, bmpFactoryOptions);

    java.io.File newImageFile = new java.io.File(newImageFilePath);
    android.net.Uri newImageUri = android.net.Uri.fromFile(newImageFile);
    OutputStream imageFileOS = getContentResolver().openOutputStream(newImageUri);
    bmp.compress(android.graphics.Bitmap.CompressFormat.JPEG, 90, imageFileOS);

    println("Resized image, wrote out to: " + newImageFile.getAbsolutePath());
  } 
  catch (Exception e) {
    println(e.toString());
    e.printStackTrace();
  }
}

Scaffolding

final int LOADING = 0;
final int HOME = 1;
final int PLAY = 2;

int screenNumber = LOADING;

void setup() {
  
}

void draw() {
  if (screenNumber == LOADING) {
     drawLoading();
  } else if (screenNumber == HOME) {
     drawHome();
  } else if (screenNumber == PLAY) {
    drawPlay();
  } 
}

void drawLoading() {
   line(0,0,width,height);
}

void drawHome() {
  
}

void drawPlay() {
  drawAnimation();
  drawLyrics();

  // Draw buttons

}

void drawAnimation() {
  
}

void drawLyrics() {
  
}


void mousePressed() {
  if (screenNumber == LOADING) {
     mousePressedLoading();
  } else if (screenNumber == HOME) {
     mousePressedHome();
  } else if (screenNumber == PLAY) {
    mousePressedPlay();
  } 
}

void mousePressedLoading() {
   screenNumber = HOME; 
}

void mousePressedHome() {
  
}

void mousePressedPlay() {
  // Test Buttons
  // Start Play/STop Play 
  startMusic();
  stopMusic();
  startRecording();
  stopRecording();
}

Player Screen

int lyricNumber = 0;

String[] lyrics = {
“Every night in my dreams”,
“I see you, I feel you”,
“”

};

SoundPlayer titanic;

void setup() {
titanic = new SoundPlayer(this,”Titanic.mid”);
titanic.play();
}

void draw() {
background(0);
textMode(SCREEN);
text(lyrics[lyricNumber],width/2,height/2);
}

void mousePressed() {
println(titanic.getPosition());

lyricNumber++;
if (lyricNumber >= lyrics.length) {
lyricNumber = 0;
}
}

And here is the song: http://rainbowsendpress.com/midi/Titanic.mid

Camera Image Rotation

On most Android devices, the image taken by the camera is while the camera is in landscape mode. Unfortunately, we don’t know by simply loading the image what the orientation is when the image was taken. The easiest way to rectify the situation is to lock our application in landscape mode:

void setup() {
     orientation(LANDSCAPE);
...
}

The opposite is portrait mode:

orientation(PORTRAIT);

Speech Recognition

Just a quick example:

final int VOICE_RECOGNITION_REQUEST_CODE = 1234;

void setup() {
}

void draw() {
}

void mousePressed() {
  android.content.Intent intent = new android.content.Intent(android.speech.RecognizerIntent.ACTION_RECOGNIZE_SPEECH);

  // Specify the calling package to identify your application
  intent.putExtra(android.speech.RecognizerIntent.EXTRA_CALLING_PACKAGE, getClass().getPackage().getName());

  // Display an hint to the user about what he should say.
  intent.putExtra(android.speech.RecognizerIntent.EXTRA_PROMPT, "Speech recognition demo");

  // Given an hint to the recognizer about what the user is going to say
  intent.putExtra(android.speech.RecognizerIntent.EXTRA_LANGUAGE_MODEL, 
    android.speech.RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);

  // Specify how many results you want to receive. The results will be sorted
  // where the first result is the one with higher confidence.
  intent.putExtra(android.speech.RecognizerIntent.EXTRA_MAX_RESULTS, 5);

  startActivityForResult(intent, VOICE_RECOGNITION_REQUEST_CODE);
}

/**
 * Handle the results from the recognition activity.
 */
@Override
protected void onActivityResult(int requestCode, int resultCode, android.content.Intent data) {
  if (requestCode == VOICE_RECOGNITION_REQUEST_CODE && resultCode == RESULT_OK) {
    // The list of possible matches
    ArrayList matches = data.getStringArrayListExtra(android.speech.RecognizerIntent.EXTRA_RESULTS);
    if (matches.size() > 0) {
      println((String)(matches.get(0)));
    }
  }
}

OutOfMemory

In some instances, the image returned by the camera may be too big for our application to load into memory outright. In order to rectify this situation, here is a method that you can use to resize an image on disk. Just pass in the path of the original and where you want to new image saved.

// Loads an image from disk at screen resolution and saves it back out at the same size.
void resizeImage(String imageFilePath, String newImageFilePath) {
  try {
    // Load up the image's dimensions not the image itself 
    android.graphics.BitmapFactory.Options bmpFactoryOptions = new android.graphics.BitmapFactory.Options(); 
    bmpFactoryOptions.inJustDecodeBounds = true; 
    android.graphics.Bitmap bmp = android.graphics.BitmapFactory.decodeFile(imageFilePath, bmpFactoryOptions);
    int heightRatio = (int) Math.ceil(bmpFactoryOptions.outHeight / (float) height);
    int widthRatio = (int) Math.ceil(bmpFactoryOptions.outWidth / (float) width);
    println("HEIGHTRATIO: " + heightRatio); 
    println("WIDTHRATIO: " + widthRatio);
    // If both of the ratios are greater than 1, one of the sides of // the image is greater than the screen 
    if (heightRatio > 1 && widthRatio > 1) {
      if (heightRatio > widthRatio) { 
        // Height ratio is larger, scale according to it 
        bmpFactoryOptions.inSampleSize = heightRatio;
      } else { 
        // Width ratio is larger, scale according to it 
        bmpFactoryOptions.inSampleSize = widthRatio;
      }
    }
    // Decode it for real 
    bmpFactoryOptions.inJustDecodeBounds = false; 
    bmp = android.graphics.BitmapFactory.decodeFile(imageFilePath, bmpFactoryOptions);
    
    java.io.File newImageFile = new java.io.File(newImageFilePath);
    android.net.Uri newImageUri = android.net.Uri.fromFile(newImageFile);
    OutputStream imageFileOS = getContentResolver().openOutputStream(newImageUri);
    bmp.compress(android.graphics.Bitmap.CompressFormat.JPEG, 90, imageFileOS);
    
    println("Resized image, wrote out to: " + newImageFile.getAbsolutePath());
    
  } catch (Exception e) {
    println(e.toString());
    e.printStackTrace(); 
  }
}

For instance, if I had a camera image on the SD card called dottie.jpg and I wanted make it smaller I would do the following:

PImage theImage;

void setup() {  
  // If the image is too big, you need to make it smaller before you can load it.
 resizeImage(android.os.Environment.getExternalStorageDirectory().getAbsolutePath() + "/dottie.jpg", android.os.Environment.getExternalStorageDirectory().getAbsolutePath() + "/dottie_small.jpg");
  theImage = loadImage(android.os.Environment.getExternalStorageDirectory().getAbsolutePath() + "/dottie_small.jpg");
}

Pixels

In Processing we can access the pixels of an image or those that are displayed on the screen. When accessing them we can pull out the color values and modify it for display.

Here is a quick example that we’ll review:

// Don't forget WRITE_EXTERNAL_STORAGE

PImage theImage;

void setup() {
  //size(400,400);
  theImage = loadImage("dottie.jpg");
}

void draw() {
  loadPixels();
  theImage.loadPixels();

  // Loop through the x
  for (int x = 0; x < theImage.width && x < width; x++ ) {
    // For each x, loop through the y
    for (int y = 0; y < theImage.height && y < height; y++ ) {
      
      // Calculate the array pixel location for the image
      int imageIndex = x + y*theImage.width;
      // Calculate the array pixel location for the screen
      int screenIndex = x + y*width;
      
      // Get the red, green and blue values from image
      float r = red (theImage.pixels[imageIndex]);
      float g = green (theImage.pixels[imageIndex]);
      float b = blue (theImage.pixels[imageIndex]);
      
      // The closer the pixel is to the center, the lower the distance
      float distance = dist(x, y, theImage.width/2, theImage.height/2);

      float maxDistance = sqrt(sq(theImage.width) + sq(theImage.height));
        
      float distanceRatio = distance/maxDistance;

      // We want closer pixels to be brighter
      r = r*(1-distanceRatio);
      g = g*(1-distanceRatio);
      b = b*(1-distanceRatio);

      // We also want sepia
      r = r + 99;
      g = g + 66;
      b = b + 33;

      // Constrain RGB to between 0-255
      r = constrain(r, 0, 255);
      g = constrain(g, 0, 255);
      b = constrain(b, 0, 255);
        
      // Make a new color out of the new RGB values and set the pixels of the sketch
      color c = color(r, g, b);
      pixels[screenIndex] = c;
    }
  }

  // Call updatePixels to tell processing to draw the pixel values to the screen
  updatePixels();
  
  filter(BLUR, 2);
}

void mousePressed() {
 // Save output to the SD card
 save(android.os.Environment.getExternalStorageDirectory().getAbsolutePath() + "/output.tif"); 
}

Checkout the filter and blend references as well.

Select an Existing Image

We can create an Intent that allows the user to select an existing image using the built-in capabilities and display that image in our sketch.

Intent choosePictureIntent = new Intent(Intent.ACTION_PICK,␣ android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);

Here is an example:

final int CHOOSE_PICTURE = 0;

String imageFilePath;
PImage selectedImage;

void setup() {
  
}

void draw() {
  if (selectedImage != null) {
    image(selectedImage,0,0,width,height); 
  }
}

void mousePressed() {
  android.content.Intent choosePictureIntent = new android.content.Intent(android.content.Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
  startActivityForResult(choosePictureIntent, CHOOSE_PICTURE);
}

protected void onActivityResult(int requestCode, int resultCode, android.content.Intent intent) { 
  super.onActivityResult(requestCode, resultCode, intent);
  if (resultCode == RESULT_OK) {
    
    android.net.Uri imageFileUri = intent.getData();
    // This Uri is a "content://" style intent which Processing can't use directly.  We need to query the MediaStore to get the file path
    //println(imageFileUri.toString());
    // content://media/external/images/media/9397
    
    String[] columns = { android.provider.MediaStore.Images.Media.DATA }; // The file path
    android.database.Cursor cursor = managedQuery(imageFileUri, columns, null, null, null);
    if (cursor.moveToFirst()) { 
      imageFilePath = cursor.getString(cursor.getColumnIndexOrThrow(android.provider.MediaStore.Images.Media.DATA));
      println(imageFilePath);
      selectedImage = loadImage(imageFilePath);
    }
  }
}