Android實現(xiàn)png轉(zhuǎn)jpg圖片的方法
一、項目概述
在 Android 應(yīng)用開發(fā)中,圖像處理是非常常見的需求。PNG 與 JPG 是兩種最常見的圖片格式:
PNG(Portable Network Graphics)支持透明通道、無損壓縮,適合需要保持圖像質(zhì)量和透明度的場景;
JPG(Joint Photographic Experts Group)采用有損壓縮,不支持透明,文件體積更小,適合照片展示與網(wǎng)絡(luò)傳輸。
本項目的目標(biāo)是:
在 Android 平臺上實現(xiàn)一個 PNG 轉(zhuǎn) JPG 的模塊,用戶可以從相冊或文件中選取 PNG 圖片,一鍵將其轉(zhuǎn)換為 JPG 并保存到本地。
核心功能點:
從系統(tǒng)相冊或文件管理器中選擇 PNG 文件
讀取并解碼為 Bitmap
處理透明通道(填充背景)
以 JPG 格式壓縮并寫入文件
返回保存路徑,并可在界面中預(yù)覽
二、技術(shù)背景與相關(guān)知識
要完成上述功能,需要掌握以下技術(shù)點:
1. 圖片格式基礎(chǔ)
PNG:
支持 RGBA 四通道,其中 A(Alpha)通道用于透明度
無損壓縮,文件較大
適合圖標(biāo)、UI 元素、需要透明的場景
JPG:
只支持 RGB 三通道,不支持透明
有損壓縮,可調(diào)壓縮質(zhì)量(0–100)
文件體積小,適合照片、展示圖
2. Android Bitmap 原理
Bitmap 是 Android 處理圖像的核心類,封裝了像素數(shù)據(jù)。
配置選項:
ARGB_8888:32 位,含透明,質(zhì)量高RGB_565:16 位,不含透明,內(nèi)存占用更少
內(nèi)存管理:
大圖容易導(dǎo)致 OOM,需要適當(dāng)縮放或使用
inSampleSize使用完畢需調(diào)用
bitmap.recycle()釋放 native 內(nèi)存
3. Canvas 與 Paint
Canvas:在 Bitmap 上繪制圖形或其他 Bitmap
Paint:控制繪制效果,如抗鋸齒、顏色濾鏡等
繪制流程:
創(chuàng)建一個目標(biāo) Bitmap
用 Canvas 綁定該 Bitmap
通過 Canvas.drawXXX() 方法繪制
4. Android 文件存儲
內(nèi)部存儲:私有,不需權(quán)限
外部存儲私有目錄(
getExternalFilesDir()):無需額外權(quán)限,卸載時會被清除外部公有目錄 / MediaStore:需運行時權(quán)限或使用 SAF
本項目使用外部私有目錄,避免申請繁瑣權(quán)限。
5. Uri 與 ContentResolver
系統(tǒng)選擇器返回的不是文件路徑,而是 Uri
需通過
context.getContentResolver().openInputStream(uri)獲取InputStream
6. Java I/O 與異常處理
使用
try–catch–finally結(jié)構(gòu)保證流關(guān)閉捕獲
IOException
7. 動態(tài)權(quán)限(API 23+)
若使用公有外部存儲,需要申請
WRITE_EXTERNAL_STORAGE本例中使用私有目錄,無需動態(tài)權(quán)限
三、項目實現(xiàn)思路
UI 交互
主界面提供“選擇 PNG”“開始轉(zhuǎn)換”按鈕與 ImageView
選擇圖片
使用
Intent.ACTION_PICK或Intent.ACTION_OPEN_DOCUMENT限定類型為image/png
加載 Bitmap
通過
ContentResolver打開 Uri 的InputStream使用
BitmapFactory.decodeStream()解碼
創(chuàng)建目標(biāo) Bitmap
調(diào)用
Bitmap.createBitmap(width, height, Config.ARGB_8888)
處理透明通道
在 Canvas 上先繪制純白背景,再繪制原 PNG
壓縮與保存
使用
Bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream)保存到
getExternalFilesDir("converted")
資源回收
調(diào)用
bitmap.recycle()并關(guān)閉流
結(jié)果反饋
返回文件路徑,UI 層顯示預(yù)覽并提示用戶
四、完整代碼(整合且注釋詳盡)
package com.example.pngtojpgconverter;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class MainActivity extends Activity {
private static final int REQUEST_PICK_PNG = 1001;
private ImageView imageView;
private Button btnSelect, btnConvert;
private Uri selectedUri;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 綁定 UI 控件
imageView = findViewById(R.id.imageView);
btnSelect = findViewById(R.id.btnSelect);
btnConvert = findViewById(R.id.btnConvert);
// 選擇 PNG 按鈕點擊
btnSelect.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 啟動系統(tǒng)相冊,僅顯示 PNG
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/png");
startActivityForResult(intent, REQUEST_PICK_PNG);
}
});
// 轉(zhuǎn)換按鈕點擊
btnConvert.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 確保已選圖片
if (selectedUri == null) {
Toast.makeText(MainActivity.this, "請先選擇 PNG 圖片", Toast.LENGTH_SHORT).show();
return;
}
// 調(diào)用工具類執(zhí)行轉(zhuǎn)換
String jpgPath = ImageConverter.convertPngToJpg(MainActivity.this, selectedUri);
if (jpgPath != null) {
// 轉(zhuǎn)換成功:提示并預(yù)覽
Toast.makeText(MainActivity.this, "轉(zhuǎn)換成功: " + jpgPath, Toast.LENGTH_LONG).show();
imageView.setImageURI(Uri.fromFile(new File(jpgPath)));
} else {
// 轉(zhuǎn)換失敗:提示
Toast.makeText(MainActivity.this, "轉(zhuǎn)換失敗", Toast.LENGTH_SHORT).show();
}
}
});
}
// 處理選擇結(jié)果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_PICK_PNG && resultCode == RESULT_OK) {
selectedUri = data.getData();
imageView.setImageURI(selectedUri);
}
}
}
// 工具類:執(zhí)行 PNG 轉(zhuǎn) JPG
class ImageConverter {
private static final String TAG = "ImageConverter";
/**
* 將 PNG 圖像轉(zhuǎn)換為 JPG 并保存
*
* @param context 應(yīng)用上下文
* @param pngUri PNG 圖像的 Uri
* @return 返回 JPG 文件絕對路徑,失敗返回 null
*/
public static String convertPngToJpg(Context context, Uri pngUri) {
Bitmap srcBitmap = null;
Bitmap dstBitmap = null;
FileOutputStream fos = null;
try {
// 1. 打開 PNG 輸入流
InputStream is = context.getContentResolver().openInputStream(pngUri);
// 2. 解碼為 Bitmap
srcBitmap = BitmapFactory.decodeStream(is);
if (srcBitmap == null) {
Log.e(TAG, "解碼 PNG Bitmap 失敗");
return null;
}
// 3. 創(chuàng)建目標(biāo) Bitmap,使用 ARGB_8888 保留高質(zhì)量
dstBitmap = Bitmap.createBitmap(
srcBitmap.getWidth(),
srcBitmap.getHeight(),
Bitmap.Config.ARGB_8888
);
// 4. 使用 Canvas 繪制:白底 + 原圖
Canvas canvas = new Canvas(dstBitmap);
canvas.drawColor(Color.WHITE); // 填充白色背景
Paint paint = new Paint();
paint.setAntiAlias(true); // 抗鋸齒
canvas.drawBitmap(srcBitmap, 0, 0, paint);
// 5. 準(zhǔn)備輸出文件
File outDir = context.getExternalFilesDir("converted");
if (outDir != null && !outDir.exists()) {
outDir.mkdirs();
}
File jpgFile = new File(outDir, "img_" + System.currentTimeMillis() + ".jpg");
fos = new FileOutputStream(jpgFile);
// 6. 壓縮為 JPG,質(zhì)量 90%
boolean ok = dstBitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos);
fos.flush();
if (ok) {
Log.i(TAG, "JPG 保存成功: " + jpgFile.getAbsolutePath());
return jpgFile.getAbsolutePath();
} else {
Log.e(TAG, "Bitmap.compress 返回 false");
return null;
}
} catch (IOException e) {
Log.e(TAG, "轉(zhuǎn)換異常: " + e.getMessage());
return null;
} finally {
// 7. 資源釋放
if (srcBitmap != null) srcBitmap.recycle();
if (dstBitmap != null) dstBitmap.recycle();
if (fos != null) {
try { fos.close(); } catch (IOException ignored) {}
}
}
}
}五、代碼解讀
onActivityResult(...)
處理系統(tǒng)返回的 PNG 文件 Uri,并在 ImageView 中展示,供用戶確認(rèn)。convertPngToJpg(...)
打開 PNG 輸入流,解碼為源 Bitmap;
創(chuàng)建目標(biāo) Bitmap(ARGB_8888);
用 Canvas 繪制白色背景,再繪制源 Bitmap,消除透明區(qū)域的黑底問題;
準(zhǔn)備輸出文件路徑(App 私有外部存儲),創(chuàng)建目錄;
調(diào)用
compress(JPEG, 90, fos)將目標(biāo) Bitmap 以 JPG 格式寫入文件;釋放 Bitmap 與關(guān)閉流。
六、項目總結(jié)與拓展
核心技術(shù)點回顧
Bitmap 解碼與創(chuàng)建
Canvas 繪圖與透明處理
Bitmap.compress 壓縮與文件寫入
Uri → InputStream → Bitmap 的流程
外部私有目錄的使用,避免權(quán)限復(fù)雜化
性能與穩(wěn)定性
使用
ARGB_8888保證質(zhì)量,必要時可改為RGB_565以節(jié)省內(nèi)存;對大圖可先按需縮放,避免 OOM;
確保在 finally 中釋放資源,防止內(nèi)存泄漏。
可擴(kuò)展功能
批量轉(zhuǎn)換:遍歷多張圖片,使用線程池并發(fā)處理;
動態(tài)壓縮質(zhì)量:在 UI 上添加 SeekBar,用戶可調(diào)整質(zhì)量值;
其他格式支持:擴(kuò)展到 PNG→WEBP、BMP→JPG 等;
保存到系統(tǒng)相冊:通過 MediaStore API 將 JPG 添加到相冊;
透明背景自定義:允許用戶選擇背景色,而非固定白色;
錯誤反饋:增強(qiáng)異常捕獲,向用戶展示詳細(xì)錯誤原因;
進(jìn)度展示:批量轉(zhuǎn)換時顯示進(jìn)度條或通知。
學(xué)習(xí)要點
深入理解 Android 圖像處理流程;
掌握文件存儲最佳實踐;
熟悉常見 Bitmap 配置與內(nèi)存優(yōu)化技巧;
掌握使用 Canvas 對位圖進(jìn)行二次加工。
以上就是Android實現(xiàn)png轉(zhuǎn)jpg圖片的方法的詳細(xì)內(nèi)容,更多關(guān)于Android png轉(zhuǎn)jpg圖片的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
NestedScrollView+Recyclerview下滑卡頓解決方法
本文為大家解決安卓開發(fā)時候NestedScrollView+Recyclerview下滑卡頓的問題,希望能夠幫助到你。2017-11-11
Android實現(xiàn)自定義的衛(wèi)星式菜單(弧形菜單)詳解
相信大家經(jīng)常在應(yīng)用中會看到衛(wèi)星菜單,那么這篇文章就來介紹在Android中如何實現(xiàn)自定義的衛(wèi)星式菜單(弧形菜單),有需要的可以參考學(xué)習(xí)。2016-08-08
Android控件Chronometer定時器的實現(xiàn)方法
這篇文章主要為大家詳細(xì)介紹了Android控件Chronometer定時器的實現(xiàn)方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11
Android 中 ActivityLifecycleCallbacks的實例詳解
這篇文章主要介紹了Android 中 ActivityLifecycleCallbacks的實例詳解的相關(guān)資料,希望通過本文大家能掌握這部分內(nèi)容,需要的朋友可以參考下2017-09-09
Flutter 系統(tǒng)是如何實現(xiàn)ExpansionPanelList的示例代碼
Flutter組件有一個很大的特色,那就是很多復(fù)雜的組件都是通過一個一個小組件拼裝而成的,今天就來說說系統(tǒng)的ExpansionPanelList是如何實現(xiàn)的,需要的朋友可以參考下2020-05-05
android中可以通過兩種方式調(diào)用接口發(fā)送短信
調(diào)用系統(tǒng)短信接口直接發(fā)送短信;調(diào)起系統(tǒng)發(fā)短信功能,本文將給出兩種方式的實現(xiàn)代碼,感興趣的朋友可以了解下,或許對你有所幫助2013-02-02

