Skip to content

Commit

Permalink
Add instance flags (#301)
Browse files Browse the repository at this point in the history
* Add optional SSL validation

* Add instance flags

* Fix GIF transparency with imagepalettetotruecolor

* Add instance flag doc to README
  • Loading branch information
VictorWesterlund authored Jul 5, 2022
1 parent bdd4df8 commit abd15ce
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 44 deletions.
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -702,13 +702,41 @@ As a best practice, always use the defined constants instead of their integers v
- `ERR_UNSUPPORTED_FORMAT` - The image format specified is not valid.
- `ERR_WEBP_NOT_ENABLED` - WEBP support is not enabled in your version of PHP.
- `ERR_WRITE` - Unable to write to the file system.
- `ERR_INVALID_FAG` - The specified flag key does not exist.

### Useful Things To Know

- Color arguments can be a CSS color name (e.g. `LightBlue`), a hex color string (e.g. `#0099dd`), or an RGB(A) array (e.g. `['red' => 255, 'green' => 0, 'blue' => 0, 'alpha' => 1]`).

- When `$thickness` > 1, GD draws lines of the desired thickness from the center origin. For example, a rectangle drawn at [10, 10, 20, 20] with a thickness of 3 will actually be draw at [9, 9, 21, 21]. This is true for all shapes and is not a bug in the SimpleImage library.

### Instance flags

Tweak the behavior of a SimpleImage instance by setting instance flag values with the `setFlag($key, $value)` method.

```php
$image = new \claviska\SimpleImage('image.jpeg')->setFlag("foo", "bar");
```

You can also pass an associative array to the SimpleImage constructor to set instance flags.

```php
$image = new \claviska\SimpleImage('image.jpeg', ['foo' => 'bar']);
// .. or without an $image
$image = new \claviska\SimpleImage(flags: ['foo' => 'bar']);
```

*Note: `setFlag()` throws an `ERR_INVALID_FLAG` exception if the key does not exist (no default value).*

#### `sslVerify`

Setting `sslVerify` to `false` (defaults to `true`) will make all images loaded over HTTPS forgo certificate peer validation. This is especially usefull for self-signed certificates.

```php
$image = new \claviska\SimpleImage('https://localhost/image.jpeg', ['sslVerify' => false]);
// Would normally throw an OpenSSL exception, but is ignored with the sslVerify flag set to false.
```

## Differences from SimpleImage 2.x

- Normalized color arguments (colors can be a CSS color name, hex color, or RGB(A) array).
Expand Down
127 changes: 83 additions & 44 deletions src/claviska/SimpleImage.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ class SimpleImage {
ERR_LIB_NOT_LOADED = 8,
ERR_UNSUPPORTED_FORMAT = 9,
ERR_WEBP_NOT_ENABLED = 10,
ERR_WRITE = 11;
ERR_WRITE = 11,
ERR_INVALID_FLAG = 12;


protected $image;
Expand All @@ -46,9 +47,10 @@ class SimpleImage {
* Creates a new SimpleImage object.
*
* @param string $image An image file or a data URI to load.
* @param array $flags Optional override of default flags.
* @throws \Exception Thrown if the GD library is not found; file|URI or image data is invalid.
*/
public function __construct($image = '') {
public function __construct($image = '', $flags = []) {
// Check for the required GD extension
if(extension_loaded('gd')) {
// Ignore JPEG warnings that cause imagecreatefromjpeg() to fail
Expand All @@ -57,6 +59,16 @@ public function __construct($image = '') {
throw new \Exception('Required extension GD is not loaded.', self::ERR_GD_NOT_ENABLED);
}

// Associative array of flags.
$this->flags = [
"sslVerify" => true // Skip SSL peer validation
];

// Override default flag values.
foreach($flags as $flag => $value) {
$this->setFlag($flag, $value);
}

// Load an image through the constructor
if(preg_match('/^data:(.*?);/', $image)) {
$this->fromDataUri($image);
Expand All @@ -77,6 +89,37 @@ public function __destruct() {
}
}

//////////////////////////////////////////////////////////////////////////////////////////////////
// Helper functions
//////////////////////////////////////////////////////////////////////////////////////////////////

/**
* Set flag value.
*
* @param string $flag Name of the flag to set.
* @param boolean $value State of the flag.
* @throws \Exception Thrown if flag does not exist (no default value).
*/
public function setFlag($flag, $value) {
// Throw if flag does not exist
if(!in_array($flag, array_keys($this->flags))) {
throw new \Exception('Invalid flag.', self::ERR_INVALID_FLAG);
}

// Set flag value by name
$this->flags[$flag] = $value;
}

/**
* Get flag value.
*
* @param string $flag Name of the flag to get.
* @return boolean|null
*/
public function getFlag($flag) {
return in_array($flag, array_keys($this->flags)) ? $this->flags[$flag] : null;
}

//////////////////////////////////////////////////////////////////////////////////////////////////
// Loaders
//////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -118,70 +161,66 @@ public function fromDataUri($uri) {
* Loads an image from a file.
*
* @param string $file The image file to load.
* @param boolean $sslVerify Set to false to skip SSL validation.
* @throws \Exception Thrown if file or image data is invalid.
* @return \claviska\SimpleImage
*/
public function fromFile($file) {
// Check if the file exists and is readable. We're using fopen() instead of file_exists()
// because not all URL wrappers support the latter.
$handle = @fopen($file, 'r');
if($handle === false) {
// Set fopen options.
$sslVerify = $this->getFlag("sslVerify"); // Don't perform peer validation when true
$opts = [
"ssl" => [
"verify_peer" => $sslVerify,
"verify_peer_name" => $sslVerify
]
];

// Check if the file exists and is readable.
$file = @file_get_contents($file, false, stream_context_create($opts));
if($file === false) {
throw new \Exception("File not found: $file", self::ERR_FILE_NOT_FOUND);
}
fclose($handle);

// Create image object from string
$this->image = imagecreatefromstring($file);

// Get image info
$info = @getimagesize($file);
$info = @getimagesizefromstring($file);
if($info === false) {
throw new \Exception("Invalid image file: $file", self::ERR_INVALID_IMAGE);
}
$this->mimeType = $info['mime'];

// Create image object from file
if(!$this->image) {
throw new \Exception("Unsupported format: " . $this->mimeType, self::ERR_UNSUPPORTED_FORMAT);
}

switch($this->mimeType) {
case 'image/gif':
// Load the gif
$gif = imagecreatefromgif($file);
if($gif) {
case 'image/gif':
// Copy the gif over to a true color image to preserve its transparency. This is a
// workaround to prevent imagepalettetruecolor() from borking transparency.
$width = imagesx($gif);
$height = imagesy($gif);
$this->image = imagecreatetruecolor((int) $width, (int) $height);
$transparentColor = imagecolorallocatealpha($this->image, 0, 0, 0, 127);
imagecolortransparent($this->image, $transparentColor);
imagefill($this->image, 0, 0, $transparentColor);
// workaround to prevent imagepalettetotruecolor() from borking transparency.
$width = imagesx($this->image);
$height = imagesx($this->image);

$gif = imagecreatetruecolor((int) $width, (int) $height);
$alpha = imagecolorallocatealpha($gif, 0, 0, 0, 127);
imagecolortransparent($gif, $alpha);
imagefill($gif, 0, 0, $alpha);

imagecopy($this->image, $gif, 0, 0, 0, 0, $width, $height);
imagedestroy($gif);
}
break;
case 'image/jpeg':
$this->image = imagecreatefromjpeg($file);
break;
case 'image/png':
$this->image = imagecreatefrompng($file);
break;
case 'image/webp':
$this->image = imagecreatefromwebp($file);
break;
case 'image/bmp':
case 'image/x-ms-bmp':
case 'image/x-windows-bmp':
$this->image = imagecreatefrombmp($file);
break;
}
if(!$this->image) {
throw new \Exception("Unsupported format: " . $this->mimeType, self::ERR_UNSUPPORTED_FORMAT);
break;
case 'image/jpeg':
// Load exif data from JPEG images
if(function_exists('exif_read_data')) {
$this->exif = @exif_read_data("data://image/jpeg;base64," . base64_encode($file));
}
break;
}

// Convert pallete images to true color images
imagepalettetotruecolor($this->image);

// Load exif data from JPEG images
if($this->mimeType === 'image/jpeg' && function_exists('exif_read_data')) {
$this->exif = @exif_read_data($file);
}

return $this;
}

Expand Down

0 comments on commit abd15ce

Please sign in to comment.