7

Possible Duplicate:
How to crop huge image

There is a question asking how to crop an image here

Ive read it and used the answers. But they all seem to require loading the image into a Bitmap or Image.

This is causing memory issues for me. As Im trying to crop 5 images (8000 x 8000) into tiles. One at a time.

Correct me if im wrong, but thats 8000x8000x4 bytes = 244 MB. per image.

And randomly I get out of memory problem.

How can I get a 1000x1000 image from another image, with reduced memory consumption.

Community
  • 1
  • 1
IAmGroot
  • 13,760
  • 18
  • 84
  • 154
  • 1
    You want to crop an image without loading it into memory? Why do you have to work on all 5 images at the same time? Process one, close it, then process another. – Rotem Dec 05 '12 at 17:52
  • 6
    Your out of memory problem is probably related to your code. Could you show us the code you used to crop the image? – user7116 Dec 05 '12 at 17:53
  • Unfortunately .NET doesn't have a way to load only a subsection of an image. – Cory Nelson Dec 05 '12 at 17:55
  • 1
    What platform/class is this? I know some platforms (Silverlight, Unity3D) can use much more memory than just 4 bytes per pixel. – Chris Sinclair Dec 05 '12 at 17:56
  • and http://stackoverflow.com/questions/569889/how-do-i-use-large-bitmaps-in-net and http://stackoverflow.com/questions/11065689/processing-on-large-bitmaps-up-to-3gb – hometoast Dec 05 '12 at 18:02
  • I don't think you can do that with many compressed formats. When the image is compressed, you must decompress it somehow, so it will end up in memory. From a raw format, you can make the algorithm yourself, it's pretty easy. But if you end up with images that big something isn't right, you should probably split it in small chunks (like google maps does). – Tibi Dec 05 '12 at 19:15
  • Reading what I wrote above gave me an idea... if memory consumption is a problem, than you could try to decompress in a file. The problem is that you will need to write the decompression algorithm yourself, and there are many image formats out there. – Tibi Dec 05 '12 at 19:18
  • 1
    @Tibi. Thats exactly what Im trying to do :p. Programmatically split the image into tiles. The images end up on the android (4 bytes per pixel, ~24MB Memory allowance). The problem is the desktop has/can have a memory limitation too. (not trying to use the entire bitmap as someone seems to think duplicate of - and other crop image "answer" was 10 mins ago). – IAmGroot Dec 05 '12 at 20:09
  • @Rotem Im doing them one at a time, as pointed out in my Question. But just one is enough to OOM. And Chris suggests each pixel is more than 4 bytes, so its even worse. I might start looking at external libraries for the target file types then. Thanks guys. – IAmGroot Dec 05 '12 at 20:13
  • Does this need to be fully in C#? If it is a one-time operation I'd suggest looking at imagemagick and see if that will will accomplish the task, probably using C# to generate the commandline arguments to imagemagick, but let it do all the heavy lifting. – Thymine Dec 05 '12 at 23:00
  • Actually imagemagick does have a couple of C# APIs available: http://www.imagemagick.org/script/api.php but I have not used that, and I only believe it can handle large images from googling "imagemagick huge images" – Thymine Dec 05 '12 at 23:36
  • @sixlettervariables I have succesfully improved my code since getting back in office today. I added the `using` around my bitmaps, since they implement `Idisposable`, and now my memory actually uses around ~250MB, rather than up to 1GB in some instances. (Due to memory not being released straight away). So I wrapped that around the image I am cutting up, and another wrap around the Tile I am creating at the time. Thanks. Still not fool proof to even bigger images though. But for now, I can live with it. – IAmGroot Dec 06 '12 at 11:10

1 Answers1

5

So, this is a decidedly non-trivial thing to do - basically, you'd have to re-implement the image decoder for a given image format. This is not simple.

For the "simple" Windows BMP format, there are these beasts to contend with:

That said, I had to give it a try over my lunch break...here's what I was able to come up with, in a nice LINQPad-ready script.

(NOTE: Windows BMP only!)

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;
}
JerKimball
  • 16,584
  • 3
  • 43
  • 55
  • +1 just for showing how complex it is, for just a single file type. Unfortunately there are a few file types I am required to use. – IAmGroot Dec 05 '12 at 20:14