Android圖像處理簡介の圖像存儲和元數據

Android提供Content Provider來實現應用程序之間的數據共享,provider提供瞭標準的接口用於存儲和檢索多種類型的數據。圖像 、音頻和視頻的標準content provider就是MediaStore。
1)獲取圖像的URI
要獲得標準的圖像存儲路徑,我們需要獲得MediaStore的引用,而這是通過content resolver來實現的(因為使用Content resolver可以獲取content provider,而MediaStore就是一個content provider)。
傳遞指定的URI給content resolver,可以得到對應的content provider,由於是新增一張圖像,所以使用insert方法,相應的URI是android.provider.MediaStore.Images.Media類定義的常量EXTERNAL_CONTENT_URI。這個常量說明我們要將圖像存儲到主外部存儲器中,通常就是SD卡;如果要將圖像存儲到設備內存中,則使用INTERNAL_CONTENT_URI。當然對於媒體文件的存儲而言,由於尺寸一般都比較大,因此會優先考慮使用EXTERNAL_CONTENT_URI。
Content resolver類的insert函數返回值是URI類型:
[java]
Uri imageFileUri = getContentResolver().insert( 
                        Media.EXTERNAL_CONTENT_URI, new ContentValues()); 
// Start the Camera App 
Intent it = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE); 
it.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, imageFileUri); 
startActivityForResult(it, CAMERA_RESULT); 

上面代碼中的ContentValues對象是捕獲的圖像在創建時要關聯的元數據,當然,上面的元數據是空的。我們可以使用put函數將元數據信息寫入ContentValues中,ContentValues是以鍵值對的形式存儲數據的,鍵名是定義在android.provider.MediaStore.Images.Media類中的常量:
[java]
// Save the name and description of an image in a ContentValues map 
ContentValues contentValues = new ContentValues(3); 
contentValues.put(Media.DISPLAY_NAME, "ASCE1885_TITLE"); 
contentValues.put(Media.DESCRIPTION, "ASCE1885_DESCRIPTION"); 
contentValues.put(Media.MIME_TYPE, "image/jpeg"); 
                 
// Add a new recode without the bitmap, but with some values set. 
// insert() returns the URI of the new record 
Uri imageFileUri = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, contentValues); 

上面獲取的Uri可能類似於:
content://media/external/images/media/16
這裡說明一點,以content開頭的Uri一般都是被content provider使用的,例如上面的Uri是被MediaStore使用的一樣。
反過來根據Uri,我們可以用來檢索這個Uri對應路徑中的圖像數據,代碼如下:
[java]
Bitmap bmp = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageFileUri),null,bmpFactory); 
<p> </p> 
在我們捕獲圖像並存放在MediaStore中後,如果還想再增加元數據信息,那麼可以使用ContentResolver的update函數來實現:
[java]
// Update the MediaStore record with Title and Description 
ContentValues contentValues = new ContentValues(3); 
contentValues.put(Media.DISPLAY_NAME, "WEN1885_TITLE"); 
contentValues.put(Media.DESCRIPTION, "WEN1885_DESCRIPTION"); 
getContentResolver().update(imageFileUri, contentValues, null, null); 
完整的代碼例子如下,先看layout/main.xml文件:
[html]
<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:orientation="vertical" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent" 
    > 
<ImageView 
    android:id="@+id/ReturnedImageView"   
    android:layout_width="wrap_content"  
    android:layout_height="wrap_content"/> 
<TextView  
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
    android:text="Title:" 
    android:id="@+id/TitleTextView" /> 
<EditText  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:id="@+id/TitleEditText"/> 
<TextView  
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
    android:text="Description" 
    android:id="@+id/DescriptionTextView"/> 
<EditText  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:id="@+id/DescriptionEditText"/> 
<Button  
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
    android:id="@+id/TakePictureButton" 
    android:text="Take Picture"/> 
<Button  
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
    android:id="@+id/SaveDataButton" 
    android:text="Save Data"/> 
</LinearLayout> 

完整的Java代碼如下:
[java]
package hust.iprai.asce1885.promedia; 
 
