2018-11-05 22:30:18 +00:00
< ? php
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * \
* Misc functions *
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
* Move a file from PHP 's temporary area into shimmie' s image storage
* hierarchy , or throw an exception trying .
*
* @ throws UploadException
*/
2019-05-29 17:23:29 +00:00
function move_upload_to_archive ( DataUploadEvent $event ) : void
2019-05-28 16:59:38 +00:00
{
$target = warehouse_path ( " images " , $event -> hash );
if ( !@ copy ( $event -> tmpname , $target )) {
$errors = error_get_last ();
throw new UploadException (
" Failed to copy file from uploads ( { $event -> tmpname } ) to archive ( $target ): " .
" { $errors [ 'type' ] } / { $errors [ 'message' ] } "
);
}
2018-11-05 22:30:18 +00:00
}
/**
* Add a directory full of images
*
2019-05-28 16:31:20 +00:00
* #return string[]
2018-11-05 22:30:18 +00:00
*/
2019-05-28 16:59:38 +00:00
function add_dir ( string $base ) : array
{
$results = [];
2018-11-05 22:30:18 +00:00
2019-05-28 16:59:38 +00:00
foreach ( list_files ( $base ) as $full_path ) {
$short_path = str_replace ( $base , " " , $full_path );
$filename = basename ( $full_path );
2018-11-05 22:30:18 +00:00
2019-05-28 16:59:38 +00:00
$tags = path_to_tags ( $short_path );
$result = " $short_path ( " . str_replace ( " " , " , " , $tags ) . " )... " ;
try {
add_image ( $full_path , $filename , $tags );
$result .= " ok " ;
} catch ( UploadException $ex ) {
$result .= " failed: " . $ex -> getMessage ();
}
$results [] = $result ;
}
2018-11-05 22:30:18 +00:00
2019-05-28 16:59:38 +00:00
return $results ;
2018-11-05 22:30:18 +00:00
}
2019-05-28 16:59:38 +00:00
function add_image ( string $tmpname , string $filename , string $tags ) : void
{
assert ( file_exists ( $tmpname ));
2018-11-05 22:30:18 +00:00
2019-05-28 16:59:38 +00:00
$pathinfo = pathinfo ( $filename );
if ( ! array_key_exists ( 'extension' , $pathinfo )) {
throw new UploadException ( " File has no extension " );
}
$metadata = [];
$metadata [ 'filename' ] = $pathinfo [ 'basename' ];
$metadata [ 'extension' ] = $pathinfo [ 'extension' ];
$metadata [ 'tags' ] = Tag :: explode ( $tags );
$metadata [ 'source' ] = null ;
$event = new DataUploadEvent ( $tmpname , $metadata );
send_event ( $event );
if ( $event -> image_id == - 1 ) {
throw new UploadException ( " File type not recognised " );
}
2018-11-05 22:30:18 +00:00
}
/**
* Given a full size pair of dimensions , return a pair scaled down to fit
* into the configured thumbnail square , with ratio intact
*
2019-05-28 16:31:20 +00:00
* #return int[]
2018-11-05 22:30:18 +00:00
*/
2019-05-28 16:59:38 +00:00
function get_thumbnail_size ( int $orig_width , int $orig_height ) : array
{
global $config ;
2018-11-05 22:30:18 +00:00
2019-05-28 16:59:38 +00:00
if ( $orig_width === 0 ) {
$orig_width = 192 ;
}
if ( $orig_height === 0 ) {
$orig_height = 192 ;
}
2018-11-05 22:30:18 +00:00
2019-05-28 16:59:38 +00:00
if ( $orig_width > $orig_height * 5 ) {
$orig_width = $orig_height * 5 ;
}
if ( $orig_height > $orig_width * 5 ) {
$orig_height = $orig_width * 5 ;
}
2018-11-05 22:30:18 +00:00
2019-05-28 16:59:38 +00:00
$max_width = $config -> get_int ( 'thumb_width' );
$max_height = $config -> get_int ( 'thumb_height' );
2018-11-05 22:30:18 +00:00
2019-05-28 16:59:38 +00:00
$xscale = ( $max_height / $orig_height );
$yscale = ( $max_width / $orig_width );
$scale = ( $xscale < $yscale ) ? $xscale : $yscale ;
2018-11-05 22:30:18 +00:00
2019-05-28 16:59:38 +00:00
if ( $scale > 1 && $config -> get_bool ( 'thumb_upscale' )) {
return [( int ) $orig_width , ( int ) $orig_height ];
} else {
return [( int )( $orig_width * $scale ), ( int )( $orig_height * $scale )];
}
2018-11-05 22:30:18 +00:00
}
2019-06-09 18:22:48 +00:00
/**
* Given a full size pair of dimensions , return a pair scaled down to fit
* into the configured thumbnail square , with ratio intact , using thumb_scaling
*
* #return int[]
*/
function get_thumbnail_size_scaled ( int $orig_width , int $orig_height ) : array
{
global $config ;
if ( $orig_width === 0 ) {
$orig_width = 192 ;
}
if ( $orig_height === 0 ) {
$orig_height = 192 ;
}
if ( $orig_width > $orig_height * 5 ) {
$orig_width = $orig_height * 5 ;
}
if ( $orig_height > $orig_width * 5 ) {
$orig_height = $orig_width * 5 ;
}
$max_size = get_thumbnail_max_size_scaled ();
$max_width = $max_size [ 0 ];
$max_height = $max_size [ 1 ];
$xscale = ( $max_height / $orig_height );
$yscale = ( $max_width / $orig_width );
$scale = ( $xscale < $yscale ) ? $xscale : $yscale ;
if ( $scale > 1 && $config -> get_bool ( 'thumb_upscale' )) {
return [( int ) $orig_width , ( int ) $orig_height ];
} else {
return [( int )( $orig_width * $scale ), ( int )( $orig_height * $scale )];
}
}
function get_thumbnail_max_size_scaled () : array
{
global $config ;
$scaling = $config -> get_int ( " thumb_scaling " );
$max_width = $config -> get_int ( 'thumb_width' ) * ( $scaling / 100 );
$max_height = $config -> get_int ( 'thumb_height' ) * ( $scaling / 100 );
return [ $max_width , $max_height ];
}
function create_thumbnail_convert ( $hash ) : bool
{
global $config ;
$inname = warehouse_path ( " images " , $hash );
$outname = warehouse_path ( " thumbs " , $hash );
$q = $config -> get_int ( " thumb_quality " );
$convert = $config -> get_string ( " thumb_convert_path " );
if ( $convert == null || $convert == " " )
{
return false ;
}
// ffff imagemagick fails sometimes, not sure why
//$format = "'%s' '%s[0]' -format '%%[fx:w] %%[fx:h]' info:";
//$cmd = sprintf($format, $convert, $inname);
//$size = shell_exec($cmd);
//$size = explode(" ", trim($size));
$tsize = get_thumbnail_max_size_scaled ();
$w = $tsize [ 0 ];
$h = $tsize [ 1 ];
// running the call with cmd.exe requires quoting for our paths
$type = $config -> get_string ( 'thumb_type' );
$options = " " ;
if ( ! $config -> get_bool ( 'thumb_upscale' )) {
$options .= " \ > " ;
}
if ( $type == " webp " ) {
$format = '"%s" -thumbnail %ux%u%s -quality %u -background none "%s[0]" %s:"%s"' ;
} else {
$format = '"%s" -flatten -strip -thumbnail %ux%u%s -quality %u "%s[0]" %s:"%s"' ;
}
$cmd = sprintf ( $format , $convert , $w , $h , $options , $q , $inname , $type , $outname );
$cmd = str_replace ( " \" convert \" " , " convert " , $cmd ); // quotes are only needed if the path to convert contains a space; some other times, quotes break things, see github bug #27
exec ( $cmd , $output , $ret );
log_debug ( 'handle_pixel' , " Generating thumbnail with command ` $cmd `, returns $ret " );
if ( $config -> get_bool ( " thumb_optim " , false )) {
exec ( " jpegoptim $outname " , $output , $ret );
}
return true ;
}
function create_thumbnail_ffmpeg ( $hash ) : bool
{
global $config ;
$ffmpeg = $config -> get_string ( " thumb_ffmpeg_path " );
if ( $ffmpeg == null || $ffmpeg == " " ) {
return false ;
}
$inname = warehouse_path ( " images " , $hash );
$outname = warehouse_path ( " thumbs " , $hash );
$orig_size = video_size ( $inname );
$scaled_size = get_thumbnail_size_scaled ( $orig_size [ 0 ], $orig_size [ 1 ]);
$codec = " mjpeg " ;
$quality = $config -> get_int ( " thumb_quality " );
if ( $config -> get_string ( " thumb_type " ) == " webp " ) {
$codec = " libwebp " ;
} else {
// mjpeg quality ranges from 2-31, with 2 being the best quality.
$quality = floor ( 31 - ( 31 * ( $quality / 100 )));
if ( $quality < 2 ) {
$quality = 2 ;
}
}
$args = [
escapeshellarg ( $ffmpeg ),
" -y " , " -i " , escapeshellarg ( $inname ),
" -vf " , " thumbnail,scale= { $scaled_size [ 0 ] } : { $scaled_size [ 1 ] } " ,
" -f " , " image2 " ,
" -vframes " , " 1 " ,
" -c:v " , $codec ,
" -q:v " , $quality ,
escapeshellarg ( $outname ),
];
$cmd = escapeshellcmd ( implode ( " " , $args ));
exec ( $cmd , $output , $ret );
if (( int ) $ret == ( int ) 0 ) {
log_debug ( 'imageboard/misc' , " Generating thumbnail with command ` $cmd `, returns $ret " );
return true ;
} else {
log_error ( 'imageboard/misc' , " Generating thumbnail with command ` $cmd `, returns $ret " );
return false ;
}
}
function video_size ( string $filename ) : array
{
global $config ;
$ffmpeg = $config -> get_string ( " thumb_ffmpeg_path " );
$cmd = escapeshellcmd ( implode ( " " , [
escapeshellarg ( $ffmpeg ),
" -y " , " -i " , escapeshellarg ( $filename ),
" -vstats "
]));
$output = shell_exec ( $cmd . " 2>&1 " );
// error_log("Getting size with `$cmd`");
$regex_sizes = " /Video: .* ([0-9] { 1,4})x([0-9] { 1,4})/ " ;
if ( preg_match ( $regex_sizes , $output , $regs )) {
if ( preg_match ( " /displaymatrix: rotation of (90|270).00 degrees/ " , $output )) {
$size = [ $regs [ 2 ], $regs [ 1 ]];
} else {
$size = [ $regs [ 1 ], $regs [ 2 ]];
}
} else {
$size = [ 1 , 1 ];
}
log_debug ( 'imageboard/misc' , " Getting video size with ` $cmd `, returns $output -- $size[0] , $size[1] " );
return $size ;
}
/**
* Check Memory usage limits
*
* Old check : $memory_use = ( filesize ( $image_filename ) * 2 ) + ( $width * $height * 4 ) + ( 4 * 1024 * 1024 );
* New check : $memory_use = $width * $height * ( $bits_per_channel ) * channels * 2.5
*
* It didn ' t make sense to compute the memory usage based on the NEW size for the image . ( $width * $height * 4 )
* We need to consider the size that we are GOING TO instead .
*
* The factor of 2.5 is simply a rough guideline .
* http :// stackoverflow . com / questions / 527532 / reasonable - php - memory - limit - for - image - resize
*/
function calc_memory_use ( array $info ) : int
{
if ( isset ( $info [ 'bits' ]) && isset ( $info [ 'channels' ])) {
$memory_use = ( $info [ 0 ] * $info [ 1 ] * ( $info [ 'bits' ] / 8 ) * $info [ 'channels' ] * 2.5 ) / 1024 ;
} else {
// If we don't have bits and channel info from the image then assume default values
// of 8 bits per color and 4 channels (R,G,B,A) -- ie: regular 24-bit color
$memory_use = ( $info [ 0 ] * $info [ 1 ] * 1 * 4 * 2.5 ) / 1024 ;
}
return ( int ) $memory_use ;
}
function image_resize_gd ( String $image_filename , array $info , int $new_width , int $new_height ,
string $output_filename = null , string $output_type = null , int $output_quality = 80 )
{
$width = $info [ 0 ];
$height = $info [ 1 ];
if ( $output_type == null ) {
/* If not specified, output to the same format as the original image */
switch ( $info [ 2 ]) {
case IMAGETYPE_GIF : $output_type = " gif " ; break ;
case IMAGETYPE_JPEG : $output_type = " jpeg " ; break ;
case IMAGETYPE_PNG : $output_type = " png " ; break ;
case IMAGETYPE_WEBP : $output_type = " webp " ; break ;
case IMAGETYPE_BMP : $output_type = " bmp " ; break ;
default : throw new ImageResizeException ( " Failed to save the new image - Unsupported image type. " );
}
}
$memory_use = calc_memory_use ( $info );
$memory_limit = get_memory_limit ();
if ( $memory_use > $memory_limit ) {
throw new InsufficientMemoryException ( " The image is too large to resize given the memory limits. ( $memory_use > $memory_limit ) " );
}
$image = imagecreatefromstring ( file_get_contents ( $image_filename ));
if ( $image == false ) {
throw new ImageResizeException ( " Could not load image: " . $image_filename );
}
$image_resized = imagecreatetruecolor ( $new_width , $new_height );
// Handle transparent images
switch ( $info [ 2 ]) {
case IMAGETYPE_GIF :
$transparency = imagecolortransparent ( $image );
$palletsize = imagecolorstotal ( $image );
// If we have a specific transparent color
if ( $transparency >= 0 && $transparency < $palletsize ) {
// Get the original image's transparent color's RGB values
$transparent_color = imagecolorsforindex ( $image , $transparency );
// Allocate the same color in the new image resource
$transparency = imagecolorallocate ( $image_resized , $transparent_color [ 'red' ], $transparent_color [ 'green' ], $transparent_color [ 'blue' ]);
// Completely fill the background of the new image with allocated color.
imagefill ( $image_resized , 0 , 0 , $transparency );
// Set the background color for new image to transparent
imagecolortransparent ( $image_resized , $transparency );
}
break ;
case IMAGETYPE_PNG :
case IMAGETYPE_WEBP :
//
// More info here: http://stackoverflow.com/questions/279236/how-do-i-resize-pngs-with-transparency-in-php
//
imagealphablending ( $image_resized , false );
imagesavealpha ( $image_resized , true );
$transparent_color = imagecolorallocatealpha ( $image_resized , 255 , 255 , 255 , 127 );
imagefilledrectangle ( $image_resized , 0 , 0 , $new_width , $new_height , $transparent_color );
break ;
}
// Actually resize the image.
imagecopyresampled (
$image_resized ,
$image ,
0 ,
0 ,
0 ,
0 ,
$new_width ,
$new_height ,
$width ,
$height
);
switch ( $output_type ) {
case " bmp " :
$result = imagebmp ( $image_resized , $output_filename , true );
break ;
case " webp " :
$result = imagewebp ( $image_resized , $output_filename , $output_quality );
break ;
case " jpg " :
case " jpeg " :
$result = imagejpeg ( $image_resized , $output_filename , $output_quality );
break ;
case " png " :
$result = imagepng ( $image_resized , $output_filename , 9 );
break ;
case " gif " :
$result = imagegif ( $image_resized , $output_filename );
break ;
default :
throw new ImageResizeException ( " Failed to save the new image - Unsupported image type: $output_type " );
}
if ( $result == false ) {
throw new ImageResizeException ( " Failed to save the new image, function returned false when saving type: $output_type " );
}
imagedestroy ( $image_resized );
}