C#如何在不使用大量内存的情况下裁剪图像?

7

可能重复:
如何裁剪大图像

有人问如何裁剪图像,在这里

我阅读了这篇文章并使用了其中的答案。但他们似乎都需要将图像加载到 BitmapImage 中。

这给我带来了内存问题。因为我正在尝试将 5 张图片(8000 x 8000)切成瓷砖。一次一个。

纠正我如果我错了,但是那是 8000x8000x4 字节 = 244 MB。每张图片。

而且我会随机遇到内存不足的问题。

如何从另一张图片中获取一个 1000x1000 的图像,同时减少内存消耗。


1
你想在不加载图像到内存的情况下裁剪它吗?为什么你必须同时处理所有5张图片?处理一张,关闭它,然后再处理另一张。 - Rotem
6
您的内存不足问题可能与您的代码有关。能否向我们展示一下您用于裁剪图像的代码? - user7116
1
这是什么平台/类别?我知道一些平台(Silverlight,Unity3D)可以使用比每像素4个字节更多的内存。 - Chris Sinclair
1
@Tibi。这正是我想做的:p。以编程方式将图像分割成平铺。图片最终会出现在Android上(每像素4个字节,约24MB内存允许)。问题是桌面也有/可以有内存限制(不是试图像某些人认为的使用整个位图,而是其他裁剪图像“答案”重复了-并且是10分钟前)。 - IAmGroot
@Rotem 我正在逐个处理它们,正如我在问题中指出的那样。但是只有一个就足以导致OOM。Chris建议每个像素超过4个字节,所以情况更糟。我可能会开始查看目标文件类型的外部库。谢谢大家。 - IAmGroot
显示剩余7条评论
1个回答

5
所以,这是一件相当不容易的事情——基本上,你需要重新实现给定图像格式的图像解码器。这并不简单。对于“简单”的Windows BMP格式,有以下问题: 话虽如此,我还是在午餐时间尝试了一下……以下是我能够想出来的,一个适合在LINQPad中使用的脚本。
(注意:仅限Windows BMP!)
void Main()
{
    // Carve out a 100x100 chunk
    var top = 100;
    var left = 100;
    var bottom = 300;
    var right = 300;

    // For BMP only - open input
    var fs = File.OpenRead(@"c:\temp\testbmp.bmp");

    // Open output
    if(File.Exists(@"c:\temp\testbmp.cropped.bmp")) File.Delete(@"c:\temp\testbmp.cropped.bmp");
    var output = File.Open(@"c:\temp\testbmp.cropped.bmp", FileMode.CreateNew);
    var bw = new BinaryWriter(output);

    // Read out the BMP header fields
    var br = new BinaryReader(fs);
    var headerField = br.ReadInt16();
    var bmpSize = br.ReadInt32();
    var reserved1 = br.ReadInt16();
    var reserved2 = br.ReadInt16();
    var startOfData = br.ReadInt32();   

    // Read out the BMP DIB header
    var header = new BITMAPV5Header();  
    var headerBlob = br.ReadBytes(Marshal.SizeOf(header));
    var tempMemory = Marshal.AllocHGlobal(Marshal.SizeOf(header));
    Marshal.Copy(headerBlob, 0, tempMemory, headerBlob.Length);
    header = (BITMAPV5Header)Marshal.PtrToStructure(tempMemory, typeof(BITMAPV5Header));
    Marshal.FreeHGlobal(tempMemory);

    // This file is a 24bpp rgb bmp, 
    var format = PixelFormats.Bgr24;
    var bytesPerPixel = (int)(format.BitsPerPixel / 8);
    Console.WriteLine("Bytes/pixel:{0}", bytesPerPixel);

    // And now I know its dimensions
    var imageWidth = header.ImageWidth;
    var imageHeight = header.ImageHeight;
    Console.WriteLine("Input image is:{0}x{1}", imageWidth, imageHeight);

    var fromX = left;
    var toX = right;
    var fromY = imageHeight - top;
    var toY = imageHeight - bottom;

    // How "long" a horizontal line is
    var strideInBytes = imageWidth * bytesPerPixel;
    Console.WriteLine("Stride size is:0x{0:x}", strideInBytes);

    // new size
    var newWidth = Math.Abs(toX - fromX);
    var newHeight = Math.Abs(toY - fromY);
    Console.WriteLine("New slice dimensions:{0}x{1}", newWidth, newHeight);

    // Write out headers to output file 
    {
        // header = "BM" = "Windows Bitmap"
        bw.Write(Encoding.ASCII.GetBytes("BM"));    
        var newSize = 14 + Marshal.SizeOf(header) + (newWidth * newHeight * bytesPerPixel);
        Console.WriteLine("New File size: 0x{0:x} bytes", newSize);
        bw.Write((uint)newSize);    
        // 2 reserved shorts
        bw.Write((ushort)0);    
        bw.Write((ushort)0);            
        // offset to "data"
        bw.Write(header.HeaderSize + 14);

        // Tweak size in header to cropped size
        header.ImageWidth = newWidth;
        header.ImageHeight = newHeight;

        // Write updated DIB header to output
        tempMemory = Marshal.AllocHGlobal(Marshal.SizeOf(header));
        Marshal.StructureToPtr(header, tempMemory, true);
        byte[] asBytes = new byte[Marshal.SizeOf(header)];
        Marshal.Copy(tempMemory, asBytes, 0, asBytes.Length);
        Marshal.FreeHGlobal(tempMemory);
        bw.Write(asBytes);
        asBytes.Dump();
    }

    // Jump to where the pixel data is located (on input side)
    Console.WriteLine("seeking to position: 0x{0:x}", startOfData);
    fs.Seek(startOfData, SeekOrigin.Begin);

    var sY = Math.Min(fromY, toY);
    var eY = Math.Max(fromY, toY);
    for(int currY = sY; currY < eY; currY++)
    {
        long offset =  startOfData + ((currY * strideInBytes) + (fromX * bytesPerPixel));
        fs.Seek(offset, SeekOrigin.Begin);      

        // Blast in each horizontal line of our chunk
        var lineBuffer = new byte[newWidth * bytesPerPixel];
        int bytesRead = fs.Read(lineBuffer, 0, lineBuffer.Length);
        output.Write(lineBuffer, 0, lineBuffer.Length);
    }

    fs.Close();
    output.Close();
}

[StructLayout(LayoutKind.Sequential, Pack=0)]
public struct BITMAPV5Header 
{
    public uint HeaderSize;
    public int ImageWidth;
    public int ImageHeight;
    public ushort Planes;
    public ushort BitCount;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst=36)]
    public byte[] DontCare;
}

+1 只是为了展示它有多么复杂,仅针对单个文件类型。不幸的是,我需要使用几种文件类型。 - IAmGroot

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接