views:

349

answers:

8

Hi all,

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.

+2  A: 

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?

Peter Tillemans
I knew this would come up, but nope as stated previously I am able to open a way bigger image and work on it in Photoshop ;)
Alexander
+1  A: 

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.

Pekka
Thanks Pekka. I don't have to unpack big images, the read part is easy. The issue is writing ! ;)
Alexander
@Alexander it's still going to be complex due to the nature of the JPG format: You can't just stitch the byte streams together as it would (sort of) be possible with a raw format that doesn't use compression. Unless you're in for it for the learning effect, I suggest using an external library for this :)
Pekka
Well, what I do for now is split the image in half and write two separate files, then having to re-stitch those in Photoshop, but I would sure be in for a learning effect because it's a personal project ;) Otherwise I think I wouldn't be asking the question at all ! If there is another format that would be suitable for this like PNG or something else than raw bmp (which results in a ridicuously large file) ...
Alexander
+2  A: 

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

Jan Oosting
Thanks Jan, I already had a read of this website but I was actually thinking more of some technique that would allow to read and write JPEG data without having to have the whole resulting image in memory at all. I know the netpbm package performs it on their native formats and can even convert between some formats without loading the whole data in RAM ...
Alexander
A: 

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.

lkessler
220 MB image (< 2 Gb) and 2.4 Gb image (> 2 Gb) is totally different leagues.You can load first image into memory. Okay, it's large, but not THAT large, so it's possible. But you can't do this for the second image. Since its size is larger than your address space.
Alexander
+2  A: 

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!

David Lively
+8  A: 

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

Alexander
A: 

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.

Stijn Sanders
Thanks but as for graphicsmagick, please read my question : "First of all, I do NOT wish to use ImageMagick to do this, but perform it inside my software !"
Alexander
GraphicsMagick has a C++ library you could use when building, and there's an OLE interface: http://www.graphicsmagick.org/programming.html (then again, I must admit, consuming a C++ lib from Delphi is a different story)
Stijn Sanders
A: 

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

Maksee