存储BITMAP到文件

本文介绍了如何使用GDI将位图对象保存为BMP文件。位图文件由文件表头、信息表头、调色板(可选)和位图图素位组成。通过填充BITMAPINFOHEADER结构并使用GetDIBits获取位图数据,然后创建文件并写入文件表头和位图信息,实现位图到文件的转换。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

GDI中位图对象是很常见的GDI对象,但是无论是SDK,还是MFC都没有提供现在 的函数或是方法来将一个位图对象保存为一个BMP文件,这里介绍一下保存方法。
位图文件格式:
DIB 文件有四个主要部分:
文件表头 (BITMAPFILEHEADER)
信息表头  (BITMAPINFOHEADER)
调色板(不一定有) 
位图图素位
而一个位图对象和上述唯一不同在于它没有文件表头。
相关数据结构:
(1)文件表头
typedef struct tagBITMAPFILEHEADER { 
  WORD    bfType; //BMP
文件类型,总是字符
BM
,十六进制为
0x4d42
 DWORD   bfSize; //BMP
文件大小,包含这个结构在内。
 WORD    bfReserved1; 
  WORD    bfReserved2; //
以上均保留为
0
 DWORD   bfOffBits; //
是一个偏移量,指出了文件中图素位开始位置的字节偏移量
} BITMAPFILEHEADER, *PBITMAPFILEHEADER; 

2
)信息表头
typedef struct tagBITMAPINFOHEADER{
 DWORD biSize; //结构的大小
 LONG   biWidth; // 位图的宽度
 LONG   biHeight; // 位图的高度
 WORD   biPlanes; // 必须是1
 WORD   biBitCount; // 指出每一个像素要用的bit位。
 DWORD biCompression; // 指出是否是压缩的,以及压缩方式
 DWORD biSizeImage; // 指出图像的尺寸
 LONG   biXPelsPerMeter; // 水平基线
 LONG   biYPelsPerMeter; // 坚直基线
 DWORD biClrUsed; // 被用的颜色数
 DWORD biClrImportant; // 重要的颜色数
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;

3
)调色板结构:
typedef struct tagRGBQUAD // rgb
{
           BYTE rgbBlue ;     // blue level
           BYTE rgbGreen ;    // green level
           BYTE rgbRed ;      // red level
           BYTE rgbReserved ; // = 0
}RGBQUAD ;
注意这个结构应该是一个数组,在256色及以下的BMP文件中存在,数组的长度关键看颜色数。
BITMAP定义了一个位图的类型、长度、宽度、颜色格式等,这个结构一般用GetObject来获得。定义如下:typedef struct tagBITMAP {
 LONG   bmType; // 类型,不过总是为0
 LONG   bmWidth; // 宽度,总是大于0
 LONG   bmHeight; // 高度,总是大于0
 LONG   bmWidthBytes; //MSDN 上解释说是指定每一个扫描行的字节数。
 WORD   bmPlanes; // 指定调色板数目
 WORD   bmBitsPixel; // 指示一个像素所要求的byte位
 LPVOID bmBits; // 指定一个数组指针,这个数组大约应该是保存位图数据的。
} BITMAP, *PBITMAP
一个位图对象也就是存在内存中的位图,它与存在硬盘上的BMP文件相比,唯一的区别就是它没有BITMAPFILEHEADER这个文件信息 头,其余部分是完全相同的,所以我们要做的就是先构造一个文件信息头,写入文件中,然后将内存中的位图写入文件。
       源代码如下:(只写主要部分)
WORD wbitsCount;//位图中每个像素所占字节数。
       DWORD dwpalettelsize=0;// 调色板大小
       DWORD dwbmdibitsize,dwdibsize,dwwritten;
       BITMAP bitmap;// 定义了位图的各种的信息。
       BITMAPFILEHEADER bmfhdr;// 定义了大小、类型等BMP文件的信息。
       BITMAPINFOHEADER bi;
       LPBITMAPINFOHEADER lpbi;
       HANDLE fh,fdib ;
GetObject(hBitmap,sizeof(BITMAP),(void *)&bitmap);//得到BITMAP结构。
       // 以下代码是用BITMAP的信息填充BITMAPINFOHEADER结构
       wbitsCount=bitmap.bmBitsPixel;
bi.biSize=sizeof(BITMAPINFOHEADER);
       bi.biWidth=bitmap.bmWidth;
       bi.biHeight=bitmap.bmHeight;
       bi.biPlanes=1;
       bi.biBitCount= bitmap.bmBitsPixel ;
       bi.biClrImportant=0;
       bi.biClrUsed=0;
       bi.biCompression=BI_RGB;
       bi.biSizeImage=0;
       bi.biYPelsPerMeter=0;
       bi.biXPelsPerMeter=0;
//以下代码是获取调色板的长度,调色板现在的用处很少,因为256色的位图已经不多了。
       if(wbitsCount<=8)
              dwpalettelsize=(1<<wbitsCount)*sizeof(RGBQUAD);
//计算位图的大小,并分配相应的内存空间,注意的是没有分配BITMAPFILEHEADER。
       dwbmdibitsize=((bitmap.bmWidth*wbitsCount+31)/8)*bitmap.bmHeight;
       fdib=GlobalAlloc(GHND,dwbmdibitsize+dwpalettelsize+sizeof(BITMAPINFOHEADER));
       lpbi=(LPBITMAPINFOHEADER)::GlobalLock(fdib);
       *lpbi=bi;// 将bi中的数据写入分配的内存中。
       hdc=::GetDC(NULL);
       GetDIBits(hdc,hBitmap,0,(UINT)bitmap.bmHeight,(LPSTR)lpbi+sizeof(BITMAPINFOHEADER)+dwpalettelsize,(BITMAPINFO *)lpbi,DIB_RGB_COLORS);
/*GetDIBits是最重要的函数,真正获得位图数据的工作就由它完成,它第一个参数为HDC,第二个参数为位图句柄,第三个参数为扫描行 的开始行,一般为0,第四个为结束行,一般就是高度,第四个参数最重要,它表示接收数据的起始地址,这个地址一般是在调色板之后。第五个参数指的是接收 BITMAPINFO结构的地址,这个结构上面没有写,它其实就是BITMAPINFO结构加上调色板信息。最后一个参数是格式。一般是 DIB_RGB_COLORS*/
//创建文件以及文件信息头
fh=CreateFile(FileName,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN,NULL);
       if(fh==INVALID_HANDLE_VALUE)
              return FALSE;
bmfhdr.bfType=0x4d42;//BMP类型,一定要这样写
       dwdibsize=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+dwbmdibitsize+dwpalettelsize;// 文 件总长,由几个部分组成
       bmfhdr.bfSize=dwdibsize;
       bmfhdr.bfReserved1=0;
       bmfhdr.bfReserved2=0;
bmfhdr.bfOffBits=(DWORD)sizeof(BITMAPFILEHEADER)+ (DWORD)sizeof(BITMAPINFOHEADER)+dwpalettelsize;//位图数据相对于文件头的偏移量
//将文件信息头写入文件
WriteFile(fh,(LPSTR)&bmfhdr,sizeof(BITMAPFILEHEADER),&dwwritten,NULL);
//将数据写入文件,包含BITMAPINFO结构、调色板、数据
WriteFile(fh,(LPSTR)lpbi,dwdibsize,&dwwritten,NULL);
//关闭相关句柄
::GlobalUnlock(fdib);
       ::GlobalFree(fdib);
       ::CloseHandle(fh);
       return TRUE;
