11
votes

Here is the problem. I have a large set of 512x512 pixels JPEG tiles as regular jpg files.

I have written a piece of software that does a bunch of things and does need to stitch all those files into a single huge JPEG at the end.

First of all, I do NOT wish to use ImageMagick to do this, but perform it inside my software !

In Delphi it is not possible to copy a JPG file onto another JPG canvas, so a TBitmap must be created first, then the tiles are copied onto the TBitmap canvas and then the TBitmap is converted to a jpeg picture and saved to a file.

The issue arises when the resulting file dimensions are too big (like 20 000 x 20 000 pixels). When I call TBitmap.SetSize I naturally get an error (out of memory or something like this).

I made some tests using Photoshop on the very same machine and was able to create a complex (non blank) 30 000 x 30 000 file and save it to JPEG.

So the question is, how could I perform the same thing ? Fing some way to stitch all those JPEGS by writing the result directly to the disk or use some other trick ?...

Even though 20k x 20k pixels seems big enough, this value does only apply to my machine (4 GB ram) so a smaller amount of ram would be even more limiting to the software !

Thanks

Edit : To clarify :

What I would like is to find a way of stitching those small JPG images and write the large one without keeping the large image in RAM. Apparently a bitmap stream read/write is possible directly on disk (not sure) but this would result in a VERY large file. So, if the JPG format does not allow to do this, any other compressed format like TIFF or PNG would do. I would also like to avoid too much recompression not to lose the (already compressed) initial JPG quality.

Hence the perfect solution would be a way to directly read the small files and write into the large one somehow. The dimensions of the tiles are 256x256 or 512x512 in case it would help for some alignment on JPEG compression stuff.

8

8 Answers

8
votes

Thanks to everyone !

Actually, the answer and possible solution is to proceed as Photoshop does, that is write a bitmap stream from the tiles to a big bmp file on the disk (for example a 20 000 x 30 000 file would be 2.4 Gb) and then use the NativeJpg library to convert this big bitmap to a jpg by feeding the bitmap data stripe by stripe, each of them being 8 pixels high.

It would also be possible to stitch a single line of tiles (512 pixels high) and then feed it to the NativeJpg library 8 by 8 and then move on to the next line of tiles !

Some sample code by Erik Turner :

procedure GetBitmapTile(BM: TBitmap; Y, X: Integer);
var JpegImage: TJpegImage;
begin
  JpegImage := NIL; // Replace with tile lookup //
  BM.PixelFormat := pf32bit;
  BM.Width := JpegImage.Width;
  BM.Height := JpegImage.Height;
  BM.Canvas.Draw(0, 0, JpegImage);
end;

procedure WriteBitmapFile(TileCountY, TileCountX: Integer; BM_Stm: TStream);
var
  BM: TBitmap;
  TileY: Integer;
  TileX: Integer;
  PixelY: Integer;
begin
  BM := TBitmap.Create;
  for TileY := 0 to TileCountY-1 do
    for TileX := 0 to TileCountX-1 do
    begin
      GetBitmapTile(BM, TileY, TileX);
      for PixelY := 0 to 511 do
        BM_Stm.Write(BM.ScanLine[PixelY]^, 512 * SizeOf(TRGBQuad));
    end;
    BM.Free;
end;

NativeJpg library : http://www.simdesign.nl/nativejpg.html

1
votes

This is a pretty tough field, and as @Peter already says, the Photoshop folks have been at this since 1990. You will probably not be able to work with your programming language's built-in JPG decoding libraries as they will likely load (and unpack) the whole image in the RAM.

I'd recommend looking for external libraries that deal with this - whether there are decent ones available for free I don't know, but it may well be the case.

1
votes

The maximum size of images in Delphi (at least in previous versions) was more dependent on the windows graphics drivers than on the amount of system memory

Some experiments on this: EFG's computer lab

1
votes

You may need to implement a custom class to handle this.

In video memory, an image (or a screen buffer) is a linear array. Horizontal rows are stored sequentially, and each pixel corresponds to the array offset at (y*width+x)-1.

Thus, in a 320x200 image, the pixel at 5,2 would be at array index 5*320+2-1, or 1601. Back in the days before hardware acceleration, you'd malloc() a buffer the size of your screen and mathematically perform an operations like drawing textures, shapes, lighting effects etc, then BLT the buffer out to video RAM.

In your case, you could use the built-in bitmap and image classes to work with smaller images that fit in memory, then copy their pixel data into a large array or series of arrays (I forget if the virtual memory will allow you to create buffers > the size of physical RAM). Then, using a JPEG library that works directly on that array (which is independent of the size of RAM installed on the machine), you should be able to feed the array into the library and have it save the contents out to disk. LZW compression is pretty straightforward and I would expect their to be a lot of material on manually implementing JPEG compression on the web.

One caveat to this: If you're using a 32-bit OS, your address space should be limited to 4GB. The only way I can think of off the top of my head to get around this would be to create smaller buffers (say, one row at a time), throw fill the data with the part of the pixel data that the row corresponds to in your to-be-stiched images, save it, and loop until you've covered your entire image area.

I hope that's clear. Good luck!

1
votes

Photoshop like many other media oriented programs had to deal with the issue of working on files bigger than main memory for a long time. For large photos one technique is tiling to only work a a part of the image.

In practice this is more convoluted (pun intended) than simply cut and paste.

I am no Delphi programmer, but something which worries me is that when you run out of memory creating the image, will the same not happen when you try to use that image?

1
votes

Fore heavy duty work like this, I resort to http://www.graphicsmagick.org/

There're also a set of Pascal units here http://graphics32.org/ but they're pretty mathematical and complex (and I therefor haven't gotten them to work), but also built for the hard work.

0
votes

Your problem also makes me think of spreadsheets. which of course only are sparsely populated. You may try looking at some image compression libraries that might give you some ideas.

What you could also do is see how someone else does it. I note that the PixeLook Development Group has their PixeLook library which are components for Delphi 6 for creating image and data processing applications.

They claim that large images and data matrices are easily handled and on their screenshot page, they show the display of a 5200 x 5200 image (26 MB) and they say their tests were also performed with the image size up to 220 MB.

If you really need large image processing directly in your application, this package might work for you for $50. If it's close but not quite right (I don't know if it will join jpegs), then you might consider buying the source for $299, seeing what it does, and extending it.

Disclaimer: I have no connection with this company.

0
votes

What about partial external software/internal call solution? IJG (Independent JPEG Group) has an excellent command-line tool jpegtran that I used in my viewer for lossless rotation. There's no problem to use CreateProcess, WaitForsingleObject inside your own code to make to look like your own code. You even can pack the executables inside your resource and extract it temporary

So they also has jpegjoin utility (find it at http://jpegclub.org/jpegtran/) that can be used the same way. UPDATE: this utility is for lossless joining, so require much smaller memory/disk footprint