'UNKOWN-IMAGE', 1 => 'gif', 2 => 'jpg', 3 => 'png' ]; $return_data = ''; $CONVERT = ''; // if CONVERT is not defined, abort /** @phan-suppress-next-line PhanUndeclaredConstant */ if (defined('CONVERT') && is_executable(CONVERT)) { /** @phan-suppress-next-line PhanUndeclaredConstant */ $CONVERT = CONVERT; } else { throw new \RuntimeException('CONVERT set binary is not executable or CONVERT is not defined'); } if (!empty($cache_source)) { $tmp_src = $cache_source; } else { $tmp_src = BASE . TMP; } // check if pic has a path, and override next sets if (strstr($pic, '/') === false) { if (empty($path)) { $path = BASE; } $filename = $path . MEDIA . PICTURES . $pic; } else { $filename = $pic; // and get the last part for pic (the filename) $tmp = explode('/', $pic); $pic = $tmp[(count($tmp) - 1)]; } // does this picture exist and is it a picture if (!file_exists($filename) || !is_file($filename)) { if (!empty($dummy) && strstr($dummy, '/') === false) { // check if we have the "dummy" image flag set $filename = PICTURES . ICONS . strtoupper($dummy) . ".png"; /** @phpstan-ignore-next-line */ if (!empty($dummy) && file_exists($filename) && is_file($filename)) { $return_data = $filename; } else { throw new \RuntimeException('Could not set dummy return file: ' . $dummy . ' in ' . $filename); } } else { $return_data = $dummy; } return $return_data; } // resize image [$width, $height, $type] = getimagesize($filename) ?: [0, 0, 0]; $convert_prefix = ''; $create_file = false; $delete_filename = ''; // check if we can skip the PDF creation: if we have size, if do not have type, we assume type png if (!$type) { $check_thumb = $tmp_src . 'thumb_' . $pic . '_' . $size_x . 'x' . $size_y . '.' . $image_types[3]; if (!is_file($check_thumb)) { $create_file = true; } else { $type = 3; } } // if type is not in the list, but returns as PDF, we need to convert to JPEG before if (!$type) { $output = []; $return = null; // is this a PDF, if no, return from here with nothing $convert_prefix = 'png:'; # TEMP convert to PNG, we then override the file name $convert_string = $CONVERT . ' ' . $filename . ' ' . $convert_prefix . $filename . '_TEMP'; $status = exec($convert_string, $output, $return); $filename .= '_TEMP'; // for delete, in case we need to glob $delete_filename = $filename; // find file, if we can't find base name, use -0 as the first one (ignore other pages in multiple ones) if (!is_file($filename)) { $filename .= '-0'; } [$width, $height, $type] = getimagesize($filename) ?: [0, 0, 0]; } // if no size given, set size to original if (!$size_x || $size_x < 1) { $size_x = $width; } if (!$size_y || $size_y < 1) { $size_y = $height; } $thumb = 'thumb_' . $pic . '_' . $size_x . 'x' . $size_y . '.' . $image_types[$type]; $thumbnail = $tmp_src . $thumb; // check if we already have this picture converted if (!is_file($thumbnail) || $clear_cache == true) { // convert the picture if ($width > $size_x) { $convert_string = $CONVERT . ' -geometry ' . $size_x . 'x ' . $filename . ' ' . $thumbnail; $status = exec($convert_string, $output, $return); // get the size of the converted data, if converted if (is_file($thumbnail)) { [$width, $height, $type] = getimagesize($thumbnail) ?: [0, 0, 0]; } } if ($height > $size_y) { $convert_string = $CONVERT . ' -geometry x' . $size_y . ' ' . $filename . ' ' . $thumbnail; $status = exec($convert_string, $output, $return); } } if (!is_file($thumbnail)) { copy($filename, $thumbnail); } $return_data = $thumb; // if we have a delete filename, delete here with glob if ($delete_filename) { array_map('unlink', glob($delete_filename . '*') ?: []); } return $return_data; } /** * simple thumbnail creation for jpeg, png only * TODO: add other types like gif, etc * - bails with false on failed create * - if either size_x or size_y are empty (0) * the resize is to max of one size * if both are set, those are the max sizes (aspect ration is always ekpt) * - if path is not given will cache folder for current path set * * @param string $filename source file name with full path * @param int $thumb_width thumbnail width * @param int $thumb_height thumbnail height * @param string|null $cache_folder path for thumbnail cache * @param string|null $web_folder frontend path for output * @param bool $create_dummy if we encounter an invalid file * create a dummy image file and return it * @param bool $use_cache default to true, set to false to skip * creating new image if exists * @param bool $high_quality default to true, uses sample version, * set to false to not use (default true) * to use quick but less nice version * @param int $jpeg_quality default 80, set image quality for jpeg only * @return string thumbnail with path * @throws \UnexpectedValueException input values for filename or cache_folder are wrong * @throws \RuntimeException convert (gd) failed */ public static function createThumbnailSimple( string $filename, int $thumb_width = 0, int $thumb_height = 0, ?string $cache_folder = null, // will be not null in future ?string $web_folder = null, bool $create_dummy = true, bool $use_cache = true, bool $high_quality = true, int $jpeg_quality = 80 ): string { $thumbnail = false; $exception_message = 'Could not create thumbnail'; // $this->debug('IMAGE PREPARE', "FILE: $filename (exists " // .(string)file_exists($filename)."), WIDTH: $thumb_width, HEIGHT: $thumb_height"); if ( $cache_folder === null || $web_folder === null ) { /** @deprecated Do use cache folder and web folder parameters */ trigger_error( 'params $cache_folder and $web_folder must be set. Setting via constants is deprecated', E_USER_DEPRECATED ); // NOTE: we need to depracte this $cache_folder = BASE . CONTENT_PATH . LAYOUT . CACHE . IMAGES; $web_folder = LAYOUT . CACHE . IMAGES; if (!is_dir($cache_folder)) { if (false === mkdir($cache_folder)) { $cache_folder = BASE . CONTENT_PATH . LAYOUT . CACHE; $web_folder = LAYOUT . CACHE; } } } // check that input image exists and is either jpeg or png // also fail if the basic CACHE folder does not exist at all if (!file_exists($filename)) { // return $thumbnail; throw new \UnexpectedValueException('Missing image file: ' . $filename); } if (!is_dir($cache_folder)) { // return $thumbnail; throw new \UnexpectedValueException('Cache folder is not a directory: ' . $cache_folder); } if (!is_writable($cache_folder)) { // return $thumbnail; throw new \UnexpectedValueException('Cache folder is not writeable: ' . $cache_folder); } // $this->debug('IMAGE PREPARE', "FILENAME OK, THUMB WIDTH/HEIGHT OK"); [$inc_width, $inc_height, $img_type] = getimagesize($filename) ?: [0, 0, null]; $thumbnail_write_path = null; $thumbnail_web_path = null; // path set first if ( $img_type == IMAGETYPE_JPEG || $img_type == IMAGETYPE_PNG || $create_dummy === true ) { // $this->debug('IMAGE PREPARE', "IMAGE TYPE OK: ".$inc_width.'x'.$inc_height); // set thumbnail paths $thumbnail_write_path = $cache_folder; $thumbnail_web_path = $web_folder; } // do resize or fall back on dummy run if ( $img_type == IMAGETYPE_JPEG || $img_type == IMAGETYPE_PNG ) { // if missing width or height in thumb, use the set one if ($thumb_width == 0) { $thumb_width = $inc_width; } if ($thumb_height == 0) { $thumb_height = $inc_height; } // check resize parameters if ($inc_width > $thumb_width || $inc_height > $thumb_height) { $thumb_width_r = 1; $thumb_height_r = 1; // we need to keep the aspect ration on longest side if ( ($inc_height > $inc_width && // and the height is bigger than thumb set $inc_height > $thumb_height) || // or the height is smaller or equal width // but the width for the thumb is equal to the image height ($inc_height <= $inc_width && $inc_width == $thumb_width ) ) { // $this->debug('IMAGE PREPARE', 'HEIGHT > WIDTH'); $ratio = $inc_height / $thumb_height; $thumb_width_r = (int)ceil($inc_width / $ratio); $thumb_height_r = $thumb_height; } else { // $this->debug('IMAGE PREPARE', 'WIDTH > HEIGHT'); $ratio = $inc_width / $thumb_width; $thumb_width_r = $thumb_width; $thumb_height_r = (int)ceil($inc_height / $ratio); } // $this->debug('IMAGE PREPARE', "Ratio: $ratio, Target size $thumb_width_r x $thumb_height_r"); // set output thumbnail name $thumbnail = 'thumb-' . pathinfo($filename)['filename'] . '-' . $thumb_width_r . 'x' . $thumb_height_r; if ( $use_cache === false || !file_exists($thumbnail_write_path . $thumbnail) ) { // image, copy source image, offset in image, source x/y, new size, source image size if ($thumb_width_r < 1) { $thumb_width_r = 1; } if ($thumb_height_r < 1) { $thumb_height_r = 1; } $thumb = imagecreatetruecolor($thumb_width_r, $thumb_height_r); if ($thumb === false) { throw new \RuntimeException( 'imagecreatetruecolor failed: ' . $thumbnail . ', ' . $filename, 1 ); } if ($img_type == IMAGETYPE_PNG) { $imagecolorallocatealpha = imagecolorallocatealpha($thumb, 0, 0, 0, 127); if ($imagecolorallocatealpha === false) { throw new \RuntimeException( 'imagecolorallocatealpha failed: ' . $thumbnail . ', ' . $filename, 2 ); } // preservere transaprency imagecolortransparent( $thumb, $imagecolorallocatealpha ); imagealphablending($thumb, false); imagesavealpha($thumb, true); } $source = null; switch ($img_type) { case IMAGETYPE_JPEG: $source = imagecreatefromjpeg($filename); break; case IMAGETYPE_PNG: $source = imagecreatefrompng($filename); break; } // check that we have a source image resource if ($source !== null && $source !== false) { // resize no shift if ($high_quality === true) { imagecopyresized( $thumb, $source, 0, 0, 0, 0, $thumb_width_r, $thumb_height_r, $inc_width, $inc_height ); } else { imagecopyresampled( $thumb, $source, 0, 0, 0, 0, $thumb_width_r, $thumb_height_r, $inc_width, $inc_height ); } // write file switch ($img_type) { case IMAGETYPE_JPEG: imagejpeg($thumb, $thumbnail_write_path . $thumbnail, $jpeg_quality); break; case IMAGETYPE_PNG: imagepng($thumb, $thumbnail_write_path . $thumbnail); break; } // free up resources (in case we are called in a loop) imagedestroy($source); imagedestroy($thumb); } else { throw new \RuntimeException( 'Invalid source image file. Only JPEG/PNG are allowed: ' . $filename, 3 ); } } } else { // we just copy over the image as is, we never upscale $thumbnail = 'thumb-' . pathinfo($filename)['filename'] . '-' . $inc_width . 'x' . $inc_height; if ( $use_cache === false || !file_exists($thumbnail_write_path . $thumbnail) ) { copy($filename, $thumbnail_write_path . $thumbnail); } } // add output path $thumbnail = $thumbnail_web_path . $thumbnail; } elseif ($create_dummy === true) { // create dummy image in the thumbnail size // if one side is missing, use the other side to create a square if (!$thumb_width) { $thumb_width = $thumb_height; } if (!$thumb_height) { $thumb_height = $thumb_width; } // do we have an image already? $thumbnail = 'thumb-' . pathinfo($filename)['filename'] . '-' . $thumb_width . 'x' . $thumb_height; if ( $use_cache === false || !file_exists($thumbnail_write_path . $thumbnail) ) { // if both are unset, set to 250 if ($thumb_height < 1) { $thumb_height = 250; } if ($thumb_width < 1) { $thumb_width = 250; } $thumb = imagecreatetruecolor($thumb_width, $thumb_height); if ($thumb === false) { throw new \RuntimeException( 'imagecreatetruecolor dummy failed: ' . $thumbnail . ', ' . $filename, 3 ); } // add outside border px = 5% (rounded up) // eg 50px -> 2.5px $gray = imagecolorallocate($thumb, 200, 200, 200); $white = imagecolorallocate($thumb, 255, 255, 255); if ($gray === false || $white === false) { throw new \RuntimeException( 'imagecolorallocate/imagecolorallocate dummy failed: ' . $thumbnail . ', ' . $filename, 2 ); } // fill gray background imagefill($thumb, 0, 0, $gray); // now create rectangle if (imagesx($thumb) < imagesy($thumb)) { $width = (int)round(imagesx($thumb) / 100 * 5); } else { $width = (int)round(imagesy($thumb) / 100 * 5); } imagefilledrectangle( $thumb, 0 + $width, 0 + $width, imagesx($thumb) - $width, imagesy($thumb) - $width, $white ); // add "No valid images source" // OR add circle // * find center // * width/height is 75% of size - border // smaller size is taken $base_width = imagesx($thumb) > imagesy($thumb) ? imagesy($thumb) : imagesx($thumb); // get 75% width $cross_width = (int)round((($base_width - ($width * 2)) / 100 * 75) / 2); $center_x = (int)round(imagesx($thumb) / 2); $center_y = (int)round(imagesy($thumb) / 2); imagefilledellipse($thumb, $center_x, $center_y, $cross_width, $cross_width, $gray); // find top left and bottom left for first line imagepng($thumb, $thumbnail_write_path . $thumbnail); } // add web path $thumbnail = $thumbnail_web_path . $thumbnail; } // if still false -> throw exception if ($thumbnail === false) { throw new \RuntimeException($exception_message); } // else return the thumbnail name + output path web return $thumbnail; } /** * reads the rotation info of an file and rotates it to be correctly upright * this is done because not all software honers the exit Orientation flag * only works with jpg or png * * @param string $filename path + filename to rotate. This file must be writeable * @return void * @throws \RuntimeException if exit_read_data is not found * @throws \UnexpectedValueException if file name not writeable or file name not found */ public static function correctImageOrientation(string $filename): void { // function exists & file is writeable, else do nothing if (!function_exists('exif_read_data')) { throw new \RuntimeException('Function \'exit_read_data\' does not exist'); } if (!file_exists($filename) || !is_file($filename)) { throw new \UnexpectedValueException('Missing image file: ' . $filename); } if (!is_writeable($filename)) { throw new \UnexpectedValueException('File name is not writeable: ' . $filename); } [$inc_width, $inc_height, $img_type] = getimagesize($filename) ?: [0, 0, null]; // add @ to avoid "file not supported error" $exif = @exif_read_data($filename); $orientation = null; $img = null; if ($exif && isset($exif['Orientation'])) { $orientation = $exif['Orientation']; } // only if we need to rotate, if 1 it is already upright if ($orientation === null || $orientation == 1) { return; } switch ($img_type) { case IMAGETYPE_JPEG: $img = imagecreatefromjpeg($filename); break; case IMAGETYPE_PNG: $img = imagecreatefrompng($filename); break; } // no image loaded (wrong type) if ($img === null || $img === false) { return; } $deg = 0; // 1 top, 6: left, 8: right, 3: bottom switch ($orientation) { case 3: $deg = 180; break; case 6: $deg = -90; break; case 8: $deg = 90; break; } // rotate if needed if ($deg) { $img = imagerotate($img, $deg, 0); } // rotate failed if ($img === false) { return; } // then rewrite the rotated image back to the disk as $filename switch ($img_type) { case IMAGETYPE_JPEG: imagejpeg($img, $filename); break; case IMAGETYPE_PNG: imagepng($img, $filename); break; } // clean up image if we have an image imagedestroy($img); } } // __END__