Jim's Blog Ramblings about novels, comics, programming, and other geek topics

4Oct/076

How to resize and/or convert large image files

Google AdSense

If you happen to work in the GIS industry, you'll routinely come across large image data sets. Some TIFF files might be several hundred megabytes each.  Although, these files look great when printed and should be kept as the source imagery. They are a bit too large for daily use and especially if you are zoomed out to a scale where multiple images are tiled onto the same screen.

This is where you can make lower resolution and lower quality images and I'll demonstrate how simple it is to do using .NET System.Drawing.Imaging classes. First, we'll make a console application so we can run it unattended at night or use batch files to call and execute the application.

The application entry point will begin as:

static void Main(string[] args)

Our main function will contain four (4) arguments and be called such as "imgcnvt.exe [drive:][path] [drive:][path] [JPEG|TIFF] [scaleFactor]." For this example, we'll support converting to JPEG and TIFF formats and use a scale factor to determine if any resampling will occur.

ImageFormat outFormat = ImageFormat.Jpeg;            
switch (args[2].ToUpper())
{
     case "JPEG": case "JPG":
         outFormat = ImageFormat.Jpeg; break;
     case "TIFF": case "TIF":
         outFormat = ImageFormat.Tiff; break;
     default:
         Console.WriteLine("ERROR!");
         return;
}
float scaleFact;
if (!float.TryParse(args[3], out scaleFact))
{
      Console.WriteLine("ERROR! The scale factor is invalid.");

return;
} if (scaleFact <= 0f || scaleFact > 1f) { Console.WriteLine("ERROR! Please enter a value >= 0 and <= 1."); return; } string currentDir = args[0]; string outputDir = args[1]; if (!Directory.Exists(outputDir)) Directory.CreateDirectory(outputDir);

Now we have some basic error handling and our four variables (outFormat, scaleFact, currentDir, and outputDir) defined and ready for processing. Passing a scale factor value of 1 would result in no scaling and a scale factor of 0.5 would change a 5000x5000 pixel image to 2500x2500 pixels.

Next, we'll get a list of the images files in the current directory and loop through each file and call a Process TIF function using the filename and other settings as arguments.

 foreach(string filename in Directory.GetFiles(currentDir, "*.tif"))
     ProcessTif(filename, outputDir, outFormat, scaleFact);

In the ProcessTif function, we will first create a string that will hold the output file name. This file name will be the concatenation of the outputDir and the filename without extension (we will append the extension associated with the outFormat). For example, if the output is a ImageFormat.Tiff the output file name will be:

string outImage = Path.Combine(outputDir, 
               Path.GetFileName(filename));

Some of the images that I was processing were over five hundred megabytes and loading those into memory resulted in a few memory and processing problems, so I needed to specifically call the garbage collector before I began to process each image to clear out the old image. Calling the garbage collector fixed my memory problems, but you shouldn't ever need to do this.

System.GC.Collect();
System.GC.WaitForPendingFinalizers();

Okay, here's the important pieces of the code. We will now begin the processing of the image and change its format and/or scale.

using (Image fullSizeImg = Image.FromFile(filename))
{
     Image.GetThumbnailImageAbort dummyCallBack = 
          new Image.GetThumbnailImageAbort(ThumbnailCallback);
     int width = (int)(fullSizeImg.Width * scaleFactor);
     int height = (int)(fullSizeImg.Height * scaleFactor);
     using (Image lowResImg  = fullSizeImg.GetThumbnailImage(
             width, height, dummyCallBack, System.IntPtr.Zero))
     {
          lowResImg.Save(outImage, outFormat);
     }
}

Here we are loading the source image from file (filename) to fullSizeImg variable. Next, we setup a ThumbnailImageAbort delegate and calculate the output image's width and height. Then we call the GetThumbnailImage function using the output image dimensions, delegate, and callback data. Lastly, we call Save and provide the filename and image format.

For the ThumbnailImageAbort delegate, I just used a dummy function that always returns false such as:

private static bool ThumbnailCallback()
{ return false; }

Of course, there's much more error trapping and exception handling that you should add to these code snippets, but this should point you in the right direction.

By processing our image data using this technique we were able to create several hundred JPG images (with JGW files) that were between 5 and 15 megabytes. That was a huge performance gain and helped offload the network from processing the several hundred TIFF images that were about 500 megabytes each. Using a scale factor of 1, left us with great images that were only slightly lossy. Also,  our TIFF images were 32 bit and the resulting JPG images were 24 bit, but there weren't any loss in color.

World Files

You can also use a similar technique using StreamReader and StreamWriter to include processing of the world files (TWF) . Within a TFW file (or JGW), there are six lines:

  1. Line 1 - Cell size in the "X" direction
  2. Line 2 - "Rotation" term for the row
  3. Line 3 - "Rotation" term for the column
  4. Line 4 - Cell size in the "Y" direction
  5. Line 5 - Easting value of insertion point "X"
  6. Line 6 - Northing value of insertion point "Y"

To update the world file just divide the value in Line 1 and Line 4 by the scale factor used to process the image. All of the other lines in the world file will remain the same.

My Related Posts: , , ,

James Welch

James Welch is a software engineer in Vermont working for a large information technology company and specializing in .NET. Additionally, he holds a Master’s Degree in Software Engineering and a Bachelor of Science Degree in Computer Science. Jim also enjoys local craft beer, comic books, and science-fiction and fantasy novels, games, and movies.

Twitter Google+ 

Comments (6) Trackbacks (0)
  1. Jim,

    This seems to be just the route we need to go on this side. Will give it a bash (have to dust of my .NET though).

    Most windows viewers and graphic editor fall over when asking them to do something with a 500MB file with 22358×41080 px dimension.

    Any chance on a copy of your source/solution as a starting point? Which version of .NET (Frameworks & VS) did you use?

  2. The above code is for .NET Framework version 2.0. You can use VS 2005 Express (free download) or any other version of VS 2005.

    The above code is about 95% of the entire program, there’s just a few brackets missing for the application. Just pick “console application” and paste the code into the main function.

  3. hi Jim,

    a small question

    Is it possible to assign a web url for the filename variable?

    Image fullSizeImg = Image.FromFile(filename)

    will it load the file to the memory?

  4. Anna,

    I don’t think so. You’ll need to download the file first (you can use the WebClient class to download URLs to disk) then you can process the file. Finally, you can delete the source file after you’ve resized the image.

  5. Hi Jim,

    How would this work with images that when loaded will occupy more physical memory than available?

    Haven’t tried your code yet, but this involve loading the whole image into memory, right?

    best regards,
    Ryan

  6. Ryan,

    Yes. The Image.FromFile method will load the entire file into memory. I didn’t find a way around that. I don’t know what would happened if the images are larger than the physical memory allocated. I’d expect that you’d get a “Out of Memory” exception or it might use your allocated swap space. I’d make sure to close down every process and application that you aren’t using, so that you get a couple more MB of memory. I’d also recommend not trying to run it through VS, just run the release compiled executable. Running the app through the debugger will use a lot more memory than just running it on the command line.

    -Jim


Leave a Reply

No trackbacks yet.