loops / gdimage
Import, transform, export images helping GD. This library provides: GIF support, JPEG support, PNG24 support, PNG8 support, animated GIF support, APNG support, HTTP support, data URI support, palette color enhancement (especially on GD transparent color leaks), resizing (crop, fit, filled), color fi
Requires
- php: >=5.3
- ext-gd: *
Suggests
- ext-fileinfo: Provide better MIME type detection.
This package is auto-updated.
Last update: 2024-10-09 13:37:54 UTC
README
Requirements:
At least PHP 5.3. GD library required.
How to use it:
Autoloading
If you do not have composer, call bootstrap:
:::php
require $path_to_library.'/bootstrap.inc.php';
This library use PSR-0 with one namespace, and will probably never go to PSR-4.
Image format
This library supports GIF, JPEG, PNG24, PNG8, AGIF (animated GIF) and APNG.
Import:
Basic usage
You can import an image from many sources: file, binary, base64, data URI…
All of them are handle by \GDImage\Factory::import()
method.
:::php
$image = \GDImage\Factory::import( $stuff );
By-pass
To optimize performance, you may want to by-pass the factory layer. In that
case, you can by-pass file or binary import with the corresponding method of
any \GDImage\Image_Interface
.
:::php
// import file as GIF
$image = new \GDImage\Image_Gif();
$image->fromFile( $filepath );
// import binary as PNG
$image = new \GDImage\Image_Png();
$image->fromBinary( $filepath );
You must know the MIME Type of the image to by-pass factory layer on import.
Export:
Basic usage
You can export an image to many formats: file, binary data, base64 data, data URI scheme…
All of them are handle in \GDImage\Factory::export()
method.
:::php
// export to file
// on file export, if no extension is specified on the filepath, it will be
// automatically added
$final_filepath = \GDImage\Factory::export( $image , $filepath );
// export to data URI
$data_uri = \GDImage\Factory::export( $image , 'data' );
// export to base 64
$data_uri = \GDImage\Factory::export( $image , 'base64' );
// export to binary
$data_uri = \GDImage\Factory::export( $image , 'binary' );
// export to output
\GDImage\Factory::export( $image , 'output' );
Force MIME Type
If you wish, you can force the image to export to an expected MIME Type.
:::php
// export to file with PNG MIME Type
$final_filepath = \GDImage\Factory::export( $image , $filepath , 'image/png' );
Export parameters
If you need extra parameters to the export, the second argument can be an array.
:::php
// export to data URI without base 64 encoding and MIME Type
$data_uri = \GDImage\Factory::export( $image , array(
'driver' => 'DataUri' ,
'base64' => false ,
'mimetype' => false ,
) );
Unnamed parameters will be passed to the final GD function — imagejpeg(), imagepng()… — but you must know which function will be used.
:::php
// export to file with JPEG MIME Type and 75 quality
$final_filepath = \GDImage\Factory::export( $image , array(
'driver' => 'File' ,
'file' => $filepath ,
95 ,
) , 'image/jpeg' );
// export to file with PNG MIME Type, 8 compression level and all PNG filters
$final_filepath = \GDImage\Factory::export( $image , array(
'driver' => 'File' ,
'file' => $filepath ,
8 ,
PNG_ALL_FILTERS ,
) , 'image/png' );
By-pass
To optimize performance, you may want to by-pass the factory layer. In that
case, you can by-pass file or binary export with the corresponding method of
any \GDImage\Image_Interface
.
:::php
// export to file
$success = $image->toFile( $filepath );
// export to binary
$binary = $image->toBinary();
Transformations:
The idea
A transformation is something to apply on an image instance in order to modify its ressource, or its ressources in case of animated images.
By separating image manipulation and image resource, the same transformation can be safely applied to any images.
:::php
$image = \GDImage\Factory::import( $import_filepath );
$transform = new \GDImage\Transform_GaussianBlur();
$image->apply( $transform );
$final_filepath = \GDImage\Factory::export( $image , $export_filepath );
Main transformations
Below is the list of main transformations:
\GDImage\Transform_Resize_Fit
: Scale the image to the largest size such
that both its width and its height can fit inside the area. One of final width
or height may be smaller than expected, not both can be smaller.
:::php
$image = \GDImage\Factory::import( $import_filepath );
$transform = new \GDImage\Transform_Resize_Fit( 300 , 300 );
$image->apply( $transform );
$final_filepath = \GDImage\Factory::export( $image , $export_filepath );
If you want the image to fit an expected width only, do it like this:
:::php
$image = \GDImage\Factory::import( $import_filepath );
$transform = new \GDImage\Transform_Resize_Fit( 300 , \PHP_INT_MAX );
$image->apply( $transform );
$final_filepath = \GDImage\Factory::export( $image , $export_filepath );
\GDImage\Transform_Resize_Crop
: Scale the image to be as large as possible
so that the area is completely covered by the image. Some parts of the image
will be cropped and the final width and height will be exactly as expected.
:::php
$image = \GDImage\Factory::import( $import_filepath );
$transform = new \GDImage\Transform_Resize_Crop( 300 , 300 );
$image->apply( $transform );
$final_filepath = \GDImage\Factory::export( $image , $export_filepath );
\GDImage\Transform_Resize_FitNFill
: Scale the image to the largest size
such that both its width and its height can fit inside the area. The empty
areas will be filled by transparent color so the final width and height will be
exactly as expected.
:::php
$image = \GDImage\Factory::import( $import_filepath );
$transform = new \GDImage\Transform_Resize_FitNFill( 300 , 300 );
$image->apply( $transform );
$final_filepath = \GDImage\Factory::export( $image , $export_filepath );
You can specify a color to fill with:
:::php
$image = \GDImage\Factory::import( $import_filepath );
// fill with white
$transform = new \GDImage\Transform_Resize_FitNFill( 300 , 300 , 'FFFFFF' );
$image->apply( $transform );
$final_filepath = \GDImage\Factory::export( $image , $export_filepath );
There is a lot of other transformations so do no hesitate to look on test cases
or the Transform
folder.
Multiple transformations
You can combine several transformations in one.
:::php
$image = \GDImage\Factory::import( $import_filepath );
$transform = new \GDImage\Transform_Multiple(
new \GDImage\Transform_Grayscale() ,
new \GDImage\Transform_Negate()
);
$image->apply( $transform );
$final_filepath = \GDImage\Factory::export( $image , $export_filepath );
Register transformations
You can register a transformation, even a multiple transformation, to use it later. By this way, all your transformations can stand in a single place, wherever you use them.
:::php
$resize = new \GDImage\Transform_Resize_Fit( 300 , 300 );
\GDImage\Transform_Collection::set( 'a_key_for_the_transformation' , clone $resize );
(...)
$image = \GDImage\Factory::import( $import_filepath );
$image->apply( \GDImage\Transform_Collection::get( 'a_key_for_the_transformation' ) );
$final_filepath = \GDImage\Factory::export( $image , $export_filepath );
Custom transformations
Each transformation must implements \GDImage\Transform_Interface
.
Note that transformation are applied on resource instance, not on image.
:::php
class MyTransform implements \GDImage\Transform_Interface
{
/**
* Apply transformation to a resource.
* Return false if the transformation fails.
*
* @param \GDImage\Resource_Abstract &$rsc
* @return boolean Success flag
* @access public
*/
public function __invoke( \GDImage\Resource_Abstract &$rsc )
{
// your stuff here
return true || false;
}
}
gd_image() helper:
In order to simplify all the image import, transformation and export process,
a single function can handle all the mess for you: gd_image()
.
Call method \GDImage\Config::helper()
anywhere in your code and gd_image()
function will be available on global namespace.
This function accept three arguments:
$import
: Stuff to import, mandatory;$export
: Export parameters, mandatory but can benull
;$transform
: Transformation to apply, optional;$failure
: Callback to execute or value to return on failure, optional.
$import
As expected, $import
argument can be anything that \GDImage\Factory
can import:
file path, data URI, base 64 data, binary data…
The only difference with \GDImage\Factory::import()
method, is that there is
no MIME Type cast.
$export
As expected, $export
argument can be anything that \GDImage\Factory
can use
to export: file path, driver name, export parameters, driver class name,
driver instance…
The first difference with \GDImage\Factory::export()
method, is that there is
no MIME Type cast.
This $export
argument also has extra behaviors:
a
null
export will result to a file export;if
$export
is a string starting with "-" or "_", this string will be considered as a suffix for a file export;if
$import
is a file path and if a file export does not specify a file name, file export will use same file name;if
$import
is not a file path and if a file export does not specify a file name, file export will use MD5 hash of$import
as file name;if
$transform
is not null and if a file export does not specify a file name or a suffix, the transformation key — or the underscored class name — prefixed by an underscore "_" will be used as suffix for the file name;if
$import
is a file path and if file export does not specify a directory, file export will use same directory;if
$import
is not a file path and if file export does not specify a directory, file export will use system temporary directory;if
$import
is a file path with creation date — orLast-modified
header in HTTP cases — and if file export result to a file that already exists, the new file will not be generated if its creation date is higher than$import
.
These behaviors can interact so look at Usage section below for more informations.
$transform
$transform
can be a key for a registered transformation, a transformation
class name (fully qualified or in \GDImage
namespace), a \GDImage
transformation
class suffix (\GDImage\Transform_*
) or a transformation instance.
Note that transformation class name that do not start with "\" may correspond
to a \GDImage
transformation instead of a global one. Watch out.
$failure
If something went wrong, a E_USER_WARNING error will be triggered and false
will
be returned. This behavior can be customize helping $failure
argument:
if
$failure
is callable, it will be executed and its result will be sent back by thegd_image()
helper — arguments of this callback are: the\GDImage\Exception
instance thrown, the$stuff
argument, the$export
argument and the$transform
argument;if
$failure
is not callable and is not null, it will be used as value to return in case of failure — the error will still triggered.
Usage
Export to same file path with suffix:
:::php
$final_filepath = gd_image( $import_filepath , '_suffix' [, $transform] );
Export to same file path with suffix from transformation:
:::php
$final_filepath = gd_image( $import_filepath , null , 'transform_key' );
$final_filepath = gd_image( $import_filepath , null , $transform );
$final_filepath = gd_image( $import_filepath , null , 'Sepia' );
$final_filepath = gd_image( $import_filepath , null , 'Transform_Negate' );
$final_filepath = gd_image( $import_filepath , null , '\\GDImage\\Transform_Grayscale' );
Export to another directory with suffix from transformation:
:::php
$final_filepath = gd_image( $import , $directory , $transform );
Export to base64:
:::php
$final_filepath = gd_image( $import , 'base64' [, $transform] );
Export to data URI without MIME Type:
:::php
$final_filepath = gd_image( $import , array(
'driver' => 'data' ,
'mimetype' => false ,
) [, $transform] );
AGIF and APNG:
This library support animated GIF (AGIF) and animated PNG (APNG).
"Visible" state
Most of concrete animated pictures are the result of some optimizations: every redudant pixels information are removed and frames are reduced to their minimal size.
In these optimized animated pictures, frame at their real state are just noisy pixels and there is no way to exploit them correctly.
That's why this library always provide frames at a "visible" state: each decomposed frames fit picture size, and if another frame should take place behind to complete transparent pixels, it is done.
This is also true for APNG frame with Blend Option to 1 (blend over): in that case the decomposed frame is the result of the merge of the two frames and the Blend Option is deactivated to avoid recomposition error.
Note that on AGIF, the merge of two frames with distinct color palettes may result to a true color frame if the merge of the two palette exceed 256 colors.
Naming
For convenience, frame properties get their names from specification. AGIF use
Delay Time (get/setDelayTime()
) and Disposal Method (get/setDisposalMethod()
)
while APNG use Delay Numerator (get/setDelayNumerator()
), Delay Denominator
(get/setDelayDenominator()
) and Dispose Option (get/setDisposeOption()
).
There is no uniformization for now, and it is not planned.
Optimization
Animated picture optimization is operational but a very long process: each frame has to be analyzed, compared to the previous frame then reduced to its minimal size.
By default, optimization is disabled, but it can be enabled using
\GDImage\Config::setAGifComposerOptimization()
or
\GDImage\Config::setAPngComposerOptimization()
settings.
These settings are bitwise of these bits:
1
: remove transparent pixels on frame borders;2
: remove redundant pixels — from disposed frame to current one — on frameborders;
4
: replace redundant pixels — from disposed frame to current one — of theframe by transparent color.
We strongly advice to enable animated picture optimization when the animated picture is generated on a background task and disable it when the generation is done on demand.
Examples:
Put all the package to a gdimage
folder on your local server,
then you will be able to see GDImage in action at
http://localhost/gdimage/demo/index.php
.
If you want to test a particular image, just put it in demo/samples
folder
and run the demo.
Also, there is no unit testing on this project, but major test cases are available from this demo.
What's next?
Chromatic transform
It should be cool to apply chromatic transformation on picture (bi-chromatic to
N-chromatic).
A picture with X colors will result to an picture with N user-defined colors
(no automatic color determination, it is annoying).
Why not Imagick?
A History of Hosting
The library has been created for hosting that does have Imagick extension — yes, there is some.
This library may not have sense on hosting solutions that provide Imagick, since a lot of libraries implement it.
Divide and Rule
Also, I do not appreciate the way Imagick has been designed: having all the stuff in a single huge class sounds like: "This class do resizing, color filtering, file export, coffee, foot massage…"
On GDImage library, there is four basic layers: Resource handle GD resource, Transform manipulate Resource, Image process Resource and Factory manage Image. It may not be the best way to achieve image manipulation, but it is relatively easy to maintain and expand.
POOP implementation:
What's this POOP?
POOP (Permissive Oriented Object Programming) is a recommendation that advocates
to voluntarily omit protected
and private
visibilities to use only public
.
- POOP is not dirty
- POOP is not useless
- POOP is not dangerous
- POOP is not hard
POOP is not poop.
Pseudo-protected properties/methods
This library follows POOP pattern, that means that every property and method is
public
, even if it should be considered as protected
.
In order to distinguish properties/methods that are really public
and
properties/methods that should be considered as protected
, the following
conventions have to be considered:
If a property/method is prefixed by a single underscore "_", it should be considered as
pseudo-protected
: do not use it if you do not know what you are doing.If a property is prefixed by two underscores "__", it should be considered as
pseudo-protected
with special care: do not manipulate it if you are not sure about what you are doing because it can cause big issues.If a method is prefixed by two underscores "__", it is a "magic" PHP method and it can be used considered as
public
.
POOP on \GDImage\Transform_*
You may notice that all \GDImage\Transform_*
classes have only setters.
In fact, having getters on transformations seems meaningless because transformations only needs parameters, and when you assign parameters, you already know them, don't you?
Of course, some of these parameters may be manipulated before assignment, so helping POOP, you are still able to get them directly if something went wrong.
Contributors:
- Pierrot Evrard aka Loops — https://twitter.com/lxxps
Wanna contribute?
That's here: https://bitbucket.org/lxxps/gdimage/src