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ò)傳輸。
本項目的目標是:
在 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)通道用于透明度
無損壓縮,文件較大
適合圖標、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)建一個目標 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)建目標 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)建目標 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. 準備輸出文件 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 中展示,供用戶確認。convertPngToJpg(...)
打開 PNG 輸入流,解碼為源 Bitmap;
創(chuàng)建目標 Bitmap(ARGB_8888);
用 Canvas 繪制白色背景,再繪制源 Bitmap,消除透明區(qū)域的黑底問題;
準備輸出文件路徑(App 私有外部存儲),創(chuàng)建目錄;
調(diào)用
compress(JPEG, 90, fos)
將目標 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)存泄漏。
可擴展功能
批量轉(zhuǎn)換:遍歷多張圖片,使用線程池并發(fā)處理;
動態(tài)壓縮質(zhì)量:在 UI 上添加 SeekBar,用戶可調(diào)整質(zhì)量值;
其他格式支持:擴展到 PNG→WEBP、BMP→JPG 等;
保存到系統(tǒng)相冊:通過 MediaStore API 將 JPG 添加到相冊;
透明背景自定義:允許用戶選擇背景色,而非固定白色;
錯誤反饋:增強異常捕獲,向用戶展示詳細錯誤原因;
進度展示:批量轉(zhuǎn)換時顯示進度條或通知。
學(xué)習(xí)要點
深入理解 Android 圖像處理流程;
掌握文件存儲最佳實踐;
熟悉常見 Bitmap 配置與內(nèi)存優(yōu)化技巧;
掌握使用 Canvas 對位圖進行二次加工。
以上就是Android實現(xiàn)png轉(zhuǎn)jpg圖片的方法的詳細內(nèi)容,更多關(guān)于Android png轉(zhuǎn)jpg圖片的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
利用Kotlin的協(xié)程實現(xiàn)簡單的異步加載詳解
這篇文章主要給大家介紹了關(guān)于利用Kotlin的協(xié)程實現(xiàn)簡單的異步加載的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-03-03Android開發(fā)中使用顏色矩陣改變圖片顏色,透明度及亮度的方法
這篇文章主要介紹了Android開發(fā)中使用顏色矩陣改變圖片顏色,透明度及亮度的方法,涉及Android針對圖片的讀取、運算、設(shè)置等相關(guān)操作技巧,需要的朋友可以參考下2017-10-10android開發(fā)基礎(chǔ)教程—文件存儲功能實現(xiàn)
文件存儲功能在實現(xiàn)數(shù)據(jù)讀寫時會頻繁使用到,接下來介紹文件存儲功能的實現(xiàn),感興趣的朋友可以了解下2013-01-01Android使用TextInputLayout創(chuàng)建登陸頁面
這篇文章主要為大家詳細介紹了Android使用TextInputLayout創(chuàng)建登陸頁面,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-10-10Android保存的文件顯示到文件管理的最近文件和下載列表中的方法
這篇記錄的是Android中如何把我們往存儲中寫入的文件,如何顯示到文件管理的下載列表、最近文件列表中,需要的朋友可以參考下2020-01-01Android中將一個圖片切割成多個圖片的實現(xiàn)方法
有種場景,我們想將一個圖片切割成多個圖片。比如我們在開發(fā)一個拼圖的游戲,就首先要對圖片進行切割2013-05-05