Upgrade to 4.0.0
This commit is contained in:
@@ -13,16 +13,7 @@ namespace Kirby\Image;
|
||||
*/
|
||||
class Camera
|
||||
{
|
||||
/**
|
||||
* Make exif data
|
||||
*/
|
||||
protected string|null $make;
|
||||
|
||||
/**
|
||||
* Model exif data
|
||||
*
|
||||
* @var
|
||||
*/
|
||||
protected string|null $model;
|
||||
|
||||
public function __construct(array $exif)
|
||||
@@ -68,6 +59,7 @@ class Camera
|
||||
|
||||
/**
|
||||
* Improved `var_dump` output
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
|
@@ -3,6 +3,8 @@
|
||||
namespace Kirby\Image;
|
||||
|
||||
use Exception;
|
||||
use Kirby\Image\Darkroom\GdLib;
|
||||
use Kirby\Image\Darkroom\ImageMagick;
|
||||
|
||||
/**
|
||||
* A wrapper around resizing and cropping
|
||||
@@ -17,17 +19,13 @@ use Exception;
|
||||
class Darkroom
|
||||
{
|
||||
public static array $types = [
|
||||
'gd' => 'Kirby\Image\Darkroom\GdLib',
|
||||
'im' => 'Kirby\Image\Darkroom\ImageMagick'
|
||||
'gd' => GdLib::class,
|
||||
'im' => ImageMagick::class
|
||||
];
|
||||
|
||||
protected array $settings = [];
|
||||
|
||||
/**
|
||||
* Darkroom constructor
|
||||
*/
|
||||
public function __construct(array $settings = [])
|
||||
{
|
||||
public function __construct(
|
||||
protected array $settings = []
|
||||
) {
|
||||
$this->settings = array_merge($this->defaults(), $settings);
|
||||
}
|
||||
|
||||
@@ -110,18 +108,24 @@ class Darkroom
|
||||
$options = $this->options($options);
|
||||
$image = new Image($file);
|
||||
|
||||
$dimensions = $image->dimensions();
|
||||
$thumbDimensions = $dimensions->thumb($options);
|
||||
$options['sourceWidth'] = $image->width();
|
||||
$options['sourceHeight'] = $image->height();
|
||||
|
||||
$sourceWidth = $image->width();
|
||||
$sourceHeight = $image->height();
|
||||
$dimensions = $image->dimensions();
|
||||
$thumbDimensions = $dimensions->thumb($options);
|
||||
|
||||
$options['width'] = $thumbDimensions->width();
|
||||
$options['height'] = $thumbDimensions->height();
|
||||
|
||||
// scale ratio compared to the source dimensions
|
||||
$options['scaleWidth'] = $sourceWidth ? $options['width'] / $sourceWidth : null;
|
||||
$options['scaleHeight'] = $sourceHeight ? $options['height'] / $sourceHeight : null;
|
||||
$options['scaleWidth'] = Focus::ratio(
|
||||
$options['width'],
|
||||
$options['sourceWidth']
|
||||
);
|
||||
$options['scaleHeight'] = Focus::ratio(
|
||||
$options['height'],
|
||||
$options['sourceHeight']
|
||||
);
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ namespace Kirby\Image\Darkroom;
|
||||
use claviska\SimpleImage;
|
||||
use Kirby\Filesystem\Mime;
|
||||
use Kirby\Image\Darkroom;
|
||||
use Kirby\Image\Focus;
|
||||
|
||||
/**
|
||||
* GdLib
|
||||
@@ -56,10 +57,34 @@ class GdLib extends Darkroom
|
||||
*/
|
||||
protected function resize(SimpleImage $image, array $options): SimpleImage
|
||||
{
|
||||
// just resize, no crop
|
||||
if ($options['crop'] === false) {
|
||||
return $image->resize($options['width'], $options['height']);
|
||||
}
|
||||
|
||||
// crop based on focus point
|
||||
if (Focus::isFocalPoint($options['crop']) === true) {
|
||||
// get crop coords for focal point:
|
||||
// if image needs to be cropped, crop before resizing
|
||||
if ($focus = Focus::coords(
|
||||
$options['crop'],
|
||||
$options['sourceWidth'],
|
||||
$options['sourceHeight'],
|
||||
$options['width'],
|
||||
$options['height']
|
||||
)) {
|
||||
$image->crop(
|
||||
$focus['x1'],
|
||||
$focus['y1'],
|
||||
$focus['x2'],
|
||||
$focus['y2']
|
||||
);
|
||||
}
|
||||
|
||||
return $image->thumbnail($options['width'], $options['height']);
|
||||
}
|
||||
|
||||
// normal crop with crop anchor
|
||||
return $image->thumbnail(
|
||||
$options['width'],
|
||||
$options['height'] ?? $options['width'],
|
||||
|
@@ -5,6 +5,7 @@ namespace Kirby\Image\Darkroom;
|
||||
use Exception;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Image\Darkroom;
|
||||
use Kirby\Image\Focus;
|
||||
|
||||
/**
|
||||
* ImageMagick
|
||||
@@ -167,20 +168,39 @@ class ImageMagick extends Darkroom
|
||||
return '-thumbnail ' . escapeshellarg(sprintf('%sx%s!', $options['width'], $options['height']));
|
||||
}
|
||||
|
||||
$gravities = [
|
||||
// crop based on focus point
|
||||
if (Focus::isFocalPoint($options['crop']) === true) {
|
||||
if ($focus = Focus::coords(
|
||||
$options['crop'],
|
||||
$options['sourceWidth'],
|
||||
$options['sourceHeight'],
|
||||
$options['width'],
|
||||
$options['height']
|
||||
)) {
|
||||
return sprintf(
|
||||
'-crop %sx%s+%s+%s -resize %sx%s^',
|
||||
$focus['width'],
|
||||
$focus['height'],
|
||||
$focus['x1'],
|
||||
$focus['y1'],
|
||||
$options['width'],
|
||||
$options['height']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// translate the gravity option into something imagemagick understands
|
||||
$gravity = match ($options['crop'] ?? null) {
|
||||
'top left' => 'NorthWest',
|
||||
'top' => 'North',
|
||||
'top right' => 'NorthEast',
|
||||
'left' => 'West',
|
||||
'center' => 'Center',
|
||||
'right' => 'East',
|
||||
'bottom left' => 'SouthWest',
|
||||
'bottom' => 'South',
|
||||
'bottom right' => 'SouthEast'
|
||||
];
|
||||
|
||||
// translate the gravity option into something imagemagick understands
|
||||
$gravity = $gravities[$options['crop']] ?? 'Center';
|
||||
'bottom right' => 'SouthEast',
|
||||
default => 'Center'
|
||||
};
|
||||
|
||||
$command = '-thumbnail ' . escapeshellarg(sprintf('%sx%s^', $options['width'], $options['height']));
|
||||
$command .= ' -gravity ' . escapeshellarg($gravity);
|
||||
|
@@ -26,6 +26,7 @@ class Dimensions
|
||||
|
||||
/**
|
||||
* Improved `var_dump` output
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
@@ -138,8 +139,10 @@ class Dimensions
|
||||
* upscaled to fit the box if smaller
|
||||
* @return $this object with recalculated dimensions
|
||||
*/
|
||||
public function fitHeight(int|null $fit = null, bool $force = false): static
|
||||
{
|
||||
public function fitHeight(
|
||||
int|null $fit = null,
|
||||
bool $force = false
|
||||
): static {
|
||||
return $this->fitSize('height', $fit, $force);
|
||||
}
|
||||
|
||||
@@ -152,8 +155,11 @@ class Dimensions
|
||||
* upscaled to fit the box if smaller
|
||||
* @return $this object with recalculated dimensions
|
||||
*/
|
||||
protected function fitSize(string $ref, int|null $fit = null, bool $force = false): static
|
||||
{
|
||||
protected function fitSize(
|
||||
string $ref,
|
||||
int|null $fit = null,
|
||||
bool $force = false
|
||||
): static {
|
||||
if ($fit === 0 || $fit === null) {
|
||||
return $this;
|
||||
}
|
||||
@@ -191,8 +197,10 @@ class Dimensions
|
||||
* upscaled to fit the box if smaller
|
||||
* @return $this object with recalculated dimensions
|
||||
*/
|
||||
public function fitWidth(int|null $fit = null, bool $force = false): static
|
||||
{
|
||||
public function fitWidth(
|
||||
int|null $fit = null,
|
||||
bool $force = false
|
||||
): static {
|
||||
return $this->fitSize('width', $fit, $force);
|
||||
}
|
||||
|
||||
@@ -254,8 +262,7 @@ class Dimensions
|
||||
$xml = simplexml_load_string($content);
|
||||
|
||||
if ($xml !== false) {
|
||||
$attr = $xml->attributes();
|
||||
|
||||
$attr = $xml->attributes();
|
||||
$rawWidth = $attr->width;
|
||||
$width = (int)$rawWidth;
|
||||
$rawHeight = $attr->height;
|
||||
@@ -300,11 +307,11 @@ class Dimensions
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->portrait()) {
|
||||
if ($this->portrait() === true) {
|
||||
return 'portrait';
|
||||
}
|
||||
|
||||
if ($this->landscape()) {
|
||||
if ($this->landscape() === true) {
|
||||
return 'landscape';
|
||||
}
|
||||
|
||||
@@ -336,7 +343,7 @@ class Dimensions
|
||||
return $this->width / $this->height;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -15,61 +15,30 @@ use Kirby\Toolkit\V;
|
||||
*/
|
||||
class Exif
|
||||
{
|
||||
/**
|
||||
* The parent image object
|
||||
*/
|
||||
protected Image $image;
|
||||
|
||||
/**
|
||||
* The raw exif array
|
||||
*/
|
||||
protected array $data = [];
|
||||
|
||||
/**
|
||||
* The camera object with model and make
|
||||
*/
|
||||
protected Camera|null $camera = null;
|
||||
|
||||
/**
|
||||
* The location object
|
||||
*/
|
||||
protected Location|null $location = null;
|
||||
|
||||
/**
|
||||
* The timestamp
|
||||
*/
|
||||
protected string|null $timestamp = null;
|
||||
|
||||
/**
|
||||
* The exposure value
|
||||
*/
|
||||
protected string|null $exposure = null;
|
||||
|
||||
/**
|
||||
* The aperture value
|
||||
*/
|
||||
protected string|null $aperture = null;
|
||||
|
||||
/**
|
||||
* ISO value
|
||||
*/
|
||||
protected string|null $iso = null;
|
||||
|
||||
/**
|
||||
* Focal length
|
||||
*/
|
||||
protected string|null $focalLength = null;
|
||||
|
||||
/**
|
||||
* Color or black/white
|
||||
*/
|
||||
protected bool|null $isColor = null;
|
||||
|
||||
public function __construct(Image $image)
|
||||
{
|
||||
$this->image = $image;
|
||||
$this->data = $this->read();
|
||||
$this->parse();
|
||||
public function __construct(
|
||||
protected Image $image
|
||||
) {
|
||||
$this->data = $this->read();
|
||||
$this->timestamp = $this->parseTimestamp();
|
||||
$this->exposure = $this->data['ExposureTime'] ?? null;
|
||||
$this->iso = $this->data['ISOSpeedRatings'] ?? null;
|
||||
$this->focalLength = $this->parseFocalLength();
|
||||
$this->aperture = $this->computed()['ApertureFNumber'] ?? null;
|
||||
$this->isColor = V::accepted($this->computed()['IsColor'] ?? null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,11 +54,7 @@ class Exif
|
||||
*/
|
||||
public function camera(): Camera
|
||||
{
|
||||
if ($this->camera !== null) {
|
||||
return $this->camera;
|
||||
}
|
||||
|
||||
return $this->camera = new Camera($this->data);
|
||||
return $this->camera ??= new Camera($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,11 +62,7 @@ class Exif
|
||||
*/
|
||||
public function location(): Location
|
||||
{
|
||||
if ($this->location !== null) {
|
||||
return $this->location;
|
||||
}
|
||||
|
||||
return $this->location = new Location($this->data);
|
||||
return $this->location ??= new Location($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,19 +144,6 @@ class Exif
|
||||
return $this->data['COMPUTED'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses and stores all relevant exif data
|
||||
*/
|
||||
protected function parse(): void
|
||||
{
|
||||
$this->timestamp = $this->parseTimestamp();
|
||||
$this->exposure = $this->data['ExposureTime'] ?? null;
|
||||
$this->iso = $this->data['ISOSpeedRatings'] ?? null;
|
||||
$this->focalLength = $this->parseFocalLength();
|
||||
$this->aperture = $this->computed()['ApertureFNumber'] ?? null;
|
||||
$this->isColor = V::accepted($this->computed()['IsColor'] ?? null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the timestamp when the picture has been taken
|
||||
*/
|
||||
@@ -240,6 +188,7 @@ class Exif
|
||||
|
||||
/**
|
||||
* Improved `var_dump` output
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
|
110
kirby/src/Image/Focus.php
Normal file
110
kirby/src/Image/Focus.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace Kirby\Image;
|
||||
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
* @package Kirby Image
|
||||
* @author Nico Hoffmann <nico@getkirby.com>
|
||||
* @link https://getkirby.com
|
||||
* @copyright Bastian Allgeier
|
||||
* @license https://opensource.org/licenses/MIT
|
||||
*/
|
||||
class Focus
|
||||
{
|
||||
/**
|
||||
* Generates crop coordinates based on focal point
|
||||
*/
|
||||
public static function coords(
|
||||
string $crop,
|
||||
int $sourceWidth,
|
||||
int $sourceHeight,
|
||||
int $width,
|
||||
int $height
|
||||
): array|null {
|
||||
[$x, $y] = static::parse($crop);
|
||||
|
||||
// determine aspect ratios
|
||||
$ratioSource = static::ratio($sourceWidth, $sourceHeight);
|
||||
$ratioThumb = static::ratio($width, $height);
|
||||
|
||||
// no cropping necessary
|
||||
if ($ratioSource == $ratioThumb) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// defaults
|
||||
$width = $sourceWidth;
|
||||
$height = $sourceHeight;
|
||||
|
||||
if ($ratioThumb > $ratioSource) {
|
||||
$height = $sourceWidth / $ratioThumb;
|
||||
} else {
|
||||
$width = $sourceHeight * $ratioThumb;
|
||||
}
|
||||
|
||||
// calculate focus for original image
|
||||
$x = $sourceWidth * $x;
|
||||
$y = $sourceHeight * $y;
|
||||
|
||||
$x1 = max(0, $x - $width / 2);
|
||||
$y1 = max(0, $y - $height / 2);
|
||||
|
||||
// off canvas?
|
||||
if ($x1 + $width > $sourceWidth) {
|
||||
$x1 = $sourceWidth - $width;
|
||||
}
|
||||
|
||||
if ($y1 + $height > $sourceHeight) {
|
||||
$y1 = $sourceHeight - $height;
|
||||
}
|
||||
|
||||
return [
|
||||
'x1' => (int)floor($x1),
|
||||
'y1' => (int)floor($y1),
|
||||
'x2' => (int)floor($x1 + $width),
|
||||
'y2' => (int)floor($y1 + $height),
|
||||
'width' => (int)floor($width),
|
||||
'height' => (int)floor($height),
|
||||
];
|
||||
}
|
||||
|
||||
public static function isFocalPoint(string $value): bool
|
||||
{
|
||||
return Str::contains($value, '%') === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the focal point's string value (from content field)
|
||||
* to a [x, y] array (values 0.0-1.0)
|
||||
*/
|
||||
public static function parse(string $value): array
|
||||
{
|
||||
// support for former Focus plugin
|
||||
if (Str::startsWith($value, '{') === true) {
|
||||
$focus = json_decode($value);
|
||||
return [$focus->x, $focus->y];
|
||||
}
|
||||
|
||||
preg_match_all("/(\d{1,3}\.?\d*)[%|,|\s]*/", $value, $points);
|
||||
|
||||
return A::map(
|
||||
$points[1],
|
||||
function ($point) {
|
||||
$point = (float)$point;
|
||||
$point = $point > 1 ? $point / 100 : $point;
|
||||
return round($point, 3);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the image ratio
|
||||
*/
|
||||
public static function ratio(int $width, int $height): float
|
||||
{
|
||||
return $height !== 0 ? $width / $height : 0;
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Kirby\Image;
|
||||
|
||||
use Kirby\Cms\Content;
|
||||
use Kirby\Content\Content;
|
||||
use Kirby\Exception\LogicException;
|
||||
use Kirby\Filesystem\File;
|
||||
use Kirby\Toolkit\Html;
|
||||
|
@@ -24,13 +24,20 @@ class Location
|
||||
*/
|
||||
public function __construct(array $exif)
|
||||
{
|
||||
if (isset($exif['GPSLatitude']) === true &&
|
||||
if (
|
||||
isset($exif['GPSLatitude']) === true &&
|
||||
isset($exif['GPSLatitudeRef']) === true &&
|
||||
isset($exif['GPSLongitude']) === true &&
|
||||
isset($exif['GPSLongitudeRef']) === true
|
||||
) {
|
||||
$this->lat = $this->gps($exif['GPSLatitude'], $exif['GPSLatitudeRef']);
|
||||
$this->lng = $this->gps($exif['GPSLongitude'], $exif['GPSLongitudeRef']);
|
||||
$this->lat = $this->gps(
|
||||
$exif['GPSLatitude'],
|
||||
$exif['GPSLatitudeRef']
|
||||
);
|
||||
$this->lng = $this->gps(
|
||||
$exif['GPSLongitude'],
|
||||
$exif['GPSLongitudeRef']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,11 +102,12 @@ class Location
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return trim(trim($this->lat() . ', ' . $this->lng(), ','));
|
||||
return trim($this->lat() . ', ' . $this->lng(), ',');
|
||||
}
|
||||
|
||||
/**
|
||||
* Improved `var_dump` output
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
|
1603
kirby/src/Image/QrCode.php
Normal file
1603
kirby/src/Image/QrCode.php
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user