C# 每像素1位大位图导致OutOfMemoryException
我想做的是从磁盘加载一个映像,并从中创建一个C# 每像素1位大位图导致OutOfMemoryException,c#,wpf,bitmap,out-of-memory,C#,Wpf,Bitmap,Out Of Memory,我想做的是从磁盘加载一个映像,并从中创建一个BitmapSource。 图像为14043px 9933px,为黑白(1bpp)。但是我遇到了一个OutOfMemoryException,因为下面的代码消耗了大约800MB的RAM 以下代码在我的特定文件的维度中生成一个ImageSource 我这样做是为了看看我是否可以在不使用磁盘上的实际文件的情况下让它工作 public System.Windows.Media.ImageSource getImageSource(){ int wi
BitmapSource
。图像为14043px 9933px,为黑白(1bpp)。但是我遇到了一个
OutOfMemoryException
,因为下面的代码消耗了大约800MB的RAM
以下代码在我的特定文件的维度中生成一个ImageSource
我这样做是为了看看我是否可以在不使用磁盘上的实际文件的情况下让它工作
public System.Windows.Media.ImageSource getImageSource(){
int width = 14043;
int height = 9933;
List<System.Windows.Media.Color> colors = new List<System.Windows.Media.Color>();
colors.Add(System.Windows.Media.Colors.Black);
colors.Add(System.Windows.Media.Colors.White);
BitmapPalette palette = new BitmapPalette(colors);
System.Windows.Media.PixelFormat pf = System.Windows.Media.PixelFormats.Indexed1;
int stride = width / pf.BitsPerPixel;
byte[] pixels = new byte[height * stride];
for (int i = 0; i < height * stride; ++i)
{
if (i < height * stride / 2)
{
pixels[i] = 0x00;
}
else
{
pixels[i] = 0xff;
}
}
BitmapSource image = BitmapSource.Create(
width,
height,
96,
96,
pf,
palette,
pixels,
stride);
return image;
}
public System.Windows.Media.ImageSource getImageSource(){
整数宽度=14043;
内部高度=9933;
列表颜色=新列表();
添加(System.Windows.Media.colors.Black);
添加(System.Windows.Media.colors.White);
BitmapPalette调色板=新的BitmapPalette(颜色);
System.Windows.Media.PixelFormat pf=System.Windows.Media.PixelFormats.Indexed1;
int步长=宽度/pf.BitsPerPixel;
字节[]像素=新字节[高度*步幅];
对于(int i=0;i<高度*步幅;++i)
{
如果(i<高度*步幅/2)
{
像素[i]=0x00;
}
其他的
{
像素[i]=0xff;
}
}
BitmapSource图像=BitmapSource.Create(
宽度,
高度,
96,
96,
爱国阵线,
调色板,
像素,
步幅);
返回图像;
}
根据我的计算,图像应该消耗大约16.7 MB。此外,在使用BitmapSource.create时,我无法指定缓存选项。但图像必须在加载时缓存。
此方法的返回值设置为图像控件的源
问题重新打开 @Clemens发布答案后,wich首先表现得非常好。我在检查我的TaskManager时注意到一个非常糟糕的行为。这是我正在使用的代码,它与@Clemens答案完全相同
public ImageSource getImageSource(){
var width = 14043;
var height = 9933;
var stride = (width + 7) / 8;
var pixels = new byte[height * stride];
for (int i = 0; i < height * stride; i++){
pixels[i] = 0xAA;
}
WriteableBitmap bitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.BlackWhite, null);
bitmap.WritePixels(new Int32Rect(0, 0, width, height), pixels, stride, 0);
bitmap.Freeze();
return bitmap;
}
公共ImageSource getImageSource(){
var宽度=14043;
变量高度=9933;
变量步长=(宽度+7)/8;
var像素=新字节[高度*步幅];
对于(int i=0;i<高度*步幅;i++){
像素[i]=0xAA;
}
WriteableBitmap位图=新的WriteableBitmap(宽度、高度、96、96、PixelFormats.BlackWhite、null);
WritePixels(新的Int32Rect(0,0,宽度,高度),像素,步长,0);
bitmap.Freeze();
返回位图;
}
在运行任何代码之前,我的taskmanager将显示以下内容:(1057 MB空闲)
启动应用程序后,结果表明,这种方法有一个非常高的内存使用峰值:(初始峰值后有497 MB可用空间)
我试了几次,发现@Clemens的常规可能不是问题所在。我将代码更改为:
private WriteableBitmap _writeableBitmap; //Added for storing the bitmap (keep it in scope)
public ImageSource getImageSource(){
var width = 14043;
var height = 9933;
var stride = (width + 7) / 8;
var pixels = new byte[height * stride];
for (int i = 0; i < height * stride; i++){
pixels[i] = 0xAA;
}
_writeableBitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.BlackWhite, null);
_writeableBitmap.WritePixels(new Int32Rect(0, 0, width, height), pixels, stride, 0);
_writeableBitmap.Freeze();
return null; //Return null (Image control source will be set to null now but bitmap still stored in private field)
}
private WriteableBitmap\u WriteableBitmap//添加用于存储位图(将其保留在范围内)
公共ImageSource getImageSource(){
var宽度=14043;
变量高度=9933;
变量步长=(宽度+7)/8;
var像素=新字节[高度*步幅];
对于(int i=0;i<高度*步幅;i++){
像素[i]=0xAA;
}
_writeableBitmap=新的writeableBitmap(宽度、高度、96、96、PixelFormats.BlackWhite、null);
_writeableBitmap.WritePixels(新的Int32Rect(0,0,宽度,高度),像素,步长,0);
_writeableBitmap.Freeze();
return null;//return null(图像控制源现在将设置为null,但位图仍存储在私有字段中)
}
我想将位图保留在内存中,但不影响图像控件,结果是:(997 MB可用)
(如你所见,右边的蓝线只增加了一点点)
有了这些知识,我相信我的图像控制也有问题。将writeableBitmap分配给图像控件时,峰值开始。这是我所有的xaml:
<Window x:Class="TifFileViewer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TifFileViewer"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
Title="MainWindow" Height="563" Width="1046">
<Grid Margin="10">
<Image x:Name="imageControl"/>
</Grid>
</Window>
编辑:我认为这是一种DIY方法,因为正如@Clemens在他的回答中巧妙地指出的那样,冻结位图也有同样的效果,但只有一行。 你真的需要把你的手弄脏才能实现你想要的;) 解释 (由@Clemens更正) NET framework无法很好地处理低于8位/像素的图像。它系统地将它们转换为32BPP,在我的例子中,这使我的进程接近200Mb。阅读这里,这是一个错误或它的设计 无论是使用
WriteableBitmap
(带/不带指针)还是BitmapSource.Create
它都会消耗大量内存,但是;只有一个地方(BitmapImage
)可以正常运行,幸运的是,它是实现您所寻找的目标的关键所在
注意:只有当1字节等于1像素时,框架才会接受小于或等于8位/像素的图像。正如您和我所看到的,每像素1位图像意味着1字节=8像素;我遵循了这个准则。虽然有人可能会说这是一个bug,但对于开发人员来说,这可能是一个不直接处理bit的便利
解决方案
(专门针对1BPP图像)
正如我所说,你必须把手弄脏,但我会解释一切,这样你就应该很快站起来跑步;)
我所做的:
using System;
using System.Windows;
using System.Windows.Media.Imaging;
using Hjg.Pngcs;
namespace WpfApplication3
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
int width = 14043;
int height = 9933;
int stride;
byte[] bytes = GetBitmap(width, height, out stride);
var imageInfo = new ImageInfo(width, height, 1, false, true, false);
PngWriter pngWriter = FileHelper.CreatePngWriter("test.png", imageInfo, true);
var row = new byte[stride];
for (int y = 0; y < height; y++)
{
int offset = y*stride;
int count = stride;
Array.Copy(bytes, offset, row, 0, count);
pngWriter.WriteRowByte(row, y);
}
pngWriter.End();
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.UriSource = new Uri("test.png", UriKind.Relative);
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
bitmapImage.EndInit();
Image1.Source = bitmapImage;
}
private byte[] GetBitmap(int width, int height, out int stride)
{
stride = (int) Math.Ceiling((double) width/8);
var pixels = new byte[stride*height];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
var color = (byte) (y < height/2 ? 0 : 1);
int byteOffset = y*stride + x/8;
int bitOffset = x%8;
byte b = pixels[byteOffset];
b |= (byte) (color << (7 - bitOffset));
pixels[byteOffset] = b;
}
}
return pixels;
}
}
}
- 以1BPP(实际上是17Mb)手动生成图像
- 将该结果写入.PNG文件
- 从该PNG文件创建了一个
BitmapImage
字节[]
。无论如何,它从来没有达到200或800兆,因为你的经验
您需要的(.NET 4.5)
- 从下载PNGCS库
- 将
重命名为Pngcs45.dll
,否则将发生Pngcs.dll
FileNotFoundException
- 添加对该DLL的引用
- 使用下面的代码
public ImageSource CreateBitmap()
{
var width = 14043;
var height = 9933;
var stride = (width + 7) / 8;
var pixels = new byte[height * stride];
for (int i = 0; i < height * stride; i++)
{
pixels[i] = 0xAA;
}
var format = PixelFormats.Indexed1;
var colors = new Color[] { Colors.Black, Colors.White };
var palette = new BitmapPalette(colors);
var bitmap = BitmapSource.Create(
width, height, 96, 96, format, palette, pixels, stride);
bitmap.Freeze(); // reduce memory consumption
return bitmap;
}
var format = PixelFormats.BlackWhite;
var bitmap = BitmapSource.Create(
width, height, 96, 96, format, null, pixels, stride);
public ImageSource CreateBitmap()
{
...
var bitmap = new WriteableBitmap(width, height, 96, 96, format, palette);
bitmap.WritePixels(new Int32Rect(0, 0, width, height), pixels, stride, 0);
bitmap.Freeze();
return bitmap;
}