<?php
/*
  :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  ::
  ::    GIFDecoder Version 2.0 by László Zsidi
  ::
  ::    Created at 2007. 02. 01. '07.47.AM'
  ::
  ::    Updated at 2009. 06. 23. '06.00.AM'
  ::
  ::  * Optimized version by xurei
  ::    https://github.com/xurei/GIFDecoder_optimized
  ::    Updated at 2015-04-13
  ::
  :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 */

class GIF_Decoder
{

  var $GIF_TransparentR = -1;
  var $GIF_TransparentG = -1;
  var $GIF_TransparentB = -1;
  var $GIF_TransparentI = 0;
  var $GIF_buffer = null;
  var $GIF_arrays = Array();
  var $GIF_delays = Array();
  var $GIF_dispos = Array();
  var $GIF_stream = "";
  var $GIF_string = "";
  var $GIF_bfseek = 0;
  var $GIF_anloop = 0;
  //xurei - frames metadata
  var $GIF_frames_meta = Array();
  var $GIF_screen = Array();
  var $GIF_global = Array(); //global color map
  var $GIF_sorted;
  var $GIF_colorS; //
  var $GIF_colorC; //Size of global color table
  var $GIF_colorF; //if 1, global color table follows image descriptor

  /*
    :::::::::::::::::::::::::::::::::::::::::::::::::::
    ::
    ::    GIFDecoder ( $GIF_pointer )
    ::
   */

  function __construct($GIF_pointer)
  {
    $this->GIF_stream = $GIF_pointer;

    GIF_Decoder::GIFGetByte(6);
    GIF_Decoder::GIFGetByte(7);

    $this->GIF_screen = $this->GIF_buffer;
    $this->GIF_colorF = $this->GIF_buffer [4] & 0x80 ? 1 : 0;
    $this->GIF_sorted = $this->GIF_buffer [4] & 0x08 ? 1 : 0;
    $this->GIF_colorC = $this->GIF_buffer [4] & 0x07;
    $this->GIF_colorS = 2 << $this->GIF_colorC;

    if ($this->GIF_colorF == 1) {
      GIF_Decoder::GIFGetByte(3 * $this->GIF_colorS);
      $this->GIF_global = $this->GIF_buffer;
    }
    for ($cycle = 1; $cycle;) {
      if (GIF_Decoder::GIFGetByte(1)) {
        switch ($this->GIF_buffer [0]) {
          case 0x21:
            GIF_Decoder::GIFReadExtensions();
            break;
          case 0x2C:
            GIF_Decoder::GIFReadDescriptor();
            break;
          case 0x3B:
            $cycle = 0;
            break;
        }
      } else {
        $cycle = 0;
      }
    }
  }
  /*
    :::::::::::::::::::::::::::::::::::::::::::::::::::
    ::
    ::    GIFReadExtension ( )
    ::
   */

  function GIFReadExtensions()
  {
    GIF_Decoder::GIFGetByte(1);
    if ($this->GIF_buffer [0] == 0xff) {
      for (;;) {
        GIF_Decoder::GIFGetByte(1);
        if (( $u = $this->GIF_buffer [0] ) == 0x00) {
          break;
        }
        GIF_Decoder::GIFGetByte($u);
        if ($u == 0x03) {
          $this->GIF_anloop = ( $this->GIF_buffer [1] | $this->GIF_buffer [2] << 8 );
        }
      }
    } else {
      for (;;) {
        GIF_Decoder::GIFGetByte(1);
        if (( $u = $this->GIF_buffer [0] ) == 0x00) {
          break;
        }
        GIF_Decoder::GIFGetByte($u);
        if ($u == 0x04) {
          $buf4 = count($this->GIF_buffer) >= 5 ? $this->GIF_buffer [4] : 0;
          if ($buf4 & 0x80) {
            $this->GIF_dispos [] = ( $this->GIF_buffer [0] >> 2 ) - 1;
          } else {
            $this->GIF_dispos [] = ( $this->GIF_buffer [0] >> 2 ) - 0;
          }
          $this->GIF_delays [] = ( $this->GIF_buffer [1] | $this->GIF_buffer [2] << 8 );
          if ($this->GIF_buffer [3]) {
            $this->GIF_TransparentI = $this->GIF_buffer [3];
          }
        }
      }
    }
  }
  /*
    :::::::::::::::::::::::::::::::::::::::::::::::::::
    ::
    ::    GIFReadDescriptor ( )
    ::
   */

