13 Replies Latest reply on Aug 31, 2012 6:14 AM by Peter Kahrel

    [CS4/JS] Pnglib.jsx -- A PNG creator function

    [Jongware]-9BC6tI Level 4

      After seeing Marc Autret's marvellous pngswatch script, I spent several hours creating PNGs with Photoshop, copying its hex data into Javascript compatible format, finding the relevant color bytes to change ... and all the while I was thinking, "there was an uncompressed PNG format, wasn't there?"

       

      That's not because I have a bad memory.

      Sure, Marc created his PNG in some other program, saved it as a compressed file, and 'only' changed the palette part in his script -- which involves delving quite deeply into the actual PNG format --, and that's a feasible way of doing the stuff he intended to: change jsut the color. But if you want to actually create a dropdown or listbox image on the fly -- say, for a line widths dropdown --, you have to be able to create an entire image a-new. And PNGs are notoriously difficult to create, because the image pixels themselves are compressed using the very advanced zlib compression.

      But (as I was thinking) ... zlib also allows a "non-compressed" format!

       

      With some sleuthing I found a couple of hints to get me started, and found a totally useful utility as well: pngcheck, which can take a PNG to bits and tell you what's wrong with it. So, here you have it: a lightweight PNGLIB Javascript, that can create any PNG right out of nothing!

       

      Any image, apart from the limitations, that is.

       

      Its main limitation is that you can only create 8-bit palettized PNGs with it. I see no reason to add umpteen functions to cater for the occasional 1-, 2-, or 4-bit or true color PNG, or to add total support for all the different types of transparency that PNG supports. But, hey, its main use is for icons, and you'll have to do with the limits of "just" 256 colors -- or even less than that, if you reserve one or more colors for transparency. On the plus side again, it's total real pixel alpha-level transparency we're talking about (overall that can make your graphics still better than the average '90s DOS game).

       

      Using the function is easy; at the bottom of the script is an example, but it boils down to:

       

      • Create a string for the palette's colors. Each color is a triplet, in RGB order.
      • Create a string for the transparency indexes. Each single entry determines the transparency of the full palette color at that index; the first entry applies to color index #0, the second to color index #1, and so on. The value [00] indicates zero opacity (fully transparent), the value [FF] full opacity. The transparency index string doesn't need to define all of your colors' transparencies; unlisted values are "normal", non-transparent, and if you only need to make color index #0 transparent, you are done right there and then. By the way, the transparency string may be omitted entirely if you don't need it.
      • Create a string for the image itself -- wide x high color indexes. Make sure you fill the entire image, 'cause my function will refuse to work if this string length isn't correct.
      • Then call my function: myImg = makePng (wide, high, palette, pixels [, transparency]);
      • The returned string can be used immediately as a source for a ScriptUI dialog image, or -- less useful, but might come in handy -- be written to a file.

       

      Tips: hmm. I dunno. Don't use this function to create super-huge PNGs, I guess. The non-compression format uses a couple of checksums on its own, and they are sure to fail on very large images. But, come on, be realistic: it's not a Photoshop replacement we're talking about, it's for icons!

       

      And Be Kind to Your Users: it's rather overkill to include all of the data for a static PNG image, such as a logo or something. Just create that once, and include the binary data in your script! This function is designed to create PNGs on the fly, from variable rather than static data.

       

      Before I forget: here it is. Enjoy!

       

      /****** PngLib.jsx ******/
      /* A Jongware Product -- based *very* heavily, however, upon Marc Autret's pngswatch
      /* script (http://forums.adobe.com/thread/780105?tstart=0), and with further
      /* help from the pages of David "Code Monk" Jones (http://drj11.wordpress.com/2007/11/20/a-use-for-uncompressed-pngs/)
      /* and Christian Fröschlin (http://www.chrfr.de/software/midp_png.html)
      /* Any errors, of course, must have crept in while I wasn't paying attention.
      /* [Jw] 26-Jan-2010
      *************************/
      
      var makePng = (function()
      {
           // Table of CRCs of 8-bit messages
           var CRC_256 = [0, 0x77073096, 0xee0e612c, 0x990951ba, 0x76dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0xedb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x9b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x1db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x6b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0xf00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x86d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x3b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x4db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0xd6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0xa00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x26d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x5005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0xcb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0xbdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d];
      
           // PNG Cyclic Redundancy Code algorithm -- http://www.w3.org/TR/PNG/#D-CRCAppendix
           var crc32s = function(/*uint[]*/buf)
                {
                var c = 0xffffffff, i;
                for( i=0 ; i < buf.length; i++ )
                     c = CRC_256[(c ^ buf.charCodeAt(i)) & 0xff] ^ (c >>> 8);
      
                return (c ^ 0xffffffff);
                };
      
      
           var header = function ()
           {
                return "\x89PNG\x0D\x0A\x1A\x0A";
           };
           var i2s = function (/*int32*/i)
           {
                return String.fromCharCode(i>>>24) + String.fromCharCode(i>>>16) + String.fromCharCode(i>>>8) + String.fromCharCode(i);
           }
           var chunk = function (/*4 Char PNG code*/chunkType, /*data*/data)
           {
                var buf = chunkType + data;
                var crc = crc32s(buf);
                buf = i2s (data.length) + buf + i2s (crc);
                return buf;
           };
      
           var adler32 = function (/*string*/buf)
           {
                var i, a = 1, b = 0;
                for (i=0; i<buf.length; i++)
                {
                     a += buf.charCodeAt(i); s1 %= 65521;
                     b += a; b %= 65521;
                }
                return (b<<16)+a;
           }
           return function(/*int*/wide, /*int*/high, /*string*/ pal, /*string*/image, /*string*/transpIndex)
           {
                var t, bits;
      
                if (pal.length % 3)
                {
                     alert ("Bad Palette length -- not a multiple of 3");
                     return null;
                }
      
                if (image.length != high*wide)
                {
                     alert ("Size error: expected "+(high*wide)+" bytes, got "+image.length);
                     return null;
                }
                
                bits = '';
                for (t=0; t<high; t++)
                     bits += "\x00"+image.substr(t*wide, wide);
                t = bits.length;
                bits += i2s (adler32(bits));
                var r = header() + chunk ('IHDR', i2s (wide)+i2s(high)+"\x08\x03\x00\x00\x00");
                r += chunk ('PLTE', pal);
                if (transpIndex != null)
                     r += chunk ('tRNS', transpIndex);
                r += chunk ('IDAT', "\x78\x9c\x01"+ String.fromCharCode (t & 0xff)+String.fromCharCode((t>>>8) & 0xff)+String.fromCharCode ((~t) & 0xff)+String.fromCharCode(~(t>>>8) & 0xff)+bits);
                r += chunk ('IEND', '');
      
                return r;
           };
      })();
      
      /* Sample usage. Remove when #including the above in _your_ script! */
      
      var pngPal  = "\x00\x00\x00"+"\xff\x00\x00"+"\x00\xff\x00"+"\x00\x00\xff"+"\xff\xff\x00"+"\x40\x40\x40";
      var pngData =     "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
                          "\x00\x01\x01\x02\x02\x03\x03\x04\x04\x00"+
                          "\x00\x01\x01\x02\x02\x03\x03\x04\x04\x00"+
                          "\x05\x01\x01\x02\x02\x03\x03\x04\x04\x05"+
                          "\x05\x01\x01\x02\x02\x03\x03\x04\x04\x05"+
                          "\x05\x01\x01\x02\x02\x03\x03\x04\x04\x05"+
                          "\x05\x01\x01\x02\x02\x03\x03\x04\x04\x05"+
                          "\x05\x01\x01\x02\x02\x03\x03\x04\x04\x05"+
                          "\x05\x01\x01\x02\x02\x03\x03\x04\x04\x05"+
                          "\x05\x01\x01\x02\x02\x03\x03\x04\x04\x05"+
                          "\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05";
      
      img = makePng (10,11, pngPal, pngData, "\x40");
      
      var w = new Window("dialog", "Image test");
      w.add ('image', undefined, img);
      
      var f = new File(Folder.myDocuments+"/test-me2.png");
      if (f.open('w'))
      {
           f.encoding = "BINARY";
           f.write (img);
           f.close();
      } else
           alert ("eh -- couldn't write test file...");
      
      
      w.show();
      

        • 1. Re: [CS4/JS] Pnglib.jsx -- A PNG creator function
          RorohikoKris-PdM8K2

          Wow - pretty cool! I never cease to be amazed at how far ExtendScript can be stretched, eh - you'd expect this to be something in the realm of C/C++/Java.

          • 2. Re: [CS4/JS] Pnglib.jsx -- A PNG creator function
            [Jongware]-9BC6tI Level 4

            Coming from you, Kris, that's a huge compliment!

             

            (Even though this is your Very First Post. Gremlins about -- I don't longer care, past-midnight or not.)

            • 3. Re: [CS4/JS] Pnglib.jsx -- A PNG creator function
              [Jongware]-9BC6tI Level 4

              Here is a more complicated (and useful ) example. (--Its actual usefulness is not as, erm, useful as I hoped, because it seems you don't have access to the built-in stroke styles! If anyone knows a work-around that, let me know ...)

               

              First, create a few custom Striped and/or Dashed stroke styles; then call this Javascript to see them created "live" in the drop down. Make sure you removed the sample code from the end of "pnglib.jsx", otherwise that dialog with interfere and mess up my nice program.

               

              stripes.png

               

               

              #include "pnglib.jsx"
               
              
              function createStripeImg (styleIndex)
              {
                   var pngPal  = "\x00\x00\x00"+"\xff\xff\xff";
                   var pngData = '';
                   var x,y, ystart;
              
                   var stripes = [];
                   var i;
                   for (y=0; y<app.activeDocument.stripedStrokeStyles[styleIndex].stripeArray.length; y++)
                        stripes.push (Math.round (11*app.activeDocument.stripedStrokeStyles[styleIndex].stripeArray[y]/100));
                   i = 0;
                   for (y=0; y<11; y++)
                   {
                        if (y >= stripes[i])
                        {
                             if (y <= stripes[i+1])
                             {
                                  for (x=0; x<48; x++)
                                       pngData += "\x00";
                                  continue;
                             }
                             i += 2;
                        }
                        for (x=0; x<48; x++)
                             pngData += "\x01";
                   }
                   return makePng (48,11, pngPal, pngData, "\xff\x00");
              }
              
              function createDashImg (styleIndex)
              {
                   var pngPal  = "\x00\x00\x00"+"\xff\xff\xff";
                   var pngData = '';
                   var x,y, xstart;
              
                   var dashes = [];
                   var i, len;
                   len = 0;
                   for (y=0; y<app.activeDocument.dashedStrokeStyles[styleIndex].dashArray.length; y++)
                        len += app.activeDocument.dashedStrokeStyles[styleIndex].dashArray[y];
                   xstart = 0;
                   for (y=0; y<app.activeDocument.dashedStrokeStyles[styleIndex].dashArray.length; y++)
                   {
                        dashes.push (xstart);
                        xstart += Math.round (48*app.activeDocument.dashedStrokeStyles[styleIndex].dashArray[y]/len);
                   }
                   dashes.push (47);
                   
                   i = 0;
                   for (y=0; y<11; y++)
                   {
                        if (y < 3 || y > 8)
                        {
                             for (x=0; x<48; x++)
                                  pngData += "\x01";
                        } else
                        {
                             xstart = 0;
                             for (x=0; x<48; x++)
                             {
                                  if (x >= dashes[xstart])
                                  {
                                       if (x >= dashes[xstart+1])
                                            xstart += 2;
                                       pngData += "\x00";
                                  } else
                                  {
                                       pngData += "\x01";
                                  }
                             }
                        }
                   }
                   return makePng (48,11, pngPal, pngData, "\xff\x00");
              }
              
              
              if (app.activeDocument.stripedStrokeStyles.length+app.activeDocument.dashedStrokeStyles.length < 1)
              {
                   alert ("This example needs a few custom stripe or dash stroke styles to play with");
                   exit (0);
              }
              
              var w = new Window("dialog", "Select a stripe type");
              var ddl = w.add("dropdownlist");
              for( i=0; i < app.activeDocument.stripedStrokeStyles.length; i++)
                   (ddl.add('item', " "+app.activeDocument.stripedStrokeStyles[i].name)).image = createStripeImg (i);
              for( i=0; i < app.activeDocument.dashedStrokeStyles.length; i++)
                   (ddl.add('item', " "+app.activeDocument.dashedStrokeStyles[i].name)).image = createDashImg (i);
              
              ddl.selection = 0;
              g = w.add ("group");
              g.orientation = 'row';
              g.add ("button", undefined, "OK");
              g.add ("button", undefined, "Cancel");
              
              w.show();
              
              • 4. Re: [CS4/JS] Pnglib.jsx -- A PNG creator function
                Harbs. Level 6

                Yes.

                Truly amazing!

                 

                Theun, I'm waiting to play space invaders...

                 

                Harbs

                • 5. Re: [CS4/JS] Pnglib.jsx -- A PNG creator function
                  Marc Autret Level 4

                  Hi Jongware,

                   

                  I feel like we've opened Pandora's box. Great job!

                  Congrats.

                   

                  @+

                  Marc

                  • 7. Re: [CS4/JS] Pnglib.jsx -- A PNG creator function
                    [Jongware]-9BC6tI Level 4

                    Appendix: pngcheck 2.3.0

                     

                    pngcheck is a great little tool to test the integrity of PNGs with -- to ensure my home made PNGs were well-formed, I wrote them out into a file and tested them with this little command line tool. At work (Windows 7), I could download the latest version as a Windows executable, but when at home (Mac OS X 10.6), I could not find a good recent version. Fortunately, I'm not one to sit down and take it like a man; since I have VirtualBox on my computer, all I had to do was copy my PNG to the VB shared folder, go to Windows, go to the command line, (sigh) enter the commands and check. Hey, an error. Re-do from Start.

                     

                    With a little more trouble, I just (1:50 in the a.m.) compiled the latest version for OS X. I have no idea if this will work on others' computers (especially if you are not running Sno Lep, as I am), but feel free to download it from my site and try:

                     

                    http://www.jongware.com/binaries/pngcheck-2.3.0.zip

                     

                    You need Terminal experience to use it. It's a command line tool. Best is to place it into your usr/bin folder, to make it available from any folder you are terminalling in:

                     

                    sudo mv pngcheck /usr/bin

                     

                    (It needs 'sudo', and your password, because the usr/bin folder is protected. ... don't go experimenting with using it on other commands!)

                    • 8. Re: [CS4/JS] Pnglib.jsx -- A PNG creator function
                      [Jongware] Most Valuable Participant

                      Update.

                       

                      The PNGs created with this script display okay in InDesign itself, and in OS X's Preview and Windows' Image Viewer. However, pngcheck sometimes fails, and Photoshop cannot read the image in that case either.

                       

                      ... 2 hours later, I found out why. My mistake! Replace the function adler32 inside the main makepng function with this one:

                       

                      var adler32 = function (/*string*/buf)
                           {
                                var i, a = 1, b = 0;
                                for (i=0; i<buf.length; i++)
                                {
                                     a += buf.charCodeAt(i); a %= 65521;
                                     b += a; b %= 65521;
                                }
                                return (b<<16)+a;
                           }
                      

                       

                      -- the error was in the first mod 65521 line, I did that on a wrong variable.

                       

                      .. Fortunately, no-one noticed this so far

                       

                      [Edit] Oh, and it transpires the main packing routine needs to check for image data > 64K (an individual data block may not be larger than the 16 bits needed for each deflate block).

                      • 9. Re: [CS4/JS] Pnglib.jsx -- A PNG creator function
                        [Jongware] Most Valuable Participant

                        As Bender once said, "I've always wondered what it would be like to be more annoying." -- now you can try that too!

                         

                        Introducing "Annoying Addy", based on Microsoft's iconic Clippy:

                         

                        addy.png

                         

                        Download http://www.jongware.com/binaries/annoyingAddy.zip and have some fun Of course the 'buttons' and 'checkboxes' don't really work, it's just an image (but maybe Marc Autret or Peter Kahrel can make them "real").

                         

                        On a slightly related note, the makepng function in this script is fixed to be able to create PNGs larger than 64K.

                        • 10. Re: [CS4/JS] Pnglib.jsx -- A PNG creator function
                          Loic.Aigon Adobe Community Professional

                          I am just speechless ! Great work Jongware

                          • 11. Re: [CS4/JS] Pnglib.jsx -- A PNG creator function
                            Peter Kahrel Adobe Community Professional & MVP

                            Almost forgot about the challenge!

                             

                            Peter

                             

                             

                            var f = File ('/d/png/addy.png');

                             

                            var w = new Window ('dialog');

                                w.orientation = 'stack';

                             

                                if ($.os.indexOf ('Windows') < 0)

                                    var addy = w.add ('image', undefined, f);

                             

                                    var rb1 = w.add ('radiobutton');

                                    var rb2 = w.add ('radiobutton');

                                    var chb = w.add ('checkbox');

                               

                                if ($.os.indexOf ('Windows') > -1)

                                    var addy = w.add ('image', undefined, f);

                             

                                w.onShow = function () {

                                    rb1.location = [25, 147];

                                    rb2.location = [25, 204];

                                    chb.location = [26, 275];

                                    }

                             

                            w.show();

                            • 12. Re: [CS4/JS] Pnglib.jsx -- A PNG creator function
                              [Jongware] Most Valuable Participant

                              That's totally wicked!

                               

                              Let's all go to the Feature Request form and submit this as a definitive "must have"!

                              • 13. Re: [CS4/JS] Pnglib.jsx -- A PNG creator function
                                Peter Kahrel Adobe Community Professional & MVP

                                Yes, let's!

                                 

                                (The script I posted was culled from another one, and I now notice that it's not necessary to ceclare the image as a variable. Just remove the two instances of var addy =.)

                                 

                                Peter