mirror of
https://github.com/friendica/friendica
synced 2025-05-05 17:44:10 +02:00
Image handling reworked, new image formats added (#13900)
* Image handling reworked, new image formats added * Updated messages.po * The dot is now part of the file extension * Added WebP in install documentation * Handle unhandled mime types * Fixed animated picture detected
This commit is contained in:
parent
1ea8a4042d
commit
14e5b06029
31 changed files with 541 additions and 386 deletions
|
@ -33,19 +33,107 @@ use Friendica\Object\Image;
|
|||
*/
|
||||
class Images
|
||||
{
|
||||
// @todo add IMAGETYPE_AVIF once our minimal supported PHP version is 8.1.0
|
||||
const IMAGETYPES = [IMAGETYPE_WEBP, IMAGETYPE_PNG, IMAGETYPE_JPEG, IMAGETYPE_GIF, IMAGETYPE_BMP];
|
||||
|
||||
/**
|
||||
* Maps Mime types to Imagick formats
|
||||
* Get the Imagick format for the given image type
|
||||
*
|
||||
* @return array Format map
|
||||
* @param int $imagetype
|
||||
* @return string
|
||||
*/
|
||||
public static function getFormatsMap()
|
||||
public static function getImagickFormatByImageType(int $imagetype): string
|
||||
{
|
||||
return [
|
||||
'image/jpeg' => 'JPG',
|
||||
'image/jpg' => 'JPG',
|
||||
'image/png' => 'PNG',
|
||||
'image/gif' => 'GIF',
|
||||
$formats = [
|
||||
// @todo add "IMAGETYPE_AVIF => 'AVIF'" once our minimal supported PHP version is 8.1.0
|
||||
IMAGETYPE_WEBP => 'WEBP',
|
||||
IMAGETYPE_PNG => 'PNG',
|
||||
IMAGETYPE_JPEG => 'JPEG',
|
||||
IMAGETYPE_GIF => 'GIF',
|
||||
IMAGETYPE_BMP => 'BMP',
|
||||
];
|
||||
|
||||
if (empty($formats[$imagetype])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $formats[$imagetype];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the provided mime type, replace invalid mime types with valid ones.
|
||||
*
|
||||
* @param string $mimetype
|
||||
* @return string
|
||||
*/
|
||||
private static function sanitizeMimeType(string $mimetype): string
|
||||
{
|
||||
$mimetype = current(explode(';', $mimetype));
|
||||
|
||||
if ($mimetype == 'image/jpg') {
|
||||
$mimetype = image_type_to_mime_type(IMAGETYPE_JPEG);
|
||||
} elseif (in_array($mimetype, ['image/vnd.mozilla.apng', 'image/apng'])) {
|
||||
$mimetype = image_type_to_mime_type(IMAGETYPE_PNG);
|
||||
} elseif (in_array($mimetype, ['image/x-ms-bmp', 'image/x-bmp'])) {
|
||||
$mimetype = image_type_to_mime_type(IMAGETYPE_BMP);
|
||||
}
|
||||
|
||||
return $mimetype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace invalid extensions with valid ones.
|
||||
*
|
||||
* @param string $extension
|
||||
* @return string
|
||||
*/
|
||||
private static function sanitizeExtensions(string $extension): string
|
||||
{
|
||||
if (in_array($extension, ['jpg', 'jpe', 'jfif'])) {
|
||||
$extension = image_type_to_extension(IMAGETYPE_JPEG, false);
|
||||
} elseif ($extension == 'apng') {
|
||||
$extension = image_type_to_extension(IMAGETYPE_PNG, false);
|
||||
} elseif ($extension == 'dib') {
|
||||
$extension = image_type_to_extension(IMAGETYPE_BMP, false);
|
||||
}
|
||||
|
||||
return $extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the image type for the given mime type
|
||||
*
|
||||
* @param string $mimetype
|
||||
* @return integer
|
||||
*/
|
||||
public static function getImageTypeByMimeType(string $mimetype): int
|
||||
{
|
||||
$mimetype = self::sanitizeMimeType($mimetype);
|
||||
|
||||
foreach (self::IMAGETYPES as $type) {
|
||||
if ($mimetype == image_type_to_mime_type($type)) {
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
|
||||
Logger::debug('Undetected mimetype', ['mimetype' => $mimetype]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the extension for the given image type
|
||||
*
|
||||
* @param integer $type
|
||||
* @return string
|
||||
*/
|
||||
public static function getExtensionByImageType(int $type): string
|
||||
{
|
||||
if (empty($type)) {
|
||||
Logger::debug('Invalid image type', ['type' => $type]);
|
||||
return '';
|
||||
}
|
||||
|
||||
return image_type_to_extension($type);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -56,51 +144,40 @@ class Images
|
|||
*/
|
||||
public static function getExtensionByMimeType(string $mimetype): string
|
||||
{
|
||||
switch ($mimetype) {
|
||||
case 'image/png':
|
||||
$imagetype = IMAGETYPE_PNG;
|
||||
break;
|
||||
|
||||
case 'image/gif':
|
||||
$imagetype = IMAGETYPE_GIF;
|
||||
break;
|
||||
|
||||
case 'image/jpeg':
|
||||
case 'image/jpg':
|
||||
$imagetype = IMAGETYPE_JPEG;
|
||||
break;
|
||||
|
||||
default: // Unknown type must be a blob then
|
||||
return 'blob';
|
||||
break;
|
||||
if (empty($mimetype)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return image_type_to_extension($imagetype);
|
||||
return self::getExtensionByImageType(self::getImageTypeByMimeType($mimetype));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns supported image mimetypes and corresponding file extensions
|
||||
* Returns supported image mimetypes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function supportedTypes(): array
|
||||
public static function supportedMimeTypes(): array
|
||||
{
|
||||
$types = [
|
||||
'image/jpeg' => 'jpg',
|
||||
'image/jpg' => 'jpg',
|
||||
];
|
||||
$types = [];
|
||||
|
||||
if (class_exists('Imagick')) {
|
||||
// Imagick::queryFormats won't help us a lot there...
|
||||
// At least, not yet, other parts of friendica uses this array
|
||||
$types += [
|
||||
'image/png' => 'png',
|
||||
'image/gif' => 'gif'
|
||||
];
|
||||
} elseif (imagetypes() & IMG_PNG) {
|
||||
$types += [
|
||||
'image/png' => 'png'
|
||||
];
|
||||
// @todo enable, once our lowest supported PHP version is 8.1.0
|
||||
//if (imagetypes() & IMG_AVIF) {
|
||||
// $types[] = image_type_to_mime_type(IMAGETYPE_AVIF);
|
||||
//}
|
||||
if (imagetypes() & IMG_WEBP) {
|
||||
$types[] = image_type_to_mime_type(IMAGETYPE_WEBP);
|
||||
}
|
||||
if (imagetypes() & IMG_PNG) {
|
||||
$types[] = image_type_to_mime_type(IMAGETYPE_PNG);
|
||||
}
|
||||
if (imagetypes() & IMG_JPG) {
|
||||
$types[] = image_type_to_mime_type(IMAGETYPE_JPEG);
|
||||
}
|
||||
if (imagetypes() & IMG_GIF) {
|
||||
$types[] = image_type_to_mime_type(IMAGETYPE_GIF);
|
||||
}
|
||||
if (imagetypes() & IMG_BMP) {
|
||||
$types[] = image_type_to_mime_type(IMAGETYPE_BMP);
|
||||
}
|
||||
|
||||
return $types;
|
||||
|
@ -115,45 +192,69 @@ class Images
|
|||
* @return string MIME type
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getMimeTypeByData(string $image_data, string $filename = '', string $default = ''): string
|
||||
public static function getMimeTypeByData(string $image_data): string
|
||||
{
|
||||
if (substr($default, 0, 6) == 'image/') {
|
||||
Logger::info('Using default mime type', ['filename' => $filename, 'mime' => $default]);
|
||||
return $default;
|
||||
}
|
||||
|
||||
$image = @getimagesizefromstring($image_data);
|
||||
if (!empty($image['mime'])) {
|
||||
Logger::info('Mime type detected via data', ['filename' => $filename, 'default' => $default, 'mime' => $image['mime']]);
|
||||
return $image['mime'];
|
||||
}
|
||||
|
||||
return self::guessTypeByExtension($filename);
|
||||
Logger::debug('Undetected mime type', ['image' => $image, 'size' => strlen($image_data)]);
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch image mimetype from the image data or guessing from the file name
|
||||
* Checks if the provided mime type is supported by the system
|
||||
*
|
||||
* @param string $sourcefile Source file of the image
|
||||
* @param string $filename File name (for guessing the type via the extension)
|
||||
* @param string $default default MIME type
|
||||
* @return string MIME type
|
||||
* @throws \Exception
|
||||
* @param string $mimetype
|
||||
* @return boolean
|
||||
*/
|
||||
public static function getMimeTypeBySource(string $sourcefile, string $filename = '', string $default = ''): string
|
||||
public static function isSupportedMimeType(string $mimetype): bool
|
||||
{
|
||||
if (substr($default, 0, 6) == 'image/') {
|
||||
Logger::info('Using default mime type', ['filename' => $filename, 'mime' => $default]);
|
||||
return $default;
|
||||
if (substr($mimetype, 0, 6) != 'image/') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$image = @getimagesize($sourcefile);
|
||||
if (!empty($image['mime'])) {
|
||||
Logger::info('Mime type detected via file', ['filename' => $filename, 'default' => $default, 'image' => $image]);
|
||||
return $image['mime'];
|
||||
return in_array(self::sanitizeMimeType($mimetype), self::supportedMimeTypes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the provided mime type is supported. If not, it is fetched from the provided image data.
|
||||
*
|
||||
* @param string $mimetype
|
||||
* @param string $image_data
|
||||
* @return string
|
||||
*/
|
||||
public static function addMimeTypeByDataIfInvalid(string $mimetype, string $image_data): string
|
||||
{
|
||||
$mimetype = self::sanitizeMimeType($mimetype);
|
||||
|
||||
if (($image_data == '') || self::isSupportedMimeType($mimetype)) {
|
||||
return $mimetype;
|
||||
}
|
||||
|
||||
return self::guessTypeByExtension($filename);
|
||||
$alternative = self::getMimeTypeByData($image_data);
|
||||
return $alternative ?: $mimetype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the provided mime type is supported. If not, it is fetched from the provided file name.
|
||||
*
|
||||
* @param string $mimetype
|
||||
* @param string $filename
|
||||
* @return string
|
||||
*/
|
||||
public static function addMimeTypeByExtensionIfInvalid(string $mimetype, string $filename): string
|
||||
{
|
||||
$mimetype = self::sanitizeMimeType($mimetype);
|
||||
|
||||
if (($filename == '') || self::isSupportedMimeType($mimetype)) {
|
||||
return $mimetype;
|
||||
}
|
||||
|
||||
$alternative = self::guessTypeByExtension($filename);
|
||||
return $alternative ?: $mimetype;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -165,17 +266,24 @@ class Images
|
|||
*/
|
||||
public static function guessTypeByExtension(string $filename): string
|
||||
{
|
||||
$ext = pathinfo(parse_url($filename, PHP_URL_PATH), PATHINFO_EXTENSION);
|
||||
$types = self::supportedTypes();
|
||||
$type = 'image/jpeg';
|
||||
foreach ($types as $m => $e) {
|
||||
if ($ext == $e) {
|
||||
$type = $m;
|
||||
if (empty($filename)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$ext = strtolower(pathinfo(parse_url($filename, PHP_URL_PATH), PATHINFO_EXTENSION));
|
||||
$ext = self::sanitizeExtensions($ext);
|
||||
if ($ext == '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
foreach (self::IMAGETYPES as $type) {
|
||||
if ($ext == image_type_to_extension($type, false)) {
|
||||
return image_type_to_mime_type($type);
|
||||
}
|
||||
}
|
||||
|
||||
Logger::info('Mime type guessed via extension', ['filename' => $filename, 'type' => $type]);
|
||||
return $type;
|
||||
Logger::debug('Unhandled extension', ['filename' => $filename, 'extension' => $ext]);
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -256,7 +364,7 @@ class Images
|
|||
return [];
|
||||
}
|
||||
|
||||
$image = new Image($img_str);
|
||||
$image = new Image($img_str, '', $url);
|
||||
|
||||
if ($image->isValid()) {
|
||||
$data['blurhash'] = $image->getBlurHash();
|
||||
|
@ -344,7 +452,7 @@ class Images
|
|||
{
|
||||
return self::getBBCodeByUrl(
|
||||
DI::baseUrl() . '/photos/' . $nickname . '/image/' . $resource_id,
|
||||
DI::baseUrl() . '/photo/' . $resource_id . '-' . $preview. '.' . $ext,
|
||||
DI::baseUrl() . '/photo/' . $resource_id . '-' . $preview. $ext,
|
||||
$description
|
||||
);
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ class ParseUrl
|
|||
return [];
|
||||
}
|
||||
|
||||
$contenttype = $curlResult->getHeader('Content-Type')[0] ?? '';
|
||||
$contenttype = $curlResult->getContentType();
|
||||
if (empty($contenttype)) {
|
||||
return ['application', 'octet-stream'];
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue