Skip to content
This repository has been archived by the owner on Jan 5, 2018. It is now read-only.

Commit

Permalink
Issue #2748507 by mondrake: Add "Opacity" effect
Browse files Browse the repository at this point in the history
  • Loading branch information
mondrake authored and mondrake committed Jan 30, 2017
1 parent ece7a2f commit 7e79819
Show file tree
Hide file tree
Showing 10 changed files with 363 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Contrast | Supports changing contrast settings of an image. Also support
Convolution | Allows to build custom image filters like blur, emboss, sharpen and others (see http://docs.gimp.org/en/plug-in-convmatrix.html). | X | IM only |
ImageMagick arguments | Directly enter ImageMagick command line arguments. | | X |
Interlace | Used to specify the type of interlacing scheme for raw image formats. | X | IM only |
Opacity | Change overall image transparency level. | X | IM only |
Set canvas | Places the source image over a colored or a transparent background of a defined size. | X | IM only |
Set transparent color | Defines the color to be used for transparency in GIF images. | X | IM only |
Sharpen | Sharpens an image (using convolution). | X | IM only |
Expand Down
8 changes: 8 additions & 0 deletions config/schema/image_effects.schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,14 @@ image.effect.image_effects_imagemagick_arguments:
type: string
label: 'Height in px or %'

image.effect.image_effects_opacity:
type: mapping
label: 'Adjust image transparency level'
mapping:
opacity:
type: integer
label: 'Opacity % of the source image'

image.effect.image_effects_set_canvas:
type: mapping
label: 'Set canvas image effect'
Expand Down
4 changes: 4 additions & 0 deletions image_effects.module
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ function image_effects_theme() {
'image_effects_imagemagick_arguments_summary' => [
'variables' => ['data' => NULL, 'effect' => []],
],
// Opacity image effect - summary
'image_effects_opacity_summary' => [
'variables' => ['data' => NULL, 'effect' => []],
],
// Set canvas image effect - summary
'image_effects_set_canvas_summary' => [
'variables' => ['data' => NULL, 'effect' => []],
Expand Down
72 changes: 72 additions & 0 deletions src/Plugin/ImageEffect/OpacityImageEffect.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace Drupal\image_effects\Plugin\ImageEffect;

use Drupal\Core\Image\ImageInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\image\ConfigurableImageEffectBase;

/**
* Adjust image transparency.
*
* @ImageEffect(
* id = "image_effects_opacity",
* label = @Translation("Opacity"),
* description = @Translation("Change overall image transparency level. Applies only to image formats that support Alpha channel, like PNG.")
* )
*/
class OpacityImageEffect extends ConfigurableImageEffectBase {

/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'opacity' => 50,
] + parent::defaultConfiguration();
}

/**
* {@inheritdoc}
*/
public function getSummary() {
return [
'#theme' => 'image_effects_opacity_summary',
'#data' => $this->configuration,
] + parent::getSummary();
}

/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form['opacity'] = [
'#type' => 'number',
'#title' => $this->t('Opacity'),
'#field_suffix' => '%',
'#description' => $this->t('Opacity: 0 - 100'),
'#default_value' => $this->configuration['opacity'],
'#min' => 0,
'#max' => 100,
'#maxlength' => 3,
'#size' => 3
];
return $form;
}

/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::submitConfigurationForm($form, $form_state);
$this->configuration['opacity'] = $form_state->getValue('opacity');
}

/**
* {@inheritdoc}
*/
public function applyEffect(ImageInterface $image) {
return $image->apply('opacity', ['opacity' => $this->configuration['opacity']]);
}

}
34 changes: 34 additions & 0 deletions src/Plugin/ImageToolkit/Operation/OpacityTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Drupal\image_effects\Plugin\ImageToolkit\Operation;

/**
* Base trait for image_effects Opacity operations.
*/
trait OpacityTrait {

/**
* {@inheritdoc}
*/
protected function arguments() {
return [
'opacity' => [
'description' => 'Opacity.',
'required' => FALSE,
'default' => 100,
],
];
}

/**
* {@inheritdoc}
*/
protected function validateArguments(array $arguments) {
// Ensure opacity is in the range 0-100.
if (!is_numeric($arguments['opacity']) || $arguments['opacity'] > 100 || $arguments['opacity'] < 0) {
throw new \InvalidArgumentException("Invalid opacity ('{$arguments['opacity']}') specified for the image 'opacity' operation");
}
return $arguments;
}

}
66 changes: 66 additions & 0 deletions src/Plugin/ImageToolkit/Operation/gd/GDOperationTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -221,4 +221,70 @@ protected function _imagettfbbox($size, $angle, $fontfile, $text) {
}
}

/**
* Change overall image transparency level.
*
* This method implements the algorithm described in
* http://php.net/manual/en/function.imagefilter.php#82162
*
* @param resource $img
* Image resource id.
* @param int $pct
* Opacity of the source image in percentage.
*
* @return bool
* Returns TRUE on success or FALSE on failure.
*
* @see http://php.net/manual/en/function.imagefilter.php#82162
*/
function filterOpacity($img, $pct) {
if (!isset($pct)) {
return false;
}
$pct /= 100;

// Get image width and height.
$w = imagesx($img);
$h = imagesy($img);

// Turn alpha blending off.
imagealphablending($img, FALSE);

// Find the most opaque pixel in the image (the one with the smallest alpha
// value).
$min_alpha = 127;
for ($x = 0; $x < $w; $x++) {
for ($y = 0; $y < $h; $y++) {
$alpha = (imagecolorat($img, $x, $y) >> 24) & 0xFF;
if ($alpha < $min_alpha) {
$min_alpha = $alpha;
}
}
}

// Loop through image pixels and modify alpha for each.
for ($x = 0; $x < $w; $x++) {
for ($y = 0; $y < $h; $y++) {
// Get current alpha value (represents the TANSPARENCY!).
$color_xy = imagecolorat($img, $x, $y);
$alpha = ($color_xy >> 24) & 0xFF;
// Calculate new alpha.
if ($min_alpha !== 127) {
$alpha = 127 + 127 * $pct * ($alpha - 127) / (127 - $min_alpha);
}
else {
$alpha += 127 * $pct;
}
// Get the color index with new alpha
$alpha_color_xy = imagecolorallocatealpha($img, ($color_xy >> 16) & 0xFF, ($color_xy >> 8) & 0xFF, $color_xy & 0xFF, $alpha);
// Set pixel with the new color + opacity.
if (!imagesetpixel($img, $x, $y, $alpha_color_xy)) {
return FALSE;
}
}
}

return TRUE;
}

}
35 changes: 35 additions & 0 deletions src/Plugin/ImageToolkit/Operation/gd/Opacity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace Drupal\image_effects\Plugin\ImageToolkit\Operation\gd;

use Drupal\system\Plugin\ImageToolkit\Operation\gd\GDImageToolkitOperationBase;
use Drupal\image_effects\Plugin\ImageToolkit\Operation\gd\GDOperationTrait;
use Drupal\image_effects\Plugin\ImageToolkit\Operation\OpacityTrait;

/**
* Defines GD Opacity operation.
*
* @ImageToolkitOperation(
* id = "image_effects_gd_opacity",
* toolkit = "gd",
* operation = "opacity",
* label = @Translation("Opacity"),
* description = @Translation("Adjust image transparency.")
* )
*/
class Opacity extends GDImageToolkitOperationBase {

use GDOperationTrait;
use OpacityTrait;

/**
* {@inheritdoc}
*/
protected function execute(array $arguments) {
if ($arguments['opacity'] < 100) {
return $this->filterOpacity($this->getToolkit()->getResource(), $arguments['opacity']);
}
return TRUE;
}

}
55 changes: 55 additions & 0 deletions src/Plugin/ImageToolkit/Operation/imagemagick/Opacity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

namespace Drupal\image_effects\Plugin\ImageToolkit\Operation\imagemagick;

use Drupal\imagemagick\Plugin\ImageToolkit\Operation\imagemagick\ImagemagickImageToolkitOperationBase;
use Drupal\image_effects\Plugin\ImageToolkit\Operation\OpacityTrait;

/**
* Defines ImageMagick Opacity operation.
*
* @ImageToolkitOperation(
* id = "image_effects_imagemagick_opacity",
* toolkit = "imagemagick",
* operation = "opacity",
* label = @Translation("Opacity"),
* description = @Translation("Adjust image transparency.")
* )
*/
class Opacity extends ImagemagickImageToolkitOperationBase {

use OpacityTrait;

/**
* {@inheritdoc}
*/
protected function execute(array $arguments) {
if ($this->getToolkit()->getPackage() === 'graphicsmagick') {
// GraphicsMagick does not support -alpha argument, return early.
// @todo implement a GraphicsMagick solution if possible.
return FALSE;
}

switch ($arguments['opacity']) {
case 100:
// Fully opaque, leave image as-is.
break;

case 0:
// Fully transparent, set full transparent for all pixels.
$this->getToolkit()->addArgument("-alpha set -channel Alpha -evaluate Set 0%");
break;

default:
// Divide existing alpha to the opacity needed. This preserves
// partially transparent images.
$divide = number_format((float) (100 / $arguments['opacity']), 4, '.', ',');
$this->getToolkit()->addArgument("-alpha set -channel Alpha -evaluate Divide {$divide}");
break;

}

return TRUE;
}

}
69 changes: 69 additions & 0 deletions src/Tests/ImageEffectsOpacityTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace Drupal\image_effects\Tests;

/**
* Opacity effect test.
*
* @group Image Effects
*/
class ImageEffectsOpacityTest extends ImageEffectsTestBase {

/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
// @todo This effect does not work on GraphicsMagick.
$this->imagemagickPackages['graphicsmagick'] = FALSE;
}

/**
* Opacity effect test.
*/
public function testOpacityEffect() {
// Test operations on toolkits.
$this->executeTestOnToolkits([$this, 'doTestOpacityOperations']);
}

/**
* Opacity operations test.
*/
public function doTestOpacityOperations() {
// Test on the PNG test image.
$original_uri = $this->getTestImageCopyUri('/files/image-test.png', 'simpletest');

// Test data.
$test_data = [
// No transparency change.
'100' => [$this->red, $this->green, $this->transparent, $this->blue],
// 50% transparency.
'50' => [[255, 0, 0, 63], [0, 255, 0, 63], $this->transparent, [0, 0, 255, 63]],
// 100% transparency.
'0' => [$this->transparent, $this->transparent, $this->transparent, $this->transparent],
];

foreach ($test_data as $opacity => $colors) {
// Add Opacity effect to the test image style.
$effect = [
'id' => 'image_effects_opacity',
'data' => [
'opacity' => $opacity,
],
];
$uuid = $this->addEffectToTestStyle($effect);

// Check that ::applyEffect generates image with expected opacity.
$derivative_uri = $this->testImageStyle->buildUri($original_uri);
$this->testImageStyle->createDerivative($original_uri, $derivative_uri);
$image = $this->imageFactory->get($derivative_uri, 'gd');
$this->assertTrue($this->colorsAreEqual($colors[0], $this->getPixelColor($image, 0, 0)));
$this->assertTrue($this->colorsAreEqual($colors[1], $this->getPixelColor($image, 39, 0)));
$this->assertTrue($this->colorsAreEqual($colors[2], $this->getPixelColor($image, 0, 19)));
$this->assertTrue($this->colorsAreEqual($colors[3], $this->getPixelColor($image, 39, 19)));

// Remove effect.
$uuid = $this->removeEffectFromTestStyle($uuid);
}
}
}
19 changes: 19 additions & 0 deletions templates/image-effects-opacity-summary.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{#
/**
* @file
* Default theme implementation for a summary of an image opacity effect.
*
* Available variables:
* - data: The current configuration for this opacity effect, including:
* - opacity: The opacity of the image.
* - effect: The effect information, including:
* - id: The effect identifier.
* - label: The effect name.
* - description: The effect description.
*
* @ingroup themeable
*/
#}
{% spaceless %}
{{ data.opacity|e }}%
{% endspaceless %}

0 comments on commit 7e79819

Please sign in to comment.