0

I have a hundred of JPG image pieces and want to merge them in one large JPG. And to accomplish that I use the following code:

using (var combinedBitmap = new Bitmap(combinedWidth, combinedHeights)) {
    combinedBitmap.SetResolution(96, 96);
    using (var g = Graphics.FromImage(combinedBitmap))
    {
        g.Clear(Color.White);

        foreach (var imagePiece in imagePieces)
        {
            var imagePath = Path.Combine(slideFolderPath, imagePiece.FileName);

            using (var image = Image.FromFile(imagePath)) 
            {
                var x = columnXs[imagePiece.Column];
                var y = rowYs[imagePiece.Row];

                g.DrawImage(image, new Point(x, y));
             }
        }
    }

    combinedBitmap.Save(combinedImagePath, ImageFormat.Jpeg);
}

Everything is fine until dimensions (combinedWidth, combinedHeights) exceed curtain threshold like says here https://stackoverflow.com/a/29175905/623190

The merged JPG file with dimensions of 23170 x 23170 pixels is about 50MB — not too big to kill the memory.

But the Bitmap can not be created with greater dimensions — just breaks with the wrong parameter exception.

Is there any other way to merge the JPG image pieces in one large JPG with dimensions greater than 23170 x 23170 using C#?

Vadim Loboda
  • 2,431
  • 27
  • 44
  • `The merged JPG file with dimensions of 23170 x 23170 pixels is about 50MB — not too big to kiil the memory.` Size on disk and size in memory aren't _necessarily_ the same. – mjwills Jun 14 '19 at 12:18
  • mjwills — of course BitMap it memory is large than compressed JPG on disk. I wonder if there is a way to work with JPG directly? :) – Vadim Loboda Jun 14 '19 at 12:20
  • I mean that my PC has 16Gb of RAM and can process a 50Mb file, as I guess. – Vadim Loboda Jun 14 '19 at 12:22
  • No, I did not. Is there any C# examples of working with WIC? – Vadim Loboda Jun 14 '19 at 12:31
  • I did some tests in C# and I could load big images with [IShellImageDataFactory](https://learn.microsoft.com/en-us/windows/desktop/api/shimgdata/nn-shimgdata-ishellimagedatafactory) (I tested with 40000 * 30000 jpg, Windows 10 with 8GB RAM) – Castorix Jun 14 '19 at 21:16
  • 1
    @Castorix Would you kindly share a C# snippet how you use the IShellImageDataFactory API to load a jpg at 40,000x30000 pixel size into the GDI Memory of .Net and do something meaningful with it ? For a standard 24bit RGB Image that is around 3.4GB of RAM for the uncompressed image data alone. .NET/GDI breaks in my test on a 8GB Ram PC. Thank you. As a reference... Here the .Net Image Memory Limit https://stackoverflow.com/questions/29175585/what-is-the-maximum-resolution-of-c-sharp-net-bitmap?noredirect=1&lq=1 – Hakan Usakli Mar 12 '20 at 10:19

1 Answers1

5

Here's a solution using libvips. This is a streaming image processing library, so rather than manipulating huge objects in memory, it builds pipelines and then runs them in parallel, streaming images in a series of small regions.

This example uses net-vips, the C# binding for libvips.

using System;
using System.Linq;
using NetVips;

class Merge
{
    static void Main(string[] args)
    {
        if (args.Length < 2)
        {
            Console.WriteLine("Usage: [output] [images]");
            return;
        }

        var image = Image.Black(60000, 60000);
        var random = new Random();

        foreach (var filename in args.Skip(1))
        {
            var tile = Image.NewFromFile(filename, access: Enums.Access.Sequential);

            var x = random.Next(0, image.Width - tile.Width);
            var y = random.Next(0, image.Height - tile.Height);

            image = image.Insert(tile, x, y);
        }

        image.WriteToFile(args[0]);
    }
}

I made a set of 1000 jpg images, each 1450 x 2048 RGB using:

for ($i = 0; $i -lt 1000; $i++)
{
    # https://commons.wikimedia.org/wiki/File:Taiaroa_Head_-_Otago.jpg
    Copy-Item "$PSScriptRoot\..\Taiaroa_Head_-_Otago.jpg" -Destination "$PSScriptRoot\$i.jpg"
}

To measure the time needed to execute the above code, I used PowerShell's built in "Measure-Command" (which is similar to Bash's "time" command):

$fileNames = (Get-ChildItem -Path $PSScriptRoot -Recurse -Include *.jpg).Name
$results = Measure-Command { dotnet Merge.dll x.jpg $fileNames }
$results | Format-List *

With the prepared images and the script above, I see this:

C:\merge>.\merge.ps1
Ticks             : 368520029
Days              : 0
Hours             : 0
Milliseconds      : 852
Minutes           : 0
Seconds           : 36
TotalDays         : 0.000426527811342593
TotalHours        : 0.0102366674722222
TotalMilliseconds : 36852.0029
TotalMinutes      : 0.614200048333333
TotalSeconds      : 36.8520029

So on my 8th generation Intel Core i5 Windows PC, it has merged 1000 jpg images into a large 60k x 60k jpg image in 36 seconds.

kleisauke
  • 515
  • 1
  • 5
  • 7