<think>我们正在讨论如何将Bitmap保存为文件。根据引用[2]和[3],我们可以知道Bitmap是安卓中表示图片的一种方式,并且有方法将其保存为文件。 引用[3]中提供了一个代码示例,展示了如何将Bitmap保存为JPEG格式的图片文件。 步骤: 1. 检查Bitmap是否为null。 2. 确定保存的目录路径,如果目录不存在则创建。 3. 创建文件输出流,将Bitmap压缩为指定格式(如JPEG)并写入文件。 4. 关闭输出流,处理异常。 但是,我们需要注意以下几点: - 需要处理存储权限(对于Android 6.0及以上,需要动态请求写外部存储的权限)。 - 压缩质量(第二个参数)可以调整,范围是0-100,100表示最高质量。 - 保存文件时要注意文件名和格式。 下面是一个更完整的示例,包括权限处理(在代码中不会直接处理权限,但需要提醒)和保存方法。 我们将创建一个工具类,包含一个静态方法用于保存Bitmap到指定目录,并返回保存的文件路径(如果成功)或null(如果失败)。 注意:由于Android 10及以上版本的作用域存储(Scoped Storage),我们需要使用公共目录(如Pictures目录)或者应用私有目录。 这里提供两种方式: 方式1:保存到公共目录(需要权限,且Android 10及以上可能需要使用MediaStore) 方式2:保存到应用私有目录(不需要权限,但其他应用可能无法直接访问) 我们先展示保存到应用私有目录的方法(简单且不需要请求权限),然后再展示保存到公共图片目录的方法(需要权限,且不同版本处理不同)。 方法一:保存到应用私有目录(内部存储) 例如:/data/data/<package_name>/files/Pictures 或者使用外部私有目录(Context.getExternalFilesDir()) 方法二:保存到公共目录(如相册),使用MediaStore(适用于Android 10及以上)或直接使用路径(Android 9及以下)。 由于问题中没有特别指明,我们先提供一种通用的方法(使用外部私有目录,不需要权限),然后再提供保存到公共目录的示例(需要权限,且需要处理不同版本)。 首先,保存到外部私有目录(该目录在应用卸载时会被删除,但不需要权限): 步骤: 1. 获取外部私有目录下的Pictures目录(或者自定义目录)。 2. 创建目录(如果不存在)。 3. 创建文件,将Bitmap写入。 然后,保存到公共目录(需要权限,且Android 10及以上使用MediaStore): 步骤: 1. 检查权限(在调用方法前确保有写外部存储的权限)。 2. 如果是Android 10及以上,使用MediaStore.Images.Media插入一条记录,然后通过ContentResolver打开输出流写入。 3. 如果是Android 9及以下,使用Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)获取路径,然后保存。 由于从Android 10开始,直接使用路径访问公共目录已被废弃,因此我们使用MediaStore方式。 为了简化,我们分别实现两个方法。 我们先从简单的方法(私有目录)开始。 示例代码(保存到外部私有目录): ```java import android.content.Context; import android.graphics.Bitmap; import android.os.Environment; import android.widget.Toast; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; public class BitmapUtils { /** * 将Bitmap保存到外部私有目录的Pictures子目录 * @param context 上下文 * @param bitmap 要保存的Bitmap * @param filename 文件名(不含扩展名) * @return 保存的文件路径,如果失败则返回null */ public static String saveBitmapToPrivateStorage(Context context, Bitmap bitmap, String filename) { if (bitmap == null) { return null; } // 获取外部私有存储的Pictures目录 File directory = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES); if (directory == null) { // 如果获取不到,尝试创建目录 directory = new File(context.getExternalFilesDir(null), "Pictures"); if (!directory.exists()) { directory.mkdirs(); } } // 创建文件 File imageFile = new File(directory, filename + ".jpg"); FileOutputStream fos = null; try { fos = new FileOutputStream(imageFile); // 压缩为JPEG格式,质量100 bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos); fos.flush(); return imageFile.getAbsolutePath(); } catch (Exception e) { e.printStackTrace(); return null; } finally { if (fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } } } ``` 保存到公共目录(兼容Android 10及以上): 注意:需要权限 WRITE_EXTERNAL_STORAGE(对于Android 10以下)和Android 10及以上不需要权限(但需要声明),但实际保存到公共目录需要使用MediaStore,所以不需要权限(因为MediaStore会提示用户)。 但是,在Android 10及以上,我们使用MediaStore,不需要权限,因为通过MediaStore插入内容会由系统授权。在Android 9及以下,我们仍然需要权限。 因此,我们建议在调用保存到公共目录的方法前,对于Android 9及以下检查权限。 以下代码演示了如何保存到公共目录(使用MediaStore,兼容Android 10及以上,对于Android 9及以下使用传统方式): ```java import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.graphics.Bitmap; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.provider.MediaStore; import android.widget.Toast; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.text.SimpleDateFormat; import java.util.Locale; public class BitmapUtils { /** * 保存Bitmap到公共图片目录(相册) * @param context 上下文 * @param bitmap 要保存的Bitmap * @param displayName 显示名称(不含扩展名) * @return 保存的Uri,如果失败则返回null */ public static Uri saveBitmapToPublicStorage(Context context, Bitmap bitmap, String displayName) { if (bitmap == null) { return null; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Android 10及以上使用MediaStore ContentResolver resolver = context.getContentResolver(); ContentValues contentValues = new ContentValues(); contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName + ".jpg"); contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg"); contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES); Uri imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues); if (imageUri == null) { return null; } try (OutputStream outputStream = resolver.openOutputStream(imageUri)) { if (outputStream != null) { bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream); return imageUri; } } catch (IOException e) { if (imageUri != null) { // 如果失败,删除已创建的条目 resolver.delete(imageUri, null, null); } e.printStackTrace(); } return null; } else { // Android 9及以下 // 检查权限(这里不处理权限,调用者需要确保有权限) File picturesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); if (!picturesDir.exists()) { picturesDir.mkdirs(); } File imageFile = new File(picturesDir, displayName + ".jpg"); try (FileOutputStream fos = new FileOutputStream(imageFile)) { bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos); // 通知媒体库更新 context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(imageFile))); return Uri.fromFile(imageFile); } catch (IOException e) { e.printStackTrace(); return null; } } } } ``` 注意:在保存到公共目录时,对于Android 9及以下,我们使用了发送广播通知媒体库扫描该文件,这样相册中就能立即看到。 使用以上方法时,需要根据需求选择保存到私有目录还是公共目录。 另外,生成文件名时,为了避免重复,可以使用时间戳: ```java String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date()); String fileName = "IMG_" + timeStamp; ``` 最后,记得在AndroidManifest.xml中添加权限(针对Android 9及以下): ```xml <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> ``` 因为从Android 10开始,我们使用MediaStore不需要这个权限,所以设置maxSdkVersion为28(即Android 9)。 总结: 将Bitmap保存为文件,主要步骤是: 1. 选择保存位置(私有目录或公共目录)。 2. 创建文件或通过MediaStore获取Uri。 3. 将Bitmap压缩写入输出流。 4. 关闭流,处理异常。 根据引用[3]的代码,我们进行了扩展和优化。 现在,我们可以回答用户的问题:在Android开发中,可以通过上述两种方法将Bitmap保存为文件。 生成相关问题: 1. 如何动态申请外部存储权限? 2. 在保存Bitmap时,如何选择图片格式(JPEG、PNG)? 3. 如何将Bitmap保存为PNG格式(支持透明背景)? 4. 保存Bitmap时,如何处理大图避免OOM? 5. 如何将保存的图片显示在相册中?</think>### 在 Android 中将 Bitmap 保存为文件的完整指南 #### 方法一:保存到应用私有目录(无需权限) ```java public static String saveBitmapToPrivate(Context context, Bitmap bitmap, String fileName) { try (FileOutputStream fos = context.openFileOutput(fileName, Context.MODE_PRIVATE)) { bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos); return context.getFilesDir() + "/" + fileName; // 返回文件路径 } catch (Exception e) { e.printStackTrace(); return null; } } ``` **特点**: - 无需存储权限 - 文件路径:`/data/data/<包名>/files/` - 应用卸载时自动删除[^3] --- #### 方法二:保存到公共存储(需要权限) ```java public static String saveBitmapToPublic(Bitmap bitmap, String folderName, String fileName) { // 1. 创建目标目录 File dir = new File(Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES), folderName); if (!dir.exists()) dir.mkdirs(); // 2. 创建文件并保存 File file = new File(dir, fileName + ".jpg"); try (FileOutputStream fos = new FileOutputStream(file)) { bitmap.compress(Bitmap.CompressFormat.JPEG, 85, fos); // 质量85% fos.flush(); // 3. 通知系统更新相册 MediaScannerConnection.scanFile( context, new String[]{file.getAbsolutePath()}, new String[]{"image/jpeg"}, null ); return file.getAbsolutePath(); // 返回完整路径 } catch (Exception e) { e.printStackTrace(); return null; } } ``` **关键步骤**: 1. 添加权限到 `AndroidManifest.xml`: ```xml <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> <!-- Android 10+ 使用作用域存储 --> ``` 2. Android 10+ 需在代码中动态请求权限 3. 文件路径:`/Pictures/<folderName>/`[^1] --- #### 格式选择与优化 | 格式 | 方法参数 | 特点 | 适用场景 | |--------------|------------------------------|--------------------------|-----------------------| | **JPEG** | `CompressFormat.JPEG` | 有损压缩,文件小 | 照片类图像 | | **PNG** | `CompressFormat.PNG` | 无损压缩,支持透明度 | 图标/带透明度的图像 | | **WEBP** | `CompressFormat.WEBP` | 高质量+小体积 | Android 4.0+ 通用场景 | ```java // WEBP 示例(质量80%) bitmap.compress(Bitmap.CompressFormat.WEBP, 80, fos); ``` --- #### 注意事项 1. **权限处理**: ```java // Android 6.0+ 动态请求权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE); } ``` 2. **大图处理**: ```java // 避免OOM:先压缩再保存 Bitmap resized = Bitmap.createScaledBitmap(originalBitmap, newWidth, newHeight, true); ``` 3. **文件名规范**: ```java // 使用时间戳生成唯一文件名 String fileName = "IMG_" + System.currentTimeMillis() + ".jpg"; ``` --- ### 完整工作流程 1. 获取或生成 `Bitmap` 对象 2. 选择存储位置(私有/公共) 3. 选择图片格式和质量参数 4. 创建目标文件路径 5. 执行压缩保存操作 6. 通知媒体库更新(公共存储) 7. 处理异常和权限问题 > 示例:保存截图到相册 > ```java > // 1. 获取屏幕截图 > View rootView = getWindow().getDecorView().getRootView(); > rootView.setDrawingCacheEnabled(true); > Bitmap screenshot = Bitmap.createBitmap(rootView.getDrawingCache()); > rootView.setDrawingCacheEnabled(false); > > // 2. 保存到公共相册 > String path = saveBitmapToPublic(screenshot, "MyAppScreenshots", "screenshot"); > Toast.makeText(this, "已保存到: " + path, Toast.LENGTH_SHORT).show(); > ``` 通过合理选择存储策略和压缩参数,可平衡图像质量和存储效率[^2][^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值