Browse Source

Merge branch '46-image-helper' of github.com:tonydspaniard/yii2 into tonydspaniard-46-image-helper

Conflicts:
	composer.json
tags/2.0.0-beta
Qiang Xue 11 years ago
parent
commit
2df7d0a8bd
  1. 7
      composer.json
  2. 12
      extensions/yii/imagine/CHANGELOG.md
  3. 327
      extensions/yii/imagine/Image.php
  4. 32
      extensions/yii/imagine/LICENSE.md
  5. 70
      extensions/yii/imagine/README.md
  6. 30
      extensions/yii/imagine/composer.json
  7. 1
      framework/CHANGELOG.md
  8. BIN
      tests/unit/data/imagine/GothamRnd-Light.otf
  9. BIN
      tests/unit/data/imagine/large.jpg
  10. BIN
      tests/unit/data/imagine/xparent.gif
  11. 118
      tests/unit/extensions/imagine/AbstractImageTest.php
  12. 31
      tests/unit/extensions/imagine/ImageGdTest.php
  13. 30
      tests/unit/extensions/imagine/ImageGmagickTest.php
  14. 30
      tests/unit/extensions/imagine/ImageImagickTest.php

7
composer.json

@ -55,6 +55,7 @@
"yiisoft/yii2-codeception": "self.version",
"yiisoft/yii2-debug": "self.version",
"yiisoft/yii2-elasticsearch": "self.version",
"yiisoft/yii2-imagine": "self.version",
"yiisoft/yii2-gii": "self.version",
"yiisoft/yii2-jui": "self.version",
"yiisoft/yii2-mongodb": "self.version",
@ -76,10 +77,6 @@
"michelf/php-markdown": "1.3.*"
},
"require-dev": {
"twbs/bootstrap": "3.0.*",
"smarty/smarty": "*",
"swiftmailer/swiftmailer": "*",
"twig/twig": "*",
"phpunit/phpunit": "3.7.*"
},
"suggest": {
@ -88,6 +85,7 @@
"ext-mongo": "required by yii2-mongo extension",
"ext-pdo": "required by yii2-sphinx extension",
"ext-pdo_mysql": "required by yii2-sphinx extension",
"imagine/imagine": "required by yii2-imagine extension",
"smarty/smarty": "required by yii2-smarty extension",
"swiftmailer/swiftmailer": "required by yii2-swiftmailer extension",
"twig/twig": "required by yii2-twig extension"
@ -100,6 +98,7 @@
"yii\\debug\\": "extensions/",
"yii\\elasticsearch\\": "extensions/",
"yii\\gii\\": "extensions/",
"yii\\imagine\\" : "extensions/",
"yii\\jui\\": "extensions/",
"yii\\mongodb\\": "extensions/",
"yii\\redis\\": "extensions/",

12
extensions/yii/imagine/CHANGELOG.md

@ -0,0 +1,12 @@
Yii Framework 2 imagine extension Change Log
================================================
2.0.0 beta under development
----------------------------
- no changes in this release.
2.0.0 alpha, December 1, 2013
-----------------------------
- Initial release.

327
extensions/yii/imagine/Image.php

@ -0,0 +1,327 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\imagine;
use Imagine\Exception\InvalidArgumentException;
use Imagine\Image\Box;
use Imagine\Image\Color;
use Imagine\Image\ManipulatorInterface;
use Imagine\Image\Point;
use yii\base\Component;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
/**
* Image implements most common image manipulation functions using Imagine library.
*
* To use Image, you should configure it in the application configuration like the following,
*
* ~~~
* 'components' => [
* ...
* 'image' => [
* 'class' => 'yii\imagine\Image',
* 'driver' => \yii\imagine\Image::DRIVER_GD2,
* ],
* ...
* ],
* ~~~
*
* But you can also use it directly,
*
* ~~~
* use yii\imagine\Image;
*
* $img = new Image();
* ~~~
*
* Example of use:
*
* ~~~
* // thumb - saved on runtime path
* $imagePath = Yii::$app->getBasePath() . '/web/img/test-image.jpg';
* $runtimePath = Yii::$app->getRuntimePath();
* Yii::$app->image
* ->thumb($imagePath, 120, 120)
* ->save($runtime . '/thumb-test-image.jpg', ['quality' => 50]);
* ~~~
*
*
* @see http://imagine.readthedocs.org/
*
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @since 2.0
*/
class Image extends Component
{
/**
* GD2 driver definition for Imagine implementation using the GD library.
*/
const DRIVER_GD2 = 'gd2';
/**
* imagick driver definition.
*/
const DRIVER_IMAGICK = 'imagick';
/**
* gmagick driver definition.
*/
const DRIVER_GMAGICK = 'gmagick';
/**
* @var \Imagine\Image\ImagineInterface instance.
*/
private $_imagine;
/**
* @var string the driver to use. These can be:
* - [[DRIVER_GD2]]
* - [[DRIVER_IMAGICK]]
* - [[DRIVER_GMAGICK]]
*/
private $_driver = self::DRIVER_GD2;
/**
* Sets the driver.
* @param $driver
* @throws \yii\base\InvalidConfigException
*/
public function setDriver($driver)
{
if (!is_string($driver) || !in_array($driver, $this->getAvailableDrivers(), true)) {
throw new InvalidConfigException(
strtr('"{class}::driver" should be string of these possible options "{drivers}", "{driver}" given.', [
'{class}' => get_class($this),
'{drivers}' => implode('", "', $this->getAvailableDrivers()),
'{driver}' => $driver
]));
}
$this->_driver = $driver;
}
/**
* Returns the driver which is going to be used for \Imagine\Image\ImagineInterface instance creation.
* @return string the driver used.
*/
public function getDriver()
{
return $this->_driver;
}
/**
* @return array of available drivers.
*/
public function getAvailableDrivers()
{
static $drivers;
if ($drivers === null) {
$drivers = [static::DRIVER_GD2, static::DRIVER_GMAGICK, static::DRIVER_IMAGICK];
}
return $drivers;
}
/**
* @return \Imagine\Image\ImagineInterface instance
*/
public function getImagine()
{
if ($this->_imagine === null) {
switch ($this->_driver) {
case static::DRIVER_GD2:
$this->_imagine = new \Imagine\Gd\Imagine();
break;
case static::DRIVER_IMAGICK:
$this->_imagine = new \Imagine\Imagick\Imagine();
break;
case static::DRIVER_GMAGICK:
$this->_imagine = new \Imagine\Gmagick\Imagine();
break;
}
}
return $this->_imagine;
}
/**
* Crops an image
* @param string $filename the full path to the image file
* @param integer $width the crop width
* @param integer $height the crop height
* @param mixed $point. This argument can be both an array or an \Imagine\Image\Point type class, containing both
* `x` and `y` coordinates. For example:
* ~~~
* // as array
* $obj->crop('path\to\image.jpg', 200, 200, [5, 5]);
* // as \Imagine\Image\Point
* $point = new \Imagine\Image\Point(5, 5);
* $obj->crop('path\to\image.jpg', 200, 200, $point);
* ~~~
* If null, it will crop from 0,0 pixel position
* @return \Imagine\Image\ManipulatorInterface
* @throws \InvalidArgumentException
*/
public function crop($filename, $width, $height, $point = null)
{
if(is_array($point)) {
list($x, $y) = $point;
$point = new Point($x, $y);
} elseif ($point === null) {
$point = new Point(0, 0);
} elseif (!$point instanceof Point ) {
throw new \InvalidArgumentException(
strtr('"{class}::crop()" "$point" if not null, should be an "array" or a "{type}" class type, containing both "x" and "y" coordinates.', [
'{class}' => get_class($this),
'{type}' => 'Imagine\\Image\\Point'
]));
}
return $this->getImagine()
->open($filename)
->copy()
->crop($point, new Box($width, $height));
}
/**
* Creates a thumbnail image. The function differs from [[\Imagine\Image\ImageInterface::thumbnail()]] function that
* it keeps the aspect ratio of the image.
* @param string $filename the full path to the image file
* @param integer $width the width to create the thumbnail
* @param integer $height the height in pixels to create the thumbnail
* @param string $mode
* @return \Imagine\Image\ImageInterface|ManipulatorInterface
*/
public function thumbnail($filename, $width, $height, $mode = ManipulatorInterface::THUMBNAIL_OUTBOUND)
{
$box = new Box($width, $height);
$img = $this->getImagine()
->open($filename);
if(($img->getSize()->getWidth() <= $box->getWidth() && $img->getSize()->getHeight() <= $box->getHeight())
|| (!$box->getWidth() && !$box->getHeight())) {
return $img->copy();
}
$img = $img->thumbnail($box, $mode);
// create empty image to preserve aspect ratio of thumbnail
$thumb = $this->getImagine()
->create($box);
// calculate points
$size = $img->getSize();
$startX = 0;
$startY = 0;
if ($size->getWidth() < $width) {
$startX = ceil($width - $size->getWidth()) / 2;
}
if ($size->getHeight() < $height) {
$startY = ceil($height - $size->getHeight()) / 2;
}
$thumb->paste($img, new Point($startX, $startY));
return $thumb;
}
/**
* Paste a watermark image onto another.
* Note: If any of `$x` or `$y` parameters are null, bottom right position will be default.
* @param string $filename the full path to the image file to apply the watermark to
* @param string $watermarkFilename the full path to the image file to apply as watermark
* @param mixed $point. This argument can be both an array or an \Imagine\Image\Point type class, containing both
* `x` and `y` coordinates. For example:
* ~~~
* // as array
* $obj->watermark('path\to\image.jpg', 'path\to\watermark.jpg', [5, 5]);
* // as \Imagine\Image\Point
* $point = new \Imagine\Image\Point(5, 5);
* $obj->watermark('path\to\image.jpg', 'path\to\watermark.jpg', $point);
* ~~~
* @return ManipulatorInterface
* @throws \InvalidArgumentException
*/
public function watermark($filename, $watermarkFilename, $point = null)
{
$img = $this->getImagine()->open($filename);
$watermark = $this->getImagine()->open($watermarkFilename);
$size = $img->getSize();
$wSize = $watermark->getSize();
// if x or y position was not given, set its bottom right by default
if(is_array($point)) {
list($x, $y) = $point;
$point = new Point($x, $y);
} elseif ($point === null) {
$x = $size->getWidth() - $wSize->getWidth();
$y = $size->getHeight() - $wSize->getHeight();
$point = new Point($x, $y);
} elseif (!$point instanceof Point) {
throw new \InvalidArgumentException(
strtr('"{class}::watermark()" "$point" if not null, should be an "array" or a "{type}" class type, containing both "x" and "y" coordinates.', [
'{class}' => get_class($this),
'{type}' => 'Imagine\\Image\\Point'
]));
}
return $img->paste($watermark, $point);
}
/**
* Draws text to an image.
* @param string $filename the full path to the image file
* @param string $text the text to write to the image
* @param array $fontConfig the font configuration. The font configuration holds the following keys:
* - font: The path to the font file to use to style the text. Required parameter.
* - size: The font size. Defaults to 12.
* - posX: The X position to write the text. Defaults to 5.
* - posY: The Y position to write the text. Defaults to 5.
* - angle: The angle to use to write the text. Defaults to 0.
* @return \Imagine\Image\ImageInterface
* @throws \Imagine\Exception\InvalidArgumentException
*/
public function text($filename, $text, array $fontConfig)
{
$img = $this->getImagine()->open($filename);
$font = ArrayHelper::getValue($fontConfig, 'font');
if ($font === null) {
throw new InvalidArgumentException('"' . get_class($this) .
'::text()" "$fontConfig" parameter should contain a "font" key with the path to the font file to use.');
}
$fontSize = ArrayHelper::getValue($fontConfig, 'size', 12);
$fontColor = ArrayHelper::getValue($fontConfig, 'color', 'fff');
$fontPosX = ArrayHelper::getValue($fontConfig, 'posX', 5);
$fontPosY = ArrayHelper::getValue($fontConfig, 'posY', 5);
$fontAngle = ArrayHelper::getValue($fontConfig, 'angle', 0);
$font = $this->getImagine()->font($font, $fontSize, new Color($fontColor));
$img->draw()->text($text, $font, new Point($fontPosX, $fontPosY), $fontAngle);
return $img;
}
/**
* Adds a frame around of the image. Please note that the image will increase `$margin` x 2.
* @param string $filename the full path to the image file
* @param integer $margin the frame size to add around the image
* @param string $color the frame color
* @param integer $alpha
* @return \Imagine\Image\ImageInterface
*/
public function frame($filename, $margin, $color='000', $alpha = 100)
{
$img = $this->getImagine()->open($filename);
$size = $img->getSize();
$pasteTo = new Point($margin, $margin);
$padColor = new Color($color, $alpha);
$box = new Box($size->getWidth() + ceil($margin * 2), $size->getHeight() + ceil($margin * 2));
$image = $this->getImagine()->create( $box, $padColor);
$image->paste($img, $pasteTo);
return $image;
}
}

32
extensions/yii/imagine/LICENSE.md

@ -0,0 +1,32 @@
The Yii framework is free software. It is released under the terms of
the following BSD License.
Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of Yii Software LLC nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

70
extensions/yii/imagine/README.md

@ -0,0 +1,70 @@
Image Extension for Yii 2
==============================
This extension adds most common image functions and also acts as a wrapper to [Imagine](http://imagine.readthedocs.org/)
image manipulation library.
Installation
------------
The preferred way to install this extension is through [composer](http://getcomposer.org/download/).
Either run
```
php composer.phar require yiisoft/yii2-imagine "*"
```
or add
```json
"yiisoft/yii2-imagine": "*"
```
to the `require` section of your composer.json.
Usage & Documentation
---------------------
This extension is a wrapper to the [Imagine](http://imagine.readthedocs.org/) and also adds the most common methods
used for Image manipulation.
To use this extension, you can use it in to ways, whether you configure it on your application file or you use it
directly.
The following shows how to use it via application configuration file:
```
// configuring on your application configuration file
'components' => [
'image' => [
'class' => 'yii\imagine\Image',
'driver' => \yii\imagine\Image::DRIVER_GD2,
]
...
]
// Once configured you can access to the extension like this:
$img = Yii::$app->image->thumb('path/to/image.jpg', 120, 120);
```
This is how to use it directly:
```
use yii\imagine\Image;
$image = new Image();
$img = $image->thumb('path/to/image.jpg', 120, 120);
```
**About the methods**
Each method returns an instance to `\Imagine\Image\ManipulatorInterface`, that means that you can easily make use of the methods included in the `Imagine` library:
```
// frame, rotate and save an image
Yii::$app->image->frame('path/to/image.jpg', 5, '666', 0)
->rotate(-8)
->save('path/to/destination/image.jpg', ['quality' => 50]);
```

30
extensions/yii/imagine/composer.json

@ -0,0 +1,30 @@
{
"name": "yiisoft/yii2-imagine",
"description": "The Imagine integration for the Yii framework",
"keywords": ["yii", "imagine", "image", "helper"],
"type": "yii2-extension",
"license": "BSD-3-Clause",
"support": {
"issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Aimagine",
"forum": "http://www.yiiframework.com/forum/",
"wiki": "http://www.yiiframework.com/wiki/",
"irc": "irc://irc.freenode.net/yii",
"source": "https://github.com/yiisoft/yii2"
},
"authors": [
{
"name": "Antonio Ramirez",
"email": "amigo.cobos@gmail.com"
}
],
"require": {
"yiisoft/yii2": "*",
"imagine/imagine": "v0.5.0"
},
"autoload": {
"psr-0": {
"yii\\imagine\\": ""
}
},
"target-dir": "yii/imagine"
}

1
framework/CHANGELOG.md

@ -24,6 +24,7 @@ Yii Framework 2 Change Log
- Bug: Json::encode() did not handle objects that implement JsonSerializable interface correctly (cebe)
- Bug: Fixed issue with tabular input on ActiveField::radio() and ActiveField::checkbox() (jom)
- Bug: Fixed the issue that query cache returns the same data for the same SQL but different query methods (qiangxue)
- Enh #46: Added Image extension based on [Imagine library](http://imagine.readthedocs.org) (tonydspaniard)
- Enh #364: Improve Inflector::slug with `intl` transliteration. Improved transliteration char map. (tonydspaniard)
- Enh #797: Added support for validating multiple columns by `UniqueValidator` and `ExistValidator` (qiangxue)
- Enh #802: Added support for retrieving sub-array element or child object property through `ArrayHelper::getValue()` (qiangxue, cebe)

BIN
tests/unit/data/imagine/GothamRnd-Light.otf

Binary file not shown.

BIN
tests/unit/data/imagine/large.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
tests/unit/data/imagine/xparent.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

118
tests/unit/extensions/imagine/AbstractImageTest.php

@ -0,0 +1,118 @@
<?php
namespace yiiunit\extensions\imagine;
use Yii;
use Imagine\Image\Point;
use yiiunit\VendorTestCase;
Yii::setAlias('@yii/imagine', __DIR__ . '/../../../../extensions/yii/imagine');
abstract class AbstractImageTest extends VendorTestCase
{
/**
* @var yii\imagine\Image
*/
protected $image;
protected $imageFile;
protected $watermarkFile;
protected $runtimeTextFile;
protected $runtimeWatermarkFile;
protected function setUp()
{
$this->imageFile = Yii::getAlias('@yiiunit/data/imagine/large') . '.jpg';
$this->watermarkFile = Yii::getAlias('@yiiunit/data/imagine/xparent') . '.gif';
$this->runtimeTextFile = Yii::getAlias('@yiiunit/runtime/image-text-test') . '.png';
$this->runtimeWatermarkFile = Yii::getAlias('@yiiunit/runtime/image-watermark-test') . '.png';
parent::setUp();
}
protected function tearDown()
{
@unlink($this->runtimeTextFile);
@unlink($this->runtimeWatermarkFile);
}
public function testText() {
if(!$this->isFontTestSupported()) {
$this->markTestSkipped('Skipping ImageGdTest Gd not installed');
}
$fontFile = Yii::getAlias('@yiiunit/data/imagine/GothamRnd-Light') . '.otf';
$img = $this->image->text($this->imageFile, 'Yii-2 Image', [
'font' => $fontFile,
'size' => 12,
'color' => '000'
]);
$img->save($this->runtimeTextFile);
$this->assertTrue(file_exists($this->runtimeTextFile));
}
public function testCrop()
{
$point = [20,20];
$img = $this->image->crop($this->imageFile, 100, 100, $point);
$this->assertEquals(100, $img->getSize()->getWidth());
$this->assertEquals(100, $img->getSize()->getHeight());
$point = new Point(20, 20);
$img = $this->image->crop($this->imageFile, 100, 100, $point);
$this->assertEquals(100, $img->getSize()->getWidth());
$this->assertEquals(100, $img->getSize()->getHeight());
}
public function testWatermark()
{
$img = $this->image->watermark($this->imageFile, $this->watermarkFile);
$img->save($this->runtimeWatermarkFile);
$this->assertTrue(file_exists($this->runtimeWatermarkFile));
}
public function testFrame()
{
$frameSize = 5;
$original = $this->image->getImagine()->open($this->imageFile);
$originalSize = $original->getSize();
$img = $this->image->frame($this->imageFile, $frameSize, '666', 0);
$size = $img->getSize();
$this->assertEquals($size->getWidth(), $originalSize->getWidth() + ($frameSize * 2));
}
public function testThumbnail()
{
$img = $this->image->thumbnail($this->imageFile, 120, 120);
$this->assertEquals(120, $img->getSize()->getWidth());
$this->assertEquals(120, $img->getSize()->getHeight());
}
/**
* @expectedException \yii\base\InvalidConfigException
*/
public function testShouldThrowExceptionOnDriverInvalidArgument() {
$this->image->setDriver('fake-driver');
}
/**
* @expectedException \InvalidArgumentException
*/
public function testShouldThrowExceptionOnCropInvalidArgument() {
$this->image->crop($this->imageFile, 100, 100, new \stdClass());
}
/**
* @expectedException \InvalidArgumentException
*/
public function testShouldThrowExceptionOnWatermarkInvalidArgument() {
$this->image->watermark($this->imageFile, $this->watermarkFile, new \stdClass());
}
abstract protected function isFontTestSupported();
}

31
tests/unit/extensions/imagine/ImageGdTest.php

@ -0,0 +1,31 @@
<?php
namespace yiiunit\extensions\imagine;
use yii\imagine\Image;
/**
* @group vendor
* @group imagine
*/
class ImageGdTest extends AbstractImageTest
{
protected function setUp()
{
if (!function_exists('gd_info')) {
$this->markTestSkipped('Skipping ImageGdTest, Gd not installed');
} else {
$this->image = new Image();
$this->image->setDriver(Image::DRIVER_GD2);
parent::setUp();
}
}
protected function isFontTestSupported()
{
$infos = gd_info();
return isset($infos['FreeType Support']) ? $infos['FreeType Support'] : false;
}
}

30
tests/unit/extensions/imagine/ImageGmagickTest.php

@ -0,0 +1,30 @@
<?php
namespace yiiunit\extensions\imagine;
use yii\imagine\Image;
/**
* @group vendor
* @group imagine
*/
class ImageGmagickTest extends AbstractImageTest
{
protected function setUp()
{
if (!class_exists('Gmagick')) {
$this->markTestSkipped('Skipping ImageGmagickTest, Gmagick is not installed');
} else {
$this->image = new Image();
$this->image->setDriver(Image::DRIVER_GMAGICK);
parent::setUp();
}
}
protected function isFontTestSupported()
{
return true;
}
}

30
tests/unit/extensions/imagine/ImageImagickTest.php

@ -0,0 +1,30 @@
<?php
namespace yiiunit\extensions\imagine;
use yii\imagine\Image;
/**
* @group vendor
* @group imagine
*/
class ImageImagickTest extends AbstractImageTest
{
protected function setUp()
{
if (!class_exists('Imagick')) {
$this->markTestSkipped('Skipping ImageImagickTest, Imagick is not installed');
} else {
$this->image = new Image();
$this->image->setDriver(Image::DRIVER_IMAGICK);
parent::setUp();
}
}
protected function isFontTestSupported()
{
return true;
}
}
Loading…
Cancel
Save