├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── composer.json
├── phpunit.xml.dist
├── src
├── AbstractContext.php
├── Engine
│ ├── DelegatingEngine.php
│ ├── EngineInterface.php
│ └── PhpGd
│ │ ├── Extension
│ │ ├── Core
│ │ │ ├── CoreExtension.php
│ │ │ ├── EventListener
│ │ │ │ ├── BackgroundLayerListener.php
│ │ │ │ ├── ImageAwareLayerListener.php
│ │ │ │ ├── ImageFactoryListener.php
│ │ │ │ ├── ImageMetadataListener.php
│ │ │ │ ├── MemoryRequirementListener.php
│ │ │ │ ├── SystemRequirementListener.php
│ │ │ │ └── TextLayerListener.php
│ │ │ ├── ImageFactory.php
│ │ │ └── ImageInfo.php
│ │ ├── DelegatingExtension.php
│ │ ├── ExtensionInterface.php
│ │ ├── Gif
│ │ │ ├── EventListener
│ │ │ │ ├── GifExtractorListener.php
│ │ │ │ ├── ImageFactoryListener.php
│ │ │ │ └── MemoryRequirementListener.php
│ │ │ ├── GifBuilder.php
│ │ │ ├── GifBuilderPlus.php
│ │ │ ├── GifExtension.php
│ │ │ ├── GifExtracted.php
│ │ │ ├── GifExtractor.php
│ │ │ ├── GifOptimizer.php
│ │ │ └── ImageFactory.php
│ │ └── Save
│ │ │ ├── EventListener
│ │ │ └── ImageFactoryListener.php
│ │ │ ├── ImageFactory.php
│ │ │ └── SaveExtension.php
│ │ ├── Helper
│ │ └── ResourceHelper.php
│ │ ├── PhpGdContext.php
│ │ ├── PhpGdEngine.php
│ │ ├── PhpGdEvent.php
│ │ ├── PhpGdEvents.php
│ │ └── README.md
├── Exception
│ ├── BadMethodCallException.php
│ ├── FileParseException.php
│ ├── InvalidArgumentException.php
│ ├── InvalidFontException.php
│ ├── InvalidImageException.php
│ ├── RuntimeException.php
│ └── TranslatedException.php
├── Image.php
├── ImageBuilder.php
├── Layer
│ ├── AbstractLayer.php
│ ├── BackgroundLayer.php
│ ├── BackgroundLayerInterface.php
│ ├── ImageAwareLayerInterface.php
│ ├── ImageLayer.php
│ ├── ImageLayerInterface.php
│ ├── LayerInterface.php
│ ├── ParameterBag.php
│ ├── ParameterBagInterface.php
│ ├── RegularLayerInterface.php
│ ├── TextLayer.php
│ └── TextLayerInterface.php
├── LayerPass
│ ├── AbstractLayerPass.php
│ ├── BackgroundLayerPass.php
│ ├── DelegatingLayerPass.php
│ ├── ImageAwareLayerPass.php
│ ├── LayerPassInterface.php
│ ├── RegularLayerPass.php
│ └── TextLayerPass.php
├── OptionPass
│ ├── CacheDirOptionPass.php
│ ├── DebugOptionPass.php
│ ├── DelegatingOptionPass.php
│ ├── EngineOptionPass.php
│ ├── GifAnimationOptionPass.php
│ ├── JpegQualityOptionPass.php
│ ├── LocaleOptionPass.php
│ ├── MemoryLimitOptionPass.php
│ ├── OptionPassInterface.php
│ ├── OutputFormatOptionPass.php
│ └── PngCompressionOptionPass.php
└── Resources
│ └── translations
│ ├── imagecraft.en.xlf
│ ├── imagecraft.zh_CN.xlf
│ └── imagecraft.zh_TW.xlf
└── tests
├── AbstractContextTest.php
├── Engine
├── DelegatingEngineTest.php
├── Fixtures
│ └── FooEngine.php
└── PhpGd
│ ├── Extension
│ ├── Core
│ │ ├── CoreExtensionTest.php
│ │ ├── EventListener
│ │ │ ├── BackgroundLayerListenerTest.php
│ │ │ ├── ImageAwareLayerListenerTest.php
│ │ │ ├── ImageFactoryListenerTest.php
│ │ │ ├── ImageMetadataListenerTest.php
│ │ │ ├── MemoryRequirementListenerTest.php
│ │ │ ├── SystemRequirementListenerTest.php
│ │ │ └── TextLayerListenerTest.php
│ │ ├── ImageFactoryTest.php
│ │ ├── ImageInfoTest.php
│ │ └── TestOutput
│ │ │ └── .gitignore
│ ├── Gif
│ │ ├── EventListener
│ │ │ ├── GifExtractorListenerTest.php
│ │ │ ├── ImageFactoryListenerTest.php
│ │ │ └── MemoryRequirementListenerTest.php
│ │ ├── GifBuilderPlusTest.php
│ │ ├── GifBuilderTest.php
│ │ ├── GifExtensionTest.php
│ │ ├── GifExtractedTest.php
│ │ ├── GifExtractorTest.php
│ │ ├── GifOptimizerTest.php
│ │ ├── ImageFactoryTest.php
│ │ └── TestOutput
│ │ │ └── .gitignore
│ └── Save
│ │ ├── EventListener
│ │ └── ImageFactoryListenerTest.php
│ │ ├── ImageFactoryTest.php
│ │ ├── SaveExtensionTest.php
│ │ └── TestOutput
│ │ └── .gitignore
│ ├── Helper
│ ├── ResourceHelperTest.php
│ └── TestOutput
│ │ └── .gitignore
│ └── PhpGdContextTest.php
├── Fixtures
├── gif_87a_palette_250x297.gif
├── gif_89a_palette_alpha_206x205.gif
├── gif_89a_palette_alpha_animated_339x473.gif
├── gif_89a_palette_animated_339x473.txt
├── gif_89a_palette_animated_375x225.gif
├── jpeg_exif_truecolor_480x360.jpg
├── jpeg_jfjf_grayscale_480x361.jpg
├── jpeg_jfjf_sos_truecolor_1920x1200.jpg
├── jpeg_jfjf_truecolor_1920x758.jpg
├── pfa_truecolor_alpha.pfa
├── png_palette_alpha_3000x1174.png
├── png_truecolor_alpha_300x395.png
├── webp_vp8_lossy_truecolor_550x368.webp
├── webp_vp8l_lossless_truecolor_alpha_800x600.webp
├── webp_vp8x_lossy_truecolor_alpha_421x163.webp
├── zz_gif_89a_palette_animated_corrupted_data_153x120.gif
└── zz_gif_89a_palette_animated_no_graphic_control_550x296.gif
├── ImageBuilderTest.php
├── LayerPass
├── AbstractLayerPassTest.php
├── BackgroundLayerPassTest.php
├── ImageAwareLayerPassTest.php
├── RegularLayerPassTest.php
└── TextLayerPassTest.php
├── OptionPass
├── CacheDirOptionPassTest.php
├── DebugOptionPassTest.php
├── DelegatingOptionPassTest.php
├── EngineOptionPassTest.php
├── GifAnimationOptionPassTest.php
├── JpegQualityOptionPassTest.php
├── LocaleOptionPassTest.php
├── MemoryLimitOptionPassTest.php
├── OutputFormatOptionPassTest.php
└── PngCompressionOptionPassTest.php
└── bootstrap.php
/.gitignore:
--------------------------------------------------------------------------------
1 | phpunit.xml
2 | composer.lock
3 | composer.phar
4 | /vendor/
5 | /reports/
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.4
5 | - 5.5
6 | - 5.6
7 |
8 | before_script:
9 | - composer install
10 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | ## 1.0.3
4 | Fixed JFIF header parsing bug
5 |
6 | ## 1.0.0
7 |
8 | * Initial release
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Xianghan Wang
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "coldume/imagecraft",
3 | "type": "library",
4 | "keywords": ["gd", "image", "animation", "resize", "thumbnail", "watermark"],
5 | "description": "A reliable and extensible PHP image manipulation library",
6 | "license": "MIT",
7 | "homepage": "https://github.com/coldume/imagecraft",
8 | "authors": [
9 | {
10 | "name": "Xianghan Wang",
11 | "email": "coldume@gmail.com"
12 | }
13 | ],
14 | "require": {
15 | "php": ">=5.4.0",
16 | "coldume/imc-stream": "~1.0",
17 | "coldume/translated-exception": "~1.0",
18 | "symfony/event-dispatcher": "~2.4"
19 | },
20 | "require-dev": {
21 | "phpunit/phpunit": "~4.3"
22 | },
23 | "autoload": {
24 | "psr-4": {
25 | "Imagecraft\\": "src/"
26 | }
27 | },
28 | "extra": {
29 | "branch-alias": {
30 | "dev-develop": "1.1.x-dev"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | tests/
7 |
8 |
9 |
10 |
11 |
12 | src/
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/AbstractContext.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | abstract class AbstractContext
10 | {
11 | /**
12 | * @return bool
13 | * @api
14 | */
15 | abstract public function isEngineSupported();
16 |
17 | /**
18 | * @return string
19 | * @api
20 | */
21 | abstract public function getSupportedImageFormatsToString();
22 |
23 | /**
24 | * @return string
25 | * @api
26 | */
27 | abstract public function getSupportedFontFormatsToString();
28 |
29 | /**
30 | * @param int $modifier
31 | * @return int
32 | */
33 | public function getMemoryLimit($modifier = 0)
34 | {
35 | $str = trim(ini_get('memory_limit'));
36 | if(!preg_match('/(?i)\\A(?\d+).*(?g|m|k).*\\Z/', $str, $matches)) {
37 | return 1024 * 1024 * 1024;
38 | };
39 | $limit = $matches['limit'];
40 | $unit = strtolower($matches['unit']);
41 | switch($unit) {
42 | case 'g':
43 | $limit *= 1024;
44 | case 'm':
45 | $limit *= 1024;
46 | case 'k':
47 | $limit *= 1024;
48 | }
49 |
50 | $modifier *= 1024 * 1024;
51 | if (0 < $modifier) {
52 | $limit = ($limit < $modifier) ? $limit : $modifier;
53 | } else {
54 | $sum = $limit + $modifier;
55 | $limit = (0 >= $sum) ? $limit : $sum;
56 | }
57 |
58 | return $limit;
59 | }
60 |
61 | /**
62 | * @return bool
63 | */
64 | public function isFileinfoExtensionEnabled()
65 | {
66 | return extension_loaded('fileinfo');
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Engine/DelegatingEngine.php:
--------------------------------------------------------------------------------
1 |
9 | * @since 1.0.0
10 | */
11 | class DelegatingEngine implements EngineInterface
12 | {
13 | /**
14 | * @return string[]
15 | */
16 | protected function getRegisteredEngines()
17 | {
18 | return [
19 | 'php_gd' => new PhpGdEngine(),
20 | ];
21 | }
22 |
23 | /**
24 | * @inheritDoc
25 | */
26 | public function getImage(array $layers, array $options)
27 | {
28 | $engines = $this->getRegisteredEngines();
29 | if (!array_key_exists($options['engine'], $engines)) {
30 | $engine = $engines['php_gd'];
31 | } else {
32 | $engine = $engines[$options['engine']];
33 | }
34 |
35 | return $engine->getImage($layers, $options);
36 | }
37 |
38 | /**
39 | * @inheritDoc
40 | */
41 | public function getContext(array $options)
42 | {
43 | $engines = $this->getRegisteredEngines();
44 | if (!array_key_exists($options['engine'], $engines)) {
45 | $engine = $engines['php_gd'];
46 | } else {
47 | $engine = $engines[$options['engine']];
48 | }
49 |
50 | return $engine->getContext($options);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Engine/EngineInterface.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | interface EngineInterface
10 | {
11 | /**
12 | * @param \Imagecraft\Layer\LayerInterface[] $layers
13 | * @param mixed[] $options
14 | * @return \Imagecraft\Image
15 | */
16 | public function getImage(array $layers, array $options);
17 |
18 | /**
19 | * @param mixed[] $options
20 | * @return \Imagecraft\AbstractContext
21 | */
22 | public function getContext(array $options);
23 | }
24 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/Extension/Core/CoreExtension.php:
--------------------------------------------------------------------------------
1 |
19 | * @since 1.0.0
20 | */
21 | class CoreExtension implements ExtensionInterface
22 | {
23 | /**
24 | * @param EventDispatcherInterface $dispatcher
25 | */
26 | public function boot(EventDispatcherInterface $dispatcher)
27 | {
28 | $context = new PhpGdContext();
29 | $rh = new ResourceHelper();
30 | $info = new ImageInfo($context);
31 | $factory = new ImageFactory($rh);
32 |
33 | $dispatcher->addSubscriber(new SystemRequirementListener($context));
34 | $dispatcher->addSubscriber(new ImageAwareLayerListener($info, $rh));
35 | $dispatcher->addSubscriber(new BackgroundLayerListener());
36 | $dispatcher->addSubscriber(new TextLayerListener($context));
37 | $dispatcher->addSubscriber(new MemoryRequirementListener($context));
38 | $dispatcher->addSubscriber(new ImageFactoryListener($factory));
39 | $dispatcher->addSubscriber(new ImageMetadataListener($context));
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/Extension/Core/EventListener/BackgroundLayerListener.php:
--------------------------------------------------------------------------------
1 |
11 | * @since 1.0.0
12 | */
13 | class BackgroundLayerListener implements EventSubscriberInterface
14 | {
15 | /**
16 | * @inheritDoc
17 | */
18 | public static function getSubscribedEvents()
19 | {
20 | return [
21 | PhpGdEvents::PRE_IMAGE => ['initFinalFormat', 859],
22 | ];
23 | }
24 |
25 | /**
26 | * @param PhpGdEvent
27 | */
28 | public function initFinalFormat(PhpGdEvent $event)
29 | {
30 | $layer = $event->getLayers()[0];
31 | $options = $event->getOptions();
32 | if ('default' === $options['output_format']) {
33 | $format = $layer->get('image.format');
34 | } else {
35 | $format = $options['output_format'];
36 | }
37 |
38 | $layer->set('final.format', $format);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/Extension/Core/EventListener/ImageAwareLayerListener.php:
--------------------------------------------------------------------------------
1 |
15 | * @since 1.0.0
16 | */
17 | class ImageAwareLayerListener implements EventSubscriberInterface
18 | {
19 | /**
20 | * @var ImageInfo
21 | */
22 | protected $info;
23 |
24 | /**
25 | * @var ResourceHelper
26 | */
27 | protected $rh;
28 |
29 | /**
30 | * @param ImageInfo
31 | */
32 | public function __construct(ImageInfo $info, ResourceHelper $rh)
33 | {
34 | $this->info = $info;
35 | $this->rh = $rh;
36 | }
37 |
38 | /**
39 | * @inheritDoc
40 | */
41 | public static function getSubscribedEvents()
42 | {
43 | return [
44 | PhpGdEvents::PRE_IMAGE => [
45 | ['initImcUri', 909],
46 | ['initFilePointer', 899],
47 | ['initImageInfo', 889],
48 | ['initFinalDimensions', 879],
49 | ['termFilePointer', 99],
50 | ],
51 | PhpGdEvents::FINISH_IMAGE => [
52 | ['termFilePointer', 99],
53 | ['termImcUri', 89],
54 | ]
55 | ];
56 | }
57 |
58 | /**
59 | * @param PhpGdEvent
60 | */
61 | public function initImcUri(PhpGdEvent $event)
62 | {
63 | ImcStream::register();
64 | $layers = $event->getLayers();
65 | foreach ($layers as $key => $layer) {
66 | if (!($layer instanceof ImageAwareLayerInterface)) {
67 | continue;
68 | }
69 | $arr = false;
70 | if ($layer->has('image.http.url')) {
71 | $arr = [
72 | 'uri' => $layer->get('image.http.url'),
73 | 'data_limit' => $layer->get('image.http.data_limit'),
74 | 'timeout' => $layer->get('image.http.timeout'),
75 | 'seek' => true,
76 | 'global' => true,
77 | ];
78 | } elseif ($layer->has('image.filename')) {
79 | $arr = [
80 | 'uri' => $layer->get('image.filename'),
81 | 'seek' => true
82 | ];
83 | }
84 | if ($arr) {
85 | $uri = 'imc://'.serialize($arr);
86 | $layer->set('image.imc_uri', $uri);
87 | }
88 | }
89 | }
90 |
91 | /**
92 | * @param PhpGdEvent
93 | */
94 | public function initFilePointer(PhpGdEvent $event)
95 | {
96 | $layers = $event->getLayers();
97 | foreach ($layers as $key => $layer) {
98 | if (!($layer instanceof ImageAwareLayerInterface)) {
99 | continue;
100 | }
101 | if ($layer->has('image.imc_uri')) {
102 | $fp = fopen($layer->get('image.imc_uri'), 'rb');
103 | } elseif ($layer->has('image.contents')) {
104 | $fp = fopen('php://temp', 'rb+');
105 | fwrite($fp, $layer->get('image.contents'));
106 | }
107 | $layer->set('image.fp', $fp);
108 | }
109 | }
110 |
111 | /**
112 | * @param PhpGdEvent
113 | */
114 | public function initImageInfo(PhpGdEvent $event)
115 | {
116 | $layers = $event->getLayers();
117 | foreach ($layers as $layer) {
118 | if (!($layer instanceof ImageAwareLayerInterface)) {
119 | continue;
120 | }
121 | $info = $this->info->resolveFromFilePointer($layer->get('image.fp'));
122 | $layer->add([
123 | 'image.width' => $info['width'],
124 | 'image.height' => $info['height'],
125 | 'image.format' => $info['format'],
126 | ]);
127 | }
128 | }
129 |
130 | /**
131 | * @param PhpGdEvent
132 | */
133 | public function initFinalDimensions(PhpGdEvent $event)
134 | {
135 | $layers = $event->getLayers();
136 | foreach ($layers as $layer) {
137 | if (!($layer instanceof ImageAwareLayerInterface)) {
138 | continue;
139 | }
140 | $width = $layer->get('image.width');
141 | $height = $layer->get('image.height');
142 | if ($layer->has('image.resize.width')) {
143 | $args = $this->rh->getResizeArguments(
144 | $width,
145 | $height,
146 | $layer->get('image.resize.width'),
147 | $layer->get('image.resize.height'),
148 | $layer->get('image.resize.option')
149 | );
150 | if ($args) {
151 | $width = $args['dst_w'];
152 | $height = $args['dst_h'];
153 | }
154 | }
155 | $layer->add(['final.width' => $width, 'final.height' => $height]);
156 | }
157 | }
158 |
159 | /**
160 | * @param PhpGdEvent
161 | */
162 | public function termFilePointer(PhpGdEvent $event)
163 | {
164 | $layers = $event->getLayers();
165 | foreach ($layers as $layer) {
166 | if (!($layer instanceof ImageAwareLayerInterface)) {
167 | continue;
168 | }
169 | if ($layer->has('image.fp')) {
170 | fclose($layer->get('image.fp'));
171 | $layer->remove('image.fp');
172 | }
173 | }
174 | }
175 |
176 | /**
177 | * @param PhpGdEvent
178 | */
179 | public function termImcUri(PhpGdEvent $event)
180 | {
181 | $layers = $event->getLayers();
182 | foreach ($layers as $key => $layer) {
183 | if (!($layer instanceof ImageAwareLayerInterface)) {
184 | continue;
185 | }
186 | if ($layer->has('image.imc_uri')) {
187 | ImcStream::fclose($layer->get('image.imc_uri'));
188 | $layer->remove('image.imc_uri');
189 | }
190 | }
191 | }
192 |
193 | }
194 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/Extension/Core/EventListener/ImageFactoryListener.php:
--------------------------------------------------------------------------------
1 |
12 | * @since 1.0.0
13 | */
14 | class ImageFactoryListener implements EventSubscriberInterface
15 | {
16 | /**
17 | * @var ImageFactory
18 | */
19 | protected $factory;
20 |
21 | /**
22 | * @param ImageFactory $factory
23 | */
24 | public function __construct(ImageFactory $factory)
25 | {
26 | $this->factory = $factory;
27 | }
28 |
29 | /**
30 | * @inheritDoc
31 | */
32 | public static function getSubscribedEvents()
33 | {
34 | return [
35 | PhpGdEvents::IMAGE => ['createImage', 99],
36 | ];
37 | }
38 |
39 | /**
40 | * @param PhpGdEvent $event
41 | */
42 | public function createImage(PhpGdEvent $event)
43 | {
44 | $image = $this->factory->createImage($event->getLayers(), $event->getOptions());
45 | $event->setImage($image);
46 | $event->stopPropagation();
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/Extension/Core/EventListener/ImageMetadataListener.php:
--------------------------------------------------------------------------------
1 |
12 | * @since 1.0.0
13 | */
14 | class ImageMetadataListener implements EventSubscriberInterface
15 | {
16 | /**
17 | * @var PhpGdContext
18 | */
19 | protected $context;
20 |
21 | /**
22 | * @var PhpGdContext $context
23 | */
24 | public function __construct(PhpGdContext $context)
25 | {
26 | $this->context = $context;
27 | }
28 |
29 | /**
30 | * @inheritDoc
31 | */
32 | public static function getSubscribedEvents()
33 | {
34 | return [
35 | PhpGdEvents::FINISH_IMAGE => ['addImageMetadatas', 899],
36 | ];
37 | }
38 |
39 | /**
40 | * @param PhpGdEvent $event
41 | */
42 | public function addImageMetadatas(PhpGdEvent $event)
43 | {
44 | $image = $event->getImage();
45 | $layers = $event->getLayers();
46 | $format = $layers[0]->get('final.format');
47 |
48 | $image->setMime($this->context->getImageMime($format));
49 | $image->setExtension($this->context->getImageExtension($format));
50 | $image->setWidth($layers[0]->get('final.width'));
51 | $image->setHeight($layers[0]->get('final.height'));
52 | $image->addExtras([
53 | 'original_width' => $layers[0]->get('image.width'),
54 | 'original_height' => $layers[0]->get('image.height'),
55 | ]);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/Extension/Core/EventListener/MemoryRequirementListener.php:
--------------------------------------------------------------------------------
1 |
14 | * @since 1.0.0
15 | */
16 | class MemoryRequirementListener implements EventSubscriberInterface
17 | {
18 | /**
19 | * @var PhpGdContext
20 | */
21 | protected $context;
22 |
23 | /**
24 | * @var mixed[]
25 | */
26 | protected $extras = [];
27 |
28 | /**
29 | * @param PhpGdContext $context
30 | */
31 | public function __construct(PhpGdContext $context)
32 | {
33 | $this->context = $context;
34 | }
35 |
36 | /**
37 | * @inheritDoc
38 | */
39 | public static function getSubscribedEvents()
40 | {
41 | return [
42 | PhpGdEvents::PRE_IMAGE => ['verifyMemoryLimit', 869],
43 | PhpGdEvents::FINISH_IMAGE => ['addImageExtras', 889],
44 | ];
45 | }
46 |
47 | /**
48 | * @param PhpGdEvent $event
49 | * @throws RuntimeException
50 | */
51 | public function verifyMemoryLimit(PhpGdEvent $event)
52 | {
53 | $options = $event->getOptions();
54 | $layers = $event->getLayers();
55 | $limit = $this->context->getMemoryLimit($options['memory_limit']);
56 | $fixed = memory_get_usage(true);
57 | $peak = 0;
58 | foreach ($layers as $key => $layer) {
59 | if (!($layer instanceof ImageAwareLayerInterface)) {
60 | continue;
61 | }
62 | $width = $layer->get('image.width');
63 | $height = $layer->get('image.height');
64 | $finalWidth = $layer->get('final.width');
65 | $finalHeight = $layer->get('final.height');
66 | $constant = $this->getMemoryConstant($layer->get('image.format'));
67 | $dynamic = $width * $height * $constant;
68 | if (0 === $key) {
69 | $fixed += $finalWidth * $finalHeight * $constant;
70 | if ($limit < $fixed + $dynamic) {
71 | if ($finalWidth * $finalHeight > $width * $height) {
72 | throw new RuntimeException(
73 | 'output.image.dimensions.exceed.limit.%cp_dimensions%',
74 | ['%cp_dimensions%' => $finalWidth.'x'.$finalHeight]
75 | );
76 | } else {
77 | throw new RuntimeException(
78 | 'image.dimensions.exceed.limit.%cp_dimensions%',
79 | ['%cp_dimensions%' => $width.'x'.$height]
80 | );
81 | }
82 | }
83 | } elseif ($limit < $fixed + $dynamic) {
84 | throw new RuntimeException('not.enough.memory.to.process.image');
85 | }
86 | $peak = ($dynamic > $peak) ? $dynamic : $peak;
87 | }
88 | $this->extras['memory_approx'] = number_format(($fixed + $peak) / (1024 * 1024), 2).' MB';
89 | }
90 |
91 | /**
92 | * @param PhpGdEvent $event
93 | */
94 | public function addImageExtras(PhpGdEvent $event)
95 | {
96 | if ($this->extras) {
97 | $image = $event->getImage();
98 | $image->addExtras($this->extras);
99 | }
100 | }
101 |
102 | /**
103 | * @param string $format
104 | * @return int|float
105 | */
106 | protected function getMemoryConstant($format)
107 | {
108 | switch($format) {
109 | case PhpGdContext::FORMAT_JPEG:
110 | $constant = 6.2;
111 | break;
112 | case PhpGdContext::FORMAT_GIF:
113 | $constant = 3;
114 | break;
115 | case PhpGdContext::FORMAT_PNG:
116 | $constant = 9.5;
117 | break;
118 | case PhpGdContext::FORMAT_WEBP:
119 | $constant = 6;
120 | break;
121 | default:
122 | $constant = 4;
123 | }
124 |
125 | return $constant;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/Extension/Core/EventListener/SystemRequirementListener.php:
--------------------------------------------------------------------------------
1 |
14 | * @since 1.0.0
15 | */
16 | class SystemRequirementListener implements EventSubscriberInterface
17 | {
18 | /**
19 | * @var PhpGdContext
20 | */
21 | protected $context;
22 |
23 | /**
24 | * @param PhpGdContext $context
25 | */
26 | public function __construct(PhpGdContext $context)
27 | {
28 | $this->context = $context;
29 | }
30 |
31 | /**
32 | * @inheritDoc
33 | */
34 | public static function getSubscribedEvents()
35 | {
36 | return [
37 | PhpGdEvents::PRE_IMAGE => [
38 | ['verifyEngine', 999],
39 | ['verifySavedFormat', 989],
40 | ],
41 | ];
42 | }
43 |
44 | /**
45 | * @throws RuntimeException
46 | */
47 | public function verifyEngine()
48 | {
49 | if (!$this->context->isEngineSupported()) {
50 | throw new RuntimeException('gd.extension.not.enabled');
51 | }
52 | }
53 |
54 | /**
55 | * @param PhpGdEvent $event
56 | * @throws InvalidArgumentException
57 | */
58 | public function verifySavedFormat(PhpGdEvent $event)
59 | {
60 | $format = $event->getOptions()['output_format'];
61 | if ('default' !== $format && !$this->context->isImageFormatSupported($format)) {
62 | throw new InvalidArgumentException(
63 | 'output.image.format.not.supported.%cp_unsupported%',
64 | ['%cp_unsupported%' => '"'.$format.'"']
65 | );
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/Extension/Core/EventListener/TextLayerListener.php:
--------------------------------------------------------------------------------
1 |
14 | * @since 1.0.0
15 | */
16 | class TextLayerListener implements EventSubscriberInterface
17 | {
18 | /**
19 | * @var PhpGdContext
20 | */
21 | protected $context;
22 |
23 | /**
24 | * @param PhpGdContext $context
25 | */
26 | public function __construct(PhpGdContext $context)
27 | {
28 | $this->context = $context;
29 | }
30 |
31 | /**
32 | * @inheritDoc
33 | */
34 | public static function getSubscribedEvents()
35 | {
36 | return [
37 | PhpGdEvents::PRE_IMAGE => ['verifyFreeType', 849],
38 | ];
39 | }
40 |
41 | /**
42 | * @param PhpGdEvent
43 | * @throws RuntimeException
44 | */
45 | public function verifyFreeType(PhpGdEvent $event)
46 | {
47 | $layers = $event->getLayers();
48 | foreach ($layers as $key => $layer) {
49 | if (!($layer instanceof TextLayerInterface)) {
50 | continue;
51 | }
52 | if (!$this->context->isFreeTypeSupported()) {
53 | throw new RuntimeException('adding.text.not.supported');
54 | }
55 | break;
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/Extension/Core/ImageFactory.php:
--------------------------------------------------------------------------------
1 |
16 | * @since 1.0.0
17 | */
18 | class ImageFactory
19 | {
20 | /**
21 | * @var ResourceHelper
22 | */
23 | protected $rh;
24 |
25 | /**
26 | * @param ResourceHelper $rh
27 | */
28 | public function __construct(ResourceHelper $rh)
29 | {
30 | $this->rh = $rh;
31 | }
32 |
33 | /**
34 | * @param LayerInterface[] $layers
35 | * @param mixed[] $options
36 | * @return Image
37 | */
38 | public function createImage(array $layers, array $options)
39 | {
40 | $this->initContents($layers, $options);
41 | $contents = $layers[0]->get('final.contents');
42 |
43 | $image = new Image();
44 | $image->setContents($contents);
45 |
46 | return $image;
47 | }
48 |
49 | /**
50 | * @param LayerInterface[] $layers
51 | * @param mixed[] $options
52 | */
53 | protected function initContents(array $layers, $options)
54 | {
55 | foreach ($layers as $layer) {
56 | if ($layer instanceof ImageAwareLayerInterface) {
57 | $this->initImageAwareLayerResource($layer);
58 | } elseif ($layer instanceof TextLayerInterface) {
59 | $this->initTextLayerResource($layer);
60 | }
61 | if ($layer instanceof RegularLayerInterface) {
62 | $resource = $this->rh->getMergedGdResource(
63 | $layers[0]->get('final.resource'),
64 | $layer->get('final.resource'),
65 | $layer->get('regular.move.x'),
66 | $layer->get('regular.move.y'),
67 | $layer->get('regular.move.gravity')
68 | );
69 | imagedestroy($layer->get('final.resource'));
70 | $layers[0]->set('final.resource', $resource);
71 | }
72 | }
73 | $contents = $this->rh->getContentsFromGdResource(
74 | $layers[0]->get('final.format'),
75 | $layers[0]->get('final.resource'),
76 | $options,
77 | true
78 | );
79 | imagedestroy($layers[0]->get('final.resource'));
80 |
81 | $layers[0]->set('final.contents', $contents);
82 | }
83 |
84 | /**
85 | * @param ImageAwareLayerInterface $layer
86 | */
87 | protected function initImageAwareLayerResource(ImageAwareLayerInterface $layer)
88 | {
89 | $format = $layer->get('image.format');
90 | if ($layer->has('image.imc_uri')) {
91 | $uri = $layer->get('image.imc_uri');
92 | $resource = $this->rh->getGdResourceFromStream($format, $uri, true);
93 | } else {
94 | $contents = $layer->get('image.contents');
95 | $resource = $this->rh->getGdResourceFromContents($format, $contents, true);
96 | }
97 |
98 | if ($layer->has('image.resize.width')) {
99 | $resource = $this->rh->getResizedGdResource(
100 | $resource,
101 | $layer->get('image.resize.width'),
102 | $layer->get('image.resize.height'),
103 | $layer->get('image.resize.option'),
104 | true
105 | );
106 | } elseif ($layer instanceof BackgroundLayerInterface && $layer->get('image.format') === PhpGdContext::FORMAT_PNG) {
107 | $resource = $this->rh->getClonedGdResource($resource);
108 | }
109 |
110 | $layer->set('final.resource', $resource);
111 | }
112 |
113 | /**
114 | * @param TextLayerInterface $layer
115 | */
116 | protected function initTextLayerResource(TextLayerInterface $layer)
117 | {
118 | $resource = $this->rh->getTextGdResource(
119 | $layer->get('text.font.filename'),
120 | $layer->get('text.font.size'),
121 | $layer->get('text.font.rgb_color'),
122 | $layer->get('text.label'),
123 | $layer->get('text.line_spacing'),
124 | $layer->get('text.angle'),
125 | $layer->get('text.box.paddings'),
126 | $layer->get('text.box.rgb_color')
127 | );
128 |
129 | $layer->set('final.resource', $resource);
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/Extension/DelegatingExtension.php:
--------------------------------------------------------------------------------
1 |
12 | * @since 1.0.0
13 | */
14 | class DelegatingExtension implements ExtensionInterface
15 | {
16 | /**
17 | * @return ExtensionInterface[]
18 | */
19 | protected function getRegisteredExtensions()
20 | {
21 | return [
22 | new CoreExtension(),
23 | new GifExtension(),
24 | new SaveExtension(),
25 | ];
26 | }
27 |
28 | /**
29 | * @inheritDoc
30 | */
31 | public function boot(EventDispatcherInterface $dispatcher)
32 | {
33 | $extensions = $this->getRegisteredExtensions();
34 | foreach ($extensions as $extension) {
35 | $extension->boot($dispatcher);
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/Extension/ExtensionInterface.php:
--------------------------------------------------------------------------------
1 |
9 | * @since 1.0.0
10 | */
11 | interface ExtensionInterface
12 | {
13 | /**
14 | * @param EventDispatcherInterface $dispatcher
15 | */
16 | public function boot(EventDispatcherInterface $dispatcher);
17 | }
18 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/Extension/Gif/EventListener/GifExtractorListener.php:
--------------------------------------------------------------------------------
1 |
14 | * @since 1.0.0
15 | */
16 | class GifExtractorListener implements EventSubscriberInterface
17 | {
18 | /**
19 | * @var GifExtractor
20 | */
21 | protected $extractor;
22 |
23 | /**
24 | * @var mixed[]
25 | */
26 | protected $extras = [];
27 |
28 | /**
29 | * @param GifExtractor $extractor
30 | */
31 | public function __construct(GifExtractor $extractor)
32 | {
33 | $this->extractor = $extractor;
34 | }
35 |
36 | /**
37 | * @return inheritDoc
38 | */
39 | public static function getSubscribedEvents()
40 | {
41 | return [
42 | PhpGdEvents::PRE_IMAGE => ['initExtracted', 829],
43 | PhpGdEvents::FINISH_IMAGE => ['addImageExtras', 879],
44 | ];
45 | }
46 |
47 | /**
48 | * @param PhpGdEvent $event
49 | */
50 | public function initExtracted(PhpGdEvent $event)
51 | {
52 | $options = $event->getOptions();
53 | $layers = $event->getLayers();
54 |
55 | if (
56 | !$options['gif_animation'] ||
57 | PhpGdContext::FORMAT_GIF !== $layers[0]->get('final.format') ||
58 | PhpGdContext::FORMAT_GIF !== $layers[0]->get('image.format')
59 | ) {
60 | return;
61 | }
62 |
63 | $fp = $layers[0]->get('image.fp');
64 | rewind($fp);
65 | if ('GIF89a' !== fread($fp, 6)) {
66 | return;
67 | }
68 | rewind($fp);
69 | $extracted = $this->extractor->extractFromFilePointer($fp);
70 | if (!$extracted->isAnimated()) {
71 | return;
72 | }
73 | if (!$extracted->isValid()) {
74 | $e = new TranslatedException('gif.animation.may.lost.due.to.corrupted.frame.data');
75 | $this->extras['gif_error'] = $e->getMessage();
76 |
77 | return;
78 | }
79 |
80 | $layers[0]->set('gif.extracted', $extracted);
81 | }
82 |
83 | /**
84 | * @param PhpGdEvent $event
85 | */
86 | public function addImageExtras(PhpGdEvent $event)
87 | {
88 | if ($this->extras) {
89 | $image = $event->getImage();
90 | $image->addExtras($this->extras);
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/Extension/Gif/EventListener/ImageFactoryListener.php:
--------------------------------------------------------------------------------
1 |
13 | * @since 1.0.0
14 | */
15 | class ImageFactoryListener implements EventSubscriberInterface
16 | {
17 | /**
18 | * @var ImageFactory
19 | */
20 | protected $factory;
21 |
22 | /**
23 | * @var mixed[]
24 | */
25 | protected $extras = [];
26 |
27 | /**
28 | * @param ImageFactory $factory
29 | */
30 | public function __construct(ImageFactory $factory)
31 | {
32 | $this->factory = $factory;
33 | }
34 |
35 | /**
36 | * @return mixed[]
37 | */
38 | public static function getSubscribedEvents()
39 | {
40 | return [
41 | PhpGdEvents::IMAGE => ['createImage', 199],
42 | PhpGdEvents::FINISH_IMAGE => ['addImageExtras', 869],
43 | ];
44 | }
45 |
46 | /**
47 | * @param PhpGdEvent $event
48 | */
49 | public function createImage(PhpGdEvent $event)
50 | {
51 | $layers = $event->getLayers();
52 | if (!$layers[0]->has('gif.extracted')) {
53 | return;
54 | }
55 |
56 | try {
57 | $options = $event->getOptions();
58 | $image = $this->factory->createImage($layers, $options);
59 | $event->setImage($image);
60 | $event->stopPropagation();
61 | } catch (\Exception $e) {
62 | $e = new TranslatedException('gif.animation.may.lost.due.to.corrupted.frame.data');
63 | $this->extras['gif_error'] = $e->getMessage();
64 | }
65 | }
66 |
67 | /**
68 | * param PhpGdEvent $event
69 | */
70 | public function addImageExtras(PhpGdEvent $event)
71 | {
72 | if (!$this->extras) {
73 | return;
74 | }
75 | $image = $event->getImage();
76 | $image->addExtras($this->extras);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/Extension/Gif/EventListener/MemoryRequirementListener.php:
--------------------------------------------------------------------------------
1 |
13 | * @since 1.0.0
14 | */
15 | class MemoryRequirementListener implements EventSubscriberInterface
16 | {
17 | /**
18 | * @var PhpGdContext
19 | */
20 | protected $context;
21 |
22 | /**
23 | * @var mixed[]
24 | */
25 | protected $extras = [];
26 |
27 | /**
28 | * @param PhpGdContext $context
29 | */
30 | public function __construct(PhpGdContext $context)
31 | {
32 | $this->context = $context;
33 | }
34 |
35 | /**
36 | * @return mixed[]
37 | */
38 | public static function getSubscribedEvents()
39 | {
40 | return [
41 | PhpGdEvents::PRE_IMAGE => ['verifyMemoryLimit', 819],
42 | PhpGdEvents::FINISH_IMAGE => ['addImageExtras', 199],
43 | ];
44 | }
45 |
46 | /**
47 | * @param PhpGdEvent $event
48 | */
49 | public function verifyMemoryLimit(PhpGdEvent $event)
50 | {
51 | $layers = $event->getLayers();
52 | $options = $event->getOptions();
53 | if (!$layers[0]->has('gif.extracted')) {
54 | return;
55 | }
56 |
57 | $totalFrames = count($layers[0]->get('gif.extracted'));
58 | $width = $layers[0]->get('image.width');
59 | $height = $layers[0]->get('image.height');
60 | $pixels = $width * $height;
61 | $finalWidth = $layers[0]->get('final.width');
62 | $finalHeight = $layers[0]->get('final.height');
63 | $finalPixels = $finalWidth * $finalHeight;
64 | $totalPixels = $totalFrames * $finalPixels;
65 | $limit = $this->context->getMemoryLimit($options['memory_limit']);
66 | $peak = memory_get_peak_usage(true) + 15 * 1024 * 1024 + 3 * ($pixels + $finalPixels);
67 |
68 | if (1000000 < $finalPixels || 57000000 < $totalPixels || $peak > $limit) {
69 | $e = new TranslatedException(
70 | 'gif.animation.may.lost.as.too.many.frames.or.dimensions.too.large.%total_frames%.%dimensions%',
71 | ['%total_frames%' => $totalFrames, '%dimensions%' => $width.'x'.$height]
72 | );
73 | $this->extras['gif_error'] = $e->getMessage();
74 | $layers[0]->remove('gif.extracted');
75 | } else {
76 | $this->extras['memory_approx'] = number_format($peak / (1024 * 1024), 2).' MB';
77 | }
78 | }
79 |
80 | /**
81 | * param PhpGdEvent $event
82 | */
83 | public function addImageExtras(PhpGdEvent $event)
84 | {
85 | $image = $event->getImage();
86 | if (array_key_exists('gif_error', $image->getExtras())) {
87 | return;
88 | }
89 | $image->addExtras($this->extras);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/Extension/Gif/GifBuilder.php:
--------------------------------------------------------------------------------
1 |
9 | * @since 1.0.0
10 | */
11 | class GifBuilder
12 | {
13 | /**
14 | * @var int
15 | */
16 | protected $imageWidth;
17 |
18 | /**
19 | * @var int
20 | */
21 | protected $imageHeight;
22 |
23 | /**
24 | * @var bool
25 | */
26 | protected $interlaceFlag;
27 |
28 | /**
29 | * @var null|int
30 | */
31 | protected $transparentColorIndex;
32 |
33 | /**
34 | * @var string
35 | */
36 | protected $colorTable;
37 |
38 | /**
39 | * @var string
40 | */
41 | protected $imageData;
42 |
43 | /**
44 | * @param int $width
45 | * @return $this
46 | */
47 | public function imageWidth($width)
48 | {
49 | $this->imageWidth = $width;
50 |
51 | return $this;
52 | }
53 |
54 | /**
55 | * @param int $height
56 | * @return $this
57 | */
58 | public function imageHeight($height)
59 | {
60 | $this->imageHeight = $height;
61 |
62 | return $this;
63 | }
64 |
65 | /**
66 | * @param bool $flag
67 | * @return $this
68 | */
69 | public function interlaceFlag($flag)
70 | {
71 | $this->interlaceFlag = $flag;
72 |
73 | return $this;
74 | }
75 |
76 | /**
77 | * @param int $index
78 | * @return $this
79 | */
80 | public function transparentColorIndex($index)
81 | {
82 | $this->transparentColorIndex = $index;
83 |
84 | return $this;
85 | }
86 |
87 | /**
88 | * @param string $contents
89 | * @return $this
90 | */
91 | public function colorTable($contents)
92 | {
93 | $this->colorTable = $contents;
94 |
95 | return $this;
96 | }
97 |
98 | /**
99 | * @param string $contents
100 | * @return $this
101 | */
102 | public function imageData($contents)
103 | {
104 | $this->imageData = $contents;
105 |
106 | return $this;
107 | }
108 |
109 | /**
110 | * @return string
111 | */
112 | public function getContents()
113 | {
114 | // Header
115 | $contents = 'GIF89a';
116 |
117 | // Logical screen descriptor
118 | $contents .= pack('v*', $this->imageWidth, $this->imageHeight);
119 | $contents .= "\x00\x00\x00";
120 |
121 | // Graphic control extension
122 | if (isset($this->transparentColorIndex)) {
123 | $contents .= "\x21\xF9\x04\x01\x00\x00";
124 | $contents .= pack('C', $this->transparentColorIndex);
125 | $contents .= "\x00";
126 | }
127 |
128 | // Image descriptor
129 | $contents .= "\x2C\x00\x00\x00\x00";
130 | $contents .= pack('v*', $this->imageWidth, $this->imageHeight);
131 | $interlace = $this->interlaceFlag ? 0b01000000 : 0;
132 | $colorTableSize = log(strlen($this->colorTable) / 3, 2) - 1;
133 | $unpack = 0b10000000 | $interlace | $colorTableSize;
134 | $pack = pack('C', $unpack);
135 | $contents .= $pack;
136 |
137 | // Local color table
138 | $contents .= $this->colorTable;
139 |
140 | // Image Data
141 | $contents .= $this->imageData;
142 |
143 | // Terminator
144 | $contents .= "\x3B";
145 |
146 | $this->imageWidth = null;
147 | $this->imageHeight = null;
148 | $this->interlaceFlag = null;
149 | $this->transparentColorIndex = null;
150 | $this->colorTable= null;
151 | $this->imageData= null;
152 |
153 | return $contents;
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/Extension/Gif/GifExtension.php:
--------------------------------------------------------------------------------
1 |
15 | * @since 1.0.0
16 | */
17 | class GifExtension implements ExtensionInterface
18 | {
19 | /**
20 | * @param EventDispatcherInterface $dispatcher
21 | */
22 | public function boot(EventDispatcherInterface $dispatcher)
23 | {
24 | $context = new PhpGdContext();
25 | $rh = new ResourceHelper();
26 | $extractor = new GifExtractor();
27 | $builder = new GifBuilder();
28 | $builderPlus = new GifBuilderPlus();
29 | $optimizer = new GifOptimizer($rh);
30 | $factory = new ImageFactory($rh, $extractor, $builder, $builderPlus, $optimizer);
31 |
32 | $dispatcher->addSubscriber(new GifExtractorListener($extractor));
33 | $dispatcher->addSubscriber(new MemoryRequirementListener($context));
34 | $dispatcher->addSubscriber(new ImageFactoryListener($factory));
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/Extension/Gif/GifExtractor.php:
--------------------------------------------------------------------------------
1 |
9 | * @since 1.0.0
10 | */
11 | class GifExtractor
12 | {
13 | /**
14 | * @var int
15 | */
16 | protected $kp;
17 |
18 | /**
19 | * @var int
20 | */
21 | protected $dp;
22 |
23 | /**
24 | * @param string $uri
25 | * @return GifExtracted
26 | */
27 | public function extractFromStream($uri)
28 | {
29 | $fp = fopen($uri, 'r');
30 | $this->i = $uri;
31 | $extracted = $this->extractFromFilePointer($fp);
32 | fclose($fp);
33 |
34 | return $extracted;
35 | }
36 |
37 | /**
38 | * @param string $contents
39 | * @return GifExtracted
40 | */
41 | public function extractFromContents($contents)
42 | {
43 | $fp = fopen('php://memory', 'r+');
44 | fwrite($fp, $contents);
45 | rewind($fp);
46 | $extracted = $this->extractFromFilePointer($fp);
47 | fclose($fp);
48 |
49 | return $extracted;
50 | }
51 |
52 | /**
53 | * @param resource $fp
54 | * @return GifExtracted
55 | * @throws FileParseException
56 | */
57 | public function extractFromFilePointer($fp)
58 | {
59 | $extracted = new GifExtracted();
60 | $this->readMetadata($fp, $extracted);
61 | try {
62 | while (!feof($fp)) {
63 | switch ($this->read($fp, 1)) {
64 | case "\x21":
65 | $this->readExtension($fp, $extracted);
66 | break;
67 | case "\x2C":
68 | $this->readFrame($fp, $extracted);
69 | break;
70 | case "\x3B":
71 | $this->readEnd($extracted);
72 | break 2;
73 | default:
74 | throw new FileParseException('gif.parse.error');
75 | }
76 | }
77 | } catch (\Exception $e) {
78 | $extracted->setValid(false);
79 | }
80 | $this->kp = null;
81 | $this->dp = null;
82 |
83 | return $extracted;
84 | }
85 |
86 | /**
87 | * @param resource $fp
88 | * @param GifExtracted $extracted
89 | */
90 | protected function readMetadata($fp, GifExtracted $extracted)
91 | {
92 | $extracted->setHeader($v = $this->read($fp, 6));
93 | $extracted->setLogicalScreenDescriptor($v = $this->read($fp, 7));
94 | if ($v = $extracted->getGlobalColorTableFlag()) {
95 | $quantity = $extracted->getTotalGlobalColors();
96 | $extracted->setGlobalColorTable($this->read($fp, $quantity * 3));
97 | }
98 | }
99 |
100 | /**
101 | * @param resource $fp
102 | * @param GifExtracted $extracted
103 | */
104 | protected function readExtension($fp, GifExtracted $extracted)
105 | {
106 | switch ($this->read($fp, 1)) {
107 | case "\xFF":
108 | if ("\x0bNETSCAPE2.0" !== $this->read($fp, 12)) {
109 | $this->readDataBlock($fp);
110 | break;
111 | }
112 | $extracted->setNetscapeExtension("\x0bNETSCAPE2.0" . $this->read($fp, 5));
113 | break;
114 | case "\xF9":
115 | $contents = $this->read($fp, 6);
116 | if ($extracted->hasGraphicControlExtension()) {
117 | break;
118 | }
119 | $extracted->setGraphicControlExtension($contents);
120 | break;
121 | default:
122 | $this->readDataBlock($fp);
123 | }
124 | }
125 |
126 | /**
127 | * @param resource $fp
128 | * @param GifExtracted $extracted
129 | */
130 | protected function readFrame($fp, GifExtracted $extracted)
131 | {
132 | if (!$extracted->hasGraphicControlExtension()) {
133 | $contents = "\x04\x00".pack('v', 10)."\x00\x00";
134 | $extracted->setGraphicControlExtension($contents);
135 | }
136 |
137 | $extracted->setLinkedKey($this->kp);
138 | $extracted->setLinkedDisposalMethod($this->dp);
139 | if (GifExtracted::DISPOSAL_METHOD_PREVIOUS !== $extracted->getDisposalMethod()) {
140 | $this->kp = $extracted->key();
141 | $this->dp = $extracted->getDisposalMethod();
142 | }
143 |
144 | $extracted->setImageDescriptor($this->read($fp, 9));
145 |
146 | if ($extracted->getLocalColorTableFlag()) {
147 | $quantity = $extracted->getTotalLocalColors();
148 | $extracted->setLocalColorTable($this->read($fp, $quantity * 3));
149 | }
150 |
151 | $extracted->setImageData($this->read($fp, 1) . $this->readDataBlock($fp));
152 | $extracted->next();
153 | }
154 |
155 | /**
156 | * @param GifExtracted $extracted
157 | */
158 | protected function readEnd(GifExtracted $extracted)
159 | {
160 | if (!$extracted->hasNetscapeExtension() && 1 < count($extracted)) {
161 | $extracted->setNetscapeExtension("\x0bNETSCAPE2.0\x03\x01\x00\x00\x00");
162 | }
163 | $extracted->rewind();
164 | }
165 |
166 | /**
167 | * @param resource $fp
168 | * @return string
169 | * @throws FileParseException
170 | */
171 | protected function readDataBlock($fp)
172 | {
173 | $str = '';
174 | while (true) {
175 | $packed = $this->read($fp, 1);
176 | if ("\x00" === $packed) {
177 | $str .= "\x00";
178 | break;
179 | }
180 | $str .= $packed;
181 | $blockSize = unpack('C', $packed)[1];
182 | if (0 === $blockSize) {
183 | throw new FileParseException('gif.parse.error');
184 | }
185 | $str .= $this->read($fp, $blockSize);
186 | }
187 |
188 | return $str;
189 | }
190 |
191 | /**
192 | * @param resource $fp
193 | * @param int $length
194 | * @return string
195 | * @throws FileParseException
196 | */
197 | protected function read($fp, $length)
198 | {
199 | $bytes = @fread($fp, $length);
200 | if (false === $bytes || $length > strlen($bytes)) {
201 | throw new FileParseException('gif.parse.error');
202 | }
203 |
204 | return $bytes;
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/Extension/Gif/GifOptimizer.php:
--------------------------------------------------------------------------------
1 |
9 | * @since 1.0.0
10 | */
11 | class GifOptimizer
12 | {
13 | /**
14 | * @var ResourceHelper
15 | */
16 | protected $rh;
17 |
18 | /**
19 | * @param ResourceHelper $rh
20 | */
21 | public function __construct(ResourceHelper $rh)
22 | {
23 | $this->rh = $rh;
24 | }
25 |
26 | /**
27 | * @param resource $resource
28 | * @param resource $controlResource
29 | * @return resource
30 | */
31 | public function getOptimizedGdResource($resource, $controlResource)
32 | {
33 | if (imageistruecolor($resource)) {
34 | $resource = $this->rh->getPalettizedGdResource($resource);
35 | }
36 | $totalColors = imagecolorstotal($resource);
37 | $trans = imagecolortransparent($resource);
38 | $reds = new \SplFixedArray($totalColors);
39 | $greens = new \SplFixedArray($totalColors);
40 | $blues = new \SplFixedArray($totalColors);
41 |
42 | $i = 0;
43 | do {
44 | $colors = imagecolorsforindex($resource, $i);
45 | $reds[$i] = $colors['red'];
46 | $greens[$i] = $colors['green'];
47 | $blues[$i] = $colors['blue'];
48 | } while (++$i < $totalColors);
49 |
50 | if (imageistruecolor($controlResource)) {
51 | $controlResource = $this->rh->getPalettizedGdResource($controlResource);
52 | }
53 | $controlTotalColors = imagecolorstotal($controlResource);
54 | $controlTrans = imagecolortransparent($controlResource);
55 | $controlReds = new \SplFixedArray($controlTotalColors);
56 | $controlGreens = new \SplFixedArray($controlTotalColors);
57 | $controlBlues = new \SplFixedArray($controlTotalColors);
58 | $i = 0;
59 | do {
60 | $colors = imagecolorsforindex($controlResource, $i);
61 | $controlReds[$i] = $colors['red'];
62 | $controlGreens[$i] = $colors['green'];
63 | $controlBlues[$i] = $colors['blue'];
64 | } while (++$i < $controlTotalColors);
65 |
66 | $width = imagesx($resource);
67 | $height = imagesy($resource);
68 | $y = 0;
69 | do{
70 | $x = 0;
71 | do {
72 | $index = imagecolorat($resource, $x, $y);
73 | $red = $reds[$index];
74 | $green = $greens[$index];
75 | $blue = $blues[$index];
76 | $controlIndex = imagecolorat($controlResource, $x, $y);
77 | $controlRed = $controlReds[$controlIndex];
78 | $controlGreen = $controlGreens[$controlIndex];
79 | $controlBlue = $controlBlues[$controlIndex];
80 | if (
81 | (($red & 0b11111100) === ($controlRed & 0b11111100)) &&
82 | (($green & 0b11111100) === ($controlGreen & 0b11111100)) &&
83 | (($blue & 0b11111100) === ($controlBlue & 0b11111100))
84 | ) {
85 | imagesetpixel($resource, $x, $y, $trans);
86 | }
87 | } while (++$x !== $width);
88 | } while (++$y !== $height);
89 |
90 | return $resource;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/Extension/Save/EventListener/ImageFactoryListener.php:
--------------------------------------------------------------------------------
1 |
12 | * @since 1.0.0
13 | */
14 | class ImageFactoryListener implements EventSubscriberInterface
15 | {
16 | /**
17 | * @var ImageFactory
18 | */
19 | protected $factory;
20 |
21 | /**
22 | * @param ImageFactory $factory
23 | */
24 | public function __construct(ImageFactory $factory)
25 | {
26 | $this->factory = $factory;
27 | }
28 |
29 | /**
30 | * @return mixed[]
31 | */
32 | public static function getSubscribedEvents()
33 | {
34 | return [
35 | PhpGdEvents::PRE_IMAGE => ['createImage', 839],
36 | ];
37 | }
38 |
39 | /**
40 | * @param PhpGdEvent $event
41 | */
42 | public function createImage(PhpGdEvent $event)
43 | {
44 | $layers = $event->getLayers();
45 | if (1 === count($layers) &&
46 | $layers[0]->get('image.width') === $layers[0]->get('final.width') &&
47 | $layers[0]->get('image.height') === $layers[0]->get('final.height') &&
48 | $layers[0]->get('image.format') === $layers[0]->get('final.format')
49 | ) {
50 | $options = $event->getOptions();
51 | $image = $this->factory->createImage($layers, $options);
52 | $event->setImage($image);
53 | $event->stopPropagation();
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/Extension/Save/ImageFactory.php:
--------------------------------------------------------------------------------
1 |
10 | * @since 1.0.0
11 | */
12 | class ImageFactory
13 | {
14 | /**
15 | * @var ResourceHelper
16 | */
17 | protected $rh;
18 |
19 | /**
20 | * @param ResourceHelper $rh
21 | */
22 | public function __construct(ResourceHelper $rh)
23 | {
24 | $this->rh = $rh;
25 | }
26 |
27 | /**
28 | * @param Imagecraft\Layer\LayerInterface[] $layers
29 | * @param mixed[] $options
30 | * @return Image
31 | */
32 | public function createImage(array $layers, array $options)
33 | {
34 | $this->initContents($layers, $options);
35 | $contents = $layers[0]->get('final.contents');
36 |
37 | $image = new Image();
38 | $image->setContents($contents);
39 |
40 | return $image;
41 | }
42 |
43 | /**
44 | * @param LayerInterface[] $layers
45 | * @param mixed[] $options
46 | */
47 | protected function initContents(array $layers, $options)
48 | {
49 | $format = $layers[0]->get('image.format');
50 | if ($layers[0]->has('image.imc_uri')) {
51 | $uri = $layers[0]->get('image.imc_uri');
52 | $resource = $this->rh->getGdResourceFromStream($format, $uri, true);
53 | imagedestroy($resource);
54 | $contents = file_get_contents($uri);
55 | } else {
56 | $contents = $layers[0]->get('image.contents');
57 | $resource = $this->rh->getGdResourceFromContents($format, $contents, true);
58 | imagedestroy($resource);
59 | }
60 |
61 | $layers[0]->set('final.contents', $contents);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/Extension/Save/SaveExtension.php:
--------------------------------------------------------------------------------
1 |
12 | * @since 1.0.0
13 | */
14 | class SaveExtension implements ExtensionInterface
15 | {
16 | /**
17 | * @param EventDispatcherInterface $dispatcher
18 | */
19 | public function boot(EventDispatcherInterface $dispatcher)
20 | {
21 | $rh = new ResourceHelper();
22 | $factory = new ImageFactory($rh);
23 |
24 | $dispatcher->addSubscriber(new ImageFactoryListener($factory));
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/PhpGdContext.php:
--------------------------------------------------------------------------------
1 |
12 | * @since 1.0.0
13 | */
14 | class PhpGdContext extends AbstractContext
15 | {
16 | const FORMAT_WEBP = 'webp';
17 | const FORMAT_PNG = 'png';
18 | const FORMAT_JPEG = 'jpeg';
19 | const FORMAT_GIF = 'gif';
20 |
21 | /**
22 | * @param string $format
23 | * @return bool
24 | */
25 | public function isImageFormatSupported($format)
26 | {
27 | switch ($format) {
28 | case static::FORMAT_WEBP:
29 | $supported = function_exists('imagecreatefromwebp');
30 | break;
31 | case static::FORMAT_PNG:
32 | $supported = function_exists('imagecreatefrompng');
33 | break;
34 | case static::FORMAT_JPEG:
35 | $supported = function_exists('imagecreatefromjpeg');
36 | break;
37 | case static::FORMAT_GIF:
38 | $supported = function_exists('imagecreatefromgif') && function_exists('imagegif');
39 | break;
40 | default:
41 | $supported = false;
42 | }
43 |
44 | return $supported;
45 | }
46 |
47 | /**
48 | * @param string $format
49 | * @return string
50 | */
51 | public function getImageMime($format)
52 | {
53 | $mimes = [
54 | static::FORMAT_WEBP => 'image/webp',
55 | static::FORMAT_PNG => 'image/png',
56 | static::FORMAT_JPEG => 'image/jpeg',
57 | static::FORMAT_GIF => 'image/gif',
58 | ];
59 |
60 | return $mimes[$format];
61 | }
62 |
63 | /**
64 | * @param string $format
65 | * @return string
66 | */
67 | public function getImageExtension($format)
68 | {
69 | $extensions = [
70 | static::FORMAT_WEBP => 'webp',
71 | static::FORMAT_PNG => 'png',
72 | static::FORMAT_JPEG => 'jpg',
73 | static::FORMAT_GIF => 'gif',
74 | ];
75 |
76 | return $extensions[$format];
77 | }
78 |
79 | /**
80 | * @return bool
81 | */
82 | public function isFreeTypeSupported()
83 | {
84 | return function_exists('imagefttext');
85 | }
86 |
87 | /**
88 | * @inheritDoc
89 | */
90 | public function isEngineSupported()
91 | {
92 | return extension_loaded('gd');
93 | }
94 |
95 | /**
96 | * @inheritDoc
97 | */
98 | public function getSupportedImageFormatsToString()
99 | {
100 | $formats = [
101 | [static::FORMAT_WEBP, 'WEBP (VP8)'],
102 | [static::FORMAT_PNG, 'PNG'],
103 | [static::FORMAT_JPEG, 'JPEG'],
104 | [static::FORMAT_GIF, 'GIF'],
105 | ];
106 | for ($i = 0, $str = ''; $i < count($formats); $i++) {
107 | if ($this->isImageFormatSupported($formats[$i][0])) {
108 | $str .= (0 == $i) ? '"' : ', "';
109 | $str .= $formats[$i][1].'"';
110 | }
111 | }
112 |
113 | return $str;
114 | }
115 |
116 | /**
117 | * @inheritDoc
118 | */
119 | public function getSupportedFontFormatsToString()
120 | {
121 | if ($this->isFreeTypeSupported()) {
122 | return '"Postscript (.pfa, .pfb)", "TureType (.ttf)", "OpenType (.otf)"';
123 | }
124 |
125 | return '';
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/PhpGdEngine.php:
--------------------------------------------------------------------------------
1 |
11 | * @since 1.0.0
12 | */
13 | class PhpGdEngine implements EngineInterface
14 | {
15 | /**
16 | * @inheritDoc
17 | */
18 | public function getImage(array $layers, array $options)
19 | {
20 | $dispatcher = new EventDispatcher();
21 | $extension = new DelegatingExtension();
22 | $extension->boot($dispatcher);
23 |
24 | $event = new PhpGdEvent($layers, $options);
25 | $dispatcher->dispatch(PhpGdEvents::PRE_IMAGE, $event);
26 |
27 | if (!$event->getImage()) {
28 | $event = new PhpGdEvent($layers, $options);
29 | $dispatcher->dispatch(PhpGdEvents::IMAGE, $event);
30 | }
31 |
32 | $image = $event->getImage();
33 | $event = new PhpGdEvent($layers, $options);
34 | $event->setImage($image);
35 | $dispatcher->dispatch(PhpGdEvents::FINISH_IMAGE, $event);
36 |
37 | return $event->getImage();
38 | }
39 |
40 | /**
41 | * @inheritDoc
42 | */
43 | public function getContext(array $options)
44 | {
45 | return new PhpGdContext();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/PhpGdEvent.php:
--------------------------------------------------------------------------------
1 |
10 | * @since 1.0.0
11 | */
12 | class PhpGdEvent extends Event
13 | {
14 | /**
15 | * @var \Imagecraft\Layer\LayerInterface[]
16 | */
17 | protected $layers;
18 |
19 | /**
20 | * @var mixed[]
21 | */
22 | protected $options;
23 |
24 | /**
25 | * @var Image
26 | */
27 | protected $image;
28 |
29 | /**
30 | * @param \Imagecraft\Layer\LayerInterface[] $layers
31 | * @param mixed[] $options
32 | */
33 | public function __construct(array $layers, array $options)
34 | {
35 | $this->layers = $layers;
36 | $this->options = $options;
37 | }
38 |
39 | /**
40 | * @return \Imagecraft\Layer\LayerInterface[]
41 | */
42 | public function getLayers()
43 | {
44 | return $this->layers;
45 | }
46 |
47 | /**
48 | * @return mixed[]
49 | */
50 | public function getOptions()
51 | {
52 | return $this->options;
53 | }
54 |
55 | /**
56 | * @param Image $image
57 | */
58 | public function setImage(Image $image)
59 | {
60 | $this->image = $image;
61 | }
62 |
63 | /**
64 | * @return Image
65 | */
66 | public function getImage()
67 | {
68 | return $this->image;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/PhpGdEvents.php:
--------------------------------------------------------------------------------
1 |
9 | * @since 1.0.0
10 | */
11 | final class PhpGdEvents
12 | {
13 | const PRE_IMAGE = 'php_gd.pre_image';
14 | const IMAGE = 'php_gd.image';
15 | const FINISH_IMAGE = 'php_gd.finish_image';
16 | }
17 |
--------------------------------------------------------------------------------
/src/Engine/PhpGd/README.md:
--------------------------------------------------------------------------------
1 | # PhpGd Engine
2 |
3 | ## EventListeners
4 |
5 | | Event | PRI | Ext | Listener | Method | PPG |
6 | | :------------- | :---- | :----- | :-------------------------- | :-------------------- | :-- |
7 | | `PRE_IMAGE` | `999` | `Core` | `SystemRequirementListener` | `verifyEngine` | |
8 | | `PRE_IMAGE` | `989` | `Core` | `SystemRequirementListener` | `verifySavedFormat` | |
9 | | `PRE_IMAGE` | `909` | `Core` | `ImageAwareLayerListener` | `initImcUri` | |
10 | | `PRE_IMAGE` | `899` | `Core` | `ImageAwareLayerListener` | `initFilePointer` | |
11 | | `PRE_IMAGE` | `889` | `Core` | `ImageAwareLayerListener` | `initImageInfo` | |
12 | | `PRE_IMAGE` | `879` | `Core` | `ImageAwareLayerListener` | `initFinalDimensions` | |
13 | | `PRE_IMAGE` | `869` | `Core` | `MemoryRequirementListener` | `verifyMemoryLimit` | |
14 | | `PRE_IMAGE` | `859` | `Core` | `BackgroundLayerListener` | `initFinalFormat` | |
15 | | `PRE_IMAGE` | `849` | `Core` | `TextLayerListener` | `verifyFreeType` | |
16 | | `PRE_IMAGE` | `839` | `Save` | `ImageFactoryListener` | `createImage` | Yes |
17 | | `PRE_IMAGE` | `829` | `Gif` | `GifExtractorListener` | `initExtracted` | |
18 | | `PRE_IMAGE` | `819` | `Gif` | `MemoryRequirementListener` | `verifyMemoryLimit` | |
19 | | `PRE_IMAGE` | `099` | `Core` | `ImageAwareLayerListener` | `termFilePointer` | |
20 | | `IMAGE` | `199` | `Gif` | `ImageFactoryListener` | `createImage` | Yes |
21 | | `IMAGE` | `099` | `Core` | `ImageFactoryListener` | `createImage` | Yes |
22 | | `FINISH_IMAGE` | `899` | `Core` | `ImageMetadataListener` | `addImageMetadatas` | |
23 | | `FINISH_IMAGE` | `889` | `Core` | `MemoryRequirementListener` | `addImageExtras` | |
24 | | `FINISH_IMAGE` | `879` | `Gif` | `GifExtractorListener` | `addImageExtras` | |
25 | | `FINISH_IMAGE` | `869` | `Gif` | `ImageFactoryListener` | `addImageExtras` | |
26 | | `FINISH_IMAGE` | `199` | `Gif` | `MemoryRequirementListener` | `addImageExtras` | |
27 | | `FINISH_IMAGE` | `099` | `Core` | `ImageAwareLayerListener` | `termFilePointer` | |
28 | | `FINISH_IMAGE` | `089` | `Core` | `ImageAwareLayerListener` | `termImcUri` | |
29 |
--------------------------------------------------------------------------------
/src/Exception/BadMethodCallException.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | class BadMethodCallException extends TranslatedException
10 | {
11 | }
12 |
--------------------------------------------------------------------------------
/src/Exception/FileParseException.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | class FileParseException extends RuntimeException
10 | {
11 | }
12 |
--------------------------------------------------------------------------------
/src/Exception/InvalidArgumentException.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | class InvalidArgumentException extends TranslatedException
10 | {
11 | }
12 |
--------------------------------------------------------------------------------
/src/Exception/InvalidFontException.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | class InvalidFontException extends RuntimeException
10 | {
11 | }
12 |
--------------------------------------------------------------------------------
/src/Exception/InvalidImageException.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | class InvalidImageException extends RuntimeException
10 | {
11 | }
12 |
--------------------------------------------------------------------------------
/src/Exception/RuntimeException.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | class RuntimeException extends TranslatedException
10 | {
11 | }
12 |
--------------------------------------------------------------------------------
/src/Exception/TranslatedException.php:
--------------------------------------------------------------------------------
1 |
9 | * @since 1.0.0
10 | */
11 | class TranslatedException extends BaseTranslatedException
12 | {
13 | /**
14 | * @param string $id
15 | * @param string[] $parameters
16 | * @param null|int $number
17 | * @param int $code
18 | * @param null|\Exception $previous
19 | */
20 | public function __construct(
21 | $id,
22 | array $parameters = [],
23 | $number = null,
24 | $code = 0,
25 | \Exception $previous = null
26 | ) {
27 | parent::__construct('imagecraft', $id, $parameters, $number, $code, $previous);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Image.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | class Image
10 | {
11 | /**
12 | * @var string
13 | */
14 | protected $contents;
15 |
16 | /**
17 | * @var int
18 | */
19 | protected $width;
20 |
21 | /**
22 | * @var int
23 | */
24 | protected $height;
25 |
26 | /**
27 | * @var string
28 | */
29 | protected $mime;
30 |
31 | /**
32 | * @var string
33 | */
34 | protected $extension;
35 |
36 | /**
37 | * @var string|null
38 | */
39 | protected $message;
40 |
41 | /**
42 | * @var string|null
43 | */
44 | protected $verboseMessage;
45 |
46 | /**
47 | * @var mixed[]
48 | */
49 | protected $extras = [];
50 |
51 | /**
52 | * @return bool
53 | * @api
54 | */
55 | public function isValid()
56 | {
57 | return !isset($this->message);
58 | }
59 |
60 | /**
61 | * @param string $message
62 | */
63 | public function setMessage($message)
64 | {
65 | $this->message = $message;
66 | }
67 |
68 | /**
69 | * @return string
70 | * @api
71 | */
72 | public function getMessage()
73 | {
74 | return $this->message;
75 | }
76 |
77 | /**
78 | * @param string $verboseMessage
79 | */
80 | public function setVerboseMessage($verboseMessage)
81 | {
82 | $this->verboseMessage = $verboseMessage;
83 | }
84 |
85 | /**
86 | * @return string
87 | * @api
88 | */
89 | public function getVerboseMessage()
90 | {
91 | return $this->verboseMessage;
92 | }
93 |
94 | /**
95 | * @param string $contents
96 | */
97 | public function setContents($contents)
98 | {
99 | $this->contents = $contents;
100 | }
101 |
102 | /**
103 | * @return string
104 | * @api
105 | */
106 | public function getContents()
107 | {
108 | return $this->contents;
109 | }
110 |
111 | /**
112 | * @param int $width
113 | */
114 | public function setWidth($width)
115 | {
116 | $this->width = $width;
117 | }
118 |
119 | /**
120 | * @return int
121 | * @api
122 | */
123 | public function getWidth()
124 | {
125 | return $this->width;
126 | }
127 |
128 | /**
129 | * @param int $height
130 | */
131 | public function setHeight($height)
132 | {
133 | $this->height = $height;
134 | }
135 |
136 | /**
137 | * @return int
138 | * @api
139 | */
140 | public function getHeight()
141 | {
142 | return $this->height;
143 | }
144 |
145 | /**
146 | * @param string $mime
147 | */
148 | public function setMime($mime)
149 | {
150 | $this->mime = $mime;
151 | }
152 |
153 | /**
154 | * @return string
155 | * @api
156 | */
157 | public function getMime()
158 | {
159 | return $this->mime;
160 | }
161 |
162 | /**
163 | * @param string $extension
164 | */
165 | public function setExtension($extension)
166 | {
167 | $this->extension = $extension;
168 | }
169 |
170 | /**
171 | * @return string
172 | * @api
173 | */
174 | public function getExtension()
175 | {
176 | return $this->extension;
177 | }
178 |
179 | /**
180 | * @param mixed $extras
181 | */
182 | public function addExtras(array $extras)
183 | {
184 | $this->extras = $extras + $this->extras;
185 | }
186 |
187 | /**
188 | * @return string
189 | * @api
190 | */
191 | public function getExtras()
192 | {
193 | return $this->extras;
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/src/ImageBuilder.php:
--------------------------------------------------------------------------------
1 |
15 | * @since 1.0.0
16 | */
17 | class ImageBuilder
18 | {
19 | /**
20 | * @var mixed[]
21 | */
22 | protected $options;
23 |
24 | /**
25 | * @var \Imagecraft\Layer\LayerInterface[]
26 | */
27 | protected $layers;
28 |
29 | /**
30 | * @param mixed[] $options
31 | */
32 | public function __construct(array $options = [])
33 | {
34 | $this->options = $options;
35 | $this->layers = [0 => null];
36 | }
37 |
38 | /**
39 | * @return BackgroundLayer
40 | * @api
41 | */
42 | public function addBackgroundLayer()
43 | {
44 | return $this->layers[0] = new BackgroundLayer($this);
45 | }
46 |
47 | /**
48 | * @return ImageLayer
49 | * @api
50 | */
51 | public function addImageLayer()
52 | {
53 | return $this->layers[] = new ImageLayer($this);
54 | }
55 |
56 | /**
57 | * @return TextLayer
58 | * @api
59 | */
60 | public function addTextLayer()
61 | {
62 | return $this->layers[] = new TextLayer($this);
63 | }
64 |
65 | /**
66 | * @return Image
67 | * @api
68 | */
69 | public function save()
70 | {
71 | try {
72 | $pass = new DelegatingOptionPass();
73 | $this->options = $pass->process($this->options);
74 | TranslatedException::init($this->options);
75 | TranslatedException::addResourceDir(__DIR__.'/Resources/translations');
76 | $pass = new DelegatingLayerPass();
77 | $this->layers = $pass->process($this->layers);
78 | $engine = new DelegatingEngine();
79 | $image = $engine->getImage($this->layers, $this->options);
80 | } catch (TranslatedException $e) {
81 | $image = new Image();
82 | $image->setMessage($e->getMessage());
83 | $image->setVerboseMessage($e->getVerboseMessage());
84 | }
85 | $this->layers = [0 => null];
86 |
87 | return $image;
88 | }
89 |
90 | /**
91 | * @return AbstractContext
92 | * @api
93 | */
94 | public function about()
95 | {
96 | $engine = new DelegatingEngine($options['engine']);
97 | $context = $engine->getContext($this->options);
98 |
99 | return $context;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/Layer/AbstractLayer.php:
--------------------------------------------------------------------------------
1 |
9 | * @since 1.0.0
10 | */
11 | abstract class AbstractLayer extends ParameterBag implements LayerInterface
12 | {
13 | /**
14 | * @param null|ImageBuilder $builder
15 | */
16 | public function __construct(ImageBuilder $builder = null)
17 | {
18 | $this->set('image_builder', $builder);
19 | }
20 |
21 | /**
22 | * @inheritDoc
23 | */
24 | public function done()
25 | {
26 | return $this->get('image_builder');
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Layer/BackgroundLayer.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | class BackgroundLayer extends AbstractLayer implements BackgroundLayerInterface
10 | {
11 | /**
12 | * @inheritDoc
13 | */
14 | public function http($url, $dataLimit = -1, $timeout = -1)
15 | {
16 | $this->add([
17 | 'image.http.url' => $url,
18 | 'image.http.data_limit' => $dataLimit,
19 | 'image.http.timeout' => $timeout,
20 | ]);
21 |
22 | return $this;
23 | }
24 |
25 | /**
26 | * @inheritDoc
27 | */
28 | public function filename($filename)
29 | {
30 | $this->set('image.filename', $filename);
31 |
32 | return $this;
33 | }
34 |
35 | /**
36 | * @inheritDoc
37 | */
38 | public function contents($contents)
39 | {
40 | $this->set('image.contents', $contents);
41 |
42 | return $this;
43 | }
44 |
45 | /**
46 | * @inheritDoc
47 | */
48 | public function resize($width, $height, $option = ImageAwareLayerInterface::RESIZE_SHRINK)
49 | {
50 | $this->add([
51 | 'image.resize.width' => $width,
52 | 'image.resize.height' => $height,
53 | 'image.resize.option' => $option,
54 | ]);
55 |
56 | return $this;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Layer/BackgroundLayerInterface.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | interface BackgroundLayerInterface extends ImageAwareLayerInterface
10 | {
11 | }
12 |
--------------------------------------------------------------------------------
/src/Layer/ImageAwareLayerInterface.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | interface ImageAwareLayerInterface extends LayerInterface
10 | {
11 | const RESIZE_SHRINK = 'shrink';
12 | const RESIZE_FILL_CROP = 'fill_crop';
13 |
14 | /**
15 | * @param string $url
16 | * @param int|float $dataLimit
17 | * @param int|float $timeout
18 | * @return $this
19 | * @api
20 | */
21 | public function http($url, $dataLimit = -1, $timeout = -1);
22 |
23 | /**
24 | * @param string $filename
25 | * @return $this
26 | * @api
27 | */
28 | public function filename($filename);
29 |
30 | /**
31 | * @param string $contents
32 | * @return $this
33 | * @api
34 | */
35 | public function contents($contents);
36 |
37 | /**
38 | * @param int $width
39 | * @param int $height
40 | * @param string $option
41 | * @return $this
42 | * @api
43 | */
44 | public function resize($width, $height, $option = self::RESIZE_SHRINK);
45 | }
46 |
--------------------------------------------------------------------------------
/src/Layer/ImageLayer.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | class ImageLayer extends AbstractLayer implements ImageLayerInterface
10 | {
11 | /**
12 | * @inheritDoc
13 | */
14 | public function http($url, $dataLimit = -1, $timeout = -1)
15 | {
16 | $this->add([
17 | 'image.http.url' => $url,
18 | 'image.http.data_limit' => $dataLimit,
19 | 'image.http.timeout' => $timeout,
20 | ]);
21 |
22 | return $this;
23 | }
24 |
25 | /**
26 | * @inheritDoc
27 | */
28 | public function filename($filename)
29 | {
30 | $this->set('image.filename', $filename);
31 |
32 | return $this;
33 | }
34 |
35 | /**
36 | * @inheritDoc
37 | */
38 | public function contents($contents)
39 | {
40 | $this->set('image.contents', $contents);
41 |
42 | return $this;
43 | }
44 |
45 | /**
46 | * @inheritDoc
47 | */
48 | public function resize($width, $height, $option = ImageAwareLayerInterface::RESIZE_SHRINK)
49 | {
50 | $this->add([
51 | 'image.resize.width' => $width,
52 | 'image.resize.height' => $height,
53 | 'image.resize.option' => $option,
54 | ]);
55 |
56 | return $this;
57 | }
58 |
59 | /**
60 | * @inheritDoc
61 | */
62 | public function move($x, $y, $gravity = RegularLayerInterface::MOVE_TOP_LEFT)
63 | {
64 | $this->add([
65 | 'regular.move.x' => $x,
66 | 'regular.move.y' => $y,
67 | 'regular.move.gravity' => $gravity,
68 | ]);
69 |
70 | return $this;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Layer/ImageLayerInterface.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | interface ImageLayerInterface extends RegularLayerInterface, ImageAwareLayerInterface
10 | {
11 | }
12 |
--------------------------------------------------------------------------------
/src/Layer/LayerInterface.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | interface LayerInterface extends ParameterBagInterface
10 | {
11 | /**
12 | * @return null|\Imagecraft\ImageBuilderInterface
13 | * @api
14 | */
15 | public function done();
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/Layer/ParameterBag.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | class ParameterBag implements ParameterBagInterface
10 | {
11 | /**
12 | * @inheritDoc
13 | */
14 | protected $parameters = [];
15 |
16 | /**
17 | * @inheritDoc
18 | */
19 | public function set($name, $value)
20 | {
21 | $this->parameters[$name] = $value;
22 | }
23 |
24 | /**
25 | * @inheritDoc
26 | */
27 | public function add(array $parameters)
28 | {
29 | $this->parameters = array_replace($this->parameters, $parameters);
30 | }
31 |
32 | /**
33 | * @inheritDoc
34 | */
35 | public function get($name)
36 | {
37 | return $this->has($name) ? $this->parameters[$name] : null;
38 | }
39 |
40 | /**
41 | * @inheritDoc
42 | */
43 | public function has($name)
44 | {
45 | return array_key_exists($name, $this->parameters);
46 | }
47 |
48 | /**
49 | * @inheritDoc
50 | */
51 | public function remove($name)
52 | {
53 | if ($this->has($name)) {
54 | unset($this->parameters[$name]);
55 | }
56 | }
57 |
58 | public function clear()
59 | {
60 | $this->parameters = [];
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Layer/ParameterBagInterface.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | interface ParameterBagInterface
10 | {
11 | /**
12 | * @param string $name
13 | * @param mixed $value
14 | */
15 | public function set($name, $value);
16 |
17 | /**
18 | * @param mixed[] $parameters
19 | */
20 | public function add(array $parameters);
21 |
22 | /**
23 | * @param string $name
24 | * @return mixed
25 | */
26 | public function get($name);
27 |
28 | /**
29 | * @param string $name
30 | * @return bool
31 | */
32 | public function has($name);
33 |
34 | /**
35 | * @param string $name
36 | */
37 | public function remove($name);
38 |
39 | public function clear();
40 | }
41 |
--------------------------------------------------------------------------------
/src/Layer/RegularLayerInterface.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | interface RegularLayerInterface extends LayerInterface
10 | {
11 | const MOVE_TOP_LEFT = 'top_left';
12 | const MOVE_TOP_CENTER = 'top_center';
13 | const MOVE_TOP_RIGHT = 'top_right';
14 | const MOVE_CENTER_LEFT = 'center_left';
15 | const MOVE_CENTER = 'center';
16 | const MOVE_CENTER_RIGHT = 'center_right';
17 | const MOVE_BOTTOM_LEFT = 'bottom_left';
18 | const MOVE_BOTTOM_CENTER = 'bottom_center';
19 | const MOVE_BOTTOM_RIGHT = 'bottom_right';
20 |
21 | /**
22 | * @param int $x
23 | * @param int $y
24 | * @param string $gravity
25 | * @return $this
26 | * @api
27 | */
28 | public function move($x, $y, $gravity = self::MOVE_TOP_LEFT);
29 | }
30 |
--------------------------------------------------------------------------------
/src/Layer/TextLayer.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | class TextLayer extends AbstractLayer implements TextLayerInterface
10 | {
11 | /**
12 | * @inheritDoc
13 | */
14 | public function font($filename, $size = 12, $color = '#FFF')
15 | {
16 | $this->add([
17 | 'text.font.filename' => $filename,
18 | 'text.font.size' => $size,
19 | 'text.font.hex_color' => $color,
20 | ]);
21 |
22 | return $this;
23 | }
24 |
25 | /**
26 | * @inheritDoc
27 | */
28 | public function label($label)
29 | {
30 | $this->set('text.label', $label);
31 |
32 | return $this;
33 | }
34 |
35 | /**
36 | * @inheritDoc
37 | */
38 | public function angle($angle)
39 | {
40 | $this->set('text.angle', $angle);
41 |
42 | return $this;
43 | }
44 |
45 | public function lineSpacing($lineSpacing)
46 | {
47 | $this->set('text.line_spacing', $lineSpacing);
48 |
49 | return $this;
50 | }
51 |
52 | /**
53 | * @inheritDoc
54 | */
55 | public function box(array $paddings, $color = null)
56 | {
57 | $this->add([
58 | 'text.box.paddings' => $paddings,
59 | 'text.box.hex_color' => $color,
60 | ]);
61 |
62 | return $this;
63 | }
64 |
65 | /**
66 | * @inheritDoc
67 | */
68 | public function move($x, $y, $gravity = RegularLayerInterface::MOVE_TOP_LEFT)
69 | {
70 | $this->add([
71 | 'regular.move.x' => $x,
72 | 'regular.move.y' => $y,
73 | 'regular.move.gravity' => $gravity,
74 | ]);
75 |
76 | return $this;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/Layer/TextLayerInterface.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | interface TextLayerInterface extends RegularLayerInterface
10 | {
11 | /**
12 | * @param string $filename
13 | * @param int $size
14 | * @param string $color
15 | * @return $this
16 | * @api
17 | */
18 | public function font($filename, $size = 12, $color = '#FFF');
19 |
20 | /**
21 | * @param string $label
22 | * @return $this
23 | * @api
24 | */
25 | public function label($label);
26 |
27 | /**
28 | * @param int $angle
29 | * @return $this
30 | * @api
31 | */
32 | public function angle($angle);
33 |
34 | /**
35 | * @param float|int $lineSpacing
36 | * @return $this
37 | * @api
38 | */
39 | public function lineSpacing($lineSpacing);
40 |
41 | /**
42 | * @param int[] $paddings
43 | * @param null|string $color
44 | * @return $this
45 | * @api
46 | */
47 | public function box(array $paddings, $color = null);
48 | }
49 |
--------------------------------------------------------------------------------
/src/LayerPass/AbstractLayerPass.php:
--------------------------------------------------------------------------------
1 |
9 | * @since 1.0.0
10 | */
11 | abstract class AbstractLayerPass implements LayerPassInterface
12 | {
13 | /**
14 | * @param string $color
15 | * @return mixed[]
16 | * @throws InvalidArgumentException
17 | */
18 | public function sanitizeHexColor($color)
19 | {
20 | if (!preg_match('/\\A#?([[:xdigit:]]{3}|[[:xdigit:]]{6})\\Z/', $color, $matches)) {
21 | throw new InvalidArgumentException(
22 | 'invalid.hex.color.%cp_invalid%.%example%',
23 | ['%cp_invalid%' => '"'.$color.'"', '%example%' => '"#CCC", "#F9F9F9"']
24 | );
25 | }
26 |
27 | $hex = strtoupper($matches[1]);
28 | if (3 === strlen($hex)) {
29 | $red = hexdec(substr($hex, 0, 1) . substr($hex, 0, 1));
30 | $green = hexdec(substr($hex, 1, 1) . substr($hex, 1, 1));
31 | $blue = hexdec(substr($hex, 2, 1) . substr($hex, 2, 1));
32 | } else {
33 | $red = hexdec(substr($hex, 0, 2));
34 | $green = hexdec(substr($hex, 2, 2));
35 | $blue = hexdec(substr($hex, 4, 2));
36 | }
37 |
38 | return ['hex' => '#'.$hex, 'rgb' => [$red, $green, $blue]];
39 | }
40 |
41 | /**
42 | * @param mixed $element
43 | * @param mixed[] $array
44 | * @return mixed
45 | * @throws InvalidArgumentException
46 | */
47 | public function sanitizeEnumeration($element, array $array)
48 | {
49 | if (!in_array($element, $array)) {
50 | throw new InvalidArgumentException(
51 | 'unexpected.argument.%cp_unexpected%.%expected%',
52 | [
53 | '%cp_unexpected%' => '"'.$element.'"',
54 | '%expected%' => implode(', ', preg_replace('/.+/', '"$0"', $array)),
55 | ]
56 | );
57 | }
58 |
59 | return $element;
60 | }
61 |
62 | /**
63 | * @param string $url
64 | * @return string
65 | */
66 | public function sanitizeUrl($url)
67 | {
68 | if (0 === preg_match("#https?://#", $url)) {
69 | $url = 'http://'.$url;
70 | }
71 |
72 | return $url;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/LayerPass/BackgroundLayerPass.php:
--------------------------------------------------------------------------------
1 |
9 | * @since 1.0.0
10 | */
11 | class BackgroundLayerPass extends AbstractLayerPass
12 | {
13 | /**
14 | * @inheritDoc
15 | */
16 | public function process(array $layers)
17 | {
18 | if (!isset($layers[0])) {
19 | throw new BadMethodCallException('no.background.layer.added');
20 | }
21 |
22 | return $layers;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/LayerPass/DelegatingLayerPass.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | class DelegatingLayerPass implements LayerPassInterface
10 | {
11 | /**
12 | * @inheritDoc
13 | */
14 | public function process(array $layers)
15 | {
16 | $passes = $this->getRegisteredPasses();
17 | foreach ($passes as $pass) {
18 | $layers = $pass->process($layers);
19 | }
20 |
21 | return $layers;
22 | }
23 |
24 | /**
25 | * return LayerPassInterface[]
26 | */
27 | protected function getRegisteredPasses()
28 | {
29 | return [
30 | new BackgroundLayerPass(),
31 | new RegularLayerPass(),
32 | new ImageAwareLayerPass(),
33 | new TextLayerPass(),
34 | ];
35 | }
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/src/LayerPass/ImageAwareLayerPass.php:
--------------------------------------------------------------------------------
1 |
11 | * @since 1.0.0
12 | */
13 | class ImageAwareLayerPass extends AbstractLayerPass
14 | {
15 | /**
16 | * @inheritDoc
17 | */
18 | public function process(array $layers)
19 | {
20 | foreach ($layers as $layer) {
21 | if (!($layer instanceof ImageAwareLayerInterface)) {
22 | continue;
23 | }
24 |
25 | $this->processResource($layer);
26 | $this->processResize($layer);
27 | }
28 |
29 | return $layers;
30 | }
31 |
32 | /**
33 | * @param ImageAwareLayerInterface $layer
34 | * @throws BadMethodCallException
35 | */
36 | public function processResource(ImageAwareLayerInterface $layer)
37 | {
38 | if ($layer->has('image.http.url')) {
39 | $url = (string) $layer->get('image.http.url');
40 | $url = $this->sanitizeUrl($url);
41 | $layer->set('image.http.url', $url);
42 | $limit = $layer->get('image.http.data_limit');
43 | $limit = (0 < $limit) ? (float) $limit : -1;
44 | $layer->set('image.http.data_limit', $limit);
45 | $timeout = $layer->get('image.http.timeout');
46 | $timeout = (0 < $timeout) ? (float) $timeout : -1;
47 | $layer->set('image.http.timeout', $timeout);
48 | } elseif ($layer->has('image.filename')) {
49 | $filename = (string) $layer->get('image.filename');
50 | $layer->set('image.filename', $filename);
51 | } elseif ($layer->has('image.contents')) {
52 | $contents = (string) $layer->get('image.contents');
53 | $layer->set('image.contents', $contents);
54 | } else {
55 | throw new BadMethodCallException('no.image.added');
56 | }
57 | }
58 |
59 | /**
60 | * @param ImageAwareLayerInterface $layer
61 | */
62 | public function processResize(ImageAwareLayerInterface $layer)
63 | {
64 | if ($layer->has('image.resize.width')) {
65 | $width = (int) $layer->get('image.resize.width');
66 | $width = (0 >= $width) ? 1 : $width;
67 | $layer->set('image.resize.width', $width);
68 |
69 | $height = (int) $layer->get('image.resize.height');
70 | $height = (0 >= $height) ? 1 : $height;
71 | $layer->set('image.resize.height', $height);
72 |
73 | $options = [
74 | ImageAwareLayerInterface::RESIZE_SHRINK,
75 | ImageAwareLayerInterface::RESIZE_FILL_CROP,
76 | ];
77 | $option = (string) $layer->get('image.resize.option');
78 | $option = $this->sanitizeEnumeration($option, $options);
79 | $layer->set('image.resize.option', $option);
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/LayerPass/LayerPassInterface.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | interface LayerPassInterface
10 | {
11 | /**
12 | * @param \Imagecraft\Layer\LayerInterface[] $layers
13 | * @return \Imagecraft\Layer\LayerInterface[]
14 | */
15 | public function process(array $layers);
16 | }
17 |
--------------------------------------------------------------------------------
/src/LayerPass/RegularLayerPass.php:
--------------------------------------------------------------------------------
1 |
10 | * @since 1.0.0
11 | */
12 | class RegularLayerPass extends AbstractLayerPass
13 | {
14 | /**
15 | * @inheritDoc
16 | */
17 | public function process(array $layers)
18 | {
19 | foreach ($layers as $layer) {
20 | if (!($layer instanceof RegularLayerInterface)) {
21 | continue;
22 | }
23 |
24 | $this->processMove($layer);
25 | }
26 |
27 | return $layers;
28 | }
29 |
30 | /**
31 | * @param RegularLayerInterface $layer
32 | */
33 | public function processMove(RegularLayerInterface $layer)
34 | {
35 | if (!$layer->has('regular.move.x')) {
36 | $layer->add([
37 | 'regular.move.x' => 0,
38 | 'regular.move.y' => 0,
39 | 'regular.move.gravity' => RegularLayerInterface::MOVE_CENTER,
40 | ]);
41 |
42 | return;
43 | }
44 |
45 | $x = (int) $layer->get('regular.move.x');
46 | $layer->set('regular.move.x', $x);
47 |
48 | $y = (int) $layer->get('regular.move.y');
49 | $layer->set('regular.move.y', $y);
50 |
51 | $gravity = (string) $layer->get('regular.move.gravity');
52 | $gravities = [
53 | RegularLayerInterface::MOVE_TOP_LEFT,
54 | RegularLayerInterface::MOVE_TOP_CENTER,
55 | RegularLayerInterface::MOVE_TOP_RIGHT,
56 | RegularLayerInterface::MOVE_CENTER_LEFT,
57 | RegularLayerInterface::MOVE_CENTER,
58 | RegularLayerInterface::MOVE_CENTER_RIGHT,
59 | RegularLayerInterface::MOVE_BOTTOM_LEFT,
60 | RegularLayerInterface::MOVE_BOTTOM_CENTER,
61 | RegularLayerInterface::MOVE_BOTTOM_RIGHT,
62 | ];
63 | $layer->set('regular.move.gravity', $gravity);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/LayerPass/TextLayerPass.php:
--------------------------------------------------------------------------------
1 |
11 | * @since 1.0.0
12 | */
13 | class TextLayerPass extends AbstractLayerPass
14 | {
15 | /**
16 | * @inheritDoc
17 | */
18 | public function process(array $layers)
19 | {
20 | foreach ($layers as $key => $layer) {
21 | if (!($layer instanceof TextLayerInterface)) {
22 | continue;
23 | }
24 | if ('' === $layer->get('text.label')) {
25 | unset($layers[$key]);
26 | continue;
27 | }
28 |
29 | $this->processFont($layer);
30 | $this->processLabel($layer);
31 | $this->processAngle($layer);
32 | $this->processLineSpacing($layer);
33 | $this->processBox($layer);
34 | }
35 |
36 | return $layers;
37 | }
38 |
39 | /**
40 | * @param TextLayerInterface $layer
41 | * @throws BadMethodCallException
42 | */
43 | public function processFont(TextLayerInterface $layer)
44 | {
45 | if (!$layer->has('text.font.filename')) {
46 | throw new BadMethodCallException('no.font.added');
47 | }
48 |
49 | $filename = (string) $layer->get('text.font.filename');
50 | $layer->set('text.font.filename', $filename);
51 |
52 | $size = (int) $layer->get('text.font.size');
53 | $size = (5 > $size) ? 5 : $size;
54 | $layer->set('text.font.size', $size);
55 |
56 | $color = (string) $layer->get('text.font.hex_color');
57 | $color = $this->sanitizeHexColor($color);
58 | $layer->set('text.font.hex_color', $color['hex']);
59 | $layer->set('text.font.rgb_color', $color['rgb']);
60 | }
61 |
62 | /**
63 | * @param TextLayerInterface $layer
64 | */
65 | public function processLabel(TextLayerInterface $layer)
66 | {
67 | if (!$layer->has('text.label')) {
68 | $label = '';
69 | } else {
70 | $label = (string) $layer->get('text.label');
71 | }
72 | $layer->set('text.label', $label);
73 | }
74 |
75 | /**
76 | * @param TextLayerInterface $layer
77 | */
78 | public function processAngle(TextLayerInterface $layer)
79 | {
80 | if (!$layer->has('text.angle')) {
81 | $angle = 0;
82 | } else {
83 | $angle = (int) $layer->get('text.angle');
84 | }
85 | $layer->set('text.angle', $angle);
86 | }
87 |
88 | /**
89 | * @param TextLayerInterface $layer
90 | */
91 | public function processLineSpacing(TextLayerInterface $layer)
92 | {
93 | if (!$layer->has('text.line_spacing')) {
94 | $lineSpacing = 0.5;
95 | } else {
96 | $lineSpacing = (float) $layer->get('text.line_spacing');
97 | }
98 | $layer->set('text.line_spacing', $lineSpacing);
99 | }
100 |
101 | /**
102 | * @param TextLayerInterface $layer
103 | */
104 | public function processBox(TextLayerInterface $layer)
105 | {
106 | if (!$layer->has('text.box.paddings')) {
107 | $layer->add([
108 | 'text.box.paddings' => [0, 0, 0, 0],
109 | 'text.box.hex_color' => null,
110 | ]);
111 |
112 | return;
113 | }
114 |
115 | $paddings = array_values($layer->get('text.box.paddings'));
116 | $arr = [];
117 | for ($i = 0; $i < 4; $i++) {
118 | if (!isset($paddings[$i]) || 0 > $paddings[$i]) {
119 | $arr[$i] = 0;
120 | } else {
121 | $arr[$i] = (int) $paddings[$i];
122 | }
123 | }
124 | $layer->set('text.box.paddings', $arr);
125 |
126 | if (null !== $color = $layer->get('text.box.hex_color')) {
127 | $color = $this->sanitizeHexColor($color);
128 | $layer->set('text.box.hex_color', $color['hex']);
129 | $layer->set('text.box.rgb_color', $color['rgb']);
130 | } else {
131 | $layer->set('text.box.rgb_color', null);
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/OptionPass/CacheDirOptionPass.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | class CacheDirOptionPass implements OptionPassInterface
10 | {
11 | /**
12 | * @inheritDoc
13 | */
14 | public function process(array $options)
15 | {
16 | if (!isset($options['cache_dir'])) {
17 | $options['cache_dir'] = null;
18 | }
19 |
20 | return $options;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/OptionPass/DebugOptionPass.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | class DebugOptionPass implements OptionPassInterface
10 | {
11 | /**
12 | * @inheritDoc
13 | */
14 | public function process(array $options)
15 | {
16 | if (!isset($options['debug'])) {
17 | $options['debug'] = true;
18 | }
19 |
20 | return $options;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/OptionPass/DelegatingOptionPass.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | class DelegatingOptionPass implements OptionPassInterface
10 | {
11 | /**
12 | * @inheritDoc
13 | */
14 | public function process(array $options)
15 | {
16 | $passes = $this->getRegisteredOptionPasses();
17 | foreach ($passes as $pass) {
18 | $options = $pass->process($options);
19 | }
20 |
21 | return $options;
22 | }
23 |
24 | /**
25 | * @return OptionPassInterface[]
26 | */
27 | protected function getRegisteredOptionPasses()
28 | {
29 | return [
30 | new CacheDirOptionPass(),
31 | new DebugOptionPass(),
32 | new EngineOptionPass(),
33 | new GifAnimationOptionPass(),
34 | new JpegQualityOptionPass(),
35 | new LocaleOptionPass(),
36 | new MemoryLimitOptionPass(),
37 | new OutputFormatOptionPass(),
38 | new PngCompressionOptionPass(),
39 | ];
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/OptionPass/EngineOptionPass.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | class EngineOptionPass implements OptionPassInterface
10 | {
11 | /**
12 | * @inheritDoc
13 | */
14 | public function process(array $options)
15 | {
16 | if (!isset($options['engine'])) {
17 | $options['engine'] = 'php_gd';
18 | }
19 |
20 | return $options;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/OptionPass/GifAnimationOptionPass.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | class GifAnimationOptionPass implements OptionPassInterface
10 | {
11 | /**
12 | * @inheritDoc
13 | */
14 | public function process(array $options)
15 | {
16 | if (!isset($options['gif_animation'])) {
17 | $options['gif_animation'] = true;
18 | }
19 |
20 | return $options;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/OptionPass/JpegQualityOptionPass.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | class JpegQualityOptionPass implements OptionPassInterface
10 | {
11 | /**
12 | * @inheritDoc
13 | */
14 | public function process(array $options)
15 | {
16 | if (
17 | !isset($options['jpeg_quality']) ||
18 | $options['jpeg_quality'] > 100 ||
19 | $options['jpeg_quality'] < 0
20 | ) {
21 | $options['jpeg_quality'] = 100;
22 | }
23 |
24 | return $options;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/OptionPass/LocaleOptionPass.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | class LocaleOptionPass implements OptionPassInterface
10 | {
11 | /**
12 | * @inheritDoc
13 | */
14 | public function process(array $options)
15 | {
16 | if (
17 | !isset($options['locale']) ||
18 | !file_exists(__DIR__.'/../Resources/translations/imagecraft.'.$options['locale'].'.xlf')
19 | ) {
20 | $options['locale'] = 'en';
21 | }
22 |
23 | return $options;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/OptionPass/MemoryLimitOptionPass.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | class MemoryLimitOptionPass implements OptionPassInterface
10 | {
11 | /**
12 | * @inheritDoc
13 | */
14 | public function process(array $options)
15 | {
16 | if (!isset($options['memory_limit'])) {
17 | $options['memory_limit'] = -10;
18 | }
19 |
20 | return $options;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/OptionPass/OptionPassInterface.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | interface OptionPassInterface
10 | {
11 | /**
12 | * @param mixed[] $options
13 | * @return mixed[]
14 | */
15 | public function process(array $options);
16 | }
17 |
--------------------------------------------------------------------------------
/src/OptionPass/OutputFormatOptionPass.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | class OutputFormatOptionPass implements OptionPassInterface
10 | {
11 | /**
12 | * @inheritDoc
13 | */
14 | public function process(array $options)
15 | {
16 | if (!isset($options['output_format'])) {
17 | $options['output_format'] = 'default';
18 | }
19 |
20 | return $options;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/OptionPass/PngCompressionOptionPass.php:
--------------------------------------------------------------------------------
1 |
7 | * @since 1.0.0
8 | */
9 | class PngCompressionOptionPass implements OptionPassInterface
10 | {
11 | /**
12 | * @inheritDoc
13 | */
14 | public function process(array $options)
15 | {
16 | if (
17 | !isset($options['png_compression']) ||
18 | $options['png_compression'] > 100 ||
19 | $options['png_compression'] < 0
20 | ) {
21 | $options['png_compression'] = 100;
22 | }
23 |
24 | return $options;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Resources/translations/imagecraft.en.xlf:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | invalid.hex.color.%cp_invalid%.%example%
10 | %cp_invalid% does not appear to be a valid hex color, here is an example: %example%.
11 |
12 |
13 | unexpected.argument.%cp_unexpected%.%expected%
14 | Expects argument to be %expected%, but received %cp_unexpected%.
15 |
16 |
17 |
18 |
19 | no.background.layer.added
20 | You haven't added a background layer yet.
21 |
22 |
23 |
24 |
25 | no.image.added
26 | You haven't added an image yet.
27 |
28 |
29 |
30 |
31 | no.font.added
32 | You haven't added a font yet.
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | image.corrupted
41 | The image file is corrputed and cannot be opened.
42 |
43 |
44 | image.process.error
45 | An error encountered while processing image.
46 |
47 |
48 | text.adding.error
49 | An error encountered while adding text to image.
50 |
51 |
52 |
53 |
54 | unsupported.image.format.or.file.corrupted.%unsupported%.%supported%
55 | The format %unsupported% is either not supported or the file is damaged. Server supports %supported%.
56 |
57 |
58 | unknown.image.format.or.file.corrupted.%supported%
59 | The image is either in unknown format or corrupted. Server supports %supported%.
60 |
61 |
62 |
63 |
64 | adding.text.not.supported
65 | Adding text to image is not supported by server.
66 |
67 |
68 |
69 |
70 | gd.extension.not.enabled
71 | PHP GD extension is not enabled.
72 |
73 |
74 | output.image.format.not.supported.%cp_unsupported%
75 | The output image format %cp_unsupported% is not supported.
76 |
77 |
78 |
79 |
80 | output.image.dimensions.exceed.limit.%cp_dimensions%
81 | The output image dimensions (%cp_dimensions%) is bigger than the server allows.
82 |
83 |
84 | image.dimensions.exceed.limit.%cp_dimensions%
85 | The image dimensions (%cp_dimensions%) exceed the server allowable limit.
86 |
87 |
88 | not.enough.memory.to.process.image
89 | There is not enough memory to process the image.
90 |
91 |
92 |
93 |
94 | gif.parse.error
95 | An error occured while parsing GIF file.
96 |
97 |
98 |
99 |
100 |
101 | gif.animation.may.lost.due.to.corrupted.frame.data
102 | GIF animation may be lost. The frame data may be damaged or corrupted.
103 |
104 |
105 |
106 |
107 | gif.animation.may.lost.as.too.many.frames.or.dimensions.too.large.%total_frames%.%dimensions%
108 | GIF animation may be lost, as it either has too many frames (%total_frames%) or dimensions (%dimensions%) are too large.
109 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/src/Resources/translations/imagecraft.zh_CN.xlf:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | invalid.hex.color.%cp_invalid%.%example%
10 | %cp_invalid% 不是一个正确的十六进制颜色,正确的格式如:%example%。
11 |
12 |
13 | unexpected.argument.%cp_unexpected%.%expected%
14 | 未知参数 %cp_unexpected%,正确的有 %expected%。
15 |
16 |
17 |
18 |
19 | no.background.layer.added
20 | 您还未添加背景图层。
21 |
22 |
23 |
24 |
25 | no.image.added
26 | 您还未为图层添加图片文件。
27 |
28 |
29 |
30 |
31 | no.font.added
32 | 您还未为图层添加字体文件。
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | image.corrupted
41 | 图片文件已损坏,请尝试上传一张新的图片。
42 |
43 |
44 | image.process.error
45 | 处理图片时发生错误
46 |
47 |
48 | text.adding.error
49 | 写入文字时发生错误。
50 |
51 |
52 |
53 |
54 | unsupported.image.format.or.file.corrupted.%unsupported%.%supported%
55 | %unsupported% 格式不被支持或文件已损坏,服务器支持的图片格式有:%supported%。
56 |
57 |
58 | unknown.image.format.or.file.corrupted.%supported%
59 | 图片格式未知或文件已损坏。
60 |
61 |
62 |
63 |
64 | adding.text.not.supported
65 | 添加文字功能不支持。
66 |
67 |
68 |
69 |
70 | gd.extension.not.enabled
71 | PHP GD 扩展还未开启。
72 |
73 |
74 | output.image.format.not.supported.%cp_unsupported%
75 | 输出的图片格式 %cp_unsupported% 无法支持。
76 |
77 |
78 |
79 |
80 | output.image.dimensions.exceed.limit.%cp_dimensions%
81 | 输出的图片尺寸 (%cp_dimensions%) 超出服务器可承受范围。
82 |
83 |
84 | image.dimensions.exceed.limit.%cp_dimensions%
85 | 图片尺寸 (%cp_dimensions%) 超出服务器可承受范围。
86 |
87 |
88 | not.enough.memory.to.process.image
89 | 没有足够的内存来处理您的图片。
90 |
91 |
92 |
93 |
94 | gif.parse.error
95 | 解析 GIF 文件时发生错误。
96 |
97 |
98 |
99 |
100 |
101 | gif.animation.may.lost.due.to.corrupted.frame.data
102 | GIF 动态效果丢失。文件已损坏或数据丢失。
103 |
104 |
105 |
106 |
107 | gif.animation.may.lost.as.too.many.frames.or.dimensions.too.large.%total_frames%.%dimensions%
108 | GIF 动态效果丢失,原因为帧数 (%total_frames%) 太多或图片尺寸 (%dimensions%) 太大。
109 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/src/Resources/translations/imagecraft.zh_TW.xlf:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | no.background.layer
10 | 還未添加背景圖層。
11 |
12 |
13 |
14 |
15 | invalid.hex.color.%invalid_color%.%example_color%
16 | 您輸入的顏色 %invalid_color% 不是一個合法的十六進制顏色,正確的格式如: %example_color%。
17 |
18 |
19 | unexpected.argument.%unexpected_argument%.%expected_arguments%
20 | 未知參數 %unexpected_argument%, 請從以下選擇一個可用的參數: %expected_arguments%。
21 |
22 |
23 | file.not.found.%filename%
24 | 無法找到文件 %filename%。
25 |
26 |
27 | file.access.denied.%filename%
28 | 您沒有權限打開文件 %filename%。
29 |
30 |
31 | url.fopen.not.enabled
32 | 服務器不支持 URL 打開文件。
33 |
34 |
35 | malformed.url.%url%
36 | 您輸入的 URL %url% 格式不正確。
37 |
38 |
39 | url.resource.not.found.%url%
40 | 無法從 URL %url% 獲取資源。
41 |
42 |
43 |
44 |
45 | no.image.added
46 | 您還未添加圖片文件。
47 |
48 |
49 |
50 |
51 | no.font.added
52 | 您還未添加字體文件。
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | unsupported.image.format.%unsupported_format%.%supported_formats%
61 | 圖片格式 %unsupported_format% 不支持,支持的格式有:%supported_formats%。
62 |
63 |
64 | unknown.image.format.or.file.corrupted.%supported_formats%
65 | 文件格式未知或者文件已損壞,服務器支持的圖片格式有:%supported_formats%。
66 |
67 |
68 |
69 |
70 | text.not.supported
71 | 服務器不支持寫入文字。
72 |
73 |
74 |
75 |
76 | gd.extension.not.enabled
77 | PHP GD 擴展還未安裝或啟動。
78 |
79 |
80 | saved.image.format.not.supported.%format%
81 | 保存的圖片格式 %format% 不支持。
82 |
83 |
84 |
85 |
86 | image.corrupted
87 | 圖片文件以損壞。
88 |
89 |
90 | processing.image.error
91 | 無法處理圖片。
92 |
93 |
94 | writing.text.error.%valid_font%
95 | 寫入文字時發生錯誤,原因可能為您所使用的字體文件不被支持。服務器支持的字體文件格式有:%valid_font%。
96 |
97 |
98 |
99 |
100 |
101 | gif.animation.lost.one.or.more.frames.corrupted
102 | GIF 動態效果丟失,原因為一個或多個幀的數據已損壞。
103 |
104 |
105 | not.animated.gif
106 | 非動態GIF。
107 |
108 |
109 |
110 |
111 | gif.animation.lost.too.many.frames.or.dimension.too.large.%frames%.%width%.%height%
112 | GIF 動態效果丟失,原因為帧數太多(%frames%) 或尺寸太大(%width% x %height%)。
113 |
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/tests/AbstractContextTest.php:
--------------------------------------------------------------------------------
1 | context = $this->getMockForAbstractClass('Imagecraft\\AbstractContext');
15 | }
16 |
17 | public function testGetMemoryLimit()
18 | {
19 | $previous = ini_get('memory_limit');
20 |
21 | ini_set('memory_limit', '40M');
22 | $this->assertEquals(40 * 1024 * 1024, $this->context->getMemoryLimit());
23 |
24 | ini_set('memory_limit', '-1');
25 | $this->assertEquals(1024 * 1024 * 1024, $this->context->getMemoryLimit());
26 |
27 | ini_set('memory_limit', '40M');
28 | $this->assertEquals(35 * 1024 * 1024, $this->context->getMemoryLimit(-5));
29 |
30 | ini_set('memory_limit', '40M');
31 | $this->assertEquals(35 * 1024 * 1024, $this->context->getMemoryLimit(35));
32 |
33 | ini_set('memory_limit', $previous);
34 | }
35 |
36 | /*
37 | * @requires extension fileinfo
38 | */
39 | public function testIsFileinfoExtensionEnabled()
40 | {
41 | $this->assertTrue($this->context->isFileinfoExtensionEnabled());
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/Engine/DelegatingEngineTest.php:
--------------------------------------------------------------------------------
1 | engine = $this->getMock('Imagecraft\\Engine\\DelegatingEngine', ['getRegisteredEngines']);
17 | $this->engine
18 | ->method('getRegisteredEngines')
19 | ->will($this->returnValue(['foo' => new FooEngine()]))
20 | ;
21 | }
22 |
23 | public function testGetImage()
24 | {
25 | $this->assertEquals('foo', $this->engine->getImage([], ['engine' => 'foo']));
26 | }
27 |
28 | public function testGetContext()
29 | {
30 | $this->assertEquals('bar', $this->engine->getContext(['engine' => 'foo']));
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/Engine/Fixtures/FooEngine.php:
--------------------------------------------------------------------------------
1 | getMock('Symfony\\Component\\EventDispatcher\\EventDispatcherInterface');
13 | $dispatcher
14 | ->expects($this->atLeastOnce())
15 | ->method('addSubscriber')
16 | ;
17 | $extension = $this->getMock('Imagecraft\\Engine\\PhpGd\\Extension\\Core\\CoreExtension', null);
18 | $extension->boot($dispatcher);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/Extension/Core/EventListener/BackgroundLayerListenerTest.php:
--------------------------------------------------------------------------------
1 | listener = $this->getMock('Imagecraft\\Engine\\PhpGd\\Extension\\Core\\EventListener\\BackgroundLayerListener', null);
19 | $this->layer = $this->getMock('Imagecraft\\Layer\\BackgroundLayer', null);
20 | $this->event = $this->getMock('Imagecraft\\Engine\\PhpGd\\PhpGdEvent', [], [], '', false);
21 | $this->event
22 | ->method('getLayers')
23 | ->will($this->returnValue([$this->layer]))
24 | ;
25 | }
26 |
27 | public function testInitFinalFormat()
28 | {
29 | $this->event
30 | ->method('getOptions')
31 | ->will($this->returnValue(['output_format' => 'default']))
32 | ;
33 | $this->layer->add(['image.format' => 'png']);
34 | $this->listener->initFinalFormat($this->event);
35 | $this->assertInternalType('string', $this->layer->get('final.format'));
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/Extension/Core/EventListener/ImageAwareLayerListenerTest.php:
--------------------------------------------------------------------------------
1 | getMock('Imagecraft\\Engine\\PhpGd\\PhpGdContext', null);
22 | $info = $this->getMock('Imagecraft\\Engine\\PhpGd\\Extension\\Core\\ImageInfo', null, [$context]);
23 | $rh = $this->getMock('Imagecraft\\Engine\\PhpGd\\Helper\\ResourceHelper', null);
24 | $this->listener = $this->getMock(
25 | 'Imagecraft\\Engine\PhpGd\\Extension\\Core\\EventListener\\ImageAwareLayerListener',
26 | null,
27 | [$info, $rh]
28 | );
29 | $this->layer = $this->getMock('Imagecraft\\Layer\\ImageLayer', null);
30 | $this->event = $this->getMock('Imagecraft\\Engine\\PhpGd\\PhpGdEvent', [], [], '', false);
31 | $this->event
32 | ->method('getLayers')
33 | ->will($this->returnValue([$this->layer]))
34 | ;
35 | }
36 |
37 | public function testInitImcUri()
38 | {
39 | if (!ini_get('allow_url_fopen') || !$r = @fsockopen('8.8.8.8', 53, $e, $r, 1)) {
40 | $this->markTestSkipped('No internet connection or allow_url_fopen is not enabled.');
41 | }
42 |
43 | $this->layer->add([
44 | 'image.http.url' => 'http://www.example.com',
45 | 'image.http.data_limit' => 500,
46 | 'image.http.timeout' => 20,
47 | ]);
48 | $this->listener->initImcUri($this->event);
49 | $this->assertNotEmpty($this->layer->get('image.imc_uri'));
50 |
51 | $this->layer->clear();
52 | $this->layer->add(['image.filename' => __FILE__]);
53 | $this->listener->initImcUri($this->event);
54 | $this->assertNotEmpty($this->layer->get('image.imc_uri'));
55 | }
56 |
57 | public function testInitFilePointer()
58 | {
59 | if (!ini_get('allow_url_fopen') || !$r = @fsockopen('8.8.8.8', 53, $e, $r, 1)) {
60 | $this->markTestSkipped('No internet connection or allow_url_fopen is not enabled.');
61 | }
62 |
63 | $this->layer->add(['image.imc_uri' => 'http://www.example.com']);
64 | $this->listener->initFilePointer($this->event);
65 | $this->assertInternalType('resource', $this->layer->get('image.fp'));
66 |
67 | $this->layer->clear();
68 | $this->layer->add(['image.contents' => file_get_contents(__FILE__)]);
69 | $this->listener->initFilePointer($this->event);
70 | $this->assertInternalType('resource', $this->layer->get('image.fp'));
71 | }
72 |
73 | public function testInitImageInfo()
74 | {
75 | $this->layer->set('image.fp', fopen(__DIR__.'/../../../../../Fixtures/gif_87a_palette_250x297.gif', 'rb'));
76 | $this->listener->initImageInfo($this->event);
77 | $this->assertInternalType('integer', $this->layer->get('image.width'));
78 | $this->assertInternalType('integer', $this->layer->get('image.height'));
79 | $this->assertInternalType('string', $this->layer->get('image.format'));
80 | }
81 |
82 | public function testInitFinalDimensions()
83 | {
84 | $this->layer->add([
85 | 'image.width' => 200,
86 | 'image.height' => 200,
87 | 'image.resize.width' => 100,
88 | 'image.resize.height' => 100,
89 | 'image.resize.option' => ImageAwareLayerInterface::RESIZE_FILL_CROP,
90 | ]);
91 | $this->listener->initFinalDimensions($this->event);
92 | $this->assertInternalType('integer', $this->layer->get('final.width'));
93 | $this->assertInternalType('integer', $this->layer->get('final.height'));
94 | }
95 |
96 | /**
97 | * @depends testInitFilePointer
98 | */
99 | public function testTermFilePointer()
100 | {
101 | $this->layer->add(['image.contents' => file_get_contents(__FILE__)]);
102 | $this->listener->initFilePointer($this->event);
103 | $this->listener->termFilePointer($this->event);
104 | $this->assertFalse($this->layer->has('image.fp'));
105 | }
106 |
107 | /**
108 | * @depends testInitImcUri
109 | */
110 | public function testTermImcUri()
111 | {
112 | if (!ini_get('allow_url_fopen') || !$r = @fsockopen('8.8.8.8', 53, $e, $r, 1)) {
113 | $this->markTestSkipped('No internet connection or allow_url_fopen is not enabled.');
114 | }
115 |
116 | $this->layer->add([
117 | 'image.http.url' => 'http://www.example.com',
118 | 'image.http.data_limit' => 500,
119 | 'image.http.timeout' => 20,
120 | ]);
121 | $this->listener->initImcUri($this->event);
122 | $this->listener->termImcUri($this->event);
123 | $this->assertFalse($this->layer->has('image.imc_uri'));
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/Extension/Core/EventListener/ImageFactoryListenerTest.php:
--------------------------------------------------------------------------------
1 | factory = $this->getMock('Imagecraft\\Engine\\PhpGd\\Extension\\Core\\ImageFactory', [], [], '', false);
19 | $this->listener = $this->getMock(
20 | 'Imagecraft\\Engine\PhpGd\\Extension\\Core\\EventListener\\ImageFactoryListener',
21 | null,
22 | [$this->factory]
23 | );
24 | $this->event = $this->getMock('Imagecraft\\Engine\\PhpGd\\PhpGdEvent', [], [], '', false);
25 | }
26 |
27 | public function testCreateImage()
28 | {
29 | $image = $this->getMock('Imagecraft\\Image', null);
30 | $this->factory
31 | ->expects($this->once())
32 | ->method('createImage')
33 | ->will($this->returnValue($image))
34 | ;
35 | $this->event
36 | ->expects($this->once())
37 | ->method('setImage')
38 | ->with($image)
39 | ;
40 | $this->event
41 | ->expects($this->once())
42 | ->method('getLayers')
43 | ->will($this->returnValue([]))
44 | ;
45 | $this->event
46 | ->expects($this->once())
47 | ->method('getOptions')
48 | ->will($this->returnValue([]))
49 | ;
50 | $this->listener->createImage($this->event);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/Extension/Core/EventListener/ImageMetadataListenerTest.php:
--------------------------------------------------------------------------------
1 | getMock('Imagecraft\\Engine\\PhpGd\\PhpGdContext', null);
23 | $this->listener = $this->getMock(
24 | 'Imagecraft\\Engine\PhpGd\\Extension\\Core\\EventListener\\ImageMetadataListener',
25 | null,
26 | [$context]
27 | );
28 | $this->layer = $this->getMock('Imagecraft\\Layer\\ImageLayer', null);
29 | $this->image = $this->getMock('Imagecraft\\Image', null);
30 | $this->event = $this->getMock('Imagecraft\\Engine\\PhpGd\\PhpGdEvent', [], [], '', false);
31 | $this->event
32 | ->method('getLayers')
33 | ->will($this->returnValue([$this->layer]))
34 | ;
35 | $this->event
36 | ->method('getImage')
37 | ->will($this->returnValue($this->image))
38 | ;
39 | }
40 |
41 | public function testAddImageMetadatas()
42 | {
43 | $this->layer->add([
44 | 'final.format' => PhpGdContext::FORMAT_JPEG,
45 | 'final.width' => 100,
46 | 'final.height' => 200,
47 | 'image.width' => 300,
48 | 'image.height' => 400,
49 | ]);
50 | $this->listener->addImageMetadatas($this->event);
51 | $this->assertInternalType('string', $this->image->getMime());
52 | $this->assertInternalType('string', $this->image->getExtension());
53 | $this->assertEquals(100, $this->image->getWidth());
54 | $this->assertEquals(200, $this->image->getHeight());
55 | $this->assertEquals(300, $this->image->getExtras()['original_width']);
56 | $this->assertEquals(400, $this->image->getExtras()['original_height']);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/Extension/Core/EventListener/MemoryRequirementListenerTest.php:
--------------------------------------------------------------------------------
1 | getMock('Imagecraft\\Engine\\PhpGd\\PhpGdContext', null);
23 | $this->listener = $this->getMock(
24 | 'Imagecraft\\Engine\PhpGd\\Extension\\Core\\EventListener\\MemoryRequirementListener',
25 | null,
26 | [$context]
27 | );
28 | $this->layer = $this->getMock('Imagecraft\\Layer\\BackgroundLayer', null);
29 | $this->event = $this->getMock('Imagecraft\\Engine\\PhpGd\\PhpGdEvent', [], [], '', false);
30 | $this->event
31 | ->method('getLayers')
32 | ->will($this->returnValue([$this->layer]))
33 | ;
34 | $this->image = $this->getMock('Imagecraft\\Image', null);
35 | $this->event
36 | ->method('getImage')
37 | ->will($this->returnValue($this->image))
38 | ;
39 | }
40 |
41 | /**
42 | * @expectedException Imagecraft\Exception\RuntimeException
43 | */
44 | public function testVerifyMemoryLimit()
45 | {
46 | $this->event
47 | ->method('getOptions')
48 | ->will($this->returnValue(30 * 1024 * 1024))
49 | ;
50 | $this->layer->add([
51 | 'image.format' => PhpGdContext::FORMAT_JPEG,
52 | 'image.width' => 1000000,
53 | 'image.height' => 1000000,
54 | 'final.width' => 200,
55 | 'final.height' => 200,
56 | ]);
57 | $this->listener->verifyMemoryLimit($this->event);
58 | }
59 |
60 | public function testAddImageExtras()
61 | {
62 | $this->event
63 | ->method('getOptions')
64 | ->will($this->returnValue(50 * 1024 * 1024))
65 | ;
66 | $this->layer->add([
67 | 'image.format' => PhpGdContext::FORMAT_JPEG,
68 | 'image.width' => 100,
69 | 'image.height' => 100,
70 | 'final.width' => 100,
71 | 'final.height' => 100,
72 | ]);
73 | $this->listener->verifyMemoryLimit($this->event);
74 | $this->listener->addImageExtras($this->event);
75 | $this->assertNotEmpty($this->image->getExtras()['memory_approx']);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/Extension/Core/EventListener/SystemRequirementListenerTest.php:
--------------------------------------------------------------------------------
1 | context = $this->getMock('Imagecraft\\Engine\\PhpGd\\PhpGdContext', []);
19 | $this->listener = $this->getMock(
20 | 'Imagecraft\\Engine\PhpGd\\Extension\\Core\\EventListener\\SystemRequirementListener',
21 | null,
22 | [$this->context]
23 | );
24 | $this->event = $this->getMock('Imagecraft\\Engine\\PhpGd\\PhpGdEvent', [], [], '', false);
25 | }
26 |
27 | /**
28 | * @expectedException Imagecraft\Exception\RuntimeException
29 | */
30 | public function testVerifyEngine()
31 | {
32 | $this->context
33 | ->expects($this->once())
34 | ->method('isEngineSupported')
35 | ->will($this->returnValue(false))
36 | ;
37 | $this->listener->verifyEngine();
38 | }
39 |
40 | /**
41 | * @expectedException Imagecraft\Exception\InvalidArgumentException
42 | */
43 | public function testVerifySavedFormatWhenFormatIsInvalid()
44 | {
45 | $this->context
46 | ->expects($this->once())
47 | ->method('isImageFormatSupported')
48 | ->will($this->returnValue(false))
49 | ;
50 | $this->event
51 | ->expects($this->once())
52 | ->method('getOptions')
53 | ->will($this->returnValue(['output_format' => 'foo']))
54 | ;
55 | $this->listener->verifySavedFormat($this->event);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/Extension/Core/EventListener/TextLayerListenerTest.php:
--------------------------------------------------------------------------------
1 | context = $this->getMock('Imagecraft\\Engine\\PhpGd\\PhpGdContext');
21 | $this->listener = $this->getMock(
22 | 'Imagecraft\\Engine\PhpGd\\Extension\\Core\\EventListener\\TextLayerListener',
23 | null,
24 | [$this->context]
25 | );
26 | $this->layer = $this->getMock('Imagecraft\\Layer\\TextLayer', null);
27 | $this->event = $this->getMock('Imagecraft\\Engine\\PhpGd\\PhpGdEvent', [], [], '', false);
28 | $this->event
29 | ->method('getLayers')
30 | ->will($this->returnValue([$this->layer]))
31 | ;
32 | }
33 |
34 | /**
35 | * @expectedException Imagecraft\Exception\RuntimeException
36 | */
37 | public function testVerifyFreeType()
38 | {
39 | $this->context
40 | ->expects($this->once())
41 | ->method('isFreeTypeSupported')
42 | ->will($this->returnValue(false))
43 | ;
44 | $this->listener->verifyFreeType($this->event);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/Extension/Core/ImageFactoryTest.php:
--------------------------------------------------------------------------------
1 | getMock('Imagecraft\\Engine\\PhpGd\\Helper\\ResourceHelper', null);
29 | $this->factory = $this->getMock('Imagecraft\\Engine\\PhpGd\\Extension\\Core\\ImageFactory', null, [$rh]);
30 | }
31 |
32 | /**
33 | * @dataProvider layerDataProvider
34 | */
35 | public function testCreateImage(array $layers, array $options, $outputName)
36 | {
37 | $image = $this->factory->createImage($layers, $options);
38 | file_put_contents(__DIR__.'/TestOutput/'.$outputName, $image->getContents());
39 | }
40 |
41 | public function layerDataProvider()
42 | {
43 | $outputName1 = 'image_factory_image_should_be_valid_01.png';
44 | $options1 = ['png_quality' => 100, 'jpeg_compression' => 100];
45 | $layers1 = [];
46 | $layers1[0] = new BackgroundLayer();
47 | $layers1[0]->add([
48 | 'image.imc_uri' => __DIR__.'/../../../../Fixtures/gif_89a_palette_alpha_animated_339x473.gif',
49 | 'image.format' => PhpGdContext::FORMAT_GIF,
50 | 'image.width' => 339,
51 | 'image.height' => 473,
52 | 'image.resize.width' => 500,
53 | 'image.resize.height' => 500,
54 | 'image.resize.option' => ImageAwareLayerInterface::RESIZE_FILL_CROP,
55 | 'final.width' => 500,
56 | 'final.height' => 500,
57 | 'final.format' => PhpGdContext::FORMAT_PNG,
58 | ]);
59 | $layers1[1] = new ImageLayer();
60 | $layers1[1]->add([
61 | 'image.contents' => file_get_contents(__DIR__.'/../../../../Fixtures/gif_89a_palette_animated_375x225.gif'),
62 | 'image.width' => 375,
63 | 'image.height' => 225,
64 | 'image.format' => PhpGdContext::FORMAT_GIF,
65 | 'image.resize.width' => 50,
66 | 'image.resize.height' => 50,
67 | 'image.resize.option' => ImageAwareLayerInterface::RESIZE_FILL_CROP,
68 | 'regular.move.x' => 10,
69 | 'regular.move.y' => 10,
70 | 'regular.move.gravity' => RegularLayerInterface::MOVE_TOP_LEFT,
71 | ]);
72 | $layers1[2] = new TextLayer();
73 | $layers1[2]->add([
74 | 'text.font.filename' => __DIR__.'/../../../../Fixtures/pfa_truecolor_alpha.pfa',
75 | 'text.font.size' => 25,
76 | 'text.font.rgb_color' => [255, 255, 255],
77 | 'text.label' => 'Hello World',
78 | 'text.angle' => 30,
79 | 'text.lineSpacing' => 1,
80 | 'text.box.paddings' => [0, 0, 0, 0],
81 | 'regular.move.x' => 0,
82 | 'regular.move.y' => 0,
83 | 'regular.move.gravity' => RegularLayerInterface::MOVE_CENTER,
84 | ]);
85 |
86 | $outputName2 = 'image_factory_image_should_be_valid_02.gif';
87 | $options2 = ['png_quality' => 100, 'jpeg_compression' => 100];
88 | $layers2 = [];
89 | $layers2[0] = new BackgroundLayer();
90 | $layers2[0]->add([
91 | 'image.imc_uri' => __DIR__.'/../../../../Fixtures/jpeg_exif_truecolor_480x360.jpg',
92 | 'image.format' => PhpGdContext::FORMAT_JPEG,
93 | 'image.width' => 480,
94 | 'image.height' => 360,
95 | 'image.resize.width' => 432,
96 | 'image.resize.height' => 324,
97 | 'image.resize.option' => ImageAwareLayerInterface::RESIZE_SHRINK,
98 | 'final.width' => 432,
99 | 'final.height' => 324,
100 | 'final.format' => PhpGdContext::FORMAT_GIF,
101 | ]);
102 | $layers2[1] = new ImageLayer();
103 | $layers2[1]->add([
104 | 'image.contents' => file_get_contents(__DIR__.'/../../../../Fixtures/webp_vp8_lossy_truecolor_550x368.webp'),
105 | 'image.width' => 550,
106 | 'image.height' => 368,
107 | 'image.format' => PhpGdContext::FORMAT_WEBP,
108 | 'image.resize.width' => 50,
109 | 'image.resize.height' => 50,
110 | 'image.resize.option' => ImageAwareLayerInterface::RESIZE_FILL_CROP,
111 | 'regular.move.x' => 10,
112 | 'regular.move.y' => 10,
113 | 'regular.move.gravity' => RegularLayerInterface::MOVE_TOP_LEFT,
114 | ]);
115 | $layers2[2] = new TextLayer();
116 | $layers2[2]->add([
117 | 'text.font.filename' => __DIR__.'/../../../../Fixtures/pfa_truecolor_alpha.pfa',
118 | 'text.font.size' => 25,
119 | 'text.font.rgb_color' => [255, 255, 255],
120 | 'text.label' => 'Hello World',
121 | 'text.angle' => 30,
122 | 'text.lineSpacing' => 1,
123 | 'text.box.paddings' => [0, 0, 0, 0],
124 | 'regular.move.x' => 0,
125 | 'regular.move.y' => 0,
126 | 'regular.move.gravity' => RegularLayerInterface::MOVE_CENTER,
127 | ]);
128 |
129 | return [
130 | [$layers1, $options1, $outputName1],
131 | [$layers2, $options2, $outputName2],
132 | ];
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/Extension/Core/ImageInfoTest.php:
--------------------------------------------------------------------------------
1 | context = $this->getMock('Imagecraft\\Engine\\PhpGd\\PhpGdContext', []);
19 | $this->info = $this->getMock('Imagecraft\\Engine\\PhpGd\\Extension\\Core\\ImageInfo', null, [$this->context]);
20 | }
21 |
22 | /**
23 | * @dataProvider imageDataProvider
24 | */
25 | public function testResolveFromFilePointer($filename, $format, $width, $height)
26 | {
27 | $this->context
28 | ->expects($this->atLeastOnce())
29 | ->method('isImageFormatSupported')
30 | ->will($this->returnValue(true))
31 | ;
32 | $fp = fopen($filename, 'rb');
33 | $info = $this->info->resolveFromFilePointer($fp);
34 | $this->assertEquals($format, $info['format']);
35 | $this->assertEquals($width, $info['width']);
36 | $this->assertEquals($height, $info['height']);
37 | }
38 |
39 | /**
40 | * @depends testResolveFromFilePointer
41 | * @dataProvider imageDataProvider
42 | */
43 | public function testResolveFromStream($filename, $format, $width, $height)
44 | {
45 | $this->context
46 | ->expects($this->atLeastOnce())
47 | ->method('isImageFormatSupported')
48 | ->will($this->returnValue(true))
49 | ;
50 | $info = $this->info->resolveFromStream($filename);
51 | $this->assertEquals($format, $info['format']);
52 | $this->assertEquals($width, $info['width']);
53 | $this->assertEquals($height, $info['height']);
54 | }
55 |
56 | /**
57 | * @depends testResolveFromFilePointer
58 | * @dataProvider imageDataProvider
59 | */
60 | public function testResolveFromContents($filename, $format, $width, $height)
61 | {
62 | $this->context
63 | ->expects($this->atLeastOnce())
64 | ->method('isImageFormatSupported')
65 | ->will($this->returnValue(true))
66 | ;
67 | $info = $this->info->resolveFromContents(file_get_contents($filename));
68 | $this->assertEquals($format, $info['format']);
69 | $this->assertEquals($width, $info['width']);
70 | $this->assertEquals($height, $info['height']);
71 | }
72 |
73 | /**
74 | * @requires extension fileinfo
75 | * @depends testResolveFromFilePointer
76 | * @expectedException Imagecraft\Exception\InvalidImageException
77 | */
78 | public function testResolveInvalidImage()
79 | {
80 | $this->context
81 | ->expects($this->atLeastOnce())
82 | ->method('isFileinfoExtensionEnabled')
83 | ->will($this->returnValue(true))
84 | ;
85 | $fp = fopen(__FILE__, 'r');
86 | $this->info->resolveFromFilePointer($fp);
87 | }
88 |
89 | /**
90 | * @depends testResolveFromFilePointer
91 | * @dataProvider invalidWebpDataProvider
92 | * @expectedException Imagecraft\Exception\InvalidImageException
93 | */
94 | public function testResolveInvalidWebp($filename)
95 | {
96 | $this->info->resolveFromFilePointer(fopen($filename, 'rb'));
97 | }
98 |
99 | public function imageDataProvider()
100 | {
101 | return [
102 | [__DIR__.'/../../../../Fixtures/webp_vp8_lossy_truecolor_550x368.webp', PhpGdContext::FORMAT_WEBP, 550, 368],
103 | [__DIR__.'/../../../../Fixtures/gif_87a_palette_250x297.gif', PhpGdContext::FORMAT_GIF, 250, 297],
104 | [__DIR__.'/../../../../Fixtures/png_truecolor_alpha_300x395.png', PhpGdContext::FORMAT_PNG, 300, 395],
105 | [__DIR__.'/../../../../Fixtures/png_palette_alpha_3000x1174.png', PhpGdContext::FORMAT_PNG, 3000, 1174],
106 | [__DIR__.'/../../../../Fixtures/jpeg_jfjf_truecolor_1920x758.jpg', PhpGdContext::FORMAT_JPEG, 1920, 758],
107 | [__DIR__.'/../../../../Fixtures/jpeg_jfjf_grayscale_480x361.jpg', PhpGdContext::FORMAT_JPEG, 480, 361],
108 | [__DIR__.'/../../../../Fixtures/jpeg_exif_truecolor_480x360.jpg', PhpGdContext::FORMAT_JPEG, 480, 360],
109 | [__DIR__.'/../../../../Fixtures/jpeg_jfjf_sos_truecolor_1920x1200.jpg', PhpGdContext::FORMAT_JPEG, 1920, 1200],
110 | ];
111 | }
112 |
113 | public function invalidWebpDataProvider()
114 | {
115 | return [
116 | [__DIR__.'/../../../../Fixtures/webp_vp8l_lossless_truecolor_alpha_800x600.webp'],
117 | [__DIR__.'/../../../../Fixtures/webp_vp8x_lossy_truecolor_alpha_421x163.webp'],
118 | ];
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/Extension/Core/TestOutput/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/Extension/Gif/EventListener/GifExtractorListenerTest.php:
--------------------------------------------------------------------------------
1 | getMock('Imagecraft\\Engine\\PhpGd\\Extension\\Gif\\GifExtractor', null);
23 | $this->listener = $this->getMock(
24 | 'Imagecraft\\Engine\\PhpGd\\Extension\\Gif\\EventListener\\GifExtractorListener',
25 | null,
26 | [$extractor]
27 | );
28 | $this->layer = $this->getMock('Imagecraft\\Layer\\BackgroundLayer', null);
29 | $this->layer->add(['final.format' => PhpGdContext::FORMAT_GIF, 'image.format' => PhpGdContext::FORMAT_GIF]);
30 | $this->event = $this->getMock('Imagecraft\\Engine\\PhpGd\\PhpGdEvent', [], [], '', false);
31 | $this->event
32 | ->method('getLayers')
33 | ->will($this->returnValue([$this->layer]))
34 | ;
35 | $this->image = $this->getMock('Imagecraft\\Image', null);
36 | $this->event
37 | ->method('getImage')
38 | ->will($this->returnValue($this->image))
39 | ;
40 | }
41 |
42 | /**
43 | * @dataProvider validAnimatedGifDataProvider
44 | */
45 | public function testInitExtracted($filename)
46 | {
47 | $this->event
48 | ->method('getOptions')
49 | ->will($this->returnValue(['gif_animation' => true]))
50 | ;
51 | $this->layer->set('image.fp', fopen($filename, 'rb'));
52 | $this->listener->initExtracted($this->event);
53 | $this->assertTrue($this->layer->has('gif.extracted'));
54 | }
55 |
56 | /**
57 | * @dataProvider invalidAnimatedGifDataProvider
58 | */
59 | public function testInitExtractedWhenGifIsInvalid($filename)
60 | {
61 | $this->event
62 | ->method('getOptions')
63 | ->will($this->returnValue(['gif_animation' => true]))
64 | ;
65 | $this->layer->set('image.fp', fopen($filename, 'rb'));
66 | $this->listener->initExtracted($this->event);
67 | $this->assertFalse($this->layer->has('gif.extracted'));
68 | }
69 |
70 | /**
71 | * @depends testInitExtractedWhenGifIsInvalid
72 | * @dataProvider corruptedAnimatedGifDataProvider
73 | */
74 | public function testAddImageExtras($filename)
75 | {
76 | $this->event
77 | ->method('getOptions')
78 | ->will($this->returnValue(['gif_animation' => true]))
79 | ;
80 | $this->layer->set('image.fp', fopen($filename, 'rb'));
81 | $this->listener->initExtracted($this->event);
82 | $this->listener->addImageExtras($this->event);
83 | $this->assertNotEmpty($this->image->getExtras()['gif_error']);
84 | }
85 |
86 | public function validAnimatedGifDataProvider()
87 | {
88 | return [
89 | [__DIR__.'/../../../../../Fixtures/gif_89a_palette_alpha_animated_339x473.gif'],
90 | ];
91 | }
92 |
93 | public function invalidAnimatedGifDataProvider()
94 | {
95 | return [
96 | [__DIR__.'/../../../../../Fixtures/gif_87a_palette_250x297.gif'],
97 | [__DIR__.'/../../../../../Fixtures/gif_89a_palette_alpha_206x205.gif'],
98 | [__DIR__.'/../../../../../Fixtures/zz_gif_89a_palette_animated_corrupted_data_153x120.gif'],
99 | [__DIR__.'/../../../../../Fixtures/jpeg_jfjf_truecolor_1920x758.jpg'],
100 | ];
101 | }
102 |
103 | public function corruptedAnimatedGifDataProvider()
104 | {
105 | return [
106 | [__DIR__.'/../../../../../Fixtures/zz_gif_89a_palette_animated_corrupted_data_153x120.gif'],
107 | ];
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/Extension/Gif/EventListener/ImageFactoryListenerTest.php:
--------------------------------------------------------------------------------
1 | factory = $this->getMock('Imagecraft\\Engine\\PhpGd\\Extension\\Gif\\ImageFactory', [], [], '', false);
23 | $this->listener = $this->getMock(
24 | 'Imagecraft\\Engine\\PhpGd\\Extension\\Gif\\EventListener\\ImageFactoryListener',
25 | null,
26 | [$this->factory]
27 | );
28 | $this->layer = $this->getMock('Imagecraft\\Layer\\BackgroundLayer', null);
29 | $this->event = $this->getMock('Imagecraft\\Engine\\PhpGd\\PhpGdEvent', [], [], '', false);
30 | $this->event
31 | ->method('getLayers')
32 | ->will($this->returnValue([$this->layer]))
33 | ;
34 | $this->image = $this->getMock('Imagecraft\\Image', null);
35 | $this->event
36 | ->method('getImage')
37 | ->will($this->returnValue($this->image))
38 | ;
39 | }
40 |
41 | public function testCreateImage()
42 | {
43 | $this->event
44 | ->expects($this->once())
45 | ->method('getOptions')
46 | ->will($this->returnValue([]))
47 | ;
48 | $this->factory
49 | ->expects($this->once())
50 | ->method('createImage')
51 | ->will($this->returnValue($this->image))
52 | ;
53 | $this->event
54 | ->expects($this->once())
55 | ->method('setImage')
56 | ->with($this->image)
57 | ;
58 | $this->layer->add(['gif.extracted' => []]);
59 | $this->listener->createImage($this->event);
60 | }
61 |
62 | public function testAddImageExtras()
63 | {
64 | $this->event
65 | ->expects($this->once())
66 | ->method('getOptions')
67 | ->will($this->returnValue([]))
68 | ;
69 | $this->factory
70 | ->expects($this->once())
71 | ->method('createImage')
72 | ->will($this->throwException(new \Exception()))
73 | ;
74 | $this->layer->add(['gif.extracted' => []]);
75 | $this->listener->createImage($this->event);
76 | $this->listener->addImageExtras($this->event);
77 | $this->assertNotEmpty($this->image->getExtras()['gif_error']);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/Extension/Gif/EventListener/MemoryRequirementListenerTest.php:
--------------------------------------------------------------------------------
1 | context = $this->getMock('Imagecraft\\Engine\\PhpGd\\PhpGdContext', null);
23 | $this->listener = $this->getMock(
24 | 'Imagecraft\\Engine\\PhpGd\\Extension\\Gif\\EventListener\\MemoryRequirementListener',
25 | null,
26 | [$this->context]
27 | );
28 | $this->layer = $this->getMock('Imagecraft\\Layer\\BackgroundLayer', null);
29 | $this->event = $this->getMock('Imagecraft\\Engine\\PhpGd\\PhpGdEvent', [], [], '', false);
30 | $this->event
31 | ->method('getLayers')
32 | ->will($this->returnValue([$this->layer]))
33 | ;
34 | $this->image = $this->getMock('Imagecraft\\Image', null);
35 | $this->event
36 | ->method('getImage')
37 | ->will($this->returnValue($this->image))
38 | ;
39 | }
40 |
41 | public function testVerifyMemoryLimitWhenImageIsCompatible()
42 | {
43 | $this->event
44 | ->expects($this->once())
45 | ->method('getOptions')
46 | ->will($this->returnValue(['memory_limit' => '120']))
47 | ;
48 | $this->layer->add([
49 | 'gif.extracted' => [0, 0, 0, 0],
50 | 'image.width' => 100,
51 | 'image.height' => 100,
52 | 'final.width' => 10,
53 | 'final.height' => 10,
54 | ]);
55 | $this->listener->verifyMemoryLimit($this->event);
56 | $this->listener->addImageExtras($this->event);
57 | $this->assertArrayHasKey('memory_approx', $this->image->getExtras());
58 | }
59 |
60 | public function testVerifyMemoryLimitWhenImageIsIncompatible()
61 | {
62 | $this->event
63 | ->expects($this->once())
64 | ->method('getOptions')
65 | ->will($this->returnValue(['memory_limit' => '120']))
66 | ;
67 | $this->layer->add([
68 | 'gif.extracted' => [0, 0, 0, 0],
69 | 'image.width' => 10000000000000000000000,
70 | 'image.height' => 10000000000000000000000,
71 | 'final.width' => 10,
72 | 'final.height' => 10,
73 | ]);
74 | $this->listener->verifyMemoryLimit($this->event);
75 | $this->listener->addImageExtras($this->event);
76 | $this->assertArrayHasKey('gif_error', $this->image->getExtras());
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/Extension/Gif/GifBuilderPlusTest.php:
--------------------------------------------------------------------------------
1 | extractor = $this->getMock('Imagecraft\\Engine\\PhpGd\\Extension\\Gif\\GifExtractor', null);
17 | $this->builder = $this->getMock('Imagecraft\\Engine\\PhpGd\\Extension\\Gif\\GifBuilderPlus', null);
18 | }
19 |
20 | /**
21 | * @dataProvider gifDataProvider
22 | */
23 | public function testGetContents($filename, $outputName)
24 | {
25 | $extracted = $this->extractor->extractFromStream($filename);
26 |
27 | $this->builder
28 | ->canvasWidth($extracted->getCanvasWidth())
29 | ->canvasHeight($extracted->getCanvasHeight())
30 | ->loop($extracted->getTotalLoops())
31 | ;
32 | for ($i = 0; $i < count($extracted); $i++) {
33 | $this->builder->addFrame();
34 | $this->builder
35 | ->imageWidth($extracted->getImageWidth())
36 | ->imageHeight($extracted->getImageHeight())
37 | ->imageLeft($extracted->getImageLeft())
38 | ->imageTop($extracted->getImageTop())
39 | ->dispose($extracted->getDisposalMethod())
40 | ->delayTime($extracted->getDelayTime())
41 | ->interlaceFlag($extracted->getInterlaceFlag())
42 | ->colorTable($extracted->getColorTable())
43 | ->imageData($extracted->getImageData())
44 | ;
45 | if ($extracted->getTransparentColorFlag()) {
46 | $this->builder->transparentColorIndex($extracted->getTransparentColorIndex());
47 | }
48 | $extracted->next();
49 | }
50 | file_put_contents(__DIR__.'/TestOutput/'.$outputName, $this->builder->getContents());
51 | }
52 |
53 | public function gifDataProvider()
54 | {
55 | return [
56 | [__DIR__.'/../../../../Fixtures/gif_89a_palette_alpha_animated_339x473.gif', 'gif_builder_plus_image_should_be_animated_01.gif'],
57 | ];
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/Extension/Gif/GifBuilderTest.php:
--------------------------------------------------------------------------------
1 | extractor = $this->getMock('Imagecraft\\Engine\\PhpGd\\Extension\\Gif\\GifExtractor', null);
15 | $this->builder = $this->getMock('Imagecraft\\Engine\\PhpGd\\Extension\\Gif\\GifBuilder', null);
16 | }
17 |
18 | /**
19 | * @dataProvider gifDataProvider
20 | */
21 | public function testGetContents($filename, $outputName)
22 | {
23 | @mkdir(__DIR__.'/TestOutput/'.$outputName);
24 | $extracted = $this->extractor->extractFromStream($filename);
25 | foreach ($extracted as $key => $frame) {
26 | $this->builder
27 | ->imageWidth($extracted->getImageWidth())
28 | ->imageHeight($extracted->getImageHeight())
29 | ->colorTable($extracted->getColorTable())
30 | ->interlaceFlag($extracted->getInterlaceFlag())
31 | ->imageData($extracted->getImageData())
32 | ;
33 | if ($extracted->getTransparentColorFlag()) {
34 | $index = $extracted->getTransparentColorIndex();
35 | $this->builder->transparentColorIndex($index);
36 | }
37 | $contents = $this->builder->getContents();
38 | file_put_contents(__DIR__.'/TestOutput/'.$outputName.'/'.$key.'.gif', $contents);
39 | }
40 | }
41 |
42 | public function gifDataProvider()
43 | {
44 | return [
45 | [__DIR__.'/../../../../Fixtures/gif_89a_palette_alpha_animated_339x473.gif', 'gif_builder_frames_should_be_valid_01'],
46 | ];
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/Extension/Gif/GifExtensionTest.php:
--------------------------------------------------------------------------------
1 | getMock('Symfony\\Component\\EventDispatcher\\EventDispatcherInterface');
13 | $dispatcher
14 | ->expects($this->atLeastOnce())
15 | ->method('addSubscriber')
16 | ;
17 | $extension = $this->getMock('Imagecraft\\Engine\\PhpGd\\Extension\\Gif\\GifExtension', null);
18 | $extension->boot($dispatcher);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/Extension/Gif/GifExtractedTest.php:
--------------------------------------------------------------------------------
1 | extractor = $this->getMock('Imagecraft\\Engine\\PhpGd\\Extension\\Gif\\GifExtractor', null);
15 | }
16 |
17 | /**
18 | * @dataProvider gifDataProvider
19 | */
20 | public function testGettersForAnimatedGif($filename, $specs)
21 | {
22 | $fp = fopen($filename, 'rb');
23 | $extracted = $this->extractor->extractFromFilePointer($fp);
24 | $this->assertTrue($extracted->isValid());
25 | $this->assertTrue($extracted->isAnimated());
26 |
27 | foreach ($extracted as $key => $frame) {
28 | if (0 === $key) {
29 | continue;
30 | }
31 | $this->assertNotEmpty($extracted->getImageData());
32 | $this->assertNotEmpty($extracted->getColorTable());
33 | }
34 |
35 | $extracted->seek(2);
36 | $this->assertEquals($specs['total_frames'], count($extracted));
37 | $this->assertEquals($specs['canvas_width'], $extracted->getCanvasWidth());
38 | $this->assertEquals($specs['canvas_height'], $extracted->getCanvasHeight());
39 | $this->assertEquals($specs['global_flag'], $extracted->getGlobalColorTableFlag());
40 | if ($specs['global_flag']) {
41 | $this->assertEquals($specs['total_globals'], $extracted->getTotalGlobalColors());
42 | }
43 | $this->assertEquals($specs['total_loops'], $extracted->getTotalLoops());
44 | $this->assertEquals($specs['disposal_method'], $extracted->getDisposalMethod());
45 | $this->assertEquals($specs['transparent_flag'], $extracted->getTransparentColorFlag());
46 | if ($specs['transparent_flag']) {
47 | $this->assertEquals($specs['transparent_index'], $extracted->getTransparentColorIndex());
48 | }
49 | $this->assertEquals($specs['delay_time'], $extracted->getDelayTime());
50 | $this->assertEquals($specs['image_left'], $extracted->getImageLeft());
51 | $this->assertEquals($specs['image_top'], $extracted->getImageTop());
52 | $this->assertEquals($specs['image_width'], $extracted->getImageWidth());
53 | $this->assertEquals($specs['image_height'], $extracted->getImageHeight());
54 | $this->assertEquals($specs['local_flag'], $extracted->getLocalColorTableFlag());
55 | if ($specs['local_flag']) {
56 | $this->assertEquals($specs['total_locals'], $extracted->getTotalLocalColors());
57 | }
58 | $this->assertEquals($specs['interlace_flag'], $extracted->getInterlaceFlag());
59 | $this->assertNotEmpty($extracted->getLinkedKey());
60 | $this->assertNotEmpty($extracted->getLinkedDisposalMethod());
61 | }
62 |
63 | public function gifDataProvider()
64 | {
65 | $filename1 = __DIR__.'/../../../../Fixtures/gif_89a_palette_alpha_animated_339x473.gif';
66 | $specs1 = [
67 | 'total_frames' => 19,
68 | 'canvas_width' => 339,
69 | 'canvas_height' => 473,
70 | 'global_flag' => true,
71 | 'total_globals' => 256,
72 | 'total_loops' => 0,
73 | 'disposal_method' => 2,
74 | 'transparent_flag' => true,
75 | 'transparent_index' => 53,
76 | 'delay_time' => 10,
77 | 'image_left' => 0,
78 | 'image_top' => 0,
79 | 'image_width' => 339,
80 | 'image_height' => 473,
81 | 'local_flag' => false,
82 | 'interlace_flag' => false,
83 | ];
84 | return [
85 | [$filename1, $specs1],
86 | ];
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/Extension/Gif/GifExtractorTest.php:
--------------------------------------------------------------------------------
1 | extractor = $this->getMock('Imagecraft\\Engine\\PhpGd\\Extension\\Gif\\GifExtractor', null);
15 | }
16 |
17 | /**
18 | * @dataProvider imageDataProvider
19 | */
20 | public function testExtractFromFilePointer($filename, $validity)
21 | {
22 | $fp = fopen($filename, 'rb');
23 | $extracted = $this->extractor->extractFromFilePointer($fp);
24 | $this->assertEquals($validity, $extracted->isValid());
25 | }
26 |
27 | /**
28 | * @depends testExtractFromFilePointer
29 | * @dataProvider imageDataProvider
30 | */
31 | public function testExtractFromStream($filename, $validity)
32 | {
33 | $extracted = $this->extractor->extractFromStream($filename, $validity);
34 | $this->assertEquals($validity, $extracted->isValid());
35 | }
36 |
37 | /**
38 | * @depends testExtractFromFilePointer
39 | * @dataProvider imageDataProvider
40 | */
41 | public function testExtractFromContents($filename, $validity)
42 | {
43 | $extracted = $this->extractor->extractFromContents(file_get_contents($filename), $validity);
44 | $this->assertEquals($validity, $extracted->isValid());
45 | }
46 |
47 | public function imageDataProvider()
48 | {
49 | return [
50 | [__DIR__.'/../../../../Fixtures/gif_89a_palette_alpha_animated_339x473.gif', true],
51 | [__DIR__.'/../../../../Fixtures/gif_87a_palette_250x297.gif', true],
52 | [__DIR__.'/../../../../Fixtures/zz_gif_89a_palette_animated_no_graphic_control_550x296.gif', true],
53 | [__DIR__.'/../../../../Fixtures/zz_gif_89a_palette_animated_corrupted_data_153x120.gif', false],
54 | [__DIR__.'/../../../../Fixtures/png_palette_alpha_3000x1174.png', false],
55 | ];
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/Extension/Gif/GifOptimizerTest.php:
--------------------------------------------------------------------------------
1 | getMock('Imagecraft\\Engine\\PhpGd\\Helper\\ResourceHelper', null);
17 | $optimizer = $this->getMock('Imagecraft\\Engine\\PhpGd\\Extension\\Gif\\GifOptimizer', null, [$rh]);
18 | $resource1 = imagecreatefromjpeg($image);
19 | $resource2 = imagecreatefromjpeg($image);
20 | $resource = $optimizer->getOptimizedGdResource($resource1, $resource2);
21 | imagegif($resource, __DIR__.'/TestOutput/'.$outputName);
22 | imagedestroy($resource);
23 | imagedestroy($resource2);
24 | }
25 |
26 | public function imageDataProvider()
27 | {
28 | return [
29 | [__DIR__.'/../../../../Fixtures/jpeg_exif_truecolor_480x360.jpg', 'gif_optimizer_image_should_be_blank_01.gif'],
30 | ];
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/Extension/Gif/TestOutput/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/Extension/Save/EventListener/ImageFactoryListenerTest.php:
--------------------------------------------------------------------------------
1 | factory = $this->getMock('Imagecraft\\Engine\\PhpGd\\Extension\\Save\\ImageFactory', [], [], '', false);
21 | $this->listener = $this->getMock(
22 | 'Imagecraft\\Engine\PhpGd\\Extension\\Save\\EventListener\\ImageFactoryListener',
23 | null,
24 | [$this->factory]
25 | );
26 | $this->layer = $this->getMock('Imagecraft\\Layer\\ImageLayer', null);
27 | $this->event = $this->getMock('Imagecraft\\Engine\\PhpGd\\PhpGdEvent', [], [], '', false);
28 | $this->event
29 | ->method('getLayers')
30 | ->will($this->returnValue([$this->layer]))
31 | ;
32 | $this->event
33 | ->expects($this->once())
34 | ->method('getOptions')
35 | ->will($this->returnValue([]))
36 | ;
37 | }
38 |
39 | public function testCreateImage()
40 | {
41 | $this->layer->add([
42 | 'image.width' => 10,
43 | 'image.height' => 100,
44 | 'final.width' => 10,
45 | 'final.height' => 100,
46 | 'image.format' => 'foo',
47 | 'final.format' => 'foo',
48 | ]);
49 | $image = $this->getMock('Imagecraft\\Image', null);
50 | $this->factory
51 | ->expects($this->once())
52 | ->method('createImage')
53 | ->will($this->returnValue($image))
54 | ;
55 | $this->event
56 | ->expects($this->once())
57 | ->method('setImage')
58 | ->with($image)
59 | ;
60 | $this->listener->createImage($this->event);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/Extension/Save/ImageFactoryTest.php:
--------------------------------------------------------------------------------
1 | getMock('Imagecraft\\Engine\\PhpGd\\Helper\\ResourceHelper', null);
24 | $this->factory = $this->getMock('Imagecraft\\Engine\\PhpGd\\Extension\\Save\\ImageFactory', null, [$rh]);
25 | }
26 |
27 | /**
28 | * @dataProvider layerDataProvider
29 | */
30 | public function testCreateImage(array $layers, array $options, $outputName)
31 | {
32 | $image = $this->factory->createImage($layers, $options);
33 | file_put_contents(__DIR__.'/TestOutput/'.$outputName, $image->getContents());
34 | }
35 |
36 | public function layerDataProvider()
37 | {
38 | $outputName1 = 'image_factory_image_should_be_valid_01.gif';
39 | $options1 = [];
40 | $layers1 = [];
41 | $layers1[0] = new BackgroundLayer();
42 | $layers1[0]->add([
43 | 'image.imc_uri' => __DIR__.'/../../../../Fixtures/gif_89a_palette_alpha_animated_339x473.gif',
44 | 'image.format' => PhpGdContext::FORMAT_GIF,
45 | ]);
46 |
47 | $outputName2 = 'image_factory_image_should_be_valid_02.jpg';
48 | $options2 = [];
49 | $layers2 = [];
50 | $layers2[0] = new BackgroundLayer();
51 | $layers2[0]->add([
52 | 'image.imc_uri' => __DIR__.'/../../../../Fixtures/jpeg_exif_truecolor_480x360.jpg',
53 | 'image.format' => PhpGdContext::FORMAT_JPEG,
54 | ]);
55 |
56 | return [
57 | [$layers1, $options1, $outputName1],
58 | [$layers2, $options2, $outputName2],
59 | ];
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/Extension/Save/SaveExtensionTest.php:
--------------------------------------------------------------------------------
1 | getMock('Symfony\\Component\\EventDispatcher\\EventDispatcherInterface');
13 | $dispatcher
14 | ->expects($this->atLeastOnce())
15 | ->method('addSubscriber')
16 | ;
17 | $extension = $this->getMock('Imagecraft\\Engine\\PhpGd\\Extension\\Save\\SaveExtension', null);
18 | $extension->boot($dispatcher);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/Extension/Save/TestOutput/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/Helper/TestOutput/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/tests/Engine/PhpGd/PhpGdContextTest.php:
--------------------------------------------------------------------------------
1 | context = $this->getMock('Imagecraft\\Engine\\PhpGd\\PhpGdContext', null);
22 | }
23 |
24 | public function testIsImageFormatSupported()
25 | {
26 | $this->assertTrue($this->context->isImageFormatSupported(PhpGdContext::FORMAT_WEBP));
27 | $this->assertTrue($this->context->isImageFormatSupported(PhpGdContext::FORMAT_PNG));
28 | $this->assertTrue($this->context->isImageFormatSupported(PhpGdContext::FORMAT_JPEG));
29 | $this->assertTrue($this->context->isImageFormatSupported(PhpGdContext::FORMAT_GIF));
30 | $this->assertFalse($this->context->isImageFormatSupported('foo'));
31 | }
32 |
33 | public function testGetSupportedImageFormatsToString()
34 | {
35 | $this->assertEquals('"WEBP (VP8)", "PNG", "JPEG", "GIF"', $this->context->getSupportedImageFormatsToString());
36 | }
37 |
38 | public function testGetImageMime()
39 | {
40 | $this->assertEquals('image/webp', $this->context->getImageMime(PhpGdContext::FORMAT_WEBP));
41 | }
42 |
43 | public function testGetImageExtension()
44 | {
45 | $this->assertEquals('webp', $this->context->getImageExtension(PhpGdContext::FORMAT_WEBP));
46 | }
47 |
48 | public function testIsEngineSupported()
49 | {
50 | $this->assertTrue($this->context->isEngineSupported());
51 | }
52 |
53 | public function testIsFreeTypeSupported()
54 | {
55 | $this->assertTrue($this->context->isFreeTypeSupported());
56 | }
57 |
58 | public function testGetSupportedFontFormatsToString()
59 | {
60 | $this->assertInternalType('string', $this->context->getSupportedFontFormatsToString());
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/tests/Fixtures/gif_87a_palette_250x297.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coldume/imagecraft/2c9ab5174ebae6733673f14b01a776c1eda66489/tests/Fixtures/gif_87a_palette_250x297.gif
--------------------------------------------------------------------------------
/tests/Fixtures/gif_89a_palette_alpha_206x205.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coldume/imagecraft/2c9ab5174ebae6733673f14b01a776c1eda66489/tests/Fixtures/gif_89a_palette_alpha_206x205.gif
--------------------------------------------------------------------------------
/tests/Fixtures/gif_89a_palette_alpha_animated_339x473.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coldume/imagecraft/2c9ab5174ebae6733673f14b01a776c1eda66489/tests/Fixtures/gif_89a_palette_alpha_animated_339x473.gif
--------------------------------------------------------------------------------
/tests/Fixtures/gif_89a_palette_animated_375x225.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coldume/imagecraft/2c9ab5174ebae6733673f14b01a776c1eda66489/tests/Fixtures/gif_89a_palette_animated_375x225.gif
--------------------------------------------------------------------------------
/tests/Fixtures/jpeg_exif_truecolor_480x360.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coldume/imagecraft/2c9ab5174ebae6733673f14b01a776c1eda66489/tests/Fixtures/jpeg_exif_truecolor_480x360.jpg
--------------------------------------------------------------------------------
/tests/Fixtures/jpeg_jfjf_grayscale_480x361.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coldume/imagecraft/2c9ab5174ebae6733673f14b01a776c1eda66489/tests/Fixtures/jpeg_jfjf_grayscale_480x361.jpg
--------------------------------------------------------------------------------
/tests/Fixtures/jpeg_jfjf_sos_truecolor_1920x1200.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coldume/imagecraft/2c9ab5174ebae6733673f14b01a776c1eda66489/tests/Fixtures/jpeg_jfjf_sos_truecolor_1920x1200.jpg
--------------------------------------------------------------------------------
/tests/Fixtures/jpeg_jfjf_truecolor_1920x758.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coldume/imagecraft/2c9ab5174ebae6733673f14b01a776c1eda66489/tests/Fixtures/jpeg_jfjf_truecolor_1920x758.jpg
--------------------------------------------------------------------------------
/tests/Fixtures/pfa_truecolor_alpha.pfa:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coldume/imagecraft/2c9ab5174ebae6733673f14b01a776c1eda66489/tests/Fixtures/pfa_truecolor_alpha.pfa
--------------------------------------------------------------------------------
/tests/Fixtures/png_palette_alpha_3000x1174.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coldume/imagecraft/2c9ab5174ebae6733673f14b01a776c1eda66489/tests/Fixtures/png_palette_alpha_3000x1174.png
--------------------------------------------------------------------------------
/tests/Fixtures/png_truecolor_alpha_300x395.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coldume/imagecraft/2c9ab5174ebae6733673f14b01a776c1eda66489/tests/Fixtures/png_truecolor_alpha_300x395.png
--------------------------------------------------------------------------------
/tests/Fixtures/webp_vp8_lossy_truecolor_550x368.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coldume/imagecraft/2c9ab5174ebae6733673f14b01a776c1eda66489/tests/Fixtures/webp_vp8_lossy_truecolor_550x368.webp
--------------------------------------------------------------------------------
/tests/Fixtures/webp_vp8l_lossless_truecolor_alpha_800x600.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coldume/imagecraft/2c9ab5174ebae6733673f14b01a776c1eda66489/tests/Fixtures/webp_vp8l_lossless_truecolor_alpha_800x600.webp
--------------------------------------------------------------------------------
/tests/Fixtures/webp_vp8x_lossy_truecolor_alpha_421x163.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coldume/imagecraft/2c9ab5174ebae6733673f14b01a776c1eda66489/tests/Fixtures/webp_vp8x_lossy_truecolor_alpha_421x163.webp
--------------------------------------------------------------------------------
/tests/Fixtures/zz_gif_89a_palette_animated_corrupted_data_153x120.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coldume/imagecraft/2c9ab5174ebae6733673f14b01a776c1eda66489/tests/Fixtures/zz_gif_89a_palette_animated_corrupted_data_153x120.gif
--------------------------------------------------------------------------------
/tests/Fixtures/zz_gif_89a_palette_animated_no_graphic_control_550x296.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coldume/imagecraft/2c9ab5174ebae6733673f14b01a776c1eda66489/tests/Fixtures/zz_gif_89a_palette_animated_no_graphic_control_550x296.gif
--------------------------------------------------------------------------------
/tests/ImageBuilderTest.php:
--------------------------------------------------------------------------------
1 | getMock('Imagecraft\\ImageBuilder', null, [['engine' => 'php_gd']]);
22 | $context = $builder->about();
23 | $this->assertInstanceOf('Imagecraft\\AbstractContext', $context);
24 | $image = $builder
25 | ->addBackgroundLayer()
26 | ->filename(__DIR__.'/Fixtures/jpeg_exif_truecolor_480x360.jpg')
27 | ->resize(400, 400, 'shrink')
28 | ->done()
29 | ->addImageLayer()
30 | ->filename(__DIR__.'/Fixtures/gif_87a_palette_250x297.gif')
31 | ->resize(100, 100)
32 | ->move(100, 100, 'top_left')
33 | ->done()
34 | ->addTextLayer()
35 | ->font(__DIR__.'/Fixtures/pfa_truecolor_alpha.pfa', 12, '#FFF')
36 | ->label('Hello World')
37 | ->box([0, 0, 0, 0], '#000')
38 | ->done()
39 | ->save()
40 | ;
41 | $this->assertTrue($image->isValid());
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/LayerPass/AbstractLayerPassTest.php:
--------------------------------------------------------------------------------
1 | pass = $this->getMockForAbstractClass('Imagecraft\\LayerPass\\AbstractLayerPass');
15 | }
16 |
17 | public function testSanitizeHexColor()
18 | {
19 | $this->assertEquals(['hex' => '#000', 'rgb' => [0, 0, 0]], $this->pass->sanitizeHexColor('000'));
20 | $this->assertEquals(['hex' => '#C0C0C0', 'rgb' => [192, 192, 192]], $this->pass->sanitizeHexColor('#c0c0c0'));
21 | }
22 |
23 | /**
24 | * @expectedException Imagecraft\Exception\InvalidArgumentException
25 | */
26 | public function testSanitizeHexColorWhenColorIsInvalid()
27 | {
28 | $this->pass->sanitizeHexColor('foo');
29 | }
30 |
31 | public function testSanitizeEnumeration()
32 | {
33 | $this->assertEquals(1, $this->pass->sanitizeEnumeration(1, [1, 2, 3]));
34 | }
35 |
36 | /**
37 | * @expectedException Imagecraft\Exception\InvalidArgumentException
38 | */
39 | public function testSanitizeEnumerationWhenEnumerationIsInvalid()
40 | {
41 | $this->pass->sanitizeEnumeration(1, [2, 3, 4]);
42 | }
43 |
44 | public function testSanitizeUrl()
45 | {
46 | $this->assertEquals('http://www.example.com', $this->pass->sanitizeUrl('www.example.com'));
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tests/LayerPass/BackgroundLayerPassTest.php:
--------------------------------------------------------------------------------
1 | pass = $this->getMock('Imagecraft\\LayerPass\\BackgroundLayerPass', null);
17 | $this->layer = $this->getMock('Imagecraft\\Layer\\BackgroundLayer', null);
18 | }
19 |
20 | /**
21 | * @expectedException Imagecraft\Exception\BadMethodCallException
22 | */
23 | public function testProcessWhenNoBackgroundLayerIsFound()
24 | {
25 | $this->pass->process([]);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/LayerPass/ImageAwareLayerPassTest.php:
--------------------------------------------------------------------------------
1 | pass = $this->getMock('Imagecraft\\LayerPass\\ImageAwareLayerPass', null);
19 | $this->layer = $this->getMock('Imagecraft\\Layer\\ImageLayer', null);
20 | }
21 |
22 | public function testProcessResource()
23 | {
24 | $this->layer->add(['image.http.url' => 'www.example.com', 'image.http.data_limit' => -10, 'timeout' => -20]);
25 | $this->pass->processResource($this->layer);
26 | $this->assertEquals($this->layer->get('image.http.url'), 'http://www.example.com');
27 | $this->assertEquals($this->layer->get('image.http.data_limit'), -1);
28 | $this->assertEquals($this->layer->get('image.http.timeout'), -1);
29 |
30 | $this->layer->clear();
31 | $this->layer->set('image.filename', 'foo');
32 | $this->pass->processResource($this->layer);
33 | $this->assertEquals($this->layer->get('image.filename'), 'foo');
34 |
35 | $this->layer->clear();
36 | $this->layer->set('image.contents', 'foo');
37 | $this->pass->processResource($this->layer);
38 | $this->assertEquals($this->layer->get('image.contents'), 'foo');
39 | }
40 |
41 | /**
42 | * @expectedException Imagecraft\Exception\BadMethodCallException
43 | */
44 | public function testProcessWhenNoResourceIsFound()
45 | {
46 | $this->pass->processResource($this->layer);
47 | }
48 |
49 | public function testProcessResize()
50 | {
51 | $this->layer->add([
52 | 'image.resize.width' => -5,
53 | 'image.resize.height' => 0,
54 | 'image.resize.option' => ImageAwareLayerInterface::RESIZE_SHRINK,
55 | ]);
56 | $this->pass->processResize($this->layer);
57 | $this->assertEquals(1, $this->layer->get('image.resize.width'));
58 | $this->assertEquals(1, $this->layer->get('image.resize.height'));
59 | $this->assertEquals(ImageAwareLayerInterface::RESIZE_SHRINK, $this->layer->get('image.resize.option'));
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/tests/LayerPass/RegularLayerPassTest.php:
--------------------------------------------------------------------------------
1 | pass = $this->getMock('Imagecraft\\LayerPass\\RegularLayerPass', null);
19 | $this->layer = $this->getMock('Imagecraft\\Layer\\ImageLayer', null);
20 | }
21 |
22 | public function testProcessMove()
23 | {
24 | $this->pass->processMove($this->layer);
25 | $this->assertTrue($this->layer->has('regular.move.x'));
26 | $this->assertTrue($this->layer->has('regular.move.y'));
27 | $this->assertTrue($this->layer->has('regular.move.gravity'));
28 |
29 | $this->layer->clear();
30 | $this->layer->add([
31 | 'regular.move.x' => 11.1,
32 | 'regular.move.y' => -10,
33 | 'regular.move.gravity' => RegularLayerInterface::MOVE_TOP_LEFT,
34 |
35 | ]);
36 | $this->pass->processMove($this->layer);
37 | $this->assertEquals(11, $this->layer->get('regular.move.x'));
38 | $this->assertEquals(-10, $this->layer->get('regular.move.y'));
39 | $this->assertEquals(RegularLayerInterface::MOVE_TOP_LEFT, $this->layer->get('regular.move.gravity'));
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tests/LayerPass/TextLayerPassTest.php:
--------------------------------------------------------------------------------
1 | pass = $this->getMock('Imagecraft\\LayerPass\\TextLayerPass', null);
17 | $this->layer = $this->getMock('Imagecraft\\Layer\\TextLayer', null);
18 | }
19 |
20 | public function testProcessFont()
21 | {
22 | $this->layer->add([
23 | 'text.font.filename' => 'foo',
24 | 'text.font.size' => 15,
25 | 'text.font.hex_color' => '#000',
26 | ]);
27 | $this->pass->processFont($this->layer);
28 | $this->assertEquals('foo', $this->layer->get('text.font.filename'));
29 | $this->assertEquals(15, $this->layer->get('text.font.size'));
30 | $this->assertEquals('#000', $this->layer->get('text.font.hex_color'));
31 | $this->assertEquals([0, 0, 0], $this->layer->get('text.font.rgb_color'));
32 | }
33 |
34 | /**
35 | * @expectedException Imagecraft\Exception\BadMethodCallException
36 | */
37 | public function testProcessFontWhenNoFontIsAdded()
38 | {
39 | $this->pass->processFont($this->layer);
40 | }
41 |
42 | public function testProcessLabel()
43 | {
44 | $this->pass->processLabel($this->layer);
45 | $this->assertEquals('', $this->layer->get('text.label'));
46 |
47 | $this->layer->clear();
48 | $this->layer->set('text.label', 'foo');
49 | $this->pass->processLabel($this->layer);
50 | $this->assertEquals('foo', $this->layer->get('text.label'));
51 | }
52 |
53 | public function testProcessAngle()
54 | {
55 | $this->pass->processAngle($this->layer);
56 | $this->assertEquals(0, $this->layer->get('text.angle'));
57 |
58 | $this->layer->clear();
59 | $this->layer->set('text.angle', 10.1);
60 | $this->pass->processAngle($this->layer);
61 | $this->assertEquals(10, $this->layer->get('text.angle'));
62 | }
63 |
64 | public function testProcessLineSpacing()
65 | {
66 | $this->pass->processLineSpacing($this->layer);
67 | $this->assertEquals(0.5, $this->layer->get('text.line_spacing'));
68 |
69 | $this->layer->clear();
70 | $this->layer->set('text.line_spacing', 10.1);
71 | $this->pass->processLineSpacing($this->layer);
72 | $this->assertEquals(10.1, $this->layer->get('text.line_spacing'));
73 | }
74 |
75 | public function testProcessBox()
76 | {
77 | $this->pass->processBox($this->layer);
78 | $this->assertEquals([0, 0, 0, 0], $this->layer->get('text.box.paddings'));
79 | $this->assertEquals(null, $this->layer->get('text.box.hex_color'));
80 |
81 | $this->layer->clear();
82 | $this->layer->add([
83 | 'text.box.paddings' => [10, 1],
84 | 'text.box.hex_color' => '#000',
85 | ]);
86 | $this->pass->processBox($this->layer);
87 | $this->assertEquals([10, 1, 0, 0], $this->layer->get('text.box.paddings'));
88 | $this->assertEquals('#000', $this->layer->get('text.box.hex_color'));
89 | $this->assertEquals([0, 0, 0], $this->layer->get('text.box.rgb_color'));
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/tests/OptionPass/CacheDirOptionPassTest.php:
--------------------------------------------------------------------------------
1 | pass = $this->getMock('Imagecraft\\OptionPass\\CacheDirOptionPass', null);
15 | }
16 |
17 | public function testProcess()
18 | {
19 | $option = $this->pass->process([]);
20 | $this->assertEquals(null, $option['cache_dir']);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/OptionPass/DebugOptionPassTest.php:
--------------------------------------------------------------------------------
1 | pass = $this->getMock('Imagecraft\\OptionPass\\DebugOptionPass', null);
15 | }
16 |
17 | public function testProcess()
18 | {
19 | $option = $this->pass->process([]);
20 | $this->assertTrue($option['debug']);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/OptionPass/DelegatingOptionPassTest.php:
--------------------------------------------------------------------------------
1 | pass = $this->getMock('Imagecraft\\OptionPass\\DelegatingOptionPass', null);
15 | }
16 |
17 | public function testProcess()
18 | {
19 | $option = $this->pass->process([]);
20 | $this->assertInternalType('array', $option);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/OptionPass/EngineOptionPassTest.php:
--------------------------------------------------------------------------------
1 | pass = $this->getMock('Imagecraft\\OptionPass\\EngineOptionPass', null);
15 | }
16 |
17 | public function testProcess()
18 | {
19 | $option = $this->pass->process([]);
20 | $this->assertEquals('php_gd', $option['engine']);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/OptionPass/GifAnimationOptionPassTest.php:
--------------------------------------------------------------------------------
1 | pass = $this->getMock('Imagecraft\\OptionPass\\GifAnimationOptionPass', null);
15 | }
16 |
17 | public function testProcess()
18 | {
19 | $option = $this->pass->process([]);
20 | $this->assertTrue($option['gif_animation']);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/OptionPass/JpegQualityOptionPassTest.php:
--------------------------------------------------------------------------------
1 | pass = $this->getMock('Imagecraft\\OptionPass\\JpegQualityOptionPass', null);
15 | }
16 |
17 | public function testProcess()
18 | {
19 | $option = $this->pass->process([]);
20 | $this->assertEquals(100, $option['jpeg_quality']);
21 |
22 | $option = $this->pass->process(['jpeg_quality' => 200]);
23 | $this->assertEquals(100, $option['jpeg_quality']);
24 |
25 | $option = $this->pass->process(['jpeg_quality' => -100]);
26 | $this->assertEquals(100, $option['jpeg_quality']);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/OptionPass/LocaleOptionPassTest.php:
--------------------------------------------------------------------------------
1 | pass = $this->getMock('Imagecraft\\OptionPass\\LocaleOptionPass', null);
15 | }
16 |
17 | public function testProcess()
18 | {
19 | $option = $this->pass->process([]);
20 | $this->assertEquals('en', $option['locale']);
21 |
22 | $option = $this->pass->process(['locale' => 'foo']);
23 | $this->assertEquals('en', $option['locale']);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/OptionPass/MemoryLimitOptionPassTest.php:
--------------------------------------------------------------------------------
1 | pass = $this->getMock('Imagecraft\\OptionPass\\MemoryLimitOptionPass', null);
15 | }
16 |
17 | public function testProcess()
18 | {
19 | $option = $this->pass->process([]);
20 | $this->assertEquals(-10, $option['memory_limit']);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/OptionPass/OutputFormatOptionPassTest.php:
--------------------------------------------------------------------------------
1 | pass = $this->getMock('Imagecraft\\OptionPass\\OutputFormatOptionPass', null);
15 | }
16 |
17 | public function testProcess()
18 | {
19 | $option = $this->pass->process([]);
20 | $this->assertEquals('default', $option['output_format']);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/OptionPass/PngCompressionOptionPassTest.php:
--------------------------------------------------------------------------------
1 | pass = $this->getMock('Imagecraft\\OptionPass\\PngCompressionOptionPass', null);
15 | }
16 |
17 | public function testProcess()
18 | {
19 | $option = $this->pass->process([]);
20 | $this->assertEquals(100, $option['png_compression']);
21 |
22 | $option = $this->pass->process(['png_compression' => 200]);
23 | $this->assertEquals(100, $option['png_compression']);
24 |
25 | $option = $this->pass->process(['png_compression' => -100]);
26 | $this->assertEquals(100, $option['png_compression']);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | addPsr4('Imagecraft\\', __DIR__);
5 |
6 | \TranslatedException\TranslatedException::init();
7 | \ImcStream\ImcStream::register();
8 |
9 | date_default_timezone_set('UTC');
10 |
--------------------------------------------------------------------------------