phpanimationpngapng

How can I split animated PNG with PHP?


There are a lot of solutions about how to create an APNG image (animated PNG), but how can I split APNG image frames into separate images?

Thanks in advance.


Solution

  • Here's some example code that will take a png in the form of a byte array and return the various frames as an array of byte arrays.

    function splitapng($data) {
      $parts = array();
    
      // Save the PNG signature   
      $signature = substr($data, 0, 8);
      $offset = 8;
      $size = strlen($data);
      while ($offset < $size) {
        // Read the chunk length
        $length = substr($data, $offset, 4);
        $offset += 4;
    
        // Read the chunk type
        $type = substr($data, $offset, 4);
        $offset += 4;
    
        // Unpack the length and read the chunk data including 4 byte CRC
        $ilength = unpack('Nlength', $length);
        $ilength = $ilength['length'];
        $chunk = substr($data, $offset, $ilength+4); 
        $offset += $ilength+4;
    
        if ($type == 'IHDR')
          $header = $length . $type . $chunk;  // save the header chunk
        else if ($type == 'IEND')
          $end = $length . $type . $chunk;     // save the end chunk
        else if ($type == 'IDAT') 
          $parts[] = $length . $type . $chunk; // save the first frame
        else if ($type == 'fdAT') {
          // Animation frames need a bit of tweaking.
          // We need to drop the first 4 bytes and set the correct type.
          $length = pack('N', $ilength-4);
          $type = 'IDAT';
          $chunk = substr($chunk,4);
          $parts[] = $length . $type . $chunk;
        }
      }
    
      // Now we just add the signature, header, and end chunks to every part.
      for ($i = 0; $i < count($parts); $i++) {
        $parts[$i] = $signature . $header . $parts[$i] . $end;
      }
    
      return $parts;
    }
    

    An example call, loading a file and saving the parts:

    $filename = 'example.png';
    
    $handle = fopen($filename, 'rb');
    $filesize = filesize($filename);
    $data = fread($handle, $filesize);
    fclose($handle);
    
    $parts = splitapng($data);
    
    for ($i = 0; $i < count($parts); $i++) {
      $handle = fopen("part-$i.png",'wb');
      fwrite($handle,$parts[$i]);
      fclose($handle);
    }