Skip to content

Commit 77dba88

Browse files
committed
initial commit
0 parents  commit 77dba88

File tree

10 files changed

+363
-0
lines changed

10 files changed

+363
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/vendor/

.idea/.gitignore

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/modules.xml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/php-imgman.iml

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/php.xml

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/vcs.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

composer.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "leongrdic/imgman",
3+
"version": "1.0.0",
4+
"description": "simple php image manipulator that can downscale and compress e.g. thumbnails and profile pictures",
5+
"license": "MIT",
6+
"homepage": "https://github.com/leongrdic/php-imgman",
7+
"authors": [
8+
{
9+
"name": "Leon",
10+
"email": "[email protected]"
11+
}
12+
],
13+
"require": {
14+
"ext-gd": "*",
15+
"ext-exif": "*"
16+
},
17+
"autoload": {
18+
"psr-4": {
19+
"Le\\ImgMan\\": "src/"
20+
}
21+
}
22+
}

composer.lock

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/ImageFormat.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php namespace Le\ImgMan;
2+
3+
enum ImageFormat: string {
4+
case jpeg = 'image/jpeg';
5+
case png = 'image/png';
6+
case webp = 'image/webp';
7+
}
8+

src/ImgMan.php

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
<?php namespace Le\ImgMan;
2+
3+
use GdImage;
4+
use Throwable, Exception;
5+
6+
class ImgMan
7+
{
8+
9+
private string $string;
10+
private string $dataUrl;
11+
private string $filename;
12+
13+
private array $exif;
14+
15+
private ImageFormat $outputFormat;
16+
private ?int $outputQuality;
17+
18+
/**
19+
* @param GdImage|null $image optionally provide an image object to be
20+
* manipulated with instead of using the from methods
21+
*/
22+
public function __construct(
23+
private ?GdImage $image = null
24+
)
25+
{
26+
}
27+
28+
/**
29+
* @param string $string raw image byte stream
30+
* @return $this a new instance
31+
*/
32+
public function fromString(string $string): static
33+
{
34+
$instance = new static;
35+
$instance->string = $string;
36+
return $instance;
37+
}
38+
39+
/**
40+
* @param string $dataUrl data URL or data URI that can be provided
41+
* by FileReader method readAsDataURL in Javascript
42+
* @return $this a new instance
43+
*/
44+
public function fromDataUrl(string $dataUrl): static
45+
{
46+
$instance = new static;
47+
$instance->dataUrl = $dataUrl;
48+
return $instance;
49+
50+
}
51+
52+
/**
53+
* @param string $filename relative path to the image file
54+
* @return $this a new instance
55+
*/
56+
public function fromFile(string $filename): static
57+
{
58+
$instance = new static;
59+
$instance->filename = $filename;
60+
return $instance;
61+
}
62+
63+
/**
64+
* reads exif data (if it exists) from the provided source
65+
* if used, this method must be called before any manipulation methods
66+
*
67+
* @throws Exception if object is not initialized or has already been manipulated
68+
* @return $this
69+
*/
70+
public function cacheExif(): static
71+
{
72+
if(!isset($this->dataUrl) && !isset($this->filename) && !isset($this->string))
73+
throw new Exception('original source missing');
74+
75+
$prefix = 'data://image/jpeg;base64,'; // because only jpegs can have exif, right?
76+
77+
try {
78+
$this->exif = match(true) {
79+
isset($this->dataUrl) => exif_read_data($this->dataUrl),
80+
isset($this->filename) => exif_read_data($this->filename),
81+
isset($this->string) => exif_read_data($prefix . base64_encode($this->string))
82+
};
83+
}
84+
catch(Throwable){ $this->exif = []; }
85+
86+
return $this;
87+
}
88+
89+
/**
90+
* rotates the image to match the exif data (if existent or cached)
91+
*
92+
* recommended way to use is after downscaling the image so less memory usage occurs
93+
* to achieve this use cacheExif() method directly after providing the image source
94+
*
95+
* @throws Exception if object is not initialized, use one of the "from" methods first
96+
* or if the image format is not supported or damaged
97+
* @return $this
98+
*/
99+
public function rotateFromExif(): static
100+
{
101+
if(!isset($this->exif)) $this->cacheExif();
102+
if(empty($this->exif['Orientation'])) return $this;
103+
104+
$image = match($this->exif['Orientation']){
105+
3, 4 => imagerotate($this->getImage(), 180, 0),
106+
5, 6 => imagerotate($this->getImage(), 270, 0),
107+
7, 8 => imagerotate($this->getImage(), 90, 0),
108+
default => $this->getImage()
109+
};
110+
111+
if(in_array($this->exif['Orientation'], [4, 5, 7]))
112+
imageflip($image, IMG_FLIP_HORIZONTAL);
113+
114+
$this->image = $image;
115+
return $this;
116+
}
117+
118+
119+
/**
120+
* downscales the image while preserving aspect ratio
121+
*
122+
* if image is smaller than provided max dimensions, no downscaling will occur
123+
*
124+
* @param int $maxWidth new image width
125+
* @param int|null $maxHeight new image height, optional: will be equal to width if not provided
126+
* @throws Exception if object is not initialized, use one of the "from" methods first
127+
* or if the image format is not supported or damaged
128+
* @return $this
129+
*/
130+
public function downscale(int $maxWidth, ?int $maxHeight = null): static
131+
{
132+
if($maxHeight === null) $maxHeight = $maxWidth;
133+
134+
$this->image = $this->getImage();
135+
$x = imagesx($this->image);
136+
$y = imagesy($this->image);
137+
$ratio = $x / $y;
138+
139+
if($x <= $maxWidth && $y <= $maxHeight)
140+
return $this; // image is already within limits
141+
142+
if( $ratio > 1 ) { // landscape
143+
$width = $maxWidth;
144+
$height = $width/$ratio;
145+
}
146+
else { // portrait
147+
$height = $maxHeight;
148+
$width = $height*$ratio;
149+
}
150+
151+
$scaledImage = imagecreatetruecolor($width, $height);
152+
imagecopyresampled($scaledImage, $this->image, 0, 0, 0, 0, $width, $height, $x, $y);
153+
$this->image = $scaledImage;
154+
155+
return $this;
156+
}
157+
158+
/**
159+
* @param ImageFormat $format specify the output format that will be used
160+
* @param int|null $quality this parameter will be passed to the image____() function's quality parameter
161+
* @return $this
162+
*/
163+
public function output(ImageFormat $format, ?int $quality = null): static
164+
{
165+
$this->outputFormat = $format;
166+
$this->outputQuality = $quality;
167+
168+
return $this;
169+
}
170+
171+
/**
172+
* @param string|null $filename image will be written to this file;
173+
* if you wish to overwrite the existing image,
174+
* leave this parameter out (only if initialized using
175+
* the fromFile() method)
176+
* @throws Exception if no output method was specified using the output() method
177+
*/
178+
public function toFile(?string $filename = null): void
179+
{
180+
if(is_null($filename)) $filename = $this->filename;
181+
$this->toFormat($filename);
182+
}
183+
184+
/**
185+
* @return string raw image bytes
186+
* @throws Exception if no output method was specified using the output() method
187+
* or the image was not initialized, or is not supported or damaged
188+
*/
189+
public function toString(): string
190+
{
191+
return $this->toFormat();
192+
}
193+
194+
/**
195+
* @return string data URL or data URI
196+
* @throws Exception if no output method was specified using the output() method
197+
* or the image was not initialized, or is not supported or damaged
198+
*/
199+
public function toDataUrl(): string
200+
{
201+
$prefix = 'data://' . $this->outputFormat->value . ';base64,';
202+
return $prefix . base64_encode($this->toFormat());
203+
}
204+
205+
/**
206+
* @throws Exception
207+
*/
208+
private function getImage(): GdImage
209+
{
210+
if(isset($this->image)) return $this->image;
211+
212+
if(!isset($this->dataUrl) && !isset($this->filename) && !isset($this->string))
213+
throw new Exception('object not initialized');
214+
215+
216+
if (isset($this->string))
217+
$image = @imagecreatefromstring($this->string);
218+
else
219+
if (isset($this->dataUrl))
220+
$image = @imagecreatefromstring(
221+
base64_decode(
222+
explode(',', $this->dataUrl, 2)[1]
223+
)
224+
);
225+
else {
226+
$image = match (getimagesize($this->filename)['mime']) {
227+
'image/bmp' => @imagecreatefrombmp($this->filename),
228+
'image/gif' => @imagecreatefromgif($this->filename),
229+
'image/png' => @imagecreatefrompng($this->filename),
230+
'image/jpeg' => @imagecreatefromjpeg($this->filename),
231+
'image/webp' => @imagecreatefromwebp($this->filename),
232+
default => throw new Exception('not a supported image file extension')
233+
};
234+
}
235+
236+
if($image === false)
237+
throw new Exception('unsuccessful image loading - could be damaged or an unsupported format');
238+
239+
unset($this->string);
240+
unset($this->dataUrl);
241+
242+
return $image;
243+
}
244+
245+
/**
246+
* @throws Exception
247+
*/
248+
private function toFormat(string $filename = null): string
249+
{
250+
if(!isset($this->outputFormat)) throw new Exception('no output format specified (output() method)');
251+
252+
ob_start();
253+
match($this->outputFormat){
254+
ImageFormat::jpeg => imagejpeg($this->getImage(), $filename, $this->outputQuality),
255+
ImageFormat::png => imagepng($this->getImage(), $filename, $this->outputQuality ?? -1),
256+
ImageFormat::webp => imagewebp($this->getImage(), $filename, $this->outputQuality)
257+
};
258+
return ob_get_clean();
259+
}
260+
261+
262+
263+
}

0 commit comments

Comments
 (0)