import java.io.FileNotFoundException; 
 
import android.app.Activity; 
import android.content.ContentValues; 
import android.content.Intent; 
import android.graphics.Bitmap; 
import android.graphics.BitmapFactory; 
import android.net.Uri; 
import android.os.Bundle; 
import android.provider.MediaStore.Images.Media; 
import android.util.Log; 
import android.view.View; 
import android.view.View.OnClickListener; 
import android.widget.Button; 
import android.widget.EditText; 
import android.widget.ImageView; 
import android.widget.TextView; 
import android.widget.Toast; 
 
public class MediaStoreCameraActivity extends Activity { 
    final static int CAMERA_RESULT = 0; 
     
    Uri imageFileUri = null; 
     
    // User interface elements, specified in res/layout/main.xml 
    ImageView returnedImageView; 
    Button takePictureButton; 
    Button saveDataButton; 
    TextView titleTextView; 
    TextView descriptionTextView; 
    EditText titleEditText; 
    EditText descriptionEditText; 
     
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
         
        // Set the content view to be what is defined in the res/layout/main.xml file 
        setContentView(R.layout.main); 
         
        // Get references to UI elements 
        returnedImageView = (ImageView) findViewById(R.id.ReturnedImageView); 
        takePictureButton = (Button) findViewById(R.id.TakePictureButton); 
        saveDataButton = (Button) findViewById(R.id.SaveDataButton); 
        titleTextView = (TextView) findViewById(R.id.TitleTextView); 
        descriptionTextView = (TextView) findViewById(R.id.DescriptionTextView); 
        titleEditText = (EditText) findViewById(R.id.TitleEditText); 
        descriptionEditText = (EditText) findViewById(R.id.DescriptionEditText); 
         
        // Set all except takePictureButton to not be visible initially 
        // View.GONE is invisible and doesn't take up space in the layout 
        returnedImageView.setVisibility(View.GONE); 
        saveDataButton.setVisibility(View.GONE); 
        titleTextView.setVisibility(View.GONE); 
        descriptionTextView.setVisibility(View.GONE); 
        titleEditText.setVisibility(View.GONE); 
        descriptionEditText.setVisibility(View.GONE); 
         
         
        // When the Take Picture Button is clicked 
        takePictureButton.setOnClickListener(new OnClickListener() { 
 
            public void onClick(View v) { 
                // Add a new record without the bitmap 
                // return the URI of the new record 
                imageFileUri = getContentResolver().insert( 
                        Media.EXTERNAL_CONTENT_URI, new ContentValues()); 
                // Start the Camera App 
                Intent it = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE); 
                it.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, imageFileUri); 
                startActivityForResult(it, CAMERA_RESULT); 
            } 
             
        }); 
         
        saveDataButton.setOnClickListener(new OnClickListener() { 
 
            public void onClick(View v) {                
                // Update the MediaStore record with Title and Description 
                ContentValues contentValues = new ContentValues(3); 
                contentValues.put(Media.DISPLAY_NAME, titleEditText.getText().toString()); 
                contentValues.put(Media.DESCRIPTION, descriptionEditText.getText().toString()); 
                getContentResolver().update(imageFileUri, contentValues, null, null); 
 
                // Tell the user 
                Toast bread = Toast.makeText(MediaStoreCameraActivity.this, "Record Updated", Toast.LENGTH_LONG); 
                bread.show(); 
                 
                // Go back to the initial state, set Take Picture Button Visible 
                // hide other UI elements 
                takePictureButton.setVisibility(View.VISIBLE); 
                returnedImageView.setVisibility(View.GONE); 
                titleTextView.setVisibility(View.GONE); 
                descriptionTextView.setVisibility(View.GONE); 
                titleEditText.setVisibility(View.GONE); 
                descriptionEditText.setVisibility(View.GONE); 
            } 
             
        }); 
    } 
 
    @Override 
    protected void onActivityResult(int requestCode, int resultCode, Intent data) { 
        super.onActivityResult(requestCode, resultCode, data); 
         
        if (RESULT_OK == resultCode) { 
            // The Camera App has returned 
            // Hide the Take Picture Button 
            takePictureButton.setVisibility(View.GONE); 
             
            // Show the other UI elements 
            saveDataButton.setVisibility(View.VISIBLE); 
            returnedImageView.setVisibility(View.VISIBLE); 
            titleTextView.setVisibility(View.VISIBLE); 
            descriptionTextView.setVisibility(View.VISIBLE); 
            titleEditText.setVisibility(View.VISIBLE); 
            descriptionEditText.setVisibility(View.VISIBLE); 
             
            // Scale the image 
            int dw = 200; // Make it at most 200 pixels wide 
            int dh = 200; // Make it at most 200 pixels tall 
             
            BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options(); 
            bmpFactoryOptions.inJustDecodeBounds = true; 
            Bitmap bmp = null; 
            try { 
                bmp = BitmapFactory.decodeStream( 
                        getContentResolver().openInputStream(imageFileUri), null, bmpFactoryOptions); 
            } catch (FileNotFoundException e) { 
                e.printStackTrace(); 
            } 
            int heightRatio = (int) Math.ceil(bmpFactoryOptions.outHeight/(float)dh); 
            int widthRatio = (int) Math.ceil(bmpFactoryOptions.outWidth/(float)dw); 
             
            Log.v("HEIGHTRATIO", "" + heightRatio); 
            Log.v("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; 
            try { 
                bmp = BitmapFactory.decodeStream( 
                        getContentResolver().openInputStream(imageFileUri), null, bmpFactoryOptions); 
            } catch (FileNotFoundException e) { 
                e.printStackTrace(); 
                Log.v("ERROR", e.toString()); 
            } 
             
            // Display it 
            returnedImageView.setImageBitmap(bmp); 
        } 
    } 

2)使用MediaStore來檢索圖像數據
MediaStore,跟所有的content provider一樣使用類似於數據庫操作的方式來檢索數據。從指定的Uri中選擇數據記錄,之後通過Cursor對象來對結果進行迭代處理。
首先需要創建一個字符串數組來表示希望返回的列類型,MediaStore中圖像數據的標準列類型在MediaStore.Images.Media類中:
[java]
String[] columns =  
    {Media.DATA, Media._ID, Media.TITLE, Media.DISPLAY_NAME}; 

