Question:
I am using GD to manipulate images in PHP, based on this code
<?php
/**
Función para detectar si una imagen es animación
*/
function is_ani($filename) {
if(!($fh = @fopen($filename, 'rb')))
return false;
$count = 0;
while(!feof($fh) && $count < 2) {
$chunk = fread($fh, 1024 * 100);
$count += preg_match_all('#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches);
}
fclose($fh);
return $count > 1;
}
?>
I intend to separate the frames inside the GIF (I can even use this library ) and save them in WEBP format.
How can I assemble/create an animated image in WEBP format using GD in PHP?
–EDITED–
PHP version:
PHP 7.2.4 (built: Apr 25 2018 12:05:18) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
gd_info:
#php -r 'print_r(gd_info());'
Array
(
[GD Version] => bundled (2.1.0 compatible)
[FreeType Support] =>
[GIF Read Support] => 1
[GIF Create Support] => 1
[JPEG Support] => 1
[PNG Support] => 1
[WBMP Support] => 1
[XPM Support] =>
[XBM Support] => 1
[WebP Support] => 1
[BMP Support] => 1
[JIS-mapped Japanese Font Support] =>
)
This system is running on Debian 8.
–EDITED 2–
Using the alo-malbarez project as a base and a class to extract the frames of the GIF's, I made a project with a cleaner and clearer extractor class for people who need specific fragments of the GIF. In this project I join both classes to make a GIF to WEBP converter, it still has bugs but I'm working on correcting them.
Answer:
The issue of how you extract the gif frames would be missing, but for that there is a link at the end
Here is a PoC on how to build an animated webp in php using only GD (with webp support)
PART 1 : encode each frame
1) the frame is converted to webp with GD and captured in a variable with ob_start
2) we discard the fileformat header of this stream and save the frameData (starts with "VP8 " and goes to the end) in an array (buffer) along with the width, height and duration info of the frame
3) returns to 1 until there are no more frames
note: to change the compression quality edit the line
imagewebp($image);
putting for example
imagewebp($image, null, 75);
the null
is so that it does not save a file
PART 2: build the animated webp according to the specification https://developers.google.com/speed/webp/docs/riff_container
An animated image with EXIF metadata may look as follows:
RIFF/WEBP
+- VP8X (descriptions of features used)
+- ANIM (global animation parameters)
+- ANMF (frame1 parameters + data)
+- ANMF (frame2 parameters + data)
+- ANMF (frame3 parameters + data)
+- ANMF (frame4 parameters + data)
+- EXIF (metadata) <- opcional
0) open stream or set in a var
1) header file format (RIFF+filesize+WEBP)
1.a) calculate the file size (uint32) the file size is the total bytes of the header file -8, or the total bytes of all chunks + 4
2) VP8X chunk header
2.a) calculate chunksize (uint32) = 10
2.b) set the alpha and animation bits to 1
2.c) encode canvas width and height to uint24 (-1)
3) ANIM chunk header
3.a) compute chunksize (uint32) = 6
3.b) background color: BGRA (0,0,0,0)
3.c) loop count (uint16) 0=infinityandbeyond
4) for each frame saved in the array (buffer) PART 1.2
4.a) ANMF chunk header
4.b) calculate chunksize (uint32) = 16 + total bytes in frameData
4.c) X,Y origin of the frame (uint24) (/2)
4.d) width and height of the frame (uint24) (-1)
4.e) duration milliseconds (uint24)
4.f) reserved (6 bits) + alpha blending (1 bit) + discard frame (1 bit)
4.g) the Data frame saved in the buffer PART 1.2
5) save to disk or close stream
#!/usr/bin/env php
<?php
function toUint32($n){
$ar = unpack("C*", pack("L", $n));
return $ar;
}
function toUint24($n){
$ar = unpack("C*", pack("L", $n));
array_pop($ar);
return $ar;
}
function toUint16($n){
$ar = unpack("C*", pack("S", $n));
return $ar;
}
function bytesToString($bytes){
return implode(array_map("chr", $bytes));
}
function binaryToBytes($bits){
$octets = explode(' ', $bits);
return array_map("bindec", $octets);
}
$oWidth=120;
$oHeight=20;
$frameArray = [];
function getFrameData($image, $msec){
$w = imagesx($image);
$h = imagesy($image);
ob_start();
imagewebp($image);
if (ob_get_length() % 2 == 1) :
echo "\0";
endif;
$image_data = ob_get_contents();
ob_end_clean();
$frameData = substr($image_data, strpos($image_data, "VP8 "));
return Array(
"frameData" => $frameData,
"duration" => bytesToString(toUint24($msec)),
"width" => bytesToString(toUint24($w - 1)),
"height" => bytesToString(toUint24($h -1 )),
);
}
// Create a blank image and add some text
$im = imagecreatetruecolor($oWidth, $oHeight);
$text_color = imagecolorallocate($im, 233, 14, 91);
imagestring($im, 1, 5, 5, 'WebP with PHP', $text_color);
$frameArray[] = getFrameData($im, 70);
imagedestroy($im);
// Create a blank image and add some text
$im = imagecreatetruecolor($oWidth, $oHeight);
$text_color = imagecolorallocate($im, 14, 233, 91);
imagestring($im, 1, 5, 5, 'WebP with PHP', $text_color);
$frameArray[] = getFrameData($im, 70);
imagedestroy($im);
// create new WEBP
$fileWEBP = "";
$fileHeader = "";
$fileContents = "";
// Chunk HEADER VP8X
$fileContents .="VP8X";
$headChunkSize = bytesToString(toUint32(10));
// bit flags Rsv|I|L|E|X|A|R| Reserved
$oVP8XflagsBin = "00010010 00000000 00000000 00000000";
$oVP8Xflags = bytesToString(binaryToBytes($oVP8XflagsBin));
$oCanvasSize = bytesToString(toUint24($oWidth-1)).bytesToString(toUint24($oHeight-1));
$fileContents .= $headChunkSize. $oVP8Xflags. $oCanvasSize;
// Chunk HEADER ANIM
$fileContents .="ANIM";
$animChunkSize = bytesToString(toUint32(6));
// loop count 16bits, 0 = infinito
// bytesToString(toUint16(0));
$oLoopCount = str_repeat(chr(0), 2);
// 32bits BGRA, Blue Green Red Alpha (0,0,0,0)
$oBackGround = str_repeat(chr(0), 4);
$fileContents .= $animChunkSize . $oBackGround . $oLoopCount;
foreach ($frameArray as $frame) :
// Chunk HEADER ANMF
$fileContents .="ANMF";
$frameDataChunkSize = bytesToString(toUint32(strlen($frame['frameData'])+16));
// frame origin X Y
// bytesToString(toUint24(originX)) . bytesToString(toUint24(originY))
// (0,0)
$fOrigin = str_repeat(chr(0), 6);
// frame size (uint24) width-1 , (uint24) height-1
$fSize = $frame['width'].$frame['height'];
// frame duration in miliseconds (uint24)
$fDuration = $frame['duration'];
// frame options bits
// reserved (6 bits) + alpha blending (1 bit) + descartar frame (1 bit)
$fFlagsBin = "00000010";
$fFlags = bytesToString(binaryToBytes($fFlagsBin));
// chunk payload
$fileContents .= $frameDataChunkSize.$fOrigin.$fSize.$fDuration.$fFlags.$frame['frameData'];
endforeach;
// calculate Size and build file header
$fileSize = bytesToString(toUint32(strlen($fileContents)+4));
$fileHeader = "RIFF".$fileSize."WEBP";
$fileWEBP = $fileHeader.$fileContents;
file_put_contents('animated.webp',$fileWEBP);
link of the repo in case you want to extend it or make a fork with the part of extracting the gif frames
https://github.com/aloMalbarez/php-GD-Animated-webp
to extract the gif frames you can use this class
https://github.com/jacoka/GIFDecoder
wave foreach ( $gifDecoder -> GIFGetFrames ( ) as $frame )
Edit I put a link to the GIFDecoder class of the original author