  function GIFReadDescriptor()
  {
    $GIF_screen = Array();

    GIF_Decoder::GIFGetByte(9);

    //xurei - metadata saving
    $this->GIF_frames_meta[] = array(
      'left' => $this->GIF_buffer[0] + ($this->GIF_buffer[1] << 8),
      'top' => $this->GIF_buffer[2] + ($this->GIF_buffer[3] << 8),
      'width' => $this->GIF_buffer[4] + ($this->GIF_buffer[5] << 8),
      'height' => $this->GIF_buffer[6] + ($this->GIF_buffer[7] << 8),
    );

    $GIF_screen = $this->GIF_buffer;
    $GIF_colorF = $this->GIF_buffer [8] & 0x80 ? 1 : 0;
    if ($GIF_colorF) {
      $GIF_code = $this->GIF_buffer [8] & 0x07;
      $GIF_sort = $this->GIF_buffer [8] & 0x20 ? 1 : 0;
    } else {
      $GIF_code = $this->GIF_colorC;
      $GIF_sort = $this->GIF_sorted;
    }
    $GIF_size = 2 << $GIF_code;
    $this->GIF_screen [4] &= 0x70;
    $this->GIF_screen [4] |= 0x80;
    $this->GIF_screen [4] |= $GIF_code;
    if ($GIF_sort) {
      $this->GIF_screen [4] |= 0x08;
    }

    /*
     *
     * GIF Data Begin
     *
     */
    if ($this->GIF_TransparentI) {
      $this->GIF_string = "GIF89a";
    } else {
      $this->GIF_string = "GIF87a";
    }
    GIF_Decoder::GIFPutByte($this->GIF_screen);
    if ($GIF_colorF == 1) {
      GIF_Decoder::GIFGetByte(3 * $GIF_size);
      if ($this->GIF_TransparentI) {
        $this->GIF_TransparentR = $this->GIF_buffer [3 * $this->GIF_TransparentI + 0];
        $this->GIF_TransparentG = $this->GIF_buffer [3 * $this->GIF_TransparentI + 1];
        $this->GIF_TransparentB = $this->GIF_buffer [3 * $this->GIF_TransparentI + 2];
      }
      GIF_Decoder::GIFPutByte($this->GIF_buffer);
    } else {
      if ($this->GIF_TransparentI) {
        $this->GIF_TransparentR = $this->GIF_global [3 * $this->GIF_TransparentI + 0];
        $this->GIF_TransparentG = $this->GIF_global [3 * $this->GIF_TransparentI + 1];
        $this->GIF_TransparentB = $this->GIF_global [3 * $this->GIF_TransparentI + 2];
      }
      GIF_Decoder::GIFPutByte($this->GIF_global);
    }
    if ($this->GIF_TransparentI) {
      $this->GIF_string .= "!\xF9\x04\x1\x0\x0" . chr($this->GIF_TransparentI) . "\x0";
    }
    $this->GIF_string .= chr(0x2C);
    $GIF_screen [8] &= 0x40;
    GIF_Decoder::GIFPutByte($GIF_screen);
    GIF_Decoder::GIFGetByte(1);
    GIF_Decoder::GIFPutByte($this->GIF_buffer);
    for (;;) {
      GIF_Decoder::GIFGetByte(1);
      GIF_Decoder::GIFPutByte($this->GIF_buffer);
      if (( $u = $this->GIF_buffer [0] ) == 0x00) {
        break;
      }

      /* for ($i=0; $i!=$u; ++$i)
        {
        $this->GIF_string .= $this->GIF_stream { $this->GIF_bfseek++ };
        } */
      $this->GIF_string .= substr($this->GIF_stream, $this->GIF_bfseek, $u);
      $this->GIF_bfseek += $u;

      //GIF_Decoder::GIFGetByte ( $u );
      //GIF_Decoder::GIFPutByte ( $this->GIF_buffer );
    }
    $this->GIF_string .= chr(0x3B);
    /*
     *
     * GIF Data End
     *
     */
    $this->GIF_arrays [] = $this->GIF_string;
  }
  /*
    :::::::::::::::::::::::::::::::::::::::::::::::::::
    ::
    ::    GIFGetByte ( $len )
    ::
   */

