Search code examples
imagemagickpipepngbmpimagemagick-convert

convert multiple pngs to bmps from stdin to stdout


I need to to convert PNGs coming from chrome-headless to BMPs to work with them, and I also need to do this through pipes without saving those images somewhere.

Chrome is set up to just screencast the images to stdout, which is working fine, but I can't seem to get the imagemagick convert tool to do, what I need.

While testing with

cat foo.png | convert PNG:- BMP:-

seems to work with one input image (Output starts with 'BM', indicating BMP binary garbage),

cat foo.png bar.png | convert PNG:- BMP:-

also returns only one image, which seems to be the first one.

How can I get convert to "overwrite" the output for each file it reads through stdin?


EDIT: The cat example is just to demonstrate the input. The final idea is more like

chrome-headless | *solution* | bmpreader

As fmw42 and Mark pointed out, this doesn't seem to be possible with imagemagick out of the box.


Solution

  • As Fred (@fmw42) says, you cannot do this with ImageMagick alone, but it should be possible to do something with a small C/C++, Perl, PHP or Python script.

    Basically, PNG files are nicely "chunked", see Wikipedia PNG Specification, and each chunk has a type - such as IHDR (header), PLTE (palette), IDAT (image data), IEND (end of file marker) and, crucially, a length in bytes.

    So, you could write a script/program that reads data off your pipe, gets the chunks and appends them to an in-memory string and keeps doing so until it reaches an IEND chunk. At that point it would either use ImageMagick or GD or Pillow to convert the in-memory string into a BMP and write it to a further pipe, or write the string to a pipe that ImageMagick was reading from and let ImageMagick convert the file. You would then zero out the accumulated (PNG) string and start reading the next file.

    I don't feel like writing and debugging all that for 15 points, but it would look something like this:

    chrome-cast | demultiplex
    

    where demultiplex would be like:

    while not error
       clear accumulated string of PNG data
       done = false
       while not done
          read chunk type and length of chunk
          read whole chunk and append to accumulated string of PNG data
          if chunk = IEND
             pass accumulated PNG string to ImageMagick to make BMP
             done = true
          endif
       end
    end
    

    Note that your program would not have to "understand" all the chunk types or their contents - just recognise the chunk length and the IEND chunk when it arrives.


    Note that you may like to run pngcheck or pngsplit to get started in understanding how PNG files are structured in chunks - they are here. In fact, pngsplit may do what you want anyway.

    Here is an example of pngcheck in action:

    pngcheck -v file.png
    
    File: file.png (462308 bytes)
      chunk IHDR at offset 0x0000c, length 13
        800 x 468 image, 32-bit RGB+alpha, non-interlaced
      chunk gAMA at offset 0x00025, length 4: 0.45455
      chunk cHRM at offset 0x00035, length 32
        White x = 0.3127 y = 0.329,  Red x = 0.64 y = 0.33
        Green x = 0.3 y = 0.6,  Blue x = 0.15 y = 0.06
      chunk bKGD at offset 0x00061, length 6
        red = 0x00ff, green = 0x00ff, blue = 0x00ff
      chunk pHYs at offset 0x00073, length 9: 3779x3779 pixels/meter (96 dpi)
      chunk tIME at offset 0x00088, length 7:  2 Oct 2017 21:34:26 UTC
      chunk IDAT at offset 0x0009b, length 32768
        zlib: deflated, 32K window, maximum compression
      chunk IDAT at offset 0x080a7, length 32768
      chunk IDAT at offset 0x100b3, length 32768
      chunk IDAT at offset 0x180bf, length 32768
      chunk IDAT at offset 0x200cb, length 32768
      chunk IDAT at offset 0x280d7, length 32768
      chunk IDAT at offset 0x300e3, length 32768
      chunk IDAT at offset 0x380ef, length 32768
      chunk IDAT at offset 0x400fb, length 32768
      chunk IDAT at offset 0x48107, length 32768
      chunk IDAT at offset 0x50113, length 32768
      chunk IDAT at offset 0x5811f, length 32768
      chunk IDAT at offset 0x6012b, length 32768
      chunk IDAT at offset 0x68137, length 32768
      chunk IDAT at offset 0x70143, length 3115
      chunk tEXt at offset 0x70d7a, length 37, keyword: date:create
      chunk tEXt at offset 0x70dab, length 37, keyword: date:modify
      chunk IEND at offset 0x70ddc, length 0
    No errors detected in file.png (24 chunks, 69.1% compression).
    

    I did a rudimentary version of the PNG chunking in Perl for another answer, so have a quick look here.