執行實際的查詢操作使用Activity的managedQuery函數,第一個參數是URI,第二個參數是列名組成的字符串數組,第三個參數是WHERE語句,後面跟的參數是WHERE包含的參數,最後一個參數是ORDER BY語句:
[java]
long oneHourAgo = System.currentTimeMillis()/1000 – (60*60); 
String[] whereValues = {"" + oneHourAgo}; 
// 指定返回結果的列 
String[] columns = {Media.DATA, Media._ID, Media.TITLE, Media.DISPLAY_NAME, Media.DATE_ADDED}; 
// 獲得遊標 
Cursor cursor = managedQuery(Media.EXTERNAL_CONTENT_URI, columns, Media.DATE_ADDED + " > ?",whereValues, Media.DATE_ADDED + " ASC"); 
// 返回指定列的索引 
int displayColumnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); 
// 移到遊標的開始處 
if (cursor.moveToFirst()) { 
    String displayName = cursor.getString(displayColumnIndex); 

完整的例子如下所示,先是layout/main.xml文件:
[html]
<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:orientation="vertical" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent" 
    > 
<ImageButton  
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
    android:id="@+id/ImageButton"/> 
<TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:id="@+id/TitleTextView" 
    android:text="Image Title"/> 
</LinearLayout> 

Java代碼如下:
[java]
package hust.iprai.asce1885.promedia; 
 
import android.app.Activity; 
import android.database.Cursor; 
import android.graphics.Bitmap; 
import android.graphics.BitmapFactory; 
import android.os.Bundle; 
import android.provider.MediaStore; 
import android.provider.MediaStore.Images.Media; 
import android.util.Log; 
import android.view.View; 
import android.view.View.OnClickListener; 
import android.widget.ImageButton; 
import android.widget.TextView; 
 
public class MediaStoreGallery extends Activity { 
 
    public final static int DISPLAYWIDTH = 200; 
    public final static int DISPLAYHEIGHT = 200; 
     
    TextView titleTextView; 
    ImageButton imageButton; 
     
    Cursor cursor; 
    Bitmap bmp; 
    String imageFilePath; 
    int fileColumn; 
    int titleColumn; 
    int displayColumn; 
     
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.main); 
         
        titleTextView = (TextView) findViewById(R.id.TitleTextView); 
        imageButton = (ImageButton) findViewById(R.id.ImageButton); 
         
        String[] columns = {Media.DATA, Media._ID, Media.TITLE, Media.DISPLAY_NAME}; 
        cursor = managedQuery(Media.EXTERNAL_CONTENT_URI, columns, null, null, null); 
         
        // 註意:Media.DATA是MediaStore.Images.Media.DATA的縮寫 
        fileColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); 
        titleColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.TITLE); 
        displayColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME); 
         
        if (cursor.moveToFirst()) { 
            titleTextView.setText(cursor.getString(titleColumn)); 
             
            imageFilePath = cursor.getString(fileColumn); 
            bmp = getBitmap(imageFilePath); 
             
            // Display it 
            imageButton.setImageBitmap(bmp); 
        } 
         
        imageButton.setOnClickListener(new OnClickListener() { 
 
            public void onClick(View v) { 
                if (cursor.moveToNext()) { 
                    titleTextView.setText(cursor.getString(displayColumn)); 
                     
                    imageFilePath = cursor.getString(fileColumn); 
                    bmp = getBitmap(imageFilePath); 
                    imageButton.setImageBitmap(bmp); 
                } 
            } 
             
        }); 
    } 
     
    private Bitmap getBitmap(String imageFilePath) { 
        // Load up the image's dimensions not the image itself 
        BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options(); 
        bmpFactoryOptions.inJustDecodeBounds = true; 
        Bitmap bmp = BitmapFactory.decodeFile(imageFilePath, bmpFactoryOptions); 
         
        int heightRatio = (int) Math.ceil(bmpFactoryOptions.outHeight/(float)DISPLAYHEIGHT); 
        int widthRatio = (int) Math.ceil(bmpFactoryOptions.outWidth/(float)DISPLAYWIDTH); 
         
        Log.v("HEIGHTRATIO", "" + heightRatio); 
        Log.v("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) { 
                bmpFactoryOptions.inSampleSize = heightRatio; 
            } else { 
                bmpFactoryOptions.inSampleSize = widthRatio; 
            } 
        } 
         
        // Decode it for real 
        bmpFactoryOptions.inJustDecodeBounds = false; 
        bmp = BitmapFactory.decodeFile(imageFilePath, bmpFactoryOptions); 
         
        return bmp; 
    } 

2)內部元數據
EXIF,可交換圖像文件格式(Exchangeable Image File Format),是將元數據保存到圖像文件裡的標準格式。它的數據存儲與JPEG格式是完全相同的,它就是在JPEG格式頭部插入瞭數碼照片的拍攝信息。
EXIF數據中包含很多與圖像拍攝緊密相關的技術參數,例如曝光時間ExposureTime和快門速度ShutterSpeedValue等。還有一些參數是我們可以在後續進行填充或修改的,例如:
UserComment: 用戶評論
ImageDescription:圖像的描述
Artist:圖像的創建者或者拍攝者
Copyright:版權
Software:創建圖像使用的軟件
Android提供瞭方便的接口ExifInterface來讀寫EXIF數據:
[java]
ExifInterface ei = new ExifInterface(imageFilePath); 
String imageDescription = ei.getAttribute("ImageDescription"); 
if (null != imageDescription) { 
        Log.v("EXIF", imageDescription); 

保存EXIF數據到圖像文件中的代碼片段如下:
[java]
ExifInterface ei = new ExifInterface(imageFilePath); 
ei.setAttribute("ImageDescription", "ASCE1885"); 

摘自 ASCE1885的專欄

發佈留言