  function GIFGetByte($len)
  {
    $this->GIF_buffer = new SplFixedArray($len);

    $l = strlen($this->GIF_stream);
    for ($i = 0; $i < $len; $i++) {
      if ($this->GIF_bfseek > $l) {
        $this->GIF_buffer->setSize($i);
        return 0;
      }
      $this->GIF_buffer [$i] = ord($this->GIF_stream [ $this->GIF_bfseek++]);
    }
    return 1;
  }
  /*
    :::::::::::::::::::::::::::::::::::::::::::::::::::
    ::
    ::    GIFPutByte ( $bytes )
    ::
   */

  function GIFPutByte($bytes)
  {
    $out = '';
    foreach ($bytes as $byte) {
      $out .= chr($byte);
    }
    $this->GIF_string .= $out;
  }
  /*
    :::::::::::::::::::::::::::::::::::::::::::::::::::
    ::
    ::    PUBLIC FUNCTIONS
    ::
    ::
    ::    GIFGetFrames ( )
    ::
   */

  function GIFGetFrames()
  {
    return ( $this->GIF_arrays );
  }
  /*
    :::::::::::::::::::::::::::::::::::::::::::::::::::
    ::
    ::    GIFGetFramesMeta ( )
    ::
    ::  xurei - returns metadata as an array of arrays
   */

  function GIFGetFramesMeta()
  {
    return ( $this->GIF_frames_meta );
  }
  /*
    :::::::::::::::::::::::::::::::::::::::::::::::::::
    ::
    ::    GIFGetDelays ( )
    ::
   */

  function GIFGetDelays()
  {
    return ( $this->GIF_delays );
  }
  /*
    :::::::::::::::::::::::::::::::::::::::::::::::::::
    ::
    ::    GIFGetLoop ( )
    ::
   */

  function GIFGetLoop()
  {
    return ( $this->GIF_anloop );
  }
  /*
    :::::::::::::::::::::::::::::::::::::::::::::::::::
    ::
    ::    GIFGetDisposal ( )
    ::
   */

  function GIFGetDisposal()
  {
    return ( $this->GIF_dispos );
  }
  /*
    :::::::::::::::::::::::::::::::::::::::::::::::::::
    ::
    ::    GIFGetTransparentR ( )
    ::
   */

  function GIFGetTransparentR()
  {
    return ( $this->GIF_TransparentR );
  }
  /*
    :::::::::::::::::::::::::::::::::::::::::::::::::::
    ::
    ::    GIFGetTransparentG ( )
    ::
   */

  function GIFGetTransparentG()
  {
    return ( $this->GIF_TransparentG );
  }
  /*
    :::::::::::::::::::::::::::::::::::::::::::::::::::
    ::
    ::    GIFGetTransparentB ( )
    ::
   */

  function GIFGetTransparentB()
  {
    return ( $this->GIF_TransparentB );
  }
  /*
    :::::::::::::::::::::::::::::::::::::::::::::::::::
    ::
    ::    GIFGetTransparentI ( )
    ::
   */

  function GIFGetTransparentI()
  {
    return ( $this->GIF_TransparentI );
  }
}
