Xin chào tất cả các bạn, hôm nay mình xin
chia sẽ với các bạn cách lấy hình ảnh thu nhỏ (thumbnail) và hình ảnh
đầy đủ sử dụng camera của thiết bị Android.
1. Vấn đề.
- Chúng ta thường sử dụng hai loại ảnh từ camera, đầu tiên là hình ảnh
thu nhỏ (thumbnail) với độ phân giải thấp và dĩ nhiên dung lượng cũng
thấp, ảnh này thường được sử dụng vào các view nhỏ để đại diện, nói có
vẻ khó hiểu nhưng rất gần gũi với chúng ta thôi đó là thư viện ảnh
(gallery) khi bạn mở lên thì sẽ có một danh sách các bức ảnh mà bạn có
trong thiết bị, mỗi bức ảnh nhỏ trong danh sách đó chính là thumbnail
image.

Như thế này chắc cũng các bạn đã hình dung ra thumbnail image là gì rồi
phải không ạ. Còn loại thứ hai chính là full image, đây chính là bức ảnh
đầy đủ mà các bạn có, nó có độ phân giải cao hơn và dĩ nhiên dung lượng
cũng lớn hơn rất nhiều so với thumbnail image. Tại sao mình lại đề cập
đến vấn đề này vì nghe có vẻ xử lí và hiển thị ảnh khá là đơn giản tuy
nhiên vấn đề không đơn giản như vậy, các bạn sẽ gặp rắc rối với nó đấy
ạ, rất nhiều bạn sẽ gặp lỗi OutOfMemmory đơn giản vì dung lượng của ảnh
là rất lớn và Android lại rất hạn chế trong việc xử lý các tệp lớn như
thế đó là lí do tại sao chúng ta lại có hai loại ảnh như vậy.
2. Giải pháp
2.1: Đầu tiên chúng ta phải chụp được ảnh đã.
static final int REQUEST_IMAGE_CAPTURE = 1;
private void dispatchTakePictureIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
}
}
Ở đây các bạn thấy chúng ta kiểm tra điều kiện
takePictureIntent.resolveActivity(getPackageManager()) != null để đảm bảo rằng app sẽ không bị crash khi bạn gọi
startActivityForResult mà không có ứng dụng nào xử lí. Vậy là đã chụp được ảnh rồi, giờ ta đến bước tiếp theo.
2.2 Lấy hình ảnh thu nhỏ (thumbnail)
- Khi bạn sử dụng Intent để chuoj ảnh như ở trên thì app camera sẽ trẻ
về cho bạn một fioe Bitmap nhỏ trong key "data" mà bạn có thể lấy được ở
trong
onActivityResult.
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
Bundle extras = data.getExtras();
Bitmap imageBitmap = (Bitmap) extras.get("data");
mImageView.setImageBitmap(imageBitmap);
}
}
- Mọi việc có vẻ khá là đơn giản, tuy nhiên đôi khi bạn sẽ cần ảnh
lớn, ví dụ như khi bạn làm app có chức năng xem ảnh hoặc đơn giản là bạn
cần up ảnh đó lên server chẳng hạn, thumbnail sẽ không đáp ứng được và
bạn sẽ phải lấy ảnh đầy đủ (full-sized image).
2.3 Lấy hình ảnh đầy đủ (full-sized image).
- Đầu tiên bạn cần phải xin quyền WRITE_EXTERNAL_STORAGE (bạn chỉ cần
xin quyền ghi thôi vì khi bạn xin quyền này thì đồng thời bạn cũng có
quyền đọc luôn rồi) , tại sao phải xin quyền này vì camera sẽ lưu cho
bạn ảnh đầy đủ nếu bạn cung cấp cho nó một tệp để lưu vào, và đương
nhiên kèm theo nó là một cái tên tệp hợp lệ.
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
...
</manifest>
Thông thường thì ảnh này sẽ được lưu trữ ở bộ nhớ ngoài chung (các
app khác cũng có thể lấy) thư mục thích hợp cho việc này được cung cấp
bởi
getExternalStoragePublicDirectory(). Tuy nhiên, nếu bạn muốn ảnh chỉ ở chế độ riêng tư cho ứng dụng của mình bạn có thể sử dụng thư mục được cung cấp bởi
getExternalFilesDir () khi bạn lưu trữ riêng tư như này thì khi bạn gỡ cài đặt ứng dụng các ảnh mà bạn lưu cũng sẽ bị xóa theo.
- Đầu tiên ta tạo File để lưu ảnh lại.
String mCurrentPhotoPath;
//Biến này để sau này bạn có thể tiện sử dụng ảnh.
private File createImageFile() throws IOException {
// Tạo tên file dựa vào nhãn thời gian.
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 */
);
// Lưu lại giá trị đường dẫn của ảnh vào biến mCurrentPhotoPath
mCurrentPhotoPath = image.getAbsolutePath();
return image;
}
- Sau khi đã tạo được File để lưu ảnh ta sử dụng Intent để chụp ảnh.
static final int REQUEST_TAKE_PHOTO = 1;
private void dispatchTakePictureIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
// Create the File where the photo should go
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
...
}
// Continue only if the File was successfully created
if (photoFile != null) {
Uri photoURI = FileProvider.getUriForFile(this,
"com.example.android.fileprovider",
photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
}
}
}
- Ở trên chắc các bạn đã thấy chúng ta lấy photoUri bằng cách sử dụng
FileProvider, việc chúng ta lấy uri này và tạo file như ở trên để
Android có thể biết được nơi để lưu ảnh.
- Tiếp theo chúng ta sẽ tạo content provider. Ở trong file Manifest các bạn khai báo thẻ providder
<application>
...
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.android.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"></meta-data>
</provider>
...
</application>
- Các bạn lưu ý cần set giá trị
authorities giống với giá trị mà các bạn sử dụng trong phương thức getUriForFile ban nãy mà các bạn đã viết ở trên.
- Trong thư mục res các bạn cần tạo một file resource
res/xml/file_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="Android/data/com.example.package.name/files/Pictures" />
</paths>
- Các bạn cần thay đổi
com.example.package.name thành tên package của ứng dụng của bạn.
- Rồi vậy là xong khá nhiều bước cầu kỳ, vậy thì tại sao chúng ta lại
phải làm như vậy, đó là tại vì khi bạn muốn lấy hình ảnh đầy đủ Android
sẽ không trả về cho bạn ảnh hoặc url của ảnh qua intent cho bạn đâu ạ,
mà bạn sẽ phải tự lấy bằng chính đường dẫn File mà các bạn tạo để lưu
file ảnh ở trên, đó là lí do chúng ta phải tự tạo file và lấy Filepath
cho việc này.
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
try {
switch (requestCode) {
case 0: {
if (resultCode == RESULT_OK) {
File file = new File(currentPhotoPath);
Bitmap bitmap = MediaStore.Images.Media
.getBitmap(context.getContentResolver(), Uri.fromFile(file));
if (bitmap != null) {
...Với file path mà các bạn đã có ở trên các bạn có thể lấy file, bitmap hay các định dạng khác mà bạn có thể dễ dàng ép kiểu sang. Ở đây mình lấy bitmap, bitmap này chính là bitmap của ảnh đầy đủ.
}
}
break;
}
}
} catch (Exception error) {
error.printStackTrace();
}
}
2.4 Xử lý ảnh đầy đủ (full-size image).
- Ok vậy là sau khi cố gắng để lấy được ảnh đầy đủ giờ chúng ta lại
chuẩn bị làm giảm chất lượng của ảnh đi ạ. Nghe có vẻ sai sai nhưng thực
ra như ban đầu mình đã trình bày file ảnh đầy đủ này là quá lớn đối với
android, và khi có nhiều ảnh thì xử lí sẽ là cả một vấn đề lớn.vì
android có bộ nhớ hạn chế. Bạn có thể giảm thiểu việc này bằng cách
chuyển file ảnh này thành một mảng các byte sao cho phù hợp, ở đây mình
xin giới thiệu hai cách đó là nén trực tiêp Bitmap và Scale image.
- Với cách nén Bitmap, các bạn sẽ nén bitmap của ảnh đầy đủ lại nhằm giảm kích cỡ.
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, bos)
- Các bạn lưu ý ở đây JPEG là định dạng mà các bạn muốn nén (mặc định
android đã cung cấp cho ta ảnh dạng jpeg để giữ cho ảnh dung lượng nhẹ
nên nếu các bạn mà xài .PNG ở đây thì không những không giảm mà kích cỡ
ảnh se tăng rất nhiều lần sau khi nén đấy ạ). Tham số thứ 2 là chất
lượng sau khi nén ở đây mình để 50 (một nửa) còn
bos chỉ đơn giản là mảng ByteArrayOutputStream()
mà mình muốn nén bitmao thành. Khi sử dụng cách này các bạn nên lưu ý
là file dữ liệu exif của ảnh sẽ bị mất thế nên nếu cần các bạn cần phải
lưu thông tin exif này trước khi nén và ghi lại sau khi nén xong.
- Cách thứ hai đó là scale kích cỡ của ảnh sao cho phù hợp với view mà chúng ta muốn hiển thi.
private void setPic() {
// Lấy kích thước của view
int targetW = mImageView.getWidth();
int targetH = mImageView.getHeight();
// Lấy kích thước của 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 file thành bitmap để set cho view
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = scaleFactor;
bmOptions.inPurgeable = true;
Bitmap bitmap = BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
mImageView.setImageBitmap(bitmap);
}
3. Tổng kết.
Trên đây là một vài chia sẻ của mình về việc lấy ảnh, xử lý ảnh trong
android, bài viết có thể có còn chỗ sai sót mong các bạn góp ý để bài
viết tốt hơn. Cảm ơn các bạn đã theo dõi bài viết của mình.
Comments
Post a Comment