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

原文 标签 c# memory image-processing out-of-memory

这个问题在这里已经有了答案:




8年前关闭。




Possible Duplicate:
How to crop huge image



有一个问题询问如何裁剪图像 here

我已经阅读并使用了答案。但它们似乎都需要将图像加载到 Bitmap 中。或 Image .

这给我造成了内存问题。当我试图将 5 张图像 (8000 x 8000) 裁剪成图块时。一次一个。

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

并且随机出现内存不足问题。

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

最佳答案

因此,这绝对是一件非常重要的事情 - 基本上,您必须为给定的图像格式重新实现图像解码器。这是不简单 .

对于“简单”的 Windows BMP 格式,有这些野兽需要应对:

  • BMP File Format
  • DIB Header Structure

  • 也就是说,我不得不在午休时间尝试一下......这是我能够想出的,在一个很好的 LINQPad-ready 脚本中。

    (注意:仅适用于 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;
    }
    

    关于C# 如何在不使用大量内存的情况下裁剪图像?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13729368/

    相关文章:

    python - OpenCV python : ValueError: too many values to unpack

    C# WPF - 如何将 TextBox Enter Key Press 绑定(bind)到方法?

    c# - Random.NextDouble 方法可以返回 '1.0' 吗?

    c++ - 为什么在Visual Studio 2013/2015上追随vsnprintf崩溃?

    c - 嵌套结构中的数据如何存储和填充?

    python - 如何褪色

    c# - 列出具有实例的可用 SQL Server 不包括 SQL Server Express

    c# - 将 ContentPresenter 用于自定义控件 (Thumb)

    MySQL:使用 MEMORY STORAGE ENGINE 来提高此查询的性能

    python - 如何沿着对象中心画一条线