Issues (1369)

tools/classes/SpriteLineGif.php (4 issues)

1
<?php
2
/** Created by Gorlum 21.01.2024 01:07 */
3
4
namespace Tools;
5
6
use GIFEndec\Decoder;
7
use GIFEndec\Events\FrameDecodedEvent;
8
use GIFEndec\Frame;
9
use GIFEndec\IO\FileStream;
10
11
class SpriteLineGif extends SpriteLine {
12
  /** @var bool $expandFrame Should frame be expanded for CSS animation? */
13
  protected $expandFrame = true;
14
15
  /** @var Frame[] $frames */
16
  protected $frames = [];
17
  /**
18
   * @var int
19
   */
20
  protected $maxWidth = 0;
21
22
  protected function addImage($imageFile) {
23
    $this->files[] = $imageFile;
24
25
    $this->frames = [];
26
27
    $this->height = $this->width = $this->maxWidth = 0;
28
29
    /** Open GIF as FileStream */
30
    // TODO - own class from loaded
31
    $gifStream = new FileStream($imageFile->fullPath);
32
    /** Create Decoder instance from MemoryStream */
33
    $gifDecoder = new Decoder($gifStream);
34
35
    /** Run decoder. Pass callback function to process decoded Frames when they're ready. */
36
    $gifDecoder->decode(function (FrameDecodedEvent $event) {
37
      $this->frames[] = $event->decodedFrame;
38
39
      $this->width    += $event->decodedFrame->getSize()->getWidth();
40
      $this->maxWidth = max($this->maxWidth, $event->decodedFrame->getSize()->getWidth());
41
42
      $this->height = max($this->height, $event->decodedFrame->getSize()->getHeight());
43
    });
44
    // For EXPAND_FRAME delta width would be equal size of the largest frame
45
//    $this->width = count($this->frames) * reset($this->frames)->getSize()->getWidth();
46
    if ($this->expandFrame) {
47
      $this->width = count($this->frames) * $this->maxWidth;
48
    }
49
  }
50
51
  /**
52
   * GIF image line considered always full
53
   *
54
   * @param $gridSize
55
   *
56
   * @return bool
57
   */
58
  protected function isFull($gridSize) {
59
    return true;
60
  }
61
62
  public function generate($posY, $scaleToPx) {
63
    // Extracting file name from full path
64
    $file     = reset($this->files);
65
    $onlyName = explode('.', $file->fileName);
66
    if (count($onlyName) > 1) {
67
      array_pop($onlyName);
68
    }
69
    $onlyName = implode('.', $onlyName);
70
    // You can't have this chars in CSS qualifier
71
    $onlyName = str_replace(['.', '#'], '_', $onlyName);
72
73
    // Expanding frames. Their sizes can change due to offset
74
    foreach ($this->frames as $i => $frame) {
75
      $this->expandFrame($i);
76
    }
77
78
//    $firstFrame = reset($this->frames);
79
//    $this->width  = imagesx($firstFrame->gdImage) * count($this->frames);
80
//    $this->height = imagesy($firstFrame->gdImage);
81
//    $maxDimension = max(imagesx($firstFrame->gdImage), imagesy($firstFrame->gdImage));
82
    $maxDimension = max($this->maxWidth, $this->height);
83
84
    // Recreating image - if any
85
    unset($this->image);
86
    $this->image = ImageContainer::create($this->width, $this->height);
87
88
    $durations = [];
89
    $position  = 0;
90
    foreach ($this->frames as $i => $frame) {
91
//      $frameGdImage = $this->expandFrame($i);
92
      $frameGdImage = $frame->gdImage;
0 ignored issues
show
The property gdImage does not seem to exist on GIFEndec\Frame.
Loading history...
93
94
      $width  = imagesx($frameGdImage);
95
      $height = imagesy($frameGdImage);
96
97
      $this->image->copyFromGd($frameGdImage, $position, 0);
98
99
//      $frame = $this->frames[$i];
100
      // Fixing duration 0 to 10
101
      $durations[$i] = ($duration = $frame->getDuration()) ? $duration : 10;
102
103
      $css = "%1\$s{$onlyName}_{$i}%2\$s{background-position: -{$position}px -{$posY}px;";
104
105
      // Extra info about frame
106
      $size   = $frame->getSize();
107
      $offset = $frame->getOffset();
108
      $css    = "/* Frame {$size->getWidth()}x{$size->getHeight()} @ ({$offset->getX()},{$offset->getY()}) duration {$frame->getDuration()} disposition {$frame->getDisposalMethod()} */" . $css;
109
110
      if ($scaleToPx > 0) {
111
        if ($maxDimension != $scaleToPx) {
112
          $css .= "zoom: calc({$scaleToPx}/{$maxDimension});";
113
        }
114
      }
115
      $css .= "width: {$width}px;height: {$height}px;}\n";
116
117
      if ($i === 0) {
118
        // If it's first frame - generating CSS for static image
119
        $css = "%1\$s{$onlyName}%2\$s,\n" . $css;
120
      }
121
122
      $this->css .= $css;
123
124
      $position += $width;
125
    }
126
127
    $totalDuration = array_sum($durations);
128
    $durInSec      = round($totalDuration / 100, 4);
129
130
    $animation  = '';
131
    $cumulative = 0;
132
    $position   = 0;
133
    foreach ($durations as $i => $duration) {
134
      $animation .= $cumulative . "%% {background-position-x: {$position}px;}\n";
135
136
      $cumulative += round($duration / $totalDuration * 100, 3);
137
      $position   -= imagesx($this->frames[$i]->gdImage);
138
    }
139
    $animation = "%1\$s{$onlyName}%2\$s {animation: {$onlyName}_animation%2\$s {$durInSec}s step-end infinite;}\n" .
140
      "@keyframes {$onlyName}_animation%2\$s {\n" .
141
      $animation .
142
      "}";
143
144
    $this->css .= $animation;
145
  }
146
147
  /**
148
   * @param int $i
149
   *
150
   * @return resource|\GdImage
151
   */
152
  protected function expandFrame($i) {
153
    /**
154
     * Disposal method
155
     * Values :
156
     *   0 - No disposal specified. The decoder is not required to take any action.
157
     *   1 - Do not dispose. The graphic is to be left in place.
158
     *   2 - Restore to background color. The area used by the graphic must be restored to the background color.
159
     *   3 - Restore to previous. The decoder is required to restore the area overwritten by the graphic with
160
     *       what was there prior to rendering the graphic.
161
     */
162
    $thisFrame = $this->frames[$i];
163
    if (!$this->expandFrame) {
164
      return $thisFrame->gdImage = $thisFrame->createGDImage();
0 ignored issues
show
The property gdImage does not seem to exist on GIFEndec\Frame.
Loading history...
165
    }
166
167
    if ($i === 0) {
168
      // This is first frame
169
      $sizeX = $this->maxWidth;
170
      $sizeY = $this->height;
171
//      $sizeX = $thisFrame->getSize()->getWidth() + $thisFrame->getOffset()->getX();
172
//      $sizeY = $thisFrame->getSize()->getHeight() + $thisFrame->getOffset()->getY();
173
    } else {
174
      $prevFrame = $this->frames[$i - 1];
175
      if (!in_array($prevFrame->getDisposalMethod(), [0, 1, 2])) {
176
        die("Disposal method {$prevFrame->getDisposalMethod()} does not supported yet");
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
177
      }
178
179
      // Creating detached copy of previous frame image
180
      $sizeX = imagesx($prevFrame->gdImage);
181
      $sizeY = imagesy($prevFrame->gdImage);
182
    }
183
    $newGdImage = imagecreatetruecolor($sizeX, $sizeY);
184
    imagealphablending($newGdImage, false);
185
    imagesavealpha($newGdImage, true);
186
    $color = imagecolorallocatealpha($newGdImage, 0, 0, 0, 127);
187
    imagefill($newGdImage, 0, 0, $color);
188
189
    if ($i !== 0) {
190
      imagecopy($newGdImage, $prevFrame->gdImage,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $prevFrame does not seem to be defined for all execution paths leading up to this point.
Loading history...
191
        0, 0,
192
        0, 0, imagesx($prevFrame->gdImage), imagesy($prevFrame->gdImage)
193
      );
194
195
      if ($prevFrame->getDisposalMethod() === 2) {
196
        imagefilledrectangle($newGdImage,
197
          $prevFrame->getOffset()->getX(), $prevFrame->getOffset()->getY(),
198
          $prevFrame->getOffset()->getX() + ($prevFrame->getSize()->getWidth() - 1),
199
          $prevFrame->getOffset()->getY() + ($prevFrame->getSize()->getHeight() - 1),
200
          $color
201
        );
202
      }
203
    }
204
205
    $anImage = $thisFrame->createGDImage();
206
    imagecopy($newGdImage, $anImage,
207
      $thisFrame->getOffset()->getX(), $thisFrame->getOffset()->getY(),
208
      0, 0, imagesx($anImage), imagesy($anImage)
209
    );
210
211
    return $thisFrame->gdImage = $newGdImage;
212
  }
213
214
}
215