├── .gitignore
├── .htaccess
├── .travis.yml
├── README.md
├── Tests
└── PlaceholderTest.php
├── composer.json
├── i
├── OFL.txt
├── Oswald-Regular.ttf
├── index.php
└── placeholder.class.php
├── nginx.conf
└── phpunit.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor/
2 | composer.lock
--------------------------------------------------------------------------------
/.htaccess:
--------------------------------------------------------------------------------
1 | # img-src.co placeholder generator rewrites
2 | RewriteEngine On
3 | RewriteBase /
4 | RewriteRule ^(\d+)/?$ /i/index.php?w=$1&h=$1
5 | RewriteRule ^(\d+)x(\d+)/?$ /i/index.php?w=$1&h=$2
6 | RewriteRule ^(\d+)/([a-fA-F0-9]{3}|[a-fA-F0-9]{6})/?$ /i/index.php?w=$1&h=$1&bgColor=$2
7 | RewriteRule ^(\d+)x(\d+)/([a-fA-F0-9]{3}|[a-fA-F0-9]{6})/?$ /i/index.php?w=$1&h=$2&bgColor=$3
8 | RewriteRule ^(\d+)/([a-fA-F0-9]{3}|[a-fA-F0-9]{6})/([a-fA-F0-9]{3}|[a-fA-F0-9]{6})/?$ /i/index.php?w=$1&h=$1&bgColor=$2&textColor=$3
9 | RewriteRule ^(\d+)x(\d+)/([a-fA-F0-9]{3}|[a-fA-F0-9]{6})/([a-fA-F0-9]{3}|[a-fA-F0-9]{6})/?$ /i/index.php?w=$1&h=$2&bgColor=$3&textColor=$4
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 | php:
3 | - 5.3
4 | - 5.4
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # img-src.co Placeholder Image Generator #
2 |
3 | [](https://travis-ci.org/img-src/placeholder)
4 |
5 | ## About ##
6 |
7 | PHP placeholder image generator used on [http://img-src.co](http://img-src.co "img-src.co").
8 |
9 | ## Requirements ##
10 |
11 | * PHP 5.1.0 or higher
12 |
13 | ## How to use ##
14 |
15 | ### Quick start ###
16 |
17 | 1. Copy the `/i/` folder and all of its contents to your web root directory.
18 | 2. You're now ready to make placeholder images using the following format:
19 | `
`
20 |
21 | Examples:
22 |
23 |
24 |
25 |
26 |
27 |
28 | ### Advanced setup ("pretty URLs") ###
29 |
30 | 1. Copy the `/i/` folder and all of its contents to your web root directory.
31 | 2. Set up the rewrite rules for your web server (see below).
32 | 3. You're now ready to make placeholder images using the following format (background & text colors are optional):
33 | `
`
34 |
35 | Examples:
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | **Apache rewrites**: This can either go in an .htaccess file or your site config file. A sample .htaccess file is included in the repository.
44 |
45 | RewriteEngine On
46 | RewriteBase /
47 | RewriteRule ^(\d+)/?$ /i/index.php?w=$1&h=$1
48 | RewriteRule ^(\d+)x(\d+)/?$ /i/index.php?w=$1&h=$2
49 | RewriteRule ^(\d+)/([a-fA-F0-9]{3}|[a-fA-F0-9]{6})/?$ /i/index.php?w=$1&h=$1&bgColor=$2
50 | RewriteRule ^(\d+)x(\d+)/([a-fA-F0-9]{3}|[a-fA-F0-9]{6})/?$ /i/index.php?w=$1&h=$2&bgColor=$3
51 | RewriteRule ^(\d+)/([a-fA-F0-9]{3}|[a-fA-F0-9]{6})/([a-fA-F0-9]{3}|[a-fA-F0-9]{6})/?$ /i/index.php?w=$1&h=$1&bgColor=$2&textColor=$3
52 | RewriteRule ^(\d+)x(\d+)/([a-fA-F0-9]{3}|[a-fA-F0-9]{6})/([a-fA-F0-9]{3}|[a-fA-F0-9]{6})/?$ /i/index.php?w=$1&h=$2&bgColor=$3&textColor=$4
53 |
54 | **Nginx rewrites**: This needs to go in your site config file. A sample config file is included in the repository that can be included by your site config.
55 |
56 | location ~* "^/(\d+)x(\d+)/([a-f0-9]{3}|[a-f0-9]{6})/([a-f0-9]{3}|[a-f0-9]{6})/?$" {
57 | try_files $uri $uri/ /i/index.php?w=$1&h=$2&bgColor=$3&textColor=$4;
58 | }
59 | location ~* "^/(\d+)/([a-f0-9]{3}|[a-f0-9]{6})/([a-f0-9]{3}|[a-f0-9]{6})/?$" {
60 | try_files $uri $uri/ /i/index.php?w=$1&h=$1&bgColor=$2&textColor=$3;
61 | }
62 | location ~* "^/(\d+)x(\d+)/([a-f0-9]{3}|[a-f0-9]{6})/?$" {
63 | try_files $uri $uri/ /i/index.php?w=$1&h=$2&bgColor=$3;
64 | }
65 | location ~* "^/(\d+)/([a-f0-9]{3}|[a-f0-9]{6})/?$" {
66 | try_files $uri $uri/ /i/index.php?w=$1&h=$1&bgColor=$2;
67 | }
68 | location ~ "^/(\d+)x(\d+)/?$" {
69 | try_files $uri $uri/ /i/index.php?w=$1&h=$2;
70 | }
71 | location ~ "^/(\d+)/?$" {
72 | try_files $uri $uri/ /i/index.php?w=$1&h=$1;
73 | }
74 |
75 | ## Support ##
76 |
77 | To report a bug, please use our [GitHub issue tracker](https://github.com/img-src/placeholder/issues "GitHub issue tracker"). Better yet, if you find a bug, feel free to fork our code and submit a pull request with your fix.
78 |
79 | ## License ##
80 |
81 | The img-src.co placeholder image generator is distributed under the terms of the [MIT license](http://www.opensource.org/licenses/mit-license.php). copyright © 2012-2013
--------------------------------------------------------------------------------
/Tests/PlaceholderTest.php:
--------------------------------------------------------------------------------
1 | placeholder = new Placeholder();
11 | }
12 |
13 | public function tearDown() {
14 | unset($this->placeholder);
15 | }
16 |
17 | // Test if valid background color (full) is assigned
18 | public function testBackgroundColorValidSixHex()
19 | {
20 | $backgroundColor = '123456';
21 | $this->placeholder->setBackgroundColor($backgroundColor);
22 | $this->assertEquals($backgroundColor, $this->placeholder->getBackgroundColor());
23 | }
24 |
25 | // Test if valid background color (abbrev) is assigned
26 | public function testBackgroundColorValidThreeHex()
27 | {
28 | $backgroundColor = '325';
29 | $this->placeholder->setBackgroundColor($backgroundColor);
30 | $this->assertEquals($backgroundColor, $this->placeholder->getBackgroundColor());
31 | }
32 |
33 | // Test if invalid background hex color throws correct exception
34 | public function testBackgroundColorInvalidHex()
35 | {
36 | $this->setExpectedException('InvalidArgumentException', 'Background color must be a valid RGB hex code.');
37 | $backgroundColor = 'xxx343';
38 | $this->placeholder->setBackgroundColor($backgroundColor);
39 | }
40 |
41 | // Test if invalid background hex color throws correct exception
42 | public function testBackgroundColorInvalidFormat()
43 | {
44 | $this->setExpectedException('InvalidArgumentException', 'Background color must be 3 or 6 character hex code.');
45 | $backgroundColor = 'xxx34';
46 | $this->placeholder->setBackgroundColor($backgroundColor);
47 | }
48 |
49 | // Test if valid text color (full) is assigned
50 | public function testTextColorValidSixHex()
51 | {
52 | $textColor = '123456';
53 | $this->placeholder->setTextColor($textColor);
54 | $this->assertEquals($textColor, $this->placeholder->getTextColor());
55 | }
56 |
57 | // Test if valid text color (abbrev) is assigned
58 | public function testTextColorValidThreeHex()
59 | {
60 | $textColor = '325';
61 | $this->placeholder->setTextColor($textColor);
62 | $this->assertEquals($textColor, $this->placeholder->getTextColor());
63 | }
64 |
65 | // Test if invalid text hex color throws correct exception
66 | public function testTextColorInvalidHex()
67 | {
68 | $this->setExpectedException('InvalidArgumentException', 'Text color must be a valid RGB hex code.');
69 | $textColor = 'xxx343';
70 | $this->placeholder->setTextColor($textColor);
71 | }
72 |
73 | // Test if invalid text hex color throws correct exception
74 | public function testTextColorInvalidFormat()
75 | {
76 | $this->setExpectedException('InvalidArgumentException', 'Text color must be 3 or 6 character hex code.');
77 | $textColor = 'xxx34';
78 | $this->placeholder->setTextColor($textColor);
79 | }
80 |
81 | // Test if setting font with valid path will assign
82 | public function testValidFont()
83 | {
84 | $fontPath = dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'i' . DIRECTORY_SEPARATOR . 'Oswald-Regular.ttf';
85 | $this->placeholder->setFont($fontPath);
86 | $this->assertEquals($fontPath, $this->placeholder->getFont());
87 | }
88 |
89 | // Test if setting font with valid path will assign
90 | public function testInvalidFont()
91 | {
92 | $this->setExpectedException('InvalidArgumentException', 'Font file must exist and be readable by web server.');
93 | $fontPath = './i-dont-exist.ttf';
94 | $this->placeholder->setFont($fontPath);
95 | }
96 |
97 | // Test if valid expires header is assigned
98 | public function testValidExpires()
99 | {
100 | $expires = 50000;
101 | $this->placeholder->setExpires($expires);
102 | $this->assertEquals($expires, $this->placeholder->getExpires());
103 | }
104 |
105 | // Test if invalid expires header throws correct exception
106 | public function testInvalidExpires()
107 | {
108 | $this->setExpectedException('InvalidArgumentException', 'Expires must be an integer.');
109 | $expires = '10 days';
110 | $this->placeholder->setExpires($expires);
111 | }
112 |
113 | // Test if valid max width is assigned
114 | public function testValidMaxWidth()
115 | {
116 | $maxWidth = 50000;
117 | $this->placeholder->setMaxWidth($maxWidth);
118 | $this->assertEquals($maxWidth, $this->placeholder->getMaxWidth());
119 | }
120 |
121 | // Test if invalid max width throws correct exception
122 | public function testInvalidMaxWidth()
123 | {
124 | $this->setExpectedException('InvalidArgumentException', 'Maximum width must be an integer.');
125 | $maxWidth = 'One million';
126 | $this->placeholder->setMaxWidth($maxWidth);
127 | }
128 |
129 | // Test if valid max height is assigned
130 | public function testValidMaxHeight()
131 | {
132 | $maxHeight = 50000;
133 | $this->placeholder->setMaxHeight($maxHeight);
134 | $this->assertEquals($maxHeight, $this->placeholder->getMaxHeight());
135 | }
136 |
137 | // Test if invalid max height throws correct exception
138 | public function testInvalidMaxHeight()
139 | {
140 | $this->setExpectedException('InvalidArgumentException', 'Maximum height must be an integer.');
141 | $maxHeight = 'One million';
142 | $this->placeholder->setMaxHeight($maxHeight);
143 | }
144 |
145 | // Test if valid cache bool is assigned
146 | public function testValidCache()
147 | {
148 | $cache = true;
149 | $this->placeholder->setCache($cache);
150 | $this->assertEquals($cache, $this->placeholder->getCache());
151 | }
152 |
153 | // Test if invalid cache bool throws correct exception
154 | public function testInvalidCache()
155 | {
156 | $this->setExpectedException('InvalidArgumentException', 'setCache expects a boolean value.');
157 | $cache = 1;
158 | $this->placeholder->setCache($cache);
159 | }
160 |
161 | // Test if valid cache directory is assigned
162 | public function testValidCacheDir()
163 | {
164 | $cacheDir = dirname(__FILE__);
165 | $this->placeholder->setCacheDir($cacheDir);
166 | $this->assertEquals($cacheDir, $this->placeholder->getCacheDir());
167 | }
168 |
169 | // Test if invalid cache directory throws correct exception
170 | public function testInvalidCacheDir()
171 | {
172 | $this->setExpectedException('InvalidArgumentException', 'setCacheDir expects a directory.');
173 | $cacheDir = __FILE__;
174 | $this->placeholder->setCacheDir($cacheDir);
175 | }
176 |
177 | // Test if valid width is assigned
178 | public function testValidWidth()
179 | {
180 | $width = 50000;
181 | $this->placeholder->setWidth($width);
182 | $this->assertEquals($width, $this->placeholder->getWidth());
183 | }
184 |
185 | // Test if invalid width throws correct exception
186 | public function testInvalidWidthFormat()
187 | {
188 | $this->setExpectedException('InvalidArgumentException', 'Width must be an integer.');
189 | $width = 'One million';
190 | $this->placeholder->setWidth($width);
191 | }
192 |
193 | // Test if invalid width throws correct exception
194 | public function testInvalidWidthZero()
195 | {
196 | $this->setExpectedException('InvalidArgumentException', 'Width must be greater than zero.');
197 | $width = 0;
198 | $this->placeholder->setWidth($width);
199 | }
200 |
201 | // Test if valid height is assigned
202 | public function testValidHeight()
203 | {
204 | $height = 50000;
205 | $this->placeholder->setHeight($height);
206 | $this->assertEquals($height, $this->placeholder->getHeight());
207 | }
208 |
209 | // Test if invalid height throws correct exception
210 | public function testInvalidHeightFormat()
211 | {
212 | $this->setExpectedException('InvalidArgumentException', 'Height must be an integer.');
213 | $height = 'One million';
214 | $this->placeholder->setHeight($height);
215 | }
216 |
217 | // Test if invalid height throws correct exception
218 | public function testInvalidHeightZero()
219 | {
220 | $this->setExpectedException('InvalidArgumentException', 'Height must be greater than zero.');
221 | $height = 0;
222 | $this->placeholder->setHeight($height);
223 | }
224 |
225 | // Test if image requested larger than max size throws correct error
226 | public function testRenderTooLarge()
227 | {
228 | $maxWidth = 400;
229 | $maxHeight = 600;
230 | $width = 500;
231 | $height = 900;
232 | $this->setExpectedException('RuntimeException', 'Placeholder size may not exceed ' . $maxWidth . 'x' . $maxHeight . ' pixels.');
233 | $this->placeholder->setMaxWidth($maxWidth);
234 | $this->placeholder->setMaxHeight($maxHeight);
235 | $this->placeholder->setWidth($width);
236 | $this->placeholder->setHeight($height);
237 | $this->placeholder->render();
238 | }
239 |
240 | // Test if valid image size returns correctly
241 | /**
242 | * @runInSeparateProcess
243 | */
244 | public function testRenderValid()
245 | {
246 | $maxWidth = 1000;
247 | $maxHeight = 1000;
248 | $width = 500;
249 | $height = 900;
250 | $this->placeholder->setMaxWidth($maxWidth);
251 | $this->placeholder->setMaxHeight($maxHeight);
252 | $this->placeholder->setWidth($width);
253 | $this->placeholder->setHeight($height);
254 | ob_start();
255 | $this->placeholder->render();
256 | $output = ob_get_contents();
257 | ob_clean();
258 | $tempFilename = '/tmp/phpunit.testImage.testRenderValid';
259 | file_put_contents($tempFilename, $output);
260 | $type = exif_imagetype($tempFilename);
261 | $this->assertEquals(IMAGETYPE_PNG, $type);
262 | $size = getimagesize($tempFilename);
263 | $this->assertEquals($size[0], $width);
264 | $this->assertEquals($size[1], $height);
265 | unlink($tempFilename);
266 | }
267 | }
268 | ?>
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "require-dev": {
3 | "phpunit/phpunit": "*"
4 | }
5 | }
--------------------------------------------------------------------------------
/i/OFL.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012, Vernon Adams (vern@newtypography.co.uk),
2 | with Reserved Font Name Oswald
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 | This license is copied below, and is also available with a FAQ at:
5 | http://scripts.sil.org/OFL
6 |
7 |
8 | -----------------------------------------------------------
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | -----------------------------------------------------------
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide
14 | development of collaborative font projects, to support the font creation
15 | efforts of academic and linguistic communities, and to provide a free and
16 | open framework in which fonts may be shared and improved in partnership
17 | with others.
18 |
19 | The OFL allows the licensed fonts to be used, studied, modified and
20 | redistributed freely as long as they are not sold by themselves. The
21 | fonts, including any derivative works, can be bundled, embedded,
22 | redistributed and/or sold with any software provided that any reserved
23 | names are not used by derivative works. The fonts and derivatives,
24 | however, cannot be released under any other type of license. The
25 | requirement for fonts to remain under this license does not apply
26 | to any document created using the fonts or their derivatives.
27 |
28 | DEFINITIONS
29 | "Font Software" refers to the set of files released by the Copyright
30 | Holder(s) under this license and clearly marked as such. This may
31 | include source files, build scripts and documentation.
32 |
33 | "Reserved Font Name" refers to any names specified as such after the
34 | copyright statement(s).
35 |
36 | "Original Version" refers to the collection of Font Software components as
37 | distributed by the Copyright Holder(s).
38 |
39 | "Modified Version" refers to any derivative made by adding to, deleting,
40 | or substituting -- in part or in whole -- any of the components of the
41 | Original Version, by changing formats or by porting the Font Software to a
42 | new environment.
43 |
44 | "Author" refers to any designer, engineer, programmer, technical
45 | writer or other person who contributed to the Font Software.
46 |
47 | PERMISSION & CONDITIONS
48 | Permission is hereby granted, free of charge, to any person obtaining
49 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
50 | redistribute, and sell modified and unmodified copies of the Font
51 | Software, subject to the following conditions:
52 |
53 | 1) Neither the Font Software nor any of its individual components,
54 | in Original or Modified Versions, may be sold by itself.
55 |
56 | 2) Original or Modified Versions of the Font Software may be bundled,
57 | redistributed and/or sold with any software, provided that each copy
58 | contains the above copyright notice and this license. These can be
59 | included either as stand-alone text files, human-readable headers or
60 | in the appropriate machine-readable metadata fields within text or
61 | binary files as long as those fields can be easily viewed by the user.
62 |
63 | 3) No Modified Version of the Font Software may use the Reserved Font
64 | Name(s) unless explicit written permission is granted by the corresponding
65 | Copyright Holder. This restriction only applies to the primary font name as
66 | presented to the users.
67 |
68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69 | Software shall not be used to promote, endorse or advertise any
70 | Modified Version, except to acknowledge the contribution(s) of the
71 | Copyright Holder(s) and the Author(s) or with their explicit written
72 | permission.
73 |
74 | 5) The Font Software, modified or unmodified, in part or in whole,
75 | must be distributed entirely under this license, and must not be
76 | distributed under any other license. The requirement for fonts to
77 | remain under this license does not apply to any document created
78 | using the Font Software.
79 |
80 | TERMINATION
81 | This license becomes null and void if any of the above conditions are
82 | not met.
83 |
84 | DISCLAIMER
85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93 | OTHER DEALINGS IN THE FONT SOFTWARE.
94 |
--------------------------------------------------------------------------------
/i/Oswald-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/img-src/placeholder/3915741e868d1f6c375f58bc0262f98a125cc637/i/Oswald-Regular.ttf
--------------------------------------------------------------------------------
/i/index.php:
--------------------------------------------------------------------------------
1 | setWidth($width);
14 | $placeholder->setHeight($height);
15 | if ($backgroundColor) $placeholder->setBackgroundColor($backgroundColor);
16 | if ($textColor) $placeholder->setTextColor($textColor);
17 | $placeholder->render();
18 | } catch (Exception $e){
19 | die($e->getMessage());
20 | }
21 |
--------------------------------------------------------------------------------
/i/placeholder.class.php:
--------------------------------------------------------------------------------
1 | backgroundColor = 'dddddd';
16 | $this->cache = false;
17 | $this->cacheDir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'cache';
18 | $this->expires = 604800;
19 | $this->font = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'Oswald-Regular.ttf';
20 | $this->maxHeight = 2000;
21 | $this->maxWidth = 2000;
22 | $this->textColor = '000000';
23 | }
24 |
25 | /**
26 | * Sets background color
27 | *
28 | * @param string $hex Hex code value
29 | * @throws InvalidArgumentException
30 | */
31 | function setBackgroundColor($hex)
32 | {
33 | if (strlen($hex) === 3 || strlen($hex) === 6) {
34 | if (preg_match('/^[a-f0-9]{3}$|^[a-f0-9]{6}$/i', $hex)) {
35 | $this->backgroundColor = $hex;
36 | } else {
37 | throw new InvalidArgumentException('Background color must be a valid RGB hex code.');
38 | }
39 | } else {
40 | throw new InvalidArgumentException('Background color must be 3 or 6 character hex code.');
41 | }
42 | }
43 |
44 | /**
45 | * Gets background color
46 | */
47 | function getBackgroundColor()
48 | {
49 | return $this->backgroundColor;
50 | }
51 |
52 | /**
53 | * Sets text color
54 | *
55 | * @param string $hex Hex code value
56 | * @throws InvalidArgumentException
57 | */
58 | function setTextColor($hex)
59 | {
60 | if (strlen($hex) === 3 || strlen($hex) === 6) {
61 | if (preg_match('/^[a-f0-9]{3}$|^[a-f0-9]{6}$/i', $hex)) {
62 | $this->textColor = $hex;
63 | } else {
64 | throw new InvalidArgumentException('Text color must be a valid RGB hex code.');
65 | }
66 | } else {
67 | throw new InvalidArgumentException('Text color must be 3 or 6 character hex code.');
68 | }
69 | }
70 |
71 | /**
72 | * Gets text color
73 | */
74 | function getTextColor()
75 | {
76 | return $this->textColor;
77 | }
78 |
79 | /**
80 | * Sets location of TTF font
81 | *
82 | * @param string $fontPath Location of TTF font
83 | * @throws InvalidArgumentException
84 | */
85 | function setFont($fontPath)
86 | {
87 | if (is_readable($fontPath)) {
88 | $this->font = $fontPath;
89 | } else {
90 | throw new InvalidArgumentException('Font file must exist and be readable by web server.');
91 | }
92 | }
93 |
94 | /**
95 | * Gets location of font
96 | */
97 | function getFont()
98 | {
99 | return $this->font;
100 | }
101 |
102 | /**
103 | * Set expires header value
104 | *
105 | * @param int $expires Seconds used in expires HTTP header
106 | * @throws InvalidArgumentException
107 | */
108 | function setExpires($expires)
109 | {
110 | if (preg_match('/^\d+$/', $expires)) {
111 | $this->expires = $expires;
112 | } else {
113 | throw new InvalidArgumentException('Expires must be an integer.');
114 | }
115 | }
116 |
117 | /**
118 | * Get expires header value
119 | */
120 | function getExpires()
121 | {
122 | return $this->expires;
123 | }
124 |
125 | /**
126 | * Set maximum width allowed for placeholder image
127 | *
128 | * @param int $maxWidth Maximum width of generated image
129 | * @throws InvalidArgumentException
130 | */
131 | function setMaxWidth($maxWidth)
132 | {
133 | if (preg_match('/^\d+$/', $maxWidth)) {
134 | $this->maxWidth = $maxWidth;
135 | } else {
136 | throw new InvalidArgumentException('Maximum width must be an integer.');
137 | }
138 | }
139 |
140 | /**
141 | * Get max width value
142 | */
143 | function getMaxWidth()
144 | {
145 | return $this->maxWidth;
146 | }
147 |
148 | /**
149 | * Set maximum height allowed for placeholder image
150 | *
151 | * @param int $maxHeight Maximum height of generated image
152 | * @throws InvalidArgumentException
153 | */
154 | function setMaxHeight($maxHeight)
155 | {
156 | if (preg_match('/^\d+$/', $maxHeight)) {
157 | $this->maxHeight = $maxHeight;
158 | } else {
159 | throw new InvalidArgumentException('Maximum height must be an integer.');
160 | }
161 | }
162 |
163 | /**
164 | * Get max height value
165 | */
166 | function getMaxHeight()
167 | {
168 | return $this->maxHeight;
169 | }
170 |
171 | /**
172 | * Enable or disable cache
173 | *
174 | * @param bool $cache Whether or not to cache
175 | * @throws InvalidArgumentException
176 | */
177 | function setCache($cache)
178 | {
179 | if (is_bool($cache)) {
180 | $this->cache = $cache;
181 | } else {
182 | throw new InvalidArgumentException('setCache expects a boolean value.');
183 | }
184 | }
185 |
186 | /**
187 | * Get cache value
188 | */
189 | function getCache()
190 | {
191 | return $this->cache;
192 | }
193 |
194 | /**
195 | * Sets caching path
196 | *
197 | * @param string $cacheDir Path to cache folder, must be writable by web server
198 | * @throws InvalidArgumentException
199 | */
200 | function setCacheDir($cacheDir)
201 | {
202 | if (is_dir($cacheDir)) {
203 | $this->cacheDir = $cacheDir;
204 | } else {
205 | throw new InvalidArgumentException('setCacheDir expects a directory.');
206 | }
207 | }
208 |
209 | /**
210 | * Get cache directory value
211 | */
212 | function getCacheDir()
213 | {
214 | return $this->cacheDir;
215 | }
216 |
217 | /**
218 | * Set width of image to render
219 | *
220 | * @param int $width Width of generated image
221 | * @throws InvalidArgumentException
222 | */
223 | function setWidth($width)
224 | {
225 | if (preg_match('/^\d+$/', $width)) {
226 | if ($width > 0) {
227 | $this->width = $width;
228 | } else {
229 | throw new InvalidArgumentException('Width must be greater than zero.');
230 | }
231 | } else {
232 | throw new InvalidArgumentException('Width must be an integer.');
233 | }
234 | }
235 |
236 | /**
237 | * Get width value
238 | */
239 | function getWidth()
240 | {
241 | return $this->width;
242 | }
243 |
244 | /**
245 | * Set height of image to render
246 | *
247 | * @param int $height Height of generated image
248 | * @throws InvalidArgumentException
249 | */
250 | function setHeight($height)
251 | {
252 | if (preg_match('/^\d+$/', $height)) {
253 | if ($height > 0) {
254 | $this->height = $height;
255 | } else {
256 | throw new InvalidArgumentException('Height must be greater than zero.');
257 | }
258 | } else {
259 | throw new InvalidArgumentException('Height must be an integer.');
260 | }
261 | }
262 |
263 | /**
264 | * Get height value
265 | */
266 | function getHeight()
267 | {
268 | return $this->height;
269 | }
270 |
271 | /**
272 | * Display image and cache (if enabled)
273 | *
274 | * @throws RuntimeException
275 | */
276 | function render()
277 | {
278 | if ($this->width <= $this->maxWidth && $this->height <= $this->maxHeight) {
279 | $cachePath = $this->cacheDir . '/' . $this->width . '_' . $this->height . '_' . (strlen($this->backgroundColor) === 3 ? $this->backgroundColor[0] . $this->backgroundColor[0] . $this->backgroundColor[1] . $this->backgroundColor[1] . $this->backgroundColor[2] . $this->backgroundColor[2] : $this->backgroundColor) . '_' . (strlen($this->textColor) === 3 ? $this->textColor[0] . $this->textColor[0] . $this->textColor[1] . $this->textColor[1] . $this->textColor[2] . $this->textColor[2] : $this->textColor) . '.png';
280 | header('Content-type: image/png');
281 | header('Expires: ' . gmdate('D, d M Y H:i:s \G\M\T', time() + $this->expires));
282 | header('Cache-Control: public');
283 | if ($this->cache === true && is_readable($cachePath)) {
284 | // send header identifying cache hit & send cached image
285 | header('img-src-cache: hit');
286 | print file_get_contents($cachePath);
287 | } else {
288 | // cache disabled or no cached copy exists
289 | // send header identifying cache miss if cache enabled
290 | if ($this->cache === true) header('img-src-cache: miss');
291 |
292 | $image = $this->createImage();
293 |
294 | imagepng($image);
295 | // write cache
296 | if ($this->cache === true && is_writable($this->cacheDir)) {
297 | imagepng($image, $cachePath);
298 | }
299 | imagedestroy($image);
300 | }
301 | } else {
302 | throw new RuntimeException('Placeholder size may not exceed ' . $this->maxWidth . 'x' . $this->maxHeight . ' pixels.');
303 | }
304 | }
305 |
306 | private function createImage()
307 | {
308 | $image = imagecreate($this->width, $this->height);
309 | // convert backgroundColor hex to RGB values
310 | list($bgR, $bgG, $bgB) = $this->hexToDec($this->backgroundColor);
311 | $backgroundColor = imagecolorallocate($image, $bgR, $bgG, $bgB);
312 | // convert textColor hex to RGB values
313 | list($textR, $textG, $textB) = $this->hexToDec($this->textColor);
314 | $textColor = imagecolorallocate($image, $textR, $textG, $textB);
315 | $text = $this->width . 'x' . $this->height;
316 | imagefilledrectangle($image, 0, 0, $this->width, $this->height, $backgroundColor);
317 | $fontSize = 26;
318 | $textBoundingBox = imagettfbbox($fontSize, 0, $this->font, $text);
319 | // decrease the default font size until it fits nicely within the image
320 | while (((($this->width - ($textBoundingBox[2] - $textBoundingBox[0])) < 10) || (($this->height - ($textBoundingBox[1] - $textBoundingBox[7])) < 10)) && ($fontSize > 1)) {
321 | $fontSize--;
322 | $textBoundingBox = imagettfbbox($fontSize, 0, $this->font, $text);
323 | }
324 | imagettftext($image, $fontSize, 0, ($this->width / 2) - (($textBoundingBox[2] - $textBoundingBox[0]) / 2), ($this->height / 2) + (($textBoundingBox[1] - $textBoundingBox[7]) / 2), $textColor, $this->font, $text);
325 |
326 | return $image;
327 | }
328 |
329 | function renderToFile($file)
330 | {
331 | if (!file_exists($file)) {
332 | touch($file);
333 | }
334 | $image = $this->createImage();
335 | imagepng($image, $file);
336 | imagedestroy($image);
337 | }
338 |
339 | /**
340 | * Convert hex code to array of RGB decimal values
341 | *
342 | * @param string $hex Hex code to convert to dec
343 | * @return array
344 | * @throws InvalidArgumentException
345 | */
346 | private function hexToDec($hex)
347 | {
348 | if (strlen($hex) === 3) {
349 | $rgbArray = array(hexdec($hex[0] . $hex[0]), hexdec($hex[1] . $hex[1]), hexdec($hex[2] . $hex[2]));
350 | } else if (strlen($hex) === 6) {
351 | $rgbArray = array(hexdec($hex[0] . $hex[1]), hexdec($hex[2] . $hex[3]), hexdec($hex[4] . $hex[5]));
352 | } else {
353 | throw new InvalidArgumentException('Could not convert hex value to decimal.');
354 | }
355 | return $rgbArray;
356 | }
357 | }
358 |
--------------------------------------------------------------------------------
/nginx.conf:
--------------------------------------------------------------------------------
1 | # img-src.co placeholder generator rewrites
2 | location ~* "^/(\d+)x(\d+)/([a-f0-9]{3}|[a-f0-9]{6})/([a-f0-9]{3}|[a-f0-9]{6})/?$" {
3 | try_files $uri $uri/ /i/index.php?w=$1&h=$2&bgColor=$3&textColor=$4;
4 | }
5 | location ~* "^/(\d+)/([a-f0-9]{3}|[a-f0-9]{6})/([a-f0-9]{3}|[a-f0-9]{6})/?$" {
6 | try_files $uri $uri/ /i/index.php?w=$1&h=$1&bgColor=$2&textColor=$3;
7 | }
8 | location ~* "^/(\d+)x(\d+)/([a-f0-9]{3}|[a-f0-9]{6})/?$" {
9 | try_files $uri $uri/ /i/index.php?w=$1&h=$2&bgColor=$3;
10 | }
11 | location ~* "^/(\d+)/([a-f0-9]{3}|[a-f0-9]{6})/?$" {
12 | try_files $uri $uri/ /i/index.php?w=$1&h=$1&bgColor=$2;
13 | }
14 | location ~ "^/(\d+)x(\d+)/?$" {
15 | try_files $uri $uri/ /i/index.php?w=$1&h=$2;
16 | }
17 | location ~ "^/(\d+)/?$" {
18 | try_files $uri $uri/ /i/index.php?w=$1&h=$1;
19 | }
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ./Tests/
6 |
7 |
8 |
--------------------------------------------------------------------------------