From e9037bc0edea83944997ec5f46c0224db2f3f61d Mon Sep 17 00:00:00 2001 From: Mikael Roos Date: Wed, 4 Mar 2015 11:00:36 +0100 Subject: [PATCH] adding phpdoc fix #48 --- docs/api/.htaccess | 5 + docs/api/classes/CHttpGet.html | 738 ++++ docs/api/classes/CImage.html | 3666 +++++++++++++++ .../classes/CImage_RemoteDownloadTest.html | 639 +++ docs/api/classes/CRemoteImage.html | 987 +++++ docs/api/classes/CWhitelist.html | 424 ++ docs/api/classes/CWhitelistTest.html | 497 +++ .../css/bootstrap-combined.no-icons.min.css | 732 +++ docs/api/css/font-awesome.min.css | 403 ++ docs/api/css/jquery.iviewer.css | 65 + .../css/phpdocumentor-clean-icons/Read Me.txt | 3 + .../fonts/phpdocumentor-clean-icons.dev.svg | 17 + .../fonts/phpdocumentor-clean-icons.eot | Bin 0 -> 2324 bytes .../fonts/phpdocumentor-clean-icons.svg | 17 + .../fonts/phpdocumentor-clean-icons.ttf | Bin 0 -> 2080 bytes .../fonts/phpdocumentor-clean-icons.woff | Bin 0 -> 1832 bytes .../css/phpdocumentor-clean-icons/lte-ie7.js | 30 + .../css/phpdocumentor-clean-icons/style.css | 48 + docs/api/css/prism.css | 204 + docs/api/css/template.css | 429 ++ docs/api/files/CHttpGet.html | 256 ++ docs/api/files/CHttpGet.php.txt | 241 + docs/api/files/CImage.html | 256 ++ docs/api/files/CImage.php.txt | 2411 ++++++++++ docs/api/files/CRemoteImage.html | 256 ++ docs/api/files/CRemoteImage.php.txt | 352 ++ docs/api/files/CWhitelist.html | 256 ++ docs/api/files/CWhitelist.php.txt | 63 + docs/api/files/autoload.html | 249 ++ docs/api/files/autoload.php.txt | 24 + .../test%2FCImage_RemoteDownloadTest.php.txt | 166 + docs/api/files/test%2FCWhitelistTest.php.txt | 95 + docs/api/files/test%2Fconfig.php.txt | 7 + .../files/test.CImage_RemoteDownloadTest.html | 267 ++ docs/api/files/test.CWhitelistTest.html | 267 ++ docs/api/files/test.config.html | 260 ++ docs/api/files/webroot%2Fcheck_system.php.txt | 16 + .../webroot%2Fcompare%2Fcompare-test.php.txt | 13 + .../files/webroot%2Fcompare%2Fcompare.php.txt | 107 + docs/api/files/webroot%2Fimg.php.txt | 916 ++++ docs/api/files/webroot%2Fimg_config.php.txt | 295 ++ docs/api/files/webroot%2Fimg_header.php.txt | 36 + docs/api/files/webroot%2Fimgd.php.txt | 3919 +++++++++++++++++ docs/api/files/webroot%2Fimgp.php.txt | 3919 +++++++++++++++++ docs/api/files/webroot%2Fimgs.php.txt | 3919 +++++++++++++++++ .../api/files/webroot%2Ftest%2Fconfig.php.txt | 9 + .../files/webroot%2Ftest%2Ftemplate.php.txt | 102 + docs/api/files/webroot%2Ftest%2Ftest.php.txt | 73 + .../webroot%2Ftest%2Ftest_issue29.php.txt | 39 + .../webroot%2Ftest%2Ftest_issue36_aro.php.txt | 43 + ...ot%2Ftest%2Ftest_issue36_rb-ra-180.php.txt | 43 + ...ot%2Ftest%2Ftest_issue36_rb-ra-270.php.txt | 43 + ...oot%2Ftest%2Ftest_issue36_rb-ra-45.php.txt | 43 + ...oot%2Ftest%2Ftest_issue36_rb-ra-90.php.txt | 43 + .../webroot%2Ftest%2Ftest_issue38.php.txt | 46 + .../webroot%2Ftest%2Ftest_issue40.php.txt | 38 + .../webroot%2Ftest%2Ftest_issue49.php.txt | 39 + .../webroot%2Ftest%2Ftest_issue52-cf.php.txt | 42 + ...root%2Ftest%2Ftest_issue52-stretch.php.txt | 42 + .../webroot%2Ftest%2Ftest_issue52.php.txt | 42 + .../webroot%2Ftest%2Ftest_issue58.php.txt | 37 + .../webroot%2Ftest%2Ftest_issue60.php.txt | 34 + .../webroot%2Ftest%2Ftest_option-crop.php.txt | 42 + ...ot%2Ftest%2Ftest_option-no-upscale.php.txt | 59 + ...broot%2Ftest%2Ftest_option-save-as.php.txt | 43 + docs/api/files/webroot.check_system.html | 260 ++ .../files/webroot.compare.compare-test.html | 260 ++ docs/api/files/webroot.compare.compare.html | 260 ++ docs/api/files/webroot.img.html | 517 +++ docs/api/files/webroot.img_config.html | 251 ++ docs/api/files/webroot.img_header.html | 278 ++ docs/api/files/webroot.imgd.html | 544 +++ docs/api/files/webroot.imgp.html | 544 +++ docs/api/files/webroot.imgs.html | 544 +++ docs/api/files/webroot.test.config.html | 260 ++ docs/api/files/webroot.test.template.html | 260 ++ docs/api/files/webroot.test.test.html | 260 ++ docs/api/files/webroot.test.test_issue29.html | 260 ++ .../files/webroot.test.test_issue36_aro.html | 260 ++ .../webroot.test.test_issue36_rb-ra-180.html | 260 ++ .../webroot.test.test_issue36_rb-ra-270.html | 260 ++ .../webroot.test.test_issue36_rb-ra-45.html | 260 ++ .../webroot.test.test_issue36_rb-ra-90.html | 260 ++ docs/api/files/webroot.test.test_issue38.html | 260 ++ docs/api/files/webroot.test.test_issue40.html | 260 ++ docs/api/files/webroot.test.test_issue49.html | 260 ++ .../files/webroot.test.test_issue52-cf.html | 260 ++ .../webroot.test.test_issue52-stretch.html | 260 ++ docs/api/files/webroot.test.test_issue52.html | 260 ++ docs/api/files/webroot.test.test_issue58.html | 260 ++ docs/api/files/webroot.test.test_issue60.html | 260 ++ .../files/webroot.test.test_option-crop.html | 260 ++ .../webroot.test.test_option-no-upscale.html | 260 ++ .../webroot.test.test_option-save-as.html | 260 ++ docs/api/font/FontAwesome.otf | Bin 0 -> 75188 bytes docs/api/font/fontawesome-webfont.eot | Bin 0 -> 72449 bytes docs/api/font/fontawesome-webfont.svg | 504 +++ docs/api/font/fontawesome-webfont.ttf | Bin 0 -> 141564 bytes docs/api/font/fontawesome-webfont.woff | Bin 0 -> 83760 bytes docs/api/graphs/class.html | 163 + docs/api/graphs/classes.svg | 37 + docs/api/images/apple-touch-icon-114x114.png | Bin 0 -> 28338 bytes docs/api/images/apple-touch-icon-72x72.png | Bin 0 -> 12751 bytes docs/api/images/apple-touch-icon.png | Bin 0 -> 8358 bytes docs/api/images/custom-icons.svg | 116 + docs/api/images/favicon.ico | Bin 0 -> 1150 bytes docs/api/images/hierarchy-item.png | Bin 0 -> 236 bytes docs/api/images/icon-class-13x13.png | Bin 0 -> 428 bytes docs/api/images/icon-class.svg | 77 + docs/api/images/icon-interface-13x13.png | Bin 0 -> 308 bytes docs/api/images/icon-interface.svg | 73 + docs/api/images/icon-trait-13x13.png | Bin 0 -> 340 bytes docs/api/images/icon-trait.svg | 73 + docs/api/images/iviewer/grab.cur | Bin 0 -> 1150 bytes docs/api/images/iviewer/hand.cur | Bin 0 -> 1150 bytes .../images/iviewer/iviewer.rotate_left.png | Bin 0 -> 1493 bytes .../images/iviewer/iviewer.rotate_right.png | Bin 0 -> 1482 bytes docs/api/images/iviewer/iviewer.zoom_fit.png | Bin 0 -> 1252 bytes docs/api/images/iviewer/iviewer.zoom_in.png | Bin 0 -> 1420 bytes docs/api/images/iviewer/iviewer.zoom_out.png | Bin 0 -> 1416 bytes docs/api/images/iviewer/iviewer.zoom_zero.png | Bin 0 -> 1091 bytes docs/api/index.html | 454 ++ docs/api/js/bootstrap.min.js | 7 + docs/api/js/html5.js | 8 + docs/api/js/jquery-1.11.0.min.js | 4 + docs/api/js/jquery.dotdotdot-1.5.9.js | 602 +++ docs/api/js/jquery.dotdotdot-1.5.9.min.js | 15 + docs/api/js/jquery.iviewer.js | 1169 +++++ docs/api/js/jquery.iviewer.min.js | 47 + docs/api/js/jquery.mousewheel.js | 201 + docs/api/js/jquery.smooth-scroll.js | 32 + docs/api/js/prism.min.js | 16 + docs/api/js/ui/1.10.4/jquery-ui.min.js | 7 + docs/api/namespaces/default.html | 454 ++ .../phpdoc-cache-2e/phpdoc-cache-settings.dat | Bin 0 -> 113 bytes ...-file_5de178c5dafcaff40d31e6fe8bec9eea.dat | Bin 0 -> 44103 bytes ...-file_58c9e20d7971fb3461feadcf8052d7c6.dat | Bin 0 -> 39662 bytes ...-file_05104b5f33216f058e4b600e59ccee4e.dat | Bin 0 -> 251851 bytes ...-file_73f6e60ef59c0c30ffc560b4da7243ca.dat | Bin 0 -> 13903 bytes ...-file_2c624667a65767f32365565ca6d89fea.dat | Bin 0 -> 30835 bytes ...-file_ec58a87628ba00fb2321bc6cdb7d54b4.dat | Bin 0 -> 10977 bytes ...-file_83ad6bd4885732fd14b9d8709d592efd.dat | Bin 0 -> 3176 bytes docs/api/reports/deprecated.html | 153 + docs/api/reports/errors.html | 378 ++ docs/api/reports/markers.html | 1084 +++++ phpdoc.xml | 15 + 146 files changed, 42719 insertions(+) create mode 100644 docs/api/.htaccess create mode 100644 docs/api/classes/CHttpGet.html create mode 100644 docs/api/classes/CImage.html create mode 100644 docs/api/classes/CImage_RemoteDownloadTest.html create mode 100644 docs/api/classes/CRemoteImage.html create mode 100644 docs/api/classes/CWhitelist.html create mode 100644 docs/api/classes/CWhitelistTest.html create mode 100644 docs/api/css/bootstrap-combined.no-icons.min.css create mode 100644 docs/api/css/font-awesome.min.css create mode 100644 docs/api/css/jquery.iviewer.css create mode 100644 docs/api/css/phpdocumentor-clean-icons/Read Me.txt create mode 100644 docs/api/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.dev.svg create mode 100644 docs/api/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.eot create mode 100644 docs/api/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.svg create mode 100644 docs/api/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.ttf create mode 100644 docs/api/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.woff create mode 100644 docs/api/css/phpdocumentor-clean-icons/lte-ie7.js create mode 100644 docs/api/css/phpdocumentor-clean-icons/style.css create mode 100644 docs/api/css/prism.css create mode 100644 docs/api/css/template.css create mode 100644 docs/api/files/CHttpGet.html create mode 100644 docs/api/files/CHttpGet.php.txt create mode 100644 docs/api/files/CImage.html create mode 100644 docs/api/files/CImage.php.txt create mode 100644 docs/api/files/CRemoteImage.html create mode 100644 docs/api/files/CRemoteImage.php.txt create mode 100644 docs/api/files/CWhitelist.html create mode 100644 docs/api/files/CWhitelist.php.txt create mode 100644 docs/api/files/autoload.html create mode 100644 docs/api/files/autoload.php.txt create mode 100644 docs/api/files/test%2FCImage_RemoteDownloadTest.php.txt create mode 100644 docs/api/files/test%2FCWhitelistTest.php.txt create mode 100644 docs/api/files/test%2Fconfig.php.txt create mode 100644 docs/api/files/test.CImage_RemoteDownloadTest.html create mode 100644 docs/api/files/test.CWhitelistTest.html create mode 100644 docs/api/files/test.config.html create mode 100644 docs/api/files/webroot%2Fcheck_system.php.txt create mode 100644 docs/api/files/webroot%2Fcompare%2Fcompare-test.php.txt create mode 100644 docs/api/files/webroot%2Fcompare%2Fcompare.php.txt create mode 100644 docs/api/files/webroot%2Fimg.php.txt create mode 100644 docs/api/files/webroot%2Fimg_config.php.txt create mode 100644 docs/api/files/webroot%2Fimg_header.php.txt create mode 100644 docs/api/files/webroot%2Fimgd.php.txt create mode 100644 docs/api/files/webroot%2Fimgp.php.txt create mode 100644 docs/api/files/webroot%2Fimgs.php.txt create mode 100644 docs/api/files/webroot%2Ftest%2Fconfig.php.txt create mode 100644 docs/api/files/webroot%2Ftest%2Ftemplate.php.txt create mode 100644 docs/api/files/webroot%2Ftest%2Ftest.php.txt create mode 100644 docs/api/files/webroot%2Ftest%2Ftest_issue29.php.txt create mode 100644 docs/api/files/webroot%2Ftest%2Ftest_issue36_aro.php.txt create mode 100644 docs/api/files/webroot%2Ftest%2Ftest_issue36_rb-ra-180.php.txt create mode 100644 docs/api/files/webroot%2Ftest%2Ftest_issue36_rb-ra-270.php.txt create mode 100644 docs/api/files/webroot%2Ftest%2Ftest_issue36_rb-ra-45.php.txt create mode 100644 docs/api/files/webroot%2Ftest%2Ftest_issue36_rb-ra-90.php.txt create mode 100644 docs/api/files/webroot%2Ftest%2Ftest_issue38.php.txt create mode 100644 docs/api/files/webroot%2Ftest%2Ftest_issue40.php.txt create mode 100644 docs/api/files/webroot%2Ftest%2Ftest_issue49.php.txt create mode 100644 docs/api/files/webroot%2Ftest%2Ftest_issue52-cf.php.txt create mode 100644 docs/api/files/webroot%2Ftest%2Ftest_issue52-stretch.php.txt create mode 100644 docs/api/files/webroot%2Ftest%2Ftest_issue52.php.txt create mode 100644 docs/api/files/webroot%2Ftest%2Ftest_issue58.php.txt create mode 100644 docs/api/files/webroot%2Ftest%2Ftest_issue60.php.txt create mode 100644 docs/api/files/webroot%2Ftest%2Ftest_option-crop.php.txt create mode 100644 docs/api/files/webroot%2Ftest%2Ftest_option-no-upscale.php.txt create mode 100644 docs/api/files/webroot%2Ftest%2Ftest_option-save-as.php.txt create mode 100644 docs/api/files/webroot.check_system.html create mode 100644 docs/api/files/webroot.compare.compare-test.html create mode 100644 docs/api/files/webroot.compare.compare.html create mode 100644 docs/api/files/webroot.img.html create mode 100644 docs/api/files/webroot.img_config.html create mode 100644 docs/api/files/webroot.img_header.html create mode 100644 docs/api/files/webroot.imgd.html create mode 100644 docs/api/files/webroot.imgp.html create mode 100644 docs/api/files/webroot.imgs.html create mode 100644 docs/api/files/webroot.test.config.html create mode 100644 docs/api/files/webroot.test.template.html create mode 100644 docs/api/files/webroot.test.test.html create mode 100644 docs/api/files/webroot.test.test_issue29.html create mode 100644 docs/api/files/webroot.test.test_issue36_aro.html create mode 100644 docs/api/files/webroot.test.test_issue36_rb-ra-180.html create mode 100644 docs/api/files/webroot.test.test_issue36_rb-ra-270.html create mode 100644 docs/api/files/webroot.test.test_issue36_rb-ra-45.html create mode 100644 docs/api/files/webroot.test.test_issue36_rb-ra-90.html create mode 100644 docs/api/files/webroot.test.test_issue38.html create mode 100644 docs/api/files/webroot.test.test_issue40.html create mode 100644 docs/api/files/webroot.test.test_issue49.html create mode 100644 docs/api/files/webroot.test.test_issue52-cf.html create mode 100644 docs/api/files/webroot.test.test_issue52-stretch.html create mode 100644 docs/api/files/webroot.test.test_issue52.html create mode 100644 docs/api/files/webroot.test.test_issue58.html create mode 100644 docs/api/files/webroot.test.test_issue60.html create mode 100644 docs/api/files/webroot.test.test_option-crop.html create mode 100644 docs/api/files/webroot.test.test_option-no-upscale.html create mode 100644 docs/api/files/webroot.test.test_option-save-as.html create mode 100644 docs/api/font/FontAwesome.otf create mode 100644 docs/api/font/fontawesome-webfont.eot create mode 100644 docs/api/font/fontawesome-webfont.svg create mode 100644 docs/api/font/fontawesome-webfont.ttf create mode 100644 docs/api/font/fontawesome-webfont.woff create mode 100644 docs/api/graphs/class.html create mode 100644 docs/api/graphs/classes.svg create mode 100644 docs/api/images/apple-touch-icon-114x114.png create mode 100644 docs/api/images/apple-touch-icon-72x72.png create mode 100644 docs/api/images/apple-touch-icon.png create mode 100644 docs/api/images/custom-icons.svg create mode 100644 docs/api/images/favicon.ico create mode 100644 docs/api/images/hierarchy-item.png create mode 100644 docs/api/images/icon-class-13x13.png create mode 100644 docs/api/images/icon-class.svg create mode 100644 docs/api/images/icon-interface-13x13.png create mode 100644 docs/api/images/icon-interface.svg create mode 100644 docs/api/images/icon-trait-13x13.png create mode 100644 docs/api/images/icon-trait.svg create mode 100644 docs/api/images/iviewer/grab.cur create mode 100644 docs/api/images/iviewer/hand.cur create mode 100644 docs/api/images/iviewer/iviewer.rotate_left.png create mode 100644 docs/api/images/iviewer/iviewer.rotate_right.png create mode 100644 docs/api/images/iviewer/iviewer.zoom_fit.png create mode 100644 docs/api/images/iviewer/iviewer.zoom_in.png create mode 100644 docs/api/images/iviewer/iviewer.zoom_out.png create mode 100644 docs/api/images/iviewer/iviewer.zoom_zero.png create mode 100644 docs/api/index.html create mode 100644 docs/api/js/bootstrap.min.js create mode 100644 docs/api/js/html5.js create mode 100644 docs/api/js/jquery-1.11.0.min.js create mode 100644 docs/api/js/jquery.dotdotdot-1.5.9.js create mode 100644 docs/api/js/jquery.dotdotdot-1.5.9.min.js create mode 100644 docs/api/js/jquery.iviewer.js create mode 100644 docs/api/js/jquery.iviewer.min.js create mode 100644 docs/api/js/jquery.mousewheel.js create mode 100644 docs/api/js/jquery.smooth-scroll.js create mode 100644 docs/api/js/prism.min.js create mode 100644 docs/api/js/ui/1.10.4/jquery-ui.min.js create mode 100644 docs/api/namespaces/default.html create mode 100644 docs/api/phpdoc-cache-2e/phpdoc-cache-settings.dat create mode 100644 docs/api/phpdoc-cache-64/phpdoc-cache-file_5de178c5dafcaff40d31e6fe8bec9eea.dat create mode 100644 docs/api/phpdoc-cache-66/phpdoc-cache-file_58c9e20d7971fb3461feadcf8052d7c6.dat create mode 100644 docs/api/phpdoc-cache-7f/phpdoc-cache-file_05104b5f33216f058e4b600e59ccee4e.dat create mode 100644 docs/api/phpdoc-cache-b5/phpdoc-cache-file_73f6e60ef59c0c30ffc560b4da7243ca.dat create mode 100644 docs/api/phpdoc-cache-b9/phpdoc-cache-file_2c624667a65767f32365565ca6d89fea.dat create mode 100644 docs/api/phpdoc-cache-e7/phpdoc-cache-file_ec58a87628ba00fb2321bc6cdb7d54b4.dat create mode 100644 docs/api/phpdoc-cache-ee/phpdoc-cache-file_83ad6bd4885732fd14b9d8709d592efd.dat create mode 100644 docs/api/reports/deprecated.html create mode 100644 docs/api/reports/errors.html create mode 100644 docs/api/reports/markers.html create mode 100644 phpdoc.xml diff --git a/docs/api/.htaccess b/docs/api/.htaccess new file mode 100644 index 0000000..7b01f9b --- /dev/null +++ b/docs/api/.htaccess @@ -0,0 +1,5 @@ +# Fixes a vulnerability in CentOS: http://stackoverflow.com/questions/20533279/prevent-php-from-parsing-non-php-files-such-as-somefile-php-txt + + RemoveHandler .php + ForceType text/plain + \ No newline at end of file diff --git a/docs/api/classes/CHttpGet.html b/docs/api/classes/CHttpGet.html new file mode 100644 index 0000000..677dd54 --- /dev/null +++ b/docs/api/classes/CHttpGet.html @@ -0,0 +1,738 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

\CHttpGet

+

Get a image from a remote server using HTTP GET and If-Modified-Since.

+ + + +
+

Summary

+
+
+ Methods +
+
+ Properties +
+
+ Constants +
+
+
+
+ __construct()
+ setUrl()
+ setHeader()
+ parseHeader()
+ doGet()
+ getStatus()
+ getLastModified()
+ getContentType()
+ getDate()
+ getMaxAge()
+ getBody()
+
+
+ No public properties found +
+
+ No constants found +
+
+
+
+ No protected methods found +
+
+ No protected properties found +
+
+ N/A +
+
+
+
+ No private methods found +
+
+ $request
+ $response
+
+
+ N/A +
+
+
+
+ +
+ + + +
+
+

Properties

+
+ +
+ +
+
+ +
+

$request

+
$request : 
+

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$response

+
$response : 
+

+ + +

Type

+ +
+
+ +
+ + + +
+

Methods

+ +
+ +
+
+ +
+

__construct()

+ +
__construct() 
+

Constructor

+ + + + + +
+
+ +
+ +
+
+ +
+

setUrl()

+ +
setUrl(string  $url) : $this
+

Set the url for the request.

+ + +

Parameters

+ + + + + + +
string$url
+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

setHeader()

+ +
setHeader(string  $field, string  $value) : $this
+

Set custom header field for the request.

+ + +

Parameters

+ + + + + + + + + + + +
string$field
string$value
+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

parseHeader()

+ +
parseHeader() : $this
+

Set header fields for the request.

+ + + + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

doGet()

+ +
doGet(boolean  $debug = false) : boolean
+

Perform the request.

+ + +

Parameters

+ + + + + + +
boolean$debug

set to true to dump headers.

+ + +

Returns

+ boolean + +
+
+ +
+ +
+
+ +
+

getStatus()

+ +
getStatus() : integer
+

Get HTTP code of response.

+ + + + +

Returns

+ integer + —

as HTTP status code or null if not available.

+ +
+
+ +
+ +
+
+ +
+

getLastModified()

+ +
getLastModified() : integer
+

Get file modification time of response.

+ + + + +

Returns

+ integer + —

as timestamp.

+ +
+
+ +
+ +
+
+ +
+

getContentType()

+ +
getContentType() : string
+

Get content type.

+ + + + +

Returns

+ string + —

as the content type or null if not existing or invalid.

+ +
+
+ +
+ +
+
+ +
+

getDate()

+ +
getDate(mixed  $default = false) : integer
+

Get file modification time of response.

+ + +

Parameters

+ + + + + + +
mixed$default

as default value (int seconds) if date is

+
                  missing in response header.
+ + +

Returns

+ integer + —

as timestamp or $default if Date is missing in

+
        response header.
+ +
+
+ +
+ +
+
+ +
+

getMaxAge()

+ +
getMaxAge(mixed  $default = false) : integer
+

Get max age of cachable item.

+ + +

Parameters

+ + + + + + +
mixed$default

as default value if date is missing in response

+
                  header.
+ + +

Returns

+ integer + —

as timestamp or false if not available.

+ +
+
+ +
+ +
+
+ +
+

getBody()

+ +
getBody() : string
+

Get body of response.

+ + + + +

Returns

+ string + —

as body.

+ +
+
+ +
+ +
+
+ + + + +
+ + + diff --git a/docs/api/classes/CImage.html b/docs/api/classes/CImage.html new file mode 100644 index 0000000..7080e6b --- /dev/null +++ b/docs/api/classes/CImage.html @@ -0,0 +1,3666 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

\CImage

+

Resize and crop images on the fly, store generated images in a cache.

+ + +

Examples

+

+
** File not found : http://dbwebb.se/opensource/cimage **
+ +
+

Summary

+
+
+ Methods +
+
+ Properties +
+
+ Constants +
+
+
+
+ __construct()
+ setVerbose()
+ setSaveFolder()
+ useCache()
+ setRemoteDownload()
+ isRemoteSource()
+ setRemoteHostWhitelist()
+ isRemoteSourceOnWhitelist()
+ downloadRemoteSource()
+ setSource()
+ setTarget()
+ setOptions()
+ loadImageDetails()
+ initDimensions()
+ calculateNewWidthAndHeight()
+ reCalculateDimensions()
+ setSaveAsExtension()
+ setJpegQuality()
+ setPngCompression()
+ useOriginalIfPossible()
+ generateFilename()
+ useCacheIfPossible()
+ failedToLoad()
+ load()
+ preResize()
+ resize()
+ postResize()
+ rotate()
+ rotateExif()
+ trueColorToPalette()
+ sharpenImage()
+ embossImage()
+ blurImage()
+ createConvolveArguments()
+ addConvolveExpressions()
+ imageConvolution()
+ setDefaultBackgroundColor()
+ setPostProcessingOptions()
+ save()
+ linkToCacheFile()
+ output()
+ json()
+ log()
+
+
+ $crop
+ $cropOrig
+ $keepRatio
+ $cropToFit
+ $crop_x
+ $crop_y
+ $filters
+
+
+ PNG_GREYSCALE
+ PNG_RGB
+ PNG_RGB_PALETTE
+ PNG_GREYSCALE_ALPHA
+ PNG_RGB_ALPHA
+ JPEG_QUALITY_DEFAULT
+ PNG_COMPRESSION_DEFAULT
+ UPSCALE_DEFAULT
+
+
+
+
+ No protected methods found +
+
+ No protected properties found +
+
+ N/A +
+
+
+
+ checkFileExtension()
+ mapFilter()
+ getPngType()
+ colorsTotal()
+ getBackgroundColor()
+ createImageKeepTransparency()
+ verboseOutput()
+ raiseError()
+
+
+ $quality
+ $useQuality
+ $compress
+ $useCompress
+ $bgColorDefault
+ $bgColor
+ $saveFolder
+ $image
+ $imageFolder
+ $imageSrc
+ $pathToImage
+ $fileExtension
+ $extension
+ $outputFormat
+ $verbose
+ $log
+ $palette
+ $cacheFileName
+ $saveAs
+ $pngFilter
+ $pngDeflate
+ $jpegOptimize
+ $width
+ $height
+ $newWidth
+ $newWidthOrig
+ $newHeight
+ $newHeightOrig
+ $dpr
+ $upscale
+ $convolve
+ $convolves
+ $fillToFit
+ $offset
+ $fillWidth
+ $fillHeight
+ $allowRemote
+ $remotePattern
+ $useCache
+ $cropWidth
+ $cropHeight
+ $type
+ $attr
+ $useOriginal
+
+
+ N/A +
+
+
+
+ +
+ + +
+
+

Constants

+
+ +
+ +
+
+ +
+

PNG_GREYSCALE

+
PNG_GREYSCALE
+

Constants type of PNG image

+ +
+
+ +
+ +
+
+ +
+

PNG_RGB

+
PNG_RGB
+

+ +
+
+ +
+ +
+
+ +
+

PNG_RGB_PALETTE

+
PNG_RGB_PALETTE
+

+ +
+
+ +
+ +
+
+ +
+

PNG_GREYSCALE_ALPHA

+
PNG_GREYSCALE_ALPHA
+

+ +
+
+ +
+ +
+
+ +
+

PNG_RGB_ALPHA

+
PNG_RGB_ALPHA
+

+ +
+
+ +
+ +
+
+ +
+

JPEG_QUALITY_DEFAULT

+
JPEG_QUALITY_DEFAULT
+

Constant for default image quality when not set

+ +
+
+ +
+ +
+
+ +
+

PNG_COMPRESSION_DEFAULT

+
PNG_COMPRESSION_DEFAULT
+

Constant for default image quality when not set

+ +
+
+ +
+ +
+
+ +
+

UPSCALE_DEFAULT

+
UPSCALE_DEFAULT
+

Always upscale images, even if they are smaller than target image.

+ +
+
+ +
+ + + +
+
+

Properties

+
+ +
+ +
+
+ +
+

$crop

+
$crop : 
+

Array with details on how to crop, incoming as argument and calculated.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$cropOrig

+
$cropOrig : 
+

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$keepRatio

+
$keepRatio : 
+

Properties, the class is mutable and the method setOptions() +decides (partly) what properties are created.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$cropToFit

+
$cropToFit : 
+

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$crop_x

+
$crop_x : 
+

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$crop_y

+
$crop_y : 
+

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$filters

+
$filters : 
+

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$quality

+
$quality : 
+

Quality level for JPEG images.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$useQuality

+
$useQuality : 
+

Is the quality level set from external use (true) or is it default (false)?

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$compress

+
$compress : 
+

Compression level for PNG images.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$useCompress

+
$useCompress : 
+

Is the compress level set from external use (true) or is it default (false)?

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$bgColorDefault

+
$bgColorDefault : 
+

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$bgColor

+
$bgColor : 
+

Background color to use, specified as part of options.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$saveFolder

+
$saveFolder : 
+

Where to save the target file.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$image

+
$image : 
+

The working image object.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$imageFolder

+
$imageFolder : 
+

The root folder of images (only used in constructor to create $pathToImage?).

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$imageSrc

+
$imageSrc : 
+

Image filename, may include subdirectory, relative from $imageFolder

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$pathToImage

+
$pathToImage : 
+

Actual path to the image, $imageFolder . '/' . $imageSrc

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$fileExtension

+
$fileExtension : 
+

Original file extension

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$extension

+
$extension : 
+

File extension to use when saving image.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$outputFormat

+
$outputFormat : 
+

Output format, supports null (image) or json.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$verbose

+
$verbose : 
+

Verbose mode to print out a trace and display the created image

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$log

+
$log : 
+

Keep a log/trace on what happens

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$palette

+
$palette : 
+

Handle image as palette image

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$cacheFileName

+
$cacheFileName : 
+

Target filename, with path, to save resulting image in.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$saveAs

+
$saveAs : 
+

Set a format to save image as, or null to use original format.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$pngFilter

+
$pngFilter : 
+

Path to command for filter optimize, for example optipng or null.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$pngDeflate

+
$pngDeflate : 
+

Path to command for deflate optimize, for example pngout or null.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$jpegOptimize

+
$jpegOptimize : 
+

Path to command to optimize jpeg images, for example jpegtran or null.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$width

+
$width : 
+

Image dimensions, calculated from loaded image.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$height

+
$height : 
+

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$newWidth

+
$newWidth : 
+

New image dimensions, incoming as argument or calculated.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$newWidthOrig

+
$newWidthOrig : 
+

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$newHeight

+
$newHeight : 
+

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$newHeightOrig

+
$newHeightOrig : 
+

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$dpr

+
$dpr : 
+

Change target height & width when different dpr, dpr 2 means double image dimensions.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$upscale

+
$upscale : 
+

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$convolve

+
$convolve : 
+

String with details on how to do image convolution. String +should map a key in the $convolvs array or be a string of +11 float values separated by comma. The first nine builds +up the matrix, then divisor and last offset.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$convolves

+
$convolves : 
+

Custom convolution expressions, matrix 3x3, divisor and offset.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$fillToFit

+
$fillToFit : 
+

Resize strategy to fill extra area with background color.

+

True or false.

+ +

Type

+ +
+
+ +
+ +
+
+ +
+

$offset

+
$offset : 
+

Used with option area to set which parts of the image to use.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$fillWidth

+
$fillWidth : 
+

Calculate target dimension for image when using fill-to-fit resize strategy.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$fillHeight

+
$fillHeight : 
+

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$allowRemote

+
$allowRemote : 
+

Allow remote file download, default is to disallow remote file download.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$remotePattern

+
$remotePattern : 
+

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$useCache

+
$useCache : 
+

Use the cache if true, set to false to ignore the cached file.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$cropWidth

+
$cropWidth : 
+

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$cropHeight

+
$cropHeight : 
+

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$type

+
$type : 
+

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$attr

+
$attr : 
+

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$useOriginal

+
$useOriginal : 
+

+ + +

Type

+ +
+
+ +
+ + + +
+

Methods

+ +
+ +
+
+ +
+

__construct()

+ +
__construct(string  $imageSrc = null, string  $imageFolder = null, string  $saveFolder = null, string  $saveName = null) 
+

Constructor, can take arguments to init the object.

+ + +

Parameters

+ + + + + + + + + + + + + + + + + + + + + +
string$imageSrc

filename which may contain subdirectory.

string$imageFolder

path to root folder for images.

string$saveFolder

path to folder where to save the new file or null to skip saving.

string$saveName

name of target file when saveing.

+ + + +
+
+ +
+ +
+
+ +
+

setVerbose()

+ +
setVerbose(boolean  $mode = true) : $this
+

Set verbose mode.

+ + +

Parameters

+ + + + + + +
boolean$mode

true or false to enable and disable verbose mode,

+
                 default is true.
+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

setSaveFolder()

+ +
setSaveFolder(string  $path) : $this
+

Set save folder, base folder for saving cache files.

+ + +

Parameters

+ + + + + + +
string$path

where to store cached files.

+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

useCache()

+ +
useCache(string  $use = true) : $this
+

Use cache or not.

+ + +

Parameters

+ + + + + + +
string$use

true or false to use cache.

+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

setRemoteDownload()

+ +
setRemoteDownload(boolean  $allow, string  $pattern = null) : $this
+

Allow or disallow remote image download.

+ + +

Parameters

+ + + + + + + + + + + +
boolean$allow

true or false to enable and disable.

string$pattern

to use to detect if its a remote file.

+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

isRemoteSource()

+ +
isRemoteSource(string  $src) : boolean
+

Check if the image resource is a remote file or not.

+ + +

Parameters

+ + + + + + +
string$src

check if src is remote.

+ + +

Returns

+ boolean + —

true if $src is a remote file, else false.

+ +
+
+ +
+ +
+
+ +
+

setRemoteHostWhitelist()

+ +
setRemoteHostWhitelist(array  $whitelist = null) : $this
+

Set whitelist for valid hostnames from where remote source can be +downloaded.

+ + +

Parameters

+ + + + + + +
array$whitelist

with regexp hostnames to allow download from.

+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

isRemoteSourceOnWhitelist()

+ +
isRemoteSourceOnWhitelist(string  $src) : boolean
+

Check if the hostname for the remote image, is on a whitelist, +if the whitelist is defined.

+ + +

Parameters

+ + + + + + +
string$src

the remote source.

+ + +

Returns

+ boolean + —

true if hostname on $src is in the whitelist, else false.

+ +
+
+ +
+ +
+
+ +
+

downloadRemoteSource()

+ +
downloadRemoteSource(string  $src) : string
+

Download a remote image and return path to its local copy.

+ + +

Parameters

+ + + + + + +
string$src

remote path to image.

+ + +

Returns

+ string + —

as path to downloaded remote source.

+ +
+
+ +
+ +
+
+ +
+

setSource()

+ +
setSource(string  $src, string  $dir = null) : $this
+

Set src file.

+ + +

Parameters

+ + + + + + + + + + + +
string$src

of image.

string$dir

as base directory where images are.

+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

setTarget()

+ +
setTarget(string  $src = null, string  $dir = null) : $this
+

Set target file.

+ + +

Parameters

+ + + + + + + + + + + +
string$src

of target image.

string$dir

as base directory where images are stored.

+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

setOptions()

+ +
setOptions(array  $args) : $this
+

Set options to use when processing image.

+ + +

Parameters

+ + + + + + +
array$args

used when processing image.

+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

loadImageDetails()

+ +
loadImageDetails(string  $file = null) : $this
+

Load image details from original image file.

+ + +

Parameters

+ + + + + + +
string$file

the file to load or null to use $this->pathToImage.

+ +

Throws

+
+
\Exception
+
+
+ +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

initDimensions()

+ +
initDimensions() : $this
+

Init new width and height and do some sanity checks on constraints, before any +processing can be done.

+ + + +

Throws

+
+
\Exception
+
+
+ +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

calculateNewWidthAndHeight()

+ +
calculateNewWidthAndHeight() : $this
+

Calculate new width and height of image, based on settings.

+ + + + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

reCalculateDimensions()

+ +
reCalculateDimensions() : $this
+

Re-calculate image dimensions when original image dimension has changed.

+ + + + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

setSaveAsExtension()

+ +
setSaveAsExtension(  $saveAs = null) : $this
+

Set extension for filename to save as.

+ + +

Parameters

+ + + + + + +
$saveAs
+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

setJpegQuality()

+ +
setJpegQuality(integer  $quality = null) : $this
+

Set JPEG quality to use when saving image

+ + +

Parameters

+ + + + + + +
integer$quality

as the quality to set.

+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

setPngCompression()

+ +
setPngCompression(integer  $compress = null) : $this
+

Set PNG compressen algorithm to use when saving image

+ + +

Parameters

+ + + + + + +
integer$compress

as the algorithm to use.

+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

useOriginalIfPossible()

+ +
useOriginalIfPossible(boolean  $useOrig = true) : $this
+

Use original image if possible, check options which affects image processing.

+ + +

Parameters

+ + + + + + +
boolean$useOrig

default is to use original if possible, else set to false.

+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

generateFilename()

+ +
generateFilename(string  $base) : $this
+

Generate filename to save file in cache.

+ + +

Parameters

+ + + + + + +
string$base

as basepath for storing file.

+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

useCacheIfPossible()

+ +
useCacheIfPossible(boolean  $useCache = true) : $this
+

Use cached version of image, if possible.

+ + +

Parameters

+ + + + + + +
boolean$useCache

is default true, set to false to avoid using cached object.

+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

failedToLoad()

+ +
failedToLoad() : void
+

Error message when failing to load somehow corrupt image.

+ + + + + +
+
+ +
+ +
+
+ +
+

load()

+ +
load(string  $src = null, string  $dir = null) : $this
+

Load image from disk.

+ + +

Parameters

+ + + + + + + + + + + +
string$src

of image.

string$dir

as base directory where images are.

+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

preResize()

+ +
preResize() : $this
+

Preprocess image before rezising it.

+ + + + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

resize()

+ +
resize() : $this
+

Resize and or crop the image.

+ + + + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

postResize()

+ +
postResize() : $this
+

Postprocess image after rezising image.

+ + + + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

rotate()

+ +
rotate(float  $angle,   $bgColor) : $this
+

Rotate image using angle.

+ + +

Parameters

+ + + + + + + + + + + +
float$angle

to rotate image.

$bgColor
+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

rotateExif()

+ +
rotateExif() : $this
+

Rotate image using information in EXIF.

+ + + + +

Returns

+ $this + +
+
+ +
+ +
+
+ + +
+ +
+ +
+
+ +
+

sharpenImage()

+ +
sharpenImage() : $this
+

Sharpen image using image convolution.

+ + + + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

embossImage()

+ +
embossImage() : $this
+

Emboss image using image convolution.

+ + + + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

blurImage()

+ +
blurImage() : $this
+

Blur image using image convolution.

+ + + + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

createConvolveArguments()

+ +
createConvolveArguments(string  $expression) : array
+

Create convolve expression and return arguments for image convolution.

+ + +

Parameters

+ + + + + + +
string$expression

constant string which evaluates to a list of

+
                      11 numbers separated by komma or such a list.
+ + +

Returns

+ array + —

as $matrix (3x3), $divisor and $offset

+ +
+
+ +
+ +
+
+ +
+

addConvolveExpressions()

+ +
addConvolveExpressions(array  $options) : $this
+

Add custom expressions (or overwrite existing) for image convolution.

+ + +

Parameters

+ + + + + + +
array$options

Key value array with strings to be converted

+
                  to convolution expressions.
+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

imageConvolution()

+ +
imageConvolution(string  $options = null) : $this
+

Image convolution.

+ + +

Parameters

+ + + + + + +
string$options

A string with 11 float separated by comma.

+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

setDefaultBackgroundColor()

+ +
setDefaultBackgroundColor(string  $color) : $this
+

Set default background color between 000000-FFFFFF or if using +alpha 00000000-FFFFFF7F.

+ + +

Parameters

+ + + + + + +
string$color

as hex value.

+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

setPostProcessingOptions()

+ +
setPostProcessingOptions(array  $options) : $this
+

Set optimizing and post-processing options.

+ + +

Parameters

+ + + + + + +
array$options

with config for postprocessing with external tools.

+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

save()

+ +
save(string  $src = null, string  $base = null) : $this
+

Save image.

+ + +

Parameters

+ + + + + + + + + + + +
string$src

as target filename.

string$base

as base directory where to store images.

+ + +

Returns

+ $this + —

or false if no folder is set.

+ +
+
+ +
+ +
+
+ +
+

linkToCacheFile()

+ +
linkToCacheFile(string  $alias) : $this
+

Create a hard link, as an alias, to the cached file.

+ + +

Parameters

+ + + + + + +
string$alias

where to store the link,

+
                 filename without extension.
+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

output()

+ +
output(string  $file = null, string  $format = null) : void
+

Output image to browser using caching.

+ + +

Parameters

+ + + + + + + + + + + +
string$file

to read and output, default is to use $this->cacheFileName

string$format

set to json to output file as json object with details

+ + + +
+
+ +
+ +
+
+ +
+

json()

+ +
json(string  $file = null) : string
+

Create a JSON object from the image details.

+ + +

Parameters

+ + + + + + +
string$file

the file to output.

+ + +

Returns

+ string + —

json-encoded representation of the image.

+ +
+
+ +
+ +
+
+ +
+

log()

+ +
log(string  $message) : \this
+

Log an event if verbose mode.

+ + +

Parameters

+ + + + + + +
string$message

to log.

+ + +

Returns

+ \this + +
+
+ +
+ +
+
+ +
+

checkFileExtension()

+ +
checkFileExtension(string  $extension) : $this
+

Check if file extension is valid as a file extension.

+ + +

Parameters

+ + + + + + +
string$extension

of image file.

+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

mapFilter()

+ +
mapFilter(string  $name) : array
+

Map filter name to PHP filter and id.

+ + +

Parameters

+ + + + + + +
string$name

the name of the filter.

+ +

Throws

+
+
\Exception
+
+
+ +

Returns

+ array + —

with filter settings

+ +
+
+ +
+ +
+
+ +
+

getPngType()

+ +
getPngType() : integer
+

Get the type of PNG image.

+ + + + +

Returns

+ integer + —

as the type of the png-image

+ +
+
+ +
+ +
+
+ +
+

colorsTotal()

+ +
colorsTotal(resource  $im) : integer
+

Calculate number of colors in an image.

+ + +

Parameters

+ + + + + + +
resource$im

the image.

+ + +

Returns

+ integer + +
+
+ +
+ +
+
+ +
+

getBackgroundColor()

+ +
getBackgroundColor(resource  $img = null) : \color
+

Get the background color.

+ + +

Parameters

+ + + + + + +
resource$img

the image to work with or null if using $this->image.

+ + +

Returns

+ \color + —

value or null if no background color is set.

+ +
+
+ +
+ +
+
+ +
+

createImageKeepTransparency()

+ +
createImageKeepTransparency(integer  $width, integer  $height) : \image
+

Create a image and keep transparency for png and gifs.

+ + +

Parameters

+ + + + + + + + + + + +
integer$width

of the new image.

integer$height

of the new image.

+ + +

Returns

+ \image + —

resource.

+ +
+
+ +
+ +
+
+ +
+

verboseOutput()

+ +
verboseOutput() : void
+

Do verbose output and print out the log and the actual images.

+ + + + + +
+
+ +
+ +
+
+ +
+

raiseError()

+ +
raiseError(string  $message) : void
+

Raise error, enables to implement a selection of error methods.

+ + +

Parameters

+ + + + + + +
string$message

the error message to display.

+ +

Throws

+
+
\Exception
+
+
+ + +
+
+ +
+ +
+
+ + + + +
+ + + diff --git a/docs/api/classes/CImage_RemoteDownloadTest.html b/docs/api/classes/CImage_RemoteDownloadTest.html new file mode 100644 index 0000000..98b698c --- /dev/null +++ b/docs/api/classes/CImage_RemoteDownloadTest.html @@ -0,0 +1,639 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

\CImage_RemoteDownloadTest

+

A testclass

+ + + +
+

Summary

+
+
+ Methods +
+
+ Properties +
+
+ Constants +
+
+
+
+ providerValidRemoteSource()
+ providerInvalidRemoteSource()
+ testAllowRemoteDownloadDefaultPatternValid()
+ testAllowRemoteDownloadDefaultPatternInvalid()
+ providerHostnameMatch()
+ testRemoteHostWhitelistMatch()
+ providerHostnameNoMatch()
+ testRemoteHostWhitelistNoMatch()
+
+
+ No public properties found +
+
+ No constants found +
+
+
+
+ No protected methods found +
+
+ No protected properties found +
+
+ N/A +
+
+
+
+ No private methods found +
+
+ $remote_whitelist
+
+
+ N/A +
+
+
+
+ +
+ + + +
+
+

Properties

+
+ +
+ +
+
+ +
+

$remote_whitelist

+
$remote_whitelist : 
+

+ + +

Type

+ +
+
+ +
+ + + +
+

Methods

+ +
+ +
+
+ +
+

providerValidRemoteSource()

+ +
providerValidRemoteSource() : array
+

Provider for valid remote sources.

+ + + + +

Returns

+ array + +
+
+ +
+ +
+
+ +
+

providerInvalidRemoteSource()

+ +
providerInvalidRemoteSource() : array
+

Provider for invalid remote sources.

+ + + + +

Returns

+ array + +
+
+ +
+ +
+
+ +
+

testAllowRemoteDownloadDefaultPatternValid()

+ +
testAllowRemoteDownloadDefaultPatternValid(  $source) : void
+

Test

+ + +

Parameters

+ + + + + + +
$source
+ + + +
+
+ +
+ +
+
+ +
+

testAllowRemoteDownloadDefaultPatternInvalid()

+ +
testAllowRemoteDownloadDefaultPatternInvalid(  $source) : void
+

Test

+ + +

Parameters

+ + + + + + +
$source
+ + + +
+
+ +
+ +
+
+ +
+

providerHostnameMatch()

+ +
providerHostnameMatch() : array
+

Provider for hostname matching the whitelist.

+ + + + +

Returns

+ array + +
+
+ +
+ +
+
+ +
+

testRemoteHostWhitelistMatch()

+ +
testRemoteHostWhitelistMatch(string  $hostname) : void
+

Test

+ + +

Parameters

+ + + + + + +
string$hostname

matches the whitelist

+ + + +
+
+ +
+ +
+
+ +
+

providerHostnameNoMatch()

+ +
providerHostnameNoMatch() : array
+

Provider for hostname not matching the whitelist.

+ + + + +

Returns

+ array + +
+
+ +
+ +
+
+ +
+

testRemoteHostWhitelistNoMatch()

+ +
testRemoteHostWhitelistNoMatch(string  $hostname) : void
+

Test

+ + +

Parameters

+ + + + + + +
string$hostname

not matching the whitelist

+ + + +
+
+ +
+ +
+
+ + + + +
+ + + diff --git a/docs/api/classes/CRemoteImage.html b/docs/api/classes/CRemoteImage.html new file mode 100644 index 0000000..c83885d --- /dev/null +++ b/docs/api/classes/CRemoteImage.html @@ -0,0 +1,987 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

\CRemoteImage

+

Get a image from a remote server using HTTP GET and If-Modified-Since.

+ + + +
+

Summary

+
+
+ Methods +
+
+ Properties +
+
+ Constants +
+
+
+
+ __construct()
+ getStatus()
+ getDetails()
+ setCache()
+ isCacheWritable()
+ useCache()
+ contentTypeToFileExtension()
+ setHeaderFields()
+ save()
+ updateCacheDetails()
+ download()
+ loadCacheDetails()
+ getCachedSource()
+
+
+ No public properties found +
+
+ No constants found +
+
+
+
+ No protected methods found +
+
+ No protected properties found +
+
+ N/A +
+
+
+
+ No private methods found +
+
+ $saveFolder
+ $useCache
+ $http
+ $status
+ $defaultMaxAge
+ $url
+ $fileName
+ $fileJson
+ $fileImage
+ $cache
+
+
+ N/A +
+
+
+
+ +
+ + + +
+
+

Properties

+
+ +
+ +
+
+ +
+

$saveFolder

+
$saveFolder : 
+

Path to cache files.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$useCache

+
$useCache : 
+

Use cache or not.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$http

+
$http : 
+

HTTP object to aid in download file.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$status

+
$status : 
+

Status of the HTTP request.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$defaultMaxAge

+
$defaultMaxAge : 
+

Defalt age for cached items 60*60*24*7.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$url

+
$url : 
+

Url of downloaded item.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$fileName

+
$fileName : 
+

Base name of cache file for downloaded item.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$fileJson

+
$fileJson : 
+

Filename for json-file with details of cached item.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$fileImage

+
$fileImage : 
+

Filename for image-file.

+ + +

Type

+ +
+
+ +
+ +
+
+ +
+

$cache

+
$cache : 
+

Cache details loaded from file.

+ + +

Type

+ +
+
+ +
+ + + +
+

Methods

+ +
+ +
+
+ +
+

__construct()

+ +
__construct() 
+

Constructor

+ + + + + +
+
+ +
+ +
+
+ +
+

getStatus()

+ +
getStatus() : integer
+

Get status of last HTTP request.

+ + + + +

Returns

+ integer + —

as status

+ +
+
+ +
+ +
+
+ +
+

getDetails()

+ +
getDetails() : array
+

Get JSON details for cache item.

+ + + + +

Returns

+ array + —

with json details on cache.

+ +
+
+ +
+ +
+
+ +
+

setCache()

+ +
setCache(  $path) : $this
+

Set the path to the cache directory.

+ + +

Parameters

+ + + + + + +
$path
+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

isCacheWritable()

+ +
isCacheWritable() : $this
+

Check if cache is writable or throw exception.

+ + + +

Throws

+
+
\Exception
+

if cahce folder is not writable.

+
+ +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

useCache()

+ +
useCache(boolean  $use = true) : $this
+

Decide if the cache should be used or not before trying to download +a remote file.

+ + +

Parameters

+ + + + + + +
boolean$use

true to use the cache and false to ignore cache.

+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

contentTypeToFileExtension()

+ +
contentTypeToFileExtension(string  $type) : string
+

Translate a content type to a file extension.

+ + +

Parameters

+ + + + + + +
string$type

a valid content type.

+ + +

Returns

+ string + —

as file extension or false if no match.

+ +
+
+ +
+ +
+
+ +
+

setHeaderFields()

+ +
setHeaderFields() : $this
+

Set header fields.

+ + + + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

save()

+ +
save() : string
+

Save downloaded resource to cache.

+ + + + +

Returns

+ string + —

as path to saved file or false if not saved.

+ +
+
+ +
+ +
+
+ +
+

updateCacheDetails()

+ +
updateCacheDetails() : string
+

Got a 304 and updates cache with new age.

+ + + + +

Returns

+ string + —

as path to cached file.

+ +
+
+ +
+ +
+
+ +
+

download()

+ +
download(string  $url) : string
+

Download a remote file and keep a cache of downloaded files.

+ + +

Parameters

+ + + + + + +
string$url

a remote url.

+ + +

Returns

+ string + —

as path to downloaded file or false if failed.

+ +
+
+ +
+ +
+
+ +
+

loadCacheDetails()

+ +
loadCacheDetails() : $this
+

Get the path to the cached image file if the cache is valid.

+ + + + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

getCachedSource()

+ +
getCachedSource() : string
+

Get the path to the cached image file if the cache is valid.

+ + + + +

Returns

+ string + —

as the path ot the image file or false if no cache.

+ +
+
+ +
+ +
+
+ + + + +
+ + + diff --git a/docs/api/classes/CWhitelist.html b/docs/api/classes/CWhitelist.html new file mode 100644 index 0000000..73bbb8c --- /dev/null +++ b/docs/api/classes/CWhitelist.html @@ -0,0 +1,424 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

\CWhitelist

+

Act as whitelist (or blacklist).

+ + + +
+

Summary

+
+
+ Methods +
+
+ Properties +
+
+ Constants +
+
+
+
+ set()
+ check()
+
+
+ No public properties found +
+
+ No constants found +
+
+
+
+ No protected methods found +
+
+ No protected properties found +
+
+ N/A +
+
+
+
+ No private methods found +
+
+ $whitelist
+
+
+ N/A +
+
+
+
+ +
+ + + +
+
+

Properties

+
+ +
+ +
+
+ +
+

$whitelist

+
$whitelist : 
+

Array to contain the whitelist options.

+ + +

Type

+ +
+
+ +
+ + + +
+

Methods

+ +
+ +
+
+ +
+

set()

+ +
set(array  $whitelist = array()) : $this
+

Set the whitelist from an array of strings, each item in the +whitelist should be a regexp without the surrounding / or #.

+ + +

Parameters

+ + + + + + +
array$whitelist

with all valid options,

+
                    default is to clear the whitelist.
+ + +

Returns

+ $this + +
+
+ +
+ +
+
+ +
+

check()

+ +
check(string  $item, array  $whitelist = null) : boolean
+

Check if item exists in the whitelist.

+ + +

Parameters

+ + + + + + + + + + + +
string$item

string to check.

array$whitelist

optional with all valid options, default is null.

+ + +

Returns

+ boolean + —

true if item is in whitelist, else false.

+ +
+
+ +
+ +
+
+ + + + +
+ + + diff --git a/docs/api/classes/CWhitelistTest.html b/docs/api/classes/CWhitelistTest.html new file mode 100644 index 0000000..047ea2a --- /dev/null +++ b/docs/api/classes/CWhitelistTest.html @@ -0,0 +1,497 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

\CWhitelistTest

+

A testclass

+ + + +
+

Summary

+
+
+ Methods +
+
+ Properties +
+
+ Constants +
+
+
+
+ providerHostnameMatch()
+ providerHostnameNoMatch()
+ testRemoteHostWhitelistMatch()
+ testRemoteHostWhitelistNoMatch()
+
+
+ No public properties found +
+
+ No constants found +
+
+
+
+ No protected methods found +
+
+ No protected properties found +
+
+ N/A +
+
+
+
+ No private methods found +
+
+ $remote_whitelist
+
+
+ N/A +
+
+
+
+ +
+ + + +
+
+

Properties

+
+ +
+ +
+
+ +
+

$remote_whitelist

+
$remote_whitelist : 
+

+ + +

Type

+ +
+
+ +
+ + + +
+

Methods

+ +
+ +
+
+ +
+

providerHostnameMatch()

+ +
providerHostnameMatch() : array
+

Provider for hostname matching the whitelist.

+ + + + +

Returns

+ array + +
+
+ +
+ +
+
+ +
+

providerHostnameNoMatch()

+ +
providerHostnameNoMatch() : array
+

Provider for hostname not matching the whitelist.

+ + + + +

Returns

+ array + +
+
+ +
+ +
+
+ +
+

testRemoteHostWhitelistMatch()

+ +
testRemoteHostWhitelistMatch(string  $hostname) : void
+

Test

+ + +

Parameters

+ + + + + + +
string$hostname

matches the whitelist

+ + + +
+
+ +
+ +
+
+ +
+

testRemoteHostWhitelistNoMatch()

+ +
testRemoteHostWhitelistNoMatch(string  $hostname) : void
+

Test

+ + +

Parameters

+ + + + + + +
string$hostname

not matching the whitelist

+ + + +
+
+ +
+ +
+
+ + + + +
+ + + diff --git a/docs/api/css/bootstrap-combined.no-icons.min.css b/docs/api/css/bootstrap-combined.no-icons.min.css new file mode 100644 index 0000000..5ab243e --- /dev/null +++ b/docs/api/css/bootstrap-combined.no-icons.min.css @@ -0,0 +1,732 @@ +/*! + * Bootstrap v2.3.2 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */ +.clearfix{*zoom:1;}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0;} +.clearfix:after{clear:both;} +.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0;} +.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;} +article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;} +audio,canvas,video{display:inline-block;*display:inline;*zoom:1;} +audio:not([controls]){display:none;} +html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;} +a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;} +a:hover,a:active{outline:0;} +sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline;} +sup{top:-0.5em;} +sub{bottom:-0.25em;} +img{max-width:100%;width:auto\9;height:auto;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic;} +#map_canvas img,.google-maps img{max-width:none;} +button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle;} +button,input{*overflow:visible;line-height:normal;} +button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0;} +button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;} +label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer;} +input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield;} +input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none;} +textarea{overflow:auto;vertical-align:top;} +@media print{*{text-shadow:none !important;color:#000 !important;background:transparent !important;box-shadow:none !important;} a,a:visited{text-decoration:underline;} a[href]:after{content:" (" attr(href) ")";} abbr[title]:after{content:" (" attr(title) ")";} .ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:"";} pre,blockquote{border:1px solid #999;page-break-inside:avoid;} thead{display:table-header-group;} tr,img{page-break-inside:avoid;} img{max-width:100% !important;} @page {margin:0.5cm;}p,h2,h3{orphans:3;widows:3;} h2,h3{page-break-after:avoid;}}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333333;background-color:#ffffff;} +a{color:#0088cc;text-decoration:none;} +a:hover,a:focus{color:#005580;text-decoration:underline;} +.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} +.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.1);} +.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px;} +.row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:"";line-height:0;} +.row:after{clear:both;} +[class*="span"]{float:left;min-height:1px;margin-left:20px;} +.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;} +.span12{width:940px;} +.span11{width:860px;} +.span10{width:780px;} +.span9{width:700px;} +.span8{width:620px;} +.span7{width:540px;} +.span6{width:460px;} +.span5{width:380px;} +.span4{width:300px;} +.span3{width:220px;} +.span2{width:140px;} +.span1{width:60px;} +.offset12{margin-left:980px;} +.offset11{margin-left:900px;} +.offset10{margin-left:820px;} +.offset9{margin-left:740px;} +.offset8{margin-left:660px;} +.offset7{margin-left:580px;} +.offset6{margin-left:500px;} +.offset5{margin-left:420px;} +.offset4{margin-left:340px;} +.offset3{margin-left:260px;} +.offset2{margin-left:180px;} +.offset1{margin-left:100px;} +.row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0;} +.row-fluid:after{clear:both;} +.row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;} +.row-fluid [class*="span"]:first-child{margin-left:0;} +.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%;} +.row-fluid .span12{width:100%;*width:99.94680851063829%;} +.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%;} +.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%;} +.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%;} +.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%;} +.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%;} +.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%;} +.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%;} +.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%;} +.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%;} +.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%;} +.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%;} +.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%;} +.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%;} +.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%;} +.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%;} +.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%;} +.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%;} +.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%;} +.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%;} +.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%;} +.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%;} +.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%;} +.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%;} +.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%;} +.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%;} +.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%;} +.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%;} +.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%;} +.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%;} +.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%;} +.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%;} +.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%;} +.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%;} +.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%;} +.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%;} +[class*="span"].hide,.row-fluid [class*="span"].hide{display:none;} +[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right;} +.container{margin-right:auto;margin-left:auto;*zoom:1;}.container:before,.container:after{display:table;content:"";line-height:0;} +.container:after{clear:both;} +.container-fluid{padding-right:20px;padding-left:20px;*zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";line-height:0;} +.container-fluid:after{clear:both;} +p{margin:0 0 10px;} +.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px;} +small{font-size:85%;} +strong{font-weight:bold;} +em{font-style:italic;} +cite{font-style:normal;} +.muted{color:#999999;} +a.muted:hover,a.muted:focus{color:#808080;} +.text-warning{color:#c09853;} +a.text-warning:hover,a.text-warning:focus{color:#a47e3c;} +.text-error{color:#b94a48;} +a.text-error:hover,a.text-error:focus{color:#953b39;} +.text-info{color:#3a87ad;} +a.text-info:hover,a.text-info:focus{color:#2d6987;} +.text-success{color:#468847;} +a.text-success:hover,a.text-success:focus{color:#356635;} +.text-left{text-align:left;} +.text-right{text-align:right;} +.text-center{text-align:center;} +h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999999;} +h1,h2,h3{line-height:40px;} +h1{font-size:38.5px;} +h2{font-size:31.5px;} +h3{font-size:24.5px;} +h4{font-size:17.5px;} +h5{font-size:14px;} +h6{font-size:11.9px;} +h1 small{font-size:24.5px;} +h2 small{font-size:17.5px;} +h3 small{font-size:14px;} +h4 small{font-size:14px;} +.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eeeeee;} +ul,ol{padding:0;margin:0 0 10px 25px;} +ul ul,ul ol,ol ol,ol ul{margin-bottom:0;} +li{line-height:20px;} +ul.unstyled,ol.unstyled{margin-left:0;list-style:none;} +ul.inline,ol.inline{margin-left:0;list-style:none;}ul.inline>li,ol.inline>li{display:inline-block;*display:inline;*zoom:1;padding-left:5px;padding-right:5px;} +dl{margin-bottom:20px;} +dt,dd{line-height:20px;} +dt{font-weight:bold;} +dd{margin-left:10px;} +.dl-horizontal{*zoom:1;}.dl-horizontal:before,.dl-horizontal:after{display:table;content:"";line-height:0;} +.dl-horizontal:after{clear:both;} +.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;} +.dl-horizontal dd{margin-left:180px;} +hr{margin:20px 0;border:0;border-top:1px solid #eeeeee;border-bottom:1px solid #ffffff;} +abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999999;} +abbr.initialism{font-size:90%;text-transform:uppercase;} +blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eeeeee;}blockquote p{margin-bottom:0;font-size:17.5px;font-weight:300;line-height:1.25;} +blockquote small{display:block;line-height:20px;color:#999999;}blockquote small:before{content:'\2014 \00A0';} +blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eeeeee;border-left:0;}blockquote.pull-right p,blockquote.pull-right small{text-align:right;} +blockquote.pull-right small:before{content:'';} +blockquote.pull-right small:after{content:'\00A0 \2014';} +q:before,q:after,blockquote:before,blockquote:after{content:"";} +address{display:block;margin-bottom:20px;font-style:normal;line-height:20px;} +code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8;white-space:nowrap;} +pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}pre.prettyprint{margin-bottom:20px;} +pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0;} +.pre-scrollable{max-height:340px;overflow-y:scroll;} +.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#ffffff;vertical-align:baseline;white-space:nowrap;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#999999;} +.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +.badge{padding-left:9px;padding-right:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px;} +.label:empty,.badge:empty{display:none;} +a.label:hover,a.label:focus,a.badge:hover,a.badge:focus{color:#ffffff;text-decoration:none;cursor:pointer;} +.label-important,.badge-important{background-color:#b94a48;} +.label-important[href],.badge-important[href]{background-color:#953b39;} +.label-warning,.badge-warning{background-color:#f89406;} +.label-warning[href],.badge-warning[href]{background-color:#c67605;} +.label-success,.badge-success{background-color:#468847;} +.label-success[href],.badge-success[href]{background-color:#356635;} +.label-info,.badge-info{background-color:#3a87ad;} +.label-info[href],.badge-info[href]{background-color:#2d6987;} +.label-inverse,.badge-inverse{background-color:#333333;} +.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a;} +.btn .label,.btn .badge{position:relative;top:-1px;} +.btn-mini .label,.btn-mini .badge{top:0;} +table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0;} +.table{width:100%;margin-bottom:20px;}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #dddddd;} +.table th{font-weight:bold;} +.table thead th{vertical-align:bottom;} +.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0;} +.table tbody+tbody{border-top:2px solid #dddddd;} +.table .table{background-color:#ffffff;} +.table-condensed th,.table-condensed td{padding:4px 5px;} +.table-bordered{border:1px solid #dddddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.table-bordered th,.table-bordered td{border-left:1px solid #dddddd;} +.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0;} +.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;} +.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;} +.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>th:first-child{-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;} +.table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tbody:last-child tr:last-child>th:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>th:last-child{-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;} +.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;-moz-border-radius-bottomleft:0;border-bottom-left-radius:0;} +.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;-moz-border-radius-bottomright:0;border-bottom-right-radius:0;} +.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;} +.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;} +.table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9;} +.table-hover tbody tr:hover>td,.table-hover tbody tr:hover>th{background-color:#f5f5f5;} +table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0;} +.table td.span1,.table th.span1{float:none;width:44px;margin-left:0;} +.table td.span2,.table th.span2{float:none;width:124px;margin-left:0;} +.table td.span3,.table th.span3{float:none;width:204px;margin-left:0;} +.table td.span4,.table th.span4{float:none;width:284px;margin-left:0;} +.table td.span5,.table th.span5{float:none;width:364px;margin-left:0;} +.table td.span6,.table th.span6{float:none;width:444px;margin-left:0;} +.table td.span7,.table th.span7{float:none;width:524px;margin-left:0;} +.table td.span8,.table th.span8{float:none;width:604px;margin-left:0;} +.table td.span9,.table th.span9{float:none;width:684px;margin-left:0;} +.table td.span10,.table th.span10{float:none;width:764px;margin-left:0;} +.table td.span11,.table th.span11{float:none;width:844px;margin-left:0;} +.table td.span12,.table th.span12{float:none;width:924px;margin-left:0;} +.table tbody tr.success>td{background-color:#dff0d8;} +.table tbody tr.error>td{background-color:#f2dede;} +.table tbody tr.warning>td{background-color:#fcf8e3;} +.table tbody tr.info>td{background-color:#d9edf7;} +.table-hover tbody tr.success:hover>td{background-color:#d0e9c6;} +.table-hover tbody tr.error:hover>td{background-color:#ebcccc;} +.table-hover tbody tr.warning:hover>td{background-color:#faf2cc;} +.table-hover tbody tr.info:hover>td{background-color:#c4e3f3;} +form{margin:0 0 20px;} +fieldset{padding:0;margin:0;border:0;} +legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333333;border:0;border-bottom:1px solid #e5e5e5;}legend small{font-size:15px;color:#999999;} +label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px;} +input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;} +label{display:block;margin-bottom:5px;} +select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#555555;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;vertical-align:middle;} +input,textarea,.uneditable-input{width:206px;} +textarea{height:auto;} +textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#ffffff;border:1px solid #cccccc;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-webkit-transition:border linear .2s, box-shadow linear .2s;-moz-transition:border linear .2s, box-shadow linear .2s;-o-transition:border linear .2s, box-shadow linear .2s;transition:border linear .2s, box-shadow linear .2s;}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82, 168, 236, 0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);} +input[type="radio"],input[type="checkbox"]{margin:4px 0 0;*margin-top:0;margin-top:1px \9;line-height:normal;} +input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto;} +select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px;} +select{width:220px;border:1px solid #cccccc;background-color:#ffffff;} +select[multiple],select[size]{height:auto;} +select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;} +.uneditable-input,.uneditable-textarea{color:#999999;background-color:#fcfcfc;border-color:#cccccc;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);cursor:not-allowed;} +.uneditable-input{overflow:hidden;white-space:nowrap;} +.uneditable-textarea{width:auto;height:auto;} +input:-moz-placeholder,textarea:-moz-placeholder{color:#999999;} +input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999999;} +input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999999;} +.radio,.checkbox{min-height:20px;padding-left:20px;} +.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px;} +.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px;} +.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle;} +.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px;} +.input-mini{width:60px;} +.input-small{width:90px;} +.input-medium{width:150px;} +.input-large{width:210px;} +.input-xlarge{width:270px;} +.input-xxlarge{width:530px;} +input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0;} +.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block;} +input,textarea,.uneditable-input{margin-left:0;} +.controls-row [class*="span"]+[class*="span"]{margin-left:20px;} +input.span12,textarea.span12,.uneditable-input.span12{width:926px;} +input.span11,textarea.span11,.uneditable-input.span11{width:846px;} +input.span10,textarea.span10,.uneditable-input.span10{width:766px;} +input.span9,textarea.span9,.uneditable-input.span9{width:686px;} +input.span8,textarea.span8,.uneditable-input.span8{width:606px;} +input.span7,textarea.span7,.uneditable-input.span7{width:526px;} +input.span6,textarea.span6,.uneditable-input.span6{width:446px;} +input.span5,textarea.span5,.uneditable-input.span5{width:366px;} +input.span4,textarea.span4,.uneditable-input.span4{width:286px;} +input.span3,textarea.span3,.uneditable-input.span3{width:206px;} +input.span2,textarea.span2,.uneditable-input.span2{width:126px;} +input.span1,textarea.span1,.uneditable-input.span1{width:46px;} +.controls-row{*zoom:1;}.controls-row:before,.controls-row:after{display:table;content:"";line-height:0;} +.controls-row:after{clear:both;} +.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left;} +.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px;} +input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eeeeee;} +input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent;} +.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853;} +.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853;} +.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #dbc59e;} +.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853;} +.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48;} +.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48;} +.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #d59392;} +.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48;} +.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847;} +.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847;} +.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7aba7b;} +.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847;} +.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad;} +.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad;} +.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7ab5d3;} +.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad;} +input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b;}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7;} +.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1;}.form-actions:before,.form-actions:after{display:table;content:"";line-height:0;} +.form-actions:after{clear:both;} +.help-block,.help-inline{color:#595959;} +.help-block{display:block;margin-bottom:10px;} +.help-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle;padding-left:5px;} +.input-append,.input-prepend{display:inline-block;margin-bottom:10px;vertical-align:middle;font-size:0;white-space:nowrap;}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu,.input-append .popover,.input-prepend .popover{font-size:14px;} +.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2;} +.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #ffffff;background-color:#eeeeee;border:1px solid #ccc;} +.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546;} +.input-prepend .add-on,.input-prepend .btn{margin-right:-1px;} +.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;} +.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;} +.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px;} +.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;} +.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;} +.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;} +.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;} +.input-prepend.input-append .btn-group:first-child{margin-left:0;} +input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;} +.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px;} +.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0;} +.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0;} +.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px;} +.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;*zoom:1;margin-bottom:0;vertical-align:middle;} +.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none;} +.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block;} +.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0;} +.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle;} +.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0;} +.control-group{margin-bottom:10px;} +legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate;} +.form-horizontal .control-group{margin-bottom:20px;*zoom:1;}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;content:"";line-height:0;} +.form-horizontal .control-group:after{clear:both;} +.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right;} +.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0;}.form-horizontal .controls:first-child{*padding-left:180px;} +.form-horizontal .help-block{margin-bottom:0;} +.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px;} +.form-horizontal .form-actions{padding-left:180px;} +.btn{display:inline-block;*display:inline;*zoom:1;padding:4px 12px;margin-bottom:0;font-size:14px;line-height:20px;text-align:center;vertical-align:middle;cursor:pointer;color:#333333;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);background-color:#f5f5f5;background-image:-moz-linear-gradient(top, #ffffff, #e6e6e6);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(top, #ffffff, #e6e6e6);background-image:-o-linear-gradient(top, #ffffff, #e6e6e6);background-image:linear-gradient(to bottom, #ffffff, #e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#e6e6e6;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border:1px solid #cccccc;*border:0;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*margin-left:.3em;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);}.btn:hover,.btn:focus,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333333;background-color:#e6e6e6;*background-color:#d9d9d9;} +.btn:active,.btn.active{background-color:#cccccc \9;} +.btn:first-child{*margin-left:0;} +.btn:hover,.btn:focus{color:#333333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position 0.1s linear;-moz-transition:background-position 0.1s linear;-o-transition:background-position 0.1s linear;transition:background-position 0.1s linear;} +.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;} +.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);} +.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} +.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px;} +.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0;} +.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px;} +.btn-mini{padding:0 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +.btn-block{display:block;width:100%;padding-left:0;padding-right:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;} +.btn-block+.btn-block{margin-top:5px;} +input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%;} +.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255, 255, 255, 0.75);} +.btn-primary{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#006dcc;background-image:-moz-linear-gradient(top, #0088cc, #0044cc);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));background-image:-webkit-linear-gradient(top, #0088cc, #0044cc);background-image:-o-linear-gradient(top, #0088cc, #0044cc);background-image:linear-gradient(to bottom, #0088cc, #0044cc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);border-color:#0044cc #0044cc #002a80;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#0044cc;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#ffffff;background-color:#0044cc;*background-color:#003bb3;} +.btn-primary:active,.btn-primary.active{background-color:#003399 \9;} +.btn-warning{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(to bottom, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);border-color:#f89406 #f89406 #ad6704;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#f89406;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#ffffff;background-color:#f89406;*background-color:#df8505;} +.btn-warning:active,.btn-warning.active{background-color:#c67605 \9;} +.btn-danger{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#da4f49;background-image:-moz-linear-gradient(top, #ee5f5b, #bd362f);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));background-image:-webkit-linear-gradient(top, #ee5f5b, #bd362f);background-image:-o-linear-gradient(top, #ee5f5b, #bd362f);background-image:linear-gradient(to bottom, #ee5f5b, #bd362f);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffbd362f', GradientType=0);border-color:#bd362f #bd362f #802420;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#bd362f;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#ffffff;background-color:#bd362f;*background-color:#a9302a;} +.btn-danger:active,.btn-danger.active{background-color:#942a25 \9;} +.btn-success{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#5bb75b;background-image:-moz-linear-gradient(top, #62c462, #51a351);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));background-image:-webkit-linear-gradient(top, #62c462, #51a351);background-image:-o-linear-gradient(top, #62c462, #51a351);background-image:linear-gradient(to bottom, #62c462, #51a351);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff51a351', GradientType=0);border-color:#51a351 #51a351 #387038;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#51a351;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#ffffff;background-color:#51a351;*background-color:#499249;} +.btn-success:active,.btn-success.active{background-color:#408140 \9;} +.btn-info{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#49afcd;background-image:-moz-linear-gradient(top, #5bc0de, #2f96b4);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));background-image:-webkit-linear-gradient(top, #5bc0de, #2f96b4);background-image:-o-linear-gradient(top, #5bc0de, #2f96b4);background-image:linear-gradient(to bottom, #5bc0de, #2f96b4);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2f96b4', GradientType=0);border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#2f96b4;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#ffffff;background-color:#2f96b4;*background-color:#2a85a0;} +.btn-info:active,.btn-info.active{background-color:#24748c \9;} +.btn-inverse{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#363636;background-image:-moz-linear-gradient(top, #444444, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#444444), to(#222222));background-image:-webkit-linear-gradient(top, #444444, #222222);background-image:-o-linear-gradient(top, #444444, #222222);background-image:linear-gradient(to bottom, #444444, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444', endColorstr='#ff222222', GradientType=0);border-color:#222222 #222222 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#222222;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-inverse:hover,.btn-inverse:focus,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#ffffff;background-color:#222222;*background-color:#151515;} +.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9;} +button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px;}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0;} +button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px;} +button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px;} +button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px;} +.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +.btn-link{border-color:transparent;cursor:pointer;color:#0088cc;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.btn-link:hover,.btn-link:focus{color:#005580;text-decoration:underline;background-color:transparent;} +.btn-link[disabled]:hover,.btn-link[disabled]:focus{color:#333333;text-decoration:none;} +.btn-group{position:relative;display:inline-block;*display:inline;*zoom:1;font-size:0;vertical-align:middle;white-space:nowrap;*margin-left:.3em;}.btn-group:first-child{*margin-left:0;} +.btn-group+.btn-group{margin-left:5px;} +.btn-toolbar{font-size:0;margin-top:10px;margin-bottom:10px;}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px;} +.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.btn-group>.btn+.btn{margin-left:-1px;} +.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:14px;} +.btn-group>.btn-mini{font-size:10.5px;} +.btn-group>.btn-small{font-size:11.9px;} +.btn-group>.btn-large{font-size:17.5px;} +.btn-group>.btn:first-child{margin-left:0;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;} +.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;} +.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px;} +.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px;} +.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2;} +.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0;} +.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);*padding-top:5px;*padding-bottom:5px;} +.btn-group>.btn-mini+.dropdown-toggle{padding-left:5px;padding-right:5px;*padding-top:2px;*padding-bottom:2px;} +.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px;} +.btn-group>.btn-large+.dropdown-toggle{padding-left:12px;padding-right:12px;*padding-top:7px;*padding-bottom:7px;} +.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);} +.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6;} +.btn-group.open .btn-primary.dropdown-toggle{background-color:#0044cc;} +.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406;} +.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f;} +.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351;} +.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4;} +.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222222;} +.btn .caret{margin-top:8px;margin-left:0;} +.btn-large .caret{margin-top:6px;} +.btn-large .caret{border-left-width:5px;border-right-width:5px;border-top-width:5px;} +.btn-mini .caret,.btn-small .caret{margin-top:8px;} +.dropup .btn-large .caret{border-bottom-width:5px;} +.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;} +.btn-group-vertical{display:inline-block;*display:inline;*zoom:1;} +.btn-group-vertical>.btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.btn-group-vertical>.btn+.btn{margin-left:0;margin-top:-1px;} +.btn-group-vertical>.btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;} +.btn-group-vertical>.btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;} +.btn-group-vertical>.btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0;} +.btn-group-vertical>.btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;} +.nav{margin-left:0;margin-bottom:20px;list-style:none;} +.nav>li>a{display:block;} +.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eeeeee;} +.nav>li>a>img{max-width:none;} +.nav>.pull-right{float:right;} +.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999999;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);text-transform:uppercase;} +.nav li+.nav-header{margin-top:9px;} +.nav-list{padding-left:15px;padding-right:15px;margin-bottom:0;} +.nav-list>li>a,.nav-list .nav-header{margin-left:-15px;margin-right:-15px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);} +.nav-list>li>a{padding:3px 15px;} +.nav-list>.active>a,.nav-list>.active>a:hover,.nav-list>.active>a:focus{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.2);background-color:#0088cc;} +.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px;} +.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;} +.nav-tabs,.nav-pills{*zoom:1;}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;content:"";line-height:0;} +.nav-tabs:after,.nav-pills:after{clear:both;} +.nav-tabs>li,.nav-pills>li{float:left;} +.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px;} +.nav-tabs{border-bottom:1px solid #ddd;} +.nav-tabs>li{margin-bottom:-1px;} +.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus{border-color:#eeeeee #eeeeee #dddddd;} +.nav-tabs>.active>a,.nav-tabs>.active>a:hover,.nav-tabs>.active>a:focus{color:#555555;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default;} +.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;} +.nav-pills>.active>a,.nav-pills>.active>a:hover,.nav-pills>.active>a:focus{color:#ffffff;background-color:#0088cc;} +.nav-stacked>li{float:none;} +.nav-stacked>li>a{margin-right:0;} +.nav-tabs.nav-stacked{border-bottom:0;} +.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;} +.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;} +.nav-tabs.nav-stacked>li>a:hover,.nav-tabs.nav-stacked>li>a:focus{border-color:#ddd;z-index:2;} +.nav-pills.nav-stacked>li>a{margin-bottom:3px;} +.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px;} +.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;} +.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} +.nav .dropdown-toggle .caret{border-top-color:#0088cc;border-bottom-color:#0088cc;margin-top:6px;} +.nav .dropdown-toggle:hover .caret,.nav .dropdown-toggle:focus .caret{border-top-color:#005580;border-bottom-color:#005580;} +.nav-tabs .dropdown-toggle .caret{margin-top:8px;} +.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff;} +.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555555;border-bottom-color:#555555;} +.nav>.dropdown.active>a:hover,.nav>.dropdown.active>a:focus{cursor:pointer;} +.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover,.nav>li.dropdown.open.active>a:focus{color:#ffffff;background-color:#999999;border-color:#999999;} +.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret,.nav li.dropdown.open a:focus .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:1;filter:alpha(opacity=100);} +.tabs-stacked .open>a:hover,.tabs-stacked .open>a:focus{border-color:#999999;} +.tabbable{*zoom:1;}.tabbable:before,.tabbable:after{display:table;content:"";line-height:0;} +.tabbable:after{clear:both;} +.tab-content{overflow:auto;} +.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0;} +.tab-content>.tab-pane,.pill-content>.pill-pane{display:none;} +.tab-content>.active,.pill-content>.active{display:block;} +.tabs-below>.nav-tabs{border-top:1px solid #ddd;} +.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0;} +.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}.tabs-below>.nav-tabs>li>a:hover,.tabs-below>.nav-tabs>li>a:focus{border-bottom-color:transparent;border-top-color:#ddd;} +.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover,.tabs-below>.nav-tabs>.active>a:focus{border-color:transparent #ddd #ddd #ddd;} +.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none;} +.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px;} +.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd;} +.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;} +.tabs-left>.nav-tabs>li>a:hover,.tabs-left>.nav-tabs>li>a:focus{border-color:#eeeeee #dddddd #eeeeee #eeeeee;} +.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover,.tabs-left>.nav-tabs .active>a:focus{border-color:#ddd transparent #ddd #ddd;*border-right-color:#ffffff;} +.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd;} +.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;} +.tabs-right>.nav-tabs>li>a:hover,.tabs-right>.nav-tabs>li>a:focus{border-color:#eeeeee #eeeeee #eeeeee #dddddd;} +.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover,.tabs-right>.nav-tabs .active>a:focus{border-color:#ddd #ddd #ddd transparent;*border-left-color:#ffffff;} +.nav>.disabled>a{color:#999999;} +.nav>.disabled>a:hover,.nav>.disabled>a:focus{text-decoration:none;background-color:transparent;cursor:default;} +.navbar{overflow:visible;margin-bottom:20px;*position:relative;*z-index:2;} +.navbar-inner{min-height:40px;padding-left:20px;padding-right:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top, #ffffff, #f2f2f2);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f2f2f2));background-image:-webkit-linear-gradient(top, #ffffff, #f2f2f2);background-image:-o-linear-gradient(top, #ffffff, #f2f2f2);background-image:linear-gradient(to bottom, #ffffff, #f2f2f2);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff2f2f2', GradientType=0);border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 4px rgba(0, 0, 0, 0.065);-moz-box-shadow:0 1px 4px rgba(0, 0, 0, 0.065);box-shadow:0 1px 4px rgba(0, 0, 0, 0.065);*zoom:1;}.navbar-inner:before,.navbar-inner:after{display:table;content:"";line-height:0;} +.navbar-inner:after{clear:both;} +.navbar .container{width:auto;} +.nav-collapse.collapse{height:auto;overflow:visible;} +.navbar .brand{float:left;display:block;padding:10px 20px 10px;margin-left:-20px;font-size:20px;font-weight:200;color:#777777;text-shadow:0 1px 0 #ffffff;}.navbar .brand:hover,.navbar .brand:focus{text-decoration:none;} +.navbar-text{margin-bottom:0;line-height:40px;color:#777777;} +.navbar-link{color:#777777;}.navbar-link:hover,.navbar-link:focus{color:#333333;} +.navbar .divider-vertical{height:40px;margin:0 9px;border-left:1px solid #f2f2f2;border-right:1px solid #ffffff;} +.navbar .btn,.navbar .btn-group{margin-top:5px;} +.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn,.navbar .input-prepend .btn-group,.navbar .input-append .btn-group{margin-top:0;} +.navbar-form{margin-bottom:0;*zoom:1;}.navbar-form:before,.navbar-form:after{display:table;content:"";line-height:0;} +.navbar-form:after{clear:both;} +.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px;} +.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0;} +.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px;} +.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap;}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0;} +.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0;}.navbar-search .search-query{margin-bottom:0;padding:4px 14px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;} +.navbar-static-top{position:static;margin-bottom:0;}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0;} +.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px;} +.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0;} +.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-left:0;padding-right:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;} +.navbar-fixed-top{top:0;} +.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,.1);box-shadow:0 1px 10px rgba(0,0,0,.1);} +.navbar-fixed-bottom{bottom:0;}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,.1);box-shadow:0 -1px 10px rgba(0,0,0,.1);} +.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0;} +.navbar .nav.pull-right{float:right;margin-right:0;} +.navbar .nav>li{float:left;} +.navbar .nav>li>a{float:none;padding:10px 15px 10px;color:#777777;text-decoration:none;text-shadow:0 1px 0 #ffffff;} +.navbar .nav .dropdown-toggle .caret{margin-top:8px;} +.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{background-color:transparent;color:#333333;text-decoration:none;} +.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0, 0, 0, 0.125);-moz-box-shadow:inset 0 3px 8px rgba(0, 0, 0, 0.125);box-shadow:inset 0 3px 8px rgba(0, 0, 0, 0.125);} +.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-left:5px;margin-right:5px;color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#ededed;background-image:-moz-linear-gradient(top, #f2f2f2, #e5e5e5);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#e5e5e5));background-image:-webkit-linear-gradient(top, #f2f2f2, #e5e5e5);background-image:-o-linear-gradient(top, #f2f2f2, #e5e5e5);background-image:linear-gradient(to bottom, #f2f2f2, #e5e5e5);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2', endColorstr='#ffe5e5e5', GradientType=0);border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#e5e5e5;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075);}.navbar .btn-navbar:hover,.navbar .btn-navbar:focus,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#ffffff;background-color:#e5e5e5;*background-color:#d9d9d9;} +.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#cccccc \9;} +.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);-moz-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);} +.btn-navbar .icon-bar+.icon-bar{margin-top:3px;} +.navbar .nav>li>.dropdown-menu:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0, 0, 0, 0.2);position:absolute;top:-7px;left:9px;} +.navbar .nav>li>.dropdown-menu:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #ffffff;position:absolute;top:-6px;left:10px;} +.navbar-fixed-bottom .nav>li>.dropdown-menu:before{border-top:7px solid #ccc;border-top-color:rgba(0, 0, 0, 0.2);border-bottom:0;bottom:-7px;top:auto;} +.navbar-fixed-bottom .nav>li>.dropdown-menu:after{border-top:6px solid #ffffff;border-bottom:0;bottom:-6px;top:auto;} +.navbar .nav li.dropdown>a:hover .caret,.navbar .nav li.dropdown>a:focus .caret{border-top-color:#333333;border-bottom-color:#333333;} +.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{background-color:#e5e5e5;color:#555555;} +.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777777;border-bottom-color:#777777;} +.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555555;border-bottom-color:#555555;} +.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{left:auto;right:0;}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{left:auto;right:12px;} +.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{left:auto;right:13px;} +.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{left:auto;right:100%;margin-left:0;margin-right:-1px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px;} +.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top, #222222, #111111);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#222222), to(#111111));background-image:-webkit-linear-gradient(top, #222222, #111111);background-image:-o-linear-gradient(top, #222222, #111111);background-image:linear-gradient(to bottom, #222222, #111111);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0);border-color:#252525;} +.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999999;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover,.navbar-inverse .brand:focus,.navbar-inverse .nav>li>a:focus{color:#ffffff;} +.navbar-inverse .brand{color:#999999;} +.navbar-inverse .navbar-text{color:#999999;} +.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{background-color:transparent;color:#ffffff;} +.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#ffffff;background-color:#111111;} +.navbar-inverse .navbar-link{color:#999999;}.navbar-inverse .navbar-link:hover,.navbar-inverse .navbar-link:focus{color:#ffffff;} +.navbar-inverse .divider-vertical{border-left-color:#111111;border-right-color:#222222;} +.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{background-color:#111111;color:#ffffff;} +.navbar-inverse .nav li.dropdown>a:hover .caret,.navbar-inverse .nav li.dropdown>a:focus .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;} +.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999999;border-bottom-color:#999999;} +.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;} +.navbar-inverse .navbar-search .search-query{color:#ffffff;background-color:#515151;border-color:#111111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);box-shadow:inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none;}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#cccccc;} +.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#cccccc;} +.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#cccccc;} +.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333333;text-shadow:0 1px 0 #ffffff;background-color:#ffffff;border:0;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);outline:0;} +.navbar-inverse .btn-navbar{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0e0e0e;background-image:-moz-linear-gradient(top, #151515, #040404);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#151515), to(#040404));background-image:-webkit-linear-gradient(top, #151515, #040404);background-image:-o-linear-gradient(top, #151515, #040404);background-image:linear-gradient(to bottom, #151515, #040404);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515', endColorstr='#ff040404', GradientType=0);border-color:#040404 #040404 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#040404;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:focus,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#ffffff;background-color:#040404;*background-color:#000000;} +.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000000 \9;} +.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.breadcrumb>li{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 0 #ffffff;}.breadcrumb>li>.divider{padding:0 5px;color:#ccc;} +.breadcrumb>.active{color:#999999;} +.pagination{margin:20px 0;} +.pagination ul{display:inline-block;*display:inline;*zoom:1;margin-left:0;margin-bottom:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);} +.pagination ul>li{display:inline;} +.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#ffffff;border:1px solid #dddddd;border-left-width:0;} +.pagination ul>li>a:hover,.pagination ul>li>a:focus,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5;} +.pagination ul>.active>a,.pagination ul>.active>span{color:#999999;cursor:default;} +.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover,.pagination ul>.disabled>a:focus{color:#999999;background-color:transparent;cursor:default;} +.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;} +.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;} +.pagination-centered{text-align:center;} +.pagination-right{text-align:right;} +.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px;} +.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px;} +.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px;} +.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-top-left-radius:3px;-moz-border-radius-topleft:3px;border-top-left-radius:3px;-webkit-border-bottom-left-radius:3px;-moz-border-radius-bottomleft:3px;border-bottom-left-radius:3px;} +.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;-moz-border-radius-topright:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;-moz-border-radius-bottomright:3px;border-bottom-right-radius:3px;} +.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px;} +.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:0 6px;font-size:10.5px;} +.pager{margin:20px 0;list-style:none;text-align:center;*zoom:1;}.pager:before,.pager:after{display:table;content:"";line-height:0;} +.pager:after{clear:both;} +.pager li{display:inline;} +.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;} +.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#f5f5f5;} +.pager .next>a,.pager .next>span{float:right;} +.pager .previous>a,.pager .previous>span{float:left;} +.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999999;background-color:#fff;cursor:default;} +.thumbnails{margin-left:-20px;list-style:none;*zoom:1;}.thumbnails:before,.thumbnails:after{display:table;content:"";line-height:0;} +.thumbnails:after{clear:both;} +.row-fluid .thumbnails{margin-left:0;} +.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px;} +.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.055);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.055);box-shadow:0 1px 3px rgba(0, 0, 0, 0.055);-webkit-transition:all 0.2s ease-in-out;-moz-transition:all 0.2s ease-in-out;-o-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out;} +a.thumbnail:hover,a.thumbnail:focus{border-color:#0088cc;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);} +.thumbnail>img{display:block;max-width:100%;margin-left:auto;margin-right:auto;} +.thumbnail .caption{padding:9px;color:#555555;} +.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.alert,.alert h4{color:#c09853;} +.alert h4{margin:0;} +.alert .close{position:relative;top:-2px;right:-21px;line-height:20px;} +.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#468847;} +.alert-success h4{color:#468847;} +.alert-danger,.alert-error{background-color:#f2dede;border-color:#eed3d7;color:#b94a48;} +.alert-danger h4,.alert-error h4{color:#b94a48;} +.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#3a87ad;} +.alert-info h4{color:#3a87ad;} +.alert-block{padding-top:14px;padding-bottom:14px;} +.alert-block>p,.alert-block>ul{margin-bottom:0;} +.alert-block p+p{margin-top:5px;} +@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-o-keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}@keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f7f7f7;background-image:-moz-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));background-image:-webkit-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-o-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:linear-gradient(to bottom, #f5f5f5, #f9f9f9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.progress .bar{width:0%;height:100%;color:#ffffff;float:left;font-size:12px;text-align:center;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top, #149bdf, #0480be);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));background-image:-webkit-linear-gradient(top, #149bdf, #0480be);background-image:-o-linear-gradient(top, #149bdf, #0480be);background-image:linear-gradient(to bottom, #149bdf, #0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width 0.6s ease;-moz-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease;} +.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15);} +.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px;} +.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite;} +.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(to bottom, #ee5f5b, #c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffc43c35', GradientType=0);} +.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} +.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(to bottom, #62c462, #57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff57a957', GradientType=0);} +.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} +.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(to bottom, #5bc0de, #339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff339bb9', GradientType=0);} +.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} +.progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(to bottom, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);} +.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} +.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eeeeee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;color:inherit;letter-spacing:-1px;} +.hero-unit li{line-height:30px;} +.media,.media-body{overflow:hidden;*overflow:visible;zoom:1;} +.media,.media .media{margin-top:15px;} +.media:first-child{margin-top:0;} +.media-object{display:block;} +.media-heading{margin:0 0 5px;} +.media>.pull-left{margin-right:10px;} +.media>.pull-right{margin-left:10px;} +.media-list{margin-left:0;list-style:none;} +.tooltip{position:absolute;z-index:1030;display:block;visibility:visible;font-size:11px;line-height:1.4;opacity:0;filter:alpha(opacity=0);}.tooltip.in{opacity:0.8;filter:alpha(opacity=80);} +.tooltip.top{margin-top:-3px;padding:5px 0;} +.tooltip.right{margin-left:3px;padding:0 5px;} +.tooltip.bottom{margin-top:3px;padding:5px 0;} +.tooltip.left{margin-left:-3px;padding:0 5px;} +.tooltip-inner{max-width:200px;padding:8px;color:#ffffff;text-align:center;text-decoration:none;background-color:#000000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid;} +.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000000;} +.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000000;} +.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000000;} +.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000000;} +.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;background-color:#ffffff;-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);white-space:normal;}.popover.top{margin-top:-10px;} +.popover.right{margin-left:10px;} +.popover.bottom{margin-top:10px;} +.popover.left{margin-left:-10px;} +.popover-title{margin:0;padding:8px 14px;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0;}.popover-title:empty{display:none;} +.popover-content{padding:9px 14px;} +.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid;} +.popover .arrow{border-width:11px;} +.popover .arrow:after{border-width:10px;content:"";} +.popover.top .arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0, 0, 0, 0.25);bottom:-11px;}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#ffffff;} +.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0, 0, 0, 0.25);}.popover.right .arrow:after{left:1px;bottom:-10px;border-left-width:0;border-right-color:#ffffff;} +.popover.bottom .arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0, 0, 0, 0.25);top:-11px;}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#ffffff;} +.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0, 0, 0, 0.25);}.popover.left .arrow:after{right:1px;border-right-width:0;border-left-color:#ffffff;bottom:-10px;} +.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000;}.modal-backdrop.fade{opacity:0;} +.modal-backdrop,.modal-backdrop.fade.in{opacity:0.8;filter:alpha(opacity=80);} +.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;outline:none;}.modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;} +.modal.fade.in{top:10%;} +.modal-header{padding:9px 15px;border-bottom:1px solid #eee;}.modal-header .close{margin-top:2px;} +.modal-header h3{margin:0;line-height:30px;} +.modal-body{position:relative;overflow-y:auto;max-height:400px;padding:15px;} +.modal-form{margin-bottom:0;} +.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;*zoom:1;}.modal-footer:before,.modal-footer:after{display:table;content:"";line-height:0;} +.modal-footer:after{clear:both;} +.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0;} +.modal-footer .btn-group .btn+.btn{margin-left:-1px;} +.modal-footer .btn-block+.btn-block{margin-left:0;} +.dropup,.dropdown{position:relative;} +.dropdown-toggle{*margin-bottom:-3px;} +.dropdown-toggle:active,.open .dropdown-toggle{outline:0;} +.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000000;border-right:4px solid transparent;border-left:4px solid transparent;content:"";} +.dropdown .caret{margin-top:8px;margin-left:2px;} +.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#ffffff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;}.dropdown-menu.pull-right{right:0;left:auto;} +.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;} +.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333333;white-space:nowrap;} +.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-submenu:hover>a,.dropdown-submenu:focus>a{text-decoration:none;color:#ffffff;background-color:#0081c2;background-image:-moz-linear-gradient(top, #0088cc, #0077b3);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));background-image:-webkit-linear-gradient(top, #0088cc, #0077b3);background-image:-o-linear-gradient(top, #0088cc, #0077b3);background-image:linear-gradient(to bottom, #0088cc, #0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);} +.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#ffffff;text-decoration:none;outline:0;background-color:#0081c2;background-image:-moz-linear-gradient(top, #0088cc, #0077b3);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));background-image:-webkit-linear-gradient(top, #0088cc, #0077b3);background-image:-o-linear-gradient(top, #0088cc, #0077b3);background-image:linear-gradient(to bottom, #0088cc, #0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);} +.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999999;} +.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);cursor:default;} +.open{*z-index:1000;}.open>.dropdown-menu{display:block;} +.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990;} +.pull-right>.dropdown-menu{right:0;left:auto;} +.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000000;content:"";} +.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px;} +.dropdown-submenu{position:relative;} +.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px;} +.dropdown-submenu:hover>.dropdown-menu{display:block;} +.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0;} +.dropdown-submenu>a:after{display:block;content:" ";float:right;width:0;height:0;border-color:transparent;border-style:solid;border-width:5px 0 5px 5px;border-left-color:#cccccc;margin-top:5px;margin-right:-10px;} +.dropdown-submenu:hover>a:after{border-left-color:#ffffff;} +.dropdown-submenu.pull-left{float:none;}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px;} +.dropdown .dropdown-menu .nav-header{padding-left:20px;padding-right:20px;} +.typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.accordion{margin-bottom:20px;} +.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.accordion-heading{border-bottom:0;} +.accordion-heading .accordion-toggle{display:block;padding:8px 15px;} +.accordion-toggle{cursor:pointer;} +.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5;} +.carousel{position:relative;margin-bottom:20px;line-height:1;} +.carousel-inner{overflow:hidden;width:100%;position:relative;} +.carousel-inner>.item{display:none;position:relative;-webkit-transition:0.6s ease-in-out left;-moz-transition:0.6s ease-in-out left;-o-transition:0.6s ease-in-out left;transition:0.6s ease-in-out left;}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;line-height:1;} +.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block;} +.carousel-inner>.active{left:0;} +.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%;} +.carousel-inner>.next{left:100%;} +.carousel-inner>.prev{left:-100%;} +.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0;} +.carousel-inner>.active.left{left:-100%;} +.carousel-inner>.active.right{left:100%;} +.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#ffffff;text-align:center;background:#222222;border:3px solid #ffffff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:0.5;filter:alpha(opacity=50);}.carousel-control.right{left:auto;right:15px;} +.carousel-control:hover,.carousel-control:focus{color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90);} +.carousel-indicators{position:absolute;top:15px;right:15px;z-index:5;margin:0;list-style:none;}.carousel-indicators li{display:block;float:left;width:10px;height:10px;margin-left:5px;text-indent:-999px;background-color:#ccc;background-color:rgba(255, 255, 255, 0.25);border-radius:5px;} +.carousel-indicators .active{background-color:#fff;} +.carousel-caption{position:absolute;left:0;right:0;bottom:0;padding:15px;background:#333333;background:rgba(0, 0, 0, 0.75);} +.carousel-caption h4,.carousel-caption p{color:#ffffff;line-height:20px;} +.carousel-caption h4{margin:0 0 5px;} +.carousel-caption p{margin-bottom:0;} +.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);} +.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} +.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000000;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20);}.close:hover,.close:focus{color:#000000;text-decoration:none;cursor:pointer;opacity:0.4;filter:alpha(opacity=40);} +button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none;} +.pull-right{float:right;} +.pull-left{float:left;} +.hide{display:none;} +.show{display:block;} +.invisible{visibility:hidden;} +.affix{position:fixed;} +.fade{opacity:0;-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;}.fade.in{opacity:1;} +.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height 0.35s ease;-moz-transition:height 0.35s ease;-o-transition:height 0.35s ease;transition:height 0.35s ease;}.collapse.in{height:auto;} +@-ms-viewport{width:device-width;}.hidden{display:none;visibility:hidden;} +.visible-phone{display:none !important;} +.visible-tablet{display:none !important;} +.hidden-desktop{display:none !important;} +.visible-desktop{display:inherit !important;} +@media (min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit !important;} .visible-desktop{display:none !important ;} .visible-tablet{display:inherit !important;} .hidden-tablet{display:none !important;}}@media (max-width:767px){.hidden-desktop{display:inherit !important;} .visible-desktop{display:none !important;} .visible-phone{display:inherit !important;} .hidden-phone{display:none !important;}}.visible-print{display:none !important;} +@media print{.visible-print{display:inherit !important;} .hidden-print{display:none !important;}}@media (max-width:767px){body{padding-left:20px;padding-right:20px;} .navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-left:-20px;margin-right:-20px;} .container-fluid{padding:0;} .dl-horizontal dt{float:none;clear:none;width:auto;text-align:left;} .dl-horizontal dd{margin-left:0;} .container{width:auto;} .row-fluid{width:100%;} .row,.thumbnails{margin-left:0;} .thumbnails>li{float:none;margin-left:0;} [class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{float:none;display:block;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;} .span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;} .row-fluid [class*="offset"]:first-child{margin-left:0;} .input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;} .input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto;} .controls-row [class*="span"]+[class*="span"]{margin-left:0;} .modal{position:fixed;top:20px;left:20px;right:20px;width:auto;margin:0;}.modal.fade{top:-100px;} .modal.fade.in{top:20px;}}@media (max-width:480px){.nav-collapse{-webkit-transform:translate3d(0, 0, 0);} .page-header h1 small{display:block;line-height:20px;} input[type="checkbox"],input[type="radio"]{border:1px solid #ccc;} .form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left;} .form-horizontal .controls{margin-left:0;} .form-horizontal .control-list{padding-top:0;} .form-horizontal .form-actions{padding-left:10px;padding-right:10px;} .media .pull-left,.media .pull-right{float:none;display:block;margin-bottom:10px;} .media-object{margin-right:0;margin-left:0;} .modal{top:10px;left:10px;right:10px;} .modal-header .close{padding:10px;margin:-10px;} .carousel-caption{position:static;}}@media (min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:"";line-height:0;} .row:after{clear:both;} [class*="span"]{float:left;min-height:1px;margin-left:20px;} .container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px;} .span12{width:724px;} .span11{width:662px;} .span10{width:600px;} .span9{width:538px;} .span8{width:476px;} .span7{width:414px;} .span6{width:352px;} .span5{width:290px;} .span4{width:228px;} .span3{width:166px;} .span2{width:104px;} .span1{width:42px;} .offset12{margin-left:764px;} .offset11{margin-left:702px;} .offset10{margin-left:640px;} .offset9{margin-left:578px;} .offset8{margin-left:516px;} .offset7{margin-left:454px;} .offset6{margin-left:392px;} .offset5{margin-left:330px;} .offset4{margin-left:268px;} .offset3{margin-left:206px;} .offset2{margin-left:144px;} .offset1{margin-left:82px;} .row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0;} .row-fluid:after{clear:both;} .row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;} .row-fluid [class*="span"]:first-child{margin-left:0;} .row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%;} .row-fluid .span12{width:100%;*width:99.94680851063829%;} .row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%;} .row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%;} .row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%;} .row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%;} .row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%;} .row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%;} .row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%;} .row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%;} .row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%;} .row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%;} .row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%;} .row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%;} .row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%;} .row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%;} .row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%;} .row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%;} .row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%;} .row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%;} .row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%;} .row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%;} .row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%;} .row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%;} .row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%;} .row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%;} .row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%;} .row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%;} .row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%;} .row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%;} .row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%;} .row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%;} .row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%;} .row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%;} .row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%;} .row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%;} .row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%;} input,textarea,.uneditable-input{margin-left:0;} .controls-row [class*="span"]+[class*="span"]{margin-left:20px;} input.span12,textarea.span12,.uneditable-input.span12{width:710px;} input.span11,textarea.span11,.uneditable-input.span11{width:648px;} input.span10,textarea.span10,.uneditable-input.span10{width:586px;} input.span9,textarea.span9,.uneditable-input.span9{width:524px;} input.span8,textarea.span8,.uneditable-input.span8{width:462px;} input.span7,textarea.span7,.uneditable-input.span7{width:400px;} input.span6,textarea.span6,.uneditable-input.span6{width:338px;} input.span5,textarea.span5,.uneditable-input.span5{width:276px;} input.span4,textarea.span4,.uneditable-input.span4{width:214px;} input.span3,textarea.span3,.uneditable-input.span3{width:152px;} input.span2,textarea.span2,.uneditable-input.span2{width:90px;} input.span1,textarea.span1,.uneditable-input.span1{width:28px;}}@media (min-width:1200px){.row{margin-left:-30px;*zoom:1;}.row:before,.row:after{display:table;content:"";line-height:0;} .row:after{clear:both;} [class*="span"]{float:left;min-height:1px;margin-left:30px;} .container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px;} .span12{width:1170px;} .span11{width:1070px;} .span10{width:970px;} .span9{width:870px;} .span8{width:770px;} .span7{width:670px;} .span6{width:570px;} .span5{width:470px;} .span4{width:370px;} .span3{width:270px;} .span2{width:170px;} .span1{width:70px;} .offset12{margin-left:1230px;} .offset11{margin-left:1130px;} .offset10{margin-left:1030px;} .offset9{margin-left:930px;} .offset8{margin-left:830px;} .offset7{margin-left:730px;} .offset6{margin-left:630px;} .offset5{margin-left:530px;} .offset4{margin-left:430px;} .offset3{margin-left:330px;} .offset2{margin-left:230px;} .offset1{margin-left:130px;} .row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0;} .row-fluid:after{clear:both;} .row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;} .row-fluid [class*="span"]:first-child{margin-left:0;} .row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%;} .row-fluid .span12{width:100%;*width:99.94680851063829%;} .row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%;} .row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%;} .row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%;} .row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%;} .row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%;} .row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%;} .row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%;} .row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%;} .row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%;} .row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%;} .row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%;} .row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%;} .row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%;} .row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%;} .row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%;} .row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%;} .row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%;} .row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%;} .row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%;} .row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%;} .row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%;} .row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%;} .row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%;} .row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%;} .row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%;} .row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%;} .row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%;} .row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%;} .row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%;} .row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%;} .row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%;} .row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%;} .row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%;} .row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%;} .row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%;} input,textarea,.uneditable-input{margin-left:0;} .controls-row [class*="span"]+[class*="span"]{margin-left:30px;} input.span12,textarea.span12,.uneditable-input.span12{width:1156px;} input.span11,textarea.span11,.uneditable-input.span11{width:1056px;} input.span10,textarea.span10,.uneditable-input.span10{width:956px;} input.span9,textarea.span9,.uneditable-input.span9{width:856px;} input.span8,textarea.span8,.uneditable-input.span8{width:756px;} input.span7,textarea.span7,.uneditable-input.span7{width:656px;} input.span6,textarea.span6,.uneditable-input.span6{width:556px;} input.span5,textarea.span5,.uneditable-input.span5{width:456px;} input.span4,textarea.span4,.uneditable-input.span4{width:356px;} input.span3,textarea.span3,.uneditable-input.span3{width:256px;} input.span2,textarea.span2,.uneditable-input.span2{width:156px;} input.span1,textarea.span1,.uneditable-input.span1{width:56px;} .thumbnails{margin-left:-30px;} .thumbnails>li{margin-left:30px;} .row-fluid .thumbnails{margin-left:0;}}@media (max-width:979px){body{padding-top:0;} .navbar-fixed-top,.navbar-fixed-bottom{position:static;} .navbar-fixed-top{margin-bottom:20px;} .navbar-fixed-bottom{margin-top:20px;} .navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px;} .navbar .container{width:auto;padding:0;} .navbar .brand{padding-left:10px;padding-right:10px;margin:0 0 0 -5px;} .nav-collapse{clear:both;} .nav-collapse .nav{float:none;margin:0 0 10px;} .nav-collapse .nav>li{float:none;} .nav-collapse .nav>li>a{margin-bottom:2px;} .nav-collapse .nav>.divider-vertical{display:none;} .nav-collapse .nav .nav-header{color:#777777;text-shadow:none;} .nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} .nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} .nav-collapse .dropdown-menu li+li a{margin-bottom:2px;} .nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2;} .navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999999;} .navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111111;} .nav-collapse.in .btn-group{margin-top:5px;padding:0;} .nav-collapse .dropdown-menu{position:static;top:auto;left:auto;float:none;display:none;max-width:none;margin:0 15px;padding:0;background-color:transparent;border:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} .nav-collapse .open>.dropdown-menu{display:block;} .nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none;} .nav-collapse .dropdown-menu .divider{display:none;} .nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none;} .nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);} .navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111111;border-bottom-color:#111111;} .navbar .nav-collapse .nav.pull-right{float:none;margin-left:0;} .nav-collapse,.nav-collapse.collapse{overflow:hidden;height:0;} .navbar .btn-navbar{display:block;} .navbar-static .navbar-inner{padding-left:10px;padding-right:10px;}}@media (min-width:980px){.nav-collapse.collapse{height:auto !important;overflow:visible !important;}} diff --git a/docs/api/css/font-awesome.min.css b/docs/api/css/font-awesome.min.css new file mode 100644 index 0000000..866437f --- /dev/null +++ b/docs/api/css/font-awesome.min.css @@ -0,0 +1,403 @@ +@font-face{font-family:'FontAwesome';src:url('../font/fontawesome-webfont.eot?v=3.2.1');src:url('../font/fontawesome-webfont.eot?#iefix&v=3.2.1') format('embedded-opentype'),url('../font/fontawesome-webfont.woff?v=3.2.1') format('woff'),url('../font/fontawesome-webfont.ttf?v=3.2.1') format('truetype'),url('../font/fontawesome-webfont.svg#fontawesomeregular?v=3.2.1') format('svg');font-weight:normal;font-style:normal;}[class^="icon-"],[class*=" icon-"]{font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;*margin-right:.3em;} +[class^="icon-"]:before,[class*=" icon-"]:before{text-decoration:inherit;display:inline-block;speak:none;} +.icon-large:before{vertical-align:-10%;font-size:1.3333333333333333em;} +a [class^="icon-"],a [class*=" icon-"]{display:inline;} +[class^="icon-"].icon-fixed-width,[class*=" icon-"].icon-fixed-width{display:inline-block;width:1.1428571428571428em;text-align:right;padding-right:0.2857142857142857em;}[class^="icon-"].icon-fixed-width.icon-large,[class*=" icon-"].icon-fixed-width.icon-large{width:1.4285714285714286em;} +.icons-ul{margin-left:2.142857142857143em;list-style-type:none;}.icons-ul>li{position:relative;} +.icons-ul .icon-li{position:absolute;left:-2.142857142857143em;width:2.142857142857143em;text-align:center;line-height:inherit;} +[class^="icon-"].hide,[class*=" icon-"].hide{display:none;} +.icon-muted{color:#eeeeee;} +.icon-light{color:#ffffff;} +.icon-dark{color:#333333;} +.icon-border{border:solid 1px #eeeeee;padding:.2em .25em .15em;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +.icon-2x{font-size:2em;}.icon-2x.icon-border{border-width:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.icon-3x{font-size:3em;}.icon-3x.icon-border{border-width:3px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;} +.icon-4x{font-size:4em;}.icon-4x.icon-border{border-width:4px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} +.icon-5x{font-size:5em;}.icon-5x.icon-border{border-width:5px;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px;} +.pull-right{float:right;} +.pull-left{float:left;} +[class^="icon-"].pull-left,[class*=" icon-"].pull-left{margin-right:.3em;} +[class^="icon-"].pull-right,[class*=" icon-"].pull-right{margin-left:.3em;} +[class^="icon-"],[class*=" icon-"]{display:inline;width:auto;height:auto;line-height:normal;vertical-align:baseline;background-image:none;background-position:0% 0%;background-repeat:repeat;margin-top:0;} +.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"]{background-image:none;} +.btn [class^="icon-"].icon-large,.nav [class^="icon-"].icon-large,.btn [class*=" icon-"].icon-large,.nav [class*=" icon-"].icon-large{line-height:.9em;} +.btn [class^="icon-"].icon-spin,.nav [class^="icon-"].icon-spin,.btn [class*=" icon-"].icon-spin,.nav [class*=" icon-"].icon-spin{display:inline-block;} +.nav-tabs [class^="icon-"],.nav-pills [class^="icon-"],.nav-tabs [class*=" icon-"],.nav-pills [class*=" icon-"],.nav-tabs [class^="icon-"].icon-large,.nav-pills [class^="icon-"].icon-large,.nav-tabs [class*=" icon-"].icon-large,.nav-pills [class*=" icon-"].icon-large{line-height:.9em;} +.btn [class^="icon-"].pull-left.icon-2x,.btn [class*=" icon-"].pull-left.icon-2x,.btn [class^="icon-"].pull-right.icon-2x,.btn [class*=" icon-"].pull-right.icon-2x{margin-top:.18em;} +.btn [class^="icon-"].icon-spin.icon-large,.btn [class*=" icon-"].icon-spin.icon-large{line-height:.8em;} +.btn.btn-small [class^="icon-"].pull-left.icon-2x,.btn.btn-small [class*=" icon-"].pull-left.icon-2x,.btn.btn-small [class^="icon-"].pull-right.icon-2x,.btn.btn-small [class*=" icon-"].pull-right.icon-2x{margin-top:.25em;} +.btn.btn-large [class^="icon-"],.btn.btn-large [class*=" icon-"]{margin-top:0;}.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x,.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-top:.05em;} +.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x{margin-right:.2em;} +.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-left:.2em;} +.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{line-height:inherit;} +.icon-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:-35%;}.icon-stack [class^="icon-"],.icon-stack [class*=" icon-"]{display:block;text-align:center;position:absolute;width:100%;height:100%;font-size:1em;line-height:inherit;*line-height:2em;} +.icon-stack .icon-stack-base{font-size:2em;*line-height:1em;} +.icon-spin{display:inline-block;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;-webkit-animation:spin 2s infinite linear;animation:spin 2s infinite linear;} +a .icon-stack,a .icon-spin{display:inline-block;text-decoration:none;} +@-moz-keyframes spin{0%{-moz-transform:rotate(0deg);} 100%{-moz-transform:rotate(359deg);}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg);} 100%{-webkit-transform:rotate(359deg);}}@-o-keyframes spin{0%{-o-transform:rotate(0deg);} 100%{-o-transform:rotate(359deg);}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg);} 100%{-ms-transform:rotate(359deg);}}@keyframes spin{0%{transform:rotate(0deg);} 100%{transform:rotate(359deg);}}.icon-rotate-90:before{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);} +.icon-rotate-180:before{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);} +.icon-rotate-270:before{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);} +.icon-flip-horizontal:before{-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1);} +.icon-flip-vertical:before{-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1);} +a .icon-rotate-90:before,a .icon-rotate-180:before,a .icon-rotate-270:before,a .icon-flip-horizontal:before,a .icon-flip-vertical:before{display:inline-block;} +.icon-glass:before{content:"\f000";} +.icon-music:before{content:"\f001";} +.icon-search:before{content:"\f002";} +.icon-envelope-alt:before{content:"\f003";} +.icon-heart:before{content:"\f004";} +.icon-star:before{content:"\f005";} +.icon-star-empty:before{content:"\f006";} +.icon-user:before{content:"\f007";} +.icon-film:before{content:"\f008";} +.icon-th-large:before{content:"\f009";} +.icon-th:before{content:"\f00a";} +.icon-th-list:before{content:"\f00b";} +.icon-ok:before{content:"\f00c";} +.icon-remove:before{content:"\f00d";} +.icon-zoom-in:before{content:"\f00e";} +.icon-zoom-out:before{content:"\f010";} +.icon-power-off:before,.icon-off:before{content:"\f011";} +.icon-signal:before{content:"\f012";} +.icon-gear:before,.icon-cog:before{content:"\f013";} +.icon-trash:before{content:"\f014";} +.icon-home:before{content:"\f015";} +.icon-file-alt:before{content:"\f016";} +.icon-time:before{content:"\f017";} +.icon-road:before{content:"\f018";} +.icon-download-alt:before{content:"\f019";} +.icon-download:before{content:"\f01a";} +.icon-upload:before{content:"\f01b";} +.icon-inbox:before{content:"\f01c";} +.icon-play-circle:before{content:"\f01d";} +.icon-rotate-right:before,.icon-repeat:before{content:"\f01e";} +.icon-refresh:before{content:"\f021";} +.icon-list-alt:before{content:"\f022";} +.icon-lock:before{content:"\f023";} +.icon-flag:before{content:"\f024";} +.icon-headphones:before{content:"\f025";} +.icon-volume-off:before{content:"\f026";} +.icon-volume-down:before{content:"\f027";} +.icon-volume-up:before{content:"\f028";} +.icon-qrcode:before{content:"\f029";} +.icon-barcode:before{content:"\f02a";} +.icon-tag:before{content:"\f02b";} +.icon-tags:before{content:"\f02c";} +.icon-book:before{content:"\f02d";} +.icon-bookmark:before{content:"\f02e";} +.icon-print:before{content:"\f02f";} +.icon-camera:before{content:"\f030";} +.icon-font:before{content:"\f031";} +.icon-bold:before{content:"\f032";} +.icon-italic:before{content:"\f033";} +.icon-text-height:before{content:"\f034";} +.icon-text-width:before{content:"\f035";} +.icon-align-left:before{content:"\f036";} +.icon-align-center:before{content:"\f037";} +.icon-align-right:before{content:"\f038";} +.icon-align-justify:before{content:"\f039";} +.icon-list:before{content:"\f03a";} +.icon-indent-left:before{content:"\f03b";} +.icon-indent-right:before{content:"\f03c";} +.icon-facetime-video:before{content:"\f03d";} +.icon-picture:before{content:"\f03e";} +.icon-pencil:before{content:"\f040";} +.icon-map-marker:before{content:"\f041";} +.icon-adjust:before{content:"\f042";} +.icon-tint:before{content:"\f043";} +.icon-edit:before{content:"\f044";} +.icon-share:before{content:"\f045";} +.icon-check:before{content:"\f046";} +.icon-move:before{content:"\f047";} +.icon-step-backward:before{content:"\f048";} +.icon-fast-backward:before{content:"\f049";} +.icon-backward:before{content:"\f04a";} +.icon-play:before{content:"\f04b";} +.icon-pause:before{content:"\f04c";} +.icon-stop:before{content:"\f04d";} +.icon-forward:before{content:"\f04e";} +.icon-fast-forward:before{content:"\f050";} +.icon-step-forward:before{content:"\f051";} +.icon-eject:before{content:"\f052";} +.icon-chevron-left:before{content:"\f053";} +.icon-chevron-right:before{content:"\f054";} +.icon-plus-sign:before{content:"\f055";} +.icon-minus-sign:before{content:"\f056";} +.icon-remove-sign:before{content:"\f057";} +.icon-ok-sign:before{content:"\f058";} +.icon-question-sign:before{content:"\f059";} +.icon-info-sign:before{content:"\f05a";} +.icon-screenshot:before{content:"\f05b";} +.icon-remove-circle:before{content:"\f05c";} +.icon-ok-circle:before{content:"\f05d";} +.icon-ban-circle:before{content:"\f05e";} +.icon-arrow-left:before{content:"\f060";} +.icon-arrow-right:before{content:"\f061";} +.icon-arrow-up:before{content:"\f062";} +.icon-arrow-down:before{content:"\f063";} +.icon-mail-forward:before,.icon-share-alt:before{content:"\f064";} +.icon-resize-full:before{content:"\f065";} +.icon-resize-small:before{content:"\f066";} +.icon-plus:before{content:"\f067";} +.icon-minus:before{content:"\f068";} +.icon-asterisk:before{content:"\f069";} +.icon-exclamation-sign:before{content:"\f06a";} +.icon-gift:before{content:"\f06b";} +.icon-leaf:before{content:"\f06c";} +.icon-fire:before{content:"\f06d";} +.icon-eye-open:before{content:"\f06e";} +.icon-eye-close:before{content:"\f070";} +.icon-warning-sign:before{content:"\f071";} +.icon-plane:before{content:"\f072";} +.icon-calendar:before{content:"\f073";} +.icon-random:before{content:"\f074";} +.icon-comment:before{content:"\f075";} +.icon-magnet:before{content:"\f076";} +.icon-chevron-up:before{content:"\f077";} +.icon-chevron-down:before{content:"\f078";} +.icon-retweet:before{content:"\f079";} +.icon-shopping-cart:before{content:"\f07a";} +.icon-folder-close:before{content:"\f07b";} +.icon-folder-open:before{content:"\f07c";} +.icon-resize-vertical:before{content:"\f07d";} +.icon-resize-horizontal:before{content:"\f07e";} +.icon-bar-chart:before{content:"\f080";} +.icon-twitter-sign:before{content:"\f081";} +.icon-facebook-sign:before{content:"\f082";} +.icon-camera-retro:before{content:"\f083";} +.icon-key:before{content:"\f084";} +.icon-gears:before,.icon-cogs:before{content:"\f085";} +.icon-comments:before{content:"\f086";} +.icon-thumbs-up-alt:before{content:"\f087";} +.icon-thumbs-down-alt:before{content:"\f088";} +.icon-star-half:before{content:"\f089";} +.icon-heart-empty:before{content:"\f08a";} +.icon-signout:before{content:"\f08b";} +.icon-linkedin-sign:before{content:"\f08c";} +.icon-pushpin:before{content:"\f08d";} +.icon-external-link:before{content:"\f08e";} +.icon-signin:before{content:"\f090";} +.icon-trophy:before{content:"\f091";} +.icon-github-sign:before{content:"\f092";} +.icon-upload-alt:before{content:"\f093";} +.icon-lemon:before{content:"\f094";} +.icon-phone:before{content:"\f095";} +.icon-unchecked:before,.icon-check-empty:before{content:"\f096";} +.icon-bookmark-empty:before{content:"\f097";} +.icon-phone-sign:before{content:"\f098";} +.icon-twitter:before{content:"\f099";} +.icon-facebook:before{content:"\f09a";} +.icon-github:before{content:"\f09b";} +.icon-unlock:before{content:"\f09c";} +.icon-credit-card:before{content:"\f09d";} +.icon-rss:before{content:"\f09e";} +.icon-hdd:before{content:"\f0a0";} +.icon-bullhorn:before{content:"\f0a1";} +.icon-bell:before{content:"\f0a2";} +.icon-certificate:before{content:"\f0a3";} +.icon-hand-right:before{content:"\f0a4";} +.icon-hand-left:before{content:"\f0a5";} +.icon-hand-up:before{content:"\f0a6";} +.icon-hand-down:before{content:"\f0a7";} +.icon-circle-arrow-left:before{content:"\f0a8";} +.icon-circle-arrow-right:before{content:"\f0a9";} +.icon-circle-arrow-up:before{content:"\f0aa";} +.icon-circle-arrow-down:before{content:"\f0ab";} +.icon-globe:before{content:"\f0ac";} +.icon-wrench:before{content:"\f0ad";} +.icon-tasks:before{content:"\f0ae";} +.icon-filter:before{content:"\f0b0";} +.icon-briefcase:before{content:"\f0b1";} +.icon-fullscreen:before{content:"\f0b2";} +.icon-group:before{content:"\f0c0";} +.icon-link:before{content:"\f0c1";} +.icon-cloud:before{content:"\f0c2";} +.icon-beaker:before{content:"\f0c3";} +.icon-cut:before{content:"\f0c4";} +.icon-copy:before{content:"\f0c5";} +.icon-paperclip:before,.icon-paper-clip:before{content:"\f0c6";} +.icon-save:before{content:"\f0c7";} +.icon-sign-blank:before{content:"\f0c8";} +.icon-reorder:before{content:"\f0c9";} +.icon-list-ul:before{content:"\f0ca";} +.icon-list-ol:before{content:"\f0cb";} +.icon-strikethrough:before{content:"\f0cc";} +.icon-underline:before{content:"\f0cd";} +.icon-table:before{content:"\f0ce";} +.icon-magic:before{content:"\f0d0";} +.icon-truck:before{content:"\f0d1";} +.icon-pinterest:before{content:"\f0d2";} +.icon-pinterest-sign:before{content:"\f0d3";} +.icon-google-plus-sign:before{content:"\f0d4";} +.icon-google-plus:before{content:"\f0d5";} +.icon-money:before{content:"\f0d6";} +.icon-caret-down:before{content:"\f0d7";} +.icon-caret-up:before{content:"\f0d8";} +.icon-caret-left:before{content:"\f0d9";} +.icon-caret-right:before{content:"\f0da";} +.icon-columns:before{content:"\f0db";} +.icon-sort:before{content:"\f0dc";} +.icon-sort-down:before{content:"\f0dd";} +.icon-sort-up:before{content:"\f0de";} +.icon-envelope:before{content:"\f0e0";} +.icon-linkedin:before{content:"\f0e1";} +.icon-rotate-left:before,.icon-undo:before{content:"\f0e2";} +.icon-legal:before{content:"\f0e3";} +.icon-dashboard:before{content:"\f0e4";} +.icon-comment-alt:before{content:"\f0e5";} +.icon-comments-alt:before{content:"\f0e6";} +.icon-bolt:before{content:"\f0e7";} +.icon-sitemap:before{content:"\f0e8";} +.icon-umbrella:before{content:"\f0e9";} +.icon-paste:before{content:"\f0ea";} +.icon-lightbulb:before{content:"\f0eb";} +.icon-exchange:before{content:"\f0ec";} +.icon-cloud-download:before{content:"\f0ed";} +.icon-cloud-upload:before{content:"\f0ee";} +.icon-user-md:before{content:"\f0f0";} +.icon-stethoscope:before{content:"\f0f1";} +.icon-suitcase:before{content:"\f0f2";} +.icon-bell-alt:before{content:"\f0f3";} +.icon-coffee:before{content:"\f0f4";} +.icon-food:before{content:"\f0f5";} +.icon-file-text-alt:before{content:"\f0f6";} +.icon-building:before{content:"\f0f7";} +.icon-hospital:before{content:"\f0f8";} +.icon-ambulance:before{content:"\f0f9";} +.icon-medkit:before{content:"\f0fa";} +.icon-fighter-jet:before{content:"\f0fb";} +.icon-beer:before{content:"\f0fc";} +.icon-h-sign:before{content:"\f0fd";} +.icon-plus-sign-alt:before{content:"\f0fe";} +.icon-double-angle-left:before{content:"\f100";} +.icon-double-angle-right:before{content:"\f101";} +.icon-double-angle-up:before{content:"\f102";} +.icon-double-angle-down:before{content:"\f103";} +.icon-angle-left:before{content:"\f104";} +.icon-angle-right:before{content:"\f105";} +.icon-angle-up:before{content:"\f106";} +.icon-angle-down:before{content:"\f107";} +.icon-desktop:before{content:"\f108";} +.icon-laptop:before{content:"\f109";} +.icon-tablet:before{content:"\f10a";} +.icon-mobile-phone:before{content:"\f10b";} +.icon-circle-blank:before{content:"\f10c";} +.icon-quote-left:before{content:"\f10d";} +.icon-quote-right:before{content:"\f10e";} +.icon-spinner:before{content:"\f110";} +.icon-circle:before{content:"\f111";} +.icon-mail-reply:before,.icon-reply:before{content:"\f112";} +.icon-github-alt:before{content:"\f113";} +.icon-folder-close-alt:before{content:"\f114";} +.icon-folder-open-alt:before{content:"\f115";} +.icon-expand-alt:before{content:"\f116";} +.icon-collapse-alt:before{content:"\f117";} +.icon-smile:before{content:"\f118";} +.icon-frown:before{content:"\f119";} +.icon-meh:before{content:"\f11a";} +.icon-gamepad:before{content:"\f11b";} +.icon-keyboard:before{content:"\f11c";} +.icon-flag-alt:before{content:"\f11d";} +.icon-flag-checkered:before{content:"\f11e";} +.icon-terminal:before{content:"\f120";} +.icon-code:before{content:"\f121";} +.icon-reply-all:before{content:"\f122";} +.icon-mail-reply-all:before{content:"\f122";} +.icon-star-half-full:before,.icon-star-half-empty:before{content:"\f123";} +.icon-location-arrow:before{content:"\f124";} +.icon-crop:before{content:"\f125";} +.icon-code-fork:before{content:"\f126";} +.icon-unlink:before{content:"\f127";} +.icon-question:before{content:"\f128";} +.icon-info:before{content:"\f129";} +.icon-exclamation:before{content:"\f12a";} +.icon-superscript:before{content:"\f12b";} +.icon-subscript:before{content:"\f12c";} +.icon-eraser:before{content:"\f12d";} +.icon-puzzle-piece:before{content:"\f12e";} +.icon-microphone:before{content:"\f130";} +.icon-microphone-off:before{content:"\f131";} +.icon-shield:before{content:"\f132";} +.icon-calendar-empty:before{content:"\f133";} +.icon-fire-extinguisher:before{content:"\f134";} +.icon-rocket:before{content:"\f135";} +.icon-maxcdn:before{content:"\f136";} +.icon-chevron-sign-left:before{content:"\f137";} +.icon-chevron-sign-right:before{content:"\f138";} +.icon-chevron-sign-up:before{content:"\f139";} +.icon-chevron-sign-down:before{content:"\f13a";} +.icon-html5:before{content:"\f13b";} +.icon-css3:before{content:"\f13c";} +.icon-anchor:before{content:"\f13d";} +.icon-unlock-alt:before{content:"\f13e";} +.icon-bullseye:before{content:"\f140";} +.icon-ellipsis-horizontal:before{content:"\f141";} +.icon-ellipsis-vertical:before{content:"\f142";} +.icon-rss-sign:before{content:"\f143";} +.icon-play-sign:before{content:"\f144";} +.icon-ticket:before{content:"\f145";} +.icon-minus-sign-alt:before{content:"\f146";} +.icon-check-minus:before{content:"\f147";} +.icon-level-up:before{content:"\f148";} +.icon-level-down:before{content:"\f149";} +.icon-check-sign:before{content:"\f14a";} +.icon-edit-sign:before{content:"\f14b";} +.icon-external-link-sign:before{content:"\f14c";} +.icon-share-sign:before{content:"\f14d";} +.icon-compass:before{content:"\f14e";} +.icon-collapse:before{content:"\f150";} +.icon-collapse-top:before{content:"\f151";} +.icon-expand:before{content:"\f152";} +.icon-euro:before,.icon-eur:before{content:"\f153";} +.icon-gbp:before{content:"\f154";} +.icon-dollar:before,.icon-usd:before{content:"\f155";} +.icon-rupee:before,.icon-inr:before{content:"\f156";} +.icon-yen:before,.icon-jpy:before{content:"\f157";} +.icon-renminbi:before,.icon-cny:before{content:"\f158";} +.icon-won:before,.icon-krw:before{content:"\f159";} +.icon-bitcoin:before,.icon-btc:before{content:"\f15a";} +.icon-file:before{content:"\f15b";} +.icon-file-text:before{content:"\f15c";} +.icon-sort-by-alphabet:before{content:"\f15d";} +.icon-sort-by-alphabet-alt:before{content:"\f15e";} +.icon-sort-by-attributes:before{content:"\f160";} +.icon-sort-by-attributes-alt:before{content:"\f161";} +.icon-sort-by-order:before{content:"\f162";} +.icon-sort-by-order-alt:before{content:"\f163";} +.icon-thumbs-up:before{content:"\f164";} +.icon-thumbs-down:before{content:"\f165";} +.icon-youtube-sign:before{content:"\f166";} +.icon-youtube:before{content:"\f167";} +.icon-xing:before{content:"\f168";} +.icon-xing-sign:before{content:"\f169";} +.icon-youtube-play:before{content:"\f16a";} +.icon-dropbox:before{content:"\f16b";} +.icon-stackexchange:before{content:"\f16c";} +.icon-instagram:before{content:"\f16d";} +.icon-flickr:before{content:"\f16e";} +.icon-adn:before{content:"\f170";} +.icon-bitbucket:before{content:"\f171";} +.icon-bitbucket-sign:before{content:"\f172";} +.icon-tumblr:before{content:"\f173";} +.icon-tumblr-sign:before{content:"\f174";} +.icon-long-arrow-down:before{content:"\f175";} +.icon-long-arrow-up:before{content:"\f176";} +.icon-long-arrow-left:before{content:"\f177";} +.icon-long-arrow-right:before{content:"\f178";} +.icon-apple:before{content:"\f179";} +.icon-windows:before{content:"\f17a";} +.icon-android:before{content:"\f17b";} +.icon-linux:before{content:"\f17c";} +.icon-dribbble:before{content:"\f17d";} +.icon-skype:before{content:"\f17e";} +.icon-foursquare:before{content:"\f180";} +.icon-trello:before{content:"\f181";} +.icon-female:before{content:"\f182";} +.icon-male:before{content:"\f183";} +.icon-gittip:before{content:"\f184";} +.icon-sun:before{content:"\f185";} +.icon-moon:before{content:"\f186";} +.icon-archive:before{content:"\f187";} +.icon-bug:before{content:"\f188";} +.icon-vk:before{content:"\f189";} +.icon-weibo:before{content:"\f18a";} +.icon-renren:before{content:"\f18b";} diff --git a/docs/api/css/jquery.iviewer.css b/docs/api/css/jquery.iviewer.css new file mode 100644 index 0000000..11f5f09 --- /dev/null +++ b/docs/api/css/jquery.iviewer.css @@ -0,0 +1,65 @@ +.viewer { + -ms-touch-action: none; +} + +.iviewer_common { + position:absolute; + bottom:10px; + border: 1px solid #000; + height: 28px; + z-index: 5000; +} + +.iviewer_cursor { + cursor: url(../images/iviewer/hand.cur) 6 8, pointer; +} + +.iviewer_drag_cursor { + cursor: url(../images/iviewer/grab.cur) 6 8, pointer; +} + +.iviewer_button { + width: 28px; + cursor: pointer; + background-position: center center; + background-repeat: no-repeat; +} + +.iviewer_zoom_in { + left: 20px; + background: url(../images/iviewer/iviewer.zoom_in.png); +} + +.iviewer_zoom_out { + left: 55px; + background: url(../images/iviewer/iviewer.zoom_out.png); +} + +.iviewer_zoom_zero { + left: 90px; + background: url(../images/iviewer/iviewer.zoom_zero.png); +} + +.iviewer_zoom_fit { + left: 125px; + background: url(../images/iviewer/iviewer.zoom_fit.png); +} + +.iviewer_zoom_status { + left: 160px; + font: 1em/28px Sans; + color: #000; + background-color: #fff; + text-align: center; + width: 60px; +} + +.iviewer_rotate_left { + left: 227px; + background: #fff url(../images/iviewer/iviewer.rotate_left.png) center center no-repeat; +} + +.iviewer_rotate_right { + left: 262px; + background: #fff url(../images/iviewer/iviewer.rotate_right.png) center center no-repeat; +} diff --git a/docs/api/css/phpdocumentor-clean-icons/Read Me.txt b/docs/api/css/phpdocumentor-clean-icons/Read Me.txt new file mode 100644 index 0000000..9d2b9e5 --- /dev/null +++ b/docs/api/css/phpdocumentor-clean-icons/Read Me.txt @@ -0,0 +1,3 @@ +To modify your generated font, use the *dev.svg* file, located in the *fonts* folder in this package. You can import this dev.svg file to the IcoMoon app. All the tags (class names) and the Unicode points of your glyphs are saved in this file. + +See the documentation for more info on how to use this package: http://icomoon.io/#docs/font-face \ No newline at end of file diff --git a/docs/api/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.dev.svg b/docs/api/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.dev.svg new file mode 100644 index 0000000..8b543c1 --- /dev/null +++ b/docs/api/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.dev.svg @@ -0,0 +1,17 @@ + + + + +This is a custom SVG font generated by IcoMoon. + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/api/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.eot b/docs/api/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.eot new file mode 100644 index 0000000000000000000000000000000000000000..ef43f265ab2f344b7871c6d26a8722bae125b956 GIT binary patch literal 2324 zcmds3O>7%Q6#iy^oj7)E$FUn4$P!bB5}bP1`D;m0g&ens6e0)_5(i`)Cr%ar;5br2 z9CARMD%1m_w;nif;KTtT=!tTo5`s%_RLBQJ;1Vi9g@i=RH@n+3EeX(y6SJOu?|tvh zym>Qk=Q-X5SOGvlfouwN)n$lW6q1{p_hdwTN)0w8KbOC>eH*(&9W+p3UqT%vY@muV zYG_evB12@2a*;>|tCZ^G);T!wH=0F`XwqucwYFem3|WqOFi#vP;|ehb74wC{!r98p zi5VcE5gk08pDkb<24H?q*?R7UW6rTFH-fTM8{^x) zl%IR0cjgv?-no7q{_GR#;n7Z8foM|<_{Yxa`D=ID7?bup5|+EPx9Kio;zh`vlBptX zq}3^Q*#u|32ps`2CzdIn_USDQ;H|y6C5?B}iTwQX>=I73anoM|*Jf?*u0T3++lL;Z zoOB;<5)s)4-E}Y}DajQLa*Z-Y0=FYuP&Vv}AZXK4}7R)TE8*mWoa@#1qe{_EcT z*sJ^h@nM|7CVrQ@>1PY~Bq|iMjr2(P*qY7>n71(Qr5(R(rdnv3M-f8|>-P z_3rLSByl(r?FwmKfnYEkiX0Hj&Zm8`zKQ(dhe%Y#FqNZ*ZWu~n7W}o zu4<~Px{1%Id+?tBMG1bp<36t-h-IHwF@f)VUc-Pm10I{SELD&u5r4!i#q;J}$#-S#iEi{<9}YQ1LJW4u!TTaSOeW#u=wx0w|9=R_z% z6`Ih^joNA + + + +This is a custom SVG font generated by IcoMoon. + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/api/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.ttf b/docs/api/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.ttf new file mode 100644 index 0000000000000000000000000000000000000000..1937c7a31fdb7659063738dfe0a1fcc8bf325636 GIT binary patch literal 2080 zcmds2O>7%Q6#iy^oj7*<7rUW`EHQN`!LD~5J84N#g&ens6e0)_5(i`)Cw3M8;5br2 z9CARMD#QW7tp^SqIB`G-dZL`Dgy7N}74iWQxRgpzAt4d-jlF5p(g2k>F{?N4eczjz z_ulO64g>%nwxMFFSX@3^eK9%@cr>!{(}jg1W?%s3_r%sS&mD7)UAi^_h!==g%JovK z8~x=f@t2fm*GrqNUXLrmyhgugedE=&t)22kKz*G4U#peU>L2qTAEi(F^HmaBp!WlP z5#mRx_0Dd`JjMBO;y}H0zJ;@3Y%xb{HA?kL-hSr`K(_$pT&ua+ z$$jwpD~x}E{w2uTDA(UBEWR{4e-mDX1n{;0vrlg8URQzW5_>Rk_veUiuZy%Kb|o!W z+TZj(W8!(ponml`=yRteWD{KVZRqfbMX^SF+SNBPjyL24o%^)c0(%Da(%Ql*PIYmE zS;-ujo3(u_gS`~-;b01Jtp9M6$ViFz^8gl!WZrS+n4$LURoGQ-fbWS-Mv<1K1qNd! z$tH|j0c9bB`uZNXyL&(O`~H8nA7`+Q-{o$)r-geG6b~yr9HP71FIYpY_nY`hT)`+N zV1cJRJZU^ABGD&=lRqhDk_j{GL`fLaVlqi0AG602V~JcMm%U&Y3igk(yZZUm+}xDx zeqPQle=DxIJe`jXcmTB^2s{%#|(0O68FmP;)ERxhK9YNkR1>D z{Sm)9Ivfl|LV;25$cU~F4+jF#!-3$CPaE=hy?$TdfLeAx4Tr+H!r=#~RK+lrqlRu6 zhHvD^_*gjP^Lb3&&>mGaRaN^3pH+|GUH6L;{B~ztTY(pAuB{@6?_68MxH#k5I=tdF z*EYysl3%1YM8@H|16x59PrJ5?b7;G^28Vye!5AG;@v>_h8-H&XAvAO8gP7(vwl literal 0 HcmV?d00001 diff --git a/docs/api/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.woff b/docs/api/css/phpdocumentor-clean-icons/fonts/phpdocumentor-clean-icons.woff new file mode 100644 index 0000000000000000000000000000000000000000..32fe30d4d42925d3011635b139e0079025c8862c GIT binary patch literal 1832 zcmZWqc{JPE9={}wwG~TEERWPuYc+{d2B|fq(o!IP5XdJ0fPaC!l_97Gcq&>t+B*=SbL1|E zfCe}KcX2JK^*ZNdt5;Xl|h3j8seJ(hz6Nr z^PYxV340V~7 zYl*hLsyt{o=Aq4QZ8|2|C3Yhy1GZgXhyAU??%JsgL6|K}ZpBWS+&c1FHhIeqwUoT(KKUIC4uI@oR zwRpPunVy;7ZAbGw7lQysa8x9XNR&|9ADOK!@STe{XuEvP-tFp;-r@~B`l{>wT-zdD zDs$sV2e%}BK0H60=v?eOcQs*{#qQ6WEPc7zdz!H=+Wu^q(MO*;E2M8CA__7*qHv`w z@(Z$iQ?5C)B0n&2?qAi%I_CROR%*&&__REW+mhQlC*FsxcfFJ+IVUXo=ou{d;jLn*}{c{w<7x9di{Ez-xyKTN8x-sNiy+=O3zR6;z*@-DMQdz-GGRRKXEw1|#% z>_Do^AwTzw!eSFg__;oQO-fQMVdxzbzu6NJt8Lo@tsHCyK?m3}LT_;l_aNXQni%Wj` z42G$=<>%;L)0Y7%{Py^e`r|K;1f7LTS$Qi;`WmHg@+t&6nCf<;K|^UDv7Gl8l!oKC z*6KNP4xU)trwgyfuw{7F)W?f$w`fcrOmr_NU71Y16mxv6d9?m%3rRYgULBLh@wFZ7 zCM4kvoAQaT2~@}pT&(7H8`dUSNk43Ato-cH&!cn|J(r%vxbO& ztJv#2euVwd^XlEmMigIZp{MiRtIJGfyQ96>+;FYCU$gAq9sZ=#B{^!$x6}CXpsJcN zb1H?;*B7wpBkcD4RdG(5wYp>n@k~5*?1OG|Op5NZRg#9{^PqWTgDUs8^shnpU9#!H z2`9qe$^_!awE{P^tLwI8b==22A2NHp!pts9!o%?vf7s2|&Me{G!np@TiIpox>Fq)M z6vA`!B1!(+jb#lEUqZrTnA;V4YhC81H{WEuGcFn1JyUxim=m1Vwy>1eOFFe&jx%=X zooho6QfRsE(-ForBd65sdA+1UbyR7xmT*lbT5kJeY_TteLh%F1#;qAIQiPFl77sW9 zz}Z4fZg&p&Qv4{CX_$`$0!i_qz-<*Vh-DOj$AF`|XZ}8vVmA9Zn{_f@fQ zPPi5f27_(H8MBi>at|^`aQo@>QsN8@_BLfl2K5D{j?p~=c|6^(3l3-kq^-32_dFDe zekF&5o<=F4fh-rA=DUQD1N@yf-ENhdTYcR zp`rEPI)P7$1N~$7XI>m_+TRBx?e=m&xFb`5LaBo`RcT5faGaOQ`k#N?(vC=qnmi2F zc?^M;P_Pl{1B~~uZ_s3fV(uEW|L}bQS7_w!-fPIghKOR34lJroy3@5BVVualk(0<| zSQ%Mwu4k4_pxom1+TN7VCV3ev?l6na>KWPXI?*IS* literal 0 HcmV?d00001 diff --git a/docs/api/css/phpdocumentor-clean-icons/lte-ie7.js b/docs/api/css/phpdocumentor-clean-icons/lte-ie7.js new file mode 100644 index 0000000..881c16e --- /dev/null +++ b/docs/api/css/phpdocumentor-clean-icons/lte-ie7.js @@ -0,0 +1,30 @@ +/* Load this script using conditional IE comments if you need to support IE 7 and IE 6. */ + +window.onload = function() { + function addIcon(el, entity) { + var html = el.innerHTML; + el.innerHTML = '' + entity + '' + html; + } + var icons = { + 'icon-trait' : '', + 'icon-interface' : '', + 'icon-class' : '' + }, + els = document.getElementsByTagName('*'), + i, attr, html, c, el; + for (i = 0; ; i += 1) { + el = els[i]; + if(!el) { + break; + } + attr = el.getAttribute('data-icon'); + if (attr) { + addIcon(el, attr); + } + c = el.className; + c = c.match(/icon-[^\s'"]+/); + if (c && icons[c[0]]) { + addIcon(el, icons[c[0]]); + } + } +}; \ No newline at end of file diff --git a/docs/api/css/phpdocumentor-clean-icons/style.css b/docs/api/css/phpdocumentor-clean-icons/style.css new file mode 100644 index 0000000..f069ec1 --- /dev/null +++ b/docs/api/css/phpdocumentor-clean-icons/style.css @@ -0,0 +1,48 @@ +@font-face { + font-family: 'phpdocumentor-clean-icons'; + src:url('fonts/phpdocumentor-clean-icons.eot'); + src:url('fonts/phpdocumentor-clean-icons.eot?#iefix') format('embedded-opentype'), + url('fonts/phpdocumentor-clean-icons.woff') format('woff'), + url('fonts/phpdocumentor-clean-icons.ttf') format('truetype'), + url('fonts/phpdocumentor-clean-icons.svg#phpdocumentor-clean-icons') format('svg'); + font-weight: normal; + font-style: normal; +} + +/* Use the following CSS code if you want to use data attributes for inserting your icons */ +[data-icon]:before { + font-family: 'phpdocumentor-clean-icons'; + content: attr(data-icon); + speak: none; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; +} + +/* Use the following CSS code if you want to have a class per icon */ +/* +Instead of a list of all class selectors, +you can use the generic selector below, but it's slower: +[class*="icon-"] { +*/ +.icon-trait, .icon-interface, .icon-class { + font-family: 'phpdocumentor-clean-icons'; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; +} +.icon-trait:before { + content: "\e000"; +} +.icon-interface:before { + content: "\e001"; +} +.icon-class:before { + content: "\e002"; +} diff --git a/docs/api/css/prism.css b/docs/api/css/prism.css new file mode 100644 index 0000000..17876af --- /dev/null +++ b/docs/api/css/prism.css @@ -0,0 +1,204 @@ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ + +code[class*="language-"], +pre[class*="language-"] { + color: black; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', monospace; + direction: ltr; + text-align: left; + white-space: pre; + word-spacing: normal; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +::-moz-selection { + text-shadow: none; + background: #b3d4fc; +} + +::selection { + text-shadow: none; + background: #b3d4fc; +} + +@media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; +} + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .1em; + border-radius: .3em; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} + +.token.punctuation { + color: #999; +} + +.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number { + color: #905; +} + +.token.selector, +.token.attr-name, +.token.string { + color: #690; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #a67f59; + background: hsla(0,0%,100%,.5); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} + + +.token.regex, +.token.important { + color: #e90; +} + +.token.important { + font-weight: bold; +} + +.token.entity { + cursor: help; +} +pre[data-line] { + position: relative; + padding: 1em 0 1em 3em; +} + +.line-highlight { + position: absolute; + left: 0; + right: 0; + padding: inherit 0; + margin-top: 1em; /* Same as .prism’s padding-top */ + + background: hsla(24, 20%, 50%,.08); + background: -moz-linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0)); + background: -webkit-linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0)); + background: -o-linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0)); + background: linear-gradient(left, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0)); + + pointer-events: none; + + line-height: inherit; + white-space: pre; +} + + .line-highlight:before, + .line-highlight[data-end]:after { + content: attr(data-start); + position: absolute; + top: .4em; + left: .6em; + min-width: 1em; + padding: 0 .5em; + background-color: hsla(24, 20%, 50%,.4); + color: hsl(24, 20%, 95%); + font: bold 65%/1.5 sans-serif; + text-align: center; + vertical-align: .3em; + border-radius: 999px; + text-shadow: none; + box-shadow: 0 1px white; + } + + .line-highlight[data-end]:after { + content: attr(data-end); + top: auto; + bottom: .4em; + } +pre.line-numbers { + position: relative; + padding-left: 3.8em; + counter-reset: linenumber; +} + +pre.line-numbers > code { + position: relative; +} + +.line-numbers .line-numbers-rows { + position: absolute; + pointer-events: none; + top: 0; + font-size: 100%; + left: -3.8em; + width: 3em; /* works for line-numbers below 1000 lines */ + letter-spacing: -1px; + border-right: 1px solid #999; + + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + +} + + .line-numbers-rows > span { + pointer-events: none; + display: block; + counter-increment: linenumber; + } + + .line-numbers-rows > span:before { + content: counter(linenumber); + color: #999; + display: block; + padding-right: 0.8em; + text-align: right; + } diff --git a/docs/api/css/template.css b/docs/api/css/template.css new file mode 100644 index 0000000..9edf5ee --- /dev/null +++ b/docs/api/css/template.css @@ -0,0 +1,429 @@ +@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro); +@import url('phpdocumentor-clean-icons/style.css'); + +body { + padding-top: 40px; + background-color: #333333; +} + +a { + color: #6495ed; +} +a.anchor { + height: 40px; + margin-top: -40px; + display: block; +} + +h1, h2, h3, h4, h5, h6, .brand { + font-family: 'Source Sans Pro', sans-serif; + font-weight: normal; + letter-spacing: 0.05em; +} + +h2, h3, .detailsbar h1 { + overflow: hidden; + white-space: nowrap; + margin: 30px 0 20px 0; +} + +h2:after, h3:after, .detailsbar h1:after { + content: ''; + display: inline-block; + vertical-align: middle; + width: 100%; + height: 2px; + margin-left: 1em; + background: silver; +} + +h3 { + margin: 10px 0 20px 0; +} + +h4 { + margin: 20px 0 10px 0; + color: gray; + font-size: 18.5px; +} + +h3.public, h3.protected, h3.private { + padding-left: 10px; + text-overflow: ellipsis; +} + +.table tr:first-of-type th, .table tr:first-of-type td { + border-top: none; +} +.detailsbar { + color: #eeeeee; + background-color: #333333; + font-size: 0.9em; + overflow: hidden; + border-left: 2px solid gray; +} + +.detailsbar h1 { + font-size: 1.5em; + margin-bottom: 20px; + margin-top: 0; +} + +.detailsbar h2 { + font-size: 1.2em; + margin: 0; + padding: 0; +} + +.detailsbar h1:after { + background: gray; +} +.detailsbar h2:after, .detailsbar h3:after { + background: transparent; +} + +.detailsbar dt { + font-variant: small-caps; + text-transform: lowercase; + font-size: 1.1em; + letter-spacing: 0.1em; + color: silver; +} + +.hierarchy div:nth-of-type(2) { margin-left: 11px; } +.hierarchy div:nth-of-type(3) { margin-left: 22px; } +.hierarchy div:nth-of-type(4) { margin-left: 33px; } +.hierarchy div:nth-of-type(5) { margin-left: 44px; } +.hierarchy div:nth-of-type(6) { margin-left: 55px; } +.hierarchy div:nth-of-type(7) { margin-left: 66px; } +.hierarchy div:nth-of-type(8) { margin-left: 77px; } +.hierarchy div:nth-of-type(9) { margin-left: 88px; } +.hierarchy div:before { + content: "\f0da"; + font-family: FontAwesome; + margin-right: 5px; +} + +.row-fluid { + background-color: white; + overflow: hidden; +} + +footer.row-fluid, footer.row-fluid * { + background-color: #333333; + color: white; +} + +footer.row-fluid { + border-top: 2px dashed #555; + margin-top: 2px; +} + +.footer-sections .span4 { + border: 2px solid #555; + text-align: center; + border-radius: 10px; + margin-top: 70px; + margin-bottom: 20px; + background: #373737; +} + +.footer-sections .span4 h1 { + background: transparent; + margin-top: -30px; + margin-bottom: 20px; + font-size: 5em; +} + +.footer-sections .span4 h1 * { + background: transparent; +} + +.footer-sections .span4 div { + border-bottom-right-radius: 6px; + border-bottom-left-radius: 6px; + padding: 10px; + min-height: 40px; +} +.footer-sections .span4 div, .footer-sections .span4 div * { + background-color: #555; +} +.footer-sections .span4 ul { + text-align: left; + list-style: none; + margin: 0; + padding: 0; +} + +.content { + background-color: white; + padding-right: 20px; +} + +.content nav { + text-align: center; + border-bottom: 1px solid silver; + margin: 5px 0 20px 0; + padding-bottom: 5px; +} + +.content > h1 { + padding-bottom: 15px; +} + +.content > h1 small { + display: block; + padding-bottom: 8px; + font-size: 0.6em; +} + +.deprecated { + text-decoration: line-through; +} + +.method { + margin-bottom: 20px; +} + +.method .signature .argument { + color: maroon; + font-weight: bold; +} + +.class #summary section.row-fluid { + overflow: hidden +} + +.class #summary .heading { + font-weight: bold; + text-align: center; +} + +.class #summary section .span4 { + padding: 3px; + overflow: hidden; + margin-bottom: -9999px; + padding-bottom: 9999px; + white-space: nowrap; + text-overflow: ellipsis; + border-left: 5px solid transparent; +} + +.class #summary section.public .span4:first-of-type:before, +.class #summary section.public .span6:first-of-type:before, +h3.public:before { + font-family: FontAwesome; + content: "\f046"; + color: green; + display: inline-block; + width: 1.2em; +} + +.class #summary section .span4:first-of-type, +.class #summary section .span6:first-of-type { + padding-left: 21px; +} +.class #summary section .span4:first-of-type:before, +.class #summary section .span6:first-of-type:before { + margin-left: -21px; +} +.class #summary section.protected .span4:first-of-type:before, +.class #summary section.protected .span6:first-of-type:before, +h3.protected:before { + font-family: FontAwesome; + content: "\f132"; + color: orange; + display: inline-block; + width: 1.2em; +} + +.class #summary section.private .span4:first-of-type:before, +.class #summary section.private .span6:first-of-type:before, +h3.private:before { + font-family: FontAwesome; + content: "\f023"; + color: red; + display: inline-block; + width: 1.2em; +} + +.class #summary section em { + font-size: 0.9em; + color: silver; +} +.class #summary .inherited { + color: gray; + font-style: italic; +} + +.accordion-group { + border: none; +} + +.accordion { + margin-bottom: 0; +} + +.accordion a:hover { + text-decoration: none; + background: #333333; + color: #eeeeee; +} + +.accordion-heading .accordion-toggle:before { + content: "\f078"; + font-family: FontAwesome; + margin-right: 5px; +} + +.accordion-heading .accordion-toggle.collapsed:before { + content: "\f054"; +} +.accordion-heading .accordion-toggle { + float: left; + width: 16px; + height: 16px; + padding: 4px 2px 4px 12px; +} +.accordion-heading a { + display: block; + padding: 4px 12px; +} + +.accordion-inner a { + display: block; + padding: 4px 12px; +} + +.accordion-inner > ul a:before { + font-family: 'phpdocumentor-clean-icons'; + content: "\e001"; + margin-right: 5px; +} + +.accordion-inner li.class a:before { + content: "\e002"; +} + +.accordion-inner li.interface a:before { + content: "\e001"; +} + +.accordion-inner li.trait a:before { + content: "\e000"; +} + +.accordion-inner { + padding: 4px 0 4px 12px; +} +.accordion-inner ul { + list-style: none; + padding: 0; + margin: 0; +} + +.row-fluid .span2 { + width: 16.5%; +} + +body .modal { + width: 90%; /* desired relative width */ + left: 5%; /* (100%-width)/2 */ + /* place center */ + margin-left:auto; + margin-right:auto; +} + +.side-nav.nav-list li a { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +@media (min-width: 767px) { + .sidebar { + position: fixed; + top: 40px; + bottom: 0; + background-color: #f3f3f3; + left: 0; + border-right: 1px solid #e9e9e9; + overflow-y: scroll; + overflow-x: hidden; + padding-top: 10px; + } + + .sidebar::-webkit-scrollbar { + width: 10px; + } + + .sidebar::-webkit-scrollbar-thumb { + background: #cccccc; + background-clip: padding-box; + border: 3px solid #f3f3f3; + border-radius: 5px; + } + + .sidebar::-webkit-scrollbar-button { + display: none; + } + + .sidebar::-webkit-scrollbar-track { + background: #f3f3f3; + } +} + +@media (max-width: 979px) { + body { + padding-top: 0; + } +} + +@media (max-width: 767px) { + .class #summary .heading { + display: none; + } + + .detailsbar h1 { + display: none; + } + + body { + background-color: white; + } + + footer.row-fluid, footer.row-fluid * { + background-color: white; + } + + .footer-sections .span4 h1 { + color: #ccccd9; + margin-top: 0; + } + + .detailsbar { + background-color: white; + color: #333; + border: none; + } + + .row-fluid .span2 { + width: 100%; + } +} + +@media (min-width: 767px) { + .detailsbar { + min-height: 100%; + margin-bottom: -99999px; + padding-bottom: 99999px; + padding-left: 20px; + padding-top: 10px; + } +} + +@media (min-width: 1200px) { + .row-fluid .span2 { + width: 16.5%; + } +} diff --git a/docs/api/files/CHttpGet.html b/docs/api/files/CHttpGet.html new file mode 100644 index 0000000..83c9bfd --- /dev/null +++ b/docs/api/files/CHttpGet.html @@ -0,0 +1,256 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

CHttpGet.php

+

+ + + + +

Classes

+ + + + + +
CHttpGetGet a image from a remote server using HTTP GET and If-Modified-Since.
+
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/CHttpGet.php.txt b/docs/api/files/CHttpGet.php.txt new file mode 100644 index 0000000..aedb57b --- /dev/null +++ b/docs/api/files/CHttpGet.php.txt @@ -0,0 +1,241 @@ +request['header'] = array(); + } + + + + /** + * Set the url for the request. + * + * @param string $url + * + * @return $this + */ + public function setUrl($url) + { + $this->request['url'] = $url; + return $this; + } + + + + /** + * Set custom header field for the request. + * + * @param string $field + * @param string $value + * + * @return $this + */ + public function setHeader($field, $value) + { + $this->request['header'][] = "$field: $value"; + return $this; + } + + + + /** + * Set header fields for the request. + * + * @param string $field + * @param string $value + * + * @return $this + */ + public function parseHeader() + { + $header = explode("\r\n", rtrim($this->response['headerRaw'], "\r\n")); + $output = array(); + + if ('HTTP' === substr($header[0], 0, 4)) { + list($output['version'], $output['status']) = explode(' ', $header[0]); + unset($header[0]); + } + + foreach ($header as $entry) { + $pos = strpos($entry, ':'); + $output[trim(substr($entry, 0, $pos))] = trim(substr($entry, $pos + 1)); + } + + $this->response['header'] = $output; + return $this; + } + + + + /** + * Perform the request. + * + * @param boolean $debug set to true to dump headers. + * + * @return boolean + */ + public function doGet($debug = false) + { + $options = array( + CURLOPT_URL => $this->request['url'], + CURLOPT_HEADER => 1, + CURLOPT_HTTPHEADER => $this->request['header'], + CURLOPT_AUTOREFERER => true, + CURLOPT_RETURNTRANSFER => true, + CURLINFO_HEADER_OUT => $debug, + CURLOPT_CONNECTTIMEOUT => 5, + CURLOPT_TIMEOUT => 5, + ); + + $ch = curl_init(); + curl_setopt_array($ch, $options); + $response = curl_exec($ch); + + if (!$response) { + return false; + } + + $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $this->response['headerRaw'] = substr($response, 0, $headerSize); + $this->response['body'] = substr($response, $headerSize); + + $this->parseHeader(); + + if ($debug) { + $info = curl_getinfo($ch); + echo "Request header
", var_dump($info['request_header']), "
"; + echo "Response header (raw)
", var_dump($this->response['headerRaw']), "
"; + echo "Response header (parsed)
", var_dump($this->response['header']), "
"; + } + + curl_close($ch); + return true; + } + + + + /** + * Get HTTP code of response. + * + * @return integer as HTTP status code or null if not available. + */ + public function getStatus() + { + return isset($this->response['header']['status']) + ? (int) $this->response['header']['status'] + : null; + } + + + + /** + * Get file modification time of response. + * + * @return int as timestamp. + */ + public function getLastModified() + { + return isset($this->response['header']['Last-Modified']) + ? strtotime($this->response['header']['Last-Modified']) + : null; + } + + + + /** + * Get content type. + * + * @return string as the content type or null if not existing or invalid. + */ + public function getContentType() + { + $type = isset($this->response['header']['Content-Type']) + ? $this->response['header']['Content-Type'] + : null; + + return preg_match('#[a-z]+/[a-z]+#', $type) + ? $type + : null; + } + + + + /** + * Get file modification time of response. + * + * @param mixed $default as default value (int seconds) if date is + * missing in response header. + * + * @return int as timestamp or $default if Date is missing in + * response header. + */ + public function getDate($default = false) + { + return isset($this->response['header']['Date']) + ? strtotime($this->response['header']['Date']) + : $default; + } + + + + /** + * Get max age of cachable item. + * + * @param mixed $default as default value if date is missing in response + * header. + * + * @return int as timestamp or false if not available. + */ + public function getMaxAge($default = false) + { + $cacheControl = isset($this->response['header']['Cache-Control']) + ? $this->response['header']['Cache-Control'] + : null; + + $maxAge = null; + if ($cacheControl) { + // max-age=2592000 + $part = explode('=', $cacheControl); + $maxAge = ($part[0] == "max-age") + ? (int) $part[1] + : null; + } + + if ($maxAge) { + return $maxAge; + } + + $expire = isset($this->response['header']['Expires']) + ? strtotime($this->response['header']['Expires']) + : null; + + return $expire ? $expire : $default; + } + + + + /** + * Get body of response. + * + * @return string as body. + */ + public function getBody() + { + return $this->response['body']; + } +} + diff --git a/docs/api/files/CImage.html b/docs/api/files/CImage.html new file mode 100644 index 0000000..5fb1379 --- /dev/null +++ b/docs/api/files/CImage.html @@ -0,0 +1,256 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

CImage.php

+

+ + + + +

Classes

+ + + + + +
CImageResize and crop images on the fly, store generated images in a cache.
+
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/CImage.php.txt b/docs/api/files/CImage.php.txt new file mode 100644 index 0000000..7513b0d --- /dev/null +++ b/docs/api/files/CImage.php.txt @@ -0,0 +1,2411 @@ + 0, + 'green' => 0, + 'blue' => 0, + 'alpha' => null, + );*/ + + + + /** + * Default background color to use. + * + * @todo remake when upgrading to PHP 5.5 + */ + //private $bgColorDefault = self::BACKGROUND_COLOR; + private $bgColorDefault = array( + 'red' => 0, + 'green' => 0, + 'blue' => 0, + 'alpha' => null, + ); + + + /** + * Background color to use, specified as part of options. + */ + private $bgColor; + + + + /** + * Where to save the target file. + */ + private $saveFolder; + + + + /** + * The working image object. + */ + private $image; + + + + /** + * The root folder of images (only used in constructor to create $pathToImage?). + */ + private $imageFolder; + + + + /** + * Image filename, may include subdirectory, relative from $imageFolder + */ + private $imageSrc; + + + + /** + * Actual path to the image, $imageFolder . '/' . $imageSrc + */ + private $pathToImage; + + + + /** + * Original file extension + */ + private $fileExtension; + + + + /** + * File extension to use when saving image. + */ + private $extension; + + + + /** + * Output format, supports null (image) or json. + */ + private $outputFormat = null; + + + + /** + * Verbose mode to print out a trace and display the created image + */ + private $verbose = false; + + + + /** + * Keep a log/trace on what happens + */ + private $log = array(); + + + + /** + * Handle image as palette image + */ + private $palette; + + + + /** + * Target filename, with path, to save resulting image in. + */ + private $cacheFileName; + + + + /** + * Set a format to save image as, or null to use original format. + */ + private $saveAs; + + + /** + * Path to command for filter optimize, for example optipng or null. + */ + private $pngFilter; + + + + /** + * Path to command for deflate optimize, for example pngout or null. + */ + private $pngDeflate; + + + + /** + * Path to command to optimize jpeg images, for example jpegtran or null. + */ + private $jpegOptimize; + + + /** + * Image dimensions, calculated from loaded image. + */ + private $width; // Calculated from source image + private $height; // Calculated from source image + + + /** + * New image dimensions, incoming as argument or calculated. + */ + private $newWidth; + private $newWidthOrig; // Save original value + private $newHeight; + private $newHeightOrig; // Save original value + + + /** + * Change target height & width when different dpr, dpr 2 means double image dimensions. + */ + private $dpr = 1; + + + /** + * Always upscale images, even if they are smaller than target image. + */ + const UPSCALE_DEFAULT = true; + private $upscale = self::UPSCALE_DEFAULT; + + + + /** + * Array with details on how to crop, incoming as argument and calculated. + */ + public $crop; + public $cropOrig; // Save original value + + + /** + * String with details on how to do image convolution. String + * should map a key in the $convolvs array or be a string of + * 11 float values separated by comma. The first nine builds + * up the matrix, then divisor and last offset. + */ + private $convolve; + + + /** + * Custom convolution expressions, matrix 3x3, divisor and offset. + */ + private $convolves = array( + 'lighten' => '0,0,0, 0,12,0, 0,0,0, 9, 0', + 'darken' => '0,0,0, 0,6,0, 0,0,0, 9, 0', + 'sharpen' => '-1,-1,-1, -1,16,-1, -1,-1,-1, 8, 0', + 'sharpen-alt' => '0,-1,0, -1,5,-1, 0,-1,0, 1, 0', + 'emboss' => '1,1,-1, 1,3,-1, 1,-1,-1, 3, 0', + 'emboss-alt' => '-2,-1,0, -1,1,1, 0,1,2, 1, 0', + 'blur' => '1,1,1, 1,15,1, 1,1,1, 23, 0', + 'gblur' => '1,2,1, 2,4,2, 1,2,1, 16, 0', + 'edge' => '-1,-1,-1, -1,8,-1, -1,-1,-1, 9, 0', + 'edge-alt' => '0,1,0, 1,-4,1, 0,1,0, 1, 0', + 'draw' => '0,-1,0, -1,5,-1, 0,-1,0, 0, 0', + 'mean' => '1,1,1, 1,1,1, 1,1,1, 9, 0', + 'motion' => '1,0,0, 0,1,0, 0,0,1, 3, 0', + ); + + + /** + * Resize strategy to fill extra area with background color. + * True or false. + */ + private $fillToFit; + + + /** + * Used with option area to set which parts of the image to use. + */ + private $offset; + + + + /** + * Calculate target dimension for image when using fill-to-fit resize strategy. + */ + private $fillWidth; + private $fillHeight; + + + + /** + * Allow remote file download, default is to disallow remote file download. + */ + private $allowRemote = false; + + + + /** + * Pattern to recognize a remote file. + */ + //private $remotePattern = '#^[http|https]://#'; + private $remotePattern = '#^https?://#'; + + + + /** + * Use the cache if true, set to false to ignore the cached file. + */ + private $useCache = true; + + + /** + * Properties, the class is mutable and the method setOptions() + * decides (partly) what properties are created. + * + * @todo Clean up these and check if and how they are used + */ + + public $keepRatio; + public $cropToFit; + private $cropWidth; + private $cropHeight; + public $crop_x; + public $crop_y; + public $filters; + private $type; // Calculated from source image + private $attr; // Calculated from source image + private $useOriginal; // Use original image if possible + + + + + /** + * Constructor, can take arguments to init the object. + * + * @param string $imageSrc filename which may contain subdirectory. + * @param string $imageFolder path to root folder for images. + * @param string $saveFolder path to folder where to save the new file or null to skip saving. + * @param string $saveName name of target file when saveing. + */ + public function __construct($imageSrc = null, $imageFolder = null, $saveFolder = null, $saveName = null) + { + $this->setSource($imageSrc, $imageFolder); + $this->setTarget($saveFolder, $saveName); + } + + + + /** + * Set verbose mode. + * + * @param boolean $mode true or false to enable and disable verbose mode, + * default is true. + * + * @return $this + */ + public function setVerbose($mode = true) + { + $this->verbose = $mode; + return $this; + } + + + + /** + * Set save folder, base folder for saving cache files. + * + * @todo clean up how $this->saveFolder is used in other methods. + * + * @param string $path where to store cached files. + * + * @return $this + */ + public function setSaveFolder($path) + { + $this->saveFolder = $path; + return $this; + } + + + + /** + * Use cache or not. + * + * @todo clean up how $this->noCache is used in other methods. + * + * @param string $use true or false to use cache. + * + * @return $this + */ + public function useCache($use = true) + { + $this->useCache = $use; + return $this; + } + + + + /** + * Allow or disallow remote image download. + * + * @param boolean $allow true or false to enable and disable. + * @param string $pattern to use to detect if its a remote file. + * + * @return $this + */ + public function setRemoteDownload($allow, $pattern = null) + { + $this->allowRemote = $allow; + $this->remotePattern = is_null($pattern) ? $this->remotePattern : $pattern; + + $this->log("Set remote download to: " + . ($this->allowRemote ? "true" : "false") + . " using pattern " + . $this->remotePattern); + + return $this; + } + + + + /** + * Check if the image resource is a remote file or not. + * + * @param string $src check if src is remote. + * + * @return boolean true if $src is a remote file, else false. + */ + public function isRemoteSource($src) + { + $remote = preg_match($this->remotePattern, $src); + $this->log("Detected remote image: " . ($remote ? "true" : "false")); + return !!$remote; + } + + + + /** + * Set whitelist for valid hostnames from where remote source can be + * downloaded. + * + * @param array $whitelist with regexp hostnames to allow download from. + * + * @return $this + */ + public function setRemoteHostWhitelist($whitelist = null) + { + $this->remoteHostWhitelist = $whitelist; + $this->log("Setting remote host whitelist to: " . print_r($this->remoteHostWhitelist, 1)); + return $this; + } + + + + /** + * Check if the hostname for the remote image, is on a whitelist, + * if the whitelist is defined. + * + * @param string $src the remote source. + * + * @return boolean true if hostname on $src is in the whitelist, else false. + */ + public function isRemoteSourceOnWhitelist($src) + { + if (is_null($this->remoteHostWhitelist)) { + $allow = true; + } else { + $whitelist = new CWhitelist(); + $hostname = parse_url($src, PHP_URL_HOST); + $allow = $whitelist->check($hostname, $this->remoteHostWhitelist); + } + + $this->log("Remote host is on whitelist: " . ($allow ? "true" : "false")); + return $allow; + } + + + + /** + * Check if file extension is valid as a file extension. + * + * @param string $extension of image file. + * + * @return $this + */ + private function checkFileExtension($extension) + { + $valid = array('jpg', 'jpeg', 'png', 'gif'); + + in_array(strtolower($extension), $valid) + or $this->raiseError('Not a valid file extension.'); + + return $this; + } + + + + /** + * Download a remote image and return path to its local copy. + * + * @param string $src remote path to image. + * + * @return string as path to downloaded remote source. + */ + public function downloadRemoteSource($src) + { + $remote = new CRemoteImage(); + $cache = $this->saveFolder . "/remote/"; + + if (!is_dir($cache)) { + if (!is_writable($this->saveFolder)) { + throw new Exception("Can not create remote cache, cachefolder not writable."); + } + mkdir($cache); + $this->log("The remote cache does not exists, creating it."); + } + + if (!is_writable($cache)) { + $this->log("The remote cache is not writable."); + } + + $remote->setCache($cache); + $remote->useCache($this->useCache); + $src = $remote->download($src); + + $this->log("Remote HTTP status: " . $remote->getStatus()); + $this->log("Remote item has local cached file: $src"); + $this->log("Remote details on cache:" . print_r($remote->getDetails(), true)); + + return $src; + } + + + + /** + * Set src file. + * + * @param string $src of image. + * @param string $dir as base directory where images are. + * + * @return $this + */ + public function setSource($src, $dir = null) + { + if (!isset($src)) { + return $this; + } + + if ($this->allowRemote && $this->isRemoteSource($src)) { + $src = $this->downloadRemoteSource($src); + $dir = null; + } + + if (!isset($dir)) { + $dir = dirname($src); + $src = basename($src); + } + + $this->imageSrc = ltrim($src, '/'); + $this->imageFolder = rtrim($dir, '/'); + $this->pathToImage = $this->imageFolder . '/' . $this->imageSrc; + $this->fileExtension = strtolower(pathinfo($this->pathToImage, PATHINFO_EXTENSION)); + //$this->extension = $this->fileExtension; + + $this->checkFileExtension($this->fileExtension); + + return $this; + } + + + + /** + * Set target file. + * + * @param string $src of target image. + * @param string $dir as base directory where images are stored. + * + * @return $this + */ + public function setTarget($src = null, $dir = null) + { + if (!(isset($src) && isset($dir))) { + return $this; + } + + $this->saveFolder = $dir; + $this->cacheFileName = $dir . '/' . $src; + + /* Allow readonly cache + is_writable($this->saveFolder) + or $this->raiseError('Target directory is not writable.'); + */ + + // Sanitize filename + $this->cacheFileName = preg_replace('/^a-zA-Z0-9\.-_/', '', $this->cacheFileName); + $this->log("The cache file name is: " . $this->cacheFileName); + + return $this; + } + + + + /** + * Set options to use when processing image. + * + * @param array $args used when processing image. + * + * @return $this + */ + public function setOptions($args) + { + $this->log("Set new options for processing image."); + + $defaults = array( + // Options for calculate dimensions + 'newWidth' => null, + 'newHeight' => null, + 'aspectRatio' => null, + 'keepRatio' => true, + 'cropToFit' => false, + 'fillToFit' => null, + 'crop' => null, //array('width'=>null, 'height'=>null, 'start_x'=>0, 'start_y'=>0), + 'area' => null, //'0,0,0,0', + 'upscale' => self::UPSCALE_DEFAULT, + + // Options for caching or using original + 'useCache' => true, + 'useOriginal' => true, + + // Pre-processing, before resizing is done + 'scale' => null, + 'rotateBefore' => null, + 'autoRotate' => false, + + // General options + 'bgColor' => null, + + // Post-processing, after resizing is done + 'palette' => null, + 'filters' => null, + 'sharpen' => null, + 'emboss' => null, + 'blur' => null, + 'convolve' => null, + 'rotateAfter' => null, + + // Output format + 'outputFormat' => null, + 'dpr' => 1, + + // Options for saving + //'quality' => null, + //'compress' => null, + //'saveAs' => null, + ); + + // Convert crop settings from string to array + if (isset($args['crop']) && !is_array($args['crop'])) { + $pices = explode(',', $args['crop']); + $args['crop'] = array( + 'width' => $pices[0], + 'height' => $pices[1], + 'start_x' => $pices[2], + 'start_y' => $pices[3], + ); + } + + // Convert area settings from string to array + if (isset($args['area']) && !is_array($args['area'])) { + $pices = explode(',', $args['area']); + $args['area'] = array( + 'top' => $pices[0], + 'right' => $pices[1], + 'bottom' => $pices[2], + 'left' => $pices[3], + ); + } + + // Convert filter settings from array of string to array of array + if (isset($args['filters']) && is_array($args['filters'])) { + foreach ($args['filters'] as $key => $filterStr) { + $parts = explode(',', $filterStr); + $filter = $this->mapFilter($parts[0]); + $filter['str'] = $filterStr; + for ($i=1; $i<=$filter['argc']; $i++) { + if (isset($parts[$i])) { + $filter["arg{$i}"] = $parts[$i]; + } else { + throw new Exception( + 'Missing arg to filter, review how many arguments are needed at + http://php.net/manual/en/function.imagefilter.php' + ); + } + } + $args['filters'][$key] = $filter; + } + } + + // Merge default arguments with incoming and set properties. + //$args = array_merge_recursive($defaults, $args); + $args = array_merge($defaults, $args); + foreach ($defaults as $key => $val) { + $this->{$key} = $args[$key]; + } + + if ($this->bgColor) { + $this->setDefaultBackgroundColor($this->bgColor); + } + + // Save original values to enable re-calculating + $this->newWidthOrig = $this->newWidth; + $this->newHeightOrig = $this->newHeight; + $this->cropOrig = $this->crop; + + return $this; + } + + + + /** + * Map filter name to PHP filter and id. + * + * @param string $name the name of the filter. + * + * @return array with filter settings + * @throws Exception + */ + private function mapFilter($name) + { + $map = array( + 'negate' => array('id'=>0, 'argc'=>0, 'type'=>IMG_FILTER_NEGATE), + 'grayscale' => array('id'=>1, 'argc'=>0, 'type'=>IMG_FILTER_GRAYSCALE), + 'brightness' => array('id'=>2, 'argc'=>1, 'type'=>IMG_FILTER_BRIGHTNESS), + 'contrast' => array('id'=>3, 'argc'=>1, 'type'=>IMG_FILTER_CONTRAST), + 'colorize' => array('id'=>4, 'argc'=>4, 'type'=>IMG_FILTER_COLORIZE), + 'edgedetect' => array('id'=>5, 'argc'=>0, 'type'=>IMG_FILTER_EDGEDETECT), + 'emboss' => array('id'=>6, 'argc'=>0, 'type'=>IMG_FILTER_EMBOSS), + 'gaussian_blur' => array('id'=>7, 'argc'=>0, 'type'=>IMG_FILTER_GAUSSIAN_BLUR), + 'selective_blur' => array('id'=>8, 'argc'=>0, 'type'=>IMG_FILTER_SELECTIVE_BLUR), + 'mean_removal' => array('id'=>9, 'argc'=>0, 'type'=>IMG_FILTER_MEAN_REMOVAL), + 'smooth' => array('id'=>10, 'argc'=>1, 'type'=>IMG_FILTER_SMOOTH), + 'pixelate' => array('id'=>11, 'argc'=>2, 'type'=>IMG_FILTER_PIXELATE), + ); + + if (isset($map[$name])) { + return $map[$name]; + } else { + throw new Exception('No such filter.'); + } + } + + + + /** + * Load image details from original image file. + * + * @param string $file the file to load or null to use $this->pathToImage. + * + * @return $this + * @throws Exception + */ + public function loadImageDetails($file = null) + { + $file = $file ? $file : $this->pathToImage; + + is_readable($file) + or $this->raiseError('Image file does not exist.'); + + // Get details on image + $info = list($this->width, $this->height, $this->type, $this->attr) = getimagesize($file); + !empty($info) or $this->raiseError("The file doesn't seem to be an image."); + + if ($this->verbose) { + $this->log("Image file: {$file}"); + $this->log("Image width x height (type): {$this->width} x {$this->height} ({$this->type})."); + $this->log("Image filesize: " . filesize($file) . " bytes."); + } + + return $this; + } + + + + /** + * Init new width and height and do some sanity checks on constraints, before any + * processing can be done. + * + * @return $this + * @throws Exception + */ + public function initDimensions() + { + $this->log("Init dimension (before) newWidth x newHeight is {$this->newWidth} x {$this->newHeight}."); + + // width as % + if ($this->newWidth[strlen($this->newWidth)-1] == '%') { + $this->newWidth = $this->width * substr($this->newWidth, 0, -1) / 100; + $this->log("Setting new width based on % to {$this->newWidth}"); + } + + // height as % + if ($this->newHeight[strlen($this->newHeight)-1] == '%') { + $this->newHeight = $this->height * substr($this->newHeight, 0, -1) / 100; + $this->log("Setting new height based on % to {$this->newHeight}"); + } + + is_null($this->aspectRatio) or is_numeric($this->aspectRatio) or $this->raiseError('Aspect ratio out of range'); + + // width & height from aspect ratio + if ($this->aspectRatio && is_null($this->newWidth) && is_null($this->newHeight)) { + if ($this->aspectRatio >= 1) { + $this->newWidth = $this->width; + $this->newHeight = $this->width / $this->aspectRatio; + $this->log("Setting new width & height based on width & aspect ratio (>=1) to (w x h) {$this->newWidth} x {$this->newHeight}"); + + } else { + $this->newHeight = $this->height; + $this->newWidth = $this->height * $this->aspectRatio; + $this->log("Setting new width & height based on width & aspect ratio (<1) to (w x h) {$this->newWidth} x {$this->newHeight}"); + } + + } elseif ($this->aspectRatio && is_null($this->newWidth)) { + $this->newWidth = $this->newHeight * $this->aspectRatio; + $this->log("Setting new width based on aspect ratio to {$this->newWidth}"); + + } elseif ($this->aspectRatio && is_null($this->newHeight)) { + $this->newHeight = $this->newWidth / $this->aspectRatio; + $this->log("Setting new height based on aspect ratio to {$this->newHeight}"); + } + + // Change width & height based on dpr + if ($this->dpr != 1) { + if (!is_null($this->newWidth)) { + $this->newWidth = round($this->newWidth * $this->dpr); + $this->log("Setting new width based on dpr={$this->dpr} - w={$this->newWidth}"); + } + if (!is_null($this->newHeight)) { + $this->newHeight = round($this->newHeight * $this->dpr); + $this->log("Setting new height based on dpr={$this->dpr} - h={$this->newHeight}"); + } + } + + // Check values to be within domain + is_null($this->newWidth) + or is_numeric($this->newWidth) + or $this->raiseError('Width not numeric'); + + is_null($this->newHeight) + or is_numeric($this->newHeight) + or $this->raiseError('Height not numeric'); + + $this->log("Init dimension (after) newWidth x newHeight is {$this->newWidth} x {$this->newHeight}."); + + return $this; + } + + + + /** + * Calculate new width and height of image, based on settings. + * + * @return $this + */ + public function calculateNewWidthAndHeight() + { + // Crop, use cropped width and height as base for calulations + $this->log("Calculate new width and height."); + $this->log("Original width x height is {$this->width} x {$this->height}."); + $this->log("Target dimension (before calculating) newWidth x newHeight is {$this->newWidth} x {$this->newHeight}."); + + // Check if there is an area to crop off + if (isset($this->area)) { + $this->offset['top'] = round($this->area['top'] / 100 * $this->height); + $this->offset['right'] = round($this->area['right'] / 100 * $this->width); + $this->offset['bottom'] = round($this->area['bottom'] / 100 * $this->height); + $this->offset['left'] = round($this->area['left'] / 100 * $this->width); + $this->offset['width'] = $this->width - $this->offset['left'] - $this->offset['right']; + $this->offset['height'] = $this->height - $this->offset['top'] - $this->offset['bottom']; + $this->width = $this->offset['width']; + $this->height = $this->offset['height']; + $this->log("The offset for the area to use is top {$this->area['top']}%, right {$this->area['right']}%, bottom {$this->area['bottom']}%, left {$this->area['left']}%."); + $this->log("The offset for the area to use is top {$this->offset['top']}px, right {$this->offset['right']}px, bottom {$this->offset['bottom']}px, left {$this->offset['left']}px, width {$this->offset['width']}px, height {$this->offset['height']}px."); + } + + $width = $this->width; + $height = $this->height; + + // Check if crop is set + if ($this->crop) { + $width = $this->crop['width'] = $this->crop['width'] <= 0 ? $this->width + $this->crop['width'] : $this->crop['width']; + $height = $this->crop['height'] = $this->crop['height'] <= 0 ? $this->height + $this->crop['height'] : $this->crop['height']; + + if ($this->crop['start_x'] == 'left') { + $this->crop['start_x'] = 0; + } elseif ($this->crop['start_x'] == 'right') { + $this->crop['start_x'] = $this->width - $width; + } elseif ($this->crop['start_x'] == 'center') { + $this->crop['start_x'] = round($this->width / 2) - round($width / 2); + } + + if ($this->crop['start_y'] == 'top') { + $this->crop['start_y'] = 0; + } elseif ($this->crop['start_y'] == 'bottom') { + $this->crop['start_y'] = $this->height - $height; + } elseif ($this->crop['start_y'] == 'center') { + $this->crop['start_y'] = round($this->height / 2) - round($height / 2); + } + + $this->log("Crop area is width {$width}px, height {$height}px, start_x {$this->crop['start_x']}px, start_y {$this->crop['start_y']}px."); + } + + // Calculate new width and height if keeping aspect-ratio. + if ($this->keepRatio) { + + $this->log("Keep aspect ratio."); + + // Crop-to-fit and both new width and height are set. + if (($this->cropToFit || $this->fillToFit) && isset($this->newWidth) && isset($this->newHeight)) { + + // Use newWidth and newHeigh as width/height, image should fit in box. + $this->log("Use newWidth and newHeigh as width/height, image should fit in box."); + + } elseif (isset($this->newWidth) && isset($this->newHeight)) { + + // Both new width and height are set. + // Use newWidth and newHeigh as max width/height, image should not be larger. + $ratioWidth = $width / $this->newWidth; + $ratioHeight = $height / $this->newHeight; + $ratio = ($ratioWidth > $ratioHeight) ? $ratioWidth : $ratioHeight; + $this->newWidth = round($width / $ratio); + $this->newHeight = round($height / $ratio); + $this->log("New width and height was set."); + + } elseif (isset($this->newWidth)) { + + // Use new width as max-width + $factor = (float)$this->newWidth / (float)$width; + $this->newHeight = round($factor * $height); + $this->log("New width was set."); + + } elseif (isset($this->newHeight)) { + + // Use new height as max-hight + $factor = (float)$this->newHeight / (float)$height; + $this->newWidth = round($factor * $width); + $this->log("New height was set."); + + } + + // Get image dimensions for pre-resize image. + if ($this->cropToFit || $this->fillToFit) { + + // Get relations of original & target image + $ratioWidth = $width / $this->newWidth; + $ratioHeight = $height / $this->newHeight; + + if ($this->cropToFit) { + + // Use newWidth and newHeigh as defined width/height, + // image should fit the area. + $this->log("Crop to fit."); + $ratio = ($ratioWidth < $ratioHeight) ? $ratioWidth : $ratioHeight; + $this->cropWidth = round($width / $ratio); + $this->cropHeight = round($height / $ratio); + $this->log("Crop width, height, ratio: $this->cropWidth x $this->cropHeight ($ratio)."); + + } else if ($this->fillToFit) { + + // Use newWidth and newHeigh as defined width/height, + // image should fit the area. + $this->log("Fill to fit."); + $ratio = ($ratioWidth < $ratioHeight) ? $ratioHeight : $ratioWidth; + $this->fillWidth = round($width / $ratio); + $this->fillHeight = round($height / $ratio); + $this->log("Fill width, height, ratio: $this->fillWidth x $this->fillHeight ($ratio)."); + } + } + } + + // Crop, ensure to set new width and height + if ($this->crop) { + $this->log("Crop."); + $this->newWidth = round(isset($this->newWidth) ? $this->newWidth : $this->crop['width']); + $this->newHeight = round(isset($this->newHeight) ? $this->newHeight : $this->crop['height']); + } + + // Fill to fit, ensure to set new width and height + /*if ($this->fillToFit) { + $this->log("FillToFit."); + $this->newWidth = round(isset($this->newWidth) ? $this->newWidth : $this->crop['width']); + $this->newHeight = round(isset($this->newHeight) ? $this->newHeight : $this->crop['height']); + }*/ + + // No new height or width is set, use existing measures. + $this->newWidth = round(isset($this->newWidth) ? $this->newWidth : $this->width); + $this->newHeight = round(isset($this->newHeight) ? $this->newHeight : $this->height); + $this->log("Calculated new width x height as {$this->newWidth} x {$this->newHeight}."); + + return $this; + } + + + + /** + * Re-calculate image dimensions when original image dimension has changed. + * + * @return $this + */ + public function reCalculateDimensions() + { + $this->log("Re-calculate image dimensions, newWidth x newHeigh was: " . $this->newWidth . " x " . $this->newHeight); + + $this->newWidth = $this->newWidthOrig; + $this->newHeight = $this->newHeightOrig; + $this->crop = $this->cropOrig; + + $this->initDimensions() + ->calculateNewWidthAndHeight(); + + return $this; + } + + + + /** + * Set extension for filename to save as. + * + * @param string $saveas extension to save image as + * + * @return $this + */ + public function setSaveAsExtension($saveAs = null) + { + if (isset($saveAs)) { + $saveAs = strtolower($saveAs); + $this->checkFileExtension($saveAs); + $this->saveAs = $saveAs; + $this->extension = $saveAs; + } + + $this->log("Prepare to save image using as: " . $this->extension); + + return $this; + } + + + + /** + * Set JPEG quality to use when saving image + * + * @param int $quality as the quality to set. + * + * @return $this + */ + public function setJpegQuality($quality = null) + { + if ($quality) { + $this->useQuality = true; + } + + $this->quality = isset($quality) + ? $quality + : self::JPEG_QUALITY_DEFAULT; + + (is_numeric($this->quality) and $this->quality > 0 and $this->quality <= 100) + or $this->raiseError('Quality not in range.'); + + $this->log("Setting JPEG quality to {$this->quality}."); + + return $this; + } + + + + /** + * Set PNG compressen algorithm to use when saving image + * + * @param int $compress as the algorithm to use. + * + * @return $this + */ + public function setPngCompression($compress = null) + { + if ($compress) { + $this->useCompress = true; + } + + $this->compress = isset($compress) + ? $compress + : self::PNG_COMPRESSION_DEFAULT; + + (is_numeric($this->compress) and $this->compress >= -1 and $this->compress <= 9) + or $this->raiseError('Quality not in range.'); + + $this->log("Setting PNG compression level to {$this->compress}."); + + return $this; + } + + + + /** + * Use original image if possible, check options which affects image processing. + * + * @param boolean $useOrig default is to use original if possible, else set to false. + * + * @return $this + */ + public function useOriginalIfPossible($useOrig = true) + { + if ($useOrig + && ($this->newWidth == $this->width) + && ($this->newHeight == $this->height) + && !$this->area + && !$this->crop + && !$this->cropToFit + && !$this->fillToFit + && !$this->filters + && !$this->sharpen + && !$this->emboss + && !$this->blur + && !$this->convolve + && !$this->palette + && !$this->useQuality + && !$this->useCompress + && !$this->saveAs + && !$this->rotateBefore + && !$this->rotateAfter + && !$this->autoRotate + && !$this->bgColor + && ($this->upscale === self::UPSCALE_DEFAULT) + ) { + $this->log("Using original image."); + $this->output($this->pathToImage); + } + + return $this; + } + + + + /** + * Generate filename to save file in cache. + * + * @param string $base as basepath for storing file. + * + * @return $this + */ + public function generateFilename($base) + { + $parts = pathinfo($this->pathToImage); + $cropToFit = $this->cropToFit ? '_cf' : null; + $fillToFit = $this->fillToFit ? '_ff' : null; + $crop_x = $this->crop_x ? "_x{$this->crop_x}" : null; + $crop_y = $this->crop_y ? "_y{$this->crop_y}" : null; + $scale = $this->scale ? "_s{$this->scale}" : null; + $bgColor = $this->bgColor ? "_bgc{$this->bgColor}" : null; + $quality = $this->quality ? "_q{$this->quality}" : null; + $compress = $this->compress ? "_co{$this->compress}" : null; + $rotateBefore = $this->rotateBefore ? "_rb{$this->rotateBefore}" : null; + $rotateAfter = $this->rotateAfter ? "_ra{$this->rotateAfter}" : null; + + $width = $this->newWidth; + $height = $this->newHeight; + + $offset = isset($this->offset) + ? '_o' . $this->offset['top'] . '-' . $this->offset['right'] . '-' . $this->offset['bottom'] . '-' . $this->offset['left'] + : null; + + $crop = $this->crop + ? '_c' . $this->crop['width'] . '-' . $this->crop['height'] . '-' . $this->crop['start_x'] . '-' . $this->crop['start_y'] + : null; + + $filters = null; + if (isset($this->filters)) { + foreach ($this->filters as $filter) { + if (is_array($filter)) { + $filters .= "_f{$filter['id']}"; + for ($i=1; $i<=$filter['argc']; $i++) { + $filters .= ":".$filter["arg{$i}"]; + } + } + } + } + + $sharpen = $this->sharpen ? 's' : null; + $emboss = $this->emboss ? 'e' : null; + $blur = $this->blur ? 'b' : null; + $palette = $this->palette ? 'p' : null; + + $autoRotate = $this->autoRotate ? 'ar' : null; + + $this->extension = isset($this->extension) + ? $this->extension + : $parts['extension']; + + $optimize = null; + if ($this->extension == 'jpeg' || $this->extension == 'jpg') { + $optimize = $this->jpegOptimize ? 'o' : null; + } elseif ($this->extension == 'png') { + $optimize .= $this->pngFilter ? 'f' : null; + $optimize .= $this->pngDeflate ? 'd' : null; + } + + $convolve = null; + if ($this->convolve) { + $convolve = '_conv' . preg_replace('/[^a-zA-Z0-9]/', '', $this->convolve); + } + + $upscale = null; + if ($this->upscale !== self::UPSCALE_DEFAULT) { + $upscale = '_nu'; + } + + $subdir = str_replace('/', '-', dirname($this->imageSrc)); + $subdir = ($subdir == '.') ? '_.' : $subdir; + $file = $subdir . '_' . $parts['filename'] . '_' . $width . '_' + . $height . $offset . $crop . $cropToFit . $fillToFit + . $crop_x . $crop_y . $upscale + . $quality . $filters . $sharpen . $emboss . $blur . $palette . $optimize + . $scale . $rotateBefore . $rotateAfter . $autoRotate . $bgColor . $convolve + . '.' . $this->extension; + + return $this->setTarget($file, $base); + } + + + + /** + * Use cached version of image, if possible. + * + * @param boolean $useCache is default true, set to false to avoid using cached object. + * + * @return $this + */ + public function useCacheIfPossible($useCache = true) + { + if ($useCache && is_readable($this->cacheFileName)) { + $fileTime = filemtime($this->pathToImage); + $cacheTime = filemtime($this->cacheFileName); + + if ($fileTime <= $cacheTime) { + if ($this->useCache) { + if ($this->verbose) { + $this->log("Use cached file."); + $this->log("Cached image filesize: " . filesize($this->cacheFileName) . " bytes."); + } + $this->output($this->cacheFileName, $this->outputFormat); + } else { + $this->log("Cache is valid but ignoring it by intention."); + } + } else { + $this->log("Original file is modified, ignoring cache."); + } + } else { + $this->log("Cachefile does not exists or ignoring it."); + } + + return $this; + } + + + + /** + * Error message when failing to load somehow corrupt image. + * + * @return void + * + */ + public function failedToLoad() + { + header("HTTP/1.0 404 Not Found"); + echo("CImage.php says 404: Fatal error when opening image.
"); + + switch ($this->fileExtension) { + case 'jpg': + case 'jpeg': + $this->image = imagecreatefromjpeg($this->pathToImage); + break; + + case 'gif': + $this->image = imagecreatefromgif($this->pathToImage); + break; + + case 'png': + $this->image = imagecreatefrompng($this->pathToImage); + break; + } + + exit(); + } + + + + /** + * Load image from disk. + * + * @param string $src of image. + * @param string $dir as base directory where images are. + * + * @return $this + * + */ + public function load($src = null, $dir = null) + { + if (isset($src)) { + $this->setSource($src, $dir); + } + + $this->log("Opening file as {$this->fileExtension}."); + + switch ($this->fileExtension) { + case 'jpg': + case 'jpeg': + $this->image = @imagecreatefromjpeg($this->pathToImage); + $this->image or $this->failedToLoad(); + break; + + case 'gif': + $this->image = @imagecreatefromgif($this->pathToImage); + $this->image or $this->failedToLoad(); + break; + + case 'png': + $this->image = @imagecreatefrompng($this->pathToImage); + $this->image or $this->failedToLoad(); + + $type = $this->getPngType(); + $hasFewColors = imagecolorstotal($this->image); + + if ($type == self::PNG_RGB_PALETTE || ($hasFewColors > 0 && $hasFewColors <= 256)) { + if ($this->verbose) { + $this->log("Handle this image as a palette image."); + } + $this->palette = true; + } + break; + + default: + $this->image = false; + throw new Exception('No support for this file extension.'); + } + + if ($this->verbose) { + $this->log("imageistruecolor() : " . (imageistruecolor($this->image) ? 'true' : 'false')); + $this->log("imagecolorstotal() : " . imagecolorstotal($this->image)); + $this->log("Number of colors in image = " . $this->colorsTotal($this->image)); + $index = imagecolortransparent($this->image); + $this->log("Detected transparent color = " . ($index > 0 ? implode(", ", imagecolorsforindex($this->image, $index)) : "NONE") . " at index = $index"); + } + + return $this; + } + + + + /** + * Get the type of PNG image. + * + * @return int as the type of the png-image + * + */ + private function getPngType() + { + $pngType = ord(file_get_contents($this->pathToImage, false, null, 25, 1)); + + switch ($pngType) { + + case self::PNG_GREYSCALE: + $this->log("PNG is type 0, Greyscale."); + break; + + case self::PNG_RGB: + $this->log("PNG is type 2, RGB"); + break; + + case self::PNG_RGB_PALETTE: + $this->log("PNG is type 3, RGB with palette"); + break; + + case self::PNG_GREYSCALE_ALPHA: + $this->Log("PNG is type 4, Greyscale with alpha channel"); + break; + + case self::PNG_RGB_ALPHA: + $this->Log("PNG is type 6, RGB with alpha channel (PNG 32-bit)"); + break; + + default: + $this->Log("PNG is UNKNOWN type, is it really a PNG image?"); + } + + return $pngType; + } + + + + /** + * Calculate number of colors in an image. + * + * @param resource $im the image. + * + * @return int + */ + private function colorsTotal($im) + { + if (imageistruecolor($im)) { + $this->log("Colors as true color."); + $h = imagesy($im); + $w = imagesx($im); + $c = array(); + for ($x=0; $x < $w; $x++) { + for ($y=0; $y < $h; $y++) { + @$c['c'.imagecolorat($im, $x, $y)]++; + } + } + return count($c); + } else { + $this->log("Colors as palette."); + return imagecolorstotal($im); + } + } + + + + /** + * Preprocess image before rezising it. + * + * @return $this + */ + public function preResize() + { + $this->log("Pre-process before resizing"); + + // Rotate image + if ($this->rotateBefore) { + $this->log("Rotating image."); + $this->rotate($this->rotateBefore, $this->bgColor) + ->reCalculateDimensions(); + } + + // Auto-rotate image + if ($this->autoRotate) { + $this->log("Auto rotating image."); + $this->rotateExif() + ->reCalculateDimensions(); + } + + // Scale the original image before starting + if (isset($this->scale)) { + $this->log("Scale by {$this->scale}%"); + $newWidth = $this->width * $this->scale / 100; + $newHeight = $this->height * $this->scale / 100; + $img = $this->CreateImageKeepTransparency($newWidth, $newHeight); + imagecopyresampled($img, $this->image, 0, 0, 0, 0, $newWidth, $newHeight, $this->width, $this->height); + $this->image = $img; + $this->width = $newWidth; + $this->height = $newHeight; + } + + return $this; + } + + + + /** + * Resize and or crop the image. + * + * @return $this + */ + public function resize() + { + + $this->log("Starting to Resize()"); + $this->log("Upscale = '$this->upscale'"); + + // Only use a specified area of the image, $this->offset is defining the area to use + if (isset($this->offset)) { + + $this->log("Offset for area to use, cropping it width={$this->offset['width']}, height={$this->offset['height']}, start_x={$this->offset['left']}, start_y={$this->offset['top']}"); + $img = $this->CreateImageKeepTransparency($this->offset['width'], $this->offset['height']); + imagecopy($img, $this->image, 0, 0, $this->offset['left'], $this->offset['top'], $this->offset['width'], $this->offset['height']); + $this->image = $img; + $this->width = $this->offset['width']; + $this->height = $this->offset['height']; + } + + if ($this->crop) { + + // Do as crop, take only part of image + $this->log("Cropping area width={$this->crop['width']}, height={$this->crop['height']}, start_x={$this->crop['start_x']}, start_y={$this->crop['start_y']}"); + $img = $this->CreateImageKeepTransparency($this->crop['width'], $this->crop['height']); + imagecopy($img, $this->image, 0, 0, $this->crop['start_x'], $this->crop['start_y'], $this->crop['width'], $this->crop['height']); + $this->image = $img; + $this->width = $this->crop['width']; + $this->height = $this->crop['height']; + } + + if (!$this->upscale) { + // Consider rewriting the no-upscale code to fit within this if-statement, + // likely to be more readable code. + // The code is more or leass equal in below crop-to-fit, fill-to-fit and stretch + } + + if ($this->cropToFit) { + + // Resize by crop to fit + $this->log("Resizing using strategy - Crop to fit"); + + if (!$this->upscale && ($this->width < $this->newWidth || $this->height < $this->newHeight)) { + $this->log("Resizing - smaller image, do not upscale."); + + $cropX = round(($this->cropWidth/2) - ($this->newWidth/2)); + $cropY = round(($this->cropHeight/2) - ($this->newHeight/2)); + + $posX = 0; + $posY = 0; + + if ($this->newWidth > $this->width) { + $posX = round(($this->newWidth - $this->width) / 2); + } + + if ($this->newHeight > $this->height) { + $posY = round(($this->newHeight - $this->height) / 2); + } + + $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); + imagecopy($imageResized, $this->image, $posX, $posY, $cropX, $cropY, $this->newWidth, $this->newHeight); + } else { + $cropX = round(($this->cropWidth/2) - ($this->newWidth/2)); + $cropY = round(($this->cropHeight/2) - ($this->newHeight/2)); + $imgPreCrop = $this->CreateImageKeepTransparency($this->cropWidth, $this->cropHeight); + $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); + imagecopyresampled($imgPreCrop, $this->image, 0, 0, 0, 0, $this->cropWidth, $this->cropHeight, $this->width, $this->height); + imagecopy($imageResized, $imgPreCrop, 0, 0, $cropX, $cropY, $this->newWidth, $this->newHeight); + } + + $this->image = $imageResized; + $this->width = $this->newWidth; + $this->height = $this->newHeight; + + } else if ($this->fillToFit) { + + // Resize by fill to fit + $this->log("Resizing using strategy - Fill to fit"); + + $posX = 0; + $posY = 0; + + $ratioOrig = $this->width / $this->height; + $ratioNew = $this->newWidth / $this->newHeight; + + // Check ratio for landscape or portrait + if ($ratioOrig < $ratioNew) { + $posX = round(($this->newWidth - $this->fillWidth) / 2); + } else { + $posY = round(($this->newHeight - $this->fillHeight) / 2); + } + + if (!$this->upscale + && ($this->width < $this->newWidth || $this->height < $this->newHeight) + ) { + + $this->log("Resizing - smaller image, do not upscale."); + $posX = round(($this->fillWidth - $this->width) / 2); + $posY = round(($this->fillHeight - $this->height) / 2); + $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); + imagecopy($imageResized, $this->image, $posX, $posY, 0, 0, $this->fillWidth, $this->fillHeight); + + } else { + $imgPreFill = $this->CreateImageKeepTransparency($this->fillWidth, $this->fillHeight); + $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); + imagecopyresampled($imgPreFill, $this->image, 0, 0, 0, 0, $this->fillWidth, $this->fillHeight, $this->width, $this->height); + imagecopy($imageResized, $imgPreFill, $posX, $posY, 0, 0, $this->fillWidth, $this->fillHeight); + } + + $this->image = $imageResized; + $this->width = $this->newWidth; + $this->height = $this->newHeight; + + } else if (!($this->newWidth == $this->width && $this->newHeight == $this->height)) { + + // Resize it + $this->log("Resizing, new height and/or width"); + + if (!$this->upscale + && ($this->width < $this->newWidth || $this->height < $this->newHeight) + ) { + $this->log("Resizing - smaller image, do not upscale."); + + if (!$this->keepRatio) { + $this->log("Resizing - stretch to fit selected."); + + $posX = 0; + $posY = 0; + $cropX = 0; + $cropY = 0; + + if ($this->newWidth > $this->width && $this->newHeight > $this->height) { + $posX = round(($this->newWidth - $this->width) / 2); + $posY = round(($this->newHeight - $this->height) / 2); + } else if ($this->newWidth > $this->width) { + $posX = round(($this->newWidth - $this->width) / 2); + $cropY = round(($this->height - $this->newHeight) / 2); + } else if ($this->newHeight > $this->height) { + $posY = round(($this->newHeight - $this->height) / 2); + $cropX = round(($this->width - $this->newWidth) / 2); + } + + //$this->log("posX=$posX, posY=$posY, cropX=$cropX, cropY=$cropY."); + $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); + imagecopy($imageResized, $this->image, $posX, $posY, $cropX, $cropY, $this->newWidth, $this->newHeight); + $this->image = $imageResized; + $this->width = $this->newWidth; + $this->height = $this->newHeight; + } + } else { + $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); + imagecopyresampled($imageResized, $this->image, 0, 0, 0, 0, $this->newWidth, $this->newHeight, $this->width, $this->height); + $this->image = $imageResized; + $this->width = $this->newWidth; + $this->height = $this->newHeight; + } + } + + return $this; + } + + + + /** + * Postprocess image after rezising image. + * + * @return $this + */ + public function postResize() + { + $this->log("Post-process after resizing"); + + // Rotate image + if ($this->rotateAfter) { + $this->log("Rotating image."); + $this->rotate($this->rotateAfter, $this->bgColor); + } + + // Apply filters + if (isset($this->filters) && is_array($this->filters)) { + + foreach ($this->filters as $filter) { + $this->log("Applying filter {$filter['type']}."); + + switch ($filter['argc']) { + + case 0: + imagefilter($this->image, $filter['type']); + break; + + case 1: + imagefilter($this->image, $filter['type'], $filter['arg1']); + break; + + case 2: + imagefilter($this->image, $filter['type'], $filter['arg1'], $filter['arg2']); + break; + + case 3: + imagefilter($this->image, $filter['type'], $filter['arg1'], $filter['arg2'], $filter['arg3']); + break; + + case 4: + imagefilter($this->image, $filter['type'], $filter['arg1'], $filter['arg2'], $filter['arg3'], $filter['arg4']); + break; + } + } + } + + // Convert to palette image + if ($this->palette) { + $this->log("Converting to palette image."); + $this->trueColorToPalette(); + } + + // Blur the image + if ($this->blur) { + $this->log("Blur."); + $this->blurImage(); + } + + // Emboss the image + if ($this->emboss) { + $this->log("Emboss."); + $this->embossImage(); + } + + // Sharpen the image + if ($this->sharpen) { + $this->log("Sharpen."); + $this->sharpenImage(); + } + + // Custom convolution + if ($this->convolve) { + //$this->log("Convolve: " . $this->convolve); + $this->imageConvolution(); + } + + return $this; + } + + + + /** + * Rotate image using angle. + * + * @param float $angle to rotate image. + * @param int $anglebgColor to fill image with if needed. + * + * @return $this + */ + public function rotate($angle, $bgColor) + { + $this->log("Rotate image " . $angle . " degrees with filler color."); + + $color = $this->getBackgroundColor(); + $this->image = imagerotate($this->image, $angle, $color); + + $this->width = imagesx($this->image); + $this->height = imagesy($this->image); + + $this->log("New image dimension width x height: " . $this->width . " x " . $this->height); + + return $this; + } + + + + /** + * Rotate image using information in EXIF. + * + * @return $this + */ + public function rotateExif() + { + if (!in_array($this->fileExtension, array('jpg', 'jpeg'))) { + $this->log("Autorotate ignored, EXIF not supported by this filetype."); + return $this; + } + + $exif = exif_read_data($this->pathToImage); + + if (!empty($exif['Orientation'])) { + switch ($exif['Orientation']) { + case 3: + $this->log("Autorotate 180."); + $this->rotate(180, $this->bgColor); + break; + + case 6: + $this->log("Autorotate -90."); + $this->rotate(-90, $this->bgColor); + break; + + case 8: + $this->log("Autorotate 90."); + $this->rotate(90, $this->bgColor); + break; + + default: + $this->log("Autorotate ignored, unknown value as orientation."); + } + } else { + $this->log("Autorotate ignored, no orientation in EXIF."); + } + + return $this; + } + + + + /** + * Convert true color image to palette image, keeping alpha. + * http://stackoverflow.com/questions/5752514/how-to-convert-png-to-8-bit-png-using-php-gd-library + * + * @return void + */ + public function trueColorToPalette() + { + $img = imagecreatetruecolor($this->width, $this->height); + $bga = imagecolorallocatealpha($img, 0, 0, 0, 127); + imagecolortransparent($img, $bga); + imagefill($img, 0, 0, $bga); + imagecopy($img, $this->image, 0, 0, 0, 0, $this->width, $this->height); + imagetruecolortopalette($img, false, 255); + imagesavealpha($img, true); + + if (imageistruecolor($this->image)) { + $this->log("Matching colors with true color image."); + imagecolormatch($this->image, $img); + } + + $this->image = $img; + } + + + + /** + * Sharpen image using image convolution. + * + * @return $this + */ + public function sharpenImage() + { + $this->imageConvolution('sharpen'); + return $this; + } + + + + /** + * Emboss image using image convolution. + * + * @return $this + */ + public function embossImage() + { + $this->imageConvolution('emboss'); + return $this; + } + + + + /** + * Blur image using image convolution. + * + * @return $this + */ + public function blurImage() + { + $this->imageConvolution('blur'); + return $this; + } + + + + /** + * Create convolve expression and return arguments for image convolution. + * + * @param string $expression constant string which evaluates to a list of + * 11 numbers separated by komma or such a list. + * + * @return array as $matrix (3x3), $divisor and $offset + */ + public function createConvolveArguments($expression) + { + // Check of matching constant + if (isset($this->convolves[$expression])) { + $expression = $this->convolves[$expression]; + } + + $part = explode(',', $expression); + $this->log("Creating convolution expressen: $expression"); + + // Expect list of 11 numbers, split by , and build up arguments + if (count($part) != 11) { + throw new Exception( + "Missmatch in argument convolve. Expected comma-separated string with + 11 float values. Got $expression." + ); + } + + array_walk($part, function ($item, $key) { + if (!is_numeric($item)) { + throw new Exception("Argument to convolve expression should be float but is not."); + } + }); + + return array( + array( + array($part[0], $part[1], $part[2]), + array($part[3], $part[4], $part[5]), + array($part[6], $part[7], $part[8]), + ), + $part[9], + $part[10], + ); + } + + + + /** + * Add custom expressions (or overwrite existing) for image convolution. + * + * @param array $options Key value array with strings to be converted + * to convolution expressions. + * + * @return $this + */ + public function addConvolveExpressions($options) + { + $this->convolves = array_merge($this->convolves, $options); + return $this; + } + + + + /** + * Image convolution. + * + * @param string $options A string with 11 float separated by comma. + * + * @return $this + */ + public function imageConvolution($options = null) + { + // Use incoming options or use $this. + $options = $options ? $options : $this->convolve; + + // Treat incoming as string, split by + + $this->log("Convolution with '$options'"); + $options = explode(":", $options); + + // Check each option if it matches constant value + foreach ($options as $option) { + list($matrix, $divisor, $offset) = $this->createConvolveArguments($option); + imageconvolution($this->image, $matrix, $divisor, $offset); + } + + return $this; + } + + + + /** + * Set default background color between 000000-FFFFFF or if using + * alpha 00000000-FFFFFF7F. + * + * @param string $color as hex value. + * + * @return $this + */ + public function setDefaultBackgroundColor($color) + { + $this->log("Setting default background color to '$color'."); + + if (!(strlen($color) == 6 || strlen($color) == 8)) { + throw new Exception( + "Background color needs a hex value of 6 or 8 + digits. 000000-FFFFFF or 00000000-FFFFFF7F. + Current value was: '$color'." + ); + } + + $red = hexdec(substr($color, 0, 2)); + $green = hexdec(substr($color, 2, 2)); + $blue = hexdec(substr($color, 4, 2)); + + $alpha = (strlen($color) == 8) + ? hexdec(substr($color, 6, 2)) + : null; + + if (($red < 0 || $red > 255) + || ($green < 0 || $green > 255) + || ($blue < 0 || $blue > 255) + || ($alpha < 0 || $alpha > 127) + ) { + throw new Exception( + "Background color out of range. Red, green blue + should be 00-FF and alpha should be 00-7F. + Current value was: '$color'." + ); + } + + $this->bgColor = strtolower($color); + $this->bgColorDefault = array( + 'red' => $red, + 'green' => $green, + 'blue' => $blue, + 'alpha' => $alpha + ); + + return $this; + } + + + + /** + * Get the background color. + * + * @param resource $img the image to work with or null if using $this->image. + * + * @return color value or null if no background color is set. + */ + private function getBackgroundColor($img = null) + { + $img = isset($img) ? $img : $this->image; + + if ($this->bgColorDefault) { + + $red = $this->bgColorDefault['red']; + $green = $this->bgColorDefault['green']; + $blue = $this->bgColorDefault['blue']; + $alpha = $this->bgColorDefault['alpha']; + + if ($alpha) { + $color = imagecolorallocatealpha($img, $red, $green, $blue, $alpha); + } else { + $color = imagecolorallocate($img, $red, $green, $blue); + } + + return $color; + + } else { + return 0; + } + } + + + + /** + * Create a image and keep transparency for png and gifs. + * + * @param int $width of the new image. + * @param int $height of the new image. + * + * @return image resource. + */ + private function createImageKeepTransparency($width, $height) + { + $this->log("Creating a new working image width={$width}px, height={$height}px."); + $img = imagecreatetruecolor($width, $height); + imagealphablending($img, false); + imagesavealpha($img, true); + + $index = imagecolortransparent($this->image); + if ($index != -1) { + + imagealphablending($img, true); + $transparent = imagecolorsforindex($this->image, $index); + $color = imagecolorallocatealpha($img, $transparent['red'], $transparent['green'], $transparent['blue'], $transparent['alpha']); + imagefill($img, 0, 0, $color); + $index = imagecolortransparent($img, $color); + $this->Log("Detected transparent color = " . implode(", ", $transparent) . " at index = $index"); + + } elseif ($this->bgColorDefault) { + + $color = $this->getBackgroundColor($img); + imagefill($img, 0, 0, $color); + $this->Log("Filling image with background color."); + } + + return $img; + } + + + + /** + * Set optimizing and post-processing options. + * + * @param array $options with config for postprocessing with external tools. + * + * @return $this + */ + public function setPostProcessingOptions($options) + { + if (isset($options['jpeg_optimize']) && $options['jpeg_optimize']) { + $this->jpegOptimizeCmd = $options['jpeg_optimize_cmd']; + } else { + $this->jpegOptimizeCmd = null; + } + + if (isset($options['png_filter']) && $options['png_filter']) { + $this->pngFilterCmd = $options['png_filter_cmd']; + } else { + $this->pngFilterCmd = null; + } + + if (isset($options['png_deflate']) && $options['png_deflate']) { + $this->pngDeflateCmd = $options['png_deflate_cmd']; + } else { + $this->pngDeflateCmd = null; + } + + return $this; + } + + + + /** + * Save image. + * + * @param string $src as target filename. + * @param string $base as base directory where to store images. + * + * @return $this or false if no folder is set. + */ + public function save($src = null, $base = null) + { + if (isset($src)) { + $this->setTarget($src, $base); + } + + is_writable($this->saveFolder) + or $this->raiseError('Target directory is not writable.'); + + switch(strtolower($this->extension)) { + + case 'jpeg': + case 'jpg': + $this->Log("Saving image as JPEG to cache using quality = {$this->quality}."); + imagejpeg($this->image, $this->cacheFileName, $this->quality); + + // Use JPEG optimize if defined + if ($this->jpegOptimizeCmd) { + if ($this->verbose) { + clearstatcache(); + $this->log("Filesize before optimize: " . filesize($this->cacheFileName) . " bytes."); + } + $res = array(); + $cmd = $this->jpegOptimizeCmd . " -outfile $this->cacheFileName $this->cacheFileName"; + exec($cmd, $res); + $this->log($cmd); + $this->log($res); + } + break; + + case 'gif': + $this->Log("Saving image as GIF to cache."); + imagegif($this->image, $this->cacheFileName); + break; + + case 'png': + $this->Log("Saving image as PNG to cache using compression = {$this->compress}."); + + // Turn off alpha blending and set alpha flag + imagealphablending($this->image, false); + imagesavealpha($this->image, true); + imagepng($this->image, $this->cacheFileName, $this->compress); + + // Use external program to filter PNG, if defined + if ($this->pngFilterCmd) { + if ($this->verbose) { + clearstatcache(); + $this->Log("Filesize before filter optimize: " . filesize($this->cacheFileName) . " bytes."); + } + $res = array(); + $cmd = $this->pngFilterCmd . " $this->cacheFileName"; + exec($cmd, $res); + $this->Log($cmd); + $this->Log($res); + } + + // Use external program to deflate PNG, if defined + if ($this->pngDeflateCmd) { + if ($this->verbose) { + clearstatcache(); + $this->Log("Filesize before deflate optimize: " . filesize($this->cacheFileName) . " bytes."); + } + $res = array(); + $cmd = $this->pngDeflateCmd . " $this->cacheFileName"; + exec($cmd, $res); + $this->Log($cmd); + $this->Log($res); + } + break; + + default: + $this->RaiseError('No support for this file extension.'); + break; + } + + if ($this->verbose) { + clearstatcache(); + $this->log("Cached image filesize: " . filesize($this->cacheFileName) . " bytes."); + $this->log("imageistruecolor() : " . (imageistruecolor($this->image) ? 'true' : 'false')); + $this->log("imagecolorstotal() : " . imagecolorstotal($this->image)); + $this->log("Number of colors in image = " . $this->ColorsTotal($this->image)); + $index = imagecolortransparent($this->image); + $this->log("Detected transparent color = " . ($index > 0 ? implode(", ", imagecolorsforindex($this->image, $index)) : "NONE") . " at index = $index"); + } + + return $this; + } + + + + /** + * Create a hard link, as an alias, to the cached file. + * + * @param string $alias where to store the link, + * filename without extension. + * + * @return $this + */ + public function linkToCacheFile($alias) + { + if ($alias === null) { + $this->log("Ignore creating alias."); + return $this; + } + + $alias = $alias . "." . $this->extension; + + if (is_readable($alias)) { + unlink($alias); + } + + $res = link($this->cacheFileName, $alias); + + if ($res) { + $this->log("Created an alias as: $alias"); + } else { + $this->log("Failed to create the alias: $alias"); + } + + return $this; + } + + + + /** + * Output image to browser using caching. + * + * @param string $file to read and output, default is to use $this->cacheFileName + * @param string $format set to json to output file as json object with details + * + * @return void + */ + public function output($file = null, $format = null) + { + if (is_null($file)) { + $file = $this->cacheFileName; + } + + if (is_null($format)) { + $format = $this->outputFormat; + } + + $this->log("Output format is: $format"); + + if (!$this->verbose && $format == 'json') { + header('Content-type: application/json'); + echo $this->json($file); + exit; + } + + $this->log("Outputting image: $file"); + + // Get image modification time + clearstatcache(); + $lastModified = filemtime($file); + $gmdate = gmdate("D, d M Y H:i:s", $lastModified); + + if (!$this->verbose) { + header('Last-Modified: ' . $gmdate . " GMT"); + } + + if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $lastModified) { + + if ($this->verbose) { + $this->log("304 not modified"); + $this->verboseOutput(); + exit; + } + + header("HTTP/1.0 304 Not Modified"); + + } else { + + if ($this->verbose) { + $this->log("Last modified: " . $gmdate . " GMT"); + $this->verboseOutput(); + exit; + } + + // Get details on image + $info = getimagesize($file); + !empty($info) or $this->raiseError("The file doesn't seem to be an image."); + $mime = $info['mime']; + + header('Content-type: ' . $mime); + readfile($file); + } + + exit; + } + + + + /** + * Create a JSON object from the image details. + * + * @param string $file the file to output. + * + * @return string json-encoded representation of the image. + */ + public function json($file = null) + { + $file = $file ? $file : $this->cacheFileName; + + $details = array(); + + clearstatcache(); + + $details['src'] = $this->imageSrc; + $lastModified = filemtime($this->pathToImage); + $details['srcGmdate'] = gmdate("D, d M Y H:i:s", $lastModified); + + $details['cache'] = basename($this->cacheFileName); + $lastModified = filemtime($this->cacheFileName); + $details['cacheGmdate'] = gmdate("D, d M Y H:i:s", $lastModified); + + $this->loadImageDetails($file); + + $details['filename'] = basename($file); + $details['width'] = $this->width; + $details['height'] = $this->height; + $details['aspectRatio'] = round($this->width / $this->height, 3); + $details['size'] = filesize($file); + + $this->load($file); + $details['colors'] = $this->colorsTotal($this->image); + + $options = null; + if (defined("JSON_PRETTY_PRINT") && defined("JSON_UNESCAPED_SLASHES")) { + $options = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES; + } + + return json_encode($details, $options); + } + + + + /** + * Log an event if verbose mode. + * + * @param string $message to log. + * + * @return this + */ + public function log($message) + { + if ($this->verbose) { + $this->log[] = $message; + } + + return $this; + } + + + + /** + * Do verbose output and print out the log and the actual images. + * + * @return void + */ + private function verboseOutput() + { + $log = null; + $this->log("As JSON: \n" . $this->json()); + $this->log("Memory peak: " . round(memory_get_peak_usage() /1024/1024) . "M"); + $this->log("Memory limit: " . ini_get('memory_limit')); + + $included = get_included_files(); + $this->log("Included files: " . count($included)); + + foreach ($this->log as $val) { + if (is_array($val)) { + foreach ($val as $val1) { + $log .= htmlentities($val1) . '
'; + } + } else { + $log .= htmlentities($val) . '
'; + } + } + + echo << + + +CImage verbose output + +

CImage Verbose Output

+
{$log}
+EOD; + } + + + + /** + * Raise error, enables to implement a selection of error methods. + * + * @param string $message the error message to display. + * + * @return void + * @throws Exception + */ + private function raiseError($message) + { + throw new Exception($message); + } +} + diff --git a/docs/api/files/CRemoteImage.html b/docs/api/files/CRemoteImage.html new file mode 100644 index 0000000..b6164f8 --- /dev/null +++ b/docs/api/files/CRemoteImage.html @@ -0,0 +1,256 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

CRemoteImage.php

+

+ + + + +

Classes

+ + + + + +
CRemoteImageGet a image from a remote server using HTTP GET and If-Modified-Since.
+
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/CRemoteImage.php.txt b/docs/api/files/CRemoteImage.php.txt new file mode 100644 index 0000000..9d28d64 --- /dev/null +++ b/docs/api/files/CRemoteImage.php.txt @@ -0,0 +1,352 @@ +status; + } + + + + /** + * Get JSON details for cache item. + * + * @return array with json details on cache. + */ + public function getDetails() + { + return $this->cache; + } + + + + /** + * Set the path to the cache directory. + * + * @param boolean $use true to use the cache and false to ignore cache. + * + * @return $this + */ + public function setCache($path) + { + $this->saveFolder = $path; + return $this; + } + + + + /** + * Check if cache is writable or throw exception. + * + * @return $this + * + * @throws Exception if cahce folder is not writable. + */ + public function isCacheWritable() + { + if (!is_writable($this->saveFolder)) { + throw new Exception("Cache folder is not writable for downloaded files."); + } + return $this; + } + + + + /** + * Decide if the cache should be used or not before trying to download + * a remote file. + * + * @param boolean $use true to use the cache and false to ignore cache. + * + * @return $this + */ + public function useCache($use = true) + { + $this->useCache = $use; + return $this; + } + + + + /** + * Translate a content type to a file extension. + * + * @param string $type a valid content type. + * + * @return string as file extension or false if no match. + */ + function contentTypeToFileExtension($type) { + $extension = array( + 'image/jpeg' => 'jpg', + 'image/png' => 'png', + 'image/gif' => 'gif', + ); + + return isset($extension[$type]) + ? $extension[$type] + : false; + } + + + + /** + * Set header fields. + * + * @return $this + */ + function setHeaderFields() { + $this->http->setHeader("User-Agent", "CImage/0.6 (PHP/". phpversion() . " cURL)"); + $this->http->setHeader("Accept", "image/jpeg,image/png,image/gif"); + + if ($this->useCache) { + $this->http->setHeader("Cache-Control", "max-age=0"); + } else { + $this->http->setHeader("Cache-Control", "no-cache"); + $this->http->setHeader("Pragma", "no-cache"); + } + } + + + + /** + * Save downloaded resource to cache. + * + * @return string as path to saved file or false if not saved. + */ + function save() { + + $this->cache = array(); + $date = $this->http->getDate(time()); + $maxAge = $this->http->getMaxAge($this->defaultMaxAge); + $lastModified = $this->http->getLastModified(); + $type = $this->http->getContentType(); + $extension = $this->contentTypeToFileExtension($type); + + $this->cache['Date'] = gmdate("D, d M Y H:i:s T", $date); + $this->cache['Max-Age'] = $maxAge; + $this->cache['Content-Type'] = $type; + $this->cache['File-Extension'] = $extension; + + if ($lastModified) { + $this->cache['Last-Modified'] = gmdate("D, d M Y H:i:s T", $lastModified); + } + + if ($extension) { + + $this->fileImage = $this->fileName . "." . $extension; + + // Save only if body is a valid image + $body = $this->http->getBody(); + $img = imagecreatefromstring($body); + + if ($img !== false) { + file_put_contents($this->fileImage, $body); + file_put_contents($this->fileJson, json_encode($this->cache)); + return $this->fileImage; + } + } + + return false; + } + + + + /** + * Got a 304 and updates cache with new age. + * + * @return string as path to cached file. + */ + function updateCacheDetails() { + + $date = $this->http->getDate(time()); + $maxAge = $this->http->getMaxAge($this->defaultMaxAge); + $lastModified = $this->http->getLastModified(); + + $this->cache['Date'] = gmdate("D, d M Y H:i:s T", $date); + $this->cache['Max-Age'] = $maxAge; + + if ($lastModified) { + $this->cache['Last-Modified'] = gmdate("D, d M Y H:i:s T", $lastModified); + } + + file_put_contents($this->fileJson, json_encode($this->cache)); + return $this->fileImage; + } + + + + /** + * Download a remote file and keep a cache of downloaded files. + * + * @param string $url a remote url. + * + * @return string as path to downloaded file or false if failed. + */ + function download($url) { + + $this->http = new CHttpGet(); + $this->url = $url; + + // First check if the cache is valid and can be used + $this->loadCacheDetails(); + + if ($this->useCache) { + $src = $this->getCachedSource(); + if ($src) { + $this->status = 1; + return $src; + } + } + + // Do a HTTP request to download item + $this->setHeaderFields(); + $this->http->setUrl($this->url); + $this->http->doGet(); + + $this->status = $this->http->getStatus(); + if ($this->status === 200) { + $this->isCacheWritable(); + return $this->save(); + } else if ($this->status === 304) { + $this->isCacheWritable(); + return $this->updateCacheDetails(); + } + + return false; + } + + + + /** + * Get the path to the cached image file if the cache is valid. + * + * @return $this + */ + public function loadCacheDetails() + { + $cacheFile = str_replace(array("/", ":", "#", ".", "?"), "-", $this->url); + $this->fileName = $this->saveFolder . $cacheFile; + $this->fileJson = $this->fileName . ".json"; + if (is_readable($this->fileJson)) { + $this->cache = json_decode(file_get_contents($this->fileJson), true); + } + } + + + + /** + * Get the path to the cached image file if the cache is valid. + * + * @return string as the path ot the image file or false if no cache. + */ + public function getCachedSource() + { + $this->fileImage = $this->fileName . "." . $this->cache['File-Extension']; + $imageExists = is_readable($this->fileImage); + + // Is cache valid? + $date = strtotime($this->cache['Date']); + $maxAge = $this->cache['Max-Age']; + $now = time(); + if ($imageExists && $date + $maxAge > $now) { + return $this->fileImage; + } + + // Prepare for a 304 if available + if ($imageExists && isset($this->cache['Last-Modified'])) { + $this->http->setHeader("If-Modified-Since", $this->cache['Last-Modified']); + } + + return false; + } +} + diff --git a/docs/api/files/CWhitelist.html b/docs/api/files/CWhitelist.html new file mode 100644 index 0000000..42d5e28 --- /dev/null +++ b/docs/api/files/CWhitelist.html @@ -0,0 +1,256 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

CWhitelist.php

+

+ + + + +

Classes

+ + + + + +
CWhitelistAct as whitelist (or blacklist).
+
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/CWhitelist.php.txt b/docs/api/files/CWhitelist.php.txt new file mode 100644 index 0000000..6f76ef0 --- /dev/null +++ b/docs/api/files/CWhitelist.php.txt @@ -0,0 +1,63 @@ +whitelist = $whitelist; + } else { + throw new Exception("Whitelist is not of a supported format."); + } + return $this; + } + + + + /** + * Check if item exists in the whitelist. + * + * @param string $item string to check. + * @param array $whitelist optional with all valid options, default is null. + * + * @return boolean true if item is in whitelist, else false. + */ + public function check($item, $whitelist = null) + { + if ($whitelist !== null) { + $this->set($whitelist); + } + + if (empty($item) or empty($this->whitelist)) { + return false; + } + + foreach ($this->whitelist as $regexp) { + if (preg_match("#$regexp#", $item)) { + return true; + } + } + + return false; + } +} + diff --git a/docs/api/files/autoload.html b/docs/api/files/autoload.html new file mode 100644 index 0000000..7fbdb71 --- /dev/null +++ b/docs/api/files/autoload.html @@ -0,0 +1,249 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

autoload.php

+

Autoloader for CImage and related class files.

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/autoload.php.txt b/docs/api/files/autoload.php.txt new file mode 100644 index 0000000..e49ff26 --- /dev/null +++ b/docs/api/files/autoload.php.txt @@ -0,0 +1,24 @@ +setRemoteDownload(true); + + $res = $img->isRemoteSource($source); + $this->assertTrue($res, "Should be a valid remote source: '$source'."); + } + + + + /** + * Test + * + * @return void + * + * @dataProvider providerInvalidRemoteSource + */ + public function testAllowRemoteDownloadDefaultPatternInvalid($source) + { + $img = new CImage(); + $img->setRemoteDownload(true); + + $res = $img->isRemoteSource($source); + $this->assertFalse($res, "Should not be a valid remote source: '$source'."); + } + + + + /** + * Provider for hostname matching the whitelist. + * + * @return array + */ + public function providerHostnameMatch() + { + return [ + [ + "any.facebook.com", + "images.ak.instagram.com", + "google.com", + ], + ]; + } + + + + /** + * Test + * + * @param string $hostname matches the whitelist + * + * @return void + * + * @dataProvider providerHostnameMatch + * + */ + public function testRemoteHostWhitelistMatch($hostname) + { + $img = new CImage(); + $img->setRemoteHostWhitelist($this->remote_whitelist); + + $res = $img->isRemoteSourceOnWhitelist("http://$hostname/img.jpg"); + $this->assertTrue($res, "Should be a valid hostname on the whitelist: '$hostname'."); + } + + + + /** + * Provider for hostname not matching the whitelist. + * + * @return array + */ + public function providerHostnameNoMatch() + { + return [ + [ + "example.com", + ".com", + "img.jpg", + ], + ]; + } + + + + /** + * Test + * + * @param string $hostname not matching the whitelist + * + * @return void + * + * @dataProvider providerHostnameNoMatch + * + */ + public function testRemoteHostWhitelistNoMatch($hostname) + { + $img = new CImage(); + $img->setRemoteHostWhitelist($this->remote_whitelist); + + $res = $img->isRemoteSourceOnWhitelist("http://$hostname/img.jpg"); + $this->assertFalse($res, "Should not be a valid hostname on the whitelist: '$hostname'."); + } +} + diff --git a/docs/api/files/test%2FCWhitelistTest.php.txt b/docs/api/files/test%2FCWhitelistTest.php.txt new file mode 100644 index 0000000..0069f4f --- /dev/null +++ b/docs/api/files/test%2FCWhitelistTest.php.txt @@ -0,0 +1,95 @@ +set($this->remote_whitelist); + + $res = $whitelist->check($hostname); + $this->assertTrue($res, "Should be a valid hostname on the whitelist: '$hostname'."); + } + + + + /** + * Test + * + * @param string $hostname not matching the whitelist + * + * @return void + * + * @dataProvider providerHostnameNoMatch + * + */ + public function testRemoteHostWhitelistNoMatch($hostname) + { + $whitelist = new CWhitelist(); + $whitelist->set($this->remote_whitelist); + + $res = $whitelist->check($hostname); + $this->assertFalse($res, "Should not be a valid hostname on the whitelist: '$hostname'."); + } +} + diff --git a/docs/api/files/test%2Fconfig.php.txt b/docs/api/files/test%2Fconfig.php.txt new file mode 100644 index 0000000..23645cf --- /dev/null +++ b/docs/api/files/test%2Fconfig.php.txt @@ -0,0 +1,7 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

testCImage_RemoteDownloadTest.php

+

+ + + + +

Classes

+ + + + + +
CImage_RemoteDownloadTestA testclass
+
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/test.CWhitelistTest.html b/docs/api/files/test.CWhitelistTest.html new file mode 100644 index 0000000..4de23cd --- /dev/null +++ b/docs/api/files/test.CWhitelistTest.html @@ -0,0 +1,267 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

testCWhitelistTest.php

+

+ + + + +

Classes

+ + + + + +
CWhitelistTestA testclass
+
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/test.config.html b/docs/api/files/test.config.html new file mode 100644 index 0000000..b694565 --- /dev/null +++ b/docs/api/files/test.config.html @@ -0,0 +1,260 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

testconfig.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot%2Fcheck_system.php.txt b/docs/api/files/webroot%2Fcheck_system.php.txt new file mode 100644 index 0000000..ca2c26d --- /dev/null +++ b/docs/api/files/webroot%2Fcheck_system.php.txt @@ -0,0 +1,16 @@ +
'; + +echo 'Running on: ' . $_SERVER['SERVER_SOFTWARE'] . '

'; + +$no = extension_loaded('gd') ? null : 'NOT'; +echo "Extension gd is $no loaded.
"; + +$no = extension_loaded('exif') ? null : 'NOT'; +echo "Extension exif is $no loaded.
"; + +if (!$no) { + echo "
", var_dump(gd_info()), "
"; +} + diff --git a/docs/api/files/webroot%2Fcompare%2Fcompare-test.php.txt b/docs/api/files/webroot%2Fcompare%2Fcompare-test.php.txt new file mode 100644 index 0000000..a16babb --- /dev/null +++ b/docs/api/files/webroot%2Fcompare%2Fcompare-test.php.txt @@ -0,0 +1,13 @@ + + + + + + + +

Compare images

+

Add link to images and visually compare them. Change the link och press return to load the image. Read more...

+ +
+

+
+
+
+
+
+ +

+
+ + + +
+ +
+ Image 1
+ +

+    
+ +
+ Image 2
+ +

+    
+ +
+ Image 3
+ +

+    
+ +
+ Image 4
+ +

+    
+ +
+ + + + + + + + + + diff --git a/docs/api/files/webroot%2Fimg.php.txt b/docs/api/files/webroot%2Fimg.php.txt new file mode 100644 index 0000000..313490b --- /dev/null +++ b/docs/api/files/webroot%2Fimg.php.txt @@ -0,0 +1,916 @@ +img.php: Uncaught exception:

" . $exception->getMessage() . "

" . $exception->getTraceAsString(), "
"); +}); + + + +/** + * Get input from query string or return default value if not set. + * + * @param mixed $key as string or array of string values to look for in $_GET. + * @param mixed $default value to return when $key is not set in $_GET. + * + * @return mixed value from $_GET or default value. + */ +function get($key, $default = null) +{ + if (is_array($key)) { + foreach ($key as $val) { + if (isset($_GET[$val])) { + return $_GET[$val]; + } + } + } elseif (isset($_GET[$key])) { + return $_GET[$key]; + } + return $default; +} + + + +/** + * Get input from query string and set to $defined if defined or else $undefined. + * + * @param mixed $key as string or array of string values to look for in $_GET. + * @param mixed $defined value to return when $key is set in $_GET. + * @param mixed $undefined value to return when $key is not set in $_GET. + * + * @return mixed value as $defined or $undefined. + */ +function getDefined($key, $defined, $undefined) +{ + return get($key) === null ? $undefined : $defined; +} + + + +/** + * Get value from config array or default if key is not set in config array. + * + * @param string $key the key in the config array. + * @param mixed $default value to be default if $key is not set in config. + * + * @return mixed value as $config[$key] or $default. + */ +function getConfig($key, $default) +{ + global $config; + return isset($config[$key]) + ? $config[$key] + : $default; +} + + + +/** + * Log when verbose mode, when used without argument it returns the result. + * + * @param string $msg to log. + * + * @return void or array. + */ +function verbose($msg = null) +{ + global $verbose; + static $log = array(); + + if (!$verbose) { + return; + } + + if (is_null($msg)) { + return $log; + } + + $log[] = $msg; +} + + + +/** + * Get configuration options from file, if the file exists, else use $config + * if its defined or create an empty $config. + */ +$configFile = __DIR__.'/'.basename(__FILE__, '.php').'_config.php'; + +if (is_file($configFile)) { + $config = require $configFile; +} else if (!isset($config)) { + $config = array(); +} + + + +/** +* verbose, v - do a verbose dump of what happens +*/ +$verbose = getDefined(array('verbose', 'v'), true, false); +verbose("img.php version = $version"); + + + +/** + * Set mode as strict, production or development. + * Default is production environment. + */ +$mode = getConfig('mode', 'production'); + +// Settings for any mode +set_time_limit(20); +ini_set('gd.jpeg_ignore_warning', 1); + +if (!extension_loaded('gd')) { + errorPage("Extension gd is nod loaded."); +} + +// Specific settings for each mode +if ($mode == 'strict') { + + error_reporting(0); + ini_set('display_errors', 0); + ini_set('log_errors', 1); + $verbose = false; + +} else if ($mode == 'production') { + + error_reporting(-1); + ini_set('display_errors', 0); + ini_set('log_errors', 1); + $verbose = false; + +} else if ($mode == 'development') { + + error_reporting(-1); + ini_set('display_errors', 1); + ini_set('log_errors', 0); + +} else { + errorPage("Unknown mode: $mode"); +} + +verbose("mode = $mode"); +verbose("error log = " . ini_get('error_log')); + + + +/** + * Set default timezone if not set or if its set in the config-file. + */ +$defaultTimezone = getConfig('default_timezone', null); + +if ($defaultTimezone) { + date_default_timezone_set($defaultTimezone); +} else if (!ini_get('default_timezone')) { + date_default_timezone_set('UTC'); +} + + + +/** + * Check if passwords are configured, used and match. + * Options decide themself if they require passwords to be used. + */ +$pwdConfig = getConfig('password', false); +$pwdAlways = getConfig('password_always', false); +$pwd = get(array('password', 'pwd'), null); + +// Check if passwords match, if configured to use passwords +$passwordMatch = null; +if ($pwdAlways) { + + $passwordMatch = ($pwdConfig === $pwd); + if (!$passwordMatch) { + errorPage("Password required and does not match or exists."); + } + +} elseif ($pwdConfig && $pwd) { + + $passwordMatch = ($pwdConfig === $pwd); +} + +verbose("password match = $passwordMatch"); + + + +/** + * Prevent hotlinking, leeching, of images by controlling who access them + * from where. + * + */ +$allowHotlinking = getConfig('allow_hotlinking', true); +$hotlinkingWhitelist = getConfig('hotlinking_whitelist', array()); + +$serverName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null; +$referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null; +$refererHost = parse_url($referer, PHP_URL_HOST); + +if (!$allowHotlinking) { + if ($passwordMatch) { + ; // Always allow when password match + } else if ($passwordMatch === false) { + errorPage("Hotlinking/leeching not allowed when password missmatch."); + } else if (!$referer) { + errorPage("Hotlinking/leeching not allowed and referer is missing."); + } else if (strcmp($serverName, $refererHost) == 0) { + ; // Allow when serverName matches refererHost + } else if (!empty($hotlinkingWhitelist)) { + + $allowedByWhitelist = false; + foreach ($hotlinkingWhitelist as $val) { + if (preg_match($val, $refererHost)) { + $allowedByWhitelist = true; + } + } + + if (!$allowedByWhitelist) { + errorPage("Hotlinking/leeching not allowed by whitelist."); + } + + } else { + errorPage("Hotlinking/leeching not allowed."); + } +} + +verbose("allow_hotlinking = $allowHotlinking"); +verbose("referer = $referer"); +verbose("referer host = $refererHost"); + + + +/** + * Get the source files. + */ +$autoloader = getConfig('autoloader', false); +$cimageClass = getConfig('cimage_class', false); + +if ($autoloader) { + require $autoloader; +} else if ($cimageClass) { + require $cimageClass; +} + + + +/** + * Create the class for the image. + */ +$img = new CImage(); +$img->setVerbose($verbose); + + + +/** + * Allow or disallow remote download of images from other servers. + * Passwords apply if used. + * + */ +$allowRemote = getConfig('remote_allow', false); + +if ($allowRemote && $passwordMatch !== false) { + $pattern = getConfig('remote_pattern', null); + $img->setRemoteDownload($allowRemote, $pattern); + + $whitelist = getConfig('remote_whitelist', null); + $img->setRemoteHostWhitelist($whitelist); +} + + + +/** + * shortcut, sc - extend arguments with a constant value, defined + * in config-file. + */ +$shortcut = get(array('shortcut', 'sc'), null); +$shortcutConfig = getConfig('shortcut', array( + 'sepia' => "&f=grayscale&f0=brightness,-10&f1=contrast,-20&f2=colorize,120,60,0,0&sharpen", +)); + +verbose("shortcut = $shortcut"); + +if (isset($shortcut) + && isset($shortcutConfig[$shortcut])) { + + parse_str($shortcutConfig[$shortcut], $get); + verbose("shortcut-constant = {$shortcutConfig[$shortcut]}"); + $_GET = array_merge($_GET, $get); +} + + + +/** + * src - the source image file. + */ +$srcImage = get('src') + or errorPage('Must set src-attribute.'); + +// Check for valid/invalid characters +$imagePath = getConfig('image_path', __DIR__ . '/img/'); +$imagePathConstraint = getConfig('image_path_constraint', true); +$validFilename = getConfig('valid_filename', '#^[a-z0-9A-Z-/_\.:]+$#'); + +preg_match($validFilename, $srcImage) + or errorPage('Filename contains invalid characters.'); + +if ($allowRemote && $img->isRemoteSource($srcImage)) { + + // If source is a remote file, ignore local file checks. + +} else if ($imagePathConstraint) { + + // Check that the image is a file below the directory 'image_path'. + $pathToImage = realpath($imagePath . $srcImage); + $imageDir = realpath($imagePath); + + is_file($pathToImage) + or errorPage( + 'Source image is not a valid file, check the filename and that a + matching file exists on the filesystem.' + ); + + substr_compare($imageDir, $pathToImage, 0, strlen($imageDir)) == 0 + or errorPage( + 'Security constraint: Source image is not below the directory "image_path" + as specified in the config file img_config.php.' + ); +} + +verbose("src = $srcImage"); + + + +/** + * Manage size constants from config file, use constants to replace values + * for width and height. + */ +$sizeConstant = getConfig('size_constant', function () { + + // Set sizes to map constant to value, easier to use with width or height + $sizes = array( + 'w1' => 613, + 'w2' => 630, + ); + + // Add grid column width, useful for use as predefined size for width (or height). + $gridColumnWidth = 30; + $gridGutterWidth = 10; + $gridColumns = 24; + + for ($i = 1; $i <= $gridColumns; $i++) { + $sizes['c' . $i] = ($gridColumnWidth + $gridGutterWidth) * $i - $gridGutterWidth; + } + + return $sizes; +}); + +$sizes = call_user_func($sizeConstant); + + + +/** + * width, w - set target width, affecting the resulting image width, height and resize options + */ +$newWidth = get(array('width', 'w')); +$maxWidth = getConfig('max_width', 2000); + +// Check to replace predefined size +if (isset($sizes[$newWidth])) { + $newWidth = $sizes[$newWidth]; +} + +// Support width as % of original width +if ($newWidth[strlen($newWidth)-1] == '%') { + is_numeric(substr($newWidth, 0, -1)) + or errorPage('Width % not numeric.'); +} else { + is_null($newWidth) + or ($newWidth > 10 && $newWidth <= $maxWidth) + or errorPage('Width out of range.'); +} + +verbose("new width = $newWidth"); + + + +/** + * height, h - set target height, affecting the resulting image width, height and resize options + */ +$newHeight = get(array('height', 'h')); +$maxHeight = getConfig('max_height', 2000); + +// Check to replace predefined size +if (isset($sizes[$newHeight])) { + $newHeight = $sizes[$newHeight]; +} + +// height +if ($newHeight[strlen($newHeight)-1] == '%') { + is_numeric(substr($newHeight, 0, -1)) + or errorPage('Height % out of range.'); +} else { + is_null($newHeight) + or ($newHeight > 10 && $newHeight <= $maxHeight) + or errorPage('Hight out of range.'); +} + +verbose("new height = $newHeight"); + + + +/** + * aspect-ratio, ar - affecting the resulting image width, height and resize options + */ +$aspectRatio = get(array('aspect-ratio', 'ar')); +$aspectRatioConstant = getConfig('aspect_ratio_constant', function () { + return array( + '3:1' => 3/1, + '3:2' => 3/2, + '4:3' => 4/3, + '8:5' => 8/5, + '16:10' => 16/10, + '16:9' => 16/9, + 'golden' => 1.618, + ); +}); + +// Check to replace predefined aspect ratio +$aspectRatios = call_user_func($aspectRatioConstant); +$negateAspectRatio = ($aspectRatio[0] == '!') ? true : false; +$aspectRatio = $negateAspectRatio ? substr($aspectRatio, 1) : $aspectRatio; + +if (isset($aspectRatios[$aspectRatio])) { + $aspectRatio = $aspectRatios[$aspectRatio]; +} + +if ($negateAspectRatio) { + $aspectRatio = 1 / $aspectRatio; +} + +is_null($aspectRatio) + or is_numeric($aspectRatio) + or errorPage('Aspect ratio out of range'); + +verbose("aspect ratio = $aspectRatio"); + + + +/** + * crop-to-fit, cf - affecting the resulting image width, height and resize options + */ +$cropToFit = getDefined(array('crop-to-fit', 'cf'), true, false); + +verbose("crop to fit = $cropToFit"); + + + +/** + * Set default background color from config file. + */ +$backgroundColor = getConfig('background_color', null); + +if ($backgroundColor) { + $img->setDefaultBackgroundColor($backgroundColor); + verbose("Using default background_color = $backgroundColor"); +} + + + +/** + * bgColor - Default background color to use + */ +$bgColor = get(array('bgColor', 'bg-color', 'bgc'), null); + +verbose("bgColor = $bgColor"); + + + +/** + * fill-to-fit, ff - affecting the resulting image width, height and resize options + */ +$fillToFit = get(array('fill-to-fit', 'ff'), null); + +verbose("fill-to-fit = $fillToFit"); + +if ($fillToFit !== null) { + + if (!empty($fillToFit)) { + $bgColor = $fillToFit; + verbose("fillToFit changed bgColor to = $bgColor"); + } + + $fillToFit = true; + verbose("fill-to-fit (fixed) = $fillToFit"); +} + + + +/** + * no-ratio, nr, stretch - affecting the resulting image width, height and resize options + */ +$keepRatio = getDefined(array('no-ratio', 'nr', 'stretch'), false, true); + +verbose("keep ratio = $keepRatio"); + + + +/** + * crop, c - affecting the resulting image width, height and resize options + */ +$crop = get(array('crop', 'c')); + +verbose("crop = $crop"); + + + +/** + * area, a - affecting the resulting image width, height and resize options + */ +$area = get(array('area', 'a')); + +verbose("area = $area"); + + + +/** + * skip-original, so - skip the original image and always process a new image + */ +$useOriginal = getDefined(array('skip-original', 'so'), false, true); + +verbose("use original = $useOriginal"); + + + +/** + * no-cache, nc - skip the cached version and process and create a new version in cache. + */ +$useCache = getDefined(array('no-cache', 'nc'), false, true); + +verbose("use cache = $useCache"); + + + +/** + * quality, q - set level of quality for jpeg images + */ +$quality = get(array('quality', 'q')); + +is_null($quality) + or ($quality > 0 and $quality <= 100) + or errorPage('Quality out of range'); + +verbose("quality = $quality"); + + + +/** + * compress, co - what strategy to use when compressing png images + */ +$compress = get(array('compress', 'co')); + + +is_null($compress) + or ($compress > 0 and $compress <= 9) + or errorPage('Compress out of range'); + +verbose("compress = $compress"); + + + +/** + * save-as, sa - what type of image to save + */ +$saveAs = get(array('save-as', 'sa')); + +verbose("save as = $saveAs"); + + + +/** + * scale, s - Processing option, scale up or down the image prior actual resize + */ +$scale = get(array('scale', 's')); + +is_null($scale) + or ($scale >= 0 and $scale <= 400) + or errorPage('Scale out of range'); + +verbose("scale = $scale"); + + + +/** + * palette, p - Processing option, create a palette version of the image + */ +$palette = getDefined(array('palette', 'p'), true, false); + +verbose("palette = $palette"); + + + +/** + * sharpen - Processing option, post filter for sharpen effect + */ +$sharpen = getDefined('sharpen', true, null); + +verbose("sharpen = $sharpen"); + + + +/** + * emboss - Processing option, post filter for emboss effect + */ +$emboss = getDefined('emboss', true, null); + +verbose("emboss = $emboss"); + + + +/** + * blur - Processing option, post filter for blur effect + */ +$blur = getDefined('blur', true, null); + +verbose("blur = $blur"); + + + +/** + * rotateBefore - Rotate the image with an angle, before processing + */ +$rotateBefore = get(array('rotateBefore', 'rotate-before', 'rb')); + +is_null($rotateBefore) + or ($rotateBefore >= -360 and $rotateBefore <= 360) + or errorPage('RotateBefore out of range'); + +verbose("rotateBefore = $rotateBefore"); + + + +/** + * rotateAfter - Rotate the image with an angle, before processing + */ +$rotateAfter = get(array('rotateAfter', 'rotate-after', 'ra', 'rotate', 'r')); + +is_null($rotateAfter) + or ($rotateAfter >= -360 and $rotateAfter <= 360) + or errorPage('RotateBefore out of range'); + +verbose("rotateAfter = $rotateAfter"); + + + +/** + * autoRotate - Auto rotate based on EXIF information + */ +$autoRotate = getDefined(array('autoRotate', 'auto-rotate', 'aro'), true, false); + +verbose("autoRotate = $autoRotate"); + + + +/** + * filter, f, f0-f9 - Processing option, post filter for various effects using imagefilter() + */ +$filters = array(); +$filter = get(array('filter', 'f')); +if ($filter) { + $filters[] = $filter; +} + +for ($i = 0; $i < 10; $i++) { + $filter = get(array("filter{$i}", "f{$i}")); + if ($filter) { + $filters[] = $filter; + } +} + +verbose("filters = " . print_r($filters, 1)); + + + +/** + * json - output the image as a JSON object with details on the image. + */ +$outputFormat = getDefined('json', 'json', null); + +verbose("json = $outputFormat"); + + + +/** + * dpr - change to get larger image to easier support larger dpr, such as retina. + */ +$dpr = get(array('ppi', 'dpr', 'device-pixel-ratio'), 1); + +verbose("dpr = $dpr"); + + + +/** + * convolve - image convolution as in http://php.net/manual/en/function.imageconvolution.php + */ +$convolve = get('convolve', null); +$convolutionConstant = getConfig('convolution_constant', array()); + +// Check if the convolve is matching an existing constant +if ($convolve && isset($convolutionConstant)) { + $img->addConvolveExpressions($convolutionConstant); + verbose("convolve constant = " . print_r($convolutionConstant, 1)); +} + +verbose("convolve = " . print_r($convolve, 1)); + + + +/** + * no-upscale, nu - Do not upscale smaller image to larger dimension. + */ +$upscale = getDefined(array('no-upscale', 'nu'), false, true); + +verbose("upscale = $upscale"); + + + +/** + * Get details for post processing + */ +$postProcessing = getConfig('postprocessing', array( + 'png_filter' => false, + 'png_filter_cmd' => '/usr/local/bin/optipng -q', + + 'png_deflate' => false, + 'png_deflate_cmd' => '/usr/local/bin/pngout -q', + + 'jpeg_optimize' => false, + 'jpeg_optimize_cmd' => '/usr/local/bin/jpegtran -copy none -optimize', +)); + + + +/** + * alias - Save resulting image to another alias name. + * Password always apply, must be defined. + */ +$alias = get('alias', null); +$aliasPath = getConfig('alias_path', null); +$validAliasname = getConfig('valid_aliasname', '#^[a-z0-9A-Z-_]+$#'); +$aliasTarget = null; + +if ($alias && $aliasPath && $passwordMatch) { + + $aliasTarget = $aliasPath . $alias; + $useCache = false; + + is_writable($aliasPath) + or errorPage("Directory for alias is not writable."); + + preg_match($validAliasname, $alias) + or errorPage('Filename for alias contains invalid characters. Do not add extension.'); + +} else if ($alias) { + errorPage('Alias is not enabled in the config file or password not matching.'); +} + +verbose("alias = $alias"); + + + +/** + * Display image if verbose mode + */ +if ($verbose) { + $query = array(); + parse_str($_SERVER['QUERY_STRING'], $query); + unset($query['verbose']); + unset($query['v']); + unset($query['nocache']); + unset($query['nc']); + unset($query['json']); + $url1 = '?' . htmlentities(urldecode(http_build_query($query))); + $url2 = '?' . urldecode(http_build_query($query)); + echo <<$url1
+ +

+
+
+
+EOD;
+}
+
+
+
+/**
+ * Get the cachepath from config.
+ */
+$cachePath = getConfig('cache_path', __DIR__ . '/../cache/');
+
+
+
+/**
+ * Load, process and output the image
+ */
+$img->log("Incoming arguments: " . print_r(verbose(), 1))
+    ->setSaveFolder($cachePath)
+    ->useCache($useCache)
+    ->setSource($srcImage, $imagePath)
+    ->setOptions(
+        array(
+            // Options for calculate dimensions
+            'newWidth'  => $newWidth,
+            'newHeight' => $newHeight,
+            'aspectRatio' => $aspectRatio,
+            'keepRatio' => $keepRatio,
+            'cropToFit' => $cropToFit,
+            'fillToFit' => $fillToFit,
+            'crop'      => $crop,
+            'area'      => $area,
+            'upscale'   => $upscale,
+
+            // Pre-processing, before resizing is done
+            'scale'        => $scale,
+            'rotateBefore' => $rotateBefore,
+            'autoRotate'   => $autoRotate,
+
+            // General processing options
+            'bgColor'    => $bgColor,
+
+            // Post-processing, after resizing is done
+            'palette'   => $palette,
+            'filters'   => $filters,
+            'sharpen'   => $sharpen,
+            'emboss'    => $emboss,
+            'blur'      => $blur,
+            'convolve'  => $convolve,
+            'rotateAfter' => $rotateAfter,
+
+            // Output format
+            'outputFormat' => $outputFormat,
+            'dpr'          => $dpr,
+        )
+    )
+    ->loadImageDetails()
+    ->initDimensions()
+    ->calculateNewWidthAndHeight()
+    ->setSaveAsExtension($saveAs)
+    ->setJpegQuality($quality)
+    ->setPngCompression($compress)
+    ->useOriginalIfPossible($useOriginal)
+    ->generateFilename($cachePath)
+    ->useCacheIfPossible($useCache)
+    ->load()
+    ->preResize()
+    ->resize()
+    ->postResize()
+    ->setPostProcessingOptions($postProcessing)
+    ->save()
+    ->linkToCacheFile($aliasTarget)
+    ->output();
+
diff --git a/docs/api/files/webroot%2Fimg_config.php.txt b/docs/api/files/webroot%2Fimg_config.php.txt
new file mode 100644
index 0000000..15b3020
--- /dev/null
+++ b/docs/api/files/webroot%2Fimg_config.php.txt
@@ -0,0 +1,295 @@
+ 'development',
+    //'mode' => 'production', // 'development', 'strict'
+
+
+
+    /**
+     * Where are the sources for the classfiles.
+     *
+     * Default values:
+     *  autoloader:  null     // used from v0.6.2
+     *  cimage_class: null    // used until v0.6.1
+     */
+    'autoloader'   =>  __DIR__ . '/../autoload.php',
+    //'cimage_class' =>  __DIR__ . '/../CImage.php',
+
+
+
+    /**
+     * Paths, where are the images stored and where is the cache.
+     * End all paths with a slash.
+     *
+     * Default values:
+     *  image_path: __DIR__ . '/img/'
+     *  cache_path: __DIR__ . '/../cache/'
+     *  alias_path: null
+     */
+    'image_path'   =>  __DIR__ . '/img/',
+    'cache_path'   =>  __DIR__ . '/../cache/',
+    //'alias_path'   =>  __DIR__ . '/img/alias/',
+
+
+
+    /**
+    * Use password to protect from missusage, send &pwd=... or &password=..
+    * with the request to match the password or set to false to disable.
+    * Passwords are only used together with the options for remote download
+    * and aliasing.
+    *
+    * Default values.
+    *  password:        false // as in do not use password
+    *  password_always: false // do not always require password,
+    */
+    //'password'        => false, // "secret-password",
+    //'password_always' => false, // always require password,
+
+
+
+    /**
+     * Allow or disallow downloading of remote images available on
+     * remote servers. Default is to disallow remote download. 
+     * 
+     * When enabling remote download, the default is to allow download any
+     * link starting with http or https. This can be changed using 
+     * remote_pattern. 
+     *
+     * When enabling remote_whitelist a check is made that the hostname of the 
+     * source to download matches the whitelist. By default the check is 
+     * disabled and thereby allowing download from any hosts.
+     *
+     * Default values.
+     *  remote_allow:     false
+     *  remote_pattern:   null  // use default values from CImage which is to
+     *                          // allow download from any http- and 
+     *                          // https-source.
+     *  remote_whitelist: null  // use default values from CImage which is to 
+     *                          // allow download from any hosts.
+     */
+    //'remote_allow'     => true,
+    //'remote_pattern'   => '#^https?://#',
+    //'remote_whitelist' => array(
+    //    '\.facebook\.com$',
+    //    '^(?:images|photos-[a-z])\.ak\.instagram\.com$',
+    //    '\.google\.com$'
+    //),
+
+
+
+    /**
+     * A regexp for validating characters in the image or alias filename.
+     *
+     * Default value:
+     *  valid_filename:  '#^[a-z0-9A-Z-/_\.:]+$#'
+     *  valid_aliasname: '#^[a-z0-9A-Z-_]+$#'
+     */
+     //'valid_filename'  => '#^[a-z0-9A-Z-/_\.:]+$#',
+     //'valid_aliasname' => '#^[a-z0-9A-Z-_]+$#',
+
+
+
+     /**
+     * Check that the imagefile is a file below 'image_path' using realpath().
+     * Security constraint to avoid reaching images outside image_path.
+     * This means that symbolic links to images outside the image_path will fail.
+     *
+     * Default value:
+     *  image_path_constraint: true
+     */
+     //'image_path_constraint' => false,
+
+
+
+     /**
+     * Set default timezone.
+     *
+     * Default values.
+     *  default_timezone: ini_get('default_timezone') or 'UTC'
+     */
+    //'default_timezone' => 'UTC',
+
+
+
+    /**
+     * Max image dimensions, larger dimensions results in 404.
+     * This is basically a security constraint to avoid using resources on creating
+     * large (unwanted) images.
+     *
+     * Default values.
+     *  max_width:  2000
+     *  max_height: 2000
+     */
+    //'max_width'     => 2000,
+    //'max_height'    => 2000,
+
+
+
+    /**
+     * Set default background color for all images. Override it using
+     * option bgColor.
+     * Colorvalue is 6 digit hex string between 000000-FFFFFF
+     * or 8 digit hex string if using the alpha channel where
+     * the alpha value is between 00 (opaqe) and 7F (transparent),
+     * that is between 00000000-FFFFFF7F.
+     *
+     * Default values.
+     *  background_color: As specified by CImage
+     */
+    //'background_color' => "FFFFFF",
+    //'background_color' => "FFFFFF7F",
+
+
+
+    /**
+     * Post processing of images using external tools, set to true or false
+     * and set command to be executed.
+     *
+     * Default values.
+     *
+     *  png_filter:        false
+     *  png_filter_cmd:    '/usr/local/bin/optipng -q'
+     *
+     *  png_deflate:       false
+     *  png_deflate_cmd:   '/usr/local/bin/pngout -q'
+     *
+     *  jpeg_optimize:     false
+     *  jpeg_optimize_cmd: '/usr/local/bin/jpegtran -copy none -optimize'
+     */
+    /*
+    'postprocessing' => array(
+        'png_filter'        => false,
+        'png_filter_cmd'    => '/usr/local/bin/optipng -q',
+
+        'png_deflate'       => false,
+        'png_deflate_cmd'   => '/usr/local/bin/pngout -q',
+
+        'jpeg_optimize'     => false,
+        'jpeg_optimize_cmd' => '/usr/local/bin/jpegtran -copy none -optimize',
+    ),
+    */
+
+
+
+    /**
+     * Create custom convolution expressions, matrix 3x3, divisor and
+     * offset.
+     *
+     * Default values.
+     *  convolution_constant: array()
+     */
+    /*
+    'convolution_constant' => array(
+        //'sharpen'       => '-1,-1,-1, -1,16,-1, -1,-1,-1, 8, 0',
+        //'sharpen-alt'   => '0,-1,0, -1,5,-1, 0,-1,0, 1, 0',
+    ),
+    */
+
+
+
+    /**
+     * Prevent leeching of images by controlling who can access them from where.
+     * Default it to allow hotlinking.
+     * Password apply when hotlinking is disallowed, use password to allow.
+     * The whitelist is an array of regexpes for allowed hostnames that can
+     * hotlink images.
+     *
+     * Default values.
+     *  allow_hotlinking:     true
+     *  hotlinking_whitelist: array()
+     */
+     /*
+    'allow_hotlinking' => false,
+    'hotlinking_whitelist' => array(
+        '#^localhost$#',
+        '#^dbwebb\.se$#',
+    ),
+    */
+
+
+
+    /**
+     * Create custom shortcuts for more advanced expressions.
+     *
+     * Default values.
+     *  shortcut: array(
+     *      'sepia' => "&f=grayscale&f0=brightness,-10&f1=contrast,-20&f2=colorize,120,60,0,0&sharpen",
+     *  )
+     */
+     /*
+    'shortcut' => array(
+        'sepia' => "&f=grayscale&f0=brightness,-10&f1=contrast,-20&f2=colorize,120,60,0,0&sharpen",
+    ),*/
+
+
+
+    /**
+     * Predefined size constants.
+     *
+     * These can be used together with &width or &height to create a constant value
+     * for a width or height where can be changed in one place.
+     * Useful when your site changes its layout or if you have a grid to fit images into.
+     *
+     * Example:
+     *  &width=w1  // results in width=613
+     *  &width=c2  // results in spanning two columns with a gutter, 30*2+10=70
+     *  &width=c24 // results in spanning whole grid 24*30+((24-1)*10)=950
+     *
+     * Default values.
+     *  size_constant: As specified by the function below.
+     */
+    /*
+    'size_constant' => function () {
+
+        // Set sizes to map constant to value, easier to use with width or height
+        $sizes = array(
+          'w1' => 613,
+          'w2' => 630,
+        );
+
+        // Add grid column width, useful for use as predefined size for width (or height).
+        $gridColumnWidth = 30;
+        $gridGutterWidth = 10;
+        $gridColumns     = 24;
+
+        for ($i = 1; $i <= $gridColumns; $i++) {
+            $sizes['c' . $i] = ($gridColumnWidth + $gridGutterWidth) * $i - $gridGutterWidth;
+        }
+
+        return $sizes;
+    },*/
+
+
+
+    /**
+     * Predefined aspect ratios.
+     *
+     * Default values.
+     *  aspect_ratio_constant: As the function below.
+     */
+    /*'aspect_ratio_constant' => function () {
+        return array(
+            '3:1'   => 3/1,
+            '3:2'   => 3/2,
+            '4:3'   => 4/3,
+            '8:5'   => 8/5,
+            '16:10' => 16/10,
+            '16:9'  => 16/9,
+            'golden' => 1.618,
+        );
+    },*/
+);
+
diff --git a/docs/api/files/webroot%2Fimg_header.php.txt b/docs/api/files/webroot%2Fimg_header.php.txt
new file mode 100644
index 0000000..4409431
--- /dev/null
+++ b/docs/api/files/webroot%2Fimg_header.php.txt
@@ -0,0 +1,36 @@
+ 'production',               // 'production', 'development', 'strict'
+    //'image_path'   =>  __DIR__ . '/img/',
+    //'cache_path'   =>  __DIR__ . '/../cache/',
+    //'alias_path'   =>  __DIR__ . '/img/alias/',
+    //'remote_allow' => true,
+    //'password'     => false,                      // "secret-password",
+
+);
+
diff --git a/docs/api/files/webroot%2Fimgd.php.txt b/docs/api/files/webroot%2Fimgd.php.txt
new file mode 100644
index 0000000..4489a24
--- /dev/null
+++ b/docs/api/files/webroot%2Fimgd.php.txt
@@ -0,0 +1,3919 @@
+ 'development',               // 'production', 'development', 'strict'
+    //'image_path'   =>  __DIR__ . '/img/',
+    //'cache_path'   =>  __DIR__ . '/../cache/',
+    //'alias_path'   =>  __DIR__ . '/img/alias/',
+    //'remote_allow' => true,
+    //'password'     => false,                      // "secret-password",
+
+);
+
+
+
+/**
+ * Get a image from a remote server using HTTP GET and If-Modified-Since.
+ *
+ */
+class CHttpGet
+{
+    private $request  = array();
+    private $response = array();
+
+
+
+    /**
+    * Constructor
+    *
+    */
+    public function __construct()
+    {
+        $this->request['header'] = array();
+    }
+
+
+
+    /**
+     * Set the url for the request.
+     *
+     * @param string $url
+     *
+     * @return $this
+     */
+    public function setUrl($url)
+    {
+        $this->request['url'] = $url;
+        return $this;
+    }
+
+
+
+    /**
+     * Set custom header field for the request.
+     *
+     * @param string $field
+     * @param string $value
+     *
+     * @return $this
+     */
+    public function setHeader($field, $value)
+    {
+        $this->request['header'][] = "$field: $value";
+        return $this;
+    }
+
+
+
+    /**
+     * Set header fields for the request.
+     *
+     * @param string $field
+     * @param string $value
+     *
+     * @return $this
+     */
+    public function parseHeader()
+    {
+        $header = explode("\r\n", rtrim($this->response['headerRaw'], "\r\n"));
+        $output = array();
+
+        if ('HTTP' === substr($header[0], 0, 4)) {
+            list($output['version'], $output['status']) = explode(' ', $header[0]);
+            unset($header[0]);
+        }
+
+        foreach ($header as $entry) {
+            $pos = strpos($entry, ':');
+            $output[trim(substr($entry, 0, $pos))] = trim(substr($entry, $pos + 1));
+        }
+
+        $this->response['header'] = $output;
+        return $this;
+    }
+
+
+
+    /**
+     * Perform the request.
+     *
+     * @param boolean $debug set to true to dump headers.
+     *
+     * @return boolean
+     */
+    public function doGet($debug = false)
+    {
+        $options = array(
+            CURLOPT_URL             => $this->request['url'],
+            CURLOPT_HEADER          => 1,
+            CURLOPT_HTTPHEADER      => $this->request['header'],
+            CURLOPT_AUTOREFERER     => true,
+            CURLOPT_RETURNTRANSFER  => true,
+            CURLINFO_HEADER_OUT     => $debug,
+            CURLOPT_CONNECTTIMEOUT  => 5,
+            CURLOPT_TIMEOUT         => 5,
+        );
+
+        $ch = curl_init();
+        curl_setopt_array($ch, $options);
+        $response = curl_exec($ch);
+
+        if (!$response) {
+            return false;
+        }
+
+        $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
+        $this->response['headerRaw'] = substr($response, 0, $headerSize);
+        $this->response['body']      = substr($response, $headerSize);
+
+        $this->parseHeader();
+
+        if ($debug) {
+            $info = curl_getinfo($ch);
+            echo "Request header
", var_dump($info['request_header']), "
"; + echo "Response header (raw)
", var_dump($this->response['headerRaw']), "
"; + echo "Response header (parsed)
", var_dump($this->response['header']), "
"; + } + + curl_close($ch); + return true; + } + + + + /** + * Get HTTP code of response. + * + * @return integer as HTTP status code or null if not available. + */ + public function getStatus() + { + return isset($this->response['header']['status']) + ? (int) $this->response['header']['status'] + : null; + } + + + + /** + * Get file modification time of response. + * + * @return int as timestamp. + */ + public function getLastModified() + { + return isset($this->response['header']['Last-Modified']) + ? strtotime($this->response['header']['Last-Modified']) + : null; + } + + + + /** + * Get content type. + * + * @return string as the content type or null if not existing or invalid. + */ + public function getContentType() + { + $type = isset($this->response['header']['Content-Type']) + ? $this->response['header']['Content-Type'] + : null; + + return preg_match('#[a-z]+/[a-z]+#', $type) + ? $type + : null; + } + + + + /** + * Get file modification time of response. + * + * @param mixed $default as default value (int seconds) if date is + * missing in response header. + * + * @return int as timestamp or $default if Date is missing in + * response header. + */ + public function getDate($default = false) + { + return isset($this->response['header']['Date']) + ? strtotime($this->response['header']['Date']) + : $default; + } + + + + /** + * Get max age of cachable item. + * + * @param mixed $default as default value if date is missing in response + * header. + * + * @return int as timestamp or false if not available. + */ + public function getMaxAge($default = false) + { + $cacheControl = isset($this->response['header']['Cache-Control']) + ? $this->response['header']['Cache-Control'] + : null; + + $maxAge = null; + if ($cacheControl) { + // max-age=2592000 + $part = explode('=', $cacheControl); + $maxAge = ($part[0] == "max-age") + ? (int) $part[1] + : null; + } + + if ($maxAge) { + return $maxAge; + } + + $expire = isset($this->response['header']['Expires']) + ? strtotime($this->response['header']['Expires']) + : null; + + return $expire ? $expire : $default; + } + + + + /** + * Get body of response. + * + * @return string as body. + */ + public function getBody() + { + return $this->response['body']; + } +} + + + +/** + * Get a image from a remote server using HTTP GET and If-Modified-Since. + * + */ +class CRemoteImage +{ + /** + * Path to cache files. + */ + private $saveFolder = null; + + + + /** + * Use cache or not. + */ + private $useCache = true; + + + + /** + * HTTP object to aid in download file. + */ + private $http; + + + + /** + * Status of the HTTP request. + */ + private $status; + + + + /** + * Defalt age for cached items 60*60*24*7. + */ + private $defaultMaxAge = 604800; + + + + /** + * Url of downloaded item. + */ + private $url; + + + + /** + * Base name of cache file for downloaded item. + */ + private $fileName; + + + + /** + * Filename for json-file with details of cached item. + */ + private $fileJson; + + + + /** + * Filename for image-file. + */ + private $fileImage; + + + + /** + * Cache details loaded from file. + */ + private $cache; + + + + /** + * Constructor + * + */ + public function __construct() + { + ; + } + + + /** + * Get status of last HTTP request. + * + * @return int as status + */ + public function getStatus() + { + return $this->status; + } + + + + /** + * Get JSON details for cache item. + * + * @return array with json details on cache. + */ + public function getDetails() + { + return $this->cache; + } + + + + /** + * Set the path to the cache directory. + * + * @param boolean $use true to use the cache and false to ignore cache. + * + * @return $this + */ + public function setCache($path) + { + $this->saveFolder = $path; + return $this; + } + + + + /** + * Check if cache is writable or throw exception. + * + * @return $this + * + * @throws Exception if cahce folder is not writable. + */ + public function isCacheWritable() + { + if (!is_writable($this->saveFolder)) { + throw new Exception("Cache folder is not writable for downloaded files."); + } + return $this; + } + + + + /** + * Decide if the cache should be used or not before trying to download + * a remote file. + * + * @param boolean $use true to use the cache and false to ignore cache. + * + * @return $this + */ + public function useCache($use = true) + { + $this->useCache = $use; + return $this; + } + + + + /** + * Translate a content type to a file extension. + * + * @param string $type a valid content type. + * + * @return string as file extension or false if no match. + */ + function contentTypeToFileExtension($type) { + $extension = array( + 'image/jpeg' => 'jpg', + 'image/png' => 'png', + 'image/gif' => 'gif', + ); + + return isset($extension[$type]) + ? $extension[$type] + : false; + } + + + + /** + * Set header fields. + * + * @return $this + */ + function setHeaderFields() { + $this->http->setHeader("User-Agent", "CImage/0.6 (PHP/". phpversion() . " cURL)"); + $this->http->setHeader("Accept", "image/jpeg,image/png,image/gif"); + + if ($this->useCache) { + $this->http->setHeader("Cache-Control", "max-age=0"); + } else { + $this->http->setHeader("Cache-Control", "no-cache"); + $this->http->setHeader("Pragma", "no-cache"); + } + } + + + + /** + * Save downloaded resource to cache. + * + * @return string as path to saved file or false if not saved. + */ + function save() { + + $this->cache = array(); + $date = $this->http->getDate(time()); + $maxAge = $this->http->getMaxAge($this->defaultMaxAge); + $lastModified = $this->http->getLastModified(); + $type = $this->http->getContentType(); + $extension = $this->contentTypeToFileExtension($type); + + $this->cache['Date'] = gmdate("D, d M Y H:i:s T", $date); + $this->cache['Max-Age'] = $maxAge; + $this->cache['Content-Type'] = $type; + $this->cache['File-Extension'] = $extension; + + if ($lastModified) { + $this->cache['Last-Modified'] = gmdate("D, d M Y H:i:s T", $lastModified); + } + + if ($extension) { + + $this->fileImage = $this->fileName . "." . $extension; + + // Save only if body is a valid image + $body = $this->http->getBody(); + $img = imagecreatefromstring($body); + + if ($img !== false) { + file_put_contents($this->fileImage, $body); + file_put_contents($this->fileJson, json_encode($this->cache)); + return $this->fileImage; + } + } + + return false; + } + + + + /** + * Got a 304 and updates cache with new age. + * + * @return string as path to cached file. + */ + function updateCacheDetails() { + + $date = $this->http->getDate(time()); + $maxAge = $this->http->getMaxAge($this->defaultMaxAge); + $lastModified = $this->http->getLastModified(); + + $this->cache['Date'] = gmdate("D, d M Y H:i:s T", $date); + $this->cache['Max-Age'] = $maxAge; + + if ($lastModified) { + $this->cache['Last-Modified'] = gmdate("D, d M Y H:i:s T", $lastModified); + } + + file_put_contents($this->fileJson, json_encode($this->cache)); + return $this->fileImage; + } + + + + /** + * Download a remote file and keep a cache of downloaded files. + * + * @param string $url a remote url. + * + * @return string as path to downloaded file or false if failed. + */ + function download($url) { + + $this->http = new CHttpGet(); + $this->url = $url; + + // First check if the cache is valid and can be used + $this->loadCacheDetails(); + + if ($this->useCache) { + $src = $this->getCachedSource(); + if ($src) { + $this->status = 1; + return $src; + } + } + + // Do a HTTP request to download item + $this->setHeaderFields(); + $this->http->setUrl($this->url); + $this->http->doGet(); + + $this->status = $this->http->getStatus(); + if ($this->status === 200) { + $this->isCacheWritable(); + return $this->save(); + } else if ($this->status === 304) { + $this->isCacheWritable(); + return $this->updateCacheDetails(); + } + + return false; + } + + + + /** + * Get the path to the cached image file if the cache is valid. + * + * @return $this + */ + public function loadCacheDetails() + { + $cacheFile = str_replace(array("/", ":", "#", ".", "?"), "-", $this->url); + $this->fileName = $this->saveFolder . $cacheFile; + $this->fileJson = $this->fileName . ".json"; + if (is_readable($this->fileJson)) { + $this->cache = json_decode(file_get_contents($this->fileJson), true); + } + } + + + + /** + * Get the path to the cached image file if the cache is valid. + * + * @return string as the path ot the image file or false if no cache. + */ + public function getCachedSource() + { + $this->fileImage = $this->fileName . "." . $this->cache['File-Extension']; + $imageExists = is_readable($this->fileImage); + + // Is cache valid? + $date = strtotime($this->cache['Date']); + $maxAge = $this->cache['Max-Age']; + $now = time(); + if ($imageExists && $date + $maxAge > $now) { + return $this->fileImage; + } + + // Prepare for a 304 if available + if ($imageExists && isset($this->cache['Last-Modified'])) { + $this->http->setHeader("If-Modified-Since", $this->cache['Last-Modified']); + } + + return false; + } +} + + + +/** + * Resize and crop images on the fly, store generated images in a cache. + * + * @author Mikael Roos mos@dbwebb.se + * @example http://dbwebb.se/opensource/cimage + * @link https://github.com/mosbth/cimage + */ +class CImage +{ + + /** + * Constants type of PNG image + */ + const PNG_GREYSCALE = 0; + const PNG_RGB = 2; + const PNG_RGB_PALETTE = 3; + const PNG_GREYSCALE_ALPHA = 4; + const PNG_RGB_ALPHA = 6; + + + + /** + * Constant for default image quality when not set + */ + const JPEG_QUALITY_DEFAULT = 60; + + + + /** + * Quality level for JPEG images. + */ + private $quality; + + + + /** + * Is the quality level set from external use (true) or is it default (false)? + */ + private $useQuality = false; + + + + /** + * Constant for default image quality when not set + */ + const PNG_COMPRESSION_DEFAULT = -1; + + + + /** + * Compression level for PNG images. + */ + private $compress; + + + + /** + * Is the compress level set from external use (true) or is it default (false)? + */ + private $useCompress = false; + + + + + /** + * Default background color, red, green, blue, alpha. + * + * @todo remake when upgrading to PHP 5.5 + */ + /* + const BACKGROUND_COLOR = array( + 'red' => 0, + 'green' => 0, + 'blue' => 0, + 'alpha' => null, + );*/ + + + + /** + * Default background color to use. + * + * @todo remake when upgrading to PHP 5.5 + */ + //private $bgColorDefault = self::BACKGROUND_COLOR; + private $bgColorDefault = array( + 'red' => 0, + 'green' => 0, + 'blue' => 0, + 'alpha' => null, + ); + + + /** + * Background color to use, specified as part of options. + */ + private $bgColor; + + + + /** + * Where to save the target file. + */ + private $saveFolder; + + + + /** + * The working image object. + */ + private $image; + + + + /** + * The root folder of images (only used in constructor to create $pathToImage?). + */ + private $imageFolder; + + + + /** + * Image filename, may include subdirectory, relative from $imageFolder + */ + private $imageSrc; + + + + /** + * Actual path to the image, $imageFolder . '/' . $imageSrc + */ + private $pathToImage; + + + + /** + * Original file extension + */ + private $fileExtension; + + + + /** + * File extension to use when saving image. + */ + private $extension; + + + + /** + * Output format, supports null (image) or json. + */ + private $outputFormat = null; + + + + /** + * Verbose mode to print out a trace and display the created image + */ + private $verbose = false; + + + + /** + * Keep a log/trace on what happens + */ + private $log = array(); + + + + /** + * Handle image as palette image + */ + private $palette; + + + + /** + * Target filename, with path, to save resulting image in. + */ + private $cacheFileName; + + + + /** + * Set a format to save image as, or null to use original format. + */ + private $saveAs; + + + /** + * Path to command for filter optimize, for example optipng or null. + */ + private $pngFilter; + + + + /** + * Path to command for deflate optimize, for example pngout or null. + */ + private $pngDeflate; + + + + /** + * Path to command to optimize jpeg images, for example jpegtran or null. + */ + private $jpegOptimize; + + + /** + * Image dimensions, calculated from loaded image. + */ + private $width; // Calculated from source image + private $height; // Calculated from source image + + + /** + * New image dimensions, incoming as argument or calculated. + */ + private $newWidth; + private $newWidthOrig; // Save original value + private $newHeight; + private $newHeightOrig; // Save original value + + + /** + * Change target height & width when different dpr, dpr 2 means double image dimensions. + */ + private $dpr = 1; + + + /** + * Always upscale images, even if they are smaller than target image. + */ + const UPSCALE_DEFAULT = true; + private $upscale = self::UPSCALE_DEFAULT; + + + + /** + * Array with details on how to crop, incoming as argument and calculated. + */ + public $crop; + public $cropOrig; // Save original value + + + /** + * String with details on how to do image convolution. String + * should map a key in the $convolvs array or be a string of + * 11 float values separated by comma. The first nine builds + * up the matrix, then divisor and last offset. + */ + private $convolve; + + + /** + * Custom convolution expressions, matrix 3x3, divisor and offset. + */ + private $convolves = array( + 'lighten' => '0,0,0, 0,12,0, 0,0,0, 9, 0', + 'darken' => '0,0,0, 0,6,0, 0,0,0, 9, 0', + 'sharpen' => '-1,-1,-1, -1,16,-1, -1,-1,-1, 8, 0', + 'sharpen-alt' => '0,-1,0, -1,5,-1, 0,-1,0, 1, 0', + 'emboss' => '1,1,-1, 1,3,-1, 1,-1,-1, 3, 0', + 'emboss-alt' => '-2,-1,0, -1,1,1, 0,1,2, 1, 0', + 'blur' => '1,1,1, 1,15,1, 1,1,1, 23, 0', + 'gblur' => '1,2,1, 2,4,2, 1,2,1, 16, 0', + 'edge' => '-1,-1,-1, -1,8,-1, -1,-1,-1, 9, 0', + 'edge-alt' => '0,1,0, 1,-4,1, 0,1,0, 1, 0', + 'draw' => '0,-1,0, -1,5,-1, 0,-1,0, 0, 0', + 'mean' => '1,1,1, 1,1,1, 1,1,1, 9, 0', + 'motion' => '1,0,0, 0,1,0, 0,0,1, 3, 0', + ); + + + /** + * Resize strategy to fill extra area with background color. + * True or false. + */ + private $fillToFit; + + + /** + * Used with option area to set which parts of the image to use. + */ + private $offset; + + + + /** + * Calculate target dimension for image when using fill-to-fit resize strategy. + */ + private $fillWidth; + private $fillHeight; + + + + /** + * Allow remote file download, default is to disallow remote file download. + */ + private $allowRemote = false; + + + + /** + * Pattern to recognize a remote file. + */ + //private $remotePattern = '#^[http|https]://#'; + private $remotePattern = '#^https?://#'; + + + + /** + * Use the cache if true, set to false to ignore the cached file. + */ + private $useCache = true; + + + /** + * Properties, the class is mutable and the method setOptions() + * decides (partly) what properties are created. + * + * @todo Clean up these and check if and how they are used + */ + + public $keepRatio; + public $cropToFit; + private $cropWidth; + private $cropHeight; + public $crop_x; + public $crop_y; + public $filters; + private $type; // Calculated from source image + private $attr; // Calculated from source image + private $useOriginal; // Use original image if possible + + + + + /** + * Constructor, can take arguments to init the object. + * + * @param string $imageSrc filename which may contain subdirectory. + * @param string $imageFolder path to root folder for images. + * @param string $saveFolder path to folder where to save the new file or null to skip saving. + * @param string $saveName name of target file when saveing. + */ + public function __construct($imageSrc = null, $imageFolder = null, $saveFolder = null, $saveName = null) + { + $this->setSource($imageSrc, $imageFolder); + $this->setTarget($saveFolder, $saveName); + } + + + + /** + * Set verbose mode. + * + * @param boolean $mode true or false to enable and disable verbose mode, + * default is true. + * + * @return $this + */ + public function setVerbose($mode = true) + { + $this->verbose = $mode; + return $this; + } + + + + /** + * Set save folder, base folder for saving cache files. + * + * @todo clean up how $this->saveFolder is used in other methods. + * + * @param string $path where to store cached files. + * + * @return $this + */ + public function setSaveFolder($path) + { + $this->saveFolder = $path; + return $this; + } + + + + /** + * Use cache or not. + * + * @todo clean up how $this->noCache is used in other methods. + * + * @param string $use true or false to use cache. + * + * @return $this + */ + public function useCache($use = true) + { + $this->useCache = $use; + return $this; + } + + + + /** + * Allow or disallow remote image download. + * + * @param boolean $allow true or false to enable and disable. + * @param string $pattern to use to detect if its a remote file. + * + * @return $this + */ + public function setRemoteDownload($allow, $pattern = null) + { + $this->allowRemote = $allow; + $this->remotePattern = $pattern ? $pattern : $this->remotePattern; + + $this->log("Set remote download to: " + . ($this->allowRemote ? "true" : "false") + . " using pattern " + . $this->remotePattern); + + return $this; + } + + + + /** + * Check if the image resource is a remote file or not. + * + * @param string $src check if src is remote. + * + * @return boolean true if $src is a remote file, else false. + */ + public function isRemoteSource($src) + { + $remote = preg_match($this->remotePattern, $src); + $this->log("Detected remote image: " . ($remote ? "true" : "false")); + return $remote; + } + + + + /** + * Check if file extension is valid as a file extension. + * + * @param string $extension of image file. + * + * @return $this + */ + private function checkFileExtension($extension) + { + $valid = array('jpg', 'jpeg', 'png', 'gif'); + + in_array(strtolower($extension), $valid) + or $this->raiseError('Not a valid file extension.'); + + return $this; + } + + + + /** + * Download a remote image and return path to its local copy. + * + * @param string $src remote path to image. + * + * @return string as path to downloaded remote source. + */ + public function downloadRemoteSource($src) + { + $remote = new CRemoteImage(); + $cache = $this->saveFolder . "/remote/"; + + if (!is_dir($cache)) { + if (!is_writable($this->saveFolder)) { + throw new Exception("Can not create remote cache, cachefolder not writable."); + } + mkdir($cache); + $this->log("The remote cache does not exists, creating it."); + } + + if (!is_writable($cache)) { + $this->log("The remote cache is not writable."); + } + + $remote->setCache($cache); + $remote->useCache($this->useCache); + $src = $remote->download($src); + + $this->log("Remote HTTP status: " . $remote->getStatus()); + $this->log("Remote item has local cached file: $src"); + $this->log("Remote details on cache:" . print_r($remote->getDetails(), true)); + + return $src; + } + + + + /** + * Set src file. + * + * @param string $src of image. + * @param string $dir as base directory where images are. + * + * @return $this + */ + public function setSource($src, $dir = null) + { + if (!isset($src)) { + return $this; + } + + if ($this->allowRemote && $this->isRemoteSource($src)) { + $src = $this->downloadRemoteSource($src); + $dir = null; + } + + if (!isset($dir)) { + $dir = dirname($src); + $src = basename($src); + } + + $this->imageSrc = ltrim($src, '/'); + $this->imageFolder = rtrim($dir, '/'); + $this->pathToImage = $this->imageFolder . '/' . $this->imageSrc; + $this->fileExtension = strtolower(pathinfo($this->pathToImage, PATHINFO_EXTENSION)); + //$this->extension = $this->fileExtension; + + $this->checkFileExtension($this->fileExtension); + + return $this; + } + + + + /** + * Set target file. + * + * @param string $src of target image. + * @param string $dir as base directory where images are stored. + * + * @return $this + */ + public function setTarget($src = null, $dir = null) + { + if (!(isset($src) && isset($dir))) { + return $this; + } + + $this->saveFolder = $dir; + $this->cacheFileName = $dir . '/' . $src; + + /* Allow readonly cache + is_writable($this->saveFolder) + or $this->raiseError('Target directory is not writable.'); + */ + + // Sanitize filename + $this->cacheFileName = preg_replace('/^a-zA-Z0-9\.-_/', '', $this->cacheFileName); + $this->log("The cache file name is: " . $this->cacheFileName); + + return $this; + } + + + + /** + * Set options to use when processing image. + * + * @param array $args used when processing image. + * + * @return $this + */ + public function setOptions($args) + { + $this->log("Set new options for processing image."); + + $defaults = array( + // Options for calculate dimensions + 'newWidth' => null, + 'newHeight' => null, + 'aspectRatio' => null, + 'keepRatio' => true, + 'cropToFit' => false, + 'fillToFit' => null, + 'crop' => null, //array('width'=>null, 'height'=>null, 'start_x'=>0, 'start_y'=>0), + 'area' => null, //'0,0,0,0', + 'upscale' => self::UPSCALE_DEFAULT, + + // Options for caching or using original + 'useCache' => true, + 'useOriginal' => true, + + // Pre-processing, before resizing is done + 'scale' => null, + 'rotateBefore' => null, + 'autoRotate' => false, + + // General options + 'bgColor' => null, + + // Post-processing, after resizing is done + 'palette' => null, + 'filters' => null, + 'sharpen' => null, + 'emboss' => null, + 'blur' => null, + 'convolve' => null, + 'rotateAfter' => null, + + // Output format + 'outputFormat' => null, + 'dpr' => 1, + + // Options for saving + //'quality' => null, + //'compress' => null, + //'saveAs' => null, + ); + + // Convert crop settings from string to array + if (isset($args['crop']) && !is_array($args['crop'])) { + $pices = explode(',', $args['crop']); + $args['crop'] = array( + 'width' => $pices[0], + 'height' => $pices[1], + 'start_x' => $pices[2], + 'start_y' => $pices[3], + ); + } + + // Convert area settings from string to array + if (isset($args['area']) && !is_array($args['area'])) { + $pices = explode(',', $args['area']); + $args['area'] = array( + 'top' => $pices[0], + 'right' => $pices[1], + 'bottom' => $pices[2], + 'left' => $pices[3], + ); + } + + // Convert filter settings from array of string to array of array + if (isset($args['filters']) && is_array($args['filters'])) { + foreach ($args['filters'] as $key => $filterStr) { + $parts = explode(',', $filterStr); + $filter = $this->mapFilter($parts[0]); + $filter['str'] = $filterStr; + for ($i=1; $i<=$filter['argc']; $i++) { + if (isset($parts[$i])) { + $filter["arg{$i}"] = $parts[$i]; + } else { + throw new Exception( + 'Missing arg to filter, review how many arguments are needed at + http://php.net/manual/en/function.imagefilter.php' + ); + } + } + $args['filters'][$key] = $filter; + } + } + + // Merge default arguments with incoming and set properties. + //$args = array_merge_recursive($defaults, $args); + $args = array_merge($defaults, $args); + foreach ($defaults as $key => $val) { + $this->{$key} = $args[$key]; + } + + if ($this->bgColor) { + $this->setDefaultBackgroundColor($this->bgColor); + } + + // Save original values to enable re-calculating + $this->newWidthOrig = $this->newWidth; + $this->newHeightOrig = $this->newHeight; + $this->cropOrig = $this->crop; + + return $this; + } + + + + /** + * Map filter name to PHP filter and id. + * + * @param string $name the name of the filter. + * + * @return array with filter settings + * @throws Exception + */ + private function mapFilter($name) + { + $map = array( + 'negate' => array('id'=>0, 'argc'=>0, 'type'=>IMG_FILTER_NEGATE), + 'grayscale' => array('id'=>1, 'argc'=>0, 'type'=>IMG_FILTER_GRAYSCALE), + 'brightness' => array('id'=>2, 'argc'=>1, 'type'=>IMG_FILTER_BRIGHTNESS), + 'contrast' => array('id'=>3, 'argc'=>1, 'type'=>IMG_FILTER_CONTRAST), + 'colorize' => array('id'=>4, 'argc'=>4, 'type'=>IMG_FILTER_COLORIZE), + 'edgedetect' => array('id'=>5, 'argc'=>0, 'type'=>IMG_FILTER_EDGEDETECT), + 'emboss' => array('id'=>6, 'argc'=>0, 'type'=>IMG_FILTER_EMBOSS), + 'gaussian_blur' => array('id'=>7, 'argc'=>0, 'type'=>IMG_FILTER_GAUSSIAN_BLUR), + 'selective_blur' => array('id'=>8, 'argc'=>0, 'type'=>IMG_FILTER_SELECTIVE_BLUR), + 'mean_removal' => array('id'=>9, 'argc'=>0, 'type'=>IMG_FILTER_MEAN_REMOVAL), + 'smooth' => array('id'=>10, 'argc'=>1, 'type'=>IMG_FILTER_SMOOTH), + 'pixelate' => array('id'=>11, 'argc'=>2, 'type'=>IMG_FILTER_PIXELATE), + ); + + if (isset($map[$name])) { + return $map[$name]; + } else { + throw new Exception('No such filter.'); + } + } + + + + /** + * Load image details from original image file. + * + * @param string $file the file to load or null to use $this->pathToImage. + * + * @return $this + * @throws Exception + */ + public function loadImageDetails($file = null) + { + $file = $file ? $file : $this->pathToImage; + + is_readable($file) + or $this->raiseError('Image file does not exist.'); + + // Get details on image + $info = list($this->width, $this->height, $this->type, $this->attr) = getimagesize($file); + !empty($info) or $this->raiseError("The file doesn't seem to be an image."); + + if ($this->verbose) { + $this->log("Image file: {$file}"); + $this->log("Image width x height (type): {$this->width} x {$this->height} ({$this->type})."); + $this->log("Image filesize: " . filesize($file) . " bytes."); + } + + return $this; + } + + + + /** + * Init new width and height and do some sanity checks on constraints, before any + * processing can be done. + * + * @return $this + * @throws Exception + */ + public function initDimensions() + { + $this->log("Init dimension (before) newWidth x newHeight is {$this->newWidth} x {$this->newHeight}."); + + // width as % + if ($this->newWidth[strlen($this->newWidth)-1] == '%') { + $this->newWidth = $this->width * substr($this->newWidth, 0, -1) / 100; + $this->log("Setting new width based on % to {$this->newWidth}"); + } + + // height as % + if ($this->newHeight[strlen($this->newHeight)-1] == '%') { + $this->newHeight = $this->height * substr($this->newHeight, 0, -1) / 100; + $this->log("Setting new height based on % to {$this->newHeight}"); + } + + is_null($this->aspectRatio) or is_numeric($this->aspectRatio) or $this->raiseError('Aspect ratio out of range'); + + // width & height from aspect ratio + if ($this->aspectRatio && is_null($this->newWidth) && is_null($this->newHeight)) { + if ($this->aspectRatio >= 1) { + $this->newWidth = $this->width; + $this->newHeight = $this->width / $this->aspectRatio; + $this->log("Setting new width & height based on width & aspect ratio (>=1) to (w x h) {$this->newWidth} x {$this->newHeight}"); + + } else { + $this->newHeight = $this->height; + $this->newWidth = $this->height * $this->aspectRatio; + $this->log("Setting new width & height based on width & aspect ratio (<1) to (w x h) {$this->newWidth} x {$this->newHeight}"); + } + + } elseif ($this->aspectRatio && is_null($this->newWidth)) { + $this->newWidth = $this->newHeight * $this->aspectRatio; + $this->log("Setting new width based on aspect ratio to {$this->newWidth}"); + + } elseif ($this->aspectRatio && is_null($this->newHeight)) { + $this->newHeight = $this->newWidth / $this->aspectRatio; + $this->log("Setting new height based on aspect ratio to {$this->newHeight}"); + } + + // Change width & height based on dpr + if ($this->dpr != 1) { + if (!is_null($this->newWidth)) { + $this->newWidth = round($this->newWidth * $this->dpr); + $this->log("Setting new width based on dpr={$this->dpr} - w={$this->newWidth}"); + } + if (!is_null($this->newHeight)) { + $this->newHeight = round($this->newHeight * $this->dpr); + $this->log("Setting new height based on dpr={$this->dpr} - h={$this->newHeight}"); + } + } + + // Check values to be within domain + is_null($this->newWidth) + or is_numeric($this->newWidth) + or $this->raiseError('Width not numeric'); + + is_null($this->newHeight) + or is_numeric($this->newHeight) + or $this->raiseError('Height not numeric'); + + $this->log("Init dimension (after) newWidth x newHeight is {$this->newWidth} x {$this->newHeight}."); + + return $this; + } + + + + /** + * Calculate new width and height of image, based on settings. + * + * @return $this + */ + public function calculateNewWidthAndHeight() + { + // Crop, use cropped width and height as base for calulations + $this->log("Calculate new width and height."); + $this->log("Original width x height is {$this->width} x {$this->height}."); + $this->log("Target dimension (before calculating) newWidth x newHeight is {$this->newWidth} x {$this->newHeight}."); + + // Check if there is an area to crop off + if (isset($this->area)) { + $this->offset['top'] = round($this->area['top'] / 100 * $this->height); + $this->offset['right'] = round($this->area['right'] / 100 * $this->width); + $this->offset['bottom'] = round($this->area['bottom'] / 100 * $this->height); + $this->offset['left'] = round($this->area['left'] / 100 * $this->width); + $this->offset['width'] = $this->width - $this->offset['left'] - $this->offset['right']; + $this->offset['height'] = $this->height - $this->offset['top'] - $this->offset['bottom']; + $this->width = $this->offset['width']; + $this->height = $this->offset['height']; + $this->log("The offset for the area to use is top {$this->area['top']}%, right {$this->area['right']}%, bottom {$this->area['bottom']}%, left {$this->area['left']}%."); + $this->log("The offset for the area to use is top {$this->offset['top']}px, right {$this->offset['right']}px, bottom {$this->offset['bottom']}px, left {$this->offset['left']}px, width {$this->offset['width']}px, height {$this->offset['height']}px."); + } + + $width = $this->width; + $height = $this->height; + + // Check if crop is set + if ($this->crop) { + $width = $this->crop['width'] = $this->crop['width'] <= 0 ? $this->width + $this->crop['width'] : $this->crop['width']; + $height = $this->crop['height'] = $this->crop['height'] <= 0 ? $this->height + $this->crop['height'] : $this->crop['height']; + + if ($this->crop['start_x'] == 'left') { + $this->crop['start_x'] = 0; + } elseif ($this->crop['start_x'] == 'right') { + $this->crop['start_x'] = $this->width - $width; + } elseif ($this->crop['start_x'] == 'center') { + $this->crop['start_x'] = round($this->width / 2) - round($width / 2); + } + + if ($this->crop['start_y'] == 'top') { + $this->crop['start_y'] = 0; + } elseif ($this->crop['start_y'] == 'bottom') { + $this->crop['start_y'] = $this->height - $height; + } elseif ($this->crop['start_y'] == 'center') { + $this->crop['start_y'] = round($this->height / 2) - round($height / 2); + } + + $this->log("Crop area is width {$width}px, height {$height}px, start_x {$this->crop['start_x']}px, start_y {$this->crop['start_y']}px."); + } + + // Calculate new width and height if keeping aspect-ratio. + if ($this->keepRatio) { + + $this->log("Keep aspect ratio."); + + // Crop-to-fit and both new width and height are set. + if (($this->cropToFit || $this->fillToFit) && isset($this->newWidth) && isset($this->newHeight)) { + + // Use newWidth and newHeigh as width/height, image should fit in box. + $this->log("Use newWidth and newHeigh as width/height, image should fit in box."); + + } elseif (isset($this->newWidth) && isset($this->newHeight)) { + + // Both new width and height are set. + // Use newWidth and newHeigh as max width/height, image should not be larger. + $ratioWidth = $width / $this->newWidth; + $ratioHeight = $height / $this->newHeight; + $ratio = ($ratioWidth > $ratioHeight) ? $ratioWidth : $ratioHeight; + $this->newWidth = round($width / $ratio); + $this->newHeight = round($height / $ratio); + $this->log("New width and height was set."); + + } elseif (isset($this->newWidth)) { + + // Use new width as max-width + $factor = (float)$this->newWidth / (float)$width; + $this->newHeight = round($factor * $height); + $this->log("New width was set."); + + } elseif (isset($this->newHeight)) { + + // Use new height as max-hight + $factor = (float)$this->newHeight / (float)$height; + $this->newWidth = round($factor * $width); + $this->log("New height was set."); + + } + + // Get image dimensions for pre-resize image. + if ($this->cropToFit || $this->fillToFit) { + + // Get relations of original & target image + $ratioWidth = $width / $this->newWidth; + $ratioHeight = $height / $this->newHeight; + + if ($this->cropToFit) { + + // Use newWidth and newHeigh as defined width/height, + // image should fit the area. + $this->log("Crop to fit."); + $ratio = ($ratioWidth < $ratioHeight) ? $ratioWidth : $ratioHeight; + $this->cropWidth = round($width / $ratio); + $this->cropHeight = round($height / $ratio); + $this->log("Crop width, height, ratio: $this->cropWidth x $this->cropHeight ($ratio)."); + + } else if ($this->fillToFit) { + + // Use newWidth and newHeigh as defined width/height, + // image should fit the area. + $this->log("Fill to fit."); + $ratio = ($ratioWidth < $ratioHeight) ? $ratioHeight : $ratioWidth; + $this->fillWidth = round($width / $ratio); + $this->fillHeight = round($height / $ratio); + $this->log("Fill width, height, ratio: $this->fillWidth x $this->fillHeight ($ratio)."); + } + } + } + + // Crop, ensure to set new width and height + if ($this->crop) { + $this->log("Crop."); + $this->newWidth = round(isset($this->newWidth) ? $this->newWidth : $this->crop['width']); + $this->newHeight = round(isset($this->newHeight) ? $this->newHeight : $this->crop['height']); + } + + // Fill to fit, ensure to set new width and height + /*if ($this->fillToFit) { + $this->log("FillToFit."); + $this->newWidth = round(isset($this->newWidth) ? $this->newWidth : $this->crop['width']); + $this->newHeight = round(isset($this->newHeight) ? $this->newHeight : $this->crop['height']); + }*/ + + // No new height or width is set, use existing measures. + $this->newWidth = round(isset($this->newWidth) ? $this->newWidth : $this->width); + $this->newHeight = round(isset($this->newHeight) ? $this->newHeight : $this->height); + $this->log("Calculated new width x height as {$this->newWidth} x {$this->newHeight}."); + + return $this; + } + + + + /** + * Re-calculate image dimensions when original image dimension has changed. + * + * @return $this + */ + public function reCalculateDimensions() + { + $this->log("Re-calculate image dimensions, newWidth x newHeigh was: " . $this->newWidth . " x " . $this->newHeight); + + $this->newWidth = $this->newWidthOrig; + $this->newHeight = $this->newHeightOrig; + $this->crop = $this->cropOrig; + + $this->initDimensions() + ->calculateNewWidthAndHeight(); + + return $this; + } + + + + /** + * Set extension for filename to save as. + * + * @param string $saveas extension to save image as + * + * @return $this + */ + public function setSaveAsExtension($saveAs = null) + { + if (isset($saveAs)) { + $saveAs = strtolower($saveAs); + $this->checkFileExtension($saveAs); + $this->saveAs = $saveAs; + $this->extension = $saveAs; + } + + $this->log("Prepare to save image using as: " . $this->extension); + + return $this; + } + + + + /** + * Set JPEG quality to use when saving image + * + * @param int $quality as the quality to set. + * + * @return $this + */ + public function setJpegQuality($quality = null) + { + if ($quality) { + $this->useQuality = true; + } + + $this->quality = isset($quality) + ? $quality + : self::JPEG_QUALITY_DEFAULT; + + (is_numeric($this->quality) and $this->quality > 0 and $this->quality <= 100) + or $this->raiseError('Quality not in range.'); + + $this->log("Setting JPEG quality to {$this->quality}."); + + return $this; + } + + + + /** + * Set PNG compressen algorithm to use when saving image + * + * @param int $compress as the algorithm to use. + * + * @return $this + */ + public function setPngCompression($compress = null) + { + if ($compress) { + $this->useCompress = true; + } + + $this->compress = isset($compress) + ? $compress + : self::PNG_COMPRESSION_DEFAULT; + + (is_numeric($this->compress) and $this->compress >= -1 and $this->compress <= 9) + or $this->raiseError('Quality not in range.'); + + $this->log("Setting PNG compression level to {$this->compress}."); + + return $this; + } + + + + /** + * Use original image if possible, check options which affects image processing. + * + * @param boolean $useOrig default is to use original if possible, else set to false. + * + * @return $this + */ + public function useOriginalIfPossible($useOrig = true) + { + if ($useOrig + && ($this->newWidth == $this->width) + && ($this->newHeight == $this->height) + && !$this->area + && !$this->crop + && !$this->cropToFit + && !$this->fillToFit + && !$this->filters + && !$this->sharpen + && !$this->emboss + && !$this->blur + && !$this->convolve + && !$this->palette + && !$this->useQuality + && !$this->useCompress + && !$this->saveAs + && !$this->rotateBefore + && !$this->rotateAfter + && !$this->autoRotate + && !$this->bgColor + && ($this->upscale === self::UPSCALE_DEFAULT) + ) { + $this->log("Using original image."); + $this->output($this->pathToImage); + } + + return $this; + } + + + + /** + * Generate filename to save file in cache. + * + * @param string $base as basepath for storing file. + * + * @return $this + */ + public function generateFilename($base) + { + $parts = pathinfo($this->pathToImage); + $cropToFit = $this->cropToFit ? '_cf' : null; + $fillToFit = $this->fillToFit ? '_ff' : null; + $crop_x = $this->crop_x ? "_x{$this->crop_x}" : null; + $crop_y = $this->crop_y ? "_y{$this->crop_y}" : null; + $scale = $this->scale ? "_s{$this->scale}" : null; + $bgColor = $this->bgColor ? "_bgc{$this->bgColor}" : null; + $quality = $this->quality ? "_q{$this->quality}" : null; + $compress = $this->compress ? "_co{$this->compress}" : null; + $rotateBefore = $this->rotateBefore ? "_rb{$this->rotateBefore}" : null; + $rotateAfter = $this->rotateAfter ? "_ra{$this->rotateAfter}" : null; + + $width = $this->newWidth; + $height = $this->newHeight; + + $offset = isset($this->offset) + ? '_o' . $this->offset['top'] . '-' . $this->offset['right'] . '-' . $this->offset['bottom'] . '-' . $this->offset['left'] + : null; + + $crop = $this->crop + ? '_c' . $this->crop['width'] . '-' . $this->crop['height'] . '-' . $this->crop['start_x'] . '-' . $this->crop['start_y'] + : null; + + $filters = null; + if (isset($this->filters)) { + foreach ($this->filters as $filter) { + if (is_array($filter)) { + $filters .= "_f{$filter['id']}"; + for ($i=1; $i<=$filter['argc']; $i++) { + $filters .= ":".$filter["arg{$i}"]; + } + } + } + } + + $sharpen = $this->sharpen ? 's' : null; + $emboss = $this->emboss ? 'e' : null; + $blur = $this->blur ? 'b' : null; + $palette = $this->palette ? 'p' : null; + + $autoRotate = $this->autoRotate ? 'ar' : null; + + $this->extension = isset($this->extension) + ? $this->extension + : $parts['extension']; + + $optimize = null; + if ($this->extension == 'jpeg' || $this->extension == 'jpg') { + $optimize = $this->jpegOptimize ? 'o' : null; + } elseif ($this->extension == 'png') { + $optimize .= $this->pngFilter ? 'f' : null; + $optimize .= $this->pngDeflate ? 'd' : null; + } + + $convolve = null; + if ($this->convolve) { + $convolve = '_conv' . preg_replace('/[^a-zA-Z0-9]/', '', $this->convolve); + } + + $upscale = null; + if ($this->upscale !== self::UPSCALE_DEFAULT) { + $upscale = '_nu'; + } + + $subdir = str_replace('/', '-', dirname($this->imageSrc)); + $subdir = ($subdir == '.') ? '_.' : $subdir; + $file = $subdir . '_' . $parts['filename'] . '_' . $width . '_' + . $height . $offset . $crop . $cropToFit . $fillToFit + . $crop_x . $crop_y . $upscale + . $quality . $filters . $sharpen . $emboss . $blur . $palette . $optimize + . $scale . $rotateBefore . $rotateAfter . $autoRotate . $bgColor . $convolve + . '.' . $this->extension; + + return $this->setTarget($file, $base); + } + + + + /** + * Use cached version of image, if possible. + * + * @param boolean $useCache is default true, set to false to avoid using cached object. + * + * @return $this + */ + public function useCacheIfPossible($useCache = true) + { + if ($useCache && is_readable($this->cacheFileName)) { + $fileTime = filemtime($this->pathToImage); + $cacheTime = filemtime($this->cacheFileName); + + if ($fileTime <= $cacheTime) { + if ($this->useCache) { + if ($this->verbose) { + $this->log("Use cached file."); + $this->log("Cached image filesize: " . filesize($this->cacheFileName) . " bytes."); + } + $this->output($this->cacheFileName, $this->outputFormat); + } else { + $this->log("Cache is valid but ignoring it by intention."); + } + } else { + $this->log("Original file is modified, ignoring cache."); + } + } else { + $this->log("Cachefile does not exists or ignoring it."); + } + + return $this; + } + + + + /** + * Error message when failing to load somehow corrupt image. + * + * @return void + * + */ + public function failedToLoad() + { + header("HTTP/1.0 404 Not Found"); + echo("CImage.php says 404: Fatal error when opening image.
"); + + switch ($this->fileExtension) { + case 'jpg': + case 'jpeg': + $this->image = imagecreatefromjpeg($this->pathToImage); + break; + + case 'gif': + $this->image = imagecreatefromgif($this->pathToImage); + break; + + case 'png': + $this->image = imagecreatefrompng($this->pathToImage); + break; + } + + exit(); + } + + + + /** + * Load image from disk. + * + * @param string $src of image. + * @param string $dir as base directory where images are. + * + * @return $this + * + */ + public function load($src = null, $dir = null) + { + if (isset($src)) { + $this->setSource($src, $dir); + } + + $this->log("Opening file as {$this->fileExtension}."); + + switch ($this->fileExtension) { + case 'jpg': + case 'jpeg': + $this->image = @imagecreatefromjpeg($this->pathToImage); + $this->image or $this->failedToLoad(); + break; + + case 'gif': + $this->image = @imagecreatefromgif($this->pathToImage); + $this->image or $this->failedToLoad(); + break; + + case 'png': + $this->image = @imagecreatefrompng($this->pathToImage); + $this->image or $this->failedToLoad(); + + $type = $this->getPngType(); + $hasFewColors = imagecolorstotal($this->image); + + if ($type == self::PNG_RGB_PALETTE || ($hasFewColors > 0 && $hasFewColors <= 256)) { + if ($this->verbose) { + $this->log("Handle this image as a palette image."); + } + $this->palette = true; + } + break; + + default: + $this->image = false; + throw new Exception('No support for this file extension.'); + } + + if ($this->verbose) { + $this->log("imageistruecolor() : " . (imageistruecolor($this->image) ? 'true' : 'false')); + $this->log("imagecolorstotal() : " . imagecolorstotal($this->image)); + $this->log("Number of colors in image = " . $this->colorsTotal($this->image)); + $index = imagecolortransparent($this->image); + $this->log("Detected transparent color = " . ($index > 0 ? implode(", ", imagecolorsforindex($this->image, $index)) : "NONE") . " at index = $index"); + } + + return $this; + } + + + + /** + * Get the type of PNG image. + * + * @return int as the type of the png-image + * + */ + private function getPngType() + { + $pngType = ord(file_get_contents($this->pathToImage, false, null, 25, 1)); + + switch ($pngType) { + + case self::PNG_GREYSCALE: + $this->log("PNG is type 0, Greyscale."); + break; + + case self::PNG_RGB: + $this->log("PNG is type 2, RGB"); + break; + + case self::PNG_RGB_PALETTE: + $this->log("PNG is type 3, RGB with palette"); + break; + + case self::PNG_GREYSCALE_ALPHA: + $this->Log("PNG is type 4, Greyscale with alpha channel"); + break; + + case self::PNG_RGB_ALPHA: + $this->Log("PNG is type 6, RGB with alpha channel (PNG 32-bit)"); + break; + + default: + $this->Log("PNG is UNKNOWN type, is it really a PNG image?"); + } + + return $pngType; + } + + + + /** + * Calculate number of colors in an image. + * + * @param resource $im the image. + * + * @return int + */ + private function colorsTotal($im) + { + if (imageistruecolor($im)) { + $this->log("Colors as true color."); + $h = imagesy($im); + $w = imagesx($im); + $c = array(); + for ($x=0; $x < $w; $x++) { + for ($y=0; $y < $h; $y++) { + @$c['c'.imagecolorat($im, $x, $y)]++; + } + } + return count($c); + } else { + $this->log("Colors as palette."); + return imagecolorstotal($im); + } + } + + + + /** + * Preprocess image before rezising it. + * + * @return $this + */ + public function preResize() + { + $this->log("Pre-process before resizing"); + + // Rotate image + if ($this->rotateBefore) { + $this->log("Rotating image."); + $this->rotate($this->rotateBefore, $this->bgColor) + ->reCalculateDimensions(); + } + + // Auto-rotate image + if ($this->autoRotate) { + $this->log("Auto rotating image."); + $this->rotateExif() + ->reCalculateDimensions(); + } + + // Scale the original image before starting + if (isset($this->scale)) { + $this->log("Scale by {$this->scale}%"); + $newWidth = $this->width * $this->scale / 100; + $newHeight = $this->height * $this->scale / 100; + $img = $this->CreateImageKeepTransparency($newWidth, $newHeight); + imagecopyresampled($img, $this->image, 0, 0, 0, 0, $newWidth, $newHeight, $this->width, $this->height); + $this->image = $img; + $this->width = $newWidth; + $this->height = $newHeight; + } + + return $this; + } + + + + /** + * Resize and or crop the image. + * + * @return $this + */ + public function resize() + { + + $this->log("Starting to Resize()"); + $this->log("Upscale = '$this->upscale'"); + + // Only use a specified area of the image, $this->offset is defining the area to use + if (isset($this->offset)) { + + $this->log("Offset for area to use, cropping it width={$this->offset['width']}, height={$this->offset['height']}, start_x={$this->offset['left']}, start_y={$this->offset['top']}"); + $img = $this->CreateImageKeepTransparency($this->offset['width'], $this->offset['height']); + imagecopy($img, $this->image, 0, 0, $this->offset['left'], $this->offset['top'], $this->offset['width'], $this->offset['height']); + $this->image = $img; + $this->width = $this->offset['width']; + $this->height = $this->offset['height']; + } + + if ($this->crop) { + + // Do as crop, take only part of image + $this->log("Cropping area width={$this->crop['width']}, height={$this->crop['height']}, start_x={$this->crop['start_x']}, start_y={$this->crop['start_y']}"); + $img = $this->CreateImageKeepTransparency($this->crop['width'], $this->crop['height']); + imagecopy($img, $this->image, 0, 0, $this->crop['start_x'], $this->crop['start_y'], $this->crop['width'], $this->crop['height']); + $this->image = $img; + $this->width = $this->crop['width']; + $this->height = $this->crop['height']; + } + + if (!$this->upscale) { + // Consider rewriting the no-upscale code to fit within this if-statement, + // likely to be more readable code. + // The code is more or leass equal in below crop-to-fit, fill-to-fit and stretch + } + + if ($this->cropToFit) { + + // Resize by crop to fit + $this->log("Resizing using strategy - Crop to fit"); + + if (!$this->upscale && ($this->width < $this->newWidth || $this->height < $this->newHeight)) { + $this->log("Resizing - smaller image, do not upscale."); + + $cropX = round(($this->cropWidth/2) - ($this->newWidth/2)); + $cropY = round(($this->cropHeight/2) - ($this->newHeight/2)); + + $posX = 0; + $posY = 0; + + if ($this->newWidth > $this->width) { + $posX = round(($this->newWidth - $this->width) / 2); + } + + if ($this->newHeight > $this->height) { + $posY = round(($this->newHeight - $this->height) / 2); + } + + $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); + imagecopy($imageResized, $this->image, $posX, $posY, $cropX, $cropY, $this->newWidth, $this->newHeight); + } else { + $cropX = round(($this->cropWidth/2) - ($this->newWidth/2)); + $cropY = round(($this->cropHeight/2) - ($this->newHeight/2)); + $imgPreCrop = $this->CreateImageKeepTransparency($this->cropWidth, $this->cropHeight); + $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); + imagecopyresampled($imgPreCrop, $this->image, 0, 0, 0, 0, $this->cropWidth, $this->cropHeight, $this->width, $this->height); + imagecopy($imageResized, $imgPreCrop, 0, 0, $cropX, $cropY, $this->newWidth, $this->newHeight); + } + + $this->image = $imageResized; + $this->width = $this->newWidth; + $this->height = $this->newHeight; + + } else if ($this->fillToFit) { + + // Resize by fill to fit + $this->log("Resizing using strategy - Fill to fit"); + + $posX = 0; + $posY = 0; + + $ratioOrig = $this->width / $this->height; + $ratioNew = $this->newWidth / $this->newHeight; + + // Check ratio for landscape or portrait + if ($ratioOrig < $ratioNew) { + $posX = round(($this->newWidth - $this->fillWidth) / 2); + } else { + $posY = round(($this->newHeight - $this->fillHeight) / 2); + } + + if (!$this->upscale + && ($this->width < $this->newWidth || $this->height < $this->newHeight) + ) { + + $this->log("Resizing - smaller image, do not upscale."); + $posX = round(($this->fillWidth - $this->width) / 2); + $posY = round(($this->fillHeight - $this->height) / 2); + $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); + imagecopy($imageResized, $this->image, $posX, $posY, 0, 0, $this->fillWidth, $this->fillHeight); + + } else { + $imgPreFill = $this->CreateImageKeepTransparency($this->fillWidth, $this->fillHeight); + $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); + imagecopyresampled($imgPreFill, $this->image, 0, 0, 0, 0, $this->fillWidth, $this->fillHeight, $this->width, $this->height); + imagecopy($imageResized, $imgPreFill, $posX, $posY, 0, 0, $this->fillWidth, $this->fillHeight); + } + + $this->image = $imageResized; + $this->width = $this->newWidth; + $this->height = $this->newHeight; + + } else if (!($this->newWidth == $this->width && $this->newHeight == $this->height)) { + + // Resize it + $this->log("Resizing, new height and/or width"); + + if (!$this->upscale + && ($this->width < $this->newWidth || $this->height < $this->newHeight) + ) { + $this->log("Resizing - smaller image, do not upscale."); + + if (!$this->keepRatio) { + $this->log("Resizing - stretch to fit selected."); + + $posX = 0; + $posY = 0; + $cropX = 0; + $cropY = 0; + + if ($this->newWidth > $this->width && $this->newHeight > $this->height) { + $posX = round(($this->newWidth - $this->width) / 2); + $posY = round(($this->newHeight - $this->height) / 2); + } else if ($this->newWidth > $this->width) { + $posX = round(($this->newWidth - $this->width) / 2); + $cropY = round(($this->height - $this->newHeight) / 2); + } else if ($this->newHeight > $this->height) { + $posY = round(($this->newHeight - $this->height) / 2); + $cropX = round(($this->width - $this->newWidth) / 2); + } + + //$this->log("posX=$posX, posY=$posY, cropX=$cropX, cropY=$cropY."); + $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); + imagecopy($imageResized, $this->image, $posX, $posY, $cropX, $cropY, $this->newWidth, $this->newHeight); + $this->image = $imageResized; + $this->width = $this->newWidth; + $this->height = $this->newHeight; + } + } else { + $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); + imagecopyresampled($imageResized, $this->image, 0, 0, 0, 0, $this->newWidth, $this->newHeight, $this->width, $this->height); + $this->image = $imageResized; + $this->width = $this->newWidth; + $this->height = $this->newHeight; + } + } + + return $this; + } + + + + /** + * Postprocess image after rezising image. + * + * @return $this + */ + public function postResize() + { + $this->log("Post-process after resizing"); + + // Rotate image + if ($this->rotateAfter) { + $this->log("Rotating image."); + $this->rotate($this->rotateAfter, $this->bgColor); + } + + // Apply filters + if (isset($this->filters) && is_array($this->filters)) { + + foreach ($this->filters as $filter) { + $this->log("Applying filter {$filter['type']}."); + + switch ($filter['argc']) { + + case 0: + imagefilter($this->image, $filter['type']); + break; + + case 1: + imagefilter($this->image, $filter['type'], $filter['arg1']); + break; + + case 2: + imagefilter($this->image, $filter['type'], $filter['arg1'], $filter['arg2']); + break; + + case 3: + imagefilter($this->image, $filter['type'], $filter['arg1'], $filter['arg2'], $filter['arg3']); + break; + + case 4: + imagefilter($this->image, $filter['type'], $filter['arg1'], $filter['arg2'], $filter['arg3'], $filter['arg4']); + break; + } + } + } + + // Convert to palette image + if ($this->palette) { + $this->log("Converting to palette image."); + $this->trueColorToPalette(); + } + + // Blur the image + if ($this->blur) { + $this->log("Blur."); + $this->blurImage(); + } + + // Emboss the image + if ($this->emboss) { + $this->log("Emboss."); + $this->embossImage(); + } + + // Sharpen the image + if ($this->sharpen) { + $this->log("Sharpen."); + $this->sharpenImage(); + } + + // Custom convolution + if ($this->convolve) { + //$this->log("Convolve: " . $this->convolve); + $this->imageConvolution(); + } + + return $this; + } + + + + /** + * Rotate image using angle. + * + * @param float $angle to rotate image. + * @param int $anglebgColor to fill image with if needed. + * + * @return $this + */ + public function rotate($angle, $bgColor) + { + $this->log("Rotate image " . $angle . " degrees with filler color."); + + $color = $this->getBackgroundColor(); + $this->image = imagerotate($this->image, $angle, $color); + + $this->width = imagesx($this->image); + $this->height = imagesy($this->image); + + $this->log("New image dimension width x height: " . $this->width . " x " . $this->height); + + return $this; + } + + + + /** + * Rotate image using information in EXIF. + * + * @return $this + */ + public function rotateExif() + { + if (!in_array($this->fileExtension, array('jpg', 'jpeg'))) { + $this->log("Autorotate ignored, EXIF not supported by this filetype."); + return $this; + } + + $exif = exif_read_data($this->pathToImage); + + if (!empty($exif['Orientation'])) { + switch ($exif['Orientation']) { + case 3: + $this->log("Autorotate 180."); + $this->rotate(180, $this->bgColor); + break; + + case 6: + $this->log("Autorotate -90."); + $this->rotate(-90, $this->bgColor); + break; + + case 8: + $this->log("Autorotate 90."); + $this->rotate(90, $this->bgColor); + break; + + default: + $this->log("Autorotate ignored, unknown value as orientation."); + } + } else { + $this->log("Autorotate ignored, no orientation in EXIF."); + } + + return $this; + } + + + + /** + * Convert true color image to palette image, keeping alpha. + * http://stackoverflow.com/questions/5752514/how-to-convert-png-to-8-bit-png-using-php-gd-library + * + * @return void + */ + public function trueColorToPalette() + { + $img = imagecreatetruecolor($this->width, $this->height); + $bga = imagecolorallocatealpha($img, 0, 0, 0, 127); + imagecolortransparent($img, $bga); + imagefill($img, 0, 0, $bga); + imagecopy($img, $this->image, 0, 0, 0, 0, $this->width, $this->height); + imagetruecolortopalette($img, false, 255); + imagesavealpha($img, true); + + if (imageistruecolor($this->image)) { + $this->log("Matching colors with true color image."); + imagecolormatch($this->image, $img); + } + + $this->image = $img; + } + + + + /** + * Sharpen image using image convolution. + * + * @return $this + */ + public function sharpenImage() + { + $this->imageConvolution('sharpen'); + return $this; + } + + + + /** + * Emboss image using image convolution. + * + * @return $this + */ + public function embossImage() + { + $this->imageConvolution('emboss'); + return $this; + } + + + + /** + * Blur image using image convolution. + * + * @return $this + */ + public function blurImage() + { + $this->imageConvolution('blur'); + return $this; + } + + + + /** + * Create convolve expression and return arguments for image convolution. + * + * @param string $expression constant string which evaluates to a list of + * 11 numbers separated by komma or such a list. + * + * @return array as $matrix (3x3), $divisor and $offset + */ + public function createConvolveArguments($expression) + { + // Check of matching constant + if (isset($this->convolves[$expression])) { + $expression = $this->convolves[$expression]; + } + + $part = explode(',', $expression); + $this->log("Creating convolution expressen: $expression"); + + // Expect list of 11 numbers, split by , and build up arguments + if (count($part) != 11) { + throw new Exception( + "Missmatch in argument convolve. Expected comma-separated string with + 11 float values. Got $expression." + ); + } + + array_walk($part, function ($item, $key) { + if (!is_numeric($item)) { + throw new Exception("Argument to convolve expression should be float but is not."); + } + }); + + return array( + array( + array($part[0], $part[1], $part[2]), + array($part[3], $part[4], $part[5]), + array($part[6], $part[7], $part[8]), + ), + $part[9], + $part[10], + ); + } + + + + /** + * Add custom expressions (or overwrite existing) for image convolution. + * + * @param array $options Key value array with strings to be converted + * to convolution expressions. + * + * @return $this + */ + public function addConvolveExpressions($options) + { + $this->convolves = array_merge($this->convolves, $options); + return $this; + } + + + + /** + * Image convolution. + * + * @param string $options A string with 11 float separated by comma. + * + * @return $this + */ + public function imageConvolution($options = null) + { + // Use incoming options or use $this. + $options = $options ? $options : $this->convolve; + + // Treat incoming as string, split by + + $this->log("Convolution with '$options'"); + $options = explode(":", $options); + + // Check each option if it matches constant value + foreach ($options as $option) { + list($matrix, $divisor, $offset) = $this->createConvolveArguments($option); + imageconvolution($this->image, $matrix, $divisor, $offset); + } + + return $this; + } + + + + /** + * Set default background color between 000000-FFFFFF or if using + * alpha 00000000-FFFFFF7F. + * + * @param string $color as hex value. + * + * @return $this + */ + public function setDefaultBackgroundColor($color) + { + $this->log("Setting default background color to '$color'."); + + if (!(strlen($color) == 6 || strlen($color) == 8)) { + throw new Exception( + "Background color needs a hex value of 6 or 8 + digits. 000000-FFFFFF or 00000000-FFFFFF7F. + Current value was: '$color'." + ); + } + + $red = hexdec(substr($color, 0, 2)); + $green = hexdec(substr($color, 2, 2)); + $blue = hexdec(substr($color, 4, 2)); + + $alpha = (strlen($color) == 8) + ? hexdec(substr($color, 6, 2)) + : null; + + if (($red < 0 || $red > 255) + || ($green < 0 || $green > 255) + || ($blue < 0 || $blue > 255) + || ($alpha < 0 || $alpha > 127) + ) { + throw new Exception( + "Background color out of range. Red, green blue + should be 00-FF and alpha should be 00-7F. + Current value was: '$color'." + ); + } + + $this->bgColor = strtolower($color); + $this->bgColorDefault = array( + 'red' => $red, + 'green' => $green, + 'blue' => $blue, + 'alpha' => $alpha + ); + + return $this; + } + + + + /** + * Get the background color. + * + * @param resource $img the image to work with or null if using $this->image. + * + * @return color value or null if no background color is set. + */ + private function getBackgroundColor($img = null) + { + $img = isset($img) ? $img : $this->image; + + if ($this->bgColorDefault) { + + $red = $this->bgColorDefault['red']; + $green = $this->bgColorDefault['green']; + $blue = $this->bgColorDefault['blue']; + $alpha = $this->bgColorDefault['alpha']; + + if ($alpha) { + $color = imagecolorallocatealpha($img, $red, $green, $blue, $alpha); + } else { + $color = imagecolorallocate($img, $red, $green, $blue); + } + + return $color; + + } else { + return 0; + } + } + + + + /** + * Create a image and keep transparency for png and gifs. + * + * @param int $width of the new image. + * @param int $height of the new image. + * + * @return image resource. + */ + private function createImageKeepTransparency($width, $height) + { + $this->log("Creating a new working image width={$width}px, height={$height}px."); + $img = imagecreatetruecolor($width, $height); + imagealphablending($img, false); + imagesavealpha($img, true); + + $index = imagecolortransparent($this->image); + if ($index != -1) { + + imagealphablending($img, true); + $transparent = imagecolorsforindex($this->image, $index); + $color = imagecolorallocatealpha($img, $transparent['red'], $transparent['green'], $transparent['blue'], $transparent['alpha']); + imagefill($img, 0, 0, $color); + $index = imagecolortransparent($img, $color); + $this->Log("Detected transparent color = " . implode(", ", $transparent) . " at index = $index"); + + } elseif ($this->bgColorDefault) { + + $color = $this->getBackgroundColor($img); + imagefill($img, 0, 0, $color); + $this->Log("Filling image with background color."); + } + + return $img; + } + + + + /** + * Set optimizing and post-processing options. + * + * @param array $options with config for postprocessing with external tools. + * + * @return $this + */ + public function setPostProcessingOptions($options) + { + if (isset($options['jpeg_optimize']) && $options['jpeg_optimize']) { + $this->jpegOptimizeCmd = $options['jpeg_optimize_cmd']; + } else { + $this->jpegOptimizeCmd = null; + } + + if (isset($options['png_filter']) && $options['png_filter']) { + $this->pngFilterCmd = $options['png_filter_cmd']; + } else { + $this->pngFilterCmd = null; + } + + if (isset($options['png_deflate']) && $options['png_deflate']) { + $this->pngDeflateCmd = $options['png_deflate_cmd']; + } else { + $this->pngDeflateCmd = null; + } + + return $this; + } + + + + /** + * Save image. + * + * @param string $src as target filename. + * @param string $base as base directory where to store images. + * + * @return $this or false if no folder is set. + */ + public function save($src = null, $base = null) + { + if (isset($src)) { + $this->setTarget($src, $base); + } + + is_writable($this->saveFolder) + or $this->raiseError('Target directory is not writable.'); + + switch(strtolower($this->extension)) { + + case 'jpeg': + case 'jpg': + $this->Log("Saving image as JPEG to cache using quality = {$this->quality}."); + imagejpeg($this->image, $this->cacheFileName, $this->quality); + + // Use JPEG optimize if defined + if ($this->jpegOptimizeCmd) { + if ($this->verbose) { + clearstatcache(); + $this->log("Filesize before optimize: " . filesize($this->cacheFileName) . " bytes."); + } + $res = array(); + $cmd = $this->jpegOptimizeCmd . " -outfile $this->cacheFileName $this->cacheFileName"; + exec($cmd, $res); + $this->log($cmd); + $this->log($res); + } + break; + + case 'gif': + $this->Log("Saving image as GIF to cache."); + imagegif($this->image, $this->cacheFileName); + break; + + case 'png': + $this->Log("Saving image as PNG to cache using compression = {$this->compress}."); + + // Turn off alpha blending and set alpha flag + imagealphablending($this->image, false); + imagesavealpha($this->image, true); + imagepng($this->image, $this->cacheFileName, $this->compress); + + // Use external program to filter PNG, if defined + if ($this->pngFilterCmd) { + if ($this->verbose) { + clearstatcache(); + $this->Log("Filesize before filter optimize: " . filesize($this->cacheFileName) . " bytes."); + } + $res = array(); + $cmd = $this->pngFilterCmd . " $this->cacheFileName"; + exec($cmd, $res); + $this->Log($cmd); + $this->Log($res); + } + + // Use external program to deflate PNG, if defined + if ($this->pngDeflateCmd) { + if ($this->verbose) { + clearstatcache(); + $this->Log("Filesize before deflate optimize: " . filesize($this->cacheFileName) . " bytes."); + } + $res = array(); + $cmd = $this->pngDeflateCmd . " $this->cacheFileName"; + exec($cmd, $res); + $this->Log($cmd); + $this->Log($res); + } + break; + + default: + $this->RaiseError('No support for this file extension.'); + break; + } + + if ($this->verbose) { + clearstatcache(); + $this->log("Cached image filesize: " . filesize($this->cacheFileName) . " bytes."); + $this->log("imageistruecolor() : " . (imageistruecolor($this->image) ? 'true' : 'false')); + $this->log("imagecolorstotal() : " . imagecolorstotal($this->image)); + $this->log("Number of colors in image = " . $this->ColorsTotal($this->image)); + $index = imagecolortransparent($this->image); + $this->log("Detected transparent color = " . ($index > 0 ? implode(", ", imagecolorsforindex($this->image, $index)) : "NONE") . " at index = $index"); + } + + return $this; + } + + + + /** + * Create a hard link, as an alias, to the cached file. + * + * @param string $alias where to store the link, + * filename without extension. + * + * @return $this + */ + public function linkToCacheFile($alias) + { + if ($alias === null) { + $this->log("Ignore creating alias."); + return $this; + } + + $alias = $alias . "." . $this->extension; + + if (is_readable($alias)) { + unlink($alias); + } + + $res = link($this->cacheFileName, $alias); + + if ($res) { + $this->log("Created an alias as: $alias"); + } else { + $this->log("Failed to create the alias: $alias"); + } + + return $this; + } + + + + /** + * Output image to browser using caching. + * + * @param string $file to read and output, default is to use $this->cacheFileName + * @param string $format set to json to output file as json object with details + * + * @return void + */ + public function output($file = null, $format = null) + { + if (is_null($file)) { + $file = $this->cacheFileName; + } + + if (is_null($format)) { + $format = $this->outputFormat; + } + + $this->log("Output format is: $format"); + + if (!$this->verbose && $format == 'json') { + header('Content-type: application/json'); + echo $this->json($file); + exit; + } + + $this->log("Outputting image: $file"); + + // Get image modification time + clearstatcache(); + $lastModified = filemtime($file); + $gmdate = gmdate("D, d M Y H:i:s", $lastModified); + + if (!$this->verbose) { + header('Last-Modified: ' . $gmdate . " GMT"); + } + + if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $lastModified) { + + if ($this->verbose) { + $this->log("304 not modified"); + $this->verboseOutput(); + exit; + } + + header("HTTP/1.0 304 Not Modified"); + + } else { + + if ($this->verbose) { + $this->log("Last modified: " . $gmdate . " GMT"); + $this->verboseOutput(); + exit; + } + + // Get details on image + $info = getimagesize($file); + !empty($info) or $this->raiseError("The file doesn't seem to be an image."); + $mime = $info['mime']; + + header('Content-type: ' . $mime); + readfile($file); + } + + exit; + } + + + + /** + * Create a JSON object from the image details. + * + * @param string $file the file to output. + * + * @return string json-encoded representation of the image. + */ + public function json($file = null) + { + $file = $file ? $file : $this->cacheFileName; + + $details = array(); + + clearstatcache(); + + $details['src'] = $this->imageSrc; + $lastModified = filemtime($this->pathToImage); + $details['srcGmdate'] = gmdate("D, d M Y H:i:s", $lastModified); + + $details['cache'] = basename($this->cacheFileName); + $lastModified = filemtime($this->cacheFileName); + $details['cacheGmdate'] = gmdate("D, d M Y H:i:s", $lastModified); + + $this->loadImageDetails($file); + + $details['filename'] = basename($file); + $details['width'] = $this->width; + $details['height'] = $this->height; + $details['aspectRatio'] = round($this->width / $this->height, 3); + $details['size'] = filesize($file); + + $this->load($file); + $details['colors'] = $this->colorsTotal($this->image); + + $options = null; + if (defined("JSON_PRETTY_PRINT") && defined("JSON_UNESCAPED_SLASHES")) { + $options = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES; + } + + return json_encode($details, $options); + } + + + + /** + * Log an event if verbose mode. + * + * @param string $message to log. + * + * @return this + */ + public function log($message) + { + if ($this->verbose) { + $this->log[] = $message; + } + + return $this; + } + + + + /** + * Do verbose output and print out the log and the actual images. + * + * @return void + */ + private function verboseOutput() + { + $log = null; + $this->log("As JSON: \n" . $this->json()); + $this->log("Memory peak: " . round(memory_get_peak_usage() /1024/1024) . "M"); + $this->log("Memory limit: " . ini_get('memory_limit')); + + $included = get_included_files(); + $this->log("Included files: " . count($included)); + + foreach ($this->log as $val) { + if (is_array($val)) { + foreach ($val as $val1) { + $log .= htmlentities($val1) . '
'; + } + } else { + $log .= htmlentities($val) . '
'; + } + } + + echo << + + +CImage verbose output + +

CImage Verbose Output

+
{$log}
+EOD; + } + + + + /** + * Raise error, enables to implement a selection of error methods. + * + * @param string $message the error message to display. + * + * @return void + * @throws Exception + */ + private function raiseError($message) + { + throw new Exception($message); + } +} + + + +/** + * Resize and crop images on the fly, store generated images in a cache. + * + * @author Mikael Roos mos@dbwebb.se + * @example http://dbwebb.se/opensource/cimage + * @link https://github.com/mosbth/cimage + * + */ + +$version = "v0.7.0 (2015-02-10)"; + + + +/** + * Default configuration options, can be overridden in own config-file. + * + * @param string $msg to display. + * + * @return void + */ +function errorPage($msg) +{ + global $mode; + + header("HTTP/1.0 500 Internal Server Error"); + + if ($mode == 'development') { + die("[img.php] $msg"); + } else { + error_log("[img.php] $msg"); + die("HTTP/1.0 500 Internal Server Error"); + } +} + + + +/** + * Custom exception handler. + */ +set_exception_handler(function ($exception) { + errorPage("

img.php: Uncaught exception:

" . $exception->getMessage() . "

" . $exception->getTraceAsString(), "
"); +}); + + + +/** + * Get input from query string or return default value if not set. + * + * @param mixed $key as string or array of string values to look for in $_GET. + * @param mixed $default value to return when $key is not set in $_GET. + * + * @return mixed value from $_GET or default value. + */ +function get($key, $default = null) +{ + if (is_array($key)) { + foreach ($key as $val) { + if (isset($_GET[$val])) { + return $_GET[$val]; + } + } + } elseif (isset($_GET[$key])) { + return $_GET[$key]; + } + return $default; +} + + + +/** + * Get input from query string and set to $defined if defined or else $undefined. + * + * @param mixed $key as string or array of string values to look for in $_GET. + * @param mixed $defined value to return when $key is set in $_GET. + * @param mixed $undefined value to return when $key is not set in $_GET. + * + * @return mixed value as $defined or $undefined. + */ +function getDefined($key, $defined, $undefined) +{ + return get($key) === null ? $undefined : $defined; +} + + + +/** + * Get value from config array or default if key is not set in config array. + * + * @param string $key the key in the config array. + * @param mixed $default value to be default if $key is not set in config. + * + * @return mixed value as $config[$key] or $default. + */ +function getConfig($key, $default) +{ + global $config; + return isset($config[$key]) + ? $config[$key] + : $default; +} + + + +/** + * Log when verbose mode, when used without argument it returns the result. + * + * @param string $msg to log. + * + * @return void or array. + */ +function verbose($msg = null) +{ + global $verbose; + static $log = array(); + + if (!$verbose) { + return; + } + + if (is_null($msg)) { + return $log; + } + + $log[] = $msg; +} + + + +/** + * Get configuration options from file, if the file exists, else use $config + * if its defined or create an empty $config. + */ +$configFile = __DIR__.'/'.basename(__FILE__, '.php').'_config.php'; + +if (is_file($configFile)) { + $config = require $configFile; +} else if (!isset($config)) { + $config = array(); +} + + + +/** +* verbose, v - do a verbose dump of what happens +*/ +$verbose = getDefined(array('verbose', 'v'), true, false); +verbose("img.php version = $version"); + + + +/** + * Set mode as strict, production or development. + * Default is production environment. + */ +$mode = getConfig('mode', 'production'); + +// Settings for any mode +set_time_limit(20); +ini_set('gd.jpeg_ignore_warning', 1); + +if (!extension_loaded('gd')) { + errorPage("Extension gd is nod loaded."); +} + +// Specific settings for each mode +if ($mode == 'strict') { + + error_reporting(0); + ini_set('display_errors', 0); + ini_set('log_errors', 1); + $verbose = false; + +} else if ($mode == 'production') { + + error_reporting(-1); + ini_set('display_errors', 0); + ini_set('log_errors', 1); + $verbose = false; + +} else if ($mode == 'development') { + + error_reporting(-1); + ini_set('display_errors', 1); + ini_set('log_errors', 0); + +} else { + errorPage("Unknown mode: $mode"); +} + +verbose("mode = $mode"); +verbose("error log = " . ini_get('error_log')); + + + +/** + * Set default timezone if not set or if its set in the config-file. + */ +$defaultTimezone = getConfig('default_timezone', null); + +if ($defaultTimezone) { + date_default_timezone_set($defaultTimezone); +} else if (!ini_get('default_timezone')) { + date_default_timezone_set('UTC'); +} + + + +/** + * Check if passwords are configured, used and match. + * Options decide themself if they require passwords to be used. + */ +$pwdConfig = getConfig('password', false); +$pwdAlways = getConfig('password_always', false); +$pwd = get(array('password', 'pwd'), null); + +// Check if passwords match, if configured to use passwords +$passwordMatch = null; +if ($pwdAlways) { + + $passwordMatch = ($pwdConfig === $pwd); + if (!$passwordMatch) { + errorPage("Password required and does not match or exists."); + } + +} elseif ($pwdConfig && $pwd) { + + $passwordMatch = ($pwdConfig === $pwd); +} + +verbose("password match = $passwordMatch"); + + + +/** + * Prevent hotlinking, leeching, of images by controlling who access them + * from where. + * + */ +$allowHotlinking = getConfig('allow_hotlinking', true); +$hotlinkingWhitelist = getConfig('hotlinking_whitelist', array()); + +$serverName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null; +$referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null; +$refererHost = parse_url($referer, PHP_URL_HOST); + +if (!$allowHotlinking) { + if ($passwordMatch) { + ; // Always allow when password match + } else if ($passwordMatch === false) { + errorPage("Hotlinking/leeching not allowed when password missmatch."); + } else if (!$referer) { + errorPage("Hotlinking/leeching not allowed and referer is missing."); + } else if (strcmp($serverName, $refererHost) == 0) { + ; // Allow when serverName matches refererHost + } else if (!empty($hotlinkingWhitelist)) { + + $allowedByWhitelist = false; + foreach ($hotlinkingWhitelist as $val) { + if (preg_match($val, $refererHost)) { + $allowedByWhitelist = true; + } + } + + if (!$allowedByWhitelist) { + errorPage("Hotlinking/leeching not allowed by whitelist."); + } + + } else { + errorPage("Hotlinking/leeching not allowed."); + } +} + +verbose("allow_hotlinking = $allowHotlinking"); +verbose("referer = $referer"); +verbose("referer host = $refererHost"); + + + +/** + * Get the source files. + */ +$autoloader = getConfig('autoloader', false); +$cimageClass = getConfig('cimage_class', false); + +if ($autoloader) { + require $autoloader; +} else if ($cimageClass) { + require $cimageClass; +} + + + +/** + * Create the class for the image. + */ +$img = new CImage(); +$img->setVerbose($verbose); + + + +/** + * Allow or disallow remote download of images from other servers. + * Passwords apply if used. + * + */ +$allowRemote = getConfig('remote_allow', false); + +if ($allowRemote && $passwordMatch !== false) { + $pattern = getConfig('remote_pattern', null); + $img->setRemoteDownload($allowRemote, $pattern); +} + + + +/** + * shortcut, sc - extend arguments with a constant value, defined + * in config-file. + */ +$shortcut = get(array('shortcut', 'sc'), null); +$shortcutConfig = getConfig('shortcut', array( + 'sepia' => "&f=grayscale&f0=brightness,-10&f1=contrast,-20&f2=colorize,120,60,0,0&sharpen", +)); + +verbose("shortcut = $shortcut"); + +if (isset($shortcut) + && isset($shortcutConfig[$shortcut])) { + + parse_str($shortcutConfig[$shortcut], $get); + verbose("shortcut-constant = {$shortcutConfig[$shortcut]}"); + $_GET = array_merge($_GET, $get); +} + + + +/** + * src - the source image file. + */ +$srcImage = get('src') + or errorPage('Must set src-attribute.'); + +// Check for valid/invalid characters +$imagePath = getConfig('image_path', __DIR__ . '/img/'); +$imagePathConstraint = getConfig('image_path_constraint', true); +$validFilename = getConfig('valid_filename', '#^[a-z0-9A-Z-/_\.:]+$#'); + +preg_match($validFilename, $srcImage) + or errorPage('Filename contains invalid characters.'); + +if ($allowRemote && $img->isRemoteSource($srcImage)) { + + // If source is a remote file, ignore local file checks. + +} else if ($imagePathConstraint) { + + // Check that the image is a file below the directory 'image_path'. + $pathToImage = realpath($imagePath . $srcImage); + $imageDir = realpath($imagePath); + + is_file($pathToImage) + or errorPage( + 'Source image is not a valid file, check the filename and that a + matching file exists on the filesystem.' + ); + + substr_compare($imageDir, $pathToImage, 0, strlen($imageDir)) == 0 + or errorPage( + 'Security constraint: Source image is not below the directory "image_path" + as specified in the config file img_config.php.' + ); +} + +verbose("src = $srcImage"); + + + +/** + * Manage size constants from config file, use constants to replace values + * for width and height. + */ +$sizeConstant = getConfig('size_constant', function () { + + // Set sizes to map constant to value, easier to use with width or height + $sizes = array( + 'w1' => 613, + 'w2' => 630, + ); + + // Add grid column width, useful for use as predefined size for width (or height). + $gridColumnWidth = 30; + $gridGutterWidth = 10; + $gridColumns = 24; + + for ($i = 1; $i <= $gridColumns; $i++) { + $sizes['c' . $i] = ($gridColumnWidth + $gridGutterWidth) * $i - $gridGutterWidth; + } + + return $sizes; +}); + +$sizes = call_user_func($sizeConstant); + + + +/** + * width, w - set target width, affecting the resulting image width, height and resize options + */ +$newWidth = get(array('width', 'w')); +$maxWidth = getConfig('max_width', 2000); + +// Check to replace predefined size +if (isset($sizes[$newWidth])) { + $newWidth = $sizes[$newWidth]; +} + +// Support width as % of original width +if ($newWidth[strlen($newWidth)-1] == '%') { + is_numeric(substr($newWidth, 0, -1)) + or errorPage('Width % not numeric.'); +} else { + is_null($newWidth) + or ($newWidth > 10 && $newWidth <= $maxWidth) + or errorPage('Width out of range.'); +} + +verbose("new width = $newWidth"); + + + +/** + * height, h - set target height, affecting the resulting image width, height and resize options + */ +$newHeight = get(array('height', 'h')); +$maxHeight = getConfig('max_height', 2000); + +// Check to replace predefined size +if (isset($sizes[$newHeight])) { + $newHeight = $sizes[$newHeight]; +} + +// height +if ($newHeight[strlen($newHeight)-1] == '%') { + is_numeric(substr($newHeight, 0, -1)) + or errorPage('Height % out of range.'); +} else { + is_null($newHeight) + or ($newHeight > 10 && $newHeight <= $maxHeight) + or errorPage('Hight out of range.'); +} + +verbose("new height = $newHeight"); + + + +/** + * aspect-ratio, ar - affecting the resulting image width, height and resize options + */ +$aspectRatio = get(array('aspect-ratio', 'ar')); +$aspectRatioConstant = getConfig('aspect_ratio_constant', function () { + return array( + '3:1' => 3/1, + '3:2' => 3/2, + '4:3' => 4/3, + '8:5' => 8/5, + '16:10' => 16/10, + '16:9' => 16/9, + 'golden' => 1.618, + ); +}); + +// Check to replace predefined aspect ratio +$aspectRatios = call_user_func($aspectRatioConstant); +$negateAspectRatio = ($aspectRatio[0] == '!') ? true : false; +$aspectRatio = $negateAspectRatio ? substr($aspectRatio, 1) : $aspectRatio; + +if (isset($aspectRatios[$aspectRatio])) { + $aspectRatio = $aspectRatios[$aspectRatio]; +} + +if ($negateAspectRatio) { + $aspectRatio = 1 / $aspectRatio; +} + +is_null($aspectRatio) + or is_numeric($aspectRatio) + or errorPage('Aspect ratio out of range'); + +verbose("aspect ratio = $aspectRatio"); + + + +/** + * crop-to-fit, cf - affecting the resulting image width, height and resize options + */ +$cropToFit = getDefined(array('crop-to-fit', 'cf'), true, false); + +verbose("crop to fit = $cropToFit"); + + + +/** + * Set default background color from config file. + */ +$backgroundColor = getConfig('background_color', null); + +if ($backgroundColor) { + $img->setDefaultBackgroundColor($backgroundColor); + verbose("Using default background_color = $backgroundColor"); +} + + + +/** + * bgColor - Default background color to use + */ +$bgColor = get(array('bgColor', 'bg-color', 'bgc'), null); + +verbose("bgColor = $bgColor"); + + + +/** + * fill-to-fit, ff - affecting the resulting image width, height and resize options + */ +$fillToFit = get(array('fill-to-fit', 'ff'), null); + +verbose("fill-to-fit = $fillToFit"); + +if ($fillToFit !== null) { + + if (!empty($fillToFit)) { + $bgColor = $fillToFit; + verbose("fillToFit changed bgColor to = $bgColor"); + } + + $fillToFit = true; + verbose("fill-to-fit (fixed) = $fillToFit"); +} + + + +/** + * no-ratio, nr, stretch - affecting the resulting image width, height and resize options + */ +$keepRatio = getDefined(array('no-ratio', 'nr', 'stretch'), false, true); + +verbose("keep ratio = $keepRatio"); + + + +/** + * crop, c - affecting the resulting image width, height and resize options + */ +$crop = get(array('crop', 'c')); + +verbose("crop = $crop"); + + + +/** + * area, a - affecting the resulting image width, height and resize options + */ +$area = get(array('area', 'a')); + +verbose("area = $area"); + + + +/** + * skip-original, so - skip the original image and always process a new image + */ +$useOriginal = getDefined(array('skip-original', 'so'), false, true); + +verbose("use original = $useOriginal"); + + + +/** + * no-cache, nc - skip the cached version and process and create a new version in cache. + */ +$useCache = getDefined(array('no-cache', 'nc'), false, true); + +verbose("use cache = $useCache"); + + + +/** + * quality, q - set level of quality for jpeg images + */ +$quality = get(array('quality', 'q')); + +is_null($quality) + or ($quality > 0 and $quality <= 100) + or errorPage('Quality out of range'); + +verbose("quality = $quality"); + + + +/** + * compress, co - what strategy to use when compressing png images + */ +$compress = get(array('compress', 'co')); + + +is_null($compress) + or ($compress > 0 and $compress <= 9) + or errorPage('Compress out of range'); + +verbose("compress = $compress"); + + + +/** + * save-as, sa - what type of image to save + */ +$saveAs = get(array('save-as', 'sa')); + +verbose("save as = $saveAs"); + + + +/** + * scale, s - Processing option, scale up or down the image prior actual resize + */ +$scale = get(array('scale', 's')); + +is_null($scale) + or ($scale >= 0 and $scale <= 400) + or errorPage('Scale out of range'); + +verbose("scale = $scale"); + + + +/** + * palette, p - Processing option, create a palette version of the image + */ +$palette = getDefined(array('palette', 'p'), true, false); + +verbose("palette = $palette"); + + + +/** + * sharpen - Processing option, post filter for sharpen effect + */ +$sharpen = getDefined('sharpen', true, null); + +verbose("sharpen = $sharpen"); + + + +/** + * emboss - Processing option, post filter for emboss effect + */ +$emboss = getDefined('emboss', true, null); + +verbose("emboss = $emboss"); + + + +/** + * blur - Processing option, post filter for blur effect + */ +$blur = getDefined('blur', true, null); + +verbose("blur = $blur"); + + + +/** + * rotateBefore - Rotate the image with an angle, before processing + */ +$rotateBefore = get(array('rotateBefore', 'rotate-before', 'rb')); + +is_null($rotateBefore) + or ($rotateBefore >= -360 and $rotateBefore <= 360) + or errorPage('RotateBefore out of range'); + +verbose("rotateBefore = $rotateBefore"); + + + +/** + * rotateAfter - Rotate the image with an angle, before processing + */ +$rotateAfter = get(array('rotateAfter', 'rotate-after', 'ra', 'rotate', 'r')); + +is_null($rotateAfter) + or ($rotateAfter >= -360 and $rotateAfter <= 360) + or errorPage('RotateBefore out of range'); + +verbose("rotateAfter = $rotateAfter"); + + + +/** + * autoRotate - Auto rotate based on EXIF information + */ +$autoRotate = getDefined(array('autoRotate', 'auto-rotate', 'aro'), true, false); + +verbose("autoRotate = $autoRotate"); + + + +/** + * filter, f, f0-f9 - Processing option, post filter for various effects using imagefilter() + */ +$filters = array(); +$filter = get(array('filter', 'f')); +if ($filter) { + $filters[] = $filter; +} + +for ($i = 0; $i < 10; $i++) { + $filter = get(array("filter{$i}", "f{$i}")); + if ($filter) { + $filters[] = $filter; + } +} + +verbose("filters = " . print_r($filters, 1)); + + + +/** + * json - output the image as a JSON object with details on the image. + */ +$outputFormat = getDefined('json', 'json', null); + +verbose("json = $outputFormat"); + + + +/** + * dpr - change to get larger image to easier support larger dpr, such as retina. + */ +$dpr = get(array('ppi', 'dpr', 'device-pixel-ratio'), 1); + +verbose("dpr = $dpr"); + + + +/** + * convolve - image convolution as in http://php.net/manual/en/function.imageconvolution.php + */ +$convolve = get('convolve', null); +$convolutionConstant = getConfig('convolution_constant', array()); + +// Check if the convolve is matching an existing constant +if ($convolve && isset($convolutionConstant)) { + $img->addConvolveExpressions($convolutionConstant); + verbose("convolve constant = " . print_r($convolutionConstant, 1)); +} + +verbose("convolve = " . print_r($convolve, 1)); + + + +/** + * no-upscale, nu - Do not upscale smaller image to larger dimension. + */ +$upscale = getDefined(array('no-upscale', 'nu'), false, true); + +verbose("upscale = $upscale"); + + + +/** + * Get details for post processing + */ +$postProcessing = getConfig('postprocessing', array( + 'png_filter' => false, + 'png_filter_cmd' => '/usr/local/bin/optipng -q', + + 'png_deflate' => false, + 'png_deflate_cmd' => '/usr/local/bin/pngout -q', + + 'jpeg_optimize' => false, + 'jpeg_optimize_cmd' => '/usr/local/bin/jpegtran -copy none -optimize', +)); + + + +/** + * alias - Save resulting image to another alias name. + * Password always apply, must be defined. + */ +$alias = get('alias', null); +$aliasPath = getConfig('alias_path', null); +$validAliasname = getConfig('valid_aliasname', '#^[a-z0-9A-Z-_]+$#'); +$aliasTarget = null; + +if ($alias && $aliasPath && $passwordMatch) { + + $aliasTarget = $aliasPath . $alias; + $useCache = false; + + is_writable($aliasPath) + or errorPage("Directory for alias is not writable."); + + preg_match($validAliasname, $alias) + or errorPage('Filename for alias contains invalid characters. Do not add extension.'); + +} else if ($alias) { + errorPage('Alias is not enabled in the config file or password not matching.'); +} + +verbose("alias = $alias"); + + + +/** + * Display image if verbose mode + */ +if ($verbose) { + $query = array(); + parse_str($_SERVER['QUERY_STRING'], $query); + unset($query['verbose']); + unset($query['v']); + unset($query['nocache']); + unset($query['nc']); + unset($query['json']); + $url1 = '?' . htmlentities(urldecode(http_build_query($query))); + $url2 = '?' . urldecode(http_build_query($query)); + echo <<$url1
+ +

+
+
+
+EOD;
+}
+
+
+
+/**
+ * Get the cachepath from config.
+ */
+$cachePath = getConfig('cache_path', __DIR__ . '/../cache/');
+
+
+
+/**
+ * Load, process and output the image
+ */
+$img->log("Incoming arguments: " . print_r(verbose(), 1))
+    ->setSaveFolder($cachePath)
+    ->useCache($useCache)
+    ->setSource($srcImage, $imagePath)
+    ->setOptions(
+        array(
+            // Options for calculate dimensions
+            'newWidth'  => $newWidth,
+            'newHeight' => $newHeight,
+            'aspectRatio' => $aspectRatio,
+            'keepRatio' => $keepRatio,
+            'cropToFit' => $cropToFit,
+            'fillToFit' => $fillToFit,
+            'crop'      => $crop,
+            'area'      => $area,
+            'upscale'   => $upscale,
+
+            // Pre-processing, before resizing is done
+            'scale'        => $scale,
+            'rotateBefore' => $rotateBefore,
+            'autoRotate'   => $autoRotate,
+
+            // General processing options
+            'bgColor'    => $bgColor,
+
+            // Post-processing, after resizing is done
+            'palette'   => $palette,
+            'filters'   => $filters,
+            'sharpen'   => $sharpen,
+            'emboss'    => $emboss,
+            'blur'      => $blur,
+            'convolve'  => $convolve,
+            'rotateAfter' => $rotateAfter,
+
+            // Output format
+            'outputFormat' => $outputFormat,
+            'dpr'          => $dpr,
+        )
+    )
+    ->loadImageDetails()
+    ->initDimensions()
+    ->calculateNewWidthAndHeight()
+    ->setSaveAsExtension($saveAs)
+    ->setJpegQuality($quality)
+    ->setPngCompression($compress)
+    ->useOriginalIfPossible($useOriginal)
+    ->generateFilename($cachePath)
+    ->useCacheIfPossible($useCache)
+    ->load()
+    ->preResize()
+    ->resize()
+    ->postResize()
+    ->setPostProcessingOptions($postProcessing)
+    ->save()
+    ->linkToCacheFile($aliasTarget)
+    ->output();
+
+
+
+
diff --git a/docs/api/files/webroot%2Fimgp.php.txt b/docs/api/files/webroot%2Fimgp.php.txt
new file mode 100644
index 0000000..cba0edb
--- /dev/null
+++ b/docs/api/files/webroot%2Fimgp.php.txt
@@ -0,0 +1,3919 @@
+ 'production',               // 'production', 'development', 'strict'
+    //'image_path'   =>  __DIR__ . '/img/',
+    //'cache_path'   =>  __DIR__ . '/../cache/',
+    //'alias_path'   =>  __DIR__ . '/img/alias/',
+    //'remote_allow' => true,
+    //'password'     => false,                      // "secret-password",
+
+);
+
+
+
+/**
+ * Get a image from a remote server using HTTP GET and If-Modified-Since.
+ *
+ */
+class CHttpGet
+{
+    private $request  = array();
+    private $response = array();
+
+
+
+    /**
+    * Constructor
+    *
+    */
+    public function __construct()
+    {
+        $this->request['header'] = array();
+    }
+
+
+
+    /**
+     * Set the url for the request.
+     *
+     * @param string $url
+     *
+     * @return $this
+     */
+    public function setUrl($url)
+    {
+        $this->request['url'] = $url;
+        return $this;
+    }
+
+
+
+    /**
+     * Set custom header field for the request.
+     *
+     * @param string $field
+     * @param string $value
+     *
+     * @return $this
+     */
+    public function setHeader($field, $value)
+    {
+        $this->request['header'][] = "$field: $value";
+        return $this;
+    }
+
+
+
+    /**
+     * Set header fields for the request.
+     *
+     * @param string $field
+     * @param string $value
+     *
+     * @return $this
+     */
+    public function parseHeader()
+    {
+        $header = explode("\r\n", rtrim($this->response['headerRaw'], "\r\n"));
+        $output = array();
+
+        if ('HTTP' === substr($header[0], 0, 4)) {
+            list($output['version'], $output['status']) = explode(' ', $header[0]);
+            unset($header[0]);
+        }
+
+        foreach ($header as $entry) {
+            $pos = strpos($entry, ':');
+            $output[trim(substr($entry, 0, $pos))] = trim(substr($entry, $pos + 1));
+        }
+
+        $this->response['header'] = $output;
+        return $this;
+    }
+
+
+
+    /**
+     * Perform the request.
+     *
+     * @param boolean $debug set to true to dump headers.
+     *
+     * @return boolean
+     */
+    public function doGet($debug = false)
+    {
+        $options = array(
+            CURLOPT_URL             => $this->request['url'],
+            CURLOPT_HEADER          => 1,
+            CURLOPT_HTTPHEADER      => $this->request['header'],
+            CURLOPT_AUTOREFERER     => true,
+            CURLOPT_RETURNTRANSFER  => true,
+            CURLINFO_HEADER_OUT     => $debug,
+            CURLOPT_CONNECTTIMEOUT  => 5,
+            CURLOPT_TIMEOUT         => 5,
+        );
+
+        $ch = curl_init();
+        curl_setopt_array($ch, $options);
+        $response = curl_exec($ch);
+
+        if (!$response) {
+            return false;
+        }
+
+        $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
+        $this->response['headerRaw'] = substr($response, 0, $headerSize);
+        $this->response['body']      = substr($response, $headerSize);
+
+        $this->parseHeader();
+
+        if ($debug) {
+            $info = curl_getinfo($ch);
+            echo "Request header
", var_dump($info['request_header']), "
"; + echo "Response header (raw)
", var_dump($this->response['headerRaw']), "
"; + echo "Response header (parsed)
", var_dump($this->response['header']), "
"; + } + + curl_close($ch); + return true; + } + + + + /** + * Get HTTP code of response. + * + * @return integer as HTTP status code or null if not available. + */ + public function getStatus() + { + return isset($this->response['header']['status']) + ? (int) $this->response['header']['status'] + : null; + } + + + + /** + * Get file modification time of response. + * + * @return int as timestamp. + */ + public function getLastModified() + { + return isset($this->response['header']['Last-Modified']) + ? strtotime($this->response['header']['Last-Modified']) + : null; + } + + + + /** + * Get content type. + * + * @return string as the content type or null if not existing or invalid. + */ + public function getContentType() + { + $type = isset($this->response['header']['Content-Type']) + ? $this->response['header']['Content-Type'] + : null; + + return preg_match('#[a-z]+/[a-z]+#', $type) + ? $type + : null; + } + + + + /** + * Get file modification time of response. + * + * @param mixed $default as default value (int seconds) if date is + * missing in response header. + * + * @return int as timestamp or $default if Date is missing in + * response header. + */ + public function getDate($default = false) + { + return isset($this->response['header']['Date']) + ? strtotime($this->response['header']['Date']) + : $default; + } + + + + /** + * Get max age of cachable item. + * + * @param mixed $default as default value if date is missing in response + * header. + * + * @return int as timestamp or false if not available. + */ + public function getMaxAge($default = false) + { + $cacheControl = isset($this->response['header']['Cache-Control']) + ? $this->response['header']['Cache-Control'] + : null; + + $maxAge = null; + if ($cacheControl) { + // max-age=2592000 + $part = explode('=', $cacheControl); + $maxAge = ($part[0] == "max-age") + ? (int) $part[1] + : null; + } + + if ($maxAge) { + return $maxAge; + } + + $expire = isset($this->response['header']['Expires']) + ? strtotime($this->response['header']['Expires']) + : null; + + return $expire ? $expire : $default; + } + + + + /** + * Get body of response. + * + * @return string as body. + */ + public function getBody() + { + return $this->response['body']; + } +} + + + +/** + * Get a image from a remote server using HTTP GET and If-Modified-Since. + * + */ +class CRemoteImage +{ + /** + * Path to cache files. + */ + private $saveFolder = null; + + + + /** + * Use cache or not. + */ + private $useCache = true; + + + + /** + * HTTP object to aid in download file. + */ + private $http; + + + + /** + * Status of the HTTP request. + */ + private $status; + + + + /** + * Defalt age for cached items 60*60*24*7. + */ + private $defaultMaxAge = 604800; + + + + /** + * Url of downloaded item. + */ + private $url; + + + + /** + * Base name of cache file for downloaded item. + */ + private $fileName; + + + + /** + * Filename for json-file with details of cached item. + */ + private $fileJson; + + + + /** + * Filename for image-file. + */ + private $fileImage; + + + + /** + * Cache details loaded from file. + */ + private $cache; + + + + /** + * Constructor + * + */ + public function __construct() + { + ; + } + + + /** + * Get status of last HTTP request. + * + * @return int as status + */ + public function getStatus() + { + return $this->status; + } + + + + /** + * Get JSON details for cache item. + * + * @return array with json details on cache. + */ + public function getDetails() + { + return $this->cache; + } + + + + /** + * Set the path to the cache directory. + * + * @param boolean $use true to use the cache and false to ignore cache. + * + * @return $this + */ + public function setCache($path) + { + $this->saveFolder = $path; + return $this; + } + + + + /** + * Check if cache is writable or throw exception. + * + * @return $this + * + * @throws Exception if cahce folder is not writable. + */ + public function isCacheWritable() + { + if (!is_writable($this->saveFolder)) { + throw new Exception("Cache folder is not writable for downloaded files."); + } + return $this; + } + + + + /** + * Decide if the cache should be used or not before trying to download + * a remote file. + * + * @param boolean $use true to use the cache and false to ignore cache. + * + * @return $this + */ + public function useCache($use = true) + { + $this->useCache = $use; + return $this; + } + + + + /** + * Translate a content type to a file extension. + * + * @param string $type a valid content type. + * + * @return string as file extension or false if no match. + */ + function contentTypeToFileExtension($type) { + $extension = array( + 'image/jpeg' => 'jpg', + 'image/png' => 'png', + 'image/gif' => 'gif', + ); + + return isset($extension[$type]) + ? $extension[$type] + : false; + } + + + + /** + * Set header fields. + * + * @return $this + */ + function setHeaderFields() { + $this->http->setHeader("User-Agent", "CImage/0.6 (PHP/". phpversion() . " cURL)"); + $this->http->setHeader("Accept", "image/jpeg,image/png,image/gif"); + + if ($this->useCache) { + $this->http->setHeader("Cache-Control", "max-age=0"); + } else { + $this->http->setHeader("Cache-Control", "no-cache"); + $this->http->setHeader("Pragma", "no-cache"); + } + } + + + + /** + * Save downloaded resource to cache. + * + * @return string as path to saved file or false if not saved. + */ + function save() { + + $this->cache = array(); + $date = $this->http->getDate(time()); + $maxAge = $this->http->getMaxAge($this->defaultMaxAge); + $lastModified = $this->http->getLastModified(); + $type = $this->http->getContentType(); + $extension = $this->contentTypeToFileExtension($type); + + $this->cache['Date'] = gmdate("D, d M Y H:i:s T", $date); + $this->cache['Max-Age'] = $maxAge; + $this->cache['Content-Type'] = $type; + $this->cache['File-Extension'] = $extension; + + if ($lastModified) { + $this->cache['Last-Modified'] = gmdate("D, d M Y H:i:s T", $lastModified); + } + + if ($extension) { + + $this->fileImage = $this->fileName . "." . $extension; + + // Save only if body is a valid image + $body = $this->http->getBody(); + $img = imagecreatefromstring($body); + + if ($img !== false) { + file_put_contents($this->fileImage, $body); + file_put_contents($this->fileJson, json_encode($this->cache)); + return $this->fileImage; + } + } + + return false; + } + + + + /** + * Got a 304 and updates cache with new age. + * + * @return string as path to cached file. + */ + function updateCacheDetails() { + + $date = $this->http->getDate(time()); + $maxAge = $this->http->getMaxAge($this->defaultMaxAge); + $lastModified = $this->http->getLastModified(); + + $this->cache['Date'] = gmdate("D, d M Y H:i:s T", $date); + $this->cache['Max-Age'] = $maxAge; + + if ($lastModified) { + $this->cache['Last-Modified'] = gmdate("D, d M Y H:i:s T", $lastModified); + } + + file_put_contents($this->fileJson, json_encode($this->cache)); + return $this->fileImage; + } + + + + /** + * Download a remote file and keep a cache of downloaded files. + * + * @param string $url a remote url. + * + * @return string as path to downloaded file or false if failed. + */ + function download($url) { + + $this->http = new CHttpGet(); + $this->url = $url; + + // First check if the cache is valid and can be used + $this->loadCacheDetails(); + + if ($this->useCache) { + $src = $this->getCachedSource(); + if ($src) { + $this->status = 1; + return $src; + } + } + + // Do a HTTP request to download item + $this->setHeaderFields(); + $this->http->setUrl($this->url); + $this->http->doGet(); + + $this->status = $this->http->getStatus(); + if ($this->status === 200) { + $this->isCacheWritable(); + return $this->save(); + } else if ($this->status === 304) { + $this->isCacheWritable(); + return $this->updateCacheDetails(); + } + + return false; + } + + + + /** + * Get the path to the cached image file if the cache is valid. + * + * @return $this + */ + public function loadCacheDetails() + { + $cacheFile = str_replace(array("/", ":", "#", ".", "?"), "-", $this->url); + $this->fileName = $this->saveFolder . $cacheFile; + $this->fileJson = $this->fileName . ".json"; + if (is_readable($this->fileJson)) { + $this->cache = json_decode(file_get_contents($this->fileJson), true); + } + } + + + + /** + * Get the path to the cached image file if the cache is valid. + * + * @return string as the path ot the image file or false if no cache. + */ + public function getCachedSource() + { + $this->fileImage = $this->fileName . "." . $this->cache['File-Extension']; + $imageExists = is_readable($this->fileImage); + + // Is cache valid? + $date = strtotime($this->cache['Date']); + $maxAge = $this->cache['Max-Age']; + $now = time(); + if ($imageExists && $date + $maxAge > $now) { + return $this->fileImage; + } + + // Prepare for a 304 if available + if ($imageExists && isset($this->cache['Last-Modified'])) { + $this->http->setHeader("If-Modified-Since", $this->cache['Last-Modified']); + } + + return false; + } +} + + + +/** + * Resize and crop images on the fly, store generated images in a cache. + * + * @author Mikael Roos mos@dbwebb.se + * @example http://dbwebb.se/opensource/cimage + * @link https://github.com/mosbth/cimage + */ +class CImage +{ + + /** + * Constants type of PNG image + */ + const PNG_GREYSCALE = 0; + const PNG_RGB = 2; + const PNG_RGB_PALETTE = 3; + const PNG_GREYSCALE_ALPHA = 4; + const PNG_RGB_ALPHA = 6; + + + + /** + * Constant for default image quality when not set + */ + const JPEG_QUALITY_DEFAULT = 60; + + + + /** + * Quality level for JPEG images. + */ + private $quality; + + + + /** + * Is the quality level set from external use (true) or is it default (false)? + */ + private $useQuality = false; + + + + /** + * Constant for default image quality when not set + */ + const PNG_COMPRESSION_DEFAULT = -1; + + + + /** + * Compression level for PNG images. + */ + private $compress; + + + + /** + * Is the compress level set from external use (true) or is it default (false)? + */ + private $useCompress = false; + + + + + /** + * Default background color, red, green, blue, alpha. + * + * @todo remake when upgrading to PHP 5.5 + */ + /* + const BACKGROUND_COLOR = array( + 'red' => 0, + 'green' => 0, + 'blue' => 0, + 'alpha' => null, + );*/ + + + + /** + * Default background color to use. + * + * @todo remake when upgrading to PHP 5.5 + */ + //private $bgColorDefault = self::BACKGROUND_COLOR; + private $bgColorDefault = array( + 'red' => 0, + 'green' => 0, + 'blue' => 0, + 'alpha' => null, + ); + + + /** + * Background color to use, specified as part of options. + */ + private $bgColor; + + + + /** + * Where to save the target file. + */ + private $saveFolder; + + + + /** + * The working image object. + */ + private $image; + + + + /** + * The root folder of images (only used in constructor to create $pathToImage?). + */ + private $imageFolder; + + + + /** + * Image filename, may include subdirectory, relative from $imageFolder + */ + private $imageSrc; + + + + /** + * Actual path to the image, $imageFolder . '/' . $imageSrc + */ + private $pathToImage; + + + + /** + * Original file extension + */ + private $fileExtension; + + + + /** + * File extension to use when saving image. + */ + private $extension; + + + + /** + * Output format, supports null (image) or json. + */ + private $outputFormat = null; + + + + /** + * Verbose mode to print out a trace and display the created image + */ + private $verbose = false; + + + + /** + * Keep a log/trace on what happens + */ + private $log = array(); + + + + /** + * Handle image as palette image + */ + private $palette; + + + + /** + * Target filename, with path, to save resulting image in. + */ + private $cacheFileName; + + + + /** + * Set a format to save image as, or null to use original format. + */ + private $saveAs; + + + /** + * Path to command for filter optimize, for example optipng or null. + */ + private $pngFilter; + + + + /** + * Path to command for deflate optimize, for example pngout or null. + */ + private $pngDeflate; + + + + /** + * Path to command to optimize jpeg images, for example jpegtran or null. + */ + private $jpegOptimize; + + + /** + * Image dimensions, calculated from loaded image. + */ + private $width; // Calculated from source image + private $height; // Calculated from source image + + + /** + * New image dimensions, incoming as argument or calculated. + */ + private $newWidth; + private $newWidthOrig; // Save original value + private $newHeight; + private $newHeightOrig; // Save original value + + + /** + * Change target height & width when different dpr, dpr 2 means double image dimensions. + */ + private $dpr = 1; + + + /** + * Always upscale images, even if they are smaller than target image. + */ + const UPSCALE_DEFAULT = true; + private $upscale = self::UPSCALE_DEFAULT; + + + + /** + * Array with details on how to crop, incoming as argument and calculated. + */ + public $crop; + public $cropOrig; // Save original value + + + /** + * String with details on how to do image convolution. String + * should map a key in the $convolvs array or be a string of + * 11 float values separated by comma. The first nine builds + * up the matrix, then divisor and last offset. + */ + private $convolve; + + + /** + * Custom convolution expressions, matrix 3x3, divisor and offset. + */ + private $convolves = array( + 'lighten' => '0,0,0, 0,12,0, 0,0,0, 9, 0', + 'darken' => '0,0,0, 0,6,0, 0,0,0, 9, 0', + 'sharpen' => '-1,-1,-1, -1,16,-1, -1,-1,-1, 8, 0', + 'sharpen-alt' => '0,-1,0, -1,5,-1, 0,-1,0, 1, 0', + 'emboss' => '1,1,-1, 1,3,-1, 1,-1,-1, 3, 0', + 'emboss-alt' => '-2,-1,0, -1,1,1, 0,1,2, 1, 0', + 'blur' => '1,1,1, 1,15,1, 1,1,1, 23, 0', + 'gblur' => '1,2,1, 2,4,2, 1,2,1, 16, 0', + 'edge' => '-1,-1,-1, -1,8,-1, -1,-1,-1, 9, 0', + 'edge-alt' => '0,1,0, 1,-4,1, 0,1,0, 1, 0', + 'draw' => '0,-1,0, -1,5,-1, 0,-1,0, 0, 0', + 'mean' => '1,1,1, 1,1,1, 1,1,1, 9, 0', + 'motion' => '1,0,0, 0,1,0, 0,0,1, 3, 0', + ); + + + /** + * Resize strategy to fill extra area with background color. + * True or false. + */ + private $fillToFit; + + + /** + * Used with option area to set which parts of the image to use. + */ + private $offset; + + + + /** + * Calculate target dimension for image when using fill-to-fit resize strategy. + */ + private $fillWidth; + private $fillHeight; + + + + /** + * Allow remote file download, default is to disallow remote file download. + */ + private $allowRemote = false; + + + + /** + * Pattern to recognize a remote file. + */ + //private $remotePattern = '#^[http|https]://#'; + private $remotePattern = '#^https?://#'; + + + + /** + * Use the cache if true, set to false to ignore the cached file. + */ + private $useCache = true; + + + /** + * Properties, the class is mutable and the method setOptions() + * decides (partly) what properties are created. + * + * @todo Clean up these and check if and how they are used + */ + + public $keepRatio; + public $cropToFit; + private $cropWidth; + private $cropHeight; + public $crop_x; + public $crop_y; + public $filters; + private $type; // Calculated from source image + private $attr; // Calculated from source image + private $useOriginal; // Use original image if possible + + + + + /** + * Constructor, can take arguments to init the object. + * + * @param string $imageSrc filename which may contain subdirectory. + * @param string $imageFolder path to root folder for images. + * @param string $saveFolder path to folder where to save the new file or null to skip saving. + * @param string $saveName name of target file when saveing. + */ + public function __construct($imageSrc = null, $imageFolder = null, $saveFolder = null, $saveName = null) + { + $this->setSource($imageSrc, $imageFolder); + $this->setTarget($saveFolder, $saveName); + } + + + + /** + * Set verbose mode. + * + * @param boolean $mode true or false to enable and disable verbose mode, + * default is true. + * + * @return $this + */ + public function setVerbose($mode = true) + { + $this->verbose = $mode; + return $this; + } + + + + /** + * Set save folder, base folder for saving cache files. + * + * @todo clean up how $this->saveFolder is used in other methods. + * + * @param string $path where to store cached files. + * + * @return $this + */ + public function setSaveFolder($path) + { + $this->saveFolder = $path; + return $this; + } + + + + /** + * Use cache or not. + * + * @todo clean up how $this->noCache is used in other methods. + * + * @param string $use true or false to use cache. + * + * @return $this + */ + public function useCache($use = true) + { + $this->useCache = $use; + return $this; + } + + + + /** + * Allow or disallow remote image download. + * + * @param boolean $allow true or false to enable and disable. + * @param string $pattern to use to detect if its a remote file. + * + * @return $this + */ + public function setRemoteDownload($allow, $pattern = null) + { + $this->allowRemote = $allow; + $this->remotePattern = $pattern ? $pattern : $this->remotePattern; + + $this->log("Set remote download to: " + . ($this->allowRemote ? "true" : "false") + . " using pattern " + . $this->remotePattern); + + return $this; + } + + + + /** + * Check if the image resource is a remote file or not. + * + * @param string $src check if src is remote. + * + * @return boolean true if $src is a remote file, else false. + */ + public function isRemoteSource($src) + { + $remote = preg_match($this->remotePattern, $src); + $this->log("Detected remote image: " . ($remote ? "true" : "false")); + return $remote; + } + + + + /** + * Check if file extension is valid as a file extension. + * + * @param string $extension of image file. + * + * @return $this + */ + private function checkFileExtension($extension) + { + $valid = array('jpg', 'jpeg', 'png', 'gif'); + + in_array(strtolower($extension), $valid) + or $this->raiseError('Not a valid file extension.'); + + return $this; + } + + + + /** + * Download a remote image and return path to its local copy. + * + * @param string $src remote path to image. + * + * @return string as path to downloaded remote source. + */ + public function downloadRemoteSource($src) + { + $remote = new CRemoteImage(); + $cache = $this->saveFolder . "/remote/"; + + if (!is_dir($cache)) { + if (!is_writable($this->saveFolder)) { + throw new Exception("Can not create remote cache, cachefolder not writable."); + } + mkdir($cache); + $this->log("The remote cache does not exists, creating it."); + } + + if (!is_writable($cache)) { + $this->log("The remote cache is not writable."); + } + + $remote->setCache($cache); + $remote->useCache($this->useCache); + $src = $remote->download($src); + + $this->log("Remote HTTP status: " . $remote->getStatus()); + $this->log("Remote item has local cached file: $src"); + $this->log("Remote details on cache:" . print_r($remote->getDetails(), true)); + + return $src; + } + + + + /** + * Set src file. + * + * @param string $src of image. + * @param string $dir as base directory where images are. + * + * @return $this + */ + public function setSource($src, $dir = null) + { + if (!isset($src)) { + return $this; + } + + if ($this->allowRemote && $this->isRemoteSource($src)) { + $src = $this->downloadRemoteSource($src); + $dir = null; + } + + if (!isset($dir)) { + $dir = dirname($src); + $src = basename($src); + } + + $this->imageSrc = ltrim($src, '/'); + $this->imageFolder = rtrim($dir, '/'); + $this->pathToImage = $this->imageFolder . '/' . $this->imageSrc; + $this->fileExtension = strtolower(pathinfo($this->pathToImage, PATHINFO_EXTENSION)); + //$this->extension = $this->fileExtension; + + $this->checkFileExtension($this->fileExtension); + + return $this; + } + + + + /** + * Set target file. + * + * @param string $src of target image. + * @param string $dir as base directory where images are stored. + * + * @return $this + */ + public function setTarget($src = null, $dir = null) + { + if (!(isset($src) && isset($dir))) { + return $this; + } + + $this->saveFolder = $dir; + $this->cacheFileName = $dir . '/' . $src; + + /* Allow readonly cache + is_writable($this->saveFolder) + or $this->raiseError('Target directory is not writable.'); + */ + + // Sanitize filename + $this->cacheFileName = preg_replace('/^a-zA-Z0-9\.-_/', '', $this->cacheFileName); + $this->log("The cache file name is: " . $this->cacheFileName); + + return $this; + } + + + + /** + * Set options to use when processing image. + * + * @param array $args used when processing image. + * + * @return $this + */ + public function setOptions($args) + { + $this->log("Set new options for processing image."); + + $defaults = array( + // Options for calculate dimensions + 'newWidth' => null, + 'newHeight' => null, + 'aspectRatio' => null, + 'keepRatio' => true, + 'cropToFit' => false, + 'fillToFit' => null, + 'crop' => null, //array('width'=>null, 'height'=>null, 'start_x'=>0, 'start_y'=>0), + 'area' => null, //'0,0,0,0', + 'upscale' => self::UPSCALE_DEFAULT, + + // Options for caching or using original + 'useCache' => true, + 'useOriginal' => true, + + // Pre-processing, before resizing is done + 'scale' => null, + 'rotateBefore' => null, + 'autoRotate' => false, + + // General options + 'bgColor' => null, + + // Post-processing, after resizing is done + 'palette' => null, + 'filters' => null, + 'sharpen' => null, + 'emboss' => null, + 'blur' => null, + 'convolve' => null, + 'rotateAfter' => null, + + // Output format + 'outputFormat' => null, + 'dpr' => 1, + + // Options for saving + //'quality' => null, + //'compress' => null, + //'saveAs' => null, + ); + + // Convert crop settings from string to array + if (isset($args['crop']) && !is_array($args['crop'])) { + $pices = explode(',', $args['crop']); + $args['crop'] = array( + 'width' => $pices[0], + 'height' => $pices[1], + 'start_x' => $pices[2], + 'start_y' => $pices[3], + ); + } + + // Convert area settings from string to array + if (isset($args['area']) && !is_array($args['area'])) { + $pices = explode(',', $args['area']); + $args['area'] = array( + 'top' => $pices[0], + 'right' => $pices[1], + 'bottom' => $pices[2], + 'left' => $pices[3], + ); + } + + // Convert filter settings from array of string to array of array + if (isset($args['filters']) && is_array($args['filters'])) { + foreach ($args['filters'] as $key => $filterStr) { + $parts = explode(',', $filterStr); + $filter = $this->mapFilter($parts[0]); + $filter['str'] = $filterStr; + for ($i=1; $i<=$filter['argc']; $i++) { + if (isset($parts[$i])) { + $filter["arg{$i}"] = $parts[$i]; + } else { + throw new Exception( + 'Missing arg to filter, review how many arguments are needed at + http://php.net/manual/en/function.imagefilter.php' + ); + } + } + $args['filters'][$key] = $filter; + } + } + + // Merge default arguments with incoming and set properties. + //$args = array_merge_recursive($defaults, $args); + $args = array_merge($defaults, $args); + foreach ($defaults as $key => $val) { + $this->{$key} = $args[$key]; + } + + if ($this->bgColor) { + $this->setDefaultBackgroundColor($this->bgColor); + } + + // Save original values to enable re-calculating + $this->newWidthOrig = $this->newWidth; + $this->newHeightOrig = $this->newHeight; + $this->cropOrig = $this->crop; + + return $this; + } + + + + /** + * Map filter name to PHP filter and id. + * + * @param string $name the name of the filter. + * + * @return array with filter settings + * @throws Exception + */ + private function mapFilter($name) + { + $map = array( + 'negate' => array('id'=>0, 'argc'=>0, 'type'=>IMG_FILTER_NEGATE), + 'grayscale' => array('id'=>1, 'argc'=>0, 'type'=>IMG_FILTER_GRAYSCALE), + 'brightness' => array('id'=>2, 'argc'=>1, 'type'=>IMG_FILTER_BRIGHTNESS), + 'contrast' => array('id'=>3, 'argc'=>1, 'type'=>IMG_FILTER_CONTRAST), + 'colorize' => array('id'=>4, 'argc'=>4, 'type'=>IMG_FILTER_COLORIZE), + 'edgedetect' => array('id'=>5, 'argc'=>0, 'type'=>IMG_FILTER_EDGEDETECT), + 'emboss' => array('id'=>6, 'argc'=>0, 'type'=>IMG_FILTER_EMBOSS), + 'gaussian_blur' => array('id'=>7, 'argc'=>0, 'type'=>IMG_FILTER_GAUSSIAN_BLUR), + 'selective_blur' => array('id'=>8, 'argc'=>0, 'type'=>IMG_FILTER_SELECTIVE_BLUR), + 'mean_removal' => array('id'=>9, 'argc'=>0, 'type'=>IMG_FILTER_MEAN_REMOVAL), + 'smooth' => array('id'=>10, 'argc'=>1, 'type'=>IMG_FILTER_SMOOTH), + 'pixelate' => array('id'=>11, 'argc'=>2, 'type'=>IMG_FILTER_PIXELATE), + ); + + if (isset($map[$name])) { + return $map[$name]; + } else { + throw new Exception('No such filter.'); + } + } + + + + /** + * Load image details from original image file. + * + * @param string $file the file to load or null to use $this->pathToImage. + * + * @return $this + * @throws Exception + */ + public function loadImageDetails($file = null) + { + $file = $file ? $file : $this->pathToImage; + + is_readable($file) + or $this->raiseError('Image file does not exist.'); + + // Get details on image + $info = list($this->width, $this->height, $this->type, $this->attr) = getimagesize($file); + !empty($info) or $this->raiseError("The file doesn't seem to be an image."); + + if ($this->verbose) { + $this->log("Image file: {$file}"); + $this->log("Image width x height (type): {$this->width} x {$this->height} ({$this->type})."); + $this->log("Image filesize: " . filesize($file) . " bytes."); + } + + return $this; + } + + + + /** + * Init new width and height and do some sanity checks on constraints, before any + * processing can be done. + * + * @return $this + * @throws Exception + */ + public function initDimensions() + { + $this->log("Init dimension (before) newWidth x newHeight is {$this->newWidth} x {$this->newHeight}."); + + // width as % + if ($this->newWidth[strlen($this->newWidth)-1] == '%') { + $this->newWidth = $this->width * substr($this->newWidth, 0, -1) / 100; + $this->log("Setting new width based on % to {$this->newWidth}"); + } + + // height as % + if ($this->newHeight[strlen($this->newHeight)-1] == '%') { + $this->newHeight = $this->height * substr($this->newHeight, 0, -1) / 100; + $this->log("Setting new height based on % to {$this->newHeight}"); + } + + is_null($this->aspectRatio) or is_numeric($this->aspectRatio) or $this->raiseError('Aspect ratio out of range'); + + // width & height from aspect ratio + if ($this->aspectRatio && is_null($this->newWidth) && is_null($this->newHeight)) { + if ($this->aspectRatio >= 1) { + $this->newWidth = $this->width; + $this->newHeight = $this->width / $this->aspectRatio; + $this->log("Setting new width & height based on width & aspect ratio (>=1) to (w x h) {$this->newWidth} x {$this->newHeight}"); + + } else { + $this->newHeight = $this->height; + $this->newWidth = $this->height * $this->aspectRatio; + $this->log("Setting new width & height based on width & aspect ratio (<1) to (w x h) {$this->newWidth} x {$this->newHeight}"); + } + + } elseif ($this->aspectRatio && is_null($this->newWidth)) { + $this->newWidth = $this->newHeight * $this->aspectRatio; + $this->log("Setting new width based on aspect ratio to {$this->newWidth}"); + + } elseif ($this->aspectRatio && is_null($this->newHeight)) { + $this->newHeight = $this->newWidth / $this->aspectRatio; + $this->log("Setting new height based on aspect ratio to {$this->newHeight}"); + } + + // Change width & height based on dpr + if ($this->dpr != 1) { + if (!is_null($this->newWidth)) { + $this->newWidth = round($this->newWidth * $this->dpr); + $this->log("Setting new width based on dpr={$this->dpr} - w={$this->newWidth}"); + } + if (!is_null($this->newHeight)) { + $this->newHeight = round($this->newHeight * $this->dpr); + $this->log("Setting new height based on dpr={$this->dpr} - h={$this->newHeight}"); + } + } + + // Check values to be within domain + is_null($this->newWidth) + or is_numeric($this->newWidth) + or $this->raiseError('Width not numeric'); + + is_null($this->newHeight) + or is_numeric($this->newHeight) + or $this->raiseError('Height not numeric'); + + $this->log("Init dimension (after) newWidth x newHeight is {$this->newWidth} x {$this->newHeight}."); + + return $this; + } + + + + /** + * Calculate new width and height of image, based on settings. + * + * @return $this + */ + public function calculateNewWidthAndHeight() + { + // Crop, use cropped width and height as base for calulations + $this->log("Calculate new width and height."); + $this->log("Original width x height is {$this->width} x {$this->height}."); + $this->log("Target dimension (before calculating) newWidth x newHeight is {$this->newWidth} x {$this->newHeight}."); + + // Check if there is an area to crop off + if (isset($this->area)) { + $this->offset['top'] = round($this->area['top'] / 100 * $this->height); + $this->offset['right'] = round($this->area['right'] / 100 * $this->width); + $this->offset['bottom'] = round($this->area['bottom'] / 100 * $this->height); + $this->offset['left'] = round($this->area['left'] / 100 * $this->width); + $this->offset['width'] = $this->width - $this->offset['left'] - $this->offset['right']; + $this->offset['height'] = $this->height - $this->offset['top'] - $this->offset['bottom']; + $this->width = $this->offset['width']; + $this->height = $this->offset['height']; + $this->log("The offset for the area to use is top {$this->area['top']}%, right {$this->area['right']}%, bottom {$this->area['bottom']}%, left {$this->area['left']}%."); + $this->log("The offset for the area to use is top {$this->offset['top']}px, right {$this->offset['right']}px, bottom {$this->offset['bottom']}px, left {$this->offset['left']}px, width {$this->offset['width']}px, height {$this->offset['height']}px."); + } + + $width = $this->width; + $height = $this->height; + + // Check if crop is set + if ($this->crop) { + $width = $this->crop['width'] = $this->crop['width'] <= 0 ? $this->width + $this->crop['width'] : $this->crop['width']; + $height = $this->crop['height'] = $this->crop['height'] <= 0 ? $this->height + $this->crop['height'] : $this->crop['height']; + + if ($this->crop['start_x'] == 'left') { + $this->crop['start_x'] = 0; + } elseif ($this->crop['start_x'] == 'right') { + $this->crop['start_x'] = $this->width - $width; + } elseif ($this->crop['start_x'] == 'center') { + $this->crop['start_x'] = round($this->width / 2) - round($width / 2); + } + + if ($this->crop['start_y'] == 'top') { + $this->crop['start_y'] = 0; + } elseif ($this->crop['start_y'] == 'bottom') { + $this->crop['start_y'] = $this->height - $height; + } elseif ($this->crop['start_y'] == 'center') { + $this->crop['start_y'] = round($this->height / 2) - round($height / 2); + } + + $this->log("Crop area is width {$width}px, height {$height}px, start_x {$this->crop['start_x']}px, start_y {$this->crop['start_y']}px."); + } + + // Calculate new width and height if keeping aspect-ratio. + if ($this->keepRatio) { + + $this->log("Keep aspect ratio."); + + // Crop-to-fit and both new width and height are set. + if (($this->cropToFit || $this->fillToFit) && isset($this->newWidth) && isset($this->newHeight)) { + + // Use newWidth and newHeigh as width/height, image should fit in box. + $this->log("Use newWidth and newHeigh as width/height, image should fit in box."); + + } elseif (isset($this->newWidth) && isset($this->newHeight)) { + + // Both new width and height are set. + // Use newWidth and newHeigh as max width/height, image should not be larger. + $ratioWidth = $width / $this->newWidth; + $ratioHeight = $height / $this->newHeight; + $ratio = ($ratioWidth > $ratioHeight) ? $ratioWidth : $ratioHeight; + $this->newWidth = round($width / $ratio); + $this->newHeight = round($height / $ratio); + $this->log("New width and height was set."); + + } elseif (isset($this->newWidth)) { + + // Use new width as max-width + $factor = (float)$this->newWidth / (float)$width; + $this->newHeight = round($factor * $height); + $this->log("New width was set."); + + } elseif (isset($this->newHeight)) { + + // Use new height as max-hight + $factor = (float)$this->newHeight / (float)$height; + $this->newWidth = round($factor * $width); + $this->log("New height was set."); + + } + + // Get image dimensions for pre-resize image. + if ($this->cropToFit || $this->fillToFit) { + + // Get relations of original & target image + $ratioWidth = $width / $this->newWidth; + $ratioHeight = $height / $this->newHeight; + + if ($this->cropToFit) { + + // Use newWidth and newHeigh as defined width/height, + // image should fit the area. + $this->log("Crop to fit."); + $ratio = ($ratioWidth < $ratioHeight) ? $ratioWidth : $ratioHeight; + $this->cropWidth = round($width / $ratio); + $this->cropHeight = round($height / $ratio); + $this->log("Crop width, height, ratio: $this->cropWidth x $this->cropHeight ($ratio)."); + + } else if ($this->fillToFit) { + + // Use newWidth and newHeigh as defined width/height, + // image should fit the area. + $this->log("Fill to fit."); + $ratio = ($ratioWidth < $ratioHeight) ? $ratioHeight : $ratioWidth; + $this->fillWidth = round($width / $ratio); + $this->fillHeight = round($height / $ratio); + $this->log("Fill width, height, ratio: $this->fillWidth x $this->fillHeight ($ratio)."); + } + } + } + + // Crop, ensure to set new width and height + if ($this->crop) { + $this->log("Crop."); + $this->newWidth = round(isset($this->newWidth) ? $this->newWidth : $this->crop['width']); + $this->newHeight = round(isset($this->newHeight) ? $this->newHeight : $this->crop['height']); + } + + // Fill to fit, ensure to set new width and height + /*if ($this->fillToFit) { + $this->log("FillToFit."); + $this->newWidth = round(isset($this->newWidth) ? $this->newWidth : $this->crop['width']); + $this->newHeight = round(isset($this->newHeight) ? $this->newHeight : $this->crop['height']); + }*/ + + // No new height or width is set, use existing measures. + $this->newWidth = round(isset($this->newWidth) ? $this->newWidth : $this->width); + $this->newHeight = round(isset($this->newHeight) ? $this->newHeight : $this->height); + $this->log("Calculated new width x height as {$this->newWidth} x {$this->newHeight}."); + + return $this; + } + + + + /** + * Re-calculate image dimensions when original image dimension has changed. + * + * @return $this + */ + public function reCalculateDimensions() + { + $this->log("Re-calculate image dimensions, newWidth x newHeigh was: " . $this->newWidth . " x " . $this->newHeight); + + $this->newWidth = $this->newWidthOrig; + $this->newHeight = $this->newHeightOrig; + $this->crop = $this->cropOrig; + + $this->initDimensions() + ->calculateNewWidthAndHeight(); + + return $this; + } + + + + /** + * Set extension for filename to save as. + * + * @param string $saveas extension to save image as + * + * @return $this + */ + public function setSaveAsExtension($saveAs = null) + { + if (isset($saveAs)) { + $saveAs = strtolower($saveAs); + $this->checkFileExtension($saveAs); + $this->saveAs = $saveAs; + $this->extension = $saveAs; + } + + $this->log("Prepare to save image using as: " . $this->extension); + + return $this; + } + + + + /** + * Set JPEG quality to use when saving image + * + * @param int $quality as the quality to set. + * + * @return $this + */ + public function setJpegQuality($quality = null) + { + if ($quality) { + $this->useQuality = true; + } + + $this->quality = isset($quality) + ? $quality + : self::JPEG_QUALITY_DEFAULT; + + (is_numeric($this->quality) and $this->quality > 0 and $this->quality <= 100) + or $this->raiseError('Quality not in range.'); + + $this->log("Setting JPEG quality to {$this->quality}."); + + return $this; + } + + + + /** + * Set PNG compressen algorithm to use when saving image + * + * @param int $compress as the algorithm to use. + * + * @return $this + */ + public function setPngCompression($compress = null) + { + if ($compress) { + $this->useCompress = true; + } + + $this->compress = isset($compress) + ? $compress + : self::PNG_COMPRESSION_DEFAULT; + + (is_numeric($this->compress) and $this->compress >= -1 and $this->compress <= 9) + or $this->raiseError('Quality not in range.'); + + $this->log("Setting PNG compression level to {$this->compress}."); + + return $this; + } + + + + /** + * Use original image if possible, check options which affects image processing. + * + * @param boolean $useOrig default is to use original if possible, else set to false. + * + * @return $this + */ + public function useOriginalIfPossible($useOrig = true) + { + if ($useOrig + && ($this->newWidth == $this->width) + && ($this->newHeight == $this->height) + && !$this->area + && !$this->crop + && !$this->cropToFit + && !$this->fillToFit + && !$this->filters + && !$this->sharpen + && !$this->emboss + && !$this->blur + && !$this->convolve + && !$this->palette + && !$this->useQuality + && !$this->useCompress + && !$this->saveAs + && !$this->rotateBefore + && !$this->rotateAfter + && !$this->autoRotate + && !$this->bgColor + && ($this->upscale === self::UPSCALE_DEFAULT) + ) { + $this->log("Using original image."); + $this->output($this->pathToImage); + } + + return $this; + } + + + + /** + * Generate filename to save file in cache. + * + * @param string $base as basepath for storing file. + * + * @return $this + */ + public function generateFilename($base) + { + $parts = pathinfo($this->pathToImage); + $cropToFit = $this->cropToFit ? '_cf' : null; + $fillToFit = $this->fillToFit ? '_ff' : null; + $crop_x = $this->crop_x ? "_x{$this->crop_x}" : null; + $crop_y = $this->crop_y ? "_y{$this->crop_y}" : null; + $scale = $this->scale ? "_s{$this->scale}" : null; + $bgColor = $this->bgColor ? "_bgc{$this->bgColor}" : null; + $quality = $this->quality ? "_q{$this->quality}" : null; + $compress = $this->compress ? "_co{$this->compress}" : null; + $rotateBefore = $this->rotateBefore ? "_rb{$this->rotateBefore}" : null; + $rotateAfter = $this->rotateAfter ? "_ra{$this->rotateAfter}" : null; + + $width = $this->newWidth; + $height = $this->newHeight; + + $offset = isset($this->offset) + ? '_o' . $this->offset['top'] . '-' . $this->offset['right'] . '-' . $this->offset['bottom'] . '-' . $this->offset['left'] + : null; + + $crop = $this->crop + ? '_c' . $this->crop['width'] . '-' . $this->crop['height'] . '-' . $this->crop['start_x'] . '-' . $this->crop['start_y'] + : null; + + $filters = null; + if (isset($this->filters)) { + foreach ($this->filters as $filter) { + if (is_array($filter)) { + $filters .= "_f{$filter['id']}"; + for ($i=1; $i<=$filter['argc']; $i++) { + $filters .= ":".$filter["arg{$i}"]; + } + } + } + } + + $sharpen = $this->sharpen ? 's' : null; + $emboss = $this->emboss ? 'e' : null; + $blur = $this->blur ? 'b' : null; + $palette = $this->palette ? 'p' : null; + + $autoRotate = $this->autoRotate ? 'ar' : null; + + $this->extension = isset($this->extension) + ? $this->extension + : $parts['extension']; + + $optimize = null; + if ($this->extension == 'jpeg' || $this->extension == 'jpg') { + $optimize = $this->jpegOptimize ? 'o' : null; + } elseif ($this->extension == 'png') { + $optimize .= $this->pngFilter ? 'f' : null; + $optimize .= $this->pngDeflate ? 'd' : null; + } + + $convolve = null; + if ($this->convolve) { + $convolve = '_conv' . preg_replace('/[^a-zA-Z0-9]/', '', $this->convolve); + } + + $upscale = null; + if ($this->upscale !== self::UPSCALE_DEFAULT) { + $upscale = '_nu'; + } + + $subdir = str_replace('/', '-', dirname($this->imageSrc)); + $subdir = ($subdir == '.') ? '_.' : $subdir; + $file = $subdir . '_' . $parts['filename'] . '_' . $width . '_' + . $height . $offset . $crop . $cropToFit . $fillToFit + . $crop_x . $crop_y . $upscale + . $quality . $filters . $sharpen . $emboss . $blur . $palette . $optimize + . $scale . $rotateBefore . $rotateAfter . $autoRotate . $bgColor . $convolve + . '.' . $this->extension; + + return $this->setTarget($file, $base); + } + + + + /** + * Use cached version of image, if possible. + * + * @param boolean $useCache is default true, set to false to avoid using cached object. + * + * @return $this + */ + public function useCacheIfPossible($useCache = true) + { + if ($useCache && is_readable($this->cacheFileName)) { + $fileTime = filemtime($this->pathToImage); + $cacheTime = filemtime($this->cacheFileName); + + if ($fileTime <= $cacheTime) { + if ($this->useCache) { + if ($this->verbose) { + $this->log("Use cached file."); + $this->log("Cached image filesize: " . filesize($this->cacheFileName) . " bytes."); + } + $this->output($this->cacheFileName, $this->outputFormat); + } else { + $this->log("Cache is valid but ignoring it by intention."); + } + } else { + $this->log("Original file is modified, ignoring cache."); + } + } else { + $this->log("Cachefile does not exists or ignoring it."); + } + + return $this; + } + + + + /** + * Error message when failing to load somehow corrupt image. + * + * @return void + * + */ + public function failedToLoad() + { + header("HTTP/1.0 404 Not Found"); + echo("CImage.php says 404: Fatal error when opening image.
"); + + switch ($this->fileExtension) { + case 'jpg': + case 'jpeg': + $this->image = imagecreatefromjpeg($this->pathToImage); + break; + + case 'gif': + $this->image = imagecreatefromgif($this->pathToImage); + break; + + case 'png': + $this->image = imagecreatefrompng($this->pathToImage); + break; + } + + exit(); + } + + + + /** + * Load image from disk. + * + * @param string $src of image. + * @param string $dir as base directory where images are. + * + * @return $this + * + */ + public function load($src = null, $dir = null) + { + if (isset($src)) { + $this->setSource($src, $dir); + } + + $this->log("Opening file as {$this->fileExtension}."); + + switch ($this->fileExtension) { + case 'jpg': + case 'jpeg': + $this->image = @imagecreatefromjpeg($this->pathToImage); + $this->image or $this->failedToLoad(); + break; + + case 'gif': + $this->image = @imagecreatefromgif($this->pathToImage); + $this->image or $this->failedToLoad(); + break; + + case 'png': + $this->image = @imagecreatefrompng($this->pathToImage); + $this->image or $this->failedToLoad(); + + $type = $this->getPngType(); + $hasFewColors = imagecolorstotal($this->image); + + if ($type == self::PNG_RGB_PALETTE || ($hasFewColors > 0 && $hasFewColors <= 256)) { + if ($this->verbose) { + $this->log("Handle this image as a palette image."); + } + $this->palette = true; + } + break; + + default: + $this->image = false; + throw new Exception('No support for this file extension.'); + } + + if ($this->verbose) { + $this->log("imageistruecolor() : " . (imageistruecolor($this->image) ? 'true' : 'false')); + $this->log("imagecolorstotal() : " . imagecolorstotal($this->image)); + $this->log("Number of colors in image = " . $this->colorsTotal($this->image)); + $index = imagecolortransparent($this->image); + $this->log("Detected transparent color = " . ($index > 0 ? implode(", ", imagecolorsforindex($this->image, $index)) : "NONE") . " at index = $index"); + } + + return $this; + } + + + + /** + * Get the type of PNG image. + * + * @return int as the type of the png-image + * + */ + private function getPngType() + { + $pngType = ord(file_get_contents($this->pathToImage, false, null, 25, 1)); + + switch ($pngType) { + + case self::PNG_GREYSCALE: + $this->log("PNG is type 0, Greyscale."); + break; + + case self::PNG_RGB: + $this->log("PNG is type 2, RGB"); + break; + + case self::PNG_RGB_PALETTE: + $this->log("PNG is type 3, RGB with palette"); + break; + + case self::PNG_GREYSCALE_ALPHA: + $this->Log("PNG is type 4, Greyscale with alpha channel"); + break; + + case self::PNG_RGB_ALPHA: + $this->Log("PNG is type 6, RGB with alpha channel (PNG 32-bit)"); + break; + + default: + $this->Log("PNG is UNKNOWN type, is it really a PNG image?"); + } + + return $pngType; + } + + + + /** + * Calculate number of colors in an image. + * + * @param resource $im the image. + * + * @return int + */ + private function colorsTotal($im) + { + if (imageistruecolor($im)) { + $this->log("Colors as true color."); + $h = imagesy($im); + $w = imagesx($im); + $c = array(); + for ($x=0; $x < $w; $x++) { + for ($y=0; $y < $h; $y++) { + @$c['c'.imagecolorat($im, $x, $y)]++; + } + } + return count($c); + } else { + $this->log("Colors as palette."); + return imagecolorstotal($im); + } + } + + + + /** + * Preprocess image before rezising it. + * + * @return $this + */ + public function preResize() + { + $this->log("Pre-process before resizing"); + + // Rotate image + if ($this->rotateBefore) { + $this->log("Rotating image."); + $this->rotate($this->rotateBefore, $this->bgColor) + ->reCalculateDimensions(); + } + + // Auto-rotate image + if ($this->autoRotate) { + $this->log("Auto rotating image."); + $this->rotateExif() + ->reCalculateDimensions(); + } + + // Scale the original image before starting + if (isset($this->scale)) { + $this->log("Scale by {$this->scale}%"); + $newWidth = $this->width * $this->scale / 100; + $newHeight = $this->height * $this->scale / 100; + $img = $this->CreateImageKeepTransparency($newWidth, $newHeight); + imagecopyresampled($img, $this->image, 0, 0, 0, 0, $newWidth, $newHeight, $this->width, $this->height); + $this->image = $img; + $this->width = $newWidth; + $this->height = $newHeight; + } + + return $this; + } + + + + /** + * Resize and or crop the image. + * + * @return $this + */ + public function resize() + { + + $this->log("Starting to Resize()"); + $this->log("Upscale = '$this->upscale'"); + + // Only use a specified area of the image, $this->offset is defining the area to use + if (isset($this->offset)) { + + $this->log("Offset for area to use, cropping it width={$this->offset['width']}, height={$this->offset['height']}, start_x={$this->offset['left']}, start_y={$this->offset['top']}"); + $img = $this->CreateImageKeepTransparency($this->offset['width'], $this->offset['height']); + imagecopy($img, $this->image, 0, 0, $this->offset['left'], $this->offset['top'], $this->offset['width'], $this->offset['height']); + $this->image = $img; + $this->width = $this->offset['width']; + $this->height = $this->offset['height']; + } + + if ($this->crop) { + + // Do as crop, take only part of image + $this->log("Cropping area width={$this->crop['width']}, height={$this->crop['height']}, start_x={$this->crop['start_x']}, start_y={$this->crop['start_y']}"); + $img = $this->CreateImageKeepTransparency($this->crop['width'], $this->crop['height']); + imagecopy($img, $this->image, 0, 0, $this->crop['start_x'], $this->crop['start_y'], $this->crop['width'], $this->crop['height']); + $this->image = $img; + $this->width = $this->crop['width']; + $this->height = $this->crop['height']; + } + + if (!$this->upscale) { + // Consider rewriting the no-upscale code to fit within this if-statement, + // likely to be more readable code. + // The code is more or leass equal in below crop-to-fit, fill-to-fit and stretch + } + + if ($this->cropToFit) { + + // Resize by crop to fit + $this->log("Resizing using strategy - Crop to fit"); + + if (!$this->upscale && ($this->width < $this->newWidth || $this->height < $this->newHeight)) { + $this->log("Resizing - smaller image, do not upscale."); + + $cropX = round(($this->cropWidth/2) - ($this->newWidth/2)); + $cropY = round(($this->cropHeight/2) - ($this->newHeight/2)); + + $posX = 0; + $posY = 0; + + if ($this->newWidth > $this->width) { + $posX = round(($this->newWidth - $this->width) / 2); + } + + if ($this->newHeight > $this->height) { + $posY = round(($this->newHeight - $this->height) / 2); + } + + $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); + imagecopy($imageResized, $this->image, $posX, $posY, $cropX, $cropY, $this->newWidth, $this->newHeight); + } else { + $cropX = round(($this->cropWidth/2) - ($this->newWidth/2)); + $cropY = round(($this->cropHeight/2) - ($this->newHeight/2)); + $imgPreCrop = $this->CreateImageKeepTransparency($this->cropWidth, $this->cropHeight); + $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); + imagecopyresampled($imgPreCrop, $this->image, 0, 0, 0, 0, $this->cropWidth, $this->cropHeight, $this->width, $this->height); + imagecopy($imageResized, $imgPreCrop, 0, 0, $cropX, $cropY, $this->newWidth, $this->newHeight); + } + + $this->image = $imageResized; + $this->width = $this->newWidth; + $this->height = $this->newHeight; + + } else if ($this->fillToFit) { + + // Resize by fill to fit + $this->log("Resizing using strategy - Fill to fit"); + + $posX = 0; + $posY = 0; + + $ratioOrig = $this->width / $this->height; + $ratioNew = $this->newWidth / $this->newHeight; + + // Check ratio for landscape or portrait + if ($ratioOrig < $ratioNew) { + $posX = round(($this->newWidth - $this->fillWidth) / 2); + } else { + $posY = round(($this->newHeight - $this->fillHeight) / 2); + } + + if (!$this->upscale + && ($this->width < $this->newWidth || $this->height < $this->newHeight) + ) { + + $this->log("Resizing - smaller image, do not upscale."); + $posX = round(($this->fillWidth - $this->width) / 2); + $posY = round(($this->fillHeight - $this->height) / 2); + $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); + imagecopy($imageResized, $this->image, $posX, $posY, 0, 0, $this->fillWidth, $this->fillHeight); + + } else { + $imgPreFill = $this->CreateImageKeepTransparency($this->fillWidth, $this->fillHeight); + $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); + imagecopyresampled($imgPreFill, $this->image, 0, 0, 0, 0, $this->fillWidth, $this->fillHeight, $this->width, $this->height); + imagecopy($imageResized, $imgPreFill, $posX, $posY, 0, 0, $this->fillWidth, $this->fillHeight); + } + + $this->image = $imageResized; + $this->width = $this->newWidth; + $this->height = $this->newHeight; + + } else if (!($this->newWidth == $this->width && $this->newHeight == $this->height)) { + + // Resize it + $this->log("Resizing, new height and/or width"); + + if (!$this->upscale + && ($this->width < $this->newWidth || $this->height < $this->newHeight) + ) { + $this->log("Resizing - smaller image, do not upscale."); + + if (!$this->keepRatio) { + $this->log("Resizing - stretch to fit selected."); + + $posX = 0; + $posY = 0; + $cropX = 0; + $cropY = 0; + + if ($this->newWidth > $this->width && $this->newHeight > $this->height) { + $posX = round(($this->newWidth - $this->width) / 2); + $posY = round(($this->newHeight - $this->height) / 2); + } else if ($this->newWidth > $this->width) { + $posX = round(($this->newWidth - $this->width) / 2); + $cropY = round(($this->height - $this->newHeight) / 2); + } else if ($this->newHeight > $this->height) { + $posY = round(($this->newHeight - $this->height) / 2); + $cropX = round(($this->width - $this->newWidth) / 2); + } + + //$this->log("posX=$posX, posY=$posY, cropX=$cropX, cropY=$cropY."); + $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); + imagecopy($imageResized, $this->image, $posX, $posY, $cropX, $cropY, $this->newWidth, $this->newHeight); + $this->image = $imageResized; + $this->width = $this->newWidth; + $this->height = $this->newHeight; + } + } else { + $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); + imagecopyresampled($imageResized, $this->image, 0, 0, 0, 0, $this->newWidth, $this->newHeight, $this->width, $this->height); + $this->image = $imageResized; + $this->width = $this->newWidth; + $this->height = $this->newHeight; + } + } + + return $this; + } + + + + /** + * Postprocess image after rezising image. + * + * @return $this + */ + public function postResize() + { + $this->log("Post-process after resizing"); + + // Rotate image + if ($this->rotateAfter) { + $this->log("Rotating image."); + $this->rotate($this->rotateAfter, $this->bgColor); + } + + // Apply filters + if (isset($this->filters) && is_array($this->filters)) { + + foreach ($this->filters as $filter) { + $this->log("Applying filter {$filter['type']}."); + + switch ($filter['argc']) { + + case 0: + imagefilter($this->image, $filter['type']); + break; + + case 1: + imagefilter($this->image, $filter['type'], $filter['arg1']); + break; + + case 2: + imagefilter($this->image, $filter['type'], $filter['arg1'], $filter['arg2']); + break; + + case 3: + imagefilter($this->image, $filter['type'], $filter['arg1'], $filter['arg2'], $filter['arg3']); + break; + + case 4: + imagefilter($this->image, $filter['type'], $filter['arg1'], $filter['arg2'], $filter['arg3'], $filter['arg4']); + break; + } + } + } + + // Convert to palette image + if ($this->palette) { + $this->log("Converting to palette image."); + $this->trueColorToPalette(); + } + + // Blur the image + if ($this->blur) { + $this->log("Blur."); + $this->blurImage(); + } + + // Emboss the image + if ($this->emboss) { + $this->log("Emboss."); + $this->embossImage(); + } + + // Sharpen the image + if ($this->sharpen) { + $this->log("Sharpen."); + $this->sharpenImage(); + } + + // Custom convolution + if ($this->convolve) { + //$this->log("Convolve: " . $this->convolve); + $this->imageConvolution(); + } + + return $this; + } + + + + /** + * Rotate image using angle. + * + * @param float $angle to rotate image. + * @param int $anglebgColor to fill image with if needed. + * + * @return $this + */ + public function rotate($angle, $bgColor) + { + $this->log("Rotate image " . $angle . " degrees with filler color."); + + $color = $this->getBackgroundColor(); + $this->image = imagerotate($this->image, $angle, $color); + + $this->width = imagesx($this->image); + $this->height = imagesy($this->image); + + $this->log("New image dimension width x height: " . $this->width . " x " . $this->height); + + return $this; + } + + + + /** + * Rotate image using information in EXIF. + * + * @return $this + */ + public function rotateExif() + { + if (!in_array($this->fileExtension, array('jpg', 'jpeg'))) { + $this->log("Autorotate ignored, EXIF not supported by this filetype."); + return $this; + } + + $exif = exif_read_data($this->pathToImage); + + if (!empty($exif['Orientation'])) { + switch ($exif['Orientation']) { + case 3: + $this->log("Autorotate 180."); + $this->rotate(180, $this->bgColor); + break; + + case 6: + $this->log("Autorotate -90."); + $this->rotate(-90, $this->bgColor); + break; + + case 8: + $this->log("Autorotate 90."); + $this->rotate(90, $this->bgColor); + break; + + default: + $this->log("Autorotate ignored, unknown value as orientation."); + } + } else { + $this->log("Autorotate ignored, no orientation in EXIF."); + } + + return $this; + } + + + + /** + * Convert true color image to palette image, keeping alpha. + * http://stackoverflow.com/questions/5752514/how-to-convert-png-to-8-bit-png-using-php-gd-library + * + * @return void + */ + public function trueColorToPalette() + { + $img = imagecreatetruecolor($this->width, $this->height); + $bga = imagecolorallocatealpha($img, 0, 0, 0, 127); + imagecolortransparent($img, $bga); + imagefill($img, 0, 0, $bga); + imagecopy($img, $this->image, 0, 0, 0, 0, $this->width, $this->height); + imagetruecolortopalette($img, false, 255); + imagesavealpha($img, true); + + if (imageistruecolor($this->image)) { + $this->log("Matching colors with true color image."); + imagecolormatch($this->image, $img); + } + + $this->image = $img; + } + + + + /** + * Sharpen image using image convolution. + * + * @return $this + */ + public function sharpenImage() + { + $this->imageConvolution('sharpen'); + return $this; + } + + + + /** + * Emboss image using image convolution. + * + * @return $this + */ + public function embossImage() + { + $this->imageConvolution('emboss'); + return $this; + } + + + + /** + * Blur image using image convolution. + * + * @return $this + */ + public function blurImage() + { + $this->imageConvolution('blur'); + return $this; + } + + + + /** + * Create convolve expression and return arguments for image convolution. + * + * @param string $expression constant string which evaluates to a list of + * 11 numbers separated by komma or such a list. + * + * @return array as $matrix (3x3), $divisor and $offset + */ + public function createConvolveArguments($expression) + { + // Check of matching constant + if (isset($this->convolves[$expression])) { + $expression = $this->convolves[$expression]; + } + + $part = explode(',', $expression); + $this->log("Creating convolution expressen: $expression"); + + // Expect list of 11 numbers, split by , and build up arguments + if (count($part) != 11) { + throw new Exception( + "Missmatch in argument convolve. Expected comma-separated string with + 11 float values. Got $expression." + ); + } + + array_walk($part, function ($item, $key) { + if (!is_numeric($item)) { + throw new Exception("Argument to convolve expression should be float but is not."); + } + }); + + return array( + array( + array($part[0], $part[1], $part[2]), + array($part[3], $part[4], $part[5]), + array($part[6], $part[7], $part[8]), + ), + $part[9], + $part[10], + ); + } + + + + /** + * Add custom expressions (or overwrite existing) for image convolution. + * + * @param array $options Key value array with strings to be converted + * to convolution expressions. + * + * @return $this + */ + public function addConvolveExpressions($options) + { + $this->convolves = array_merge($this->convolves, $options); + return $this; + } + + + + /** + * Image convolution. + * + * @param string $options A string with 11 float separated by comma. + * + * @return $this + */ + public function imageConvolution($options = null) + { + // Use incoming options or use $this. + $options = $options ? $options : $this->convolve; + + // Treat incoming as string, split by + + $this->log("Convolution with '$options'"); + $options = explode(":", $options); + + // Check each option if it matches constant value + foreach ($options as $option) { + list($matrix, $divisor, $offset) = $this->createConvolveArguments($option); + imageconvolution($this->image, $matrix, $divisor, $offset); + } + + return $this; + } + + + + /** + * Set default background color between 000000-FFFFFF or if using + * alpha 00000000-FFFFFF7F. + * + * @param string $color as hex value. + * + * @return $this + */ + public function setDefaultBackgroundColor($color) + { + $this->log("Setting default background color to '$color'."); + + if (!(strlen($color) == 6 || strlen($color) == 8)) { + throw new Exception( + "Background color needs a hex value of 6 or 8 + digits. 000000-FFFFFF or 00000000-FFFFFF7F. + Current value was: '$color'." + ); + } + + $red = hexdec(substr($color, 0, 2)); + $green = hexdec(substr($color, 2, 2)); + $blue = hexdec(substr($color, 4, 2)); + + $alpha = (strlen($color) == 8) + ? hexdec(substr($color, 6, 2)) + : null; + + if (($red < 0 || $red > 255) + || ($green < 0 || $green > 255) + || ($blue < 0 || $blue > 255) + || ($alpha < 0 || $alpha > 127) + ) { + throw new Exception( + "Background color out of range. Red, green blue + should be 00-FF and alpha should be 00-7F. + Current value was: '$color'." + ); + } + + $this->bgColor = strtolower($color); + $this->bgColorDefault = array( + 'red' => $red, + 'green' => $green, + 'blue' => $blue, + 'alpha' => $alpha + ); + + return $this; + } + + + + /** + * Get the background color. + * + * @param resource $img the image to work with or null if using $this->image. + * + * @return color value or null if no background color is set. + */ + private function getBackgroundColor($img = null) + { + $img = isset($img) ? $img : $this->image; + + if ($this->bgColorDefault) { + + $red = $this->bgColorDefault['red']; + $green = $this->bgColorDefault['green']; + $blue = $this->bgColorDefault['blue']; + $alpha = $this->bgColorDefault['alpha']; + + if ($alpha) { + $color = imagecolorallocatealpha($img, $red, $green, $blue, $alpha); + } else { + $color = imagecolorallocate($img, $red, $green, $blue); + } + + return $color; + + } else { + return 0; + } + } + + + + /** + * Create a image and keep transparency for png and gifs. + * + * @param int $width of the new image. + * @param int $height of the new image. + * + * @return image resource. + */ + private function createImageKeepTransparency($width, $height) + { + $this->log("Creating a new working image width={$width}px, height={$height}px."); + $img = imagecreatetruecolor($width, $height); + imagealphablending($img, false); + imagesavealpha($img, true); + + $index = imagecolortransparent($this->image); + if ($index != -1) { + + imagealphablending($img, true); + $transparent = imagecolorsforindex($this->image, $index); + $color = imagecolorallocatealpha($img, $transparent['red'], $transparent['green'], $transparent['blue'], $transparent['alpha']); + imagefill($img, 0, 0, $color); + $index = imagecolortransparent($img, $color); + $this->Log("Detected transparent color = " . implode(", ", $transparent) . " at index = $index"); + + } elseif ($this->bgColorDefault) { + + $color = $this->getBackgroundColor($img); + imagefill($img, 0, 0, $color); + $this->Log("Filling image with background color."); + } + + return $img; + } + + + + /** + * Set optimizing and post-processing options. + * + * @param array $options with config for postprocessing with external tools. + * + * @return $this + */ + public function setPostProcessingOptions($options) + { + if (isset($options['jpeg_optimize']) && $options['jpeg_optimize']) { + $this->jpegOptimizeCmd = $options['jpeg_optimize_cmd']; + } else { + $this->jpegOptimizeCmd = null; + } + + if (isset($options['png_filter']) && $options['png_filter']) { + $this->pngFilterCmd = $options['png_filter_cmd']; + } else { + $this->pngFilterCmd = null; + } + + if (isset($options['png_deflate']) && $options['png_deflate']) { + $this->pngDeflateCmd = $options['png_deflate_cmd']; + } else { + $this->pngDeflateCmd = null; + } + + return $this; + } + + + + /** + * Save image. + * + * @param string $src as target filename. + * @param string $base as base directory where to store images. + * + * @return $this or false if no folder is set. + */ + public function save($src = null, $base = null) + { + if (isset($src)) { + $this->setTarget($src, $base); + } + + is_writable($this->saveFolder) + or $this->raiseError('Target directory is not writable.'); + + switch(strtolower($this->extension)) { + + case 'jpeg': + case 'jpg': + $this->Log("Saving image as JPEG to cache using quality = {$this->quality}."); + imagejpeg($this->image, $this->cacheFileName, $this->quality); + + // Use JPEG optimize if defined + if ($this->jpegOptimizeCmd) { + if ($this->verbose) { + clearstatcache(); + $this->log("Filesize before optimize: " . filesize($this->cacheFileName) . " bytes."); + } + $res = array(); + $cmd = $this->jpegOptimizeCmd . " -outfile $this->cacheFileName $this->cacheFileName"; + exec($cmd, $res); + $this->log($cmd); + $this->log($res); + } + break; + + case 'gif': + $this->Log("Saving image as GIF to cache."); + imagegif($this->image, $this->cacheFileName); + break; + + case 'png': + $this->Log("Saving image as PNG to cache using compression = {$this->compress}."); + + // Turn off alpha blending and set alpha flag + imagealphablending($this->image, false); + imagesavealpha($this->image, true); + imagepng($this->image, $this->cacheFileName, $this->compress); + + // Use external program to filter PNG, if defined + if ($this->pngFilterCmd) { + if ($this->verbose) { + clearstatcache(); + $this->Log("Filesize before filter optimize: " . filesize($this->cacheFileName) . " bytes."); + } + $res = array(); + $cmd = $this->pngFilterCmd . " $this->cacheFileName"; + exec($cmd, $res); + $this->Log($cmd); + $this->Log($res); + } + + // Use external program to deflate PNG, if defined + if ($this->pngDeflateCmd) { + if ($this->verbose) { + clearstatcache(); + $this->Log("Filesize before deflate optimize: " . filesize($this->cacheFileName) . " bytes."); + } + $res = array(); + $cmd = $this->pngDeflateCmd . " $this->cacheFileName"; + exec($cmd, $res); + $this->Log($cmd); + $this->Log($res); + } + break; + + default: + $this->RaiseError('No support for this file extension.'); + break; + } + + if ($this->verbose) { + clearstatcache(); + $this->log("Cached image filesize: " . filesize($this->cacheFileName) . " bytes."); + $this->log("imageistruecolor() : " . (imageistruecolor($this->image) ? 'true' : 'false')); + $this->log("imagecolorstotal() : " . imagecolorstotal($this->image)); + $this->log("Number of colors in image = " . $this->ColorsTotal($this->image)); + $index = imagecolortransparent($this->image); + $this->log("Detected transparent color = " . ($index > 0 ? implode(", ", imagecolorsforindex($this->image, $index)) : "NONE") . " at index = $index"); + } + + return $this; + } + + + + /** + * Create a hard link, as an alias, to the cached file. + * + * @param string $alias where to store the link, + * filename without extension. + * + * @return $this + */ + public function linkToCacheFile($alias) + { + if ($alias === null) { + $this->log("Ignore creating alias."); + return $this; + } + + $alias = $alias . "." . $this->extension; + + if (is_readable($alias)) { + unlink($alias); + } + + $res = link($this->cacheFileName, $alias); + + if ($res) { + $this->log("Created an alias as: $alias"); + } else { + $this->log("Failed to create the alias: $alias"); + } + + return $this; + } + + + + /** + * Output image to browser using caching. + * + * @param string $file to read and output, default is to use $this->cacheFileName + * @param string $format set to json to output file as json object with details + * + * @return void + */ + public function output($file = null, $format = null) + { + if (is_null($file)) { + $file = $this->cacheFileName; + } + + if (is_null($format)) { + $format = $this->outputFormat; + } + + $this->log("Output format is: $format"); + + if (!$this->verbose && $format == 'json') { + header('Content-type: application/json'); + echo $this->json($file); + exit; + } + + $this->log("Outputting image: $file"); + + // Get image modification time + clearstatcache(); + $lastModified = filemtime($file); + $gmdate = gmdate("D, d M Y H:i:s", $lastModified); + + if (!$this->verbose) { + header('Last-Modified: ' . $gmdate . " GMT"); + } + + if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $lastModified) { + + if ($this->verbose) { + $this->log("304 not modified"); + $this->verboseOutput(); + exit; + } + + header("HTTP/1.0 304 Not Modified"); + + } else { + + if ($this->verbose) { + $this->log("Last modified: " . $gmdate . " GMT"); + $this->verboseOutput(); + exit; + } + + // Get details on image + $info = getimagesize($file); + !empty($info) or $this->raiseError("The file doesn't seem to be an image."); + $mime = $info['mime']; + + header('Content-type: ' . $mime); + readfile($file); + } + + exit; + } + + + + /** + * Create a JSON object from the image details. + * + * @param string $file the file to output. + * + * @return string json-encoded representation of the image. + */ + public function json($file = null) + { + $file = $file ? $file : $this->cacheFileName; + + $details = array(); + + clearstatcache(); + + $details['src'] = $this->imageSrc; + $lastModified = filemtime($this->pathToImage); + $details['srcGmdate'] = gmdate("D, d M Y H:i:s", $lastModified); + + $details['cache'] = basename($this->cacheFileName); + $lastModified = filemtime($this->cacheFileName); + $details['cacheGmdate'] = gmdate("D, d M Y H:i:s", $lastModified); + + $this->loadImageDetails($file); + + $details['filename'] = basename($file); + $details['width'] = $this->width; + $details['height'] = $this->height; + $details['aspectRatio'] = round($this->width / $this->height, 3); + $details['size'] = filesize($file); + + $this->load($file); + $details['colors'] = $this->colorsTotal($this->image); + + $options = null; + if (defined("JSON_PRETTY_PRINT") && defined("JSON_UNESCAPED_SLASHES")) { + $options = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES; + } + + return json_encode($details, $options); + } + + + + /** + * Log an event if verbose mode. + * + * @param string $message to log. + * + * @return this + */ + public function log($message) + { + if ($this->verbose) { + $this->log[] = $message; + } + + return $this; + } + + + + /** + * Do verbose output and print out the log and the actual images. + * + * @return void + */ + private function verboseOutput() + { + $log = null; + $this->log("As JSON: \n" . $this->json()); + $this->log("Memory peak: " . round(memory_get_peak_usage() /1024/1024) . "M"); + $this->log("Memory limit: " . ini_get('memory_limit')); + + $included = get_included_files(); + $this->log("Included files: " . count($included)); + + foreach ($this->log as $val) { + if (is_array($val)) { + foreach ($val as $val1) { + $log .= htmlentities($val1) . '
'; + } + } else { + $log .= htmlentities($val) . '
'; + } + } + + echo << + + +CImage verbose output + +

CImage Verbose Output

+
{$log}
+EOD; + } + + + + /** + * Raise error, enables to implement a selection of error methods. + * + * @param string $message the error message to display. + * + * @return void + * @throws Exception + */ + private function raiseError($message) + { + throw new Exception($message); + } +} + + + +/** + * Resize and crop images on the fly, store generated images in a cache. + * + * @author Mikael Roos mos@dbwebb.se + * @example http://dbwebb.se/opensource/cimage + * @link https://github.com/mosbth/cimage + * + */ + +$version = "v0.7.0 (2015-02-10)"; + + + +/** + * Default configuration options, can be overridden in own config-file. + * + * @param string $msg to display. + * + * @return void + */ +function errorPage($msg) +{ + global $mode; + + header("HTTP/1.0 500 Internal Server Error"); + + if ($mode == 'development') { + die("[img.php] $msg"); + } else { + error_log("[img.php] $msg"); + die("HTTP/1.0 500 Internal Server Error"); + } +} + + + +/** + * Custom exception handler. + */ +set_exception_handler(function ($exception) { + errorPage("

img.php: Uncaught exception:

" . $exception->getMessage() . "

" . $exception->getTraceAsString(), "
"); +}); + + + +/** + * Get input from query string or return default value if not set. + * + * @param mixed $key as string or array of string values to look for in $_GET. + * @param mixed $default value to return when $key is not set in $_GET. + * + * @return mixed value from $_GET or default value. + */ +function get($key, $default = null) +{ + if (is_array($key)) { + foreach ($key as $val) { + if (isset($_GET[$val])) { + return $_GET[$val]; + } + } + } elseif (isset($_GET[$key])) { + return $_GET[$key]; + } + return $default; +} + + + +/** + * Get input from query string and set to $defined if defined or else $undefined. + * + * @param mixed $key as string or array of string values to look for in $_GET. + * @param mixed $defined value to return when $key is set in $_GET. + * @param mixed $undefined value to return when $key is not set in $_GET. + * + * @return mixed value as $defined or $undefined. + */ +function getDefined($key, $defined, $undefined) +{ + return get($key) === null ? $undefined : $defined; +} + + + +/** + * Get value from config array or default if key is not set in config array. + * + * @param string $key the key in the config array. + * @param mixed $default value to be default if $key is not set in config. + * + * @return mixed value as $config[$key] or $default. + */ +function getConfig($key, $default) +{ + global $config; + return isset($config[$key]) + ? $config[$key] + : $default; +} + + + +/** + * Log when verbose mode, when used without argument it returns the result. + * + * @param string $msg to log. + * + * @return void or array. + */ +function verbose($msg = null) +{ + global $verbose; + static $log = array(); + + if (!$verbose) { + return; + } + + if (is_null($msg)) { + return $log; + } + + $log[] = $msg; +} + + + +/** + * Get configuration options from file, if the file exists, else use $config + * if its defined or create an empty $config. + */ +$configFile = __DIR__.'/'.basename(__FILE__, '.php').'_config.php'; + +if (is_file($configFile)) { + $config = require $configFile; +} else if (!isset($config)) { + $config = array(); +} + + + +/** +* verbose, v - do a verbose dump of what happens +*/ +$verbose = getDefined(array('verbose', 'v'), true, false); +verbose("img.php version = $version"); + + + +/** + * Set mode as strict, production or development. + * Default is production environment. + */ +$mode = getConfig('mode', 'production'); + +// Settings for any mode +set_time_limit(20); +ini_set('gd.jpeg_ignore_warning', 1); + +if (!extension_loaded('gd')) { + errorPage("Extension gd is nod loaded."); +} + +// Specific settings for each mode +if ($mode == 'strict') { + + error_reporting(0); + ini_set('display_errors', 0); + ini_set('log_errors', 1); + $verbose = false; + +} else if ($mode == 'production') { + + error_reporting(-1); + ini_set('display_errors', 0); + ini_set('log_errors', 1); + $verbose = false; + +} else if ($mode == 'development') { + + error_reporting(-1); + ini_set('display_errors', 1); + ini_set('log_errors', 0); + +} else { + errorPage("Unknown mode: $mode"); +} + +verbose("mode = $mode"); +verbose("error log = " . ini_get('error_log')); + + + +/** + * Set default timezone if not set or if its set in the config-file. + */ +$defaultTimezone = getConfig('default_timezone', null); + +if ($defaultTimezone) { + date_default_timezone_set($defaultTimezone); +} else if (!ini_get('default_timezone')) { + date_default_timezone_set('UTC'); +} + + + +/** + * Check if passwords are configured, used and match. + * Options decide themself if they require passwords to be used. + */ +$pwdConfig = getConfig('password', false); +$pwdAlways = getConfig('password_always', false); +$pwd = get(array('password', 'pwd'), null); + +// Check if passwords match, if configured to use passwords +$passwordMatch = null; +if ($pwdAlways) { + + $passwordMatch = ($pwdConfig === $pwd); + if (!$passwordMatch) { + errorPage("Password required and does not match or exists."); + } + +} elseif ($pwdConfig && $pwd) { + + $passwordMatch = ($pwdConfig === $pwd); +} + +verbose("password match = $passwordMatch"); + + + +/** + * Prevent hotlinking, leeching, of images by controlling who access them + * from where. + * + */ +$allowHotlinking = getConfig('allow_hotlinking', true); +$hotlinkingWhitelist = getConfig('hotlinking_whitelist', array()); + +$serverName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null; +$referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null; +$refererHost = parse_url($referer, PHP_URL_HOST); + +if (!$allowHotlinking) { + if ($passwordMatch) { + ; // Always allow when password match + } else if ($passwordMatch === false) { + errorPage("Hotlinking/leeching not allowed when password missmatch."); + } else if (!$referer) { + errorPage("Hotlinking/leeching not allowed and referer is missing."); + } else if (strcmp($serverName, $refererHost) == 0) { + ; // Allow when serverName matches refererHost + } else if (!empty($hotlinkingWhitelist)) { + + $allowedByWhitelist = false; + foreach ($hotlinkingWhitelist as $val) { + if (preg_match($val, $refererHost)) { + $allowedByWhitelist = true; + } + } + + if (!$allowedByWhitelist) { + errorPage("Hotlinking/leeching not allowed by whitelist."); + } + + } else { + errorPage("Hotlinking/leeching not allowed."); + } +} + +verbose("allow_hotlinking = $allowHotlinking"); +verbose("referer = $referer"); +verbose("referer host = $refererHost"); + + + +/** + * Get the source files. + */ +$autoloader = getConfig('autoloader', false); +$cimageClass = getConfig('cimage_class', false); + +if ($autoloader) { + require $autoloader; +} else if ($cimageClass) { + require $cimageClass; +} + + + +/** + * Create the class for the image. + */ +$img = new CImage(); +$img->setVerbose($verbose); + + + +/** + * Allow or disallow remote download of images from other servers. + * Passwords apply if used. + * + */ +$allowRemote = getConfig('remote_allow', false); + +if ($allowRemote && $passwordMatch !== false) { + $pattern = getConfig('remote_pattern', null); + $img->setRemoteDownload($allowRemote, $pattern); +} + + + +/** + * shortcut, sc - extend arguments with a constant value, defined + * in config-file. + */ +$shortcut = get(array('shortcut', 'sc'), null); +$shortcutConfig = getConfig('shortcut', array( + 'sepia' => "&f=grayscale&f0=brightness,-10&f1=contrast,-20&f2=colorize,120,60,0,0&sharpen", +)); + +verbose("shortcut = $shortcut"); + +if (isset($shortcut) + && isset($shortcutConfig[$shortcut])) { + + parse_str($shortcutConfig[$shortcut], $get); + verbose("shortcut-constant = {$shortcutConfig[$shortcut]}"); + $_GET = array_merge($_GET, $get); +} + + + +/** + * src - the source image file. + */ +$srcImage = get('src') + or errorPage('Must set src-attribute.'); + +// Check for valid/invalid characters +$imagePath = getConfig('image_path', __DIR__ . '/img/'); +$imagePathConstraint = getConfig('image_path_constraint', true); +$validFilename = getConfig('valid_filename', '#^[a-z0-9A-Z-/_\.:]+$#'); + +preg_match($validFilename, $srcImage) + or errorPage('Filename contains invalid characters.'); + +if ($allowRemote && $img->isRemoteSource($srcImage)) { + + // If source is a remote file, ignore local file checks. + +} else if ($imagePathConstraint) { + + // Check that the image is a file below the directory 'image_path'. + $pathToImage = realpath($imagePath . $srcImage); + $imageDir = realpath($imagePath); + + is_file($pathToImage) + or errorPage( + 'Source image is not a valid file, check the filename and that a + matching file exists on the filesystem.' + ); + + substr_compare($imageDir, $pathToImage, 0, strlen($imageDir)) == 0 + or errorPage( + 'Security constraint: Source image is not below the directory "image_path" + as specified in the config file img_config.php.' + ); +} + +verbose("src = $srcImage"); + + + +/** + * Manage size constants from config file, use constants to replace values + * for width and height. + */ +$sizeConstant = getConfig('size_constant', function () { + + // Set sizes to map constant to value, easier to use with width or height + $sizes = array( + 'w1' => 613, + 'w2' => 630, + ); + + // Add grid column width, useful for use as predefined size for width (or height). + $gridColumnWidth = 30; + $gridGutterWidth = 10; + $gridColumns = 24; + + for ($i = 1; $i <= $gridColumns; $i++) { + $sizes['c' . $i] = ($gridColumnWidth + $gridGutterWidth) * $i - $gridGutterWidth; + } + + return $sizes; +}); + +$sizes = call_user_func($sizeConstant); + + + +/** + * width, w - set target width, affecting the resulting image width, height and resize options + */ +$newWidth = get(array('width', 'w')); +$maxWidth = getConfig('max_width', 2000); + +// Check to replace predefined size +if (isset($sizes[$newWidth])) { + $newWidth = $sizes[$newWidth]; +} + +// Support width as % of original width +if ($newWidth[strlen($newWidth)-1] == '%') { + is_numeric(substr($newWidth, 0, -1)) + or errorPage('Width % not numeric.'); +} else { + is_null($newWidth) + or ($newWidth > 10 && $newWidth <= $maxWidth) + or errorPage('Width out of range.'); +} + +verbose("new width = $newWidth"); + + + +/** + * height, h - set target height, affecting the resulting image width, height and resize options + */ +$newHeight = get(array('height', 'h')); +$maxHeight = getConfig('max_height', 2000); + +// Check to replace predefined size +if (isset($sizes[$newHeight])) { + $newHeight = $sizes[$newHeight]; +} + +// height +if ($newHeight[strlen($newHeight)-1] == '%') { + is_numeric(substr($newHeight, 0, -1)) + or errorPage('Height % out of range.'); +} else { + is_null($newHeight) + or ($newHeight > 10 && $newHeight <= $maxHeight) + or errorPage('Hight out of range.'); +} + +verbose("new height = $newHeight"); + + + +/** + * aspect-ratio, ar - affecting the resulting image width, height and resize options + */ +$aspectRatio = get(array('aspect-ratio', 'ar')); +$aspectRatioConstant = getConfig('aspect_ratio_constant', function () { + return array( + '3:1' => 3/1, + '3:2' => 3/2, + '4:3' => 4/3, + '8:5' => 8/5, + '16:10' => 16/10, + '16:9' => 16/9, + 'golden' => 1.618, + ); +}); + +// Check to replace predefined aspect ratio +$aspectRatios = call_user_func($aspectRatioConstant); +$negateAspectRatio = ($aspectRatio[0] == '!') ? true : false; +$aspectRatio = $negateAspectRatio ? substr($aspectRatio, 1) : $aspectRatio; + +if (isset($aspectRatios[$aspectRatio])) { + $aspectRatio = $aspectRatios[$aspectRatio]; +} + +if ($negateAspectRatio) { + $aspectRatio = 1 / $aspectRatio; +} + +is_null($aspectRatio) + or is_numeric($aspectRatio) + or errorPage('Aspect ratio out of range'); + +verbose("aspect ratio = $aspectRatio"); + + + +/** + * crop-to-fit, cf - affecting the resulting image width, height and resize options + */ +$cropToFit = getDefined(array('crop-to-fit', 'cf'), true, false); + +verbose("crop to fit = $cropToFit"); + + + +/** + * Set default background color from config file. + */ +$backgroundColor = getConfig('background_color', null); + +if ($backgroundColor) { + $img->setDefaultBackgroundColor($backgroundColor); + verbose("Using default background_color = $backgroundColor"); +} + + + +/** + * bgColor - Default background color to use + */ +$bgColor = get(array('bgColor', 'bg-color', 'bgc'), null); + +verbose("bgColor = $bgColor"); + + + +/** + * fill-to-fit, ff - affecting the resulting image width, height and resize options + */ +$fillToFit = get(array('fill-to-fit', 'ff'), null); + +verbose("fill-to-fit = $fillToFit"); + +if ($fillToFit !== null) { + + if (!empty($fillToFit)) { + $bgColor = $fillToFit; + verbose("fillToFit changed bgColor to = $bgColor"); + } + + $fillToFit = true; + verbose("fill-to-fit (fixed) = $fillToFit"); +} + + + +/** + * no-ratio, nr, stretch - affecting the resulting image width, height and resize options + */ +$keepRatio = getDefined(array('no-ratio', 'nr', 'stretch'), false, true); + +verbose("keep ratio = $keepRatio"); + + + +/** + * crop, c - affecting the resulting image width, height and resize options + */ +$crop = get(array('crop', 'c')); + +verbose("crop = $crop"); + + + +/** + * area, a - affecting the resulting image width, height and resize options + */ +$area = get(array('area', 'a')); + +verbose("area = $area"); + + + +/** + * skip-original, so - skip the original image and always process a new image + */ +$useOriginal = getDefined(array('skip-original', 'so'), false, true); + +verbose("use original = $useOriginal"); + + + +/** + * no-cache, nc - skip the cached version and process and create a new version in cache. + */ +$useCache = getDefined(array('no-cache', 'nc'), false, true); + +verbose("use cache = $useCache"); + + + +/** + * quality, q - set level of quality for jpeg images + */ +$quality = get(array('quality', 'q')); + +is_null($quality) + or ($quality > 0 and $quality <= 100) + or errorPage('Quality out of range'); + +verbose("quality = $quality"); + + + +/** + * compress, co - what strategy to use when compressing png images + */ +$compress = get(array('compress', 'co')); + + +is_null($compress) + or ($compress > 0 and $compress <= 9) + or errorPage('Compress out of range'); + +verbose("compress = $compress"); + + + +/** + * save-as, sa - what type of image to save + */ +$saveAs = get(array('save-as', 'sa')); + +verbose("save as = $saveAs"); + + + +/** + * scale, s - Processing option, scale up or down the image prior actual resize + */ +$scale = get(array('scale', 's')); + +is_null($scale) + or ($scale >= 0 and $scale <= 400) + or errorPage('Scale out of range'); + +verbose("scale = $scale"); + + + +/** + * palette, p - Processing option, create a palette version of the image + */ +$palette = getDefined(array('palette', 'p'), true, false); + +verbose("palette = $palette"); + + + +/** + * sharpen - Processing option, post filter for sharpen effect + */ +$sharpen = getDefined('sharpen', true, null); + +verbose("sharpen = $sharpen"); + + + +/** + * emboss - Processing option, post filter for emboss effect + */ +$emboss = getDefined('emboss', true, null); + +verbose("emboss = $emboss"); + + + +/** + * blur - Processing option, post filter for blur effect + */ +$blur = getDefined('blur', true, null); + +verbose("blur = $blur"); + + + +/** + * rotateBefore - Rotate the image with an angle, before processing + */ +$rotateBefore = get(array('rotateBefore', 'rotate-before', 'rb')); + +is_null($rotateBefore) + or ($rotateBefore >= -360 and $rotateBefore <= 360) + or errorPage('RotateBefore out of range'); + +verbose("rotateBefore = $rotateBefore"); + + + +/** + * rotateAfter - Rotate the image with an angle, before processing + */ +$rotateAfter = get(array('rotateAfter', 'rotate-after', 'ra', 'rotate', 'r')); + +is_null($rotateAfter) + or ($rotateAfter >= -360 and $rotateAfter <= 360) + or errorPage('RotateBefore out of range'); + +verbose("rotateAfter = $rotateAfter"); + + + +/** + * autoRotate - Auto rotate based on EXIF information + */ +$autoRotate = getDefined(array('autoRotate', 'auto-rotate', 'aro'), true, false); + +verbose("autoRotate = $autoRotate"); + + + +/** + * filter, f, f0-f9 - Processing option, post filter for various effects using imagefilter() + */ +$filters = array(); +$filter = get(array('filter', 'f')); +if ($filter) { + $filters[] = $filter; +} + +for ($i = 0; $i < 10; $i++) { + $filter = get(array("filter{$i}", "f{$i}")); + if ($filter) { + $filters[] = $filter; + } +} + +verbose("filters = " . print_r($filters, 1)); + + + +/** + * json - output the image as a JSON object with details on the image. + */ +$outputFormat = getDefined('json', 'json', null); + +verbose("json = $outputFormat"); + + + +/** + * dpr - change to get larger image to easier support larger dpr, such as retina. + */ +$dpr = get(array('ppi', 'dpr', 'device-pixel-ratio'), 1); + +verbose("dpr = $dpr"); + + + +/** + * convolve - image convolution as in http://php.net/manual/en/function.imageconvolution.php + */ +$convolve = get('convolve', null); +$convolutionConstant = getConfig('convolution_constant', array()); + +// Check if the convolve is matching an existing constant +if ($convolve && isset($convolutionConstant)) { + $img->addConvolveExpressions($convolutionConstant); + verbose("convolve constant = " . print_r($convolutionConstant, 1)); +} + +verbose("convolve = " . print_r($convolve, 1)); + + + +/** + * no-upscale, nu - Do not upscale smaller image to larger dimension. + */ +$upscale = getDefined(array('no-upscale', 'nu'), false, true); + +verbose("upscale = $upscale"); + + + +/** + * Get details for post processing + */ +$postProcessing = getConfig('postprocessing', array( + 'png_filter' => false, + 'png_filter_cmd' => '/usr/local/bin/optipng -q', + + 'png_deflate' => false, + 'png_deflate_cmd' => '/usr/local/bin/pngout -q', + + 'jpeg_optimize' => false, + 'jpeg_optimize_cmd' => '/usr/local/bin/jpegtran -copy none -optimize', +)); + + + +/** + * alias - Save resulting image to another alias name. + * Password always apply, must be defined. + */ +$alias = get('alias', null); +$aliasPath = getConfig('alias_path', null); +$validAliasname = getConfig('valid_aliasname', '#^[a-z0-9A-Z-_]+$#'); +$aliasTarget = null; + +if ($alias && $aliasPath && $passwordMatch) { + + $aliasTarget = $aliasPath . $alias; + $useCache = false; + + is_writable($aliasPath) + or errorPage("Directory for alias is not writable."); + + preg_match($validAliasname, $alias) + or errorPage('Filename for alias contains invalid characters. Do not add extension.'); + +} else if ($alias) { + errorPage('Alias is not enabled in the config file or password not matching.'); +} + +verbose("alias = $alias"); + + + +/** + * Display image if verbose mode + */ +if ($verbose) { + $query = array(); + parse_str($_SERVER['QUERY_STRING'], $query); + unset($query['verbose']); + unset($query['v']); + unset($query['nocache']); + unset($query['nc']); + unset($query['json']); + $url1 = '?' . htmlentities(urldecode(http_build_query($query))); + $url2 = '?' . urldecode(http_build_query($query)); + echo <<$url1
+ +

+
+
+
+EOD;
+}
+
+
+
+/**
+ * Get the cachepath from config.
+ */
+$cachePath = getConfig('cache_path', __DIR__ . '/../cache/');
+
+
+
+/**
+ * Load, process and output the image
+ */
+$img->log("Incoming arguments: " . print_r(verbose(), 1))
+    ->setSaveFolder($cachePath)
+    ->useCache($useCache)
+    ->setSource($srcImage, $imagePath)
+    ->setOptions(
+        array(
+            // Options for calculate dimensions
+            'newWidth'  => $newWidth,
+            'newHeight' => $newHeight,
+            'aspectRatio' => $aspectRatio,
+            'keepRatio' => $keepRatio,
+            'cropToFit' => $cropToFit,
+            'fillToFit' => $fillToFit,
+            'crop'      => $crop,
+            'area'      => $area,
+            'upscale'   => $upscale,
+
+            // Pre-processing, before resizing is done
+            'scale'        => $scale,
+            'rotateBefore' => $rotateBefore,
+            'autoRotate'   => $autoRotate,
+
+            // General processing options
+            'bgColor'    => $bgColor,
+
+            // Post-processing, after resizing is done
+            'palette'   => $palette,
+            'filters'   => $filters,
+            'sharpen'   => $sharpen,
+            'emboss'    => $emboss,
+            'blur'      => $blur,
+            'convolve'  => $convolve,
+            'rotateAfter' => $rotateAfter,
+
+            // Output format
+            'outputFormat' => $outputFormat,
+            'dpr'          => $dpr,
+        )
+    )
+    ->loadImageDetails()
+    ->initDimensions()
+    ->calculateNewWidthAndHeight()
+    ->setSaveAsExtension($saveAs)
+    ->setJpegQuality($quality)
+    ->setPngCompression($compress)
+    ->useOriginalIfPossible($useOriginal)
+    ->generateFilename($cachePath)
+    ->useCacheIfPossible($useCache)
+    ->load()
+    ->preResize()
+    ->resize()
+    ->postResize()
+    ->setPostProcessingOptions($postProcessing)
+    ->save()
+    ->linkToCacheFile($aliasTarget)
+    ->output();
+
+
+
+
diff --git a/docs/api/files/webroot%2Fimgs.php.txt b/docs/api/files/webroot%2Fimgs.php.txt
new file mode 100644
index 0000000..4489a24
--- /dev/null
+++ b/docs/api/files/webroot%2Fimgs.php.txt
@@ -0,0 +1,3919 @@
+ 'development',               // 'production', 'development', 'strict'
+    //'image_path'   =>  __DIR__ . '/img/',
+    //'cache_path'   =>  __DIR__ . '/../cache/',
+    //'alias_path'   =>  __DIR__ . '/img/alias/',
+    //'remote_allow' => true,
+    //'password'     => false,                      // "secret-password",
+
+);
+
+
+
+/**
+ * Get a image from a remote server using HTTP GET and If-Modified-Since.
+ *
+ */
+class CHttpGet
+{
+    private $request  = array();
+    private $response = array();
+
+
+
+    /**
+    * Constructor
+    *
+    */
+    public function __construct()
+    {
+        $this->request['header'] = array();
+    }
+
+
+
+    /**
+     * Set the url for the request.
+     *
+     * @param string $url
+     *
+     * @return $this
+     */
+    public function setUrl($url)
+    {
+        $this->request['url'] = $url;
+        return $this;
+    }
+
+
+
+    /**
+     * Set custom header field for the request.
+     *
+     * @param string $field
+     * @param string $value
+     *
+     * @return $this
+     */
+    public function setHeader($field, $value)
+    {
+        $this->request['header'][] = "$field: $value";
+        return $this;
+    }
+
+
+
+    /**
+     * Set header fields for the request.
+     *
+     * @param string $field
+     * @param string $value
+     *
+     * @return $this
+     */
+    public function parseHeader()
+    {
+        $header = explode("\r\n", rtrim($this->response['headerRaw'], "\r\n"));
+        $output = array();
+
+        if ('HTTP' === substr($header[0], 0, 4)) {
+            list($output['version'], $output['status']) = explode(' ', $header[0]);
+            unset($header[0]);
+        }
+
+        foreach ($header as $entry) {
+            $pos = strpos($entry, ':');
+            $output[trim(substr($entry, 0, $pos))] = trim(substr($entry, $pos + 1));
+        }
+
+        $this->response['header'] = $output;
+        return $this;
+    }
+
+
+
+    /**
+     * Perform the request.
+     *
+     * @param boolean $debug set to true to dump headers.
+     *
+     * @return boolean
+     */
+    public function doGet($debug = false)
+    {
+        $options = array(
+            CURLOPT_URL             => $this->request['url'],
+            CURLOPT_HEADER          => 1,
+            CURLOPT_HTTPHEADER      => $this->request['header'],
+            CURLOPT_AUTOREFERER     => true,
+            CURLOPT_RETURNTRANSFER  => true,
+            CURLINFO_HEADER_OUT     => $debug,
+            CURLOPT_CONNECTTIMEOUT  => 5,
+            CURLOPT_TIMEOUT         => 5,
+        );
+
+        $ch = curl_init();
+        curl_setopt_array($ch, $options);
+        $response = curl_exec($ch);
+
+        if (!$response) {
+            return false;
+        }
+
+        $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
+        $this->response['headerRaw'] = substr($response, 0, $headerSize);
+        $this->response['body']      = substr($response, $headerSize);
+
+        $this->parseHeader();
+
+        if ($debug) {
+            $info = curl_getinfo($ch);
+            echo "Request header
", var_dump($info['request_header']), "
"; + echo "Response header (raw)
", var_dump($this->response['headerRaw']), "
"; + echo "Response header (parsed)
", var_dump($this->response['header']), "
"; + } + + curl_close($ch); + return true; + } + + + + /** + * Get HTTP code of response. + * + * @return integer as HTTP status code or null if not available. + */ + public function getStatus() + { + return isset($this->response['header']['status']) + ? (int) $this->response['header']['status'] + : null; + } + + + + /** + * Get file modification time of response. + * + * @return int as timestamp. + */ + public function getLastModified() + { + return isset($this->response['header']['Last-Modified']) + ? strtotime($this->response['header']['Last-Modified']) + : null; + } + + + + /** + * Get content type. + * + * @return string as the content type or null if not existing or invalid. + */ + public function getContentType() + { + $type = isset($this->response['header']['Content-Type']) + ? $this->response['header']['Content-Type'] + : null; + + return preg_match('#[a-z]+/[a-z]+#', $type) + ? $type + : null; + } + + + + /** + * Get file modification time of response. + * + * @param mixed $default as default value (int seconds) if date is + * missing in response header. + * + * @return int as timestamp or $default if Date is missing in + * response header. + */ + public function getDate($default = false) + { + return isset($this->response['header']['Date']) + ? strtotime($this->response['header']['Date']) + : $default; + } + + + + /** + * Get max age of cachable item. + * + * @param mixed $default as default value if date is missing in response + * header. + * + * @return int as timestamp or false if not available. + */ + public function getMaxAge($default = false) + { + $cacheControl = isset($this->response['header']['Cache-Control']) + ? $this->response['header']['Cache-Control'] + : null; + + $maxAge = null; + if ($cacheControl) { + // max-age=2592000 + $part = explode('=', $cacheControl); + $maxAge = ($part[0] == "max-age") + ? (int) $part[1] + : null; + } + + if ($maxAge) { + return $maxAge; + } + + $expire = isset($this->response['header']['Expires']) + ? strtotime($this->response['header']['Expires']) + : null; + + return $expire ? $expire : $default; + } + + + + /** + * Get body of response. + * + * @return string as body. + */ + public function getBody() + { + return $this->response['body']; + } +} + + + +/** + * Get a image from a remote server using HTTP GET and If-Modified-Since. + * + */ +class CRemoteImage +{ + /** + * Path to cache files. + */ + private $saveFolder = null; + + + + /** + * Use cache or not. + */ + private $useCache = true; + + + + /** + * HTTP object to aid in download file. + */ + private $http; + + + + /** + * Status of the HTTP request. + */ + private $status; + + + + /** + * Defalt age for cached items 60*60*24*7. + */ + private $defaultMaxAge = 604800; + + + + /** + * Url of downloaded item. + */ + private $url; + + + + /** + * Base name of cache file for downloaded item. + */ + private $fileName; + + + + /** + * Filename for json-file with details of cached item. + */ + private $fileJson; + + + + /** + * Filename for image-file. + */ + private $fileImage; + + + + /** + * Cache details loaded from file. + */ + private $cache; + + + + /** + * Constructor + * + */ + public function __construct() + { + ; + } + + + /** + * Get status of last HTTP request. + * + * @return int as status + */ + public function getStatus() + { + return $this->status; + } + + + + /** + * Get JSON details for cache item. + * + * @return array with json details on cache. + */ + public function getDetails() + { + return $this->cache; + } + + + + /** + * Set the path to the cache directory. + * + * @param boolean $use true to use the cache and false to ignore cache. + * + * @return $this + */ + public function setCache($path) + { + $this->saveFolder = $path; + return $this; + } + + + + /** + * Check if cache is writable or throw exception. + * + * @return $this + * + * @throws Exception if cahce folder is not writable. + */ + public function isCacheWritable() + { + if (!is_writable($this->saveFolder)) { + throw new Exception("Cache folder is not writable for downloaded files."); + } + return $this; + } + + + + /** + * Decide if the cache should be used or not before trying to download + * a remote file. + * + * @param boolean $use true to use the cache and false to ignore cache. + * + * @return $this + */ + public function useCache($use = true) + { + $this->useCache = $use; + return $this; + } + + + + /** + * Translate a content type to a file extension. + * + * @param string $type a valid content type. + * + * @return string as file extension or false if no match. + */ + function contentTypeToFileExtension($type) { + $extension = array( + 'image/jpeg' => 'jpg', + 'image/png' => 'png', + 'image/gif' => 'gif', + ); + + return isset($extension[$type]) + ? $extension[$type] + : false; + } + + + + /** + * Set header fields. + * + * @return $this + */ + function setHeaderFields() { + $this->http->setHeader("User-Agent", "CImage/0.6 (PHP/". phpversion() . " cURL)"); + $this->http->setHeader("Accept", "image/jpeg,image/png,image/gif"); + + if ($this->useCache) { + $this->http->setHeader("Cache-Control", "max-age=0"); + } else { + $this->http->setHeader("Cache-Control", "no-cache"); + $this->http->setHeader("Pragma", "no-cache"); + } + } + + + + /** + * Save downloaded resource to cache. + * + * @return string as path to saved file or false if not saved. + */ + function save() { + + $this->cache = array(); + $date = $this->http->getDate(time()); + $maxAge = $this->http->getMaxAge($this->defaultMaxAge); + $lastModified = $this->http->getLastModified(); + $type = $this->http->getContentType(); + $extension = $this->contentTypeToFileExtension($type); + + $this->cache['Date'] = gmdate("D, d M Y H:i:s T", $date); + $this->cache['Max-Age'] = $maxAge; + $this->cache['Content-Type'] = $type; + $this->cache['File-Extension'] = $extension; + + if ($lastModified) { + $this->cache['Last-Modified'] = gmdate("D, d M Y H:i:s T", $lastModified); + } + + if ($extension) { + + $this->fileImage = $this->fileName . "." . $extension; + + // Save only if body is a valid image + $body = $this->http->getBody(); + $img = imagecreatefromstring($body); + + if ($img !== false) { + file_put_contents($this->fileImage, $body); + file_put_contents($this->fileJson, json_encode($this->cache)); + return $this->fileImage; + } + } + + return false; + } + + + + /** + * Got a 304 and updates cache with new age. + * + * @return string as path to cached file. + */ + function updateCacheDetails() { + + $date = $this->http->getDate(time()); + $maxAge = $this->http->getMaxAge($this->defaultMaxAge); + $lastModified = $this->http->getLastModified(); + + $this->cache['Date'] = gmdate("D, d M Y H:i:s T", $date); + $this->cache['Max-Age'] = $maxAge; + + if ($lastModified) { + $this->cache['Last-Modified'] = gmdate("D, d M Y H:i:s T", $lastModified); + } + + file_put_contents($this->fileJson, json_encode($this->cache)); + return $this->fileImage; + } + + + + /** + * Download a remote file and keep a cache of downloaded files. + * + * @param string $url a remote url. + * + * @return string as path to downloaded file or false if failed. + */ + function download($url) { + + $this->http = new CHttpGet(); + $this->url = $url; + + // First check if the cache is valid and can be used + $this->loadCacheDetails(); + + if ($this->useCache) { + $src = $this->getCachedSource(); + if ($src) { + $this->status = 1; + return $src; + } + } + + // Do a HTTP request to download item + $this->setHeaderFields(); + $this->http->setUrl($this->url); + $this->http->doGet(); + + $this->status = $this->http->getStatus(); + if ($this->status === 200) { + $this->isCacheWritable(); + return $this->save(); + } else if ($this->status === 304) { + $this->isCacheWritable(); + return $this->updateCacheDetails(); + } + + return false; + } + + + + /** + * Get the path to the cached image file if the cache is valid. + * + * @return $this + */ + public function loadCacheDetails() + { + $cacheFile = str_replace(array("/", ":", "#", ".", "?"), "-", $this->url); + $this->fileName = $this->saveFolder . $cacheFile; + $this->fileJson = $this->fileName . ".json"; + if (is_readable($this->fileJson)) { + $this->cache = json_decode(file_get_contents($this->fileJson), true); + } + } + + + + /** + * Get the path to the cached image file if the cache is valid. + * + * @return string as the path ot the image file or false if no cache. + */ + public function getCachedSource() + { + $this->fileImage = $this->fileName . "." . $this->cache['File-Extension']; + $imageExists = is_readable($this->fileImage); + + // Is cache valid? + $date = strtotime($this->cache['Date']); + $maxAge = $this->cache['Max-Age']; + $now = time(); + if ($imageExists && $date + $maxAge > $now) { + return $this->fileImage; + } + + // Prepare for a 304 if available + if ($imageExists && isset($this->cache['Last-Modified'])) { + $this->http->setHeader("If-Modified-Since", $this->cache['Last-Modified']); + } + + return false; + } +} + + + +/** + * Resize and crop images on the fly, store generated images in a cache. + * + * @author Mikael Roos mos@dbwebb.se + * @example http://dbwebb.se/opensource/cimage + * @link https://github.com/mosbth/cimage + */ +class CImage +{ + + /** + * Constants type of PNG image + */ + const PNG_GREYSCALE = 0; + const PNG_RGB = 2; + const PNG_RGB_PALETTE = 3; + const PNG_GREYSCALE_ALPHA = 4; + const PNG_RGB_ALPHA = 6; + + + + /** + * Constant for default image quality when not set + */ + const JPEG_QUALITY_DEFAULT = 60; + + + + /** + * Quality level for JPEG images. + */ + private $quality; + + + + /** + * Is the quality level set from external use (true) or is it default (false)? + */ + private $useQuality = false; + + + + /** + * Constant for default image quality when not set + */ + const PNG_COMPRESSION_DEFAULT = -1; + + + + /** + * Compression level for PNG images. + */ + private $compress; + + + + /** + * Is the compress level set from external use (true) or is it default (false)? + */ + private $useCompress = false; + + + + + /** + * Default background color, red, green, blue, alpha. + * + * @todo remake when upgrading to PHP 5.5 + */ + /* + const BACKGROUND_COLOR = array( + 'red' => 0, + 'green' => 0, + 'blue' => 0, + 'alpha' => null, + );*/ + + + + /** + * Default background color to use. + * + * @todo remake when upgrading to PHP 5.5 + */ + //private $bgColorDefault = self::BACKGROUND_COLOR; + private $bgColorDefault = array( + 'red' => 0, + 'green' => 0, + 'blue' => 0, + 'alpha' => null, + ); + + + /** + * Background color to use, specified as part of options. + */ + private $bgColor; + + + + /** + * Where to save the target file. + */ + private $saveFolder; + + + + /** + * The working image object. + */ + private $image; + + + + /** + * The root folder of images (only used in constructor to create $pathToImage?). + */ + private $imageFolder; + + + + /** + * Image filename, may include subdirectory, relative from $imageFolder + */ + private $imageSrc; + + + + /** + * Actual path to the image, $imageFolder . '/' . $imageSrc + */ + private $pathToImage; + + + + /** + * Original file extension + */ + private $fileExtension; + + + + /** + * File extension to use when saving image. + */ + private $extension; + + + + /** + * Output format, supports null (image) or json. + */ + private $outputFormat = null; + + + + /** + * Verbose mode to print out a trace and display the created image + */ + private $verbose = false; + + + + /** + * Keep a log/trace on what happens + */ + private $log = array(); + + + + /** + * Handle image as palette image + */ + private $palette; + + + + /** + * Target filename, with path, to save resulting image in. + */ + private $cacheFileName; + + + + /** + * Set a format to save image as, or null to use original format. + */ + private $saveAs; + + + /** + * Path to command for filter optimize, for example optipng or null. + */ + private $pngFilter; + + + + /** + * Path to command for deflate optimize, for example pngout or null. + */ + private $pngDeflate; + + + + /** + * Path to command to optimize jpeg images, for example jpegtran or null. + */ + private $jpegOptimize; + + + /** + * Image dimensions, calculated from loaded image. + */ + private $width; // Calculated from source image + private $height; // Calculated from source image + + + /** + * New image dimensions, incoming as argument or calculated. + */ + private $newWidth; + private $newWidthOrig; // Save original value + private $newHeight; + private $newHeightOrig; // Save original value + + + /** + * Change target height & width when different dpr, dpr 2 means double image dimensions. + */ + private $dpr = 1; + + + /** + * Always upscale images, even if they are smaller than target image. + */ + const UPSCALE_DEFAULT = true; + private $upscale = self::UPSCALE_DEFAULT; + + + + /** + * Array with details on how to crop, incoming as argument and calculated. + */ + public $crop; + public $cropOrig; // Save original value + + + /** + * String with details on how to do image convolution. String + * should map a key in the $convolvs array or be a string of + * 11 float values separated by comma. The first nine builds + * up the matrix, then divisor and last offset. + */ + private $convolve; + + + /** + * Custom convolution expressions, matrix 3x3, divisor and offset. + */ + private $convolves = array( + 'lighten' => '0,0,0, 0,12,0, 0,0,0, 9, 0', + 'darken' => '0,0,0, 0,6,0, 0,0,0, 9, 0', + 'sharpen' => '-1,-1,-1, -1,16,-1, -1,-1,-1, 8, 0', + 'sharpen-alt' => '0,-1,0, -1,5,-1, 0,-1,0, 1, 0', + 'emboss' => '1,1,-1, 1,3,-1, 1,-1,-1, 3, 0', + 'emboss-alt' => '-2,-1,0, -1,1,1, 0,1,2, 1, 0', + 'blur' => '1,1,1, 1,15,1, 1,1,1, 23, 0', + 'gblur' => '1,2,1, 2,4,2, 1,2,1, 16, 0', + 'edge' => '-1,-1,-1, -1,8,-1, -1,-1,-1, 9, 0', + 'edge-alt' => '0,1,0, 1,-4,1, 0,1,0, 1, 0', + 'draw' => '0,-1,0, -1,5,-1, 0,-1,0, 0, 0', + 'mean' => '1,1,1, 1,1,1, 1,1,1, 9, 0', + 'motion' => '1,0,0, 0,1,0, 0,0,1, 3, 0', + ); + + + /** + * Resize strategy to fill extra area with background color. + * True or false. + */ + private $fillToFit; + + + /** + * Used with option area to set which parts of the image to use. + */ + private $offset; + + + + /** + * Calculate target dimension for image when using fill-to-fit resize strategy. + */ + private $fillWidth; + private $fillHeight; + + + + /** + * Allow remote file download, default is to disallow remote file download. + */ + private $allowRemote = false; + + + + /** + * Pattern to recognize a remote file. + */ + //private $remotePattern = '#^[http|https]://#'; + private $remotePattern = '#^https?://#'; + + + + /** + * Use the cache if true, set to false to ignore the cached file. + */ + private $useCache = true; + + + /** + * Properties, the class is mutable and the method setOptions() + * decides (partly) what properties are created. + * + * @todo Clean up these and check if and how they are used + */ + + public $keepRatio; + public $cropToFit; + private $cropWidth; + private $cropHeight; + public $crop_x; + public $crop_y; + public $filters; + private $type; // Calculated from source image + private $attr; // Calculated from source image + private $useOriginal; // Use original image if possible + + + + + /** + * Constructor, can take arguments to init the object. + * + * @param string $imageSrc filename which may contain subdirectory. + * @param string $imageFolder path to root folder for images. + * @param string $saveFolder path to folder where to save the new file or null to skip saving. + * @param string $saveName name of target file when saveing. + */ + public function __construct($imageSrc = null, $imageFolder = null, $saveFolder = null, $saveName = null) + { + $this->setSource($imageSrc, $imageFolder); + $this->setTarget($saveFolder, $saveName); + } + + + + /** + * Set verbose mode. + * + * @param boolean $mode true or false to enable and disable verbose mode, + * default is true. + * + * @return $this + */ + public function setVerbose($mode = true) + { + $this->verbose = $mode; + return $this; + } + + + + /** + * Set save folder, base folder for saving cache files. + * + * @todo clean up how $this->saveFolder is used in other methods. + * + * @param string $path where to store cached files. + * + * @return $this + */ + public function setSaveFolder($path) + { + $this->saveFolder = $path; + return $this; + } + + + + /** + * Use cache or not. + * + * @todo clean up how $this->noCache is used in other methods. + * + * @param string $use true or false to use cache. + * + * @return $this + */ + public function useCache($use = true) + { + $this->useCache = $use; + return $this; + } + + + + /** + * Allow or disallow remote image download. + * + * @param boolean $allow true or false to enable and disable. + * @param string $pattern to use to detect if its a remote file. + * + * @return $this + */ + public function setRemoteDownload($allow, $pattern = null) + { + $this->allowRemote = $allow; + $this->remotePattern = $pattern ? $pattern : $this->remotePattern; + + $this->log("Set remote download to: " + . ($this->allowRemote ? "true" : "false") + . " using pattern " + . $this->remotePattern); + + return $this; + } + + + + /** + * Check if the image resource is a remote file or not. + * + * @param string $src check if src is remote. + * + * @return boolean true if $src is a remote file, else false. + */ + public function isRemoteSource($src) + { + $remote = preg_match($this->remotePattern, $src); + $this->log("Detected remote image: " . ($remote ? "true" : "false")); + return $remote; + } + + + + /** + * Check if file extension is valid as a file extension. + * + * @param string $extension of image file. + * + * @return $this + */ + private function checkFileExtension($extension) + { + $valid = array('jpg', 'jpeg', 'png', 'gif'); + + in_array(strtolower($extension), $valid) + or $this->raiseError('Not a valid file extension.'); + + return $this; + } + + + + /** + * Download a remote image and return path to its local copy. + * + * @param string $src remote path to image. + * + * @return string as path to downloaded remote source. + */ + public function downloadRemoteSource($src) + { + $remote = new CRemoteImage(); + $cache = $this->saveFolder . "/remote/"; + + if (!is_dir($cache)) { + if (!is_writable($this->saveFolder)) { + throw new Exception("Can not create remote cache, cachefolder not writable."); + } + mkdir($cache); + $this->log("The remote cache does not exists, creating it."); + } + + if (!is_writable($cache)) { + $this->log("The remote cache is not writable."); + } + + $remote->setCache($cache); + $remote->useCache($this->useCache); + $src = $remote->download($src); + + $this->log("Remote HTTP status: " . $remote->getStatus()); + $this->log("Remote item has local cached file: $src"); + $this->log("Remote details on cache:" . print_r($remote->getDetails(), true)); + + return $src; + } + + + + /** + * Set src file. + * + * @param string $src of image. + * @param string $dir as base directory where images are. + * + * @return $this + */ + public function setSource($src, $dir = null) + { + if (!isset($src)) { + return $this; + } + + if ($this->allowRemote && $this->isRemoteSource($src)) { + $src = $this->downloadRemoteSource($src); + $dir = null; + } + + if (!isset($dir)) { + $dir = dirname($src); + $src = basename($src); + } + + $this->imageSrc = ltrim($src, '/'); + $this->imageFolder = rtrim($dir, '/'); + $this->pathToImage = $this->imageFolder . '/' . $this->imageSrc; + $this->fileExtension = strtolower(pathinfo($this->pathToImage, PATHINFO_EXTENSION)); + //$this->extension = $this->fileExtension; + + $this->checkFileExtension($this->fileExtension); + + return $this; + } + + + + /** + * Set target file. + * + * @param string $src of target image. + * @param string $dir as base directory where images are stored. + * + * @return $this + */ + public function setTarget($src = null, $dir = null) + { + if (!(isset($src) && isset($dir))) { + return $this; + } + + $this->saveFolder = $dir; + $this->cacheFileName = $dir . '/' . $src; + + /* Allow readonly cache + is_writable($this->saveFolder) + or $this->raiseError('Target directory is not writable.'); + */ + + // Sanitize filename + $this->cacheFileName = preg_replace('/^a-zA-Z0-9\.-_/', '', $this->cacheFileName); + $this->log("The cache file name is: " . $this->cacheFileName); + + return $this; + } + + + + /** + * Set options to use when processing image. + * + * @param array $args used when processing image. + * + * @return $this + */ + public function setOptions($args) + { + $this->log("Set new options for processing image."); + + $defaults = array( + // Options for calculate dimensions + 'newWidth' => null, + 'newHeight' => null, + 'aspectRatio' => null, + 'keepRatio' => true, + 'cropToFit' => false, + 'fillToFit' => null, + 'crop' => null, //array('width'=>null, 'height'=>null, 'start_x'=>0, 'start_y'=>0), + 'area' => null, //'0,0,0,0', + 'upscale' => self::UPSCALE_DEFAULT, + + // Options for caching or using original + 'useCache' => true, + 'useOriginal' => true, + + // Pre-processing, before resizing is done + 'scale' => null, + 'rotateBefore' => null, + 'autoRotate' => false, + + // General options + 'bgColor' => null, + + // Post-processing, after resizing is done + 'palette' => null, + 'filters' => null, + 'sharpen' => null, + 'emboss' => null, + 'blur' => null, + 'convolve' => null, + 'rotateAfter' => null, + + // Output format + 'outputFormat' => null, + 'dpr' => 1, + + // Options for saving + //'quality' => null, + //'compress' => null, + //'saveAs' => null, + ); + + // Convert crop settings from string to array + if (isset($args['crop']) && !is_array($args['crop'])) { + $pices = explode(',', $args['crop']); + $args['crop'] = array( + 'width' => $pices[0], + 'height' => $pices[1], + 'start_x' => $pices[2], + 'start_y' => $pices[3], + ); + } + + // Convert area settings from string to array + if (isset($args['area']) && !is_array($args['area'])) { + $pices = explode(',', $args['area']); + $args['area'] = array( + 'top' => $pices[0], + 'right' => $pices[1], + 'bottom' => $pices[2], + 'left' => $pices[3], + ); + } + + // Convert filter settings from array of string to array of array + if (isset($args['filters']) && is_array($args['filters'])) { + foreach ($args['filters'] as $key => $filterStr) { + $parts = explode(',', $filterStr); + $filter = $this->mapFilter($parts[0]); + $filter['str'] = $filterStr; + for ($i=1; $i<=$filter['argc']; $i++) { + if (isset($parts[$i])) { + $filter["arg{$i}"] = $parts[$i]; + } else { + throw new Exception( + 'Missing arg to filter, review how many arguments are needed at + http://php.net/manual/en/function.imagefilter.php' + ); + } + } + $args['filters'][$key] = $filter; + } + } + + // Merge default arguments with incoming and set properties. + //$args = array_merge_recursive($defaults, $args); + $args = array_merge($defaults, $args); + foreach ($defaults as $key => $val) { + $this->{$key} = $args[$key]; + } + + if ($this->bgColor) { + $this->setDefaultBackgroundColor($this->bgColor); + } + + // Save original values to enable re-calculating + $this->newWidthOrig = $this->newWidth; + $this->newHeightOrig = $this->newHeight; + $this->cropOrig = $this->crop; + + return $this; + } + + + + /** + * Map filter name to PHP filter and id. + * + * @param string $name the name of the filter. + * + * @return array with filter settings + * @throws Exception + */ + private function mapFilter($name) + { + $map = array( + 'negate' => array('id'=>0, 'argc'=>0, 'type'=>IMG_FILTER_NEGATE), + 'grayscale' => array('id'=>1, 'argc'=>0, 'type'=>IMG_FILTER_GRAYSCALE), + 'brightness' => array('id'=>2, 'argc'=>1, 'type'=>IMG_FILTER_BRIGHTNESS), + 'contrast' => array('id'=>3, 'argc'=>1, 'type'=>IMG_FILTER_CONTRAST), + 'colorize' => array('id'=>4, 'argc'=>4, 'type'=>IMG_FILTER_COLORIZE), + 'edgedetect' => array('id'=>5, 'argc'=>0, 'type'=>IMG_FILTER_EDGEDETECT), + 'emboss' => array('id'=>6, 'argc'=>0, 'type'=>IMG_FILTER_EMBOSS), + 'gaussian_blur' => array('id'=>7, 'argc'=>0, 'type'=>IMG_FILTER_GAUSSIAN_BLUR), + 'selective_blur' => array('id'=>8, 'argc'=>0, 'type'=>IMG_FILTER_SELECTIVE_BLUR), + 'mean_removal' => array('id'=>9, 'argc'=>0, 'type'=>IMG_FILTER_MEAN_REMOVAL), + 'smooth' => array('id'=>10, 'argc'=>1, 'type'=>IMG_FILTER_SMOOTH), + 'pixelate' => array('id'=>11, 'argc'=>2, 'type'=>IMG_FILTER_PIXELATE), + ); + + if (isset($map[$name])) { + return $map[$name]; + } else { + throw new Exception('No such filter.'); + } + } + + + + /** + * Load image details from original image file. + * + * @param string $file the file to load or null to use $this->pathToImage. + * + * @return $this + * @throws Exception + */ + public function loadImageDetails($file = null) + { + $file = $file ? $file : $this->pathToImage; + + is_readable($file) + or $this->raiseError('Image file does not exist.'); + + // Get details on image + $info = list($this->width, $this->height, $this->type, $this->attr) = getimagesize($file); + !empty($info) or $this->raiseError("The file doesn't seem to be an image."); + + if ($this->verbose) { + $this->log("Image file: {$file}"); + $this->log("Image width x height (type): {$this->width} x {$this->height} ({$this->type})."); + $this->log("Image filesize: " . filesize($file) . " bytes."); + } + + return $this; + } + + + + /** + * Init new width and height and do some sanity checks on constraints, before any + * processing can be done. + * + * @return $this + * @throws Exception + */ + public function initDimensions() + { + $this->log("Init dimension (before) newWidth x newHeight is {$this->newWidth} x {$this->newHeight}."); + + // width as % + if ($this->newWidth[strlen($this->newWidth)-1] == '%') { + $this->newWidth = $this->width * substr($this->newWidth, 0, -1) / 100; + $this->log("Setting new width based on % to {$this->newWidth}"); + } + + // height as % + if ($this->newHeight[strlen($this->newHeight)-1] == '%') { + $this->newHeight = $this->height * substr($this->newHeight, 0, -1) / 100; + $this->log("Setting new height based on % to {$this->newHeight}"); + } + + is_null($this->aspectRatio) or is_numeric($this->aspectRatio) or $this->raiseError('Aspect ratio out of range'); + + // width & height from aspect ratio + if ($this->aspectRatio && is_null($this->newWidth) && is_null($this->newHeight)) { + if ($this->aspectRatio >= 1) { + $this->newWidth = $this->width; + $this->newHeight = $this->width / $this->aspectRatio; + $this->log("Setting new width & height based on width & aspect ratio (>=1) to (w x h) {$this->newWidth} x {$this->newHeight}"); + + } else { + $this->newHeight = $this->height; + $this->newWidth = $this->height * $this->aspectRatio; + $this->log("Setting new width & height based on width & aspect ratio (<1) to (w x h) {$this->newWidth} x {$this->newHeight}"); + } + + } elseif ($this->aspectRatio && is_null($this->newWidth)) { + $this->newWidth = $this->newHeight * $this->aspectRatio; + $this->log("Setting new width based on aspect ratio to {$this->newWidth}"); + + } elseif ($this->aspectRatio && is_null($this->newHeight)) { + $this->newHeight = $this->newWidth / $this->aspectRatio; + $this->log("Setting new height based on aspect ratio to {$this->newHeight}"); + } + + // Change width & height based on dpr + if ($this->dpr != 1) { + if (!is_null($this->newWidth)) { + $this->newWidth = round($this->newWidth * $this->dpr); + $this->log("Setting new width based on dpr={$this->dpr} - w={$this->newWidth}"); + } + if (!is_null($this->newHeight)) { + $this->newHeight = round($this->newHeight * $this->dpr); + $this->log("Setting new height based on dpr={$this->dpr} - h={$this->newHeight}"); + } + } + + // Check values to be within domain + is_null($this->newWidth) + or is_numeric($this->newWidth) + or $this->raiseError('Width not numeric'); + + is_null($this->newHeight) + or is_numeric($this->newHeight) + or $this->raiseError('Height not numeric'); + + $this->log("Init dimension (after) newWidth x newHeight is {$this->newWidth} x {$this->newHeight}."); + + return $this; + } + + + + /** + * Calculate new width and height of image, based on settings. + * + * @return $this + */ + public function calculateNewWidthAndHeight() + { + // Crop, use cropped width and height as base for calulations + $this->log("Calculate new width and height."); + $this->log("Original width x height is {$this->width} x {$this->height}."); + $this->log("Target dimension (before calculating) newWidth x newHeight is {$this->newWidth} x {$this->newHeight}."); + + // Check if there is an area to crop off + if (isset($this->area)) { + $this->offset['top'] = round($this->area['top'] / 100 * $this->height); + $this->offset['right'] = round($this->area['right'] / 100 * $this->width); + $this->offset['bottom'] = round($this->area['bottom'] / 100 * $this->height); + $this->offset['left'] = round($this->area['left'] / 100 * $this->width); + $this->offset['width'] = $this->width - $this->offset['left'] - $this->offset['right']; + $this->offset['height'] = $this->height - $this->offset['top'] - $this->offset['bottom']; + $this->width = $this->offset['width']; + $this->height = $this->offset['height']; + $this->log("The offset for the area to use is top {$this->area['top']}%, right {$this->area['right']}%, bottom {$this->area['bottom']}%, left {$this->area['left']}%."); + $this->log("The offset for the area to use is top {$this->offset['top']}px, right {$this->offset['right']}px, bottom {$this->offset['bottom']}px, left {$this->offset['left']}px, width {$this->offset['width']}px, height {$this->offset['height']}px."); + } + + $width = $this->width; + $height = $this->height; + + // Check if crop is set + if ($this->crop) { + $width = $this->crop['width'] = $this->crop['width'] <= 0 ? $this->width + $this->crop['width'] : $this->crop['width']; + $height = $this->crop['height'] = $this->crop['height'] <= 0 ? $this->height + $this->crop['height'] : $this->crop['height']; + + if ($this->crop['start_x'] == 'left') { + $this->crop['start_x'] = 0; + } elseif ($this->crop['start_x'] == 'right') { + $this->crop['start_x'] = $this->width - $width; + } elseif ($this->crop['start_x'] == 'center') { + $this->crop['start_x'] = round($this->width / 2) - round($width / 2); + } + + if ($this->crop['start_y'] == 'top') { + $this->crop['start_y'] = 0; + } elseif ($this->crop['start_y'] == 'bottom') { + $this->crop['start_y'] = $this->height - $height; + } elseif ($this->crop['start_y'] == 'center') { + $this->crop['start_y'] = round($this->height / 2) - round($height / 2); + } + + $this->log("Crop area is width {$width}px, height {$height}px, start_x {$this->crop['start_x']}px, start_y {$this->crop['start_y']}px."); + } + + // Calculate new width and height if keeping aspect-ratio. + if ($this->keepRatio) { + + $this->log("Keep aspect ratio."); + + // Crop-to-fit and both new width and height are set. + if (($this->cropToFit || $this->fillToFit) && isset($this->newWidth) && isset($this->newHeight)) { + + // Use newWidth and newHeigh as width/height, image should fit in box. + $this->log("Use newWidth and newHeigh as width/height, image should fit in box."); + + } elseif (isset($this->newWidth) && isset($this->newHeight)) { + + // Both new width and height are set. + // Use newWidth and newHeigh as max width/height, image should not be larger. + $ratioWidth = $width / $this->newWidth; + $ratioHeight = $height / $this->newHeight; + $ratio = ($ratioWidth > $ratioHeight) ? $ratioWidth : $ratioHeight; + $this->newWidth = round($width / $ratio); + $this->newHeight = round($height / $ratio); + $this->log("New width and height was set."); + + } elseif (isset($this->newWidth)) { + + // Use new width as max-width + $factor = (float)$this->newWidth / (float)$width; + $this->newHeight = round($factor * $height); + $this->log("New width was set."); + + } elseif (isset($this->newHeight)) { + + // Use new height as max-hight + $factor = (float)$this->newHeight / (float)$height; + $this->newWidth = round($factor * $width); + $this->log("New height was set."); + + } + + // Get image dimensions for pre-resize image. + if ($this->cropToFit || $this->fillToFit) { + + // Get relations of original & target image + $ratioWidth = $width / $this->newWidth; + $ratioHeight = $height / $this->newHeight; + + if ($this->cropToFit) { + + // Use newWidth and newHeigh as defined width/height, + // image should fit the area. + $this->log("Crop to fit."); + $ratio = ($ratioWidth < $ratioHeight) ? $ratioWidth : $ratioHeight; + $this->cropWidth = round($width / $ratio); + $this->cropHeight = round($height / $ratio); + $this->log("Crop width, height, ratio: $this->cropWidth x $this->cropHeight ($ratio)."); + + } else if ($this->fillToFit) { + + // Use newWidth and newHeigh as defined width/height, + // image should fit the area. + $this->log("Fill to fit."); + $ratio = ($ratioWidth < $ratioHeight) ? $ratioHeight : $ratioWidth; + $this->fillWidth = round($width / $ratio); + $this->fillHeight = round($height / $ratio); + $this->log("Fill width, height, ratio: $this->fillWidth x $this->fillHeight ($ratio)."); + } + } + } + + // Crop, ensure to set new width and height + if ($this->crop) { + $this->log("Crop."); + $this->newWidth = round(isset($this->newWidth) ? $this->newWidth : $this->crop['width']); + $this->newHeight = round(isset($this->newHeight) ? $this->newHeight : $this->crop['height']); + } + + // Fill to fit, ensure to set new width and height + /*if ($this->fillToFit) { + $this->log("FillToFit."); + $this->newWidth = round(isset($this->newWidth) ? $this->newWidth : $this->crop['width']); + $this->newHeight = round(isset($this->newHeight) ? $this->newHeight : $this->crop['height']); + }*/ + + // No new height or width is set, use existing measures. + $this->newWidth = round(isset($this->newWidth) ? $this->newWidth : $this->width); + $this->newHeight = round(isset($this->newHeight) ? $this->newHeight : $this->height); + $this->log("Calculated new width x height as {$this->newWidth} x {$this->newHeight}."); + + return $this; + } + + + + /** + * Re-calculate image dimensions when original image dimension has changed. + * + * @return $this + */ + public function reCalculateDimensions() + { + $this->log("Re-calculate image dimensions, newWidth x newHeigh was: " . $this->newWidth . " x " . $this->newHeight); + + $this->newWidth = $this->newWidthOrig; + $this->newHeight = $this->newHeightOrig; + $this->crop = $this->cropOrig; + + $this->initDimensions() + ->calculateNewWidthAndHeight(); + + return $this; + } + + + + /** + * Set extension for filename to save as. + * + * @param string $saveas extension to save image as + * + * @return $this + */ + public function setSaveAsExtension($saveAs = null) + { + if (isset($saveAs)) { + $saveAs = strtolower($saveAs); + $this->checkFileExtension($saveAs); + $this->saveAs = $saveAs; + $this->extension = $saveAs; + } + + $this->log("Prepare to save image using as: " . $this->extension); + + return $this; + } + + + + /** + * Set JPEG quality to use when saving image + * + * @param int $quality as the quality to set. + * + * @return $this + */ + public function setJpegQuality($quality = null) + { + if ($quality) { + $this->useQuality = true; + } + + $this->quality = isset($quality) + ? $quality + : self::JPEG_QUALITY_DEFAULT; + + (is_numeric($this->quality) and $this->quality > 0 and $this->quality <= 100) + or $this->raiseError('Quality not in range.'); + + $this->log("Setting JPEG quality to {$this->quality}."); + + return $this; + } + + + + /** + * Set PNG compressen algorithm to use when saving image + * + * @param int $compress as the algorithm to use. + * + * @return $this + */ + public function setPngCompression($compress = null) + { + if ($compress) { + $this->useCompress = true; + } + + $this->compress = isset($compress) + ? $compress + : self::PNG_COMPRESSION_DEFAULT; + + (is_numeric($this->compress) and $this->compress >= -1 and $this->compress <= 9) + or $this->raiseError('Quality not in range.'); + + $this->log("Setting PNG compression level to {$this->compress}."); + + return $this; + } + + + + /** + * Use original image if possible, check options which affects image processing. + * + * @param boolean $useOrig default is to use original if possible, else set to false. + * + * @return $this + */ + public function useOriginalIfPossible($useOrig = true) + { + if ($useOrig + && ($this->newWidth == $this->width) + && ($this->newHeight == $this->height) + && !$this->area + && !$this->crop + && !$this->cropToFit + && !$this->fillToFit + && !$this->filters + && !$this->sharpen + && !$this->emboss + && !$this->blur + && !$this->convolve + && !$this->palette + && !$this->useQuality + && !$this->useCompress + && !$this->saveAs + && !$this->rotateBefore + && !$this->rotateAfter + && !$this->autoRotate + && !$this->bgColor + && ($this->upscale === self::UPSCALE_DEFAULT) + ) { + $this->log("Using original image."); + $this->output($this->pathToImage); + } + + return $this; + } + + + + /** + * Generate filename to save file in cache. + * + * @param string $base as basepath for storing file. + * + * @return $this + */ + public function generateFilename($base) + { + $parts = pathinfo($this->pathToImage); + $cropToFit = $this->cropToFit ? '_cf' : null; + $fillToFit = $this->fillToFit ? '_ff' : null; + $crop_x = $this->crop_x ? "_x{$this->crop_x}" : null; + $crop_y = $this->crop_y ? "_y{$this->crop_y}" : null; + $scale = $this->scale ? "_s{$this->scale}" : null; + $bgColor = $this->bgColor ? "_bgc{$this->bgColor}" : null; + $quality = $this->quality ? "_q{$this->quality}" : null; + $compress = $this->compress ? "_co{$this->compress}" : null; + $rotateBefore = $this->rotateBefore ? "_rb{$this->rotateBefore}" : null; + $rotateAfter = $this->rotateAfter ? "_ra{$this->rotateAfter}" : null; + + $width = $this->newWidth; + $height = $this->newHeight; + + $offset = isset($this->offset) + ? '_o' . $this->offset['top'] . '-' . $this->offset['right'] . '-' . $this->offset['bottom'] . '-' . $this->offset['left'] + : null; + + $crop = $this->crop + ? '_c' . $this->crop['width'] . '-' . $this->crop['height'] . '-' . $this->crop['start_x'] . '-' . $this->crop['start_y'] + : null; + + $filters = null; + if (isset($this->filters)) { + foreach ($this->filters as $filter) { + if (is_array($filter)) { + $filters .= "_f{$filter['id']}"; + for ($i=1; $i<=$filter['argc']; $i++) { + $filters .= ":".$filter["arg{$i}"]; + } + } + } + } + + $sharpen = $this->sharpen ? 's' : null; + $emboss = $this->emboss ? 'e' : null; + $blur = $this->blur ? 'b' : null; + $palette = $this->palette ? 'p' : null; + + $autoRotate = $this->autoRotate ? 'ar' : null; + + $this->extension = isset($this->extension) + ? $this->extension + : $parts['extension']; + + $optimize = null; + if ($this->extension == 'jpeg' || $this->extension == 'jpg') { + $optimize = $this->jpegOptimize ? 'o' : null; + } elseif ($this->extension == 'png') { + $optimize .= $this->pngFilter ? 'f' : null; + $optimize .= $this->pngDeflate ? 'd' : null; + } + + $convolve = null; + if ($this->convolve) { + $convolve = '_conv' . preg_replace('/[^a-zA-Z0-9]/', '', $this->convolve); + } + + $upscale = null; + if ($this->upscale !== self::UPSCALE_DEFAULT) { + $upscale = '_nu'; + } + + $subdir = str_replace('/', '-', dirname($this->imageSrc)); + $subdir = ($subdir == '.') ? '_.' : $subdir; + $file = $subdir . '_' . $parts['filename'] . '_' . $width . '_' + . $height . $offset . $crop . $cropToFit . $fillToFit + . $crop_x . $crop_y . $upscale + . $quality . $filters . $sharpen . $emboss . $blur . $palette . $optimize + . $scale . $rotateBefore . $rotateAfter . $autoRotate . $bgColor . $convolve + . '.' . $this->extension; + + return $this->setTarget($file, $base); + } + + + + /** + * Use cached version of image, if possible. + * + * @param boolean $useCache is default true, set to false to avoid using cached object. + * + * @return $this + */ + public function useCacheIfPossible($useCache = true) + { + if ($useCache && is_readable($this->cacheFileName)) { + $fileTime = filemtime($this->pathToImage); + $cacheTime = filemtime($this->cacheFileName); + + if ($fileTime <= $cacheTime) { + if ($this->useCache) { + if ($this->verbose) { + $this->log("Use cached file."); + $this->log("Cached image filesize: " . filesize($this->cacheFileName) . " bytes."); + } + $this->output($this->cacheFileName, $this->outputFormat); + } else { + $this->log("Cache is valid but ignoring it by intention."); + } + } else { + $this->log("Original file is modified, ignoring cache."); + } + } else { + $this->log("Cachefile does not exists or ignoring it."); + } + + return $this; + } + + + + /** + * Error message when failing to load somehow corrupt image. + * + * @return void + * + */ + public function failedToLoad() + { + header("HTTP/1.0 404 Not Found"); + echo("CImage.php says 404: Fatal error when opening image.
"); + + switch ($this->fileExtension) { + case 'jpg': + case 'jpeg': + $this->image = imagecreatefromjpeg($this->pathToImage); + break; + + case 'gif': + $this->image = imagecreatefromgif($this->pathToImage); + break; + + case 'png': + $this->image = imagecreatefrompng($this->pathToImage); + break; + } + + exit(); + } + + + + /** + * Load image from disk. + * + * @param string $src of image. + * @param string $dir as base directory where images are. + * + * @return $this + * + */ + public function load($src = null, $dir = null) + { + if (isset($src)) { + $this->setSource($src, $dir); + } + + $this->log("Opening file as {$this->fileExtension}."); + + switch ($this->fileExtension) { + case 'jpg': + case 'jpeg': + $this->image = @imagecreatefromjpeg($this->pathToImage); + $this->image or $this->failedToLoad(); + break; + + case 'gif': + $this->image = @imagecreatefromgif($this->pathToImage); + $this->image or $this->failedToLoad(); + break; + + case 'png': + $this->image = @imagecreatefrompng($this->pathToImage); + $this->image or $this->failedToLoad(); + + $type = $this->getPngType(); + $hasFewColors = imagecolorstotal($this->image); + + if ($type == self::PNG_RGB_PALETTE || ($hasFewColors > 0 && $hasFewColors <= 256)) { + if ($this->verbose) { + $this->log("Handle this image as a palette image."); + } + $this->palette = true; + } + break; + + default: + $this->image = false; + throw new Exception('No support for this file extension.'); + } + + if ($this->verbose) { + $this->log("imageistruecolor() : " . (imageistruecolor($this->image) ? 'true' : 'false')); + $this->log("imagecolorstotal() : " . imagecolorstotal($this->image)); + $this->log("Number of colors in image = " . $this->colorsTotal($this->image)); + $index = imagecolortransparent($this->image); + $this->log("Detected transparent color = " . ($index > 0 ? implode(", ", imagecolorsforindex($this->image, $index)) : "NONE") . " at index = $index"); + } + + return $this; + } + + + + /** + * Get the type of PNG image. + * + * @return int as the type of the png-image + * + */ + private function getPngType() + { + $pngType = ord(file_get_contents($this->pathToImage, false, null, 25, 1)); + + switch ($pngType) { + + case self::PNG_GREYSCALE: + $this->log("PNG is type 0, Greyscale."); + break; + + case self::PNG_RGB: + $this->log("PNG is type 2, RGB"); + break; + + case self::PNG_RGB_PALETTE: + $this->log("PNG is type 3, RGB with palette"); + break; + + case self::PNG_GREYSCALE_ALPHA: + $this->Log("PNG is type 4, Greyscale with alpha channel"); + break; + + case self::PNG_RGB_ALPHA: + $this->Log("PNG is type 6, RGB with alpha channel (PNG 32-bit)"); + break; + + default: + $this->Log("PNG is UNKNOWN type, is it really a PNG image?"); + } + + return $pngType; + } + + + + /** + * Calculate number of colors in an image. + * + * @param resource $im the image. + * + * @return int + */ + private function colorsTotal($im) + { + if (imageistruecolor($im)) { + $this->log("Colors as true color."); + $h = imagesy($im); + $w = imagesx($im); + $c = array(); + for ($x=0; $x < $w; $x++) { + for ($y=0; $y < $h; $y++) { + @$c['c'.imagecolorat($im, $x, $y)]++; + } + } + return count($c); + } else { + $this->log("Colors as palette."); + return imagecolorstotal($im); + } + } + + + + /** + * Preprocess image before rezising it. + * + * @return $this + */ + public function preResize() + { + $this->log("Pre-process before resizing"); + + // Rotate image + if ($this->rotateBefore) { + $this->log("Rotating image."); + $this->rotate($this->rotateBefore, $this->bgColor) + ->reCalculateDimensions(); + } + + // Auto-rotate image + if ($this->autoRotate) { + $this->log("Auto rotating image."); + $this->rotateExif() + ->reCalculateDimensions(); + } + + // Scale the original image before starting + if (isset($this->scale)) { + $this->log("Scale by {$this->scale}%"); + $newWidth = $this->width * $this->scale / 100; + $newHeight = $this->height * $this->scale / 100; + $img = $this->CreateImageKeepTransparency($newWidth, $newHeight); + imagecopyresampled($img, $this->image, 0, 0, 0, 0, $newWidth, $newHeight, $this->width, $this->height); + $this->image = $img; + $this->width = $newWidth; + $this->height = $newHeight; + } + + return $this; + } + + + + /** + * Resize and or crop the image. + * + * @return $this + */ + public function resize() + { + + $this->log("Starting to Resize()"); + $this->log("Upscale = '$this->upscale'"); + + // Only use a specified area of the image, $this->offset is defining the area to use + if (isset($this->offset)) { + + $this->log("Offset for area to use, cropping it width={$this->offset['width']}, height={$this->offset['height']}, start_x={$this->offset['left']}, start_y={$this->offset['top']}"); + $img = $this->CreateImageKeepTransparency($this->offset['width'], $this->offset['height']); + imagecopy($img, $this->image, 0, 0, $this->offset['left'], $this->offset['top'], $this->offset['width'], $this->offset['height']); + $this->image = $img; + $this->width = $this->offset['width']; + $this->height = $this->offset['height']; + } + + if ($this->crop) { + + // Do as crop, take only part of image + $this->log("Cropping area width={$this->crop['width']}, height={$this->crop['height']}, start_x={$this->crop['start_x']}, start_y={$this->crop['start_y']}"); + $img = $this->CreateImageKeepTransparency($this->crop['width'], $this->crop['height']); + imagecopy($img, $this->image, 0, 0, $this->crop['start_x'], $this->crop['start_y'], $this->crop['width'], $this->crop['height']); + $this->image = $img; + $this->width = $this->crop['width']; + $this->height = $this->crop['height']; + } + + if (!$this->upscale) { + // Consider rewriting the no-upscale code to fit within this if-statement, + // likely to be more readable code. + // The code is more or leass equal in below crop-to-fit, fill-to-fit and stretch + } + + if ($this->cropToFit) { + + // Resize by crop to fit + $this->log("Resizing using strategy - Crop to fit"); + + if (!$this->upscale && ($this->width < $this->newWidth || $this->height < $this->newHeight)) { + $this->log("Resizing - smaller image, do not upscale."); + + $cropX = round(($this->cropWidth/2) - ($this->newWidth/2)); + $cropY = round(($this->cropHeight/2) - ($this->newHeight/2)); + + $posX = 0; + $posY = 0; + + if ($this->newWidth > $this->width) { + $posX = round(($this->newWidth - $this->width) / 2); + } + + if ($this->newHeight > $this->height) { + $posY = round(($this->newHeight - $this->height) / 2); + } + + $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); + imagecopy($imageResized, $this->image, $posX, $posY, $cropX, $cropY, $this->newWidth, $this->newHeight); + } else { + $cropX = round(($this->cropWidth/2) - ($this->newWidth/2)); + $cropY = round(($this->cropHeight/2) - ($this->newHeight/2)); + $imgPreCrop = $this->CreateImageKeepTransparency($this->cropWidth, $this->cropHeight); + $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); + imagecopyresampled($imgPreCrop, $this->image, 0, 0, 0, 0, $this->cropWidth, $this->cropHeight, $this->width, $this->height); + imagecopy($imageResized, $imgPreCrop, 0, 0, $cropX, $cropY, $this->newWidth, $this->newHeight); + } + + $this->image = $imageResized; + $this->width = $this->newWidth; + $this->height = $this->newHeight; + + } else if ($this->fillToFit) { + + // Resize by fill to fit + $this->log("Resizing using strategy - Fill to fit"); + + $posX = 0; + $posY = 0; + + $ratioOrig = $this->width / $this->height; + $ratioNew = $this->newWidth / $this->newHeight; + + // Check ratio for landscape or portrait + if ($ratioOrig < $ratioNew) { + $posX = round(($this->newWidth - $this->fillWidth) / 2); + } else { + $posY = round(($this->newHeight - $this->fillHeight) / 2); + } + + if (!$this->upscale + && ($this->width < $this->newWidth || $this->height < $this->newHeight) + ) { + + $this->log("Resizing - smaller image, do not upscale."); + $posX = round(($this->fillWidth - $this->width) / 2); + $posY = round(($this->fillHeight - $this->height) / 2); + $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); + imagecopy($imageResized, $this->image, $posX, $posY, 0, 0, $this->fillWidth, $this->fillHeight); + + } else { + $imgPreFill = $this->CreateImageKeepTransparency($this->fillWidth, $this->fillHeight); + $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); + imagecopyresampled($imgPreFill, $this->image, 0, 0, 0, 0, $this->fillWidth, $this->fillHeight, $this->width, $this->height); + imagecopy($imageResized, $imgPreFill, $posX, $posY, 0, 0, $this->fillWidth, $this->fillHeight); + } + + $this->image = $imageResized; + $this->width = $this->newWidth; + $this->height = $this->newHeight; + + } else if (!($this->newWidth == $this->width && $this->newHeight == $this->height)) { + + // Resize it + $this->log("Resizing, new height and/or width"); + + if (!$this->upscale + && ($this->width < $this->newWidth || $this->height < $this->newHeight) + ) { + $this->log("Resizing - smaller image, do not upscale."); + + if (!$this->keepRatio) { + $this->log("Resizing - stretch to fit selected."); + + $posX = 0; + $posY = 0; + $cropX = 0; + $cropY = 0; + + if ($this->newWidth > $this->width && $this->newHeight > $this->height) { + $posX = round(($this->newWidth - $this->width) / 2); + $posY = round(($this->newHeight - $this->height) / 2); + } else if ($this->newWidth > $this->width) { + $posX = round(($this->newWidth - $this->width) / 2); + $cropY = round(($this->height - $this->newHeight) / 2); + } else if ($this->newHeight > $this->height) { + $posY = round(($this->newHeight - $this->height) / 2); + $cropX = round(($this->width - $this->newWidth) / 2); + } + + //$this->log("posX=$posX, posY=$posY, cropX=$cropX, cropY=$cropY."); + $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); + imagecopy($imageResized, $this->image, $posX, $posY, $cropX, $cropY, $this->newWidth, $this->newHeight); + $this->image = $imageResized; + $this->width = $this->newWidth; + $this->height = $this->newHeight; + } + } else { + $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); + imagecopyresampled($imageResized, $this->image, 0, 0, 0, 0, $this->newWidth, $this->newHeight, $this->width, $this->height); + $this->image = $imageResized; + $this->width = $this->newWidth; + $this->height = $this->newHeight; + } + } + + return $this; + } + + + + /** + * Postprocess image after rezising image. + * + * @return $this + */ + public function postResize() + { + $this->log("Post-process after resizing"); + + // Rotate image + if ($this->rotateAfter) { + $this->log("Rotating image."); + $this->rotate($this->rotateAfter, $this->bgColor); + } + + // Apply filters + if (isset($this->filters) && is_array($this->filters)) { + + foreach ($this->filters as $filter) { + $this->log("Applying filter {$filter['type']}."); + + switch ($filter['argc']) { + + case 0: + imagefilter($this->image, $filter['type']); + break; + + case 1: + imagefilter($this->image, $filter['type'], $filter['arg1']); + break; + + case 2: + imagefilter($this->image, $filter['type'], $filter['arg1'], $filter['arg2']); + break; + + case 3: + imagefilter($this->image, $filter['type'], $filter['arg1'], $filter['arg2'], $filter['arg3']); + break; + + case 4: + imagefilter($this->image, $filter['type'], $filter['arg1'], $filter['arg2'], $filter['arg3'], $filter['arg4']); + break; + } + } + } + + // Convert to palette image + if ($this->palette) { + $this->log("Converting to palette image."); + $this->trueColorToPalette(); + } + + // Blur the image + if ($this->blur) { + $this->log("Blur."); + $this->blurImage(); + } + + // Emboss the image + if ($this->emboss) { + $this->log("Emboss."); + $this->embossImage(); + } + + // Sharpen the image + if ($this->sharpen) { + $this->log("Sharpen."); + $this->sharpenImage(); + } + + // Custom convolution + if ($this->convolve) { + //$this->log("Convolve: " . $this->convolve); + $this->imageConvolution(); + } + + return $this; + } + + + + /** + * Rotate image using angle. + * + * @param float $angle to rotate image. + * @param int $anglebgColor to fill image with if needed. + * + * @return $this + */ + public function rotate($angle, $bgColor) + { + $this->log("Rotate image " . $angle . " degrees with filler color."); + + $color = $this->getBackgroundColor(); + $this->image = imagerotate($this->image, $angle, $color); + + $this->width = imagesx($this->image); + $this->height = imagesy($this->image); + + $this->log("New image dimension width x height: " . $this->width . " x " . $this->height); + + return $this; + } + + + + /** + * Rotate image using information in EXIF. + * + * @return $this + */ + public function rotateExif() + { + if (!in_array($this->fileExtension, array('jpg', 'jpeg'))) { + $this->log("Autorotate ignored, EXIF not supported by this filetype."); + return $this; + } + + $exif = exif_read_data($this->pathToImage); + + if (!empty($exif['Orientation'])) { + switch ($exif['Orientation']) { + case 3: + $this->log("Autorotate 180."); + $this->rotate(180, $this->bgColor); + break; + + case 6: + $this->log("Autorotate -90."); + $this->rotate(-90, $this->bgColor); + break; + + case 8: + $this->log("Autorotate 90."); + $this->rotate(90, $this->bgColor); + break; + + default: + $this->log("Autorotate ignored, unknown value as orientation."); + } + } else { + $this->log("Autorotate ignored, no orientation in EXIF."); + } + + return $this; + } + + + + /** + * Convert true color image to palette image, keeping alpha. + * http://stackoverflow.com/questions/5752514/how-to-convert-png-to-8-bit-png-using-php-gd-library + * + * @return void + */ + public function trueColorToPalette() + { + $img = imagecreatetruecolor($this->width, $this->height); + $bga = imagecolorallocatealpha($img, 0, 0, 0, 127); + imagecolortransparent($img, $bga); + imagefill($img, 0, 0, $bga); + imagecopy($img, $this->image, 0, 0, 0, 0, $this->width, $this->height); + imagetruecolortopalette($img, false, 255); + imagesavealpha($img, true); + + if (imageistruecolor($this->image)) { + $this->log("Matching colors with true color image."); + imagecolormatch($this->image, $img); + } + + $this->image = $img; + } + + + + /** + * Sharpen image using image convolution. + * + * @return $this + */ + public function sharpenImage() + { + $this->imageConvolution('sharpen'); + return $this; + } + + + + /** + * Emboss image using image convolution. + * + * @return $this + */ + public function embossImage() + { + $this->imageConvolution('emboss'); + return $this; + } + + + + /** + * Blur image using image convolution. + * + * @return $this + */ + public function blurImage() + { + $this->imageConvolution('blur'); + return $this; + } + + + + /** + * Create convolve expression and return arguments for image convolution. + * + * @param string $expression constant string which evaluates to a list of + * 11 numbers separated by komma or such a list. + * + * @return array as $matrix (3x3), $divisor and $offset + */ + public function createConvolveArguments($expression) + { + // Check of matching constant + if (isset($this->convolves[$expression])) { + $expression = $this->convolves[$expression]; + } + + $part = explode(',', $expression); + $this->log("Creating convolution expressen: $expression"); + + // Expect list of 11 numbers, split by , and build up arguments + if (count($part) != 11) { + throw new Exception( + "Missmatch in argument convolve. Expected comma-separated string with + 11 float values. Got $expression." + ); + } + + array_walk($part, function ($item, $key) { + if (!is_numeric($item)) { + throw new Exception("Argument to convolve expression should be float but is not."); + } + }); + + return array( + array( + array($part[0], $part[1], $part[2]), + array($part[3], $part[4], $part[5]), + array($part[6], $part[7], $part[8]), + ), + $part[9], + $part[10], + ); + } + + + + /** + * Add custom expressions (or overwrite existing) for image convolution. + * + * @param array $options Key value array with strings to be converted + * to convolution expressions. + * + * @return $this + */ + public function addConvolveExpressions($options) + { + $this->convolves = array_merge($this->convolves, $options); + return $this; + } + + + + /** + * Image convolution. + * + * @param string $options A string with 11 float separated by comma. + * + * @return $this + */ + public function imageConvolution($options = null) + { + // Use incoming options or use $this. + $options = $options ? $options : $this->convolve; + + // Treat incoming as string, split by + + $this->log("Convolution with '$options'"); + $options = explode(":", $options); + + // Check each option if it matches constant value + foreach ($options as $option) { + list($matrix, $divisor, $offset) = $this->createConvolveArguments($option); + imageconvolution($this->image, $matrix, $divisor, $offset); + } + + return $this; + } + + + + /** + * Set default background color between 000000-FFFFFF or if using + * alpha 00000000-FFFFFF7F. + * + * @param string $color as hex value. + * + * @return $this + */ + public function setDefaultBackgroundColor($color) + { + $this->log("Setting default background color to '$color'."); + + if (!(strlen($color) == 6 || strlen($color) == 8)) { + throw new Exception( + "Background color needs a hex value of 6 or 8 + digits. 000000-FFFFFF or 00000000-FFFFFF7F. + Current value was: '$color'." + ); + } + + $red = hexdec(substr($color, 0, 2)); + $green = hexdec(substr($color, 2, 2)); + $blue = hexdec(substr($color, 4, 2)); + + $alpha = (strlen($color) == 8) + ? hexdec(substr($color, 6, 2)) + : null; + + if (($red < 0 || $red > 255) + || ($green < 0 || $green > 255) + || ($blue < 0 || $blue > 255) + || ($alpha < 0 || $alpha > 127) + ) { + throw new Exception( + "Background color out of range. Red, green blue + should be 00-FF and alpha should be 00-7F. + Current value was: '$color'." + ); + } + + $this->bgColor = strtolower($color); + $this->bgColorDefault = array( + 'red' => $red, + 'green' => $green, + 'blue' => $blue, + 'alpha' => $alpha + ); + + return $this; + } + + + + /** + * Get the background color. + * + * @param resource $img the image to work with or null if using $this->image. + * + * @return color value or null if no background color is set. + */ + private function getBackgroundColor($img = null) + { + $img = isset($img) ? $img : $this->image; + + if ($this->bgColorDefault) { + + $red = $this->bgColorDefault['red']; + $green = $this->bgColorDefault['green']; + $blue = $this->bgColorDefault['blue']; + $alpha = $this->bgColorDefault['alpha']; + + if ($alpha) { + $color = imagecolorallocatealpha($img, $red, $green, $blue, $alpha); + } else { + $color = imagecolorallocate($img, $red, $green, $blue); + } + + return $color; + + } else { + return 0; + } + } + + + + /** + * Create a image and keep transparency for png and gifs. + * + * @param int $width of the new image. + * @param int $height of the new image. + * + * @return image resource. + */ + private function createImageKeepTransparency($width, $height) + { + $this->log("Creating a new working image width={$width}px, height={$height}px."); + $img = imagecreatetruecolor($width, $height); + imagealphablending($img, false); + imagesavealpha($img, true); + + $index = imagecolortransparent($this->image); + if ($index != -1) { + + imagealphablending($img, true); + $transparent = imagecolorsforindex($this->image, $index); + $color = imagecolorallocatealpha($img, $transparent['red'], $transparent['green'], $transparent['blue'], $transparent['alpha']); + imagefill($img, 0, 0, $color); + $index = imagecolortransparent($img, $color); + $this->Log("Detected transparent color = " . implode(", ", $transparent) . " at index = $index"); + + } elseif ($this->bgColorDefault) { + + $color = $this->getBackgroundColor($img); + imagefill($img, 0, 0, $color); + $this->Log("Filling image with background color."); + } + + return $img; + } + + + + /** + * Set optimizing and post-processing options. + * + * @param array $options with config for postprocessing with external tools. + * + * @return $this + */ + public function setPostProcessingOptions($options) + { + if (isset($options['jpeg_optimize']) && $options['jpeg_optimize']) { + $this->jpegOptimizeCmd = $options['jpeg_optimize_cmd']; + } else { + $this->jpegOptimizeCmd = null; + } + + if (isset($options['png_filter']) && $options['png_filter']) { + $this->pngFilterCmd = $options['png_filter_cmd']; + } else { + $this->pngFilterCmd = null; + } + + if (isset($options['png_deflate']) && $options['png_deflate']) { + $this->pngDeflateCmd = $options['png_deflate_cmd']; + } else { + $this->pngDeflateCmd = null; + } + + return $this; + } + + + + /** + * Save image. + * + * @param string $src as target filename. + * @param string $base as base directory where to store images. + * + * @return $this or false if no folder is set. + */ + public function save($src = null, $base = null) + { + if (isset($src)) { + $this->setTarget($src, $base); + } + + is_writable($this->saveFolder) + or $this->raiseError('Target directory is not writable.'); + + switch(strtolower($this->extension)) { + + case 'jpeg': + case 'jpg': + $this->Log("Saving image as JPEG to cache using quality = {$this->quality}."); + imagejpeg($this->image, $this->cacheFileName, $this->quality); + + // Use JPEG optimize if defined + if ($this->jpegOptimizeCmd) { + if ($this->verbose) { + clearstatcache(); + $this->log("Filesize before optimize: " . filesize($this->cacheFileName) . " bytes."); + } + $res = array(); + $cmd = $this->jpegOptimizeCmd . " -outfile $this->cacheFileName $this->cacheFileName"; + exec($cmd, $res); + $this->log($cmd); + $this->log($res); + } + break; + + case 'gif': + $this->Log("Saving image as GIF to cache."); + imagegif($this->image, $this->cacheFileName); + break; + + case 'png': + $this->Log("Saving image as PNG to cache using compression = {$this->compress}."); + + // Turn off alpha blending and set alpha flag + imagealphablending($this->image, false); + imagesavealpha($this->image, true); + imagepng($this->image, $this->cacheFileName, $this->compress); + + // Use external program to filter PNG, if defined + if ($this->pngFilterCmd) { + if ($this->verbose) { + clearstatcache(); + $this->Log("Filesize before filter optimize: " . filesize($this->cacheFileName) . " bytes."); + } + $res = array(); + $cmd = $this->pngFilterCmd . " $this->cacheFileName"; + exec($cmd, $res); + $this->Log($cmd); + $this->Log($res); + } + + // Use external program to deflate PNG, if defined + if ($this->pngDeflateCmd) { + if ($this->verbose) { + clearstatcache(); + $this->Log("Filesize before deflate optimize: " . filesize($this->cacheFileName) . " bytes."); + } + $res = array(); + $cmd = $this->pngDeflateCmd . " $this->cacheFileName"; + exec($cmd, $res); + $this->Log($cmd); + $this->Log($res); + } + break; + + default: + $this->RaiseError('No support for this file extension.'); + break; + } + + if ($this->verbose) { + clearstatcache(); + $this->log("Cached image filesize: " . filesize($this->cacheFileName) . " bytes."); + $this->log("imageistruecolor() : " . (imageistruecolor($this->image) ? 'true' : 'false')); + $this->log("imagecolorstotal() : " . imagecolorstotal($this->image)); + $this->log("Number of colors in image = " . $this->ColorsTotal($this->image)); + $index = imagecolortransparent($this->image); + $this->log("Detected transparent color = " . ($index > 0 ? implode(", ", imagecolorsforindex($this->image, $index)) : "NONE") . " at index = $index"); + } + + return $this; + } + + + + /** + * Create a hard link, as an alias, to the cached file. + * + * @param string $alias where to store the link, + * filename without extension. + * + * @return $this + */ + public function linkToCacheFile($alias) + { + if ($alias === null) { + $this->log("Ignore creating alias."); + return $this; + } + + $alias = $alias . "." . $this->extension; + + if (is_readable($alias)) { + unlink($alias); + } + + $res = link($this->cacheFileName, $alias); + + if ($res) { + $this->log("Created an alias as: $alias"); + } else { + $this->log("Failed to create the alias: $alias"); + } + + return $this; + } + + + + /** + * Output image to browser using caching. + * + * @param string $file to read and output, default is to use $this->cacheFileName + * @param string $format set to json to output file as json object with details + * + * @return void + */ + public function output($file = null, $format = null) + { + if (is_null($file)) { + $file = $this->cacheFileName; + } + + if (is_null($format)) { + $format = $this->outputFormat; + } + + $this->log("Output format is: $format"); + + if (!$this->verbose && $format == 'json') { + header('Content-type: application/json'); + echo $this->json($file); + exit; + } + + $this->log("Outputting image: $file"); + + // Get image modification time + clearstatcache(); + $lastModified = filemtime($file); + $gmdate = gmdate("D, d M Y H:i:s", $lastModified); + + if (!$this->verbose) { + header('Last-Modified: ' . $gmdate . " GMT"); + } + + if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $lastModified) { + + if ($this->verbose) { + $this->log("304 not modified"); + $this->verboseOutput(); + exit; + } + + header("HTTP/1.0 304 Not Modified"); + + } else { + + if ($this->verbose) { + $this->log("Last modified: " . $gmdate . " GMT"); + $this->verboseOutput(); + exit; + } + + // Get details on image + $info = getimagesize($file); + !empty($info) or $this->raiseError("The file doesn't seem to be an image."); + $mime = $info['mime']; + + header('Content-type: ' . $mime); + readfile($file); + } + + exit; + } + + + + /** + * Create a JSON object from the image details. + * + * @param string $file the file to output. + * + * @return string json-encoded representation of the image. + */ + public function json($file = null) + { + $file = $file ? $file : $this->cacheFileName; + + $details = array(); + + clearstatcache(); + + $details['src'] = $this->imageSrc; + $lastModified = filemtime($this->pathToImage); + $details['srcGmdate'] = gmdate("D, d M Y H:i:s", $lastModified); + + $details['cache'] = basename($this->cacheFileName); + $lastModified = filemtime($this->cacheFileName); + $details['cacheGmdate'] = gmdate("D, d M Y H:i:s", $lastModified); + + $this->loadImageDetails($file); + + $details['filename'] = basename($file); + $details['width'] = $this->width; + $details['height'] = $this->height; + $details['aspectRatio'] = round($this->width / $this->height, 3); + $details['size'] = filesize($file); + + $this->load($file); + $details['colors'] = $this->colorsTotal($this->image); + + $options = null; + if (defined("JSON_PRETTY_PRINT") && defined("JSON_UNESCAPED_SLASHES")) { + $options = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES; + } + + return json_encode($details, $options); + } + + + + /** + * Log an event if verbose mode. + * + * @param string $message to log. + * + * @return this + */ + public function log($message) + { + if ($this->verbose) { + $this->log[] = $message; + } + + return $this; + } + + + + /** + * Do verbose output and print out the log and the actual images. + * + * @return void + */ + private function verboseOutput() + { + $log = null; + $this->log("As JSON: \n" . $this->json()); + $this->log("Memory peak: " . round(memory_get_peak_usage() /1024/1024) . "M"); + $this->log("Memory limit: " . ini_get('memory_limit')); + + $included = get_included_files(); + $this->log("Included files: " . count($included)); + + foreach ($this->log as $val) { + if (is_array($val)) { + foreach ($val as $val1) { + $log .= htmlentities($val1) . '
'; + } + } else { + $log .= htmlentities($val) . '
'; + } + } + + echo << + + +CImage verbose output + +

CImage Verbose Output

+
{$log}
+EOD; + } + + + + /** + * Raise error, enables to implement a selection of error methods. + * + * @param string $message the error message to display. + * + * @return void + * @throws Exception + */ + private function raiseError($message) + { + throw new Exception($message); + } +} + + + +/** + * Resize and crop images on the fly, store generated images in a cache. + * + * @author Mikael Roos mos@dbwebb.se + * @example http://dbwebb.se/opensource/cimage + * @link https://github.com/mosbth/cimage + * + */ + +$version = "v0.7.0 (2015-02-10)"; + + + +/** + * Default configuration options, can be overridden in own config-file. + * + * @param string $msg to display. + * + * @return void + */ +function errorPage($msg) +{ + global $mode; + + header("HTTP/1.0 500 Internal Server Error"); + + if ($mode == 'development') { + die("[img.php] $msg"); + } else { + error_log("[img.php] $msg"); + die("HTTP/1.0 500 Internal Server Error"); + } +} + + + +/** + * Custom exception handler. + */ +set_exception_handler(function ($exception) { + errorPage("

img.php: Uncaught exception:

" . $exception->getMessage() . "

" . $exception->getTraceAsString(), "
"); +}); + + + +/** + * Get input from query string or return default value if not set. + * + * @param mixed $key as string or array of string values to look for in $_GET. + * @param mixed $default value to return when $key is not set in $_GET. + * + * @return mixed value from $_GET or default value. + */ +function get($key, $default = null) +{ + if (is_array($key)) { + foreach ($key as $val) { + if (isset($_GET[$val])) { + return $_GET[$val]; + } + } + } elseif (isset($_GET[$key])) { + return $_GET[$key]; + } + return $default; +} + + + +/** + * Get input from query string and set to $defined if defined or else $undefined. + * + * @param mixed $key as string or array of string values to look for in $_GET. + * @param mixed $defined value to return when $key is set in $_GET. + * @param mixed $undefined value to return when $key is not set in $_GET. + * + * @return mixed value as $defined or $undefined. + */ +function getDefined($key, $defined, $undefined) +{ + return get($key) === null ? $undefined : $defined; +} + + + +/** + * Get value from config array or default if key is not set in config array. + * + * @param string $key the key in the config array. + * @param mixed $default value to be default if $key is not set in config. + * + * @return mixed value as $config[$key] or $default. + */ +function getConfig($key, $default) +{ + global $config; + return isset($config[$key]) + ? $config[$key] + : $default; +} + + + +/** + * Log when verbose mode, when used without argument it returns the result. + * + * @param string $msg to log. + * + * @return void or array. + */ +function verbose($msg = null) +{ + global $verbose; + static $log = array(); + + if (!$verbose) { + return; + } + + if (is_null($msg)) { + return $log; + } + + $log[] = $msg; +} + + + +/** + * Get configuration options from file, if the file exists, else use $config + * if its defined or create an empty $config. + */ +$configFile = __DIR__.'/'.basename(__FILE__, '.php').'_config.php'; + +if (is_file($configFile)) { + $config = require $configFile; +} else if (!isset($config)) { + $config = array(); +} + + + +/** +* verbose, v - do a verbose dump of what happens +*/ +$verbose = getDefined(array('verbose', 'v'), true, false); +verbose("img.php version = $version"); + + + +/** + * Set mode as strict, production or development. + * Default is production environment. + */ +$mode = getConfig('mode', 'production'); + +// Settings for any mode +set_time_limit(20); +ini_set('gd.jpeg_ignore_warning', 1); + +if (!extension_loaded('gd')) { + errorPage("Extension gd is nod loaded."); +} + +// Specific settings for each mode +if ($mode == 'strict') { + + error_reporting(0); + ini_set('display_errors', 0); + ini_set('log_errors', 1); + $verbose = false; + +} else if ($mode == 'production') { + + error_reporting(-1); + ini_set('display_errors', 0); + ini_set('log_errors', 1); + $verbose = false; + +} else if ($mode == 'development') { + + error_reporting(-1); + ini_set('display_errors', 1); + ini_set('log_errors', 0); + +} else { + errorPage("Unknown mode: $mode"); +} + +verbose("mode = $mode"); +verbose("error log = " . ini_get('error_log')); + + + +/** + * Set default timezone if not set or if its set in the config-file. + */ +$defaultTimezone = getConfig('default_timezone', null); + +if ($defaultTimezone) { + date_default_timezone_set($defaultTimezone); +} else if (!ini_get('default_timezone')) { + date_default_timezone_set('UTC'); +} + + + +/** + * Check if passwords are configured, used and match. + * Options decide themself if they require passwords to be used. + */ +$pwdConfig = getConfig('password', false); +$pwdAlways = getConfig('password_always', false); +$pwd = get(array('password', 'pwd'), null); + +// Check if passwords match, if configured to use passwords +$passwordMatch = null; +if ($pwdAlways) { + + $passwordMatch = ($pwdConfig === $pwd); + if (!$passwordMatch) { + errorPage("Password required and does not match or exists."); + } + +} elseif ($pwdConfig && $pwd) { + + $passwordMatch = ($pwdConfig === $pwd); +} + +verbose("password match = $passwordMatch"); + + + +/** + * Prevent hotlinking, leeching, of images by controlling who access them + * from where. + * + */ +$allowHotlinking = getConfig('allow_hotlinking', true); +$hotlinkingWhitelist = getConfig('hotlinking_whitelist', array()); + +$serverName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null; +$referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null; +$refererHost = parse_url($referer, PHP_URL_HOST); + +if (!$allowHotlinking) { + if ($passwordMatch) { + ; // Always allow when password match + } else if ($passwordMatch === false) { + errorPage("Hotlinking/leeching not allowed when password missmatch."); + } else if (!$referer) { + errorPage("Hotlinking/leeching not allowed and referer is missing."); + } else if (strcmp($serverName, $refererHost) == 0) { + ; // Allow when serverName matches refererHost + } else if (!empty($hotlinkingWhitelist)) { + + $allowedByWhitelist = false; + foreach ($hotlinkingWhitelist as $val) { + if (preg_match($val, $refererHost)) { + $allowedByWhitelist = true; + } + } + + if (!$allowedByWhitelist) { + errorPage("Hotlinking/leeching not allowed by whitelist."); + } + + } else { + errorPage("Hotlinking/leeching not allowed."); + } +} + +verbose("allow_hotlinking = $allowHotlinking"); +verbose("referer = $referer"); +verbose("referer host = $refererHost"); + + + +/** + * Get the source files. + */ +$autoloader = getConfig('autoloader', false); +$cimageClass = getConfig('cimage_class', false); + +if ($autoloader) { + require $autoloader; +} else if ($cimageClass) { + require $cimageClass; +} + + + +/** + * Create the class for the image. + */ +$img = new CImage(); +$img->setVerbose($verbose); + + + +/** + * Allow or disallow remote download of images from other servers. + * Passwords apply if used. + * + */ +$allowRemote = getConfig('remote_allow', false); + +if ($allowRemote && $passwordMatch !== false) { + $pattern = getConfig('remote_pattern', null); + $img->setRemoteDownload($allowRemote, $pattern); +} + + + +/** + * shortcut, sc - extend arguments with a constant value, defined + * in config-file. + */ +$shortcut = get(array('shortcut', 'sc'), null); +$shortcutConfig = getConfig('shortcut', array( + 'sepia' => "&f=grayscale&f0=brightness,-10&f1=contrast,-20&f2=colorize,120,60,0,0&sharpen", +)); + +verbose("shortcut = $shortcut"); + +if (isset($shortcut) + && isset($shortcutConfig[$shortcut])) { + + parse_str($shortcutConfig[$shortcut], $get); + verbose("shortcut-constant = {$shortcutConfig[$shortcut]}"); + $_GET = array_merge($_GET, $get); +} + + + +/** + * src - the source image file. + */ +$srcImage = get('src') + or errorPage('Must set src-attribute.'); + +// Check for valid/invalid characters +$imagePath = getConfig('image_path', __DIR__ . '/img/'); +$imagePathConstraint = getConfig('image_path_constraint', true); +$validFilename = getConfig('valid_filename', '#^[a-z0-9A-Z-/_\.:]+$#'); + +preg_match($validFilename, $srcImage) + or errorPage('Filename contains invalid characters.'); + +if ($allowRemote && $img->isRemoteSource($srcImage)) { + + // If source is a remote file, ignore local file checks. + +} else if ($imagePathConstraint) { + + // Check that the image is a file below the directory 'image_path'. + $pathToImage = realpath($imagePath . $srcImage); + $imageDir = realpath($imagePath); + + is_file($pathToImage) + or errorPage( + 'Source image is not a valid file, check the filename and that a + matching file exists on the filesystem.' + ); + + substr_compare($imageDir, $pathToImage, 0, strlen($imageDir)) == 0 + or errorPage( + 'Security constraint: Source image is not below the directory "image_path" + as specified in the config file img_config.php.' + ); +} + +verbose("src = $srcImage"); + + + +/** + * Manage size constants from config file, use constants to replace values + * for width and height. + */ +$sizeConstant = getConfig('size_constant', function () { + + // Set sizes to map constant to value, easier to use with width or height + $sizes = array( + 'w1' => 613, + 'w2' => 630, + ); + + // Add grid column width, useful for use as predefined size for width (or height). + $gridColumnWidth = 30; + $gridGutterWidth = 10; + $gridColumns = 24; + + for ($i = 1; $i <= $gridColumns; $i++) { + $sizes['c' . $i] = ($gridColumnWidth + $gridGutterWidth) * $i - $gridGutterWidth; + } + + return $sizes; +}); + +$sizes = call_user_func($sizeConstant); + + + +/** + * width, w - set target width, affecting the resulting image width, height and resize options + */ +$newWidth = get(array('width', 'w')); +$maxWidth = getConfig('max_width', 2000); + +// Check to replace predefined size +if (isset($sizes[$newWidth])) { + $newWidth = $sizes[$newWidth]; +} + +// Support width as % of original width +if ($newWidth[strlen($newWidth)-1] == '%') { + is_numeric(substr($newWidth, 0, -1)) + or errorPage('Width % not numeric.'); +} else { + is_null($newWidth) + or ($newWidth > 10 && $newWidth <= $maxWidth) + or errorPage('Width out of range.'); +} + +verbose("new width = $newWidth"); + + + +/** + * height, h - set target height, affecting the resulting image width, height and resize options + */ +$newHeight = get(array('height', 'h')); +$maxHeight = getConfig('max_height', 2000); + +// Check to replace predefined size +if (isset($sizes[$newHeight])) { + $newHeight = $sizes[$newHeight]; +} + +// height +if ($newHeight[strlen($newHeight)-1] == '%') { + is_numeric(substr($newHeight, 0, -1)) + or errorPage('Height % out of range.'); +} else { + is_null($newHeight) + or ($newHeight > 10 && $newHeight <= $maxHeight) + or errorPage('Hight out of range.'); +} + +verbose("new height = $newHeight"); + + + +/** + * aspect-ratio, ar - affecting the resulting image width, height and resize options + */ +$aspectRatio = get(array('aspect-ratio', 'ar')); +$aspectRatioConstant = getConfig('aspect_ratio_constant', function () { + return array( + '3:1' => 3/1, + '3:2' => 3/2, + '4:3' => 4/3, + '8:5' => 8/5, + '16:10' => 16/10, + '16:9' => 16/9, + 'golden' => 1.618, + ); +}); + +// Check to replace predefined aspect ratio +$aspectRatios = call_user_func($aspectRatioConstant); +$negateAspectRatio = ($aspectRatio[0] == '!') ? true : false; +$aspectRatio = $negateAspectRatio ? substr($aspectRatio, 1) : $aspectRatio; + +if (isset($aspectRatios[$aspectRatio])) { + $aspectRatio = $aspectRatios[$aspectRatio]; +} + +if ($negateAspectRatio) { + $aspectRatio = 1 / $aspectRatio; +} + +is_null($aspectRatio) + or is_numeric($aspectRatio) + or errorPage('Aspect ratio out of range'); + +verbose("aspect ratio = $aspectRatio"); + + + +/** + * crop-to-fit, cf - affecting the resulting image width, height and resize options + */ +$cropToFit = getDefined(array('crop-to-fit', 'cf'), true, false); + +verbose("crop to fit = $cropToFit"); + + + +/** + * Set default background color from config file. + */ +$backgroundColor = getConfig('background_color', null); + +if ($backgroundColor) { + $img->setDefaultBackgroundColor($backgroundColor); + verbose("Using default background_color = $backgroundColor"); +} + + + +/** + * bgColor - Default background color to use + */ +$bgColor = get(array('bgColor', 'bg-color', 'bgc'), null); + +verbose("bgColor = $bgColor"); + + + +/** + * fill-to-fit, ff - affecting the resulting image width, height and resize options + */ +$fillToFit = get(array('fill-to-fit', 'ff'), null); + +verbose("fill-to-fit = $fillToFit"); + +if ($fillToFit !== null) { + + if (!empty($fillToFit)) { + $bgColor = $fillToFit; + verbose("fillToFit changed bgColor to = $bgColor"); + } + + $fillToFit = true; + verbose("fill-to-fit (fixed) = $fillToFit"); +} + + + +/** + * no-ratio, nr, stretch - affecting the resulting image width, height and resize options + */ +$keepRatio = getDefined(array('no-ratio', 'nr', 'stretch'), false, true); + +verbose("keep ratio = $keepRatio"); + + + +/** + * crop, c - affecting the resulting image width, height and resize options + */ +$crop = get(array('crop', 'c')); + +verbose("crop = $crop"); + + + +/** + * area, a - affecting the resulting image width, height and resize options + */ +$area = get(array('area', 'a')); + +verbose("area = $area"); + + + +/** + * skip-original, so - skip the original image and always process a new image + */ +$useOriginal = getDefined(array('skip-original', 'so'), false, true); + +verbose("use original = $useOriginal"); + + + +/** + * no-cache, nc - skip the cached version and process and create a new version in cache. + */ +$useCache = getDefined(array('no-cache', 'nc'), false, true); + +verbose("use cache = $useCache"); + + + +/** + * quality, q - set level of quality for jpeg images + */ +$quality = get(array('quality', 'q')); + +is_null($quality) + or ($quality > 0 and $quality <= 100) + or errorPage('Quality out of range'); + +verbose("quality = $quality"); + + + +/** + * compress, co - what strategy to use when compressing png images + */ +$compress = get(array('compress', 'co')); + + +is_null($compress) + or ($compress > 0 and $compress <= 9) + or errorPage('Compress out of range'); + +verbose("compress = $compress"); + + + +/** + * save-as, sa - what type of image to save + */ +$saveAs = get(array('save-as', 'sa')); + +verbose("save as = $saveAs"); + + + +/** + * scale, s - Processing option, scale up or down the image prior actual resize + */ +$scale = get(array('scale', 's')); + +is_null($scale) + or ($scale >= 0 and $scale <= 400) + or errorPage('Scale out of range'); + +verbose("scale = $scale"); + + + +/** + * palette, p - Processing option, create a palette version of the image + */ +$palette = getDefined(array('palette', 'p'), true, false); + +verbose("palette = $palette"); + + + +/** + * sharpen - Processing option, post filter for sharpen effect + */ +$sharpen = getDefined('sharpen', true, null); + +verbose("sharpen = $sharpen"); + + + +/** + * emboss - Processing option, post filter for emboss effect + */ +$emboss = getDefined('emboss', true, null); + +verbose("emboss = $emboss"); + + + +/** + * blur - Processing option, post filter for blur effect + */ +$blur = getDefined('blur', true, null); + +verbose("blur = $blur"); + + + +/** + * rotateBefore - Rotate the image with an angle, before processing + */ +$rotateBefore = get(array('rotateBefore', 'rotate-before', 'rb')); + +is_null($rotateBefore) + or ($rotateBefore >= -360 and $rotateBefore <= 360) + or errorPage('RotateBefore out of range'); + +verbose("rotateBefore = $rotateBefore"); + + + +/** + * rotateAfter - Rotate the image with an angle, before processing + */ +$rotateAfter = get(array('rotateAfter', 'rotate-after', 'ra', 'rotate', 'r')); + +is_null($rotateAfter) + or ($rotateAfter >= -360 and $rotateAfter <= 360) + or errorPage('RotateBefore out of range'); + +verbose("rotateAfter = $rotateAfter"); + + + +/** + * autoRotate - Auto rotate based on EXIF information + */ +$autoRotate = getDefined(array('autoRotate', 'auto-rotate', 'aro'), true, false); + +verbose("autoRotate = $autoRotate"); + + + +/** + * filter, f, f0-f9 - Processing option, post filter for various effects using imagefilter() + */ +$filters = array(); +$filter = get(array('filter', 'f')); +if ($filter) { + $filters[] = $filter; +} + +for ($i = 0; $i < 10; $i++) { + $filter = get(array("filter{$i}", "f{$i}")); + if ($filter) { + $filters[] = $filter; + } +} + +verbose("filters = " . print_r($filters, 1)); + + + +/** + * json - output the image as a JSON object with details on the image. + */ +$outputFormat = getDefined('json', 'json', null); + +verbose("json = $outputFormat"); + + + +/** + * dpr - change to get larger image to easier support larger dpr, such as retina. + */ +$dpr = get(array('ppi', 'dpr', 'device-pixel-ratio'), 1); + +verbose("dpr = $dpr"); + + + +/** + * convolve - image convolution as in http://php.net/manual/en/function.imageconvolution.php + */ +$convolve = get('convolve', null); +$convolutionConstant = getConfig('convolution_constant', array()); + +// Check if the convolve is matching an existing constant +if ($convolve && isset($convolutionConstant)) { + $img->addConvolveExpressions($convolutionConstant); + verbose("convolve constant = " . print_r($convolutionConstant, 1)); +} + +verbose("convolve = " . print_r($convolve, 1)); + + + +/** + * no-upscale, nu - Do not upscale smaller image to larger dimension. + */ +$upscale = getDefined(array('no-upscale', 'nu'), false, true); + +verbose("upscale = $upscale"); + + + +/** + * Get details for post processing + */ +$postProcessing = getConfig('postprocessing', array( + 'png_filter' => false, + 'png_filter_cmd' => '/usr/local/bin/optipng -q', + + 'png_deflate' => false, + 'png_deflate_cmd' => '/usr/local/bin/pngout -q', + + 'jpeg_optimize' => false, + 'jpeg_optimize_cmd' => '/usr/local/bin/jpegtran -copy none -optimize', +)); + + + +/** + * alias - Save resulting image to another alias name. + * Password always apply, must be defined. + */ +$alias = get('alias', null); +$aliasPath = getConfig('alias_path', null); +$validAliasname = getConfig('valid_aliasname', '#^[a-z0-9A-Z-_]+$#'); +$aliasTarget = null; + +if ($alias && $aliasPath && $passwordMatch) { + + $aliasTarget = $aliasPath . $alias; + $useCache = false; + + is_writable($aliasPath) + or errorPage("Directory for alias is not writable."); + + preg_match($validAliasname, $alias) + or errorPage('Filename for alias contains invalid characters. Do not add extension.'); + +} else if ($alias) { + errorPage('Alias is not enabled in the config file or password not matching.'); +} + +verbose("alias = $alias"); + + + +/** + * Display image if verbose mode + */ +if ($verbose) { + $query = array(); + parse_str($_SERVER['QUERY_STRING'], $query); + unset($query['verbose']); + unset($query['v']); + unset($query['nocache']); + unset($query['nc']); + unset($query['json']); + $url1 = '?' . htmlentities(urldecode(http_build_query($query))); + $url2 = '?' . urldecode(http_build_query($query)); + echo <<$url1
+ +

+
+
+
+EOD;
+}
+
+
+
+/**
+ * Get the cachepath from config.
+ */
+$cachePath = getConfig('cache_path', __DIR__ . '/../cache/');
+
+
+
+/**
+ * Load, process and output the image
+ */
+$img->log("Incoming arguments: " . print_r(verbose(), 1))
+    ->setSaveFolder($cachePath)
+    ->useCache($useCache)
+    ->setSource($srcImage, $imagePath)
+    ->setOptions(
+        array(
+            // Options for calculate dimensions
+            'newWidth'  => $newWidth,
+            'newHeight' => $newHeight,
+            'aspectRatio' => $aspectRatio,
+            'keepRatio' => $keepRatio,
+            'cropToFit' => $cropToFit,
+            'fillToFit' => $fillToFit,
+            'crop'      => $crop,
+            'area'      => $area,
+            'upscale'   => $upscale,
+
+            // Pre-processing, before resizing is done
+            'scale'        => $scale,
+            'rotateBefore' => $rotateBefore,
+            'autoRotate'   => $autoRotate,
+
+            // General processing options
+            'bgColor'    => $bgColor,
+
+            // Post-processing, after resizing is done
+            'palette'   => $palette,
+            'filters'   => $filters,
+            'sharpen'   => $sharpen,
+            'emboss'    => $emboss,
+            'blur'      => $blur,
+            'convolve'  => $convolve,
+            'rotateAfter' => $rotateAfter,
+
+            // Output format
+            'outputFormat' => $outputFormat,
+            'dpr'          => $dpr,
+        )
+    )
+    ->loadImageDetails()
+    ->initDimensions()
+    ->calculateNewWidthAndHeight()
+    ->setSaveAsExtension($saveAs)
+    ->setJpegQuality($quality)
+    ->setPngCompression($compress)
+    ->useOriginalIfPossible($useOriginal)
+    ->generateFilename($cachePath)
+    ->useCacheIfPossible($useCache)
+    ->load()
+    ->preResize()
+    ->resize()
+    ->postResize()
+    ->setPostProcessingOptions($postProcessing)
+    ->save()
+    ->linkToCacheFile($aliasTarget)
+    ->output();
+
+
+
+
diff --git a/docs/api/files/webroot%2Ftest%2Fconfig.php.txt b/docs/api/files/webroot%2Ftest%2Fconfig.php.txt
new file mode 100644
index 0000000..8e301bd
--- /dev/null
+++ b/docs/api/files/webroot%2Ftest%2Fconfig.php.txt
@@ -0,0 +1,9 @@
+
+
+  
+  <?=$title?>
+  
+  
+
+
+
+
+
+

+ + +

+ + +

Images used in test

+ +

The following images are used for this test.

+ + +

+ + + (json) + (verbose) + +
+

+

+ +

+
+
+
+
+
+
+

Testcases used for each image

+ +

The following testcases are used for each image.

+ + +
+ + + + +

For each image, apply all testcases

+ + +

. Using source image

+ +

+ + + (json) + (verbose) + +
+ +

+ +

+
+
+
+

Testcase :

+ +

+ + + (json) + (verbose) + +
+ +

+ +

+
+
+
+
+
diff --git a/docs/api/files/webroot%2Ftest%2Ftest.php.txt b/docs/api/files/webroot%2Ftest%2Ftest.php.txt
new file mode 100644
index 0000000..d7676a5
--- /dev/null
+++ b/docs/api/files/webroot%2Ftest%2Ftest.php.txt
@@ -0,0 +1,73 @@
+
+
+  
+  Testing img resizing using CImage.php
+
+
+

Testing CImage.php through img.php

+ +

Testcases

+ +'Original image', 'query'=>''), + array('text'=>'Crop out a rectangle of 100x100, start by position 200x200.', 'query'=>'&crop=100,100,200,200'), + array('text'=>'Crop out a full width rectangle with height of 200, start by position 0x100.', 'query'=>'&crop=0,200,0,100'), + array('text'=>'Max width 200.', 'query'=>'&w=200'), + array('text'=>'Max height 200.', 'query'=>'&h=200'), + array('text'=>'Max width 200 and max height 200.', 'query'=>'&w=200&h=200'), + array('text'=>'No-ratio makes image fit in area of width 200 and height 200.', 'query'=>'&w=200&h=200&no-ratio'), + array('text'=>'Crop to fit in width 200 and height 200.', 'query'=>'&w=200&h=200&crop-to-fit'), + array('text'=>'Crop to fit in width 200 and height 100.', 'query'=>'&w=200&h=100&crop-to-fit'), + array('text'=>'Crop to fit in width 100 and height 200.', 'query'=>'&w=100&h=200&crop-to-fit'), + array('text'=>'Quality 70', 'query'=>'&w=200&h=200&quality=70'), + array('text'=>'Quality 40', 'query'=>'&w=200&h=200&quality=40'), + array('text'=>'Quality 10', 'query'=>'&w=200&h=200&quality=10'), + array('text'=>'Filter: Negate', 'query'=>'&w=200&h=200&f=negate'), + array('text'=>'Filter: Grayscale', 'query'=>'&w=200&h=200&f=grayscale'), + array('text'=>'Filter: Brightness 90', 'query'=>'&w=200&h=200&f=brightness,90'), + array('text'=>'Filter: Contrast 50', 'query'=>'&w=200&h=200&f=contrast,50'), + array('text'=>'Filter: Colorize 0,255,0,0', 'query'=>'&w=200&h=200&f=colorize,0,255,0,0'), + array('text'=>'Filter: Edge detect', 'query'=>'&w=200&h=200&f=edgedetect'), + array('text'=>'Filter: Emboss', 'query'=>'&w=200&h=200&f=emboss'), + array('text'=>'Filter: Gaussian blur', 'query'=>'&w=200&h=200&f=gaussian_blur'), + array('text'=>'Filter: Selective blur', 'query'=>'&w=200&h=200&f=selective_blur'), + array('text'=>'Filter: Mean removal', 'query'=>'&w=200&h=200&f=mean_removal'), + array('text'=>'Filter: Smooth 2', 'query'=>'&w=200&h=200&f=smooth,2'), + array('text'=>'Filter: Pixelate 10,10', 'query'=>'&w=200&h=200&f=pixelate,10,10'), + array('text'=>'Multiple filter: Negate, Grayscale and Pixelate 10,10', 'query'=>'&w=200&h=200&&f=negate&f0=grayscale&f1=pixelate,10,10'), + array('text'=>'Crop with width & height and crop-to-fit with quality and filter', 'query'=>'&crop=100,100,100,100&w=200&h=200&crop-to-fit&q=70&f0=grayscale'), +); +?> + +

Test case with image wider.jpg

+ + + + + $val) { + $url = "../img.php?src=wider.jpg{$val['query']}"; + echo ""; +} +?> + +
Test case with image wider.jpg
Testcase:Result:
$key
{$val['text']}
".htmlentities($url)."
+ +

Test case with image higher.jpg

+ + + + + $val) { + $url = "../img.php?src=higher.jpg{$val['query']}"; + echo ""; +} +?> + +
Test case with image higher.jpg
Testcase:Result:
$key
{$val['text']}
".htmlentities($url)."
+ + + + diff --git a/docs/api/files/webroot%2Ftest%2Ftest_issue29.php.txt b/docs/api/files/webroot%2Ftest%2Ftest_issue29.php.txt new file mode 100644 index 0000000..86fd55f --- /dev/null +++ b/docs/api/files/webroot%2Ftest%2Ftest_issue29.php.txt @@ -0,0 +1,39 @@ +"; + + + +// Provide a short description of the testcase. +$description = ""; + + + +// Use these images in the test +$images = array( + 'kodim08.png', + 'kodim04.png', +); + + + +// For each image, apply these testcases +$testcase = array( + "&rb=$angle&nc", + "&rb=$angle&nc&w=200", + "&rb=$angle&nc&h=200", + "&rb=$angle&nc&w=200&h=200&cf", + "&ra=$angle&nc", + "&ra=$angle&nc&w=200", + "&ra=$angle&nc&h=200", + "&ra=$angle&nc&w=200&h=200&cf", +); + + + +// Apply testcases and present results +include __DIR__ . "/template.php"; + diff --git a/docs/api/files/webroot%2Ftest%2Ftest_issue36_rb-ra-270.php.txt b/docs/api/files/webroot%2Ftest%2Ftest_issue36_rb-ra-270.php.txt new file mode 100644 index 0000000..8f98d26 --- /dev/null +++ b/docs/api/files/webroot%2Ftest%2Ftest_issue36_rb-ra-270.php.txt @@ -0,0 +1,43 @@ +"; + + + +// Provide a short description of the testcase. +$description = ""; + + + +// Use these images in the test +$images = array( + 'kodim08.png', + 'kodim04.png', +); + + + +// For each image, apply these testcases +$testcase = array( + "&rb=$angle&nc", + "&rb=$angle&nc&w=200", + "&rb=$angle&nc&h=200", + "&rb=$angle&nc&w=200&h=200&cf", + "&ra=$angle&nc", + "&ra=$angle&nc&w=200", + "&ra=$angle&nc&h=200", + "&ra=$angle&nc&w=200&h=200&cf", +); + + + +// Apply testcases and present results +include __DIR__ . "/template.php"; + diff --git a/docs/api/files/webroot%2Ftest%2Ftest_issue36_rb-ra-45.php.txt b/docs/api/files/webroot%2Ftest%2Ftest_issue36_rb-ra-45.php.txt new file mode 100644 index 0000000..019678b --- /dev/null +++ b/docs/api/files/webroot%2Ftest%2Ftest_issue36_rb-ra-45.php.txt @@ -0,0 +1,43 @@ +"; + + + +// Provide a short description of the testcase. +$description = ""; + + + +// Use these images in the test +$images = array( + 'kodim08.png', + 'kodim04.png', +); + + + +// For each image, apply these testcases +$testcase = array( + "&rb=$angle&nc", + "&rb=$angle&nc&w=200", + "&rb=$angle&nc&h=200", + "&rb=$angle&nc&w=200&h=200&cf", + "&ra=$angle&nc", + "&ra=$angle&nc&w=200", + "&ra=$angle&nc&h=200", + "&ra=$angle&nc&w=200&h=200&cf", +); + + + +// Apply testcases and present results +include __DIR__ . "/template.php"; + diff --git a/docs/api/files/webroot%2Ftest%2Ftest_issue36_rb-ra-90.php.txt b/docs/api/files/webroot%2Ftest%2Ftest_issue36_rb-ra-90.php.txt new file mode 100644 index 0000000..4e46d7f --- /dev/null +++ b/docs/api/files/webroot%2Ftest%2Ftest_issue36_rb-ra-90.php.txt @@ -0,0 +1,43 @@ +"; + + + +// Provide a short description of the testcase. +$description = ""; + + + +// Use these images in the test +$images = array( + 'kodim08.png', + 'kodim04.png', +); + + + +// For each image, apply these testcases +$testcase = array( + "&rb=$angle&nc", + "&rb=$angle&nc&w=200", + "&rb=$angle&nc&h=200", + "&rb=$angle&nc&w=200&h=200&cf", + "&ra=$angle&nc", + "&ra=$angle&nc&w=200", + "&ra=$angle&nc&h=200", + "&ra=$angle&nc&w=200&h=200&cf", +); + + + +// Applu testcases and present results +include __DIR__ . "/template.php"; + diff --git a/docs/api/files/webroot%2Ftest%2Ftest_issue38.php.txt b/docs/api/files/webroot%2Ftest%2Ftest_issue38.php.txt new file mode 100644 index 0000000..a486353 --- /dev/null +++ b/docs/api/files/webroot%2Ftest%2Ftest_issue38.php.txt @@ -0,0 +1,46 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webrootcheck_system.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.compare.compare-test.html b/docs/api/files/webroot.compare.compare-test.html new file mode 100644 index 0000000..a8ab23c --- /dev/null +++ b/docs/api/files/webroot.compare.compare-test.html @@ -0,0 +1,260 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webroot/comparecompare-test.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.compare.compare.html b/docs/api/files/webroot.compare.compare.html new file mode 100644 index 0000000..4428a9b --- /dev/null +++ b/docs/api/files/webroot.compare.compare.html @@ -0,0 +1,260 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webroot/comparecompare.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.img.html b/docs/api/files/webroot.img.html new file mode 100644 index 0000000..147cdc4 --- /dev/null +++ b/docs/api/files/webroot.img.html @@ -0,0 +1,517 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webrootimg.php

+

Resize and crop images on the fly, store generated images in a cache.

+ + + + +
+ + +
+ + +
+
+

Functions

+
+ +
+ +
+
+ +
+

errorPage()

+ +
errorPage(string  $msg) : void
+

Default configuration options, can be overridden in own config-file.

+ + +

Parameters

+ + + + + + +
string$msg

to display.

+ + + +
+
+ +
+ +
+
+ +
+

get()

+ +
get(mixed  $key, mixed  $default = null) : mixed
+

Get input from query string or return default value if not set.

+ + +

Parameters

+ + + + + + + + + + + +
mixed$key

as string or array of string values to look for in $_GET.

mixed$default

value to return when $key is not set in $_GET.

+ + +

Returns

+ mixed + —

value from $_GET or default value.

+ +
+
+ +
+ +
+
+ +
+

getDefined()

+ +
getDefined(mixed  $key, mixed  $defined, mixed  $undefined) : mixed
+

Get input from query string and set to $defined if defined or else $undefined.

+ + +

Parameters

+ + + + + + + + + + + + + + + + +
mixed$key

as string or array of string values to look for in $_GET.

mixed$defined

value to return when $key is set in $_GET.

mixed$undefined

value to return when $key is not set in $_GET.

+ + +

Returns

+ mixed + —

value as $defined or $undefined.

+ +
+
+ +
+ +
+
+ +
+

getConfig()

+ +
getConfig(string  $key, mixed  $default) : mixed
+

Get value from config array or default if key is not set in config array.

+ + +

Parameters

+ + + + + + + + + + + +
string$key

the key in the config array.

mixed$default

value to be default if $key is not set in config.

+ + +

Returns

+ mixed + —

value as $config[$key] or $default.

+ +
+
+ +
+ +
+
+ +
+

verbose()

+ +
verbose(string  $msg = null) : void
+

Log when verbose mode, when used without argument it returns the result.

+ + +

Parameters

+ + + + + + +
string$msg

to log.

+ + + +
+
+ +
+ + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.img_config.html b/docs/api/files/webroot.img_config.html new file mode 100644 index 0000000..891c4aa --- /dev/null +++ b/docs/api/files/webroot.img_config.html @@ -0,0 +1,251 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webrootimg_config.php

+

Configuration for img.php, name the config file the same as your img.php and +append _config. If you are testing out some in imgtest.php then label that +config-file imgtest_config.php.

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.img_header.html b/docs/api/files/webroot.img_header.html new file mode 100644 index 0000000..4e2bf74 --- /dev/null +++ b/docs/api/files/webroot.img_header.html @@ -0,0 +1,278 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webrootimg_header.php

+

Resize and crop images on the fly, store generated images in a cache.

+

This version is a all-in-one version of img.php, it is not dependant an any other file +so you can simply copy it to any place you want it.

+ + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.imgd.html b/docs/api/files/webroot.imgd.html new file mode 100644 index 0000000..bd841bc --- /dev/null +++ b/docs/api/files/webroot.imgd.html @@ -0,0 +1,544 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webrootimgd.php

+

Resize and crop images on the fly, store generated images in a cache.

+

This version is a all-in-one version of img.php, it is not dependant an any other file +so you can simply copy it to any place you want it.

+ + + +

Classes

+ + + + + + + + + + + + + +
CHttpGetGet a image from a remote server using HTTP GET and If-Modified-Since.
CRemoteImageGet a image from a remote server using HTTP GET and If-Modified-Since.
CImageResize and crop images on the fly, store generated images in a cache.
+
+ + +
+ + +
+
+

Functions

+
+ +
+ +
+
+ +
+

errorPage()

+ +
errorPage(string  $msg) : void
+

Default configuration options, can be overridden in own config-file.

+ + +

Parameters

+ + + + + + +
string$msg

to display.

+ + + +
+
+ +
+ +
+
+ +
+

get()

+ +
get(mixed  $key, mixed  $default = null) : mixed
+

Get input from query string or return default value if not set.

+ + +

Parameters

+ + + + + + + + + + + +
mixed$key

as string or array of string values to look for in $_GET.

mixed$default

value to return when $key is not set in $_GET.

+ + +

Returns

+ mixed + —

value from $_GET or default value.

+ +
+
+ +
+ +
+
+ +
+

getDefined()

+ +
getDefined(mixed  $key, mixed  $defined, mixed  $undefined) : mixed
+

Get input from query string and set to $defined if defined or else $undefined.

+ + +

Parameters

+ + + + + + + + + + + + + + + + +
mixed$key

as string or array of string values to look for in $_GET.

mixed$defined

value to return when $key is set in $_GET.

mixed$undefined

value to return when $key is not set in $_GET.

+ + +

Returns

+ mixed + —

value as $defined or $undefined.

+ +
+
+ +
+ +
+
+ +
+

getConfig()

+ +
getConfig(string  $key, mixed  $default) : mixed
+

Get value from config array or default if key is not set in config array.

+ + +

Parameters

+ + + + + + + + + + + +
string$key

the key in the config array.

mixed$default

value to be default if $key is not set in config.

+ + +

Returns

+ mixed + —

value as $config[$key] or $default.

+ +
+
+ +
+ +
+
+ +
+

verbose()

+ +
verbose(string  $msg = null) : void
+

Log when verbose mode, when used without argument it returns the result.

+ + +

Parameters

+ + + + + + +
string$msg

to log.

+ + + +
+
+ +
+ + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.imgp.html b/docs/api/files/webroot.imgp.html new file mode 100644 index 0000000..a17bd29 --- /dev/null +++ b/docs/api/files/webroot.imgp.html @@ -0,0 +1,544 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webrootimgp.php

+

Resize and crop images on the fly, store generated images in a cache.

+

This version is a all-in-one version of img.php, it is not dependant an any other file +so you can simply copy it to any place you want it.

+ + + +

Classes

+ + + + + + + + + + + + + +
CHttpGetGet a image from a remote server using HTTP GET and If-Modified-Since.
CRemoteImageGet a image from a remote server using HTTP GET and If-Modified-Since.
CImageResize and crop images on the fly, store generated images in a cache.
+
+ + +
+ + +
+
+

Functions

+
+ +
+ +
+
+ +
+

errorPage()

+ +
errorPage(string  $msg) : void
+

Default configuration options, can be overridden in own config-file.

+ + +

Parameters

+ + + + + + +
string$msg

to display.

+ + + +
+
+ +
+ +
+
+ +
+

get()

+ +
get(mixed  $key, mixed  $default = null) : mixed
+

Get input from query string or return default value if not set.

+ + +

Parameters

+ + + + + + + + + + + +
mixed$key

as string or array of string values to look for in $_GET.

mixed$default

value to return when $key is not set in $_GET.

+ + +

Returns

+ mixed + —

value from $_GET or default value.

+ +
+
+ +
+ +
+
+ +
+

getDefined()

+ +
getDefined(mixed  $key, mixed  $defined, mixed  $undefined) : mixed
+

Get input from query string and set to $defined if defined or else $undefined.

+ + +

Parameters

+ + + + + + + + + + + + + + + + +
mixed$key

as string or array of string values to look for in $_GET.

mixed$defined

value to return when $key is set in $_GET.

mixed$undefined

value to return when $key is not set in $_GET.

+ + +

Returns

+ mixed + —

value as $defined or $undefined.

+ +
+
+ +
+ +
+
+ +
+

getConfig()

+ +
getConfig(string  $key, mixed  $default) : mixed
+

Get value from config array or default if key is not set in config array.

+ + +

Parameters

+ + + + + + + + + + + +
string$key

the key in the config array.

mixed$default

value to be default if $key is not set in config.

+ + +

Returns

+ mixed + —

value as $config[$key] or $default.

+ +
+
+ +
+ +
+
+ +
+

verbose()

+ +
verbose(string  $msg = null) : void
+

Log when verbose mode, when used without argument it returns the result.

+ + +

Parameters

+ + + + + + +
string$msg

to log.

+ + + +
+
+ +
+ + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.imgs.html b/docs/api/files/webroot.imgs.html new file mode 100644 index 0000000..643aa98 --- /dev/null +++ b/docs/api/files/webroot.imgs.html @@ -0,0 +1,544 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webrootimgs.php

+

Resize and crop images on the fly, store generated images in a cache.

+

This version is a all-in-one version of img.php, it is not dependant an any other file +so you can simply copy it to any place you want it.

+ + + +

Classes

+ + + + + + + + + + + + + +
CHttpGetGet a image from a remote server using HTTP GET and If-Modified-Since.
CRemoteImageGet a image from a remote server using HTTP GET and If-Modified-Since.
CImageResize and crop images on the fly, store generated images in a cache.
+
+ + +
+ + +
+
+

Functions

+
+ +
+ +
+
+ +
+

errorPage()

+ +
errorPage(string  $msg) : void
+

Default configuration options, can be overridden in own config-file.

+ + +

Parameters

+ + + + + + +
string$msg

to display.

+ + + +
+
+ +
+ +
+
+ +
+

get()

+ +
get(mixed  $key, mixed  $default = null) : mixed
+

Get input from query string or return default value if not set.

+ + +

Parameters

+ + + + + + + + + + + +
mixed$key

as string or array of string values to look for in $_GET.

mixed$default

value to return when $key is not set in $_GET.

+ + +

Returns

+ mixed + —

value from $_GET or default value.

+ +
+
+ +
+ +
+
+ +
+

getDefined()

+ +
getDefined(mixed  $key, mixed  $defined, mixed  $undefined) : mixed
+

Get input from query string and set to $defined if defined or else $undefined.

+ + +

Parameters

+ + + + + + + + + + + + + + + + +
mixed$key

as string or array of string values to look for in $_GET.

mixed$defined

value to return when $key is set in $_GET.

mixed$undefined

value to return when $key is not set in $_GET.

+ + +

Returns

+ mixed + —

value as $defined or $undefined.

+ +
+
+ +
+ +
+
+ +
+

getConfig()

+ +
getConfig(string  $key, mixed  $default) : mixed
+

Get value from config array or default if key is not set in config array.

+ + +

Parameters

+ + + + + + + + + + + +
string$key

the key in the config array.

mixed$default

value to be default if $key is not set in config.

+ + +

Returns

+ mixed + —

value as $config[$key] or $default.

+ +
+
+ +
+ +
+
+ +
+

verbose()

+ +
verbose(string  $msg = null) : void
+

Log when verbose mode, when used without argument it returns the result.

+ + +

Parameters

+ + + + + + +
string$msg

to log.

+ + + +
+
+ +
+ + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.test.config.html b/docs/api/files/webroot.test.config.html new file mode 100644 index 0000000..cadcf2f --- /dev/null +++ b/docs/api/files/webroot.test.config.html @@ -0,0 +1,260 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webroot/testconfig.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.test.template.html b/docs/api/files/webroot.test.template.html new file mode 100644 index 0000000..7a2e8e0 --- /dev/null +++ b/docs/api/files/webroot.test.template.html @@ -0,0 +1,260 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webroot/testtemplate.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.test.test.html b/docs/api/files/webroot.test.test.html new file mode 100644 index 0000000..121c169 --- /dev/null +++ b/docs/api/files/webroot.test.test.html @@ -0,0 +1,260 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webroot/testtest.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.test.test_issue29.html b/docs/api/files/webroot.test.test_issue29.html new file mode 100644 index 0000000..c0a8cd5 --- /dev/null +++ b/docs/api/files/webroot.test.test_issue29.html @@ -0,0 +1,260 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webroot/testtest_issue29.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.test.test_issue36_aro.html b/docs/api/files/webroot.test.test_issue36_aro.html new file mode 100644 index 0000000..ac49903 --- /dev/null +++ b/docs/api/files/webroot.test.test_issue36_aro.html @@ -0,0 +1,260 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webroot/testtest_issue36_aro.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.test.test_issue36_rb-ra-180.html b/docs/api/files/webroot.test.test_issue36_rb-ra-180.html new file mode 100644 index 0000000..dba207e --- /dev/null +++ b/docs/api/files/webroot.test.test_issue36_rb-ra-180.html @@ -0,0 +1,260 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webroot/testtest_issue36_rb-ra-180.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.test.test_issue36_rb-ra-270.html b/docs/api/files/webroot.test.test_issue36_rb-ra-270.html new file mode 100644 index 0000000..023764e --- /dev/null +++ b/docs/api/files/webroot.test.test_issue36_rb-ra-270.html @@ -0,0 +1,260 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webroot/testtest_issue36_rb-ra-270.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.test.test_issue36_rb-ra-45.html b/docs/api/files/webroot.test.test_issue36_rb-ra-45.html new file mode 100644 index 0000000..687f2a6 --- /dev/null +++ b/docs/api/files/webroot.test.test_issue36_rb-ra-45.html @@ -0,0 +1,260 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webroot/testtest_issue36_rb-ra-45.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.test.test_issue36_rb-ra-90.html b/docs/api/files/webroot.test.test_issue36_rb-ra-90.html new file mode 100644 index 0000000..e757b56 --- /dev/null +++ b/docs/api/files/webroot.test.test_issue36_rb-ra-90.html @@ -0,0 +1,260 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webroot/testtest_issue36_rb-ra-90.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.test.test_issue38.html b/docs/api/files/webroot.test.test_issue38.html new file mode 100644 index 0000000..1a0c854 --- /dev/null +++ b/docs/api/files/webroot.test.test_issue38.html @@ -0,0 +1,260 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webroot/testtest_issue38.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.test.test_issue40.html b/docs/api/files/webroot.test.test_issue40.html new file mode 100644 index 0000000..ad94e98 --- /dev/null +++ b/docs/api/files/webroot.test.test_issue40.html @@ -0,0 +1,260 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webroot/testtest_issue40.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.test.test_issue49.html b/docs/api/files/webroot.test.test_issue49.html new file mode 100644 index 0000000..776ea64 --- /dev/null +++ b/docs/api/files/webroot.test.test_issue49.html @@ -0,0 +1,260 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webroot/testtest_issue49.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.test.test_issue52-cf.html b/docs/api/files/webroot.test.test_issue52-cf.html new file mode 100644 index 0000000..0fa6dc8 --- /dev/null +++ b/docs/api/files/webroot.test.test_issue52-cf.html @@ -0,0 +1,260 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webroot/testtest_issue52-cf.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.test.test_issue52-stretch.html b/docs/api/files/webroot.test.test_issue52-stretch.html new file mode 100644 index 0000000..3c9da99 --- /dev/null +++ b/docs/api/files/webroot.test.test_issue52-stretch.html @@ -0,0 +1,260 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webroot/testtest_issue52-stretch.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.test.test_issue52.html b/docs/api/files/webroot.test.test_issue52.html new file mode 100644 index 0000000..c1825aa --- /dev/null +++ b/docs/api/files/webroot.test.test_issue52.html @@ -0,0 +1,260 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webroot/testtest_issue52.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.test.test_issue58.html b/docs/api/files/webroot.test.test_issue58.html new file mode 100644 index 0000000..1c32519 --- /dev/null +++ b/docs/api/files/webroot.test.test_issue58.html @@ -0,0 +1,260 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webroot/testtest_issue58.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.test.test_issue60.html b/docs/api/files/webroot.test.test_issue60.html new file mode 100644 index 0000000..5bc10d2 --- /dev/null +++ b/docs/api/files/webroot.test.test_issue60.html @@ -0,0 +1,260 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webroot/testtest_issue60.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.test.test_option-crop.html b/docs/api/files/webroot.test.test_option-crop.html new file mode 100644 index 0000000..532b505 --- /dev/null +++ b/docs/api/files/webroot.test.test_option-crop.html @@ -0,0 +1,260 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webroot/testtest_option-crop.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.test.test_option-no-upscale.html b/docs/api/files/webroot.test.test_option-no-upscale.html new file mode 100644 index 0000000..fe8e98e --- /dev/null +++ b/docs/api/files/webroot.test.test_option-no-upscale.html @@ -0,0 +1,260 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webroot/testtest_option-no-upscale.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/files/webroot.test.test_option-save-as.html b/docs/api/files/webroot.test.test_option-save-as.html new file mode 100644 index 0000000..6f1be3d --- /dev/null +++ b/docs/api/files/webroot.test.test_option-save-as.html @@ -0,0 +1,260 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + +

webroot/testtest_option-save-as.php

+

+ + + + +
+ + +
+ + + +
+
+ + + + +
+ + + diff --git a/docs/api/font/FontAwesome.otf b/docs/api/font/FontAwesome.otf new file mode 100644 index 0000000000000000000000000000000000000000..3461e3fce6a37f2321ecbe64707f04c0a4f05424 GIT binary patch literal 75188 zcmd42cYG5^*C@QQyeoNEmI+v3OJ1!hp?BN#Bql&0F(rUQ=*C61jEjsU_uhM#yN!)a zZ=nSOfy5~U0x2Yzbn+xmdPp$|WF(Ia_sq&BJV=l2St!J@u6Ld}hLIigG(oNmSNVYp2bu*`8&mCT(g%JrKs|W6VjPjD7@?@<57`EsF_Gij(~7S;$jX z2uy5njLz_R|~-giuu0_{g+f+ve88OQ{Hz6>Y}qGm4HH8LdoEP_Ir~wHA13xKq37E z1Q7#%ImkKEQfdVC%s|@tAvjG9rGf|G%tLS)wVE;wz~z*JdUGJ{Lb24ffiy>{sLuw{ zN=i%p4&x(nc7ggcB(4K#2{l|&I*@jvl#*QoX(=^T^1?vc`5_#d8Y8(m0B0V8%cxE# z$pBnVc{p_qq+KX?r%B0{5Tf&5n`|=c zVocso$A%h=aRy_sSr<0ddtr36w}@);HtI||V*&u5GQ^q}ChAOv7#*33dEO5J<`I%J z*XfL=NJFf*@6;JnrxS?Jv(dU>lMZNv>x|xJgST0)^ZvUTCS9nR;D;OFCALM<4|cS0 zYNX*m0;fd-nOKu<8nuWrP;pc;Wuzjh2ue?xfq7<9)2SJhFQgVyVemeL(m{GHV42{( zj*5ZUn|hjxr9$DY5z3R_VDViTHB~GZO+`ceH&s%?2xUzWj8p>r63nNdWGs|hNF|Ez z3_x2)t$`3h#RG+4z;(3FM9l*V{~4dWakP0RwGPS}p_WLIvN!Z%D)eP4^k@*r2UcJ- zU?=@*H?{x2(58Ba^p5QH?|rs+TW>_~_TISOtlp~Lj^1Ov-M!a( zzv}J1P2C=H+Z$+4xIO*0|LtdQZ@V3LJL`7U?T*{WZg=0lcKa(r`~KGyb$|2y)%O?P zpZ?_!U)FuOW{B$$#SnIg%Mkex*jk1-50MUW8X_4&x!rfW>vqTOPq#nZZoBom{jU7N z?Kihy-F|Vq<@U4NPi{ZD{owY!+jnl?x_#sJZ|;=H59|LA1MdHSc=vyM#lTJ^gTau`vLeC!n{ysFfXP_Uc`a;;df9b8Q`%5CI-q;j?f_Z z08@+f2t13LIfyd|dpIWbJ7SE2M+X6Y`2Iv zkp55o6=8%9;E*zdF@cM1gm+?lAU^<05&JWMOK=9?GfrY#nxQ=#37!98@E7s2C_kX( zC)VL8>pEDTzy|wG(u4WIx(CZZyip8U549cAFn<07M;srB30*Ni03+$ax};f-cVgcG z?mU=>^dM|7CT$V}dFOaPnef&?TC8tyti(D1AN0WcgLMzq`5)sfN=5Jn`%Q2L%ZV|e zl|>C1nDg+#cYyEwFueh|8;M1@qnrlv{tx2;EpI}L@Bj%;S(HBnvCo4r5Z3J3VAh|L z<;C|91Fq}f+8ik7{a@>YGGgBWt|0H6vm9+D_>zG%!GU*vgSez_v3`gN?0**q@gSZe z&4DsfsLMf%#B&~$%c2BmvjBC70pNldvK)OGz|)9*7$^_8{)72JI)izrZzX|2bz&g6 z5X63xu^GT)2Fno{M$)8hgZ%>hi3CQMO{9n`r5)Xz4^*h=``X=^^&>Ji>7z6GQLVJL| z_aKddx*yOSg#T~iaf59p@jw_#VuBCxViH3?=0bWXsoR0$7|#Km$Kk!l!}JNx|I;6t zQ24u_Le9kh{ZB$TOd^pe9yTF>YR?YaZFd!x{0fp_1!PlmxoWQIAlbaCRI;O$No?ms&tQPAvhI zcLfzlZKi@i0oH?3y#tirbSj%Fr|PH{s)IU29j9KRy5UrMnfj3Wg8G5#rT(J&;SlaD zkxPb1R1%Fukc^g0koZa-mn@S6N`fTOk~m4Kq)<{PX_a(J4ogl*PD{>7UX{Elxh%OM z`M2a}$#0TBB}j75iFF$8q;VSOd9(^983P8*!UoeWONP6bYtPEAf-PDhGoA5e9%B|W&ob+o%}g+} zor!0%nS7>*zlX3A#CmdZBB z^s;1GuB<{3O7?^7SJ@vjyIdjH$RCx@kk69Omp?9FB3~hY zP97?ck{jen@yA4hRgPR6M?4L6qa z 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�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 DgjuED literal 0 HcmV?d00001 diff --git a/docs/api/font/fontawesome-webfont.eot b/docs/api/font/fontawesome-webfont.eot new file mode 100644 index 0000000000000000000000000000000000000000..6cfd56609567bc9db55186415c694d1d32808fc2 GIT binary patch literal 72449 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
U~M!&>g0b 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 literal 0 HcmV?d00001 diff --git a/docs/api/font/fontawesome-webfont.svg b/docs/api/font/fontawesome-webfont.svg new file mode 100644 index 0000000..a9f8469 --- /dev/null +++ b/docs/api/font/fontawesome-webfont.svg @@ -0,0 +1,504 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/api/font/fontawesome-webfont.ttf b/docs/api/font/fontawesome-webfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..5cd6cff6d6f6cf438a882e366420dbcc5dddd3f1 GIT binary patch literal 141564 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ƓCICn66&u!PHl6pn& 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%0D%8GQ!Ai?6y^fVUY%4hi>cI9O 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
KT23~?3Dw&p)A=062yj$!;@;Sv13 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 + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+ + +
+ + + diff --git a/docs/api/graphs/classes.svg b/docs/api/graphs/classes.svg new file mode 100644 index 0000000..62bcf27 --- /dev/null +++ b/docs/api/graphs/classes.svg @@ -0,0 +1,37 @@ + + + + + + +G + +cluster_Global + +Global + + +\\CRemoteImage + +CRemoteImage + + +\\CImage + +CImage + + +\\CHttpGet + +CHttpGet + + +\\CWhitelist + +CWhitelist + + + diff --git a/docs/api/images/apple-touch-icon-114x114.png b/docs/api/images/apple-touch-icon-114x114.png new file mode 100644 index 0000000000000000000000000000000000000000..1506f6a668fbb2837c06b561895da248c310ac53 GIT binary patch literal 28338 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�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 literal 0 HcmV?d00001 diff --git a/docs/api/images/apple-touch-icon.png b/docs/api/images/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2d320cb5e1215894ef37cf8c6fd7a0085eba06f3 GIT binary patch literal 8358 zcmV;XAX(puP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iyV# z6d)&eD=4M_03ZNKL_t(&-p!kNwC!hE-#^bg{D!^v8SlxRax-(2keMNY5JDspOb`X9 z)*7v>qAhC0sue1UN@i)9v|M(qT zZCCrh?^^qhv-V#5yuast-skxa&lYyq;-JsYWcr;y`QbNyfA5mB_wQR?TD(f*RtR1v zAH*mnB7(LC(K)SafP?`AA*4u15MxAQKr2CIIVpADYqSs>PqGRh6Os~{GQ<>!K#1K3 zjUG%=(YlSCH~0iz9renu3CXxh5QDnm>WA3ZM8MP3X=VM#_4Lm<;aQ#(wiX`>(^ zQsfz~bwnjpbxV<13Tr_nnx>(49-}jQnZgICTgSAnlU7Px+om!rWYFugvUlHbtoncO zv;XRS@4D+lznyM+%davh3v}x}A9}~nbMaltu|85@lHsV!&iM0@&Q5lmZcy5{xnAd4|>s=+Z4Am@+4{HP&WWs|hiZ zqziPBXJom-2S*Y^jLs3Yn#>qPX{^zNlprNSjM&}H+02l>bHP*UPAzqtRzQ}Q zC~HaHVY7^sBBC|Hd#p7O1Cd1V5n~iZZt>m|QX(cOttKXD+m?jFW)>o7r4c}Bg^P)4 z)23dpr^e%9I&a^~3+I=Yzx>dXM~*8Xzi9u`^`qg=nK30akQ*%sDbEUAjLe#rm=Xyg zGhl3Yf^BGsAwf)NV-V?1OGsFoA<>g05tBy*lr^LjQCgFtLmQ1s5v9~_fFjmtw9@zl zAq0xd014Mrgajrt1m`hYBP6ue_z;*>)07#c%ngMx>};GqaOU#d^+2I7KL4yU&)v89 z?oy|H#wZXaMk|!o61+ocgZB|3cI`kZl5as-VhA7#NeQjGAjIeqAVp7%5t2en#Ar=I zL5wI-#NCRn1d<5e?PiVDcpnMAB}G9K2?5%W2q}_MLJ_-D6rpKrY8S{eD_NE!!Kbn) z#V8>ud(KwvZdZfC>@7{Y;`tLNlaERAOs5nRMhTheQc0p`Qaf7bNimV<1v+{Z5QD?} zh)M~??mbi*C5pCfnGAPmT_hQc$xE{SJf_ztMWLO|n2d(hvuRg(jYeyOZyG`nlz>tO z*H$?1QA8wq6ha7&x~-^v#F#{;HJMVBxs{Yc+8B+J_p=L4@@JUYbUK$@jmdM_98XwC zeOc(`-6^3^BwD(NPi7TmW+`$@R^<5DHU8E$NEPVM4XCRbn``Ts{yb-#^Fme*p2vZ6 zF5rxVhZrm@po}57z+||^`l;h=9Dk0}N1x*O(~pyncUW3pAqj+(D2=9RYFeLA0x^Qw z1r9z2YL~D^cfdglMhT5mY>h`Us#+Ej%%;clKp3$#euV5 zz`OqVPVW8mC%E&&zr$Q^(M3rN0Zl@b(6*k+c}iju{b5)n{jpIi-{=`E}*PdfC+F-i1&EnpDi0!d*)&(5A@Jf~r9$;bR9OhRtV3X@^ zzL}TZbThyAwx47B=;O={O0-t^Buwg>vdA%7QD{v}!e}yM);dB0WfV4Ql+{Gn zDO6_hvxdoRM!zgEO5?rX&GA5_rI(qm!b(CTv`x)3r^dYL9ly<0*WU3=r}@o~-oe3h7D%Z=WFKGu^v8MTzR$3b zE2_zms`dzp$#lxZ7_?6K07isaQ!^OM6XJ|KE6}paaC4p0kA9PLu6_+K{hl`hC;6^7 z+{g`hZN*WgS47lWF zH}TcK{GZJAGA5N{<^xIuYcn*EVn8cRA~I_lnxH6i%j3s4ZqqCCy~CHExBu1<6FMbY z*X6d+8m)xr19=BNfy8iY#LaL1Wv+b1i%BU^SCQM^^V>A1pT-))#`+qg(TEI%C{2n9 zB|>z*LoZ2CDG_7Bh~S;aXhrKI(K*JW5z1O*w#5_Qet`2|d=16~_Uu1EpwB~JyB8E_ z5wsF;Eg~Qyv>|pu6HAN{MdI-jTem3@$V8DTO~2P?(C?#_qG=rCX-(}Mt!u%zScJuc zhk5lIzMGULNK)MK!4D8No?|qbaO(6bnbBm{Qa3GDghawh!a2vr<`xe>_-&qi>N!pw zdk%@7%xF^V0K7_xx^5Y7Z&6K#>>PWFPk!Koh*}_pF|T>!_n^uZtOy3kl*VX-_nw#_ z#YjS^>lw4E!nF;ggo-gyHw|suF?_2uJ)zdXDW_62ILKGpyz{crQoYbpgr{{U%m4)h)(D-@=t&7_t{$Crpz-M?}%EHL}@DIoa^IUt=_w(*g-pdQGzKp?w<>HrK$XkE^W2{_!En)s3 zX8#pz?@TZ;;bO!nMQU0q2cP`FZ?Uy8fEL~1 zTdhe6ZaSiCl#u6^2Om7bpWgpXEF&o9dCyP%W6oSs ztgWuoxEdc4gv9*bgV;T1am`J)aK^p^jCVGfjb|MF>u)o+w9FOPUq^_E(Z-k)&pga! zue_PN{^a-hyYKinOm;T7?R~$)@YrL7@fN1vL$v0={=IBX;I04u--91A8Y|v(&DC6d z>AA!ZNueubQH0t#oR5r~meLwN``0IaK+l)<@L3DRtu7|C$gU{Tp$J6beamz_;-(+} zX)b@oZTlX_4 zgr@NnMM=JV7JCouA@603wgX#7{u-O-L=hSvXnnwiNb5a*m+wBiHo8qOl=krXE4^Do zh-g4kf)w%2BMOw!Os0XGe&Sa4uPlN#eBpn5oYAo-NzM^cmtKRiWIV2sl(_7bujcTJ z53{gGbKv~5Svhne_xzU+V{D&%Zh@7v&f)ZtCwbsApX18c+{hI-T+fA9U&FKCet<83 z{Nudn#+Pxr&Nny*m%ieq;2R=v-(7bhx~r<*c|wSU6!E?z^|^*; z*T=URV+{SlJYhP+D8Z^OT}hn~721F;2kcp1BD$8X)eY8(4$+ zJDWjh$%~sFF zP8{d%kKE4jXC9?*JuwMJ61l+U@uRFi{TwTMmsnX|0^36g;2k0nqZKLw3cL%DGzl=G z6j`358jsC1Mj}QVi~?;m$$9qdJ;dBxjwr=!Rx#aJC5oo@j^rI}RWn#Q#2bI{ZJauC zgu&b*M;>^Tlh5Q_aMdM@H-?-(ay!M`Jclm2it{di3C4IHy8Ex#bMP$c$%v{NbLhes za`|g+;m?2TZ7eV3#3E-ptEj!h)e5tEnpsE?J#%L)uy^l4>g}Ug1y<)c?>k~mA}LK0 zI+E>uU^Jdm*ELn$P`gO$18oB5Beiq%miAEg`-miB>f!4#RtXsao#6bJ-OQVR=eM}@ z+RON^A9@Xgg@U7xKfk9i3qK8>}>DQ>t`tGn)lMagDmeqK&(e- zqq*!=S5wR{^08lkCl_D)N_vY2_~sp-Vz6g{*T41WXsd=V{+Ituf6rdLi`;+b=b3DM zf-^6@m^b~#+xc(5`yrmc_kWW2=V%&-SAz4Asv6>2)ukXBljZo35Fe1F2+>0lQcMUP zvLYo+iUD8EkO)di*I$xCJ5QEbT!g$(5IqvQq7+>N(cq(JFjw;M9e+m8T3+>|ucgc_ zsomiPR~=%{yZ#w}{7-(F(dG`{{|j&4CC`p2flF_^i3dJ&7f;^zSzh}yzsv4FC}WY>cEV6fC_L6=^sELGk)+A6_(+GBRd<(_5)2|Jt%V%IB(+7L)+)9IM((yYmBhOZiO zl@Q|yCZ~1|trA9;OhzO2EG<%>ew5F>|GixIQ*YtWMf-q0&8*?suieeVci+K_u6;R| zzWxU}_p0;B`vnk5VFuBmYzfTs$d|s#xBlYYT=ar72!2YdpbcHC!WJmgAhLjO9ChRH z0x@=X&(qZtkI;BeiUA)ZCdNozSIioZ#DKM$6gp8T(;90HaXh6_n!Ge*Y_l>yhmo4t zHl!4ZA#!?s6Kf3%XCLCrAN_MKc8O!3Bl_D*&-D?a<~U!s5LY_563O{8W7 z#(*+}Q1bZaALLtie2SH`&t~oTb7a))Olq`N=-7ELg)Xr`N|CCm@xyKUd4>xSA3v*pe}TdnVmZg@qsK6Lv0J_s z));Jph2=g!__m+t@h^XkA9}|-fgPY>_1H1SRm(R&c_)XjzLNf8kFBF?Y@XQQ;m>`M z{^Ecidgm|EUpmagU-<$`0=d@snD8V*OxVFZnbJs%%qBC2>!)`m(||=#bS)#K*tNh; zvc_1WSse74)-7dj(Ywg5b&eDxq7`nk%I5J^_8Z}hJ$vY{9AJF%8467&^Y5blD1s=3 zmYOSWxPs)K??8po7-d=9e~9y6ejT6vj~``e|9-M^z`=_T@%_K_)AW{lFdIVJKuf|W zXnp7c4M<3wap-LJ?OOrgu(>&8HXETN5n`vdhm?pBVhpr(i?X__u1dPgUNsHQx7~xK z?xm#DTdlG@cjP#iUV0wND6V_`E!_Fk14K9L#9gh(iDX(>d-_S%j*mHT?i^|6P)38Y zh-h-7x%P*@m(8OmSURwW{+z{F)m6i4jAS{;9@+qwL?(eYDkfD!IaubJTfPU?&%g=K zKK2|c)HvySVpM`_JM7vxk53?~lj&8}wwzd7XLD!BcrvAJ+Ff}c@;qnIE7`Yao_jv> zF?`-9wT_oudj+-bV|xWw6r~anMI91l8u7`0`Kvtqr6-Wy97!u^8nkhs6uo)FxtH!^ zeknssBE>`$jY~O?f8#m+>{s5xoQ#>emL`HVuxEZiMZrt1zJxR!Q>UDJ{`5~+S)QkF zH7Z6z>rf&nrOERGWi*XX#FS7erOx`6&galdD6D0^SI{>atGZS~yT)ff{yC&9KmxD- zhd;;8>1}$Yr49)f5=Ii$WP&y|5B}-<`RoTj!f4n+o)f&nWEx43d_-)CbHc`nF^}H; zE&l7<{y7i+=l7$gN6B(csT2Uys^#dhO@8EOeikGky@K2S_`j1Cj$zdx+E5lb3;mKz z3(+++)sz$yMUkU+=^~jHlC|{noJ=Kht;jOdc^?6i((E~K2A})rN4VjQw@~^quXx>c z9DnTZbMMFg0|(DOhzgG2TV^hzjmGq0^^tq|;`@(s*^m4<$}V<>k?DiUdFZaMa^it+ zq9*HXub-q(U~Mu%`M_{iA;z#c=yCM9V_f?qZ|0>pTnV!o>GAxN z&vV5M*OA6M9DecT9Di<`?WZ2V^mf@F`iWv8Ul*Vtu4-f`S)?_+kYChwS$yB{^&Q~kEvH_ zniiE3NffnnjK`Di9_o%32|}3}p4pt;rss2g_`Id^R*T=W%g^=Qk$Fmk~H1qRwWJVL6BR7g(-uaL&1SWNh zcQsqbS2^d}>lpNezDitiw1{;p<@fKWm>gNM?8xWMp(~JDItkV087aZE zs{bZ<&+k;7GVp@^i?`amKr4{o$#oY6E#QKuZavO-wt272=_|)KK7B8TU;ZlkS)wd6 zu6pea?XIr_r1hsn-Cldp$-ARD|$6eyF0Jb4OVDvP&PwI9NVdH(~G_Q@OdkP zTPKqVbD5==XLMu+oDa;}7HcfpXa;4D4xaINLVsZR`dwe<%*(G~>ChsX2Uu2*)AtZ_{r*SQrHpGHV z_IjPr8v-P#eV`42%v!9`EcAM)7#NO5Bx~8)*k}b#b3W5nsJ$ZOOI&cn_wvKv9Rmd(vAs-~rh5t4LMCrS}wB*a8akv4V?03w7WWJ+;lySh!zUzvN|Eteg- zb7xwS8e?yq?2 z{(Gr>&3Jp8BF|`3qHZHmgv?qBqiNcfY27fc8XD(md}rK-kk~Wmard*^Z!|u5>bfGt z$h7vNi1dnrJj=*5%=dasMnjyB=sagKnNsE%rBTEX*cgsU3F58z$# z7FaF#0FK1Yq-Jr@+buf~V`5m<0-%gRQu4-yz&=dA<@-VXY{wSv`FkZ7jAZ*%=Ny^R=zec}_xM zY|e0d2S}884;LeC1X{^1^`rLLZ8T!uBx)rYSpp=re)2C8alw$In?VT-pMUUiL`eljI8f7ix(S%51zQ`$xf|>Ie zYan#KV2sd&Nc0{_oiVz;zJY5Rs_BerZrN<$<>|P6|lKFY3?iYY}hm{$BrFKm`tK**4H;u zo)st}nax1d?qrlOYa4QFLAvpmn1ryq$>2THy6zspI})nXK$WIx8XDK&oujTDZPRuw ztEv&LX?>(^TkV$+K(2FPs`y4{WTj6O~15mew~nQ{L-|5|VQcWelU~49Rk~ws$bvl7a`VnKT|B zB0dCy^8^$Q>6|VE=UST9}!_PYIQr4~(ZXHYUwOPp^%?ATZvwub4iyIvkz3XYrE#i-WzV zHn&n)_QWc|H;wfA1L~$tw(N^@j;gA#TA`H&ZD~U2O0~B5w(e3-6yAGOiroaVk5qMy z+7*7AkZ`FBLQ0WoT@#e0s%tWn(KrWM6H{bTH6TJ!WcV0pePmWQ31y_N8;MGi)`sz{ zN`oR(n{{~NQ;!^bAMjP6)^sCfO@ghHqk7ahx9{BL!I>c@nN6pZWe*o3$r#bbrj5-_ zDe{bz621O_;dDllu22}DZ5?geJ(V_4kLw(7*`D;3eI`bF4(SoqzRpz z(z<}nbEb8Rww9gkA%ppOTJLd@)Os(ij|ruj&SoSuXUzB1w@+-}|BdH2|1iY#X{}^E zCAy*B-SP8ri0SnDq}`~KHhW98cdlP%N-L>+l%nXR)s0PIcZ@sBa@r7?wjStIg%CO@ zSo?%1P3s(1YoZutt;fZL05QO{btG$PL!fCI0x)eoiEaejdq;@SghW)Tn@?{WvaF=3 zYiW~8Rb3NF)%Sza=!DzdC#G~A}w8=#fOYS4KRwkkhL3{omOTZ>r1`h4M_UumnDgO4X1T??~Z~{0ku#x_+UlRO(AM*b#4&@I3000_E wL_t(~-_WJ?-=+ur&Wb?T{fR{S|9$9x0o_ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/docs/api/images/favicon.ico b/docs/api/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..9575ac8f5be939dfdba64bf595be644d900f55ec GIT binary patch literal 1150 zcmb7@T})eL7{^OAdSkNPDABB~f}pG$@MxMIpqW7mIX;E^75S4MuOE;bUjQn_}Q21AftSU z6k~||rZAd@Fh@Xhb=BXDCE*8YG7?-JRj5suW1Qf!HkqW7@xu!$_iExwPFSsJ9+KE?NUAM))E z6xydTc$U#SViedH_U78S|A2y~DCx#-(v$$H`4$dn?PQhqqi-4`G_s7vHHlVwl(t1q zPQFK-`)yuqk7H=Pg5~8mY4w~ZN7lJ4funnYpCmsKejmm0 zI^W5@)HSc7sq7`Q*oDs2A?z?EjxaaHYfEpO8h@OE@;-jJvB9RWUtC?ogo`hvUUopeH2@VsCs3J64wOs3hABt1amM>Q}7*8oO&6(;~#MAZ>a5atT{#k<_C;QeW_4mAtO^GM3?S@9^N?*dhxFqXh2S_1RZKS!-Min;3o4boTRiI9=4DG*+DTTXE}DUtoZ LNHCv2rc(U_{c|km literal 0 HcmV?d00001 diff --git a/docs/api/images/hierarchy-item.png b/docs/api/images/hierarchy-item.png new file mode 100644 index 0000000000000000000000000000000000000000..c7756e75ac9fc933cbd3b26d0f2817d33857d15b GIT binary patch literal 236 zcmeAS@N?(olHy`uVBq!ia0vp^{6H+o!2~3GGZMc6Ddu7)&kzm{j@u9Y9{{-mC9V-A z!TD(=<%vb948a+tB_*jv$xRnHfpQ(5E{-7@=aWs$%+eI33=AhQX#}3tTDL~=wC|Fv zu%{hu$0B^+IX=i!aWC8~w073*RaN&Uv6)APHLbdN<3>b%>>TUAi;C*}O=q!*m27po z*1LM!!3v?DGncuq?yGJ6C|P)9&kpBM|CRr~zT5MTS-)Pmq&#hz+O8m*w9T6~{rmTK i_6>m}i>4eazx`$b{PnHj4|Gm%darcLQk;o* zf#$tx=CVf?b@LflRVgT`A4r>)ny*wOfBfV0Re8*{>=Tw{&Jvb7&^=Rj=FL?dSI+o+ zj+wDu!1bbDfvS@D-MsHb)4DtUth5UMD^ajC=%tzbteK)-Y1>$NqSuB6eNo_^)WOrR zY1Opp)8-WOJ21tpe#PamG$?X)*y`7bP0l+XkK@Tjds literal 0 HcmV?d00001 diff --git a/docs/api/images/icon-class.svg b/docs/api/images/icon-class.svg new file mode 100644 index 0000000..7dacd0c --- /dev/null +++ b/docs/api/images/icon-class.svg @@ -0,0 +1,77 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/docs/api/images/icon-interface-13x13.png b/docs/api/images/icon-interface-13x13.png new file mode 100644 index 0000000000000000000000000000000000000000..aa24fa96a487f029b3d4ce8c94d363450d4d17f2 GIT binary patch literal 308 zcmeAS@N?(olHy`uVBq!ia0vp@Ak4uAB#T}@sR2?f#ZI0f96(URkK{-HEu#$20vdnjmCqCCqTlR9{ z``_NTxAhrHntD@-v|F+sFBiRXjpPCa_N + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/docs/api/images/icon-trait-13x13.png b/docs/api/images/icon-trait-13x13.png new file mode 100644 index 0000000000000000000000000000000000000000..3c2792b1b1bd6b2ffed9fa34e05db2b7405c1503 GIT binary patch literal 340 zcmeAS@N?(olHy`uVBq!ia0vp@Ak4uAB#T}@sR2?f#ZI0f96(URkpP3?N+}ye3 zderN?20Z@Of&3c#Z@=H~WO&R%^mySNk6l@_p4G1U|05->KS%sy!2_jpLKim9y=2w0 eH#6MfJFD7!kJZ`Qj1oZ4F?hQAxvX + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/docs/api/images/iviewer/grab.cur b/docs/api/images/iviewer/grab.cur new file mode 100644 index 0000000000000000000000000000000000000000..ef540be09383a215ba21287683ae956b74a6dbbf GIT binary patch literal 1150 zcmeHEF%H8Z3_L;+LM2k9N*yv~>~H#wK9i4B>Jy?)*;1Rr5Mf~HSVzWizBA$iDEg)e zv@Up@fGdC-B|(IIaVjw`XMnR4do4(}ceLED$#?I4PhZt?V;F{(zNyX4aU6>oTI<~I zR%1+|Z@pO>D9unx@mz^sV2R6KAHrH&a3w(Uah_+1dcf$ic$W0J$AsEGc`x}F-{G8# oT0W#ZA~$;zN&n?%4r~@-v(K$0B;Q#;t literal 0 HcmV?d00001 diff --git a/docs/api/images/iviewer/hand.cur b/docs/api/images/iviewer/hand.cur new file mode 100644 index 0000000000000000000000000000000000000000..1a5bafb5263fd4937dda4098b04aa5c70e2de924 GIT binary patch literal 1150 zcma)3OKyWe4E694LIn}22q6~8?0U4`qB$BzVUYuLnN^5A#g-vqrbZ8co}UNC0FIu^ z1?Y|NodI+J@I{~!RsQPaynTKEFe0{|*)R-iM9cJuPktQ*F&}vmxJX7;nU}sFz|xzG*A)o1}vy~Np8!oh{{D) z3FWrSE(G-lOIo^%@l%Jh z7d(bKZD(=f(+rDu3Ibk!p~fAzvAEw(G5oFaf>VGX;I$X`=G^#MhKzfwaaX++^R_aG z{23g@-KfbsV!rlS8y~jNpC$Q98001E5Sq?VpxukSs-?)+t$^aeGL+RsprA4wk<#_> z_+u(WTiH*vZ^%;_bKH_BUEq}jd#?-l#9J=q_v{?VaI4}Q9JQ|FoaxO4M#r~&(9l-@ga5_qddH0^Pp)3D%%13U()$KPZ9mfuKHeN zh|FV%AcKd9$Xte~JpHr7ISdEsS=dbylRpV}b%EG*(H$Zm7BrYE89CoGzCv2mgg*7U?}T-eQj1 z{ctQepE}YD%L6}S{pbtWcxoXGX*qRvIs~7NleuJ1rI2sTU9}wgEO}#naM{Mwl$wh8u zCnBy@;!tLlFk2ANZy*yRh*u1IjEu7sgd^u~j?-pP>cuPS5S69F5h60HO2`84nde#W zyHhV;*NN!tY8*EZeXCkX$LB3TNWEh+HIj>(aWc0CrwBgBQY@5_fO(q}ggUVHfHYwh z33cBmty=DP=A9=gECUXndMm_NP*e}Z-LCDyS)!}~n7b*S3$R(WUj{y!TOYyebROJ- zFF$p&>5+Q7DfKeS+K_mst_KprM8H(Hy%~al3{|Z#A5JZTYe-7!V4F>!)KgSFrn=vN z3kCIkDE(K!G*^RwzlwE`Dy#7rUC1u2#kz=0tng2mHP|cZPQ4qN4qPSy<|wW2D5KC{MlR z;>)=A5V)!Cz;#udS#FfKqnHHD=_R1Ptpjzf{nOgnDXf!ZtKqUo3Ky}YM}T&xUI8_p z+{#DD)pj7q40-gM-UA6eVt~J6Ck37*j6+@9f{jOSVfEgWC}V(*7I~i&Dx8uU4S&P_ z;3=qW>uyHVjD_fF#+pz$KVvi(@T$|6gmQ;1NkX6Jb)Y}^@BGWy!Sjrgf9<#ho=bDz zCj-9w{fH|?2J}+O500000NkvXXu0mjf*RRDb literal 0 HcmV?d00001 diff --git a/docs/api/images/iviewer/iviewer.rotate_right.png b/docs/api/images/iviewer/iviewer.rotate_right.png new file mode 100644 index 0000000000000000000000000000000000000000..7a6c829871d058af5a5ca0ac67256c259f84c5ac GIT binary patch literal 1482 zcmV;*1vUDKP)W@q8L24va@vW@P7*!=(&yIADtgIo?`IW@@z#oP%kAO5~e4>aSvdau$keO zelrxe7h>a?XEPGL-8ZvUK7S5JxO_SC%cam%h9U3nOynA8Bd1~p@(p_slER=;FN42~ zVT$Cx%L&IBfqkNPU0Q$wV<=M8^Ktsd`-seX8%Hx4(Mza9RSeQph6Cvg*A2_?bs*!Z zJm9Mh7vz)H#bKgf96Z*=%6lrx!F>}8+<9p(ZW^~EGIuaSuQBYWfCsY}!gCl75#iYs zn4X1YwmkE{L{qbYU7_|vC;44Kc>_>d53G;Ofy>H6K72&Q1n)K0hYkOJ{}5yueQ_kO zAEZ3kO@`uTdQ^;QD4P$Xc-46$f7y>g|g44T+ z#BgE=@!>Z#-vKoL6Wa&pE*R*#HWno`0s)tGNL1G$LZyexFDGPt0qahH^Hl0tOywX% zlsY1!jA8k4hEa>zCTrlAtqC<~Car`X>shKzaMo5pP+|!}lx4#8x7#jb=yVHAyecTTC4J8t z#9H#H1x)ab5$qRn{T_~Gwh%$&8k>4#Nl^>etY1&RH*c@JV*VdWES08K(6hJ3ua1F+ z1Vqx@pUkersqB_Ip|BQ5mEtYg?Pld26u-(ZDhIXZN8p8vpywZqUmXq21K`XJ6VBRD zSPjgUh_hsuo0Zq@#H52sg=lQDK%p}U0=)MqxPXrz3y|HcLaYr6jTyT{TGH;2&kl$g zvn(tPnZ+iUo13A~-oxKTMnQmAi+~YaKrsopkYC+_iv!`6FZ*8at59p?CSTLD^ce@1hVDp0-#Q zq7i&EGSIAWEAo-(3@vR~e8qHMf3DS%&Pzg-H4FABh2HRK5QhTu_A0SBL~)<~@sXQv zl!l*(`MH4my)*cGH#$9gu8S$5cCU4I9uNy}^C|I;^|0b5vYngtVB^%;d7vlsx1OAw ktS2S0?VM~5JkO{63m1~IVD>3BuK)l507*qoM6N<$f)6RO6951J literal 0 HcmV?d00001 diff --git a/docs/api/images/iviewer/iviewer.zoom_fit.png b/docs/api/images/iviewer/iviewer.zoom_fit.png new file mode 100644 index 0000000000000000000000000000000000000000..364e01d90eae19584713851314629f2468198e9e GIT binary patch literal 1252 zcmbVMZD`zN9M7nO!F>6)Qu>rmKTa$KDx31r-tX6e<%`(6Krd_QfiW`8t`1_@uq7ekl83AbH+? zzu*7$$#_?1`>KY!8VG_|C2bSZcx-mx`sMihAai~i59?7fi@MD`D#97s?^ptvbD@%0UaWlEb9Y?!POak>Kac>j_lvC`$F%JGkXW>_ZEGx zJom!Kv#(7V2O9tK-kSZb!BUHpo@HF_;W5I?7`%^kn|fV6LyKC%38?;(5V z!PU9ZKS#&d``e%Ktf-E%=q0M&EZqyy}ir$_H&JcW2fJ{{PgiX z8-IUUIXQgrq&9v}_F_|k9sDEvsw97zUz^v|wk^6~k{(E}g+-F{ISKr))j<47BXr41~e0blAr&bSs z|IrSo^7@evzS-=(@x`Huqc2r99GkQ^PabxeDqKE2-7pQ1>fi>@%sWi*eedZ1Y?O%#JYqAlBWQjldYscGecHU zg%s&*JUU5b0YHS4wwDMcmP5FMWn^R!Bl9wx$OeF{94|*YYA6A$q^fAQR`OuPF9B(% zR#J>vU<+rUs_EP&o+@f8usfP+92%!2CmYQ2;-UaU2_)!cYTX{(tCg(l;^I8G4N1Ut zh)|=IY&cb7v4IAbr$9^wOC7Kh29<~mkt? zy6{56v}sFRX(iQyz~K<#uSL*ic3n>tk$HCK_ zK(lTzs7O|_b%ItRdb$w;!&xj_!fwxIqQsOzUXp_l84NK@Fs^m9M<}HJbz@t!$KJ?M zP$A`E>v)G)57)jBS6;_)Fv2Ks*P|kY8`^A2u!ZfsWAnHVJ68Y*ZF@k28qrD)nZSf_1^Pw#| z@pZkmCA&G}0&7bcjq%x*?aRCV3irha4o~M_DArx{=C$ijm7gxpJfn{2L~$I+EADGSEAJts^bthXdZ0!+7(pGrKca+s`)MokNH6@80m`S3a@u zY1ygFvFkgB8j(NleZ>ubn=|IJ|7-yHzY}KZV-M0lk+#l6X1~TQy|p*uWp8(@{ii>t zUM%g1pU@A_-dyI62l_3g1J;f*Z-2$!(N|(ifOlu2-fw+1YIXRfE0Nik_Z$MUT=6@` he~f-qo>Y;tmQ(?tGoVp(DLyXve>58lh(UdY?;kaa{Rsd7 literal 0 HcmV?d00001 diff --git a/docs/api/images/iviewer/iviewer.zoom_out.png b/docs/api/images/iviewer/iviewer.zoom_out.png new file mode 100644 index 0000000000000000000000000000000000000000..893f3502baaab667354fe8b560c1712b72630836 GIT binary patch literal 1416 zcmeAS@N?(olHy`uVBq!ia0vp^av;pX1|+Qw)-3{3k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m{T%CB1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxKsVXI%s|1+P|wiV z#N6CmN5ROz&_Lh7NZ-&%*U;R`*vQJjKmiJrfVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8EkR}&8R-I5=oVMzl_XZ^<`pZ$OmImpPA){ffi_eM3D1{oGuTzrd=COM+4n&cLd=IHa;5RX-@T zIKQ+g85kdF$}r8qu)}W=NFmTQR{lkqz(`5Vami0E%}vcK@pQ3O0?O#6WTsddyBS)T zxj4DGI+;7U8WbpSnwXlJx+y{RrjQe2`as9%gOUbPQh^Bp(;tWlPxwF%JnN+90rN`{ zFk|eTuyZ2=1LH1F7srr_TYIJ*^g8Sy&?e2@*d%abWdMgri<5t&U4#0<`~ZPHTulKV zL_SFVVrt@l5PQLUVYuUp3a@^r?zky&+EXok@RH&z@w!8r$f$z%0{cUx;M)M%(BO@dlSAr|x|-`3zUuyW0)IVtW>|s>WVT?mGEc zSkr=`cGH2CXUi731dH|WWa9iWg>~1jL@c(3*I%OB-d`n9ld0_~{X@e~VM+?bvqa&xDF~4=3Dc{-t;PQB<+c zv78v6y_Q$sW5p8UNtB8n}}+(%+^^VTDLxDs_M@V zktZgdHP!#l9(Qv}igT01!>vuNAL^777;7#{%n;%?`4GGKfzXddg$9fTvuy=L>XpA# emo~({32$Iz;FK4L4Ckl>myMpTelF{r5}E)fw)-Uj literal 0 HcmV?d00001 diff --git a/docs/api/images/iviewer/iviewer.zoom_zero.png b/docs/api/images/iviewer/iviewer.zoom_zero.png new file mode 100644 index 0000000000000000000000000000000000000000..c981db6d690774d0c2e67e21c9081d5c89b5fd2d GIT binary patch literal 1091 zcmbVL&ui0A9M4pSOy+USDH`Jt54L&lWo?(ZTAQSq1v^VuUA;)t~d_LcwSBo>#!$V_3 z9LEjkPwFMM?vLJ`gY0_@JiN=6Jv3LLv!qU2W`Mb@O=_6WyXHJDVbfl^_!>`f+;-b3 zS7@bhO0kF=HzS*P+w~cm6!@+QT}TTXPE`s;ULyhK6LAo; zKoamt7>CkCDwR6QBLIO2kO)x>rW6S&0Pwv>U}}L~S4z6k(_*Kz(4f>;M6uOs#amLG z1oI-4WjW$ND8?*e;gUzqcFYS8^%-;=T7lzJhj@I%Xx2!RrUmBdMhLE7C~OjYVJ}fE zWn$a(MHmO7>qc>PtwUPEf85y8IxH{wSS;a?Gy{v(qkgClX1V*fP-MuwQBDUAD~h?O z6RYWBkLLBX!2ZN-$5tc*P9}BL$f+qc2OyjTdQR0O9U94`B&o2^u@x4nAeBw1(2x=n z5avKO31v+Nl7u7!=(60$=Dm=bo`w6m4%6*n!9THz7GRT-piIbOzXOU5LP^*lKCjIt z_&LY3Nh^$svk|L~1LqR9jexj(H@k|ng?bW90+iEJ2GWfvYE_c6auz`iY1tG)S)qru z|0iQ2b4H9>hb!)51ol8qidf>uee2NcllE7&Y7PpcX!=7_qbu-x&Nqo^YXsS zx4psB1L(rH2OVv_Ry|wWf8@aTYwPTIZ{+>+x4F)XFBxU;`RVIF##f(pxPd|L)0^?x S8}D{hqp{BCX7q<@b>%mi3|d_P literal 0 HcmV?d00001 diff --git a/docs/api/index.html b/docs/api/index.html new file mode 100644 index 0000000..4a9bb30 --- /dev/null +++ b/docs/api/index.html @@ -0,0 +1,454 @@ + + + + + + CImage API Documentaion + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ +

\

+ + + + +

Classes

+ + + + + + + + + + + + + + + + + +
CHttpGetGet a image from a remote server using HTTP GET and If-Modified-Since.
CImageResize and crop images on the fly, store generated images in a cache.
CRemoteImageGet a image from a remote server using HTTP GET and If-Modified-Since.
CWhitelistAct as whitelist (or blacklist).
+
+ + +
+ + +
+
+

Functions

+
+ +
+ +
+
+ +
+

errorPage()

+ +
errorPage(string  $msg) : void
+

Default configuration options, can be overridden in own config-file.

+ + +

Parameters

+ + + + + + +
string$msg

to display.

+ + + +
+
+ +
+ +
+
+ +
+

get()

+ +
get(mixed  $key, mixed  $default = null) : mixed
+

Get input from query string or return default value if not set.

+ + +

Parameters

+ + + + + + + + + + + +
mixed$key

as string or array of string values to look for in $_GET.

mixed$default

value to return when $key is not set in $_GET.

+ + +

Returns

+ mixed + —

value from $_GET or default value.

+ +
+
+ +
+ +
+
+ +
+

getConfig()

+ +
getConfig(string  $key, mixed  $default) : mixed
+

Get value from config array or default if key is not set in config array.

+ + +

Parameters

+ + + + + + + + + + + +
string$key

the key in the config array.

mixed$default

value to be default if $key is not set in config.

+ + +

Returns

+ mixed + —

value as $config[$key] or $default.

+ +
+
+ +
+ +
+
+ +
+

getDefined()

+ +
getDefined(mixed  $key, mixed  $defined, mixed  $undefined) : mixed
+

Get input from query string and set to $defined if defined or else $undefined.

+ + +

Parameters

+ + + + + + + + + + + + + + + + +
mixed$key

as string or array of string values to look for in $_GET.

mixed$defined

value to return when $key is set in $_GET.

mixed$undefined

value to return when $key is not set in $_GET.

+ + +

Returns

+ mixed + —

value as $defined or $undefined.

+ +
+
+ +
+ +
+
+ +
+

verbose()

+ +
verbose(string  $msg = null) : void
+

Log when verbose mode, when used without argument it returns the result.

+ + +

Parameters

+ + + + + + +
string$msg

to log.

+ + + +
+
+ +
+ + +
+
+ + +
+ + + diff --git a/docs/api/js/bootstrap.min.js b/docs/api/js/bootstrap.min.js new file mode 100644 index 0000000..319a85d --- /dev/null +++ b/docs/api/js/bootstrap.min.js @@ -0,0 +1,7 @@ +/** +* Bootstrap.js by @fat & @mdo +* plugins: bootstrap-transition.js, bootstrap-modal.js, bootstrap-dropdown.js, bootstrap-scrollspy.js, bootstrap-tab.js, bootstrap-tooltip.js, bootstrap-popover.js, bootstrap-affix.js, bootstrap-alert.js, bootstrap-button.js, bootstrap-collapse.js, bootstrap-carousel.js, bootstrap-typeahead.js +* Copyright 2012 Twitter, Inc. +* http://www.apache.org/licenses/LICENSE-2.0.txt +*/ +!function(a){a(function(){a.support.transition=function(){var a=function(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},c;for(c in b)if(a.style[c]!==undefined)return b[c]}();return a&&{end:a}}()})}(window.jQuery),!function(a){var b=function(b,c){this.options=c,this.$element=a(b).delegate('[data-dismiss="modal"]',"click.dismiss.modal",a.proxy(this.hide,this)),this.options.remote&&this.$element.find(".modal-body").load(this.options.remote)};b.prototype={constructor:b,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var b=this,c=a.Event("show");this.$element.trigger(c);if(this.isShown||c.isDefaultPrevented())return;this.isShown=!0,this.escape(),this.backdrop(function(){var c=a.support.transition&&b.$element.hasClass("fade");b.$element.parent().length||b.$element.appendTo(document.body),b.$element.show(),c&&b.$element[0].offsetWidth,b.$element.addClass("in").attr("aria-hidden",!1),b.enforceFocus(),c?b.$element.one(a.support.transition.end,function(){b.$element.focus().trigger("shown")}):b.$element.focus().trigger("shown")})},hide:function(b){b&&b.preventDefault();var c=this;b=a.Event("hide"),this.$element.trigger(b);if(!this.isShown||b.isDefaultPrevented())return;this.isShown=!1,this.escape(),a(document).off("focusin.modal"),this.$element.removeClass("in").attr("aria-hidden",!0),a.support.transition&&this.$element.hasClass("fade")?this.hideWithTransition():this.hideModal()},enforceFocus:function(){var b=this;a(document).on("focusin.modal",function(a){b.$element[0]!==a.target&&!b.$element.has(a.target).length&&b.$element.focus()})},escape:function(){var a=this;this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.modal",function(b){b.which==27&&a.hide()}):this.isShown||this.$element.off("keyup.dismiss.modal")},hideWithTransition:function(){var b=this,c=setTimeout(function(){b.$element.off(a.support.transition.end),b.hideModal()},500);this.$element.one(a.support.transition.end,function(){clearTimeout(c),b.hideModal()})},hideModal:function(){var a=this;this.$element.hide(),this.backdrop(function(){a.removeBackdrop(),a.$element.trigger("hidden")})},removeBackdrop:function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},backdrop:function(b){var c=this,d=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var e=a.support.transition&&d;this.$backdrop=a('