diff --git a/sites/all/modules/imagecache_actions/.gitignore b/sites/all/modules/imagecache_actions/.gitignore
deleted file mode 100644
index 1d45c0a..0000000
--- a/sites/all/modules/imagecache_actions/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-*.patch
diff --git a/sites/all/modules/imagecache_actions/CHANGELOG.txt b/sites/all/modules/imagecache_actions/CHANGELOG.txt
index 74ad35e..f1866f0 100644
--- a/sites/all/modules/imagecache_actions/CHANGELOG.txt
+++ b/sites/all/modules/imagecache_actions/CHANGELOG.txt
@@ -3,6 +3,140 @@ Imagecache Actions
Imagecache Actions 7.x-1.x-dev
------------------------------
+
+Imagecache Actions 7.x-1.9
+--------------------------
+- [#2760121] by lebster, fietserwin: Add a "Perspective" effect.
+- [#2947014]: PHP 7.2 warning that each() has been deprecated.
+
+Imagecache Actions 7.x-1.8
+--------------------------
+- [#2917097]: PHP 7.1 Warning "A non-numeric value encountered".
+- [#2905130] by bserem, fietserwin: Remove animation from moving images.
+- [#2888678] by das-peter: ImageMagick: Add support for invert effect.
+- [#1099300]: Notice: A non well formed numeric value encountered in
+ image_gd_definecanvas() (line 381 of canvasactions.inc).
+- [#2719661] by SKAUGHT: Division by zero.
+- [#2717789]: Prevent exif_read_data() warnings.
+- Warning: Theme hook coloractions_invert_summary not found.
+- [#2696225] by mnlund, fietserwin: Add support for multiply color blending.
+- by fietserwin: image_styles_admin: sort image styles by label.
+- [#2039379]: Add some better examples for using custom actions.
+- Made more contextual information (image style and image effect) available in
+ the custom action and text image effects.
+- [#2690337] by Ajithlal, fietserwin: Would like a feature for converting case
+ in text effect.
+
+Imagecache Actions 7.x-1.7
+--------------------------
+- [#2671526]: Fatal error on image style flush when image styles are defined in
+ Features.
+
+Imagecache Actions 7.x-1.6
+--------------------------
+- [#2664020]: IE fails to display images whose extension and Content-Type header
+ do not match the actual image type.
+- [#1591484]: Underlay not working (on IM).
+- [#2359523]: Follow up to improve help text.
+- [#2615986] by hargobind: Effect summary in image_styles_admin.module.
+- Notice: Undefined index: element in theme_coloractions_alpha_summary() (line
+ 98 of coloractions\transparency.inc).
+- Removed superfluous theme entry 'imagecache_subroutine_summary' from
+ autorotate sub module.
+- [#2080877] by heathdutton: Interlace / Progressive effect.
+- [#760438] by fietserwin: Resize Overlay / Watermark for ImageMagick.
+- [#760438] by raintonr, Ace Cooper, ttkaminski, thijsvdanker, fietserwin:
+ Resize Overlay / Watermark (GD only).
+- [#768980] by idebr, fietserwin: Blur effect.
+- [#2636314]: 'Import style' can fail on newline characters.
+- [#2573225]: Resize by percentage not calculating other field.
+- [#2447419]: Correct erroneous call to module_load_include in
+ imagecache_canvasactions.module.
+- [#2416805]: Remove reference to non-existent imagecache_coloractions.install.
+- [#2416805]: remove deprecated jquery attr() and replace with FAPI #states
+ (rounded corners UI form).
+- [#2580805]: Coding standards corrections.
+
+
+Imagecache Actions 7.x-1.5 2014-11-29
+-------------------------------------
+- [#2366163]: autorotate sometimes doesn't work with ImageMagick.
+- Text effect: small clean-up of hook_exit().
+- [#2379359]: image_imagemagick_overlay() passes a stream wrapper to convert.
+- [#2359523]: Overlay: allow to define offsets from all sides.
+- [#1717436]: Add effects: color level adjustment and desaturate while retaining
+ alpha.
+- [#2278233]: Call to undefined function imagecache_actions_get_style_label().
+- [#2183721]: Duplicate/Export links just edit, they don't duplicate or export.
+ possible solution only.
+- [#2176727]: New actions: scale / resize by percent.
+- [#2190759]: hook_image_style_flush(): Prefer silent failure over clear failure.
+- [#2166715]: Typo causes extra query every page load.
+- [#2139091]: # sign before HEX in settings form breaks Define canvas action.
+- [#2085967]: Remove .gitignore from version control.
+- Text overlay: treat empty texts as normal situation.
+- [#2003446]: Clean up or remove .install files, adapt .info files.
+- [1854270-48]: Minor optimization for Posterize.
+- Refactor: eliminate test warnings and errors.
+- Refactor: standardize function and parameter naming, function order, doxygen
+ documentation and comments, remove spelling errors in doc and comments.
+
+
+Imagecache Actions 7.x-1.4 2013-08-24
+-------------------------------------
+- [#1778214]: Remove project from image_effects_text_test.info
+- [#1854270]: Posterize action for file size/bandwidth saving on PNGs.
+- [#2060173]: Support image labels (introduced by D7.23).
+- Cleaned up the output of image style export. Id's and help texts are not used
+ on import, so don't have to be exported.
+- Repaired farbtastic colorpicker. It seems the library invocation changed
+ sometime in history. Rebuilt the color picker form to use #attached libraries
+ and simpler inline script instead. REMOVED the theme function that is now
+ redundant as #attached is a better way than drupal_add_js.
+- [#1152736]: Support tokens in text actions for images.
+- [#2025631]: Imagecache Testsuite refers to non-existent permissions.
+- Drupal.org went from http to https: changed all links in our documentation and
+ code.
+- Added ImageEffects as other image effect providing module.
+
+
+Imagecache Actions 7.x-1.3 2013-06-04
+-------------------------------------
+- Removed some errors and warnings from the test suite code (but not all).
+- [#2010560]: Fix spelling for "Update 7001".
+
+
+Imagecache Actions 7.x-1.2 2013-06-01
+-------------------------------------
+- [#1830130]: Allow file fields as referring entities in effects with context.
+- [#1446160]: Integrate with media module: Title and Alt text.
+- [#1591198]: Have dimensions calculations not ignore NULL's.
+- Refactored canvas effects: added parameter types in documentation and
+ signatures. Standardized function documentation. Comments according to Drupal
+ coding standards.
+- [#1999140]: Canvas effects using imagemagick might fail due to earlier
+ -gravity settings.
+- Refactored the image_effects_text_test module to no longer depend on features.
+- [#1998354]: Non-ASCII characters fail to display correctly on many systems
+ (when using Imagemagick).
+- Further code enhancements (messages, comments) to autorotate.
+- [#1930728]: Autorotate does not reset EXIF orientation tag.
+- [#1990620] Many config form docs missing from the D7 upgrade. Converted all
+ legacy instances of #type=markup into #markup='content..'
+- Removed deprecated functions imagecache_actions_entities_from_references() and
+ imagecache_actions_fields_from_filepath() (see [#1844298]).
+- [#1983168]: Custom text via PHP causing EntityMalformedException ...
+- Replaced lhandw.ttf font with some real free fonts that may also be
+ distributed for free. Changed all text effect tests accordingly.
+- Better text effect defaults (translated sample text, text visible by default,
+ sample font file).
+- [#1986304]: Strict warning: Only variables should be passed by reference ...
+- [#1981490]: Wrong filename imagecache_Actions.install.
+- [#1858760]: Unable to find the file 'lhandw.ttf'. Please check the path.
+
+
+Imagecache Actions 7.x-1.1 2012-12-04
+-------------------------------------
- [#1591198]: Image dimensions callbacks should handle unknown (NULL) dimensions
as valid input.
- [#464092]: Aspect Switcher -- Need to Flush Presets. Side-effect: now allows
@@ -38,7 +172,7 @@ Incompatibilities:
file. This may cause current styles to not being able to find specified files
anymore.
- Custom actions: custom snippets are now executed using the PHP filter module,
- meaning that the image syle editor must have the 'Use PHP for settings'
+ meaning that the image style editor must have the 'Use PHP for settings'
permission to be able to edit the custom action snippet.
- Custom actions: information and variables that are available in your custom
snippet have changed. See the README.txt of the custom actions module.
@@ -54,6 +188,6 @@ coverage".
Current and past maintainers for Imagecache Actions
---------------------------------------------------
-- dman (http://drupal.org/user/33240)
-- sidneyshan (http://drupal.org/user/652426)
-- fietserwin (http://drupal.org/user/750928)
+- dman (https://drupal.org/user/33240)
+- sidneyshan (https://drupal.org/user/652426)
+- fietserwin (https://drupal.org/user/750928)
diff --git a/sites/all/modules/imagecache_actions/README.txt b/sites/all/modules/imagecache_actions/README.txt
index 8a26e60..5cc9074 100644
--- a/sites/all/modules/imagecache_actions/README.txt
+++ b/sites/all/modules/imagecache_actions/README.txt
@@ -1,15 +1,30 @@
README for the Imagecache Actions Drupal module
-----------------------------------------------
-Project page: http://drupal.org/project/imagecache_actions
+Project page: https://drupal.org/project/imagecache_actions
Current and past maintainers for Imagecache Actions:
-- dman (http://drupal.org/user/33240)
-- sidneyshan (http://drupal.org/user/652426)
-- fietserwin (http://drupal.org/user/750928)
+- dman (https://drupal.org/user/33240)
+- sidneyshan (https://drupal.org/user/652426)
+- fietserwin (https://drupal.org/user/750928)
+
+
+Release notes for 7.x-1.x-dev
+-----------------------------
+- Clear all caches after updating.
+
+
+Release notes for 7.x-1.4
+-------------------------
+- This release supports image labels as introduced by Drupal 7.23.
+- See CHANGELOG.txt for a full overview of changes.
+
Release notes for 7.x-1.1
-------------------------
+- If you use the module:// notation anywhere in an image effect, you must now
+ install the System Stream Wrapper module
+ (https://drupal.org/project/system_stream_wrapper).
- Clear the cache after updating.
@@ -57,27 +72,28 @@ The additional effects that Imagecache Actions provides include:
- Text overlay: add e.g. a copyright notice to your image.
- Color-shifting: colorize images.
- Brighten/Darken.
-- Alpha blending: use a grayscale image to define the transparency layer of an
+- Alpha blending: use a gray scale image to define the transparency layer of an
image.
-- Canvas manipulation: resize the canvas and add a backgroundcolor or image.
-- File Format switcher: if you need tranparency in JPGs, make them PNG. If your
+- Canvas manipulation: resize the canvas and add a background color or image.
+- File Format switcher: if you need transparency in JPGs, make them PNG. If your
PNG thumbnails are 30K each, save them as JPGs.
- Rounded corners.
-- TODO: complete list, check short descrptions
+- TODO: complete list, check short descriptions
-These effects are grouped in submodules. Just enable the ones you want to use.
-TODO: list submodules and their sets of effects.
+These effects are grouped in sub-modules. Just enable the ones you want to use.
+TODO: list sub-modules and their sets of effects.
Imagecache Actions supports both the GD toolkit from Drupal core and the
Imagemagick toolkit. However, please note that Imagemagick support is not yet
complete. Please file an issue if you encounter problems in using Imagemagick.
+
What is imagecache_action not?
------------------------------
Imagecache Actions does not provide a new UI or new menu items. It hooks into
the already existing image styles system (from Drupal core). See
-http://drupal.org/documentation/modules/image for more information about working
-with images.
+https://drupal.org/documentation/modules/image for more information about
+working with images.
A note about the name of this module
@@ -90,7 +106,7 @@ porting to D7, that name has not been changed (yet).
Which toolkit to use?
---------------------
-Personally, I (fieterwin) prefer the imagemagick toolkit:
+Personally, I (fietserwin) prefer the imagemagick toolkit:
- It is better in anti-aliasing. Try to rotate an image using both toolkits and
you will see what I mean.
- It does not execute in the PHP memory space, so is not restricted by the
@@ -102,27 +118,36 @@ On the other hand: the GD toolkit is always available (in the correct version),
whereas imagemagick is not always present on shared hosting or may be present in
an antique version that might give problems.
-Please also note that effects may give different results depending on the
+Please note that effects may give different results depending on the
toolkit used.
+Please also note that a 3rd image toolkit exists:
+ Imagick (https://www.drupal.org/project/imagick)
+This toolkit uses the Imagick extension and thus does not call the ImageMagick
+binaries directly. However our module does not implement the toolkit specific
+parts for the effects we provide for the Imagick toolkit, but the Imagick
+toolkit comes with its own set of effects that partly covers our effects. So,
+depending on the effects you require it may be a replacement for both our module
+and the Imagemagick module or the core GD toolkit.
Hard Dependencies
-----------------
-- Drupal 7.x
- Image module from Drupal core
At least 1 of the available image toolkits:
-- GD toolkit from Drupal core
-- Imagemagick toolkit: http://drupal.org/project/imagemagick
+- GD toolkit from Drupal core.
+- Imagemagick toolkit: https://drupal.org/project/imagemagick.
+
Soft Dependencies
-----------------
-- System stream wrapper (http://drupal.org/project/system_stream_wrapper)
-- Remote stream wrapper (http://drupal.org/project/remote_stream_wrapper)
+- System stream wrapper (https://drupal.org/project/system_stream_wrapper)
+- Remote stream wrapper (https://drupal.org/project/remote_stream_wrapper)
These modules provide additional stream wrappers. Especially the system stream
wrapper is very handy as it provides, among others, a module:// and theme://
wrapper.
+
Installing
----------
As usual.
@@ -175,7 +200,6 @@ be defined using either:
- A relative (to the current directory, probably Drupal root) or absolute path.
-
Support
-------
Via the issue queue of this project at Drupal.org.
@@ -194,3 +218,8 @@ well).
- Brightness values outside the -250 .. 250 range are accepted.
- Check color fields that allow a transparency component or allow to be empty to
specify fully transparent.
+
+Known problems: Imagemagick
+---------------------------
+- Define canvas using offsets may bot work on older versions. We have an error
+ report for version 6.5.4.7 (2009-07) (https://drupal.org/node/888644).
diff --git a/sites/all/modules/imagecache_actions/ROADMAP.txt b/sites/all/modules/imagecache_actions/ROADMAP.txt
index eca5a64..544cff8 100644
--- a/sites/all/modules/imagecache_actions/ROADMAP.txt
+++ b/sites/all/modules/imagecache_actions/ROADMAP.txt
@@ -1,90 +1,112 @@
-Imagecache Actions roadmap
---------------------------
-
-This is a non-contractual roadmap for the D7 branch of the imagecache actions
-module. Actual release dates and completed features will largely depend on
-available time. So support is always welcome. Furthermore, critical bugs may
-make us release more often, but that should only change the release number in
-which certain features are planned, not the timeline.
-
-
-Imagecache Actions 7.x-1.0
---------------------------
-Targeted release date: may 2012
-
-- (DONE) Clean up D7 issue queue
-- (DONE) Commit posted patches from the issue queue
-- (DONE) Solve easy to solve bug reports
-- Do some basic testing
-- (DONE) Reintroduce the text action, currently living in a sandbox project at
- http://drupal.org/node/1090312
-- (DONE) Update README.txt
-- (DONE) Introduce CHANGELOG.txt
-- (DONE) Add this ROADMAP.txt
-
-
-Imagecache Actions 7.x-1.1
---------------------------
-Targeted release date: end 2012
-
-Mainly a bug fix release
-- (DONE) Solve remaining outstanding bug reports
-- (ALMOST DONE) Keep D7 issue queue clean
-
-
-Imagecache Actions 7.x-1.2
---------------------------
-Targeted release date: spring 2013
-
-- Check help and documentation. a.o: hook_help, effect descriptions.
-- Continue to keep the D7 issue queue clean.
-- Improve Imagemagick support and/or document what effects are not working for
- which toolkit (version).
-- Check that all effects implement all of the effects API (especially the
- dimensions callback).
-- Add testing. The idea is to create a set of image styles that cover all
- effects and can be used to visually check for regressions.
-
- Automated testing would be nice, but I am not sure that we can guarantee that
- jpeg or png files are binary identical on each run across different computers.
- But if the number of false positives is small, it would certainly be a useful
- addition.
-
-
-Imagecache Actions 7.x-1.3
---------------------------
-Targeted release date: ...
-
-- Look at outstanding feature requests
-- Continue to keep the D7 issue queue clean
-- Keep improving Imagemagick support, a.o:
- - Try to get insight into what version of imagemagick is required by what
- effect.
- - Document which effects might produce different results.
-- Refactor code
- - Extract common form fields
- - Extract common error handling
- - (STARTED) Clean up comments, todo's, etc.
- - (STARTED) Doxygen code documentation according to latest standards
- - Increase the amount of lazy loaded code
-
-
-Imagecache Actions 7.x-2.x
---------------------------
-Targeted release date: ...
-
-- Can we refactor effects into auto loading class based plugins? What does
- CTools offer in these? How do D8 core effects do this?
-- If so, will it make implementing new effects much simpler? Because that is
- what we should strive for.
-- Should we rename the module to image_effects?
-- If we manage to provide an easy to use plugin system, we should strive to
- merge with other modules that provide effects:
- - FiltersIE module (http://drupal.org/project/filtersie)
- - Imagecache Effects: http://drupal.org/project/imagecache_effects (D6 only,
- but are their effects more advanced then our counterparts or do they add new
- effects?)
- - Image watermark: http://drupal.org/project/watermark (D5 only, but is their
- watermark effect more advanced?)
- - ImageCache Scale-9 Actions: http://drupal.org/project/imagecache_scale9actions
- - etc.
+Imagecache Actions roadmap
+--------------------------
+
+This is a non-contractual roadmap for the D7 branch of the imagecache actions
+module. Actual release dates and completed features will largely depend on
+available time. So support is always welcome. Furthermore, critical bugs may
+make us release more often, but that should only change the release number in
+which certain features are planned, not the timeline.
+
+
+Imagecache Actions 7.x-1.0
+--------------------------
+Targeted release date: may 2012
+
+- (DONE) Clean up D7 issue queue
+- (DONE) Commit posted patches from the issue queue
+- (DONE) Solve easy to solve bug reports
+- Do some basic testing
+- (DONE) Reintroduce the text action, currently living in a sandbox project at
+ https://drupal.org/node/1090312
+- (DONE) Update README.txt
+- (DONE) Introduce CHANGELOG.txt
+- (DONE) Add this ROADMAP.txt
+
+
+Imagecache Actions 7.x-1.1
+--------------------------
+Targeted release date: end 2012
+
+Mainly a bug fix release
+- (DONE) Solve remaining outstanding bug reports
+- (ALMOST DONE) Keep D7 issue queue clean
+
+
+Imagecache Actions 7.x-1.2
+--------------------------
+Targeted release date: spring 2013
+
+- Check help and documentation. a.o: hook_help, effect descriptions.
+- (DONE) Continue to keep the D7 issue queue clean.
+- Improve Imagemagick support and/or document what effects are not working for
+ which toolkit (version).
+- (ALMOST DONE) Check that all effects implement all of the effects API (especially the
+ dimensions callback).
+- Add testing. The idea is to create a set of image styles that cover all
+ effects and can be used to visually check for regressions.
+
+ Automated testing would be nice, but I am not sure that we can guarantee that
+ jpeg or png files are binary identical on each run across different computers.
+ But if the number of false positives is small, it would certainly be a useful
+ addition.
+
+
+Imagecache Actions 7.x-1.3
+--------------------------
+Targeted release date: ...
+
+- Continue to keep the D7 issue queue clean.
+- Check help and documentation. a.o: hook_help, effect descriptions.
+- Improve Imagemagick support and/or document what effects are not working for
+ which toolkit (version).
+- Add testing. The idea is to create a set of image styles that cover all
+ effects and can be used to visually check for regressions.
+
+ Automated testing would be nice, but I am not sure that we can guarantee that
+ jpeg or png files are binary identical on each run across different computers.
+ But if the number of false positives is small, it would certainly be a useful
+ addition.
+- Refactor code:
+ - Extract common form fields.
+ - Extract common error handling.
+ - (STARTED) Clean up comments, todo's, etc.
+ - (STARTED) Doxygen code documentation according to latest standards
+ - Increase the amount of lazy loaded code.
+
+
+Imagecache Actions 7.x-1.5
+--------------------------
+Targeted release date: ...
+
+- Look at outstanding feature requests
+- Continue to keep the D7 issue queue clean.
+- Keep improving Imagemagick support, a.o:
+ - Try to get insight into what version of imagemagick is required by what
+ effect.
+ - Document which effects might produce different results.
+- Continue refactoring code:
+ - Extract common form fields.
+ - Extract common error handling.
+ - (STARTED) Clean up comments, todo's, etc.
+ - (STARTED) Doxygen code documentation according to latest standards.
+ - Increase the amount of lazy loaded code.
+
+
+Imagecache Actions 8.x-2.x
+--------------------------
+Targeted release date: ...
+
+- Convert image effects into plugins (as [#1821854] does for core).
+- This will make adding new effects much simpler, so we can easier add them.
+- Should we rename the module to image_effects?
+- We should strive to merge with other modules that provide image effects:
+ - FiltersIE module (https://drupal.org/project/filtersie)
+ - Imagecache Effects: https://drupal.org/project/imagecache_effects (D6 only,
+ but are their effects more advanced then our counterparts or do they add new
+ effects?)
+ - Image watermark: https://drupal.org/project/watermark (D5 only, but is their
+ watermark effect more advanced?)
+ - ImageCache Scale-9 Actions: https://drupal.org/project/imagecache_scale9actions
+ - ImageEffects: https://drupal.org/project/imageeffects (pixelate and flip).
+ - Imagick: https://www.drupal.org/project/imagick
+ - etc.
diff --git a/sites/all/modules/imagecache_actions/autorotate/imagecache_autorotate.info b/sites/all/modules/imagecache_actions/autorotate/imagecache_autorotate.info
index 65de451..5b5a913 100644
--- a/sites/all/modules/imagecache_actions/autorotate/imagecache_autorotate.info
+++ b/sites/all/modules/imagecache_actions/autorotate/imagecache_autorotate.info
@@ -1,16 +1,13 @@
name = Imagecache Autorotate
-description = Autorotate image based on EXIF Orientation.
+description = Provides an image effect to autorotate an image based on EXIF data.
package = Media
core = 7.x
dependencies[] = image
-files[] = imagecache_autorotate.install
-files[] = imagecache_autorotate.module
-
-; Information added by drupal.org packaging script on 2012-12-04
-version = "7.x-1.1"
+; Information added by Drupal.org packaging script on 2018-03-20
+version = "7.x-1.9"
core = "7.x"
project = "imagecache_actions"
-datestamp = "1354653754"
+datestamp = "1521550387"
diff --git a/sites/all/modules/imagecache_actions/autorotate/imagecache_autorotate.install b/sites/all/modules/imagecache_actions/autorotate/imagecache_autorotate.install
index 1f8c8f0..4e55856 100644
--- a/sites/all/modules/imagecache_actions/autorotate/imagecache_autorotate.install
+++ b/sites/all/modules/imagecache_actions/autorotate/imagecache_autorotate.install
@@ -1,13 +1,25 @@
'EXIF extension',
+ 'value'=> $phase === 'runtime' ? $t('Disabled') : '',
+ 'description'=> $t('The autorotate image effect requires the exif extension to be enabled.'),
+ 'severity' => REQUIREMENT_ERROR,
+ );
}
+ else {
+ $result['imagecache_autorotate_exif_extension'] = array(
+ 'title'=> 'EXIF extension',
+ 'value'=> $t('Enabled'),
+ 'severity' => REQUIREMENT_OK,
+ );
+
+ }
+ return $result;
}
diff --git a/sites/all/modules/imagecache_actions/autorotate/imagecache_autorotate.module b/sites/all/modules/imagecache_actions/autorotate/imagecache_autorotate.module
index 106faad..2889ca6 100644
--- a/sites/all/modules/imagecache_actions/autorotate/imagecache_autorotate.module
+++ b/sites/all/modules/imagecache_actions/autorotate/imagecache_autorotate.module
@@ -1,61 +1,71 @@
-
- There are no user-configurable options for this process. -
- Certain cameras can embed orientation information into image + '#markup' => "
There are no user-configurable options for this process.
+Certain cameras can embed orientation information into image files when they save them. This information is embedded in an EXIF tag and can be used to rotate images to their correct position for display. -
Not all cameras or images contain this information. - This process is only useful for those that do. -
- The expected/supported values are
-
Tag: 0x0112 Orientation
+ This process is only useful for images that contain this information,
+ whereas for other images it is harmless.
+
Although most modern browsers do support the orientation tag, the + information may get lost or become incorrect by other operations. + So, to support all browsers and prevent rotation errors, it is better to + start each image style with this effect. +
+The expected/supported values are:
+ Tag: 0x0112 Orientation
- -EXIF Reference -
+Wikipedia: Exchangeable image file format
", ); return $form; } /** - * Autorotate image based on EXIF Orientation tag. + * Implements theme_hook() for the autorotate effect summary. * - * See code at - * http://sylvana.net/jpegcrop/exif_orientation.html + * param array $variables + * An associative array containing: + * - data: The current configuration for this image effect. * - * and reference at - * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html - * - * @todo: - * Add horizontal and vertical flips etc. - * Need to create sample set for tests. + * @return string + * The HTML for the summary of this image effect. + * @ingroup themeable */ -function imagecache_autorotate_image($image, $data) { - // Test to see if EXIF is supported for image type (e.g. not PNG). - if ($image->info['mime_type'] == 'image/jpeg') { - if (!function_exists('exif_read_data')) { - watchdog('image', 'The image %file could not be auto-rotated because the exif_read_data() function is not available in this PHP installation. You probably will have to enable the exif extension.', array('%file' => $image->source)); - return FALSE; - } - $exif = exif_read_data(drupal_realpath($image->source)); - if (isset($exif['Orientation'])) { - switch ($exif['Orientation']) { - case 3: - $degrees = 180; - break; - case 6: - $degrees = 90; - break; - case 8: - $degrees = 270; - break; - default: - $degrees = 0; - } - if ($degrees != 0) { - $org_width = $image->info['width']; - $org_height = $image->info['height']; - image_rotate($image, $degrees); - if (($degrees === 90 || $degrees === 270) && $image->info['width'] === $org_width) { - // The toolkit failed to alter the dimensions (imagemagick currently - // fails to do so). So we do it ourselves. - $image->info['width'] = $org_height; - $image->info['height'] = $org_width; - } - } - } +function theme_imagecache_autorotate_summary(/*array $variables*/) { + return 'image based on its EXIF data.'; +} + +/** + * Autorotate image based on EXIF Orientation tag. + */ +function imagecache_autorotate_effect(stdClass $image /*, $data*/) { + // Test to see if EXIF is supported by the current image type. + if (in_array($image->info['mime_type'], array('image/jpeg', 'image/tiff'))) { + // Hand over to toolkit. + return image_toolkit_invoke('imagecache_autorotate', $image); } - else if ($image->source === 'modules/image/sample.png' && !function_exists('exif_read_data')) { - // Issue a warning if we are in the admin screen and the exif extension is - // not enabled. - drupal_set_message(t('The exif_read_data() function is not available in this PHP installation. You probably will have to enable the exif extension.'), 'warning'); + else if ($image->source === 'modules/image/sample.png' && user_access('administer image styles')) { + if (!extension_loaded('exif')) { + // Issue a warning if we are in the admin screen and the exif extension is + // not enabled. + drupal_set_message(t('The autorotate image effect requires the exif extension to be enabled.'), 'warning'); + if ($image->toolkit === 'imagemagick') { + drupal_set_message(t('Though imagemagick will work without the exif extension, subsequent effects may fail as the image dimensions cannot be updated.'), 'warning'); + } + } } return TRUE; } /** - * Image dimensions callback; Auto-rotate. + * GD toolkit specific implementation of this image effect. + * + * @param stdClass $image + * + * @return bool + * true on success, false otherwise. + */ +function image_gd_imagecache_autorotate(stdClass $image) { + if (!function_exists('exif_read_data')) { + watchdog('imagecache_actions', 'Image %file could not be auto-rotated: !message', array('%file' => $image->source, '!message' => t('The exif_read_data() function is not available in this PHP installation. You probably have to enable the exif extension.'))); + return FALSE; + } + + // Read and check result. + $exif = @exif_read_data(drupal_realpath($image->source)); + if ($exif === FALSE && $image->extension === 'jpg') { + watchdog('imagecache_actions', 'Image %file could not be auto-rotated: !message', array('%file' => $image->source, '!message' => t('The exif_read_data() function returned FALSE.'))); + return FALSE; + } + + if (isset($exif['Orientation'])) { + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html: + // 1 = Horizontal (normal) + // 2 = Mirror horizontal + // 3 = Rotate 180 + // 4 = Mirror vertical + // 5 = Mirror horizontal and rotate 270 CW + // 6 = Rotate 90 CW + // 7 = Mirror horizontal and rotate 90 CW + // 8 = Rotate 270 CW + // @todo: Add horizontal and vertical flips etc. + // imagecopy seems to be able to mirror, see conmments on + // http://php.net/manual/en/function.imagecopy.php + // @todo: Create sample set for tests. + switch ($exif['Orientation']) { + case 3: + $degrees = 180; + break; + case 6: + $degrees = 90; + break; + case 8: + $degrees = 270; + break; + default: + $degrees = 0; + } + if ($degrees != 0) { + return image_rotate($image, $degrees); + } + } + return TRUE; +} + +/** + * Imagemagick toolkit specific implementation of this image effect. + * + * @param stdClass $image + * An image object. + * + * @return bool + * true on success, false otherwise. + * + * @see http://www.imagemagick.org/script/command-line-options.php#auto-orient + */ +function image_imagemagick_imagecache_autorotate(stdClass $image) { + // Use the exif extension, if enabled, to figure out the new dimensions. + // Moreover (see [#2366163]): to prevent a bug in IM to incorrectly rotate the + // image when it should not, we only pass the auto-orient argument when the + // exif extension could detect the 'Orientation' tag. + if (function_exists('exif_read_data')) { + $exif = @exif_read_data(drupal_realpath($image->source)); + if (isset($exif['Orientation'])) { + switch ($exif['Orientation']) { + case 1: + // Normal orientation: no need to rotate or to change the dimensions. + break; + case 5: + case 6: + case 7: + case 8: + // 90 or 270 degrees rotation (+ optional mirror): swap dimensions. + $image->ops[] = '-auto-orient'; + $tmp = $image->info['width']; + $image->info['width'] = $image->info['height']; + $image->info['height'] = $tmp; + break; + default: + // All other orientations: pass the arguments, but the dimensions + // remain the same. + $image->ops[] = '-auto-orient'; + break; + } + } + elseif ($exif === FALSE && $image->extension === 'jpg') { + watchdog('imagecache_actions', 'Image %file could not be auto-rotated: !message', array('%file' => $image->source, '!message' => t('The exif_read_data() function returned FALSE.'))); + } + } + else { + // We do add the auto-orient argument to IM. IM will determine itself + // whether to rotate or not. + $image->ops[] = '-auto-orient'; + // However we cannot keep track of the dimensions anymore. + if ($image->info['width'] !== $image->info['height']) { + $image->info['width'] = $image->info['height'] = NULL;; + } + } + return TRUE; +} + +/** + * Image dimensions callback for this image effect. * * @param array $dimensions * An array with the dimensions (in pixels) to be modified. - * @param array $data - * An array of parameters for the autorotate effect (empty for this effect). -*/ -function imagecache_autorotate_dimensions(array &$dimensions, array $data) { + * param array $data + * An associative array containing the effect data. + */ +function imagecache_autorotate_dimensions(array &$dimensions/*, array $data*/) { // We can only know the resulting dimensions if both dimensions are equal. // Otherwise we need to inspect the image itself, which is not passed in here. - // (this callback was introduced to enhance performance by not accessing the + // (this callback was introduced to enhance performance by NOT accessing the // image file when rendering the width and height attributes of the html img // tag). if ($dimensions['width'] !== $dimensions['height']) { diff --git a/sites/all/modules/imagecache_actions/autorotate/portrait-painting.jpg b/sites/all/modules/imagecache_actions/autorotate/portrait-painting.jpg new file mode 100644 index 0000000..c6e4fdb Binary files /dev/null and b/sites/all/modules/imagecache_actions/autorotate/portrait-painting.jpg differ diff --git a/sites/all/modules/imagecache_actions/canvasactions/canvasactions.inc b/sites/all/modules/imagecache_actions/canvasactions/canvasactions.inc index 4c2d04c..b5ecf4c 100644 --- a/sites/all/modules/imagecache_actions/canvasactions/canvasactions.inc +++ b/sites/all/modules/imagecache_actions/canvasactions/canvasactions.inc @@ -1,14 +1,14 @@ toolkit); if ($mask) { // @todo: (sydneyshan) Consider best way to add offset support - I assume we @@ -83,21 +90,24 @@ function canvasactions_imagemask_image(&$image, $data = array()) { } /** - * Implements the image mask effect using the GD toolkit. + * GD toolkit specific implementation of the image mask effect. * - * $image object + * @param stdClass $image * Image object containing the GD image resource to operate on. - * $mask object + * @param stdClass $mask * An image object containing the image to use as mask. + * + * @return bool + * true on success, false otherwise. */ -function image_gd_imagemask($image, $mask) { +function image_gd_imagemask(stdClass $image, stdClass $mask) { $newPicture = imagecreatetruecolor($image->info['width'], $image->info['height']); imagesavealpha($newPicture, TRUE); imagealphablending($newPicture, TRUE); $transparent = imagecolorallocatealpha($newPicture, 0, 0, 0, 127); imagefill($newPicture, 0, 0, $transparent); - // Perform pixel-based alpha map application + // Perform pixel-based alpha map application. for ($x = 0; $x < $image->info['width']; $x++) { for ($y = 0; $y < $image->info['height']; $y++) { // Deal with images with mismatched sizes @@ -113,7 +123,7 @@ function image_gd_imagemask($image, $mask) { } } - // Copy back to original picture + // Copy back to original picture. imagedestroy($image->resource); $image->resource = $newPicture; @@ -122,30 +132,32 @@ function image_gd_imagemask($image, $mask) { /** - * Implements the image mask effect using the imagemagick toolkit. + * Imagemagick toolkit specific implementation of the image mask effect. * - * $image object - * Image object containing a.o. the image to operate on. - * $mask object + * @param stdClass $image + * Image object containing the image resource to operate on. + * @param stdClass $mask * An image object containing the image to use as mask. + * + * @return bool + * true on success, false otherwise. */ -function image_imagemagick_imagemask($image, $mask) { - $image->ops[] = escapeshellarg($mask->source) . ' -alpha Off -compose CopyOpacity -composite'; +function image_imagemagick_imagemask(stdClass $image, stdClass $mask) { + $image->ops[] = escapeshellarg($mask->source); + $image->ops[] = '-alpha Off -compose CopyOpacity -composite'; return TRUE; } -//////////////////////////////////////////////// - - /** - * Implementation of imagecache_hook_form() + * Image effect form callback for the define canvas effect. * - * Settings for preparing a canvas. + * @param array $data + * The current configuration for this image effect. * - * @param $data array of settings for this action - * @return a form definition + * @return array + * The form definition for this effect. */ -function canvasactions_definecanvas_form($data) { +function canvasactions_definecanvas_form(array $data) { module_load_include('inc', 'imagecache_actions', 'utility-color'); $defaults = array( 'RGB' => array( @@ -165,13 +177,12 @@ function canvasactions_definecanvas_form($data) { 'bottomdiff' => '', ), ); - $data = array_merge($defaults, (array) $data); + $data += $defaults; $form = array( 'RGB' => imagecache_rgb_form($data['RGB']), 'help' => array( - '#type' => 'markup', - '#value' => t('Enter no color value for transparent. This will have the effect of adding clear margins around the image.'), + '#markup' => t('Enter no color value for transparent. This will have the effect of adding clear margins around the image.'), '#prefix' => '', '#suffix' => '
', ), @@ -188,8 +199,7 @@ function canvasactions_definecanvas_form($data) { '#collapsible' => TRUE, '#title' => 'Exact size', 'help' => array( - '#type' => 'markup', - '#value' => t('Set the canvas to a precise size, possibly cropping the image. Use to start with a known size.'), + '#markup' => t('Set the canvas to a precise size, possibly cropping the image. Use to start with a known size.'), '#prefix' => '', '#suffix' => '
', ), @@ -218,8 +228,9 @@ function canvasactions_definecanvas_form($data) { '#collapsible' => TRUE, '#title' => t('Relative size'), 'help' => array( - '#type' => 'markup', - '#value' => '' . t('Set the canvas to a relative size, based on the current image dimensions. Use to add simple borders or expand by a fixed amount. Negative values may crop the image.') . '
', + '#markup' => t('Set the canvas to a relative size, based on the current image dimensions. Use to add simple borders or expand by a fixed amount. Negative values may crop the image.'), + '#prefix' => '', + '#suffix' => '
', ), 'leftdiff' => array( '#type' => 'textfield', @@ -258,10 +269,19 @@ function canvasactions_definecanvas_form($data) { return $form; } +/** @noinspection PhpDocMissingThrowsInspection */ /** - * Implementation of theme_hook() for imagecache_ui.module + * Implements theme_hook() for the define canvas effect summary. + * + * @param array $variables + * An associative array containing: + * - data: The current configuration for this image effect. + * + * @return string + * The HTML for the summary of this image effect. + * @ingroup themeable */ -function theme_canvasactions_definecanvas_summary($variables) { +function theme_canvasactions_definecanvas_summary(array $variables) { $data = $variables['data']; if ($data['exact']['width'] || $data['exact']['height']) { $w = !empty($data['exact']['width']) ? $data['exact']['width'] : '100%'; @@ -276,28 +296,22 @@ function theme_canvasactions_definecanvas_summary($variables) { $output .= ' top:' . $data['relative']['topdiff']; $output .= ' bottom:' . $data['relative']['bottomdiff']; } + /** @noinspection PhpUnhandledExceptionInspection */ $output .= theme('imagecacheactions_rgb', array('RGB' => $data['RGB'])); $output .= ($data['under']) ? t(" under image ") : t(" over image "); return $output; } /** - * Implementation of hook_image() + * Image effect callback for the define canvas effect. * - * Creates a solid background canvas + * @param stdClass $image + * @param array $data * - * Process the imagecache action on the passed image - * - * @param $image - * array defining an image file, including : - * - * $image- >source as the filename, - * - * $image->info array - * - * $image->resource handle on the image object + * @return boolean + * true on success, false otherwise. */ -function canvasactions_definecanvas_effect($image, $data) { +function canvasactions_definecanvas_effect(stdClass $image, array $data) { // May be given either exact or relative dimensions. if ($data['exact']['width'] || $data['exact']['height']) { // Allows only one dimension to be used if the other is unset. @@ -308,37 +322,126 @@ function canvasactions_definecanvas_effect($image, $data) { $data['exact']['height'] = $image->info['height']; } - $targetsize['width'] = imagecache_actions_percent_filter($data['exact']['width'], $image->info['width']); - $targetsize['height'] = imagecache_actions_percent_filter($data['exact']['height'], $image->info['height']); + $target_size['width'] = imagecache_actions_percent_filter($data['exact']['width'], $image->info['width']); + $target_size['height'] = imagecache_actions_percent_filter($data['exact']['height'], $image->info['height']); - $targetsize['left'] = image_filter_keyword($data['exact']['xpos'], $targetsize['width'], $image->info['width']); - $targetsize['top'] = image_filter_keyword($data['exact']['ypos'], $targetsize['height'], $image->info['height']); + $target_size['left'] = image_filter_keyword($data['exact']['xpos'], $target_size['width'], $image->info['width']); + $target_size['top'] = image_filter_keyword($data['exact']['ypos'], $target_size['height'], $image->info['height']); } else { - // calculate relative size - $targetsize['width'] = $image->info['width'] + $data['relative']['leftdiff'] + $data['relative']['rightdiff']; - $targetsize['height'] = $image->info['height'] + $data['relative']['topdiff'] + $data['relative']['bottomdiff']; - $targetsize['left'] = $data['relative']['leftdiff']; - $targetsize['top'] = $data['relative']['topdiff']; + // Calculate relative size. + $target_size['width'] = $image->info['width'] + ((int) $data['relative']['leftdiff']) + ((int) $data['relative']['rightdiff']); + $target_size['height'] = $image->info['height'] + ((int) $data['relative']['topdiff']) + ((int) $data['relative']['bottomdiff']); + $target_size['left'] = (int) $data['relative']['leftdiff']; + $target_size['top'] = (int) $data['relative']['topdiff']; } - // convert from hex (as it is stored in the UI) + // Convert from hex (as it is stored in the UI). if ($data['RGB']['HEX'] && $deduced = imagecache_actions_hex2rgba($data['RGB']['HEX'])) { $data['RGB'] = array_merge($data['RGB'], $deduced); } - // All the maths is done, now defer to the api toolkits; - $data['targetsize'] = $targetsize; + // All the math is done, now defer to the toolkit in use. + $data['targetsize'] = $target_size; $success = image_toolkit_invoke('definecanvas', $image, array($data)); if ($success) { - $image->info['width'] = $targetsize['width']; - $image->info['height'] = $targetsize['height']; + $image->info['width'] = $target_size['width']; + $image->info['height'] = $target_size['height']; } return $success; } +/** + * GD toolkit specific implementation of the define canvas effect. + * + * @param stdClass $image + * @param array $data + * The parameters for this effect. $data['targetsize'] is an array expected to + * contain a width, height and a left, top. + * + * @return bool + * true on success, false otherwise. + */ +function image_gd_definecanvas(stdClass $image, array $data) { + $target_size = $data['targetsize']; + $RGB = $data['RGB']; + + $newcanvas = imagecreatetruecolor($target_size['width'], $target_size['height']); + imagesavealpha($newcanvas, TRUE); + imagealphablending($newcanvas, FALSE); + imagesavealpha($image->resource, TRUE); + if ($RGB['HEX']) { + // Set color, allow it to define transparency, or assume opaque. + $background = imagecolorallocatealpha($newcanvas, $RGB['red'], $RGB['green'], $RGB['blue'], $RGB['alpha']); + } + else { + // No color, attempt transparency, assume white. + $background = imagecolorallocatealpha($newcanvas, 255, 255, 255, 127); + } + imagefilledrectangle($newcanvas, 0, 0, $target_size['width'], $target_size['height'], $background); + + if ($data['under']) { + $canvas_object = new stdClass(); + $canvas_object->resource = $newcanvas; + $canvas_object->info = array( + 'width' => $target_size['width'], + 'height' => $target_size['height'], + 'mime_type' => $image->info['mime_type'], + 'extension' => $image->info['extension'], + ); + $canvas_object->toolkit = $image->toolkit; + image_overlay($image, $canvas_object, $target_size['left'], $target_size['top'], 100, TRUE); + } + else { + $image->resource = $newcanvas; + } + return TRUE; +} + +/** + * Imagemagick toolkit specific implementation of the define canvas effect. + * + * @param stdClass $image + * @param array $data + * The parameters for this effect. $data['targetsize'] is an array expected to + * contain a width, height and a left, top. + * + * @return bool + * true on success, false otherwise. + * + * @see http://www.imagemagick.org/script/command-line-options.php#extent + */ +function image_imagemagick_definecanvas(stdClass $image, $data) { + // Reset any gravity settings from earlier effects. + $image->ops[] = '-gravity None'; + + $backgroundcolor = $data['RGB']['HEX'] != '' ? '#' . ltrim($data['RGB']['HEX'], '#') : 'None'; + $image->ops[] = '-background ' . escapeshellarg($backgroundcolor); + + $compose_operator = $data['under'] ? 'src-over' : 'dst-over'; + $image->ops[] = "-compose $compose_operator"; + + $target_size = $data['targetsize']; + $geometry = sprintf('%dx%d', $target_size['width'], $target_size['height']); + if ($target_size['left'] || $target_size['top']) { + $geometry .= sprintf('%+d%+d', -$target_size['left'], -$target_size['top']); + } + $image->ops[] = '-extent ' . escapeshellarg($geometry); + + return TRUE; +} + +/** + * Image dimensions callback for the define canvas effect. + * + * @param array $dimensions + * Dimensions to be modified - an associative array containing the items + * 'width' and 'height' (in pixels). + * @param array $data + * An associative array containing the effect data. + */ function canvasactions_definecanvas_dimensions(array &$dimensions, array $data) { // May be given either exact or relative dimensions. if ($data['exact']['width'] || $data['exact']['height']) { @@ -354,96 +457,27 @@ function canvasactions_definecanvas_dimensions(array &$dimensions, array $data) $dimensions['height'] = imagecache_actions_percent_filter($data['exact']['height'], $dimensions['height']); } else { - // calculate relative size - $dimensions['width'] = $dimensions['width'] + $data['relative']['leftdiff'] + $data['relative']['rightdiff']; - $dimensions['height'] = $dimensions['height'] + $data['relative']['topdiff'] + $data['relative']['bottomdiff']; + // Calculate relative sizes (only possible if we have the current size). + if ($dimensions['width'] !== NULL) { + $dimensions['width'] = $dimensions['width'] + (int) $data['relative']['leftdiff'] + (int) $data['relative']['rightdiff']; + } + if ($dimensions['height'] !== NULL) { + $dimensions['height'] = $dimensions['height'] + (int) $data['relative']['topdiff'] + (int) $data['relative']['bottomdiff']; + } } } -/** - * Draw a color (or transparency) behind an image - * - * $targetsize is an array expected to contain a width,height and a left,top - * offset. - */ -function image_gd_definecanvas($image, $data = array()) { - $targetsize = $data['targetsize']; - $RGB = $data['RGB']; - - $newcanvas = imagecreatetruecolor($targetsize['width'], $targetsize['height']); - imagesavealpha($newcanvas, TRUE); - imagealphablending($newcanvas, FALSE); - imagesavealpha($image->resource, TRUE); - if ($RGB['HEX']) { - // Set color, allow it to define transparency, or assume opaque. - $background = imagecolorallocatealpha($newcanvas, $RGB['red'], $RGB['green'], $RGB['blue'], $RGB['alpha']); - } - else { - // No color, attempt transparency, assume white - $background = imagecolorallocatealpha($newcanvas, 255, 255, 255, 127); - } - imagefilledrectangle($newcanvas, 0, 0, $targetsize['width'], $targetsize['height'], $background); - # imagealphablending($newcanvas, TRUE); - - if ($data['under']) { - $canvas_object = (object) array( - 'resource' => $newcanvas, - 'info' => array( - 'width' => $targetsize['width'], - 'height' => $targetsize['height'], - 'mime_type' => $image->info['mime_type'], - 'extension' => $image->info['extension'], - ), - 'toolkit' => $image->toolkit, - ); - image_overlay($image, $canvas_object, $targetsize['left'], $targetsize['top'], 100, TRUE); - } - else { - $image->resource = $newcanvas; - } - return TRUE; -} /** - * Draw a color (or transparency) behind an image - * $targetsize is an array expected to contain a width,height and a left,top - * offset. + * Image effect form callback for the underlay (background) effect. * - * See http://www.imagemagick.org/script/command-line-options.php#extent - * @todo: reset gravity? + * @param array $data + * The current configuration for this image effect. + * + * @return array + * The form definition for this effect. */ -function image_imagemagick_definecanvas($image, $data = array()) { - $backgroundcolor = $data['RGB']['HEX'] != '' ? '#' . $data['RGB']['HEX'] : 'None'; - $image->ops[] = '-background ' . escapeshellarg($backgroundcolor); - - $compose_operator = $data['under'] ? 'src-over' : 'dst-over'; - $image->ops[] = "-compose $compose_operator"; - - $targetsize = $data['targetsize']; - $geometry = sprintf('%dx%d', $targetsize['width'], $targetsize['height']); - if ($targetsize['left'] || $targetsize['top']) { - $geometry .= sprintf('%+d%+d', -$targetsize['left'], -$targetsize['top']); - } - $image->ops[] = "-extent $geometry"; - - return TRUE; -} - -//////////////////////////////////////////////// - -/** - * Place a given image under the current canvas - * - * Implementation of imagecache_hook_form() - * - * @param $data array of settings for this action - * @return a form definition - */ -function canvasactions_canvas2file_form($data) { -// if (image_get_toolkit() != 'gd') { -// drupal_set_message('Overlays are not currently supported by using imagemagick. This effect requires GD image toolkit only.', 'warning'); -// } - +function canvasactions_canvas2file_form(array $data) { $defaults = array( 'xpos' => '0', 'ypos' => '0', @@ -484,7 +518,15 @@ function canvasactions_canvas2file_form($data) { } /** - * Implementation of theme_hook() for imagecache_ui.module + * Implements theme_hook() for the underlay (background) effect summary. + * + * @param array $variables + * An associative array containing: + * - data: The current configuration for this image effect. + * + * @return string + * The HTML for the summary of this image effect. + * @ingroup themeable */ function theme_canvasactions_canvas2file_summary($variables) { $data = $variables['data']; @@ -493,17 +535,15 @@ function theme_canvasactions_canvas2file_summary($variables) { } /** - * Place the source image on the current background + * Image effect callback for the underlay (background) effect. * - * Implementation of hook_image() + * @param stdClass $image + * @param array $data * - * Note - this is currently incompatable with imagemagick, due to the way it - * addresses $image->resource directly - a gd only thing. - * - * @param $image - * @param $data + * @return boolean + * true on success, false otherwise. */ -function canvasactions_canvas2file_image(&$image, $data = array()) { +function canvasactions_canvas2file_effect(stdClass $image, array $data) { $underlay = imagecache_actions_image_load($data['path'], $image->toolkit); if ($underlay) { // To handle odd sizes, we will resize/crop the background image to the @@ -511,7 +551,7 @@ function canvasactions_canvas2file_image(&$image, $data = array()) { // imagecopymerge, and the watermark library both do not allow overlays to // be bigger than the target. - // Adjust size + // Adjust size. $crop_rules = array( 'xoffset' => 0, 'yoffset' => 0, @@ -527,7 +567,7 @@ function canvasactions_canvas2file_image(&$image, $data = array()) { // which will produce a 'cropped' image larger than the original. // In this case, we need to calculate the position of the bg image // in relation to the space it will occupy under the top layer - #$crop_rules['xoffset'] = $underlay->info['width'] - $image->info['width'] ; + //$crop_rules['xoffset'] = $underlay->info['width'] - $image->info['width'] ; $crop_rules['width'] = $image->info['width']; $crop_rules['height'] = $image->info['height']; @@ -549,14 +589,14 @@ function canvasactions_canvas2file_image(&$image, $data = array()) { // Crop both before processing to avoid unwanted processing. image_crop_effect($underlay, $crop_rules); - # BUG - this doesn't position either + // @todo: BUG - this doesn't position either // Actually this fails because imagecache_crop fills it with solid color when 'cropping' to a larger size. - #imagecache_crop_image($image, $crop_rules); - #dpm(get_defined_vars()); - // This func modifies the underlay image by ref, placing the current canvas on it + //imagecache_crop_image($image, $crop_rules); + //dpm(get_defined_vars()); + // This func modifies the underlay image by ref, placing the current canvas on it. if (image_overlay($image, $underlay, $data['xpos'], $data['ypos'], $data['alpha'], TRUE)) { - #$image->resource = $underlay->resource; - $image = $underlay; + //$image->resource = $underlay->resource; + //$image = $underlay; //@todo: this is a no-op. return TRUE; } } @@ -564,12 +604,12 @@ function canvasactions_canvas2file_image(&$image, $data = array()) { } /** - * Image dimensions callback; canvas2file (underlay/background). + * Image dimensions callback for the underlay (background) effect. * * @param array $dimensions * Dimensions to be modified - an associative array containing the items * 'width' and 'height' (in pixels). - * @param $data + * @param array $data * An associative array containing the effect data. */ function canvasactions_canvas2file_dimensions(array &$dimensions, array $data) { @@ -586,40 +626,50 @@ function canvasactions_canvas2file_dimensions(array &$dimensions, array $data) { $dimensions['height'] = $underlay->info['height']; break; case 'minimum': - $dimensions['width'] = isset($dimensions['width']) ? min($underlay->info['width'], $dimensions['width']) : NULL; - $dimensions['height'] = isset($dimensions['height']) ? min($underlay->info['height'], $dimensions['height']) : NULL; + if ($dimensions['width'] !== NULL) { + $dimensions['width'] = min($underlay->info['width'], $dimensions['width']); + } + if ($dimensions['height'] !== NULL) { + $dimensions['height'] = min($underlay->info['height'], $dimensions['height']); + } break; case 'maximum': - $dimensions['width'] = isset($dimensions['width']) ? max($underlay->info['width'], $dimensions['width']) : NULL; - $dimensions['height'] = isset($dimensions['height']) ? max($underlay->info['height'], $dimensions['height']) : NULL; - break; + if ($dimensions['width'] !== NULL) { + $dimensions['width'] = max($underlay->info['width'], $dimensions['width']); + } + if ($dimensions['height'] !== NULL) { + $dimensions['height'] = max($underlay->info['height'], $dimensions['height']); + } + break; } } } } + /** - * Place a given image on top of the current canvas + * Image effect form callback for the overlay (watermark) effect. * - * Implementation of imagecache_hook_form() + * @param array $data + * The current configuration for this image effect. * - * @param $data array of settings for this action - * @return a form definition + * @return array + * The form definition for this effect. */ -function canvasactions_file2canvas_form($data) { +function canvasactions_file2canvas_form(array $data) { $defaults = array( 'xpos' => '', 'ypos' => '', 'alpha' => '100', + 'scale' => '', 'path' => '', ); $data = array_merge($defaults, (array) $data); $form = array( 'help' => array( - '#type' => 'markup', - '#value' => t('Note that using a transparent overlay that is larger than the source image may result in unwanted results - a solid background.'), + '#markup' => t('Note that using a non transparent overlay that is larger than the source image may result in unwanted results - a solid background.'), ), ); $form += imagecache_actions_pos_form($data); @@ -627,8 +677,19 @@ function canvasactions_file2canvas_form($data) { '#type' => 'textfield', '#title' => t('opacity'), '#default_value' => $data['alpha'], + '#field_suffix' => t('%'), '#size' => 6, '#description' => t('Opacity: 0-100. Warning: Due to a limitation in the GD toolkit, using an opacity other than 100% requires the system to use an algorithm that\'s much slower than the built-in functions. If you want partial transparency, you are better to use an already-transparent png as the overlay source image.'), + '#element_validate' => array('imagecache_actions_validate_number_non_negative'), + ); + $form['scale'] = array( + '#type' => 'textfield', + '#title' => t('scale'), + '#default_value' => $data['scale'], + '#field_suffix' => t('%'), + '#size' => 6, + '#description' => t('Scales the overlay with respect to the source, thus not its own dimensions. Leave empty to use the original size of overlay image.'), + '#element_validate' => array('imagecache_actions_validate_number_positive'), ); $form['path'] = array( '#type' => 'textfield', @@ -641,25 +702,41 @@ function canvasactions_file2canvas_form($data) { } /** - * Implementation of theme_hook() for imagecache_ui.module + * Implements theme_hook() for the overlay (watermark) effect summary. + * + * @param array $variables + * An associative array containing: + * - data: The current configuration for this image effect. + * + * @return string + * The HTML for the summary of this image effect. + * @ingroup themeable */ -function theme_canvasactions_file2canvas_summary($variables) { +function theme_canvasactions_file2canvas_summary(array $variables) { $data = $variables['data']; - return '' . $data['path'] . ' x:' . $data['xpos'] . ', y:' . $data['ypos'] . ' alpha:' . (@$data['alpha'] ? $data['alpha'] : 100) . '%'; + return '' . $data['path'] . ', x:' . $data['xpos'] . ', y:' . $data['ypos'] . ', alpha:' . (!empty($data['alpha']) ? $data['alpha'] : 100) . '%' . ', scale:' . (!empty($data['scale']) ? $data['scale'].'%' : '-'); } /** - * Place the source image on the current background + * Image effect callback for the overlay (watermark) image effect. * - * Implementation of hook_image() + * @param stdClass $image + * @param array $data * - * - * @param $image - * @param $data + * @return boolean + * true on success, false otherwise. */ -function canvasactions_file2canvas_image($image, $data = array()) { +function canvasactions_file2canvas_effect(stdClass $image, array $data) { $overlay = imagecache_actions_image_load($data['path']); if ($overlay) { + if (!empty($data['scale']) && $data['scale'] > 0) { + // Scale the overlay with respect to the dimensions of the source being + // overlaid. To maintain the aspect ratio, only the width of the overlay + // is scaled like that, the height of the overlay follows the aspect + // ratio (that is why we use image_scale instead of image_resize). + $overlay_w = $image->info['width'] * $data['scale'] / 100; + image_scale($overlay, $overlay_w, NULL, TRUE); + } if (!isset($data['alpha'])) { $data['alpha'] = 100; } @@ -668,17 +745,15 @@ function canvasactions_file2canvas_image($image, $data = array()) { return FALSE; } -/////////////////////////////////////////////////////////////////// /** - * Place the source image on top of the current canvas + * Image effect form callback for the overlay: source image to canvas effect. * - * Implementation of imagecache_hook_form() + * @param array $data + * The current configuration for this image effect. * - * - * - * @param $data array of settings for this action - * @return a form definition + * @return array + * The form definition for this effect. */ function canvasactions_source2canvas_form($data) { $defaults = array( @@ -700,53 +775,64 @@ function canvasactions_source2canvas_form($data) { return $form; } - /** - * Implementation of theme_hook() for imagecache_ui.module + * Implements theme_hook() for the overlay: source img to canvas effect summary. + * + * @param array $variables + * An associative array containing: + * - data: The current configuration for this image effect. + * + * @return string + * The HTML for the summary of this image effect. + * @ingroup themeable */ -function theme_canvasactions_source2canvas_summary($variables) { +function theme_canvasactions_source2canvas_summary(array $variables) { $data = $variables['data']; return 'xpos:' . $data['xpos'] . ', ypos:' . $data['ypos'] . ' alpha:' . $data['alpha'] . '%'; } /** - * Place the source image on the current background + * Image effect callback for the overlay: source image to canvas effect. * - * Implementation of hook_image() + * @param stdClass $image + * @param array $data * - * - * @param $image - * @param $data + * @return boolean + * true on success, false otherwise. */ -function canvasactions_source2canvas_image($image, $data = array()) { +function canvasactions_source2canvas_effect(stdClass $image, array $data) { $overlay = image_load($image->source, $image->toolkit); return image_overlay($image, $overlay, $data['xpos'], $data['ypos'], $data['alpha']); } + /** - * Implements the image effect form callback for the aspect switcher effect. + * Image effect form callback for the aspect switcher effect. * * @param array $data - * Array of settings for this action + * The current configuration for this image effect. + * * @return array - * The form definition + * The form definition for this effect. */ -function canvasactions_aspect_form($data) { +function canvasactions_aspect_form(array $data) { $defaults = array( 'ratio_adjustment' => 1, - 'portrait' => NULL, - 'landscape' => NULL, + 'portrait' => '', + 'landscape' => '', ); $data = array_merge($defaults, (array) $data); $form = array( 'help' => array( - '#type' => 'markup', - '#value' => t('You must create the two presets to use before enabling this process.'), + '#markup' => t('You must create the two presets to use before enabling this process.'), ) ); - $styles = image_style_options(TRUE); + // The PASS_THROUGH parameter is new as of D7.23, and is added here to prevent + // image_style_options() from double-encoding the human-readable image style + // name, since the form API will already sanitize options in a select list. + $styles = image_style_options(TRUE, PASS_THROUGH); // @todo: remove the current style to prevent (immediate) recursion? $form['portrait'] = array( @@ -782,30 +868,39 @@ If n < 1 then blunt portraits will be treated as landscape. /** - * Implements the summary theme callback for the aspect switcher effect. + * Implements theme_hook() for the aspect switcher effect summary. * * @param array $variables + * An associative array containing: + * - data: The current configuration for this image effect. * * @return string + * The HTML for the summary of this image effect. + * @ingroup themeable */ -function theme_canvasactions_aspect_summary($variables) { +function theme_canvasactions_aspect_summary(array $variables) { $data = $variables['data']; - $ratio_adjustment = ''; + + $label = imagecache_actions_get_style_label($data['portrait']); + $output = t('Portrait size: %label', array('%label' => $label)); + $label = imagecache_actions_get_style_label($data['landscape']); + $output .= ', ' . t('Landscape size: %label', array('%label' => $label)); if ($data['ratio_adjustment'] != 1) { - $ratio_adjustment = " (switch at 1:{$data['ratio_adjustment']})"; + $output .= ', ' . t("(switch at 1:@ratio_adjustment)", array('@ratio_adjustment' => $data['ratio_adjustment'])); } - return 'Portrait size: ' . $data['portrait'] . '. Landscape size: ' . $data['landscape'] . '' . $ratio_adjustment; + return trim($output); } /** - * Implements the image effect callback for the aspect switcher effect. + * Image effect callback for the aspect switcher effect. * - * @param object $image + * @param stdClass $image * @param array $data * - * @return bool + * @return boolean + * true on success, false otherwise. */ -function canvasactions_aspect_image($image, $data = array()) { +function canvasactions_aspect_effect(stdClass $image, array $data) { $ratio_adjustment = isset($data['ratio_adjustment']) ? floatval( $data['ratio_adjustment']) : 1; $aspect = $image->info['width'] / $image->info['height']; @@ -821,7 +916,7 @@ function canvasactions_aspect_image($image, $data = array()) { if (empty($style)) { // Required preset has gone missing? - watchdog('imagecache_canvasactions', "When running 'aspect' action, I was unable to load sub-action %style_name. Either it's been deleted or the DB needs an update", array('%style_name' => $style_name), WATCHDOG_ERROR); + watchdog('imagecache_actions', "When running 'aspect' action, I was unable to load sub-action %style_name. Either it's been deleted or the DB needs an update", array('%style_name' => $style_name), WATCHDOG_ERROR); return FALSE; } @@ -833,7 +928,7 @@ function canvasactions_aspect_image($image, $data = array()) { } /** - * Implements the dimension callback for the aspect switcher effect. + * Image dimensions callback for the aspect switcher effect. * * @param array $dimensions * Dimensions to be modified - an associative array containing the items @@ -843,10 +938,756 @@ function canvasactions_aspect_image($image, $data = array()) { */ function canvasactions_aspect_dimensions(array &$dimensions, array $data) { if (empty($dimensions['width']) || empty($dimensions['height'])) { - return; + // We cannot know which preset would be executed and thus cannot know the + // resulting dimensions, unless both styles return the same dimensions: + $landscape_dimensions = $portrait_dimensions = $dimensions; + image_style_transform_dimensions($data['landscape'], $landscape_dimensions); + image_style_transform_dimensions($data['portrait'], $portrait_dimensions); + if ($landscape_dimensions == $portrait_dimensions) { + $dimensions = $landscape_dimensions; + } + else { + $dimensions['width'] = $dimensions['height'] = NULL; + } + } + else { + $ratio_adjustment = isset($data['ratio_adjustment']) ? floatval( $data['ratio_adjustment']) : 1; + $aspect = $dimensions['width'] / $dimensions['height']; + $style_name = (($aspect * $ratio_adjustment) > 1) ? $data['landscape'] : $data['portrait']; + image_style_transform_dimensions($style_name, $dimensions); } - $ratio_adjustment = isset($data['ratio_adjustment']) ? floatval( $data['ratio_adjustment']) : 1; - $aspect = $dimensions['width'] / $dimensions['height']; - $style_name = (($aspect * $ratio_adjustment) > 1) ? $data['landscape'] : $data['portrait']; - image_style_transform_dimensions($style_name, $dimensions); +} + +/** + * Image effect form callback for the resize percent effect. + * + * @param array $data + * The current configuration for this image effect. + * + * @return array + * The form definition for this effect. + */ +function canvasactions_resizepercent_form(array $data) { + $defaults = array( + 'width' => '', + 'height' => '', + ); + $data = array_merge($defaults, (array) $data); + + $form['#element_validate'] = array('image_effect_scale_validate'); + + $form['width'] = array( + '#type' => 'textfield', + '#title' => t('Width'), + '#default_value' => !empty($data['width']) ? (float) $data['width'] : '', + '#field_suffix' => ' ' . t('percent'), + '#required' => FALSE, + '#size' => 10, + '#element_validate' => array('canvasactions_resizepercent_validate'), + '#allow_negative' => FALSE, + ); + $form['height'] = array( + '#type' => 'textfield', + '#title' => t('Height'), + '#default_value' => !empty($data['height']) ? (float) $data['height'] : '', + '#field_suffix' => ' ' . t('percent'), + '#required' => FALSE, + '#size' => 10, + '#element_validate' => array('canvasactions_resizepercent_validate'), + '#allow_negative' => FALSE, + ); + + return $form; +} + +/** + * Element validate handler to ensure that a positive number is specified. + * + * @param array $element + * The form element to validate. + * @param array $form_state + * The form state. + */ +function canvasactions_resizepercent_validate($element, &$form_state) { + element_validate_number($element, $form_state); + if (!form_get_error($element)) { + if ($element['#value'] != '' && (float) $element['#value'] <= 0) { + form_error($element, t('!name must be a positive number.', array('!name' => $element['#title']))); + } + } +} +/** + * Calculate percent based on input, fallback if only one value is provided. + * + * @param array $data + * + * @return float[]|FALSE + */ +function _canvasactions_resizepercent_calculate_percent(array $data) { + // Fallback if only one value is provided. + if (empty($data['height'])) { + if (empty($data['width'])) { + return FALSE; + } + $data['height'] = $data['width']; + } + else if (empty($data['width'])) { + $data['width'] = $data['height']; + } + + // Get percentage values in decimal values. + $data['width'] = (float) $data['width'] / 100; + $data['height'] = (float) $data['height'] / 100; + + return $data; +} + +/** + * Implements theme_hook() for the resize percent effect summary. + * + * @param array $variables + * An associative array containing: + * - data: The current configuration for this image effect. + * + * @return string + * The HTML for the summary of this image effect. + * @ingroup themeable + */ +function theme_canvasactions_resizepercent_summary(array $variables) { + $data = _canvasactions_resizepercent_calculate_percent($variables['data']); + if (!$data) { + return t('Invalid effect data'); + } + if ($data['width'] != $data['height']) { + return t('@width%x@height%', array('@width' => 100 * $data['width'], '@height' => 100 * $data['height'])); + } + else { + return t('scale to @percent%', array('@percent' => (float) 100 * $data['height'])); + } +} + +/** + * Image effect callback for the resize percent effect. + * + * @param stdClass $image + * @param array $data + * + * @return boolean + * true on success, false otherwise. + */ +function canvasactions_resizepercent_effect(stdClass $image, array $data) { + $data = _canvasactions_resizepercent_calculate_percent($data); + + $data['width'] = (int) round($image->info['width'] * $data['width']); + $data['height'] = (int) round($image->info['height'] * $data['height']); + + return image_resize_effect($image, $data); +} + +/** + * Image dimensions callback for the resize percent effect. + * + * @param array $dimensions + * Dimensions to be modified - an associative array containing the items + * 'width' and 'height' (in pixels). + * @param array $data + * An associative array containing the effect data. + */ +function canvasactions_resizepercent_dimensions(array &$dimensions, array $data) { + $data = _canvasactions_resizepercent_calculate_percent($data); + + $dimensions['width'] = (int) round($dimensions['width'] * $data['width']); + $dimensions['height'] = (int) round($dimensions['height'] * $data['height']); +} + +/** + * Image effect form callback for the blur effect. + * + * @param array $data + * The current configuration for this image effect. + * + * @return array + * The form definition for this effect. + */ +function canvasactions_blur_form(array $data) { + $form['intensity'] = array( + '#type' => 'textfield', + '#title' => t('Blur intensity'), + '#description' => t('A higher intensity results in more blur. The larger the image, the larger value you need to get a really blurred image, think 50 to 100 for 600x400 images.'), + '#size' => 5, + '#default_value' => isset($data['intensity']) ? (int) $data['intensity'] : 2, + '#element_validate' => array('element_validate_integer_positive'), + ); + return $form; +} + +/** + * Implements theme_hook() for the underlay (background) effect summary. + * + * @param array $variables + * An associative array containing: + * - data: The current configuration for this image effect. + * + * @return string + * The HTML for the summary of this image effect. + * @ingroup themeable + */ +function theme_canvasactions_blur_summary($variables) { + return t('Intensity: @intensity', array('@intensity' => $variables['data']['intensity'])); +} + +/** + * Image effect callback for the resize percent effect. + * + * @param stdClass $image + * @param array $data + * + * @return boolean + * true on success, false otherwise. + */ +function canvasactions_blur_effect(stdClass $image, array $data) { + return image_toolkit_invoke('blur', $image, $data); +} + +/** + * GD toolkit specific implementation of the blur effect. + * + * @param stdClass $image + * @param int $intensity + * The number of times to apply the blur filter. + * + * @return boolean + * True on success, false otherwise. + */ +function image_gd_blur(stdClass $image, $intensity) { + $intensity = (int) $intensity; + $result = TRUE; + $i = 0; + while ($result && $i++ < $intensity) { + $result = imagefilter($image->resource, IMG_FILTER_GAUSSIAN_BLUR); + } + return $result; +} + +/** + * Imagemagick toolkit specific implementation of the blur effect. + * + * See http://www.imagemagick.org/Usage/blur/. + * + * @param stdClass $image + * @param int $intensity + * The "intensity" of the blur effect. + * + * @return boolean + * True on success, false otherwise. + */ +function image_imagemagick_blur(stdClass $image, $intensity) { + // To get similar results asd with the GD factor, we use a formula to alter + // the intensity into the sigma value that is passed to IM. + $sigma = 4.0 + 0.8 * $intensity; + $image->ops[] = '-blur ' . escapeshellarg(sprintf('0x%f', $sigma)); + return TRUE; +} + +/** + * Builds the interlace form. + * + * This effect has no options, only some help text, so the form is displayed + * anyway. + */ +function canvasactions_interlace_form() { + $form = array(); + $form['help'] = array( + '#markup' => 'There are no user-configurable options for this process.
+This effect will save the derivative image in an interlace / progressive way + which might improve perceived performance, especially for large images. + File size and loading speed will not change, but the user will pretty quickly + see a "degraded" copy of the entire image instead of a clear copy of the upper + part of the image.
+Wikipedia: Interlacing (bitmaps)
', + ); + return $form; +} + +/** + * Image effect callback for the Interlace / Progressive effect. + * + * @param stdClass $image + * @param array $data + * + * @return bool + * true on success, false otherwise. + */ +function canvasactions_interlace_effect(stdClass $image, array $data) { + return image_toolkit_invoke('interlace', $image, array($data)); +} + +/** + * GD toolkit specific implementation of the image interlace effect. + * + * @param stdClass $image + * Image object containing the GD image resource to operate on. + * param array $data + * The current configuration for this image effect. + * + * @return bool + * true on success, false otherwise. + */ +function image_gd_interlace($image/*, array $data*/) { + imageinterlace($image->resource, 1); + return TRUE; +} + +/** + * Imagemagick toolkit specific implementation of the image interlace effect. + * + * @param stdClass $image + * Image object containing the image resource to operate on. + * param array $data + * The current configuration for this image effect. + * + * @return bool + * true on success, false otherwise. + */ +function image_imagemagick_interlace($image/*, array $data*/) { + $image->ops[] = '-interlace Plane'; + return TRUE; +} + +/** + * Image effect form callback for the Perspective effect. + * + * @param array $data + * The current configuration for this image effect. + * + * @return array + * The form definition for this effect. + */ +function canvasactions_perspective_form(array $data) { + $defaults = array( + 'vanish' => 'right', + 'symmetry' => 'symmetrical', + 'distortion' => 14, + 'opposite_distortion' => 10, + ); + $data = array_merge($defaults, $data); + + $form['vanish'] = array( + '#type' => 'radios', + '#title' => t('Vanishing point'), + '#options' => array( + 'top' => t('Top'), + 'left' => t('Left'), + 'right' => t('Right'), + 'bottom' => t('Bottom'), + ), + '#theme' => 'canvasactions_perspective_anchor', + '#default_value' => $data['vanish'], + '#description' => t('The position of the vanishing point relative to the source image.'), + ); + + $form['symmetry'] = array( + '#type' => 'radios', + '#title' => t('Symmetry of image perspective'), + '#description' => t('If symmetrical, the perspective effect will be built symmetrically. If asymmetrical, you can set different distortion values for both sides. Mathematically speaking: symmetrical distortion results in an isosceles trapezoid, whereas asymmetrical distortion results in just an acute trapezoid.'), + '#default_value' => $data['symmetry'], + '#options' => array( + 'symmetrical' => t('Symmetrical perspective'), + 'asymmetrical' => t('Asymmetrical perspective'), + ), + ); + + $form['distortion'] = array( + '#type' => 'textfield', + '#title' => t('Distortion'), + '#field_suffix' => '%', + '#size' => 5, + '#default_value' => $data['distortion'], + '#element_validate' => array('canvasactions_perspective_distortion_validate'), + '#description' => t('How much the corner(s) (on the vanishing point side of the image) should move to the horizon (i.e. the line containing the vanishing point). With 0% you will have no perspective effect at all and the vanishing point will be infinitely far away. With a sum of 100%, the 2 corner(s) and the vanishing point will be the same, resulting in a triangle instead of a trapezoid. For a pleasing effect, you should choose (a) number(s) between 0 and 35%, especially with ImageMagick as that toolkit also adds some stretching within the image.'), + ); + + $form['opposite_distortion'] = array( + '#type' => 'textfield', + '#title' => t('Distortion for opposite side'), + '#states' => array( + 'visible' => array( + ':input[name="data[symmetry]"]' => array('value' => 'asymmetrical'), + ), + ), + '#field_suffix' => '%', + '#size' => 5, + '#default_value' => $data['opposite_distortion'], + '#element_validate' => array('canvasactions_perspective_distortion_validate'), + '#description' => t('How much the 2nd corner on the vanishing point side of the image should move to the horizon line containing the vanishing point.'), + ); + + $form['additional_help'] = array( + '#markup' => 'Some notes:
+Note that colorshift is a mathematical filter that doesn't always have the expected result. @@ -139,29 +238,37 @@ function coloractions_colorshift_form($action) { desaturate (greyscale) it before colorizing. The hue (color wheel) is the direction the existing colors are shifted. The tone (inner box) is the amount. - Keep the tone half-way up the left site of the color box + Keep the tone half-way up the left side of the color box for best results.
")); return $form; } - /** - * Implementation of theme_hook() for imagecache_ui.module + * Implements theme_hook() for the color shift effect summary. + * + * @param array $variables + * An associative array containing: + * - data: The current configuration for this image effect. + * + * @return string + * The HTML for the summary of this image effect. + * @ingroup themeable */ -function theme_coloractions_colorshift_summary($variables) { +function theme_coloractions_colorshift_summary(array $variables) { return theme_imagecacheactions_rgb($variables['data']); } - /** - * Implementation of hook_image() + * Image effect callback for the color shift effect. * - * Process the imagecache action on the passed image + * @param stdClass $image + * @param array $data * - * Just converts and passes the vals to the all-purpose 'filter' action + * @return bool + * true on success, false otherwise. */ -function coloractions_colorshift_image($image, $data = array()) { +function coloractions_colorshift_effect(stdClass $image, array $data) { // convert color from hex (as it is stored in the UI) if ($data['RGB']['HEX'] && $deduced = imagecache_actions_hex2rgba($data['RGB']['HEX'])) { $data['RGB'] = array_merge($data['RGB'], $deduced); @@ -170,9 +277,16 @@ function coloractions_colorshift_image($image, $data = array()) { } /** - * Implementation of hook_{toolkit}_{effect}() + * GD toolkit specific implementation of the color shift effect. + * + * @param stdClass $image + * @param array $data + * The parameters for this effect. + * + * @return bool + * true on success, false otherwise. */ -function image_gd_colorshift($image, $data = array()) { +function image_gd_colorshift(stdClass $image, array $data) { $RGB = $data['RGB']; if (!function_exists('imagefilter')) { module_load_include('inc', 'imagecache_actions', 'imagefilter'); @@ -181,9 +295,16 @@ function image_gd_colorshift($image, $data = array()) { } /** - * Implementation of hook_{toolkit}_{effect}() + * Imagemagick toolkit specific implementation of the color shift effect. + * + * @param stdClass $image + * @param array $data + * The parameters for this effect. + * + * @return bool + * true on success, false otherwise. */ -function image_imagemagick_colorshift($image, $data = array()) { +function image_imagemagick_colorshift(stdClass $image, array $data) { $RGB = $data['RGB']; $image->ops[] = "-fill rgb" . escapeshellcmd('(') . "{$RGB['red']},{$RGB['green']},{$RGB['blue']}" . escapeshellcmd(')') . " -colorize 50" . escapeshellcmd('%'); return TRUE; @@ -191,22 +312,23 @@ function image_imagemagick_colorshift($image, $data = array()) { /** - * Implementation of imagecache_hook_form() + * Image effect form callback for the color overlay effect. * - * Settings for coloroverlay actions. + * @param array $data + * The current configuration for this image effect. * - * @param $action array of settings for this action - * @return a form definition + * @return array + * The form definition for this effect. */ -function coloractions_coloroverlay_form($action) { +function coloractions_coloroverlay_form(array $data) { $defaults = array( 'RGB' => array( - 'HEX' => '#E2DB6A', + 'HEX' => '', ), ); - $action = array_merge($defaults, (array) $action); + $data = array_merge($defaults, (array) $data); $form = array('#theme' => 'imagecache_rgb_form'); - $form['RGB'] = imagecache_rgb_form($action['RGB']); + $form['RGB'] = imagecache_rgb_form($data['RGB']); $form['note'] = array('#value' => t("Note that color overlay is a mathematical filter that doesn't always have the expected result. @@ -214,29 +336,37 @@ function coloractions_coloroverlay_form($action) { desaturate (greyscale) it before colorizing. The hue (color wheel) is the direction the existing colors are shifted. The tone (inner box) is the amount. - Keep the tone half-way up the left site of the color box + Keep the tone half-way up the left side of the color box for best results.
")); return $form; } - /** - * Implementation of theme_hook() for imagecache_ui.module + * Implements theme_hook() for the color overlay effect summary. + * + * @param array $variables + * An associative array containing: + * - data: The current configuration for this image effect. + * + * @return string + * The HTML for the summary of this image effect. + * @ingroup themeable */ -function theme_coloractions_coloroverlay_summary($variables) { +function theme_coloractions_coloroverlay_summary(array $variables) { return theme_imagecacheactions_rgb($variables['data']); } - /** - * Implementation of hook_image() + * Image effect callback for the color overlay effect. * - * Process the imagecache action on the passed image + * @param stdClass $image + * @param array $data * - * Just converts and passes the vals to the all-purpose 'filter' action + * @return bool + * true on success, false otherwise. */ -function coloractions_coloroverlay_image($image, $data = array()) { +function coloractions_coloroverlay_effect(stdClass $image, array $data) { // convert color from hex (as it is stored in the UI) if ($data['RGB']['HEX'] && $deduced = imagecache_actions_hex2rgba($data['RGB']['HEX'])) { $data['RGB'] = array_merge($data['RGB'], $deduced); @@ -245,9 +375,16 @@ function coloractions_coloroverlay_image($image, $data = array()) { } /** - * Implementation of hook_{toolkit}_{effect}() + * GD toolkit specific implementation of the color overlay effect. + * + * @param stdClass $image + * @param array $data + * The parameters for this effect. + * + * @return bool + * true on success, false otherwise. */ -function image_gd_coloroverlay($image, $data = array()) { +function image_gd_coloroverlay(stdClass $image, array $data) { $RGB = $data['RGB']; $w = $image->info['width']; @@ -282,130 +419,316 @@ function image_gd_coloroverlay($image, $data = array()) { } /** - * Implementation of hook_{toolkit}_{effect}() + * Imagemagick toolkit specific implementation of the color overlay effect. + * + * @param stdClass $image + * @param array $data + * The parameters for this effect. + * + * @return bool + * true on success, false otherwise. */ -function image_imagemagick_coloroverlay($image, $data = array()) { +function image_imagemagick_coloroverlay(stdClass $image, array $data) { $RGB = $data['RGB']; $image->ops[] = escapeshellcmd('(') . " +clone +matte -fill rgb" . escapeshellcmd('(') . "{$RGB['red']},{$RGB['green']},{$RGB['blue']}" . escapeshellcmd(')') . " -colorize 100" . escapeshellcmd('%') . " +clone +swap -compose overlay -composite " . escapeshellcmd(')') . " -compose SrcIn -composite"; return TRUE; } +/** + * Image effect form callback for the color multiply effect. + * + * @param array $data + * The current configuration for this image effect. + * + * @return array + * The form definition for this effect. + */ +function coloractions_colormultiply_form(array $data) { + $defaults = array( + 'RGB' => array( + 'HEX' => '', + ), + ); + $data = array_merge($defaults, (array) $data); + $form = array('#theme' => 'imagecache_rgb_form'); + $form['RGB'] = imagecache_rgb_form($data['RGB']); + $form['note'] = array('#value' => t("+ Note that color multiply is a mathematical filter that doesn't always + have the expected result. + To shift an image precisely TO a target color, + desaturate (greyscale) it before colorizing. + The hue (color wheel) is the direction the + existing colors are shifted. The tone (inner box) is the amount. + Keep the tone half-way up the left side of the color box + for best results. +
")); + return $form; +} /** - * Implementation of imagecache_hook_form() + * Implements theme_hook() for the color multiply effect summary. * - * Settings for colorshift actions. + * @param array $variables + * An associative array containing: + * - data: The current configuration for this image effect. * - * @param $action array of settings for this action - * @return a form definition + * @return string + * The HTML for the summary of this image effect. + * @ingroup themeable */ -function coloractions_brightness_form($action) { +function theme_coloractions_colormultiply_summary(array $variables) { + return theme_imagecacheactions_rgb($variables['data']); +} + +/** + * Image effect callback for the color multiply effect. + * + * @param stdClass $image + * @param array $data + * + * @return bool + * true on success, false otherwise. + */ +function coloractions_colormultiply_effect(stdClass $image, array $data) { + // Convert color from hex (as it is stored in the UI). + if ($data['RGB']['HEX'] && $deduced = imagecache_actions_hex2rgba($data['RGB']['HEX'])) { + $data['RGB'] = array_merge($data['RGB'], $deduced); + } + return image_toolkit_invoke('colormultiply', $image, array($data)); +} + +/** + * GD toolkit specific implementation of the color multiply effect. + * + * @param stdClass $image + * @param array $data + * The parameters for this effect. + * + * @return bool + * true on success, false otherwise. + */ +function image_gd_colormultiply(stdClass $image, array $data) { + $factor_r = $data['RGB']['red'] / 255; + $factor_g = $data['RGB']['green'] / 255; + $factor_b = $data['RGB']['blue'] / 255; + + $w = $image->info['width']; + $h = $image->info['height']; + + for ($y = 0; $y < $h; $y++) { + for ($x = 0; $x < $w; $x++) { + $rgb = imagecolorat($image->resource, $x, $y); + $source = imagecolorsforindex($image->resource, $rgb); + + $final_r = (int) ($source['red'] * $factor_r); + $final_g = (int) ($source['green'] * $factor_g); + $final_b = (int) ($source['blue'] * $factor_b); + + $final_colour = imagecolorallocate($image->resource, $final_r, $final_g, $final_b); + if ($final_colour === FALSE) { + return FALSE; + } + if (!imagesetpixel($image->resource, $x, $y, $final_colour)) { + return FALSE; + } + } + } + + return TRUE; +} + +/** + * Imagemagick toolkit specific implementation of the color multiply effect. + * + * @param stdClass $image + * @param array $data + * The parameters for this effect. + * + * @return bool + * true on success, false otherwise. + */ +function image_imagemagick_colormultiply(stdClass $image, array $data) { + $multiply_color = $data['RGB']['HEX'] != '' ? '#' . ltrim($data['RGB']['HEX'], '#') : 'None'; + $image->ops[] = escapeshellcmd('(') . ' +clone -fill ' . escapeshellarg($multiply_color) . ' -colorize 100 ' . escapeshellcmd(')'); + $image->ops[] = '-compose multiply -composite'; + return TRUE; +} + + +/** + * Image effect form callback for the brightness effect. + * + * @param array $data + * The current configuration for this image effect. + * + * @return array + * The form definition for this effect. + */ +function coloractions_brightness_form(array $data) { $default = array('filter_arg1' => '100'); - $action = array_merge($default, (array) $action); + $data = array_merge($default, (array) $data); $form = array(); $form['help'] = array('#value' => t("The brightness effect seldom looks good on its own, but can be useful to wash out an image before making it transparent - eg for a watermark.")); $form['filter_arg1'] = array( '#type' => 'textfield', '#title' => t('Brightness'), '#description' => t('-255 - +255'), - '#default_value' => $action['filter_arg1'], + '#default_value' => $data['filter_arg1'], '#size' => 3, ); return $form; } /** - * Implementation of hook_image() + * Implements theme_hook() for the brightness effect summary. * - * Process the imagecache action on the passed image + * @param array $variables + * An associative array containing: + * - data: The current configuration for this image effect. + * + * @return string + * The HTML for the summary of this image effect. + * @ingroup themeable */ -function coloractions_brightness_image($image, $data = array()) { +function theme_coloractions_brightness_summary(array $variables) { + return t("Adjust") . " : " . $variables['data']['filter_arg1']; +} + +/** + * Image effect callback for the brightness effect. + * + * @param stdClass $image + * @param array $data + * + * @return bool + * true on success, false otherwise. + */ +function coloractions_brightness_effect(stdClass $image, array $data) { return image_toolkit_invoke('brightness', $image, array($data)); } /** - * Implementation of hook_{toolkit}_{effect}() + * GD toolkit specific implementation of the brightness effect. + * + * @param stdClass $image + * @param array $data + * The parameters for this effect. + * + * @return bool + * true on success, false otherwise. */ -function image_gd_brightness($image, $data = array()) { +function image_gd_brightness(stdClass $image, array $data) { if (!function_exists('imagefilter')) { module_load_include('inc', 'imagecache_actions', 'imagefilter'); } return imagefilter($image->resource, 2, $data['filter_arg1']); } + /** - * Implementation of hook_{toolkit}_{effect}() + * Imagemagick toolkit specific implementation of the brightness effect. + * + * @param stdClass $image + * @param array $data + * The parameters for this effect. + * + * @return bool + * true on success, false otherwise. */ -function image_imagemagick_brightness($image, $data = array()) { +function image_imagemagick_brightness(stdClass $image, array $data) { $image->ops[] = "-modulate " . (int)(100 + ( $data['filter_arg1'] / 128 * 100 )); return TRUE; } -/** - * Implementation of theme_hook() for imagecache_ui.module - */ -function theme_coloractions_brightness_summary($variables) { - return t("Adjust") . " : " . $variables['data']['filter_arg1']; -} - /** - * Implementation of imagecache_hook_form() + * Image effect form callback for the image invert effect. * - * No settings. + * This effect has no parameters. * - * @param $action array of settings for this action - * @return a form definition + * param array $data + * The current configuration for this image effect. + * + * @return array + * The form definition for this effect. */ -function coloractions_inverse_form($action) { +function coloractions_invert_form(/*array $data*/) { $form = array(); return $form; } /** - * Implementation of hook_image() + * Image effect callback for the image invert effect. * - * Process the imagecache action on the passed image + * @param stdClass $image + * @param array $data + * + * @return bool + * true on success, false otherwise. */ -function coloractions_inverse_image($image, $data = array()) { - return image_toolkit_invoke('inverse', $image, array($data)); +function coloractions_invert_effect(stdClass $image, array $data) { + return image_toolkit_invoke('invert', $image, array($data)); } /** - * Implementation of hook_{toolkit}_{effect}() + * GD toolkit specific implementation of the image invert effect. + * + * @param stdClass $image + * param array $data + * The parameters for this effect. + * + * @return bool + * true on success, false otherwise. */ -function image_gd_inverse($image, $data = array()) { +function image_gd_invert(stdClass $image/*, array $data*/) { if (!function_exists('imagefilter')) { module_load_include('inc', 'imagecache_actions', 'imagefilter'); } return imagefilter($image->resource, 0); } -/** - * Implementation of hook_{toolkit}_{effect}() - */ -function image_imagemagick_inverse(&$image, $data = array()) { - // TODO - return FALSE; -} /** - * Implementation of imagecache_hook_form() + * Imagemagick toolkit specific implementation of the image invert effect. * - * @param $action array of settings for this action - * @return a form definition + * @param stdClass $image + * param array $data + * The parameters for this effect. + * + * @return bool + * true on success, false otherwise. */ -function coloractions_convert_form($action) { +function image_imagemagick_invert(stdClass $image/*, array $data*/) { + // http://www.imagemagick.org/script/command-line-options.php?#negate + $image->ops[] = "-negate"; + return TRUE; +} + + +/** + * Image effect form callback for the convert image format effect. + * + * @param array $data + * The current configuration for this image effect. + * + * @return array + * The form definition for this effect. + */ +function coloractions_convert_form(array $data) { + $defaults = array( + 'format' => 'image/png', + 'quality' => '75', + ); + $data = array_merge($defaults, $data); + $form = array( 'help' => array( - '#type' => 'markup', - '#value' => t("If you've been using transparencies in the process, the result may get saved as a PNG (as the image was treated as a one in in-between processes). If this is not desired (file sizes may get too big) you should use this process to force a flatten action before saving. "), + '#markup' => t("If you've been using transparencies in the process, the result may get saved as a PNG (as the image was treated as a one in in-between processes). If this is not desired (file sizes may get too big) you should use this process to force a flatten action before saving. "), ), 'help2' => array( - '#type' => 'markup', - '#value' => t("For technical reasons, changing the file format within imagecache does not change the filename suffix. A png may be saved as a *.jpg or vice versa. This may confuse some browsers and image software, but most of them have no trouble. "), + '#markup' => t("For technical reasons, changing the file format within imagecache does not change the filename suffix. A png may be saved as a *.jpg or vice versa. This may confuse some browsers and image software, but most of them have no trouble. "), ), 'format' => array( '#title' => t("File format"), '#type' => 'select', - '#default_value' => isset($action['format']) ? $action['format'] : 'image/png', + '#default_value' => isset($data['format']) ? $data['format'] : 'image/png', '#options' => coloractions_file_formats(), ), 'quality' => array( @@ -414,15 +737,24 @@ function coloractions_convert_form($action) { '#description' => t('Override the default image quality. Works for Imagemagick only. Ranges from 0 to 100. For jpg, higher values mean better image quality but bigger files. For png it is a combination of compression and filter'), '#size' => 10, '#maxlength' => 3, - '#default_value' => isset($action['quality']) ? $action['quality'] : '75', + '#default_value' => $data['quality'], '#field_suffix' => '%', ), ); + return $form; } /** - * Implementation of theme_hook() for imagecache_ui.module + * Implements theme_hook() for the convert image format effect summary. + * + * @param array $variables + * An associative array containing: + * - data: The current configuration for this image effect. + * + * @return string + * The HTML for the summary of this image effect. + * @ingroup themeable */ function theme_coloractions_convert_summary($variables) { $data = $variables['data']; @@ -439,44 +771,58 @@ function theme_coloractions_convert_summary($variables) { } /** - * Implementation of hook_image() + * Image effect callback for the convert image format effect. * - * Process the imagecache action on the passed image + * @param stdClass $image + * @param array $data + * + * @return bool + * true on success, false otherwise. */ -function coloractions_convert_image($image, $data = array()) { +function coloractions_convert_effect(stdClass $image, array $data) { $formats = coloractions_file_formats(); $image->info['mime_type'] = $data['format']; $image->info['extension'] = $formats[$data['format']]; - image_toolkit_invoke('convert_image', $image, array($data)); + image_toolkit_invoke('convert', $image, array($data)); return TRUE; } /** - * Implementation of hook_{toolkit}_{effect}() + * GD toolkit specific implementation of the convert image format effect. * - * image_toolkit_invoke will exit with an error when no implementation is - * provided for the active toolkit so provide an empty operation for the GD - * tookit + * param stdClass $image + * param array $data + * The parameters for this effect. + * + * @return bool + * true on success, false otherwise. */ -function image_gd_convert_image($image, $data) { +function image_gd_convert(/*stdClass $image, array $data*/) { return TRUE; } /** - * Implements hook_{toolkit}_{effect}(). + * Imagemagick toolkit specific implementation of the color shift effect. * * Converting the image format with imagemagick is done by prepending the output * format to the target file separated by a colon (:). This is done with * hook_imagemagick_arguments_alter(), see below. + * + * @param stdClass $image + * @param array $data + * The parameters for this effect. + * + * @return bool + * true on success, false otherwise. */ -function image_imagemagick_convert_image($image, $data) { +function image_imagemagick_convert(stdClass $image, array $data) { $image->ops['output_format'] = $image->info['extension']; $image->ops['custom_quality_value'] = (int) $data['quality']; return TRUE; } /** - * Implements hook_imagemagick_arguments_alter. + * Implements hook_imagemagick_arguments_alter(). * * This hook moves a change in output format from the args (action list) to the * destination format setting within the context. @@ -494,8 +840,493 @@ function imagecache_coloractions_imagemagick_arguments_alter(&$args, &$context) /** * Mini mime-type list + * + * image_type_to_extension and image_type_to_mime_type? */ function coloractions_file_formats() { return array('image/jpeg' => 'jpg', 'image/gif' => 'gif', 'image/png' => 'png'); } + +/** + * Image effect form callback for the posterize effect. + * + * @param array $data + * The current configuration for this image effect. + * + * @return array + * The form definition for this effect. + */ +function coloractions_posterize_form(array $data) { + $form = array(); + + $form['colors'] = array( + '#type' => 'textfield', + '#title' => t('Color levels per channel'), + '#default_value' => isset($data['colors']) ? $data['colors'] : '', + '#required' => TRUE, + '#size' => 10, + '#element_validate' => array('image_effect_integer_validate'), + '#allow_negative' => FALSE, + '#description' => t('Number of unique values per color channel to reduce this image to. The transparency channel is left unchanged. This effect can be used to reduce file size on png images.'), + ); + + return $form; +} + +/** + * Implements theme_hook() for the posterize effect summary. + * + * @param array $variables + * An associative array containing: + * - data: The current configuration for this image effect. + * + * @return string + * The HTML for the summary of this image effect. + * @ingroup themeable + */ +function theme_coloractions_posterize_summary(array $variables) { + return t(': Reduce to @colors color levels per channel', array('@colors' => $variables['data']['colors'])); +} + +/** + * Image effect callback for the posterize effect. + * + * @param stdClass $image + * @param array $data + * + * @return bool + * true on success, false otherwise. + */ +function coloractions_posterize_effect(stdClass $image, array $data) { + if (!image_toolkit_invoke('posterize', $image, array($data['colors']))) { + watchdog('imagecache_actions', 'Image posterize failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array( + '%toolkit' => $image->toolkit, + '%path' => $image->source, + '%mimetype' => $image->info['mime_type'], + '%dimensions' => $image->info['height'] . 'x' . $image->info['height'], + ), WATCHDOG_ERROR); + return FALSE; + } + return TRUE; +} + +/** + * GD toolkit specific implementation of the posterize effect. + * + * Based on: + * http://www.qtcentre.org/threads/36385-Posterizes-an-image-with-results-identical-to-Gimp-s-Posterize-command?p=167712#post167712 + * + * @param stdClass $image + * @param int $colors + * The parameter for this effect. + * + * @return bool + * true on success, false otherwise. + */ +function image_gd_posterize(stdClass $image, $colors) { + // Value step for colors per channel. + $round_to = 255 / ($colors - 1); + $alpha_bit_mask = 255 << 24; + + for ($x = imagesx($image->resource); $x--; ) { + for ($y = imagesy($image->resource); $y--; ) { + $rgb = imagecolorat($image->resource, $x, $y); + + // Use bitmasks to extract numbers we want, faster equivalent to imagecolorsforindex(). + $a = $rgb & $alpha_bit_mask; // Alpha + $r = $rgb >> 16 & 255; // Red + $g = $rgb >> 8 & 255; // Green + $b = $rgb & 255; // Blue + + // (int) (value + 0.5) faster equivalent to round() and already an int. + $new_r = (int) (((int) ($r / $round_to + 0.5)) * $round_to + 0.5); + $new_g = (int) (((int) ($g / $round_to + 0.5)) * $round_to + 0.5); + $new_b = (int) (((int) ($b / $round_to + 0.5)) * $round_to + 0.5); + + // Faster equivalent to imagecolorallocatealpha(). + $color_combined = $a | ($new_r << 16) | ($new_g << 8) | $new_b; + imagesetpixel($image->resource, $x, $y, $color_combined); + } + } + + return TRUE; +} + +/** + * Imagemagick toolkit specific implementation of the color shift effect. + * + * @param stdClass $image + * @param int $colors + * The parameter for this effect. + * + * @return bool + * true on success, false otherwise. + */ +function image_imagemagick_posterize(stdClass $image, $colors) { + // In newer versions of ImageMagick dithering has no effect on posterize. + // Turn dithering off on older versions of ImageMagick for consistency. + $image->ops[] = ' +dither -posterize ' . (int) $colors; + return TRUE; +} + +/** + * Image effect form callback for the brightness effect. + * + * Settings for color level adjustment actions. + * + * @param array $data + * The current configuration for this image effect. + * + * @return array + * The form definition for this effect. + */ +function coloractions_adjustlevels_form(array $data) { + + $defaults = array( + 'independent_colors' => FALSE, + 'all_colors' => array( + 'low' => 0, + 'high' => 1, + ), + 'per_color' => array( + 'low_red' => 0, + 'high_red' => 1, + 'low_green' => 0, + 'high_green' => 1, + 'low_blue' => 0, + 'high_blue' => 1, + ), + ); + $data = array_merge($defaults, $data); + + $form = array( + '#type' => 'container', + 'help' => array( + '#type' => 'markup', + '#markup' => t("Adjusting color levels scales the given channels to a range specified by the 'low' and 'high' values. + These 'low' and 'high' values can be any value between 0 and 1. + E.g. assume that 'low' is 0.2 and 'high' is 0.9. + Pixels that had a value of 0, will get a value of 0.2 * 255 = 51 and pixels that had a value of 255, will get a value of 0.9 * 255 = 229.
+Note that color level adjustment is a mathematical filter and a such doesn't do automatic balancing.
"), + ), + '#element_validate' => array('coloractions_validate_form'), + ) ; + + $form['independent_colors'] = array( + '#type' => 'checkbox', + '#title' => t('Set each color independently'), + '#default_value' => $data['independent_colors'], + ); + + $form['all_colors'] = array( + '#type' => 'container', + '#tree' => TRUE, + '#title' => t('All colors range'), + '#required' => !$data['independent_colors'], + '#states' => array( + 'visible' => array(':input[name="data[independent_colors]"]' => array('checked' => FALSE)), + 'required' => array(':input[name="data[independent_colors]"]' => array('checked' => FALSE)), + ), + ); + $form['all_colors'] += coloractions_adjustlevels_form_helper(array( + 'low' => array('title' => t('Low'), 'default' => $data['all_colors']['low']), + 'high' => array('title' => t('High'), 'default' => $data['all_colors']['high']), + )); + + $form['per_color'] = array( + '#type' => 'container', + '#tree' => TRUE, + '#title' => t('Individual Color Ranges'), + '#required' => $data['independent_colors'], + '#states' => array( + 'visible' => array(':input[name="data[independent_colors]"]' => array('checked' => TRUE)), + 'required' => array(':input[name="data[independent_colors]"]' => array('checked' => TRUE)), + ), + ); + $form['per_color'] += coloractions_adjustlevels_form_helper(array( + 'low_red' => array('title' => t('Red Low'), 'default' => $data['per_color']['low_red']), + 'high_red' => array('title' => t('Red High'), 'default' => $data['per_color']['high_red']), + 'low_green' => array('title' => t('Green Low'), 'default' => $data['per_color']['low_green']), + 'high_green' => array('title' => t('Green High'), 'default' => $data['per_color']['high_green']), + 'low_blue' => array('title' => t('Blue Low'), 'default' => $data['per_color']['low_blue']), + 'high_blue' => array('title' => t('Blue High'), 'default' => $data['per_color']['high_blue']), + )); + + return $form; +} + +/** + * Helper function to create the form for the color level adjustment effect. + * + * @param array $data + * Array containing the form elements + * names as keys for array elements containing title and default value. + * + * @return array + */ +function coloractions_adjustlevels_form_helper(array $data) { + $form = array(); + + foreach ($data as $name => $value) { + $form[$name] = array( + '#type' => 'textfield', + '#title' => $value['title'], + '#default_value' => $value['default'], + '#size' => 5, + '#element_validate' => array('coloractions_validate_scale_0_1'), + ); + } + + return $form; +} + +/** + * Form element validation handler for elements that should contain a number + * between 0 and 1. + */ +function coloractions_validate_scale_0_1($element/*, &$form_state*/) { + $value = $element['#value']; + if ($value != '' && (!is_numeric($value) || (float) $value > 1.0 || (float) $value < 0.0)) { + form_error($element, t('%name must be a value between 0 and 1.', array('%name' => $element['#title']))); + } +} + +/** + * Form element validation handler that compares low and high values. + */ +function coloractions_validate_form($element/*, &$form_state*/) { + $independent_colors = !empty($element['independent_colors']['#value']); + if (!$independent_colors) { + // Compare low and high. + coloractions_validate_low_and_high($element, 'all_colors', ''); + } + else { + // Compare low and high per color + coloractions_validate_low_and_high($element, 'per_color', '_red'); + coloractions_validate_low_and_high($element, 'per_color', '_green'); + coloractions_validate_low_and_high($element, 'per_color', '_blue'); + } +} + +function coloractions_validate_low_and_high($element, $fieldset, $suffix) { + if ((float) $element[$fieldset]["low$suffix"]['#value'] > (float) $element[$fieldset]["high$suffix"]['#value']) { + form_error($element[$fieldset]["high$suffix"], t('%name-high must be higher then %name-low.', + array('%name-high' => $element[$fieldset]["high$suffix"]['#title'], '%name-low' => $element[$fieldset]["low$suffix"]['#title']))); + } + +} + +/** + * Implements theme_hook() for the adjust color levels effect summary. + * + * @param array $variables + * An associative array containing: + * - data: The current configuration for this image effect. + * + * @return string + * The HTML for the summary of this image effect. + * @ingroup themeable + */ +function theme_coloractions_adjustlevels_summary(array $variables) { + $data = $variables['data']; + if (empty($data['independent_colors'])) { + return t('@range', + array('@range' => "[{$data['all_colors']['low']} - {$data['all_colors']['high']}]")); + } + else { + return t('red: @red-range, green: @green-range, blue: @blue-range', + array('@red-range' => "[{$data['per_color']['low_red']} - {$data['per_color']['high_red']}]", + '@green-range' => "[{$data['per_color']['low_green']} - {$data['per_color']['high_green']}]", + '@blue-range' => "[{$data['per_color']['low_blue']} - {$data['per_color']['high_blue']}]")); + } +} + +/** + * Image effect callback for the adjust levels effect. + * + * @param stdClass $image + * @param array $data + * + * @return bool + * true on success, false otherwise. + */ +function coloractions_adjustlevels_effect(stdClass $image, array $data) { + return image_toolkit_invoke('adjustlevels', $image, array($data)); +} + +/** + * GD toolkit specific implementation of the adjust levels effect. + * + * @param stdClass $image + * @param array $data + * The parameters for this effect. + * + * @return bool + * true on success, false otherwise. + */ +function image_gd_adjustlevels(stdClass $image, array $data) { + + $width = $image->info['width']; + $height = $image->info['height']; + + if ($data['independent_colors']) { + $lower_r = $data['per_color']['low_red'] * 255; + $factor_r = ($data['per_color']['high_red'] * 255 - $lower_r) / 255; + $lower_g = $data['per_color']['low_green'] * 255; + $factor_g = ($data['per_color']['high_green'] * 255 - $lower_g) / 255; + $lower_b = $data['per_color']['low_blue'] * 255; + $factor_b = ($data['per_color']['high_blue'] * 255 - $lower_b) / 255; + } + else { + $lower_r = $lower_g = $lower_b = $data['all_colors']['low'] * 255; + $factor_r = $factor_g = $factor_b = ($data['all_colors']['high'] * 255 - $lower_r) / 255; + } + + for ($y = 0; $y < $height; $y++) { + for ($x = 0; $x < $width; $x++) { + $rgb = imagecolorat($image->resource, $x, $y); + $source = imagecolorsforindex($image->resource, $rgb); + + $final_r = $lower_r + $factor_r * $source['red']; + $final_g = $lower_g + $factor_g * $source['green']; + $final_b = $lower_b + $factor_b * $source['blue']; + + $final_colour = imagecolorallocatealpha($image->resource, $final_r, $final_g, $final_b, $source['alpha']); + imagesetpixel($image->resource, $x, $y, $final_colour); + } + } + + return TRUE; +} + +/** + * Implements theme_hook() for the desaturate alpha effect summary. + * + * param array $variables + * An associative array containing: + * - data: The current configuration for this image effect. + * + * @return string + * The HTML for the summary of this image effect. + * @ingroup themeable + */ +function theme_coloractions_desaturatealpha_summary(/*array $variables*/) { + return t(': Desaturates the image while retaining transparency.'); +} + +/** + * Image effect callback for the desaturate alpha effect. + * + * @param stdClass $image + * @param array $data + * + * @return bool + * true on success, false otherwise. + */ +function coloractions_desaturatealpha_effect(stdClass $image, array $data) { + return image_toolkit_invoke('desaturatealpha', $image, array($data)); +} + +/** + * GD toolkit specific implementation of the adjust levels effect. + * + * @param stdClass $image + * param array $data + * The parameters for this effect. + * + * @return bool + * true on success, false otherwise. + */ +function image_gd_desaturatealpha(stdClass $image/*, array $data*/) { + imagealphablending($image->resource, FALSE); + $result = imagefilter($image->resource, IMG_FILTER_GRAYSCALE); + imagealphablending($image->resource, TRUE); + return $result; +} + +/** + * Image effect form callback for the remove animation effect. + * + * This effect has no parameters. + * + * param array $data + * The current configuration for this image effect. + * + * @return array + * The form definition for this effect. + */ +function coloractions_removeanimation_form(/*array $data*/) { + $form = array(); + $form['help'] = array( + '#markup' => "There are no user-configurable options for this effect.
+This image effect will remove all animation from a gif image, keeping only the first frame. Some notes:
+Alpha toning is an advanced method of greyscaling or colorizing. + $form['description'] = array( + '#value' => t( + "
Alpha toning is an advanced method of greyscaling or colorizing. It works using transparency, not colour matching. The results of this filter are excellent for using as watermarks, and for 'sepia' type imprints on coloured or textured backgrounds. @@ -58,9 +59,10 @@ function imagecache_alpha_form($action) { unless you either flatten this image against a background color or image in a later process or convert it to a PNG before saving using available imagecache actions.
" - )); + ) + ); - $form['RGB'] = imagecache_rgb_form($action['RGB']); + $form['RGB'] = imagecache_rgb_form($data['RGB']); $form['RGB']['#type'] = 'fieldset'; $form['RGB']['#title'] = t('Fill Color'); $form['RGB']['HEX']['#description'] = t(" @@ -70,114 +72,121 @@ function imagecache_alpha_form($action) { Set it to nothing to not perform any color shift. "); - $form['flatten'] = array( '#type' => 'checkbox', '#title' => t('Flatten Transparency'), - '#default_value' => $action['flatten'], + '#default_value' => $data['flatten'], '#return_value' => TRUE, '#description' => t("The opposite of adding alpha transparency, 'flatten' will place the given colour solidly behind the image. Use this if you can't trust IE, or you really do want the image filled in with a solid colour."), ); + return $form; } - /** - * Implementation of theme_hook() for imagecache_ui.module + * Implements theme_hook() for the alpha effect summary. + * + * @param array $variables + * An associative array containing: + * - data: The current configuration for this image effect. + * + * @return string + * The HTML for the summary of this image effect. + * + * @ingroup themeable */ -function theme_imagecache_alpha($variables) { - $element = $variables['element']; - return ($element['#value']['flatten'] ? t("Flatten") : t("Transparent")) - . ($element['#value']['opacity'] ? " : " . ($element['#value']['opacity'] * 100) . '%' : '') - . " : " . theme_imagecacheactions_rgb($element['#value']['RGB']); +function theme_coloractions_alpha_summary(array $variables) { + $data = $variables['data']; + return ($data['flatten'] ? t("Flatten") : t("Transparent")) + . ($data['opacity'] ? " : " . ($data['opacity'] * 100) . '%' : '') + . " : " . theme_imagecacheactions_rgb($data['RGB']); } - /** - * Given an image, manipulate the transparancy behaviour. - * - * implementation of hook_image() + * Image effect callback for the alpha effect. * * Either convert light parts of an image to see-through, or place a solid * colour behind areas that would otherwise be see-though * - * An imagecache_action_hook() . Handle a pipelined image transformation. + * To save a partially transparent image, the image resource must be switched to + * PNG. REMEMBER TO SWITCH IT BACK if needed. * - * To save a partially transparent image, the image resource must be switched to PNG. - * REMEMBER TO SWITCH IT BACK if needed + * @param stdClass $image + * @param array $data * - * @param $image handle on the image definition, including a gd image resource - * to act upon - * @param $data settings for this process. - * @return bool success + * @return bool + * true on success, false otherwise. */ -function imagecache_alpha_image($image, $data = array()) { - if (! $data['flatten']) { - // given an image, convert dark areas to opaque, - // light to transparent, +function coloractions_alpha_effect(stdClass $image, array $data) { + // @todo: Extract to GD specific function. + if (!$data['flatten']) { + // Given an image, convert dark areas to opaque, light to transparent. return png_color2alpha($image, $data['RGB']['HEX'], $data['opacity']); } else { - // Do the opposite, flatten the transparency ONTO the given colour + // Do the opposite, flatten the transparency ONTO the given color. $info = $image->info; if (!$info) { - watchdog("imagecache", "Problem converting image to fill behind. Source image returned no info"); - #dsm($source); - return; // error + watchdog('imagecache_actions', "Problem converting image to fill behind. Source image returned no info"); + return FALSE; } $base_image = imagecreatetruecolor($info['width'], $info['height']); imagesavealpha($base_image, TRUE); imagealphablending($base_image, FALSE); - // Start with a solid colour + // Start with a solid color. $background_rgb = imagecache_actions_hex2rgba($data['RGB']['HEX']); - // Setting the background colour here solid is what flattens the image - $background_color = @imagecolorallocatealpha($base_image, $background_rgb['red'], $background_rgb['green'], $background_rgb['blue'], 0); - - // But what I really want to do is set it - // coloured rgb AND 100% transparent, in the hope that - // a failure to render transparency would instead render + // Setting the background color here solid is what flattens the image. + // But what I really want to do is set it colored rgb AND 100% transparent, + // in the hope that a failure to render transparency would instead render // THAT colour. $background_color = @imagecolorallocatealpha($base_image, $background_rgb['red'], $background_rgb['green'], $background_rgb['blue'], 0); - // But that still doesn't work. - // Yet somehow I've seen transparent images that fallback to white, not silver. + // But that still doesn't work. Yet somehow I've seen transparent images + // that fallback to white, not silver. - imagefill( $base_image, 0, 0, $background_color ); + imagefill($base_image, 0, 0, $background_color); - // And set the overlay behaviour back again + // And set the overlay behavior back again. imagealphablending($base_image, TRUE); - // Place the current image over it - $foreground = $image->resource; - $success = imagecopy($base_image, $image->resource, 0, 0, 0, 0, $info['width'], $info['height']); + // Place the current image over it. + if ($result = imagecopy($base_image, $image->resource, 0, 0, 0, 0, $info['width'], $info['height'])) { + imagedestroy($image->resource); + $image->resource = $base_image; + } - $image->resource = $base_image; - return TRUE; + return $result; } } /** - * This achives a tonal effect by converting the images combined tone and + * This achieves a tonal effect by converting the images combined tone and * existing transparency into one shade value. This is then used as the ALPHA * transparency for that pixel, while the whole thing is coloured the same - * shade. Images 'greytoned' in this manner should sit smoothly on any + * shade. Images 'grey toned' in this manner should sit smoothly on any * background. * * With no color set, use the existing hue. * - * To save a partially transparent image, the image resource must be switched to PNG. - * ... or maybe not. Just flatten it yourself, or switch the format yourself. - * This hack would produce side effects otherwise. + * To save a partially transparent image, the image resource must be switched to + * PNG. ... or maybe not. Just flatten it yourself, or switch the format + * yourself. This hack would produce side effects otherwise. * * This algorithm runs maths per-pixel, and therefore is incredibly much more * inefficient than any native routine. Will kill the server on large images. - * - * @param $opacity between 0 transparent and 1 solid. + * + * @param stdClass $image + * @param string $color + * @param float $opacity + * between 0 transparent and 1 solid. + * + * @return bool + * true on success, false otherwise. */ -function png_color2alpha($image, $color, $opacity = NULL) { +function png_color2alpha(stdClass $image, $color, $opacity = NULL) { $info = $image->info; if (!$info) { return FALSE; @@ -195,11 +204,11 @@ function png_color2alpha($image, $color, $opacity = NULL) { if (($width * $height) > (1200 * 1200)) { watchdog('imagecache_actions', __FUNCTION__ . " on {$image->source}. Image is TOO BIG to run the per-pixel algorithm. Aborting."); - return TRUE; + return FALSE; } for ($i = 0; $i < $height; $i++) { //this loop traverses each row in the image - for ($j = 0; $j < $width; $j++) { + for ($j = 0; $j < $width; $j++) { //this loop traverses each pixel of each row // Get the color & alpha info of the current pixel. $retrieved_color = imagecolorat($im1, $j, $i); // an index @@ -207,33 +216,33 @@ function png_color2alpha($image, $color, $opacity = NULL) { $alpha = 127; // Calculate the total shade value of this pixel. - - // If the rule sets a color, then the darkness of the existing + + // If the rule sets a color, then the darkness of the existing // pixel will define the desired alpha value. if ($color) { - $lightness = ( $rgba_array['red'] + $rgba_array['green'] + $rgba_array['blue'] ) / 3; + $lightness = ($rgba_array['red'] + $rgba_array['green'] + $rgba_array['blue']) / 3; // Need to flip the numbers around before doing maths. - #$opacity = 1-($rgba_array['alpha']/127); - #$darkness = 1-($lightness/256); // 0 is white, 1 is black - #$visibility = $darkness * $opacity; - #$alpha = (1-$visibility) * 127; - $alpha = (1 - ((1 -($lightness / 256)) * (1 -($rgba_array['alpha'] / 127)))) * 127; + //$opacity = 1-($rgba_array['alpha']/127); + //$darkness = 1-($lightness/256); // 0 is white, 1 is black + //$visibility = $darkness * $opacity; + //$alpha = (1-$visibility) * 127; + $alpha = (1 - ((1 - ($lightness / 256)) * (1 - ($rgba_array['alpha'] / 127)))) * 127; } - // If color is NOT set, then the existing color is passed though, only + // If color is NOT set, then the existing color is passed though, only // made somewhat transparent. if (!$color) { $background = $rgba_array; } if ($opacity) { - // It's a user-defined alpha value + // It's a user-defined alpha value. $alpha = $alpha * $opacity; } - + // Paint the pixel. + /** @noinspection PhpUndefinedVariableInspection */ $color_to_paint = imagecolorallocatealpha($image->resource, $background['red'], $background['green'], $background['blue'], $alpha); imagesetpixel($image->resource, $j, $i, $color_to_paint); } } return TRUE; } - diff --git a/sites/all/modules/imagecache_actions/customactions/README.txt b/sites/all/modules/imagecache_actions/customactions/README.txt index e46617c..814c858 100644 --- a/sites/all/modules/imagecache_actions/customactions/README.txt +++ b/sites/all/modules/imagecache_actions/customactions/README.txt @@ -21,9 +21,8 @@ Personally, I prefer the imagemagick toolkit: you will see what I mean. - It does not execute in the PHP memory space, so is not restricted by the memory_limit PHP setting. -- The GD toolkit will, at least on my Windows configuration, keep the font file +- The GD toolkit will, at least on my Windows configuration, keep font files open after a text operation, so you cannot delete, move or rename it anymore. -- This module does a better job with Imagemagick (see below). Installing @@ -59,10 +58,11 @@ record so by changing $image->info['width'] and/or $image->info['height']. General ------- To ease your task, this effect makes some information regarding the image being -processed available in 2 variables: $image and $image_context. These variables -are readily available in your snippet. +processed available in a number of variables: $image, $image_context, +$image_style, and $image_effect_id. These variables are readily available in +your snippet. -$image is an associative array containing: +$image is an object containing the following properties: - source: string, the source of the image, e.g. public://photo.jpg - info: array, example data: - width (int) 180 @@ -113,6 +113,18 @@ $image_context is an associative array containing: - title (string) ... - ... +$image_style is an associative array containing the current image style being +processed. It ocntians a.o.: +- isid: the unique image style id +- name: machine name. +- label: Human readable name. +- effects: An array with the effects of this image style, ordered in the way + they should be applied. + +$image_effect_id is an int containng the unique id of the current image effect +being applied. This can be used to look the current image effect up in the +$image_style array. + Of course there are many other possible useful globals. Think of: - base_url - base_path @@ -144,3 +156,41 @@ foreach ($referring_entities as $field_name => $field_referring_entities) { } } ?> + +"Dynamic" parameters +-------------------- +Thee are many requests for adding token support or allowing for dynamic +parameters in another way. However, the current image style processing does not +easily allow for this. But for these cases we have the custom action to our +rescue. It is quite easy to: +- Create you own array of parameters. +- Call the effect callback yourself + +Exanple, calling the watermark/canvas overlay effect: + 'center', + 'ypos' => 'center', + 'alpha' => '100', + 'scale' => '', + 'path' => 'module://imagecache_actions/tests/black-ribbon.gif', +); +return canvasactions_file2canvas_effect($image, $data); +?> + +Or, to be on the safe side with effect info altering: + 'center', + 'ypos' => 'center', + 'alpha' => '100', + 'scale' => '', + 'path' => 'module://imagecache_actions/tests/black-ribbon.gif', + ); + return $callback($image, $data); +} +return FALSE; +?> diff --git a/sites/all/modules/imagecache_actions/customactions/imagecache_customactions.info b/sites/all/modules/imagecache_actions/customactions/imagecache_customactions.info index bd17ada..7cee055 100644 --- a/sites/all/modules/imagecache_actions/customactions/imagecache_customactions.info +++ b/sites/all/modules/imagecache_actions/customactions/imagecache_customactions.info @@ -1,17 +1,14 @@ name = Imagecache Custom Actions -description = Allow direct PHP code manipulation of imagecache images. +description = Provides the custom and subroutine image effects. package = Media core = 7.x dependencies[] = imagecache_actions dependencies[] = image -files[] = imagecache_customactions.install -files[] = imagecache_customactions.module - -; Information added by drupal.org packaging script on 2012-12-04 -version = "7.x-1.1" +; Information added by Drupal.org packaging script on 2018-03-20 +version = "7.x-1.9" core = "7.x" project = "imagecache_actions" -datestamp = "1354653754" +datestamp = "1521550387" diff --git a/sites/all/modules/imagecache_actions/customactions/imagecache_customactions.install b/sites/all/modules/imagecache_actions/customactions/imagecache_customactions.install index 818f28e..2e43bb4 100644 --- a/sites/all/modules/imagecache_actions/customactions/imagecache_customactions.install +++ b/sites/all/modules/imagecache_actions/customactions/imagecache_customactions.install @@ -1,8 +1,8 @@ fields('image_effects') ->condition('name', 'imagecache_customactions', '=') diff --git a/sites/all/modules/imagecache_actions/customactions/imagecache_customactions.module b/sites/all/modules/imagecache_actions/customactions/imagecache_customactions.module index 9088577..db60579 100644 --- a/sites/all/modules/imagecache_actions/customactions/imagecache_customactions.module +++ b/sites/all/modules/imagecache_actions/customactions/imagecache_customactions.module @@ -1,57 +1,82 @@ t('Custom action'), 'help' => t('Runs custom PHP code.'), - 'effect callback' => 'imagecache_customactions_image', + 'effect callback' => 'imagecache_customactions_effect', 'dimensions callback' => 'imagecache_customactions_dimensions', 'form callback' => 'imagecache_customactions_form', + 'summary theme' => 'imagecache_customactions_summary', ); $effects['imagecache_subroutine'] = array( 'label' => t('Subroutine'), 'help' => t('Runs another defined preset on the image.'), - 'effect callback' => 'imagecache_subroutine_image', + 'effect callback' => 'imagecache_subroutine_effect', 'dimensions callback' => 'imagecache_subroutine_dimensions', 'form callback' => 'imagecache_subroutine_form', + 'summary theme' => 'imagecache_subroutine_summary', ); return $effects; } /** - * Implements hook_image_style_flush. + * Implements hook_theme(). * - * This hook checks if the image style that is being flushed is used in an - * subroutine effect. If so, the style that contains the subroutine effect, - * should be flushed as well as the flushed style was probably changed. + * Registers theme functions for the effect summaries. + */ +function imagecache_customactions_theme() { + return array( + 'imagecache_customactions_summary' => array( + 'variables' => array('data' => NULL), + ), + 'imagecache_subroutine_summary' => array( + 'variables' => array('data' => NULL), + ), + ); +} + +/** + * Implements hook_image_style_flush(). + * + * This hook checks if the image style that is being flushed is used in a + * subroutine effect. If so, the style that contains the subroutine effect + * should be flushed as well. + * + * This may lead to recursive calls to image_style_flush() and thus to this + * hook. Without loops in styles that call each other as subroutine, this + * recursion will always end. * * @param array $flushed_style * The image style that is being flushed. */ -function imagecache_customactions_image_style_flush($flushed_style) { +function imagecache_customactions_image_style_flush(/*array*/ $flushed_style) { $styles = image_styles(); foreach ($styles as $style) { if ($style['name'] !== $flushed_style['name']) { @@ -67,44 +92,34 @@ function imagecache_customactions_image_style_flush($flushed_style) { } /** - * @deprecated replaced by summary theme callback - * Implementation of theme_hook() for imagecache_customactions.module - */ -function imagecache_customactions_theme() { - return array( - 'imagecache_subroutine' => array( - 'render element' => 'element', - ), - ); -} - - -/** - * Implements hook_form(). + * Image effect form callback for the custom action effect. + * + * Note that this is not a complete form, it only contains the portion of the + * form for configuring the effect options. Therefore it does not not need to + * include metadata about the effect, nor a submit button. * * @param array $data - * Array of settings for this action. + * The current configuration for this image effect. + * * @return array - * A form definition. + * The form definition for this effect. */ -function imagecache_customactions_form($data) { +function imagecache_customactions_form(array $data) { // Add defaults. $data += array('php' => 'return TRUE;'); // Note: we also need to check for the existence of the module: admin has // all rights, so user_acccess(...) returns TRUE even if the module is not // enabled and the permission does not exist. - $allow_dynamic = user_access('use PHP for settings') && module_exists('php'); + $allow_php = module_exists('php') && user_access('use PHP for settings'); - // @todo: for imagemagick, the PHP code should add a set of commands to the - // ops aray of $image. Document this in description. $form = array( 'php' => array( '#type' => 'textarea', - '#rows' => 10, + '#rows' => 12, '#title' => t('PHP code'), '#default_value' => $data['php'], - '#disabled' => !$allow_dynamic, + '#disabled' => !$allow_php, '#description' => t("A piece of PHP code that modifies the image. It should return a boolean indicating success or failure. You will need the '%use_php' permission, defined by the 'PHP filter' module. @@ -117,15 +132,30 @@ See the help for an extensive explanation of the possibilities.
", } /** - * Implements hook_image(). + * Implements theme_hook() for the custom action effect summary. * - * @param object $image + * param array $variables + * An associative array containing: + * - data: The current configuration for this image effect. + * + * @return string + * The HTML for the summary of this image effect. + * @ingroup themeable + */ +function theme_imagecache_customactions_summary(/*array $variables*/) { + return 'Custom PHP code'; +} + +/** + * Image effect callback for the custom action effect. + * + * @param stdClass $image * @param array $data * - * @return bool + * @return boolean + * true on success, false otherwise. */ -function imagecache_customactions_image($image, $data) { - // Check that the PHP filter module is enabled. +function imagecache_customactions_effect(stdClass $image, array $data) { $result = module_exists('php'); if ($result) { // Get context about the image. @@ -133,10 +163,17 @@ function imagecache_customactions_image($image, $data) { $GLOBALS['image_context'] = imagecache_actions_get_image_context($image, $data); $GLOBALS['image'] = $image; + // Get (non-alterable) context about the image style and image effect. + $execution_info = imagecache_actions_get_image_effect_context(); + $GLOBALS['image_style'] = $execution_info['image_style']; + $GLOBALS['image_effect_id'] = $execution_info['image_effect_id']; + $result = php_eval('<' . '?php global $image, $image_context; ' . $data['php'] . ' ?' . '>'); // php_eval returns '1' if the snippet returns true. $result = $result === '1'; + unset($GLOBALS['image_effect_id']); + unset($GLOBALS['image_style']); unset($GLOBALS['image']); unset($GLOBALS['image_context']); } @@ -150,94 +187,102 @@ function imagecache_customactions_image($image, $data) { } /** - * Image dimensions callback; Custom action. + * Image dimensions callback for the custom action effect. * * @param array $dimensions - * Dimensions to be modified - an array with components width and height, in - * pixels. - * @param array $data - * An array with the effect options. + * Dimensions to be modified - an associative array containing the items + * 'width' and 'height' (in pixels). + * param array $data + * An associative array containing the effect data. */ -function imagecache_customactions_dimensions(array &$dimensions, array $data) { - // @todo: add form field asking if dimensions stay the same (or if they know - // the new dimesions). +function imagecache_customactions_dimensions(array &$dimensions/*, array $data*/) { $dimensions['width'] = NULL; $dimensions['height'] = NULL; } -/** - * @todo change into summary theme callback - * Implementation of theme_hook() for imagecache_ui.module - */ -function theme_imagecache_customactions($element) { - // TODO: Should this theme imagecache_customactions be declared in hook_theme()? - $data = $element['#value']; - return "" . $data['text'] . ""; -} - /** * Subroutine - an imagecache action that just calls another one. * * Contributed by Alan D - * http://drupal.org/node/618784 + * https://drupal.org/node/618784 * * Reworked into customactions by dman 2010-07 */ /** - * Config form for this preset. - * - * Implementation of imagecache_hook_form() + * Image effect form callback for the subroutine effect. * * @param array $data - * The effect data for this effect. + * The current configuration for this image effect. + * * @return array - * The form definition. + * The form definition for this effect. */ -function imagecache_subroutine_form($data) { - $data = (array) $data; - $form = array(); +function imagecache_subroutine_form(array $data) { + // Add defaults. + $data += array('subroutine_presetname' => ''); - // List available presets - // @todo: use image_style_options and remove current style? - $presets = array(); - foreach (image_styles(TRUE) as $preset) { - $presets[$preset['name']] = $preset['name']; - } + // List available image styles. + // The PASS_THROUGH parameter is new as of D7.23, and is added here to prevent + // image_style_options() from double-encoding the human-readable image style + // name, since the form API will already sanitize options in a select list. + $styles = image_style_options(TRUE, PASS_THROUGH); + //@todo: unset the current style to prevent obvious recursion. + + $form = array(); $form['subroutine_presetname'] = array( '#type' => 'select', - '#title' => t('Preset to call'), + '#title' => t('Image style to call'), '#default_value' => $data['subroutine_presetname'], - '#options' => $presets, + '#options' => $styles, + '#required' => TRUE, ); return $form; } - /** - * Actually invoke the action - which means just handing off to the named real - * preset to do the job. + * Implements theme_hook() for the subroutine effect summary. * - * Implementation of hook_image() + * @param array $variables + * An associative array containing: + * - data: The current configuration for this image effect. * - * @param object $image - * @param array $data - * - * @return bool + * @return string + * The HTML for the summary of this image effect. + * @ingroup themeable */ -function imagecache_subroutine_image($image, $data) { - if ($preset = image_style_load($data['subroutine_presetname'])) { - foreach ($preset['effects'] as $effect) { - image_effect_apply($image, $effect); - } - } - return TRUE; +function theme_imagecache_subroutine_summary(array $variables) { + $data = $variables['data']; + + module_load_include('inc', 'imagecache_actions', 'utility'); + $label = imagecache_actions_get_style_label($data['subroutine_presetname']); + return t('Apply image style %label', array('%label' => $label)); } /** - * Image dimensions callback; Subroutine. + * Image effect callback for the subroutine effect. + * + * @param stdClass $image + * @param array $data + * + * @return boolean + * true on success, false otherwise. + */ +function imagecache_subroutine_effect(stdClass $image, array $data) { + $result = FALSE; + if ($style = image_style_load($data['subroutine_presetname'])) { + $result = TRUE; + foreach ($style['effects'] as $effect) { + $result = $result && image_effect_apply($image, $effect); + } + } + return $result; +} + +/** + * Image dimensions callback for the subroutine effect. * * @param array $dimensions * Dimensions to be modified - an array with components width and height, in @@ -246,25 +291,6 @@ function imagecache_subroutine_image($image, $data) { * An array with the effect options. */ function imagecache_subroutine_dimensions(array &$dimensions, array $data) { - // @todo: dimensions - // @todo: call dimensions callback on subroutine style. - $dimensions['width'] = NULL; - $dimensions['height'] = NULL; -} - -/** - * @todo change into summary theme callback - * This lets the user see what parameters were selected for the action - */ -function theme_imagecache_subroutine($variables) { - // @todo: better decsription, do not use internal id's, imagecache_preset_by_name does not exist: fatal error? - $element = $variables['element']; - $data = $element['#value']; - if ($preset = imagecache_preset_by_name($data['subroutine_presetname'])) { - return t('%name (pid: !presetid)', array( - '%name' => $preset['presetname'], - '!presetid' => $preset['presetid'] - )); - } - return t('Invalid reference. The referenced preset may have been deleted!'); + // Let the subroutine transform the dimensions. + image_style_transform_dimensions($data['subroutine_presetname'], $dimensions); } diff --git a/sites/all/modules/imagecache_actions/image_effects_text/Apostrophic Labs License Komika).txt b/sites/all/modules/imagecache_actions/image_effects_text/Apostrophic Labs License Komika).txt new file mode 100644 index 0000000..92296f5 --- /dev/null +++ b/sites/all/modules/imagecache_actions/image_effects_text/Apostrophic Labs License Komika).txt @@ -0,0 +1,36 @@ +Komika font by APOSTROPHIC LABS: +http://moorstation.org/typoasis/designers/lab/index.htm. + +Below the license as was packaged with the font download: + +Here is the "Read Me" from the original http://www.apostrophiclab.com website: + +Archived here: http://web.archive.org/web/20030408055445/www.hardcovermedia.com/lab/Pages/info.html + +and here: http://apostrophiclab.pedroreina.net/info.html + +----------- + +Apostrophic Laboratories does not collect or relay any personal information about its visitors. + +Apostrophic Laboratories is not affiliated with the company that provides its web hosting service. + +Apostrophic Laboratories is not affiliated with any third party advertising service. This lab will always be advertisement-free to maintain the spirit and integrity of freedom of information that the world wide web was intended to represent. + +The fonts on this site are freeware and can be used as they are in any context without permission from Apostrophic Laboratories, except to produce material that is racist, criminal and/or illegal in nature. It is prohibited to modify any Apostrophic Laboratories font(s) for repackaging and/or re-release without an express written authorization by the designer(s) of the font(s) or Apostrophic Laboratories. Under no circumstance shall any Apostrophic Laboratories design or font design be sold or purchased. Email info@apostrophiclab.com if you want more information. + +The Apostrophic Laboratories site and its contents are the property of Apostrophic Laboratories and the contents' creators. It is prohibited to use the graphic designs shown on this site or any of the site's elements without obtaining written authorization from the designer(s) and/or developer(s) of the content in question. Email info@apostrophiclab.com if you want more information. + +Read everything written by Kurt Vonnegut. +Hear everything written by Mozart. +Live every day like it is your first. +Smell the flowers. +Touch the faces of babies. +Run in the rain. +Eat pop corn. + +The following constitutes the different methods of contacting Apostrophic Laboratories. +Web: http://www.apostrophiclab.com +Email: info@apostrophiclab.com +For individual designer email addresses, click here. +Address: Apostrophic Laboratories, 343 Kingswood Road, Toronto, Ontario, Canada M4E 3N8 diff --git a/sites/all/modules/imagecache_actions/image_effects_text/Blokletters-Balpen.ttf b/sites/all/modules/imagecache_actions/image_effects_text/Blokletters-Balpen.ttf new file mode 100644 index 0000000..d3ecaa8 Binary files /dev/null and b/sites/all/modules/imagecache_actions/image_effects_text/Blokletters-Balpen.ttf differ diff --git a/sites/all/modules/imagecache_actions/image_effects_text/Blokletters-Potlood.ttf b/sites/all/modules/imagecache_actions/image_effects_text/Blokletters-Potlood.ttf new file mode 100644 index 0000000..dae65c6 Binary files /dev/null and b/sites/all/modules/imagecache_actions/image_effects_text/Blokletters-Potlood.ttf differ diff --git a/sites/all/modules/imagecache_actions/image_effects_text/Blokletters-Viltstift.ttf b/sites/all/modules/imagecache_actions/image_effects_text/Blokletters-Viltstift.ttf new file mode 100644 index 0000000..1846868 Binary files /dev/null and b/sites/all/modules/imagecache_actions/image_effects_text/Blokletters-Viltstift.ttf differ diff --git a/sites/all/modules/imagecache_actions/image_effects_text/Creative Commons Attribution Share 2.5 (Blokletters).txt b/sites/all/modules/imagecache_actions/image_effects_text/Creative Commons Attribution Share 2.5 (Blokletters).txt new file mode 100644 index 0000000..58b02df --- /dev/null +++ b/sites/all/modules/imagecache_actions/image_effects_text/Creative Commons Attribution Share 2.5 (Blokletters).txt @@ -0,0 +1,24 @@ +Blokletters fonts by Lefly Fonts: http://lefly.vepar.nl/ under the following +license: + +You are free: + +to Share — to copy, distribute and transmit the work +to Remix — to adapt the work + +Under the following conditions: + +Attribution — You must attribute the work in the manner specified by the author or licensor (but not in any way that suggests that they endorse you or your use of the work). + +Share Alike — If you alter, transform, or build upon this work, you may distribute the resulting work only under the same or similar license to this one. + +With the understanding that: + +Waiver — Any of the above conditions can be waived if you get permission from the copyright holder. + +Other Rights — In no way are any of the following rights affected by the license: +- Your fair dealing or fair use rights; +- The author's moral rights; +- Rights other persons may have either in the work itself or in how the work is used, such as publicity or privacy rights. + +Notice — For any reuse or distribution, you must make clear to others the license terms of this work. The best way to do this is with a link to this web page. diff --git a/sites/all/modules/imagecache_actions/image_effects_text/Komika_display.ttf b/sites/all/modules/imagecache_actions/image_effects_text/Komika_display.ttf new file mode 100644 index 0000000..5ae5bc9 Binary files /dev/null and b/sites/all/modules/imagecache_actions/image_effects_text/Komika_display.ttf differ diff --git a/sites/all/modules/imagecache_actions/image_effects_text/OFL (Pacifico).txt b/sites/all/modules/imagecache_actions/image_effects_text/OFL (Pacifico).txt new file mode 100644 index 0000000..190e986 --- /dev/null +++ b/sites/all/modules/imagecache_actions/image_effects_text/OFL (Pacifico).txt @@ -0,0 +1,93 @@ +Copyright (c) 2011, Vernon Adams (vern@newtypography.co.uk), +with Reserved Font Name Pacifico. +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/sites/all/modules/imagecache_actions/image_effects_text/Pacifico.ttf b/sites/all/modules/imagecache_actions/image_effects_text/Pacifico.ttf new file mode 100644 index 0000000..122e451 Binary files /dev/null and b/sites/all/modules/imagecache_actions/image_effects_text/Pacifico.ttf differ diff --git a/sites/all/modules/imagecache_actions/image_effects_text/README.txt b/sites/all/modules/imagecache_actions/image_effects_text/README.txt index 7308b2a..a8aa868 100644 --- a/sites/all/modules/imagecache_actions/image_effects_text/README.txt +++ b/sites/all/modules/imagecache_actions/image_effects_text/README.txt @@ -2,7 +2,7 @@ README ------ README for the Image effect text module. -Author Erwin Derksen (fietserwin: http://drupal.org/user/750928) +Author Erwin Derksen (fietserwin: https://drupal.org/user/750928) Dependencies @@ -12,10 +12,10 @@ Hard dependencies: - Image (Drupal core). Soft dependencies/recommended modules: -- Imagemagick (preferred toolkit, http://drupal.org/project/imagemagick). +- Imagemagick (preferred toolkit, https://drupal.org/project/imagemagick). - PHP filter (Drupal core, if yuo want to use PHP to create the text to render). -- System stream wrapper (http://drupal.org/project/system_stream_wrapper) -- Remote stream wrapper (http://drupal.org/project/remote_stream_wrapper) +- System stream wrapper (https://drupal.org/project/system_stream_wrapper) +- Remote stream wrapper (https://drupal.org/project/remote_stream_wrapper) The latter 2 provide additional stream wrappers. Especially the system stream wrapper is very handy as it provides, among others, a module:// and theme:// wrapper. @@ -45,9 +45,12 @@ More information about the effect data options Font ---- -You have to supply the font file to use. The font type supported depend on the -toolkit in use, but at least ttf files will always work. This option accepts -either: +This module comes with some free fonts so you can easily test this effect. +Please read their respective licences. + +For real use, you normally want to use your own font as dictated by the website +design. The font types supported depend on the toolkit in use, but at least ttf +files will always work. This option accepts either: - 1 of the (enabled) scheme's: * public:// * private:// Preferred for site specific masks, overlays, etc, that do not @@ -59,13 +62,17 @@ either: * theme:// idem. * profile:// idem. * library:// idem. -- A relative (to the current directory, probably Drupal root) or absolute path. +- A relative path (relative to the current directory, probably Drupal root). +- An absolute path. +- A system or toolkit font specification. E.g. on my Windows system 'arial.ttf' + worked with both GD and Imagemagick. A warning will be issued but that may be + ignored when it works as expected. Text position ------------- The text position defines the point in the image where you want to place (align) -your text. It starts at the top left corner of the image with postion 0,0 and +your text. It starts at the top left corner of the image with position 0,0 and the positive directions are to the right and down. The definition of the vertical position differs per toolkit. For GD it is the @@ -89,49 +96,65 @@ for this yourself. Rotation -------- -The text can be rotated before being overlaid on the image. The vlaue is in -degrees, so 90 degrees is straight down. Positive values are rotated clockwise, +The text can be rotated before being overlaid on the image. The value is in +degrees. Positive values are rotated clockwise, So 90 degrees is straight down. negative values counter clockwise. In Imagemagick the text is rotated around the text position. Thus centered text is rotated around its own center. GD, on the other hand, always rotates around -the left bottom (baseline) position, regardless the text alignment. Using +the left bottom (baseline) position, regardless the text alignment. So using rotation with a non default alignment (left bottom) will give surprising results. Text source ----------- -Note: this module is not build to handle multi line texts. Not when the text -contains new lines and/or carriage returns, and not to split a given text over -multiple lines given an available width. Using "\n" in GD seems to work though. - The text to place on the image may come from different sources: -- Static: the text to place on the image is static and is defined in the image - effect data. Use this e.g. for a fixed copyright notice. +- Text (with token replacement): the text to place on the image has to be + entered on the image effect form. Use this e.g. for a copyright notice. + notes: + * Token replacement: you can use all global tokens, the file tokens, and + tokens from entities referring to the image via an image field. Example: if + you know that the image style is only used for article nodes, you can use + [node:field-image:alt] to get the alt text of the image. Note: this specific + example requires the entity_token module. + * New lines: you can add a new line by adding \n to your text. To get a + literal \n, use \\n. - PHP: the text to place on the image comes from a piece of PHP code that should return the text to place on the image. Only users with the 'use PHP for settings' permission are allowed to use this source. This permission and the evaluation of the PHP code come from the PHP filter module which is part of Drupal core and thus needs to be enabled, also during image generation. -- To alleviate the need to enable the PHP filter module, 2 commonly used sources - for dynamic texts are directly available without any coding, namely the alt - and title properties of the image field linked to the image at hand. Note that - multiple image fields, possibly in different languages, may be referring to - the image that is being processed. This module will take the first image field - it finds to extract the alt and title. If the field in itself is multi - lingual, thus not a synced field, the current language will be taken, which is - the language of the user that happens to request this styled image first. + To add new lines to your text add them literally to the string you return, + normally by using "\n" in your PHP code. +- Image Alt or Title: to alleviate the need to enable the PHP filter module, 2 + commonly used sources for dynamic texts are directly available without any + coding: the alt and title properties of an image field linked to the image at + hand. + +Notes: +- When using token replacement or the image alt or title, multiple image fields, + possibly in different languages, may be referring to the image that is being + processed. This module will take the first image field it finds to extract the + alt and title. If the field in itself is multi-lingual, thus not a synced + field, the current language will be taken, which is the language of the user + that happens to request this image derivative first. +- This module will not automatically break text based on available space. +- Due to the way that GD text box positioning works it is quite difficult to + correctly position multiple lines of text with GD. If you have a working + solution please post a patch. (Probably involves exploding the text in + separate lines and then positioning each line separately.) PHP snippets to determine the text ---------------------------------- Given the correct permission, you can write your own PHP snippet to compute the text to display. To ease this task, this module makes some information regarding -the image being processed available in 2 variables: $image and $image_context. -These variables are readily available in your snippet. +the image being processed available in a number of variables: $image, +$image_context, $image_style, and $image_effect_id. These variables are readily +available in your snippet. -$image is an associative array containing: +$image is an object containing the following properties: - source: string, the source of the image, e.g. public://photo.jpg - info: array, example data: - width (int) 180 @@ -153,7 +176,7 @@ $image_context is an associative array containing: - HEX (string) 000000 - alpha (string) 100 - angle (string) 0 - - fontfile (string:10) lhandw.ttf + - fontfile (string:46) module://image_effects_text/Komika_display.ttf - text_source (string) text - text (string) Hello World! - php (string) return 'Hello World!' @@ -192,6 +215,18 @@ $image_context is an associative array containing: - title (string) ... - ... +$image_style is an associative array containing the current image style being +processed. It ocntians a.o.: +- isid: the unique image style id +- name: machine name. +- label: Human readable name. +- effects: An array with the effects of this image style, ordered in the way + they should be applied. + +$image_effect_id is an int containng the unique id of the current image effect +being applied. This can be used to look the current image effect up in the +$image_style array. + Of course there are many other possible useful globals. Think of: - base_url - base_path @@ -205,20 +240,30 @@ Using these information you can access entity data as follows: Specific case (1 entity, of known entity_type, referring to the image): Or the more general case (not knowing the referring type, or multiple entities that may be referring to the image): $field_referring_entities) { foreach ($field_referring_entities as $entity_type => $entities) { foreach ($entities as $entity_id => $entity) { $field = field_get_items($entity_type, $entity, $field_name); + // ... } } } @@ -232,7 +277,7 @@ TODO do the same? - Language and alt/title: what if the first user to pass by and that generates the image is in a language that has no alt/title? -- Newlines: seem to work in GD, not in Imagemagick. +- Check for existence of imagettftext() and fail properly. To quote http://www.imagemagick.org/Usage/text/#draw: As of IM version 6.2.4, the "-draw text" operation no longer understands the use diff --git a/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text.inc b/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text.inc index b14d7c5..72218f2 100644 --- a/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text.inc +++ b/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text.inc @@ -10,7 +10,7 @@ * Only tuned for Ubuntu so far. I've been unable do find ubiquitous tools that * provide useful font listings.' */ -function image_effects_text_help_inc($path, $arg) { +function image_effects_text_help_inc(/*$path, $arg*/) { $output = "For text rendering to work on a server, we must know the system path to the font file, not just the name. @@ -39,7 +39,7 @@ function image_effects_text_help_inc($path, $arg) { } /** - * Builds the form structure for the overlay text image effect. + * Image effect form callback for the text effect. * * Note that this is not a complete form, it only contains the portion of the * form for configuring the effect options. Therefore it does not not need to @@ -51,36 +51,132 @@ function image_effects_text_help_inc($path, $arg) { * @return array * The form definition for this effect. */ -function image_effects_text_form_inc($data) { +function image_effects_text_form_inc(array $data) { // Use of functions imagecache_file_...() creates a dependency on file utility.inc. module_load_include('inc', 'imagecache_actions', 'utility'); // Use of function imagecache_rgb_form() creates a dependency on file utility-color.inc. module_load_include('inc', 'imagecache_actions', 'utility-color'); // Note: we also need to check for the existence of the module: admin has - // all rights, so user_acccess(...) returns TRUE even if the module is not + // all rights, so user_access(...) returns TRUE even if the module is not // enabled and the permission does not exist. // A user without the 'use PHP for settings' permission (defined by the core // PHP filter module) may not: // - Select the 'PHP code' text source option if it is currently not selected. // - Change the 'PHP code' textarea. - $allow_dynamic = user_access('use PHP for settings') && module_exists('php'); + $allow_php = module_exists('php') && user_access('use PHP for settings'); $defaults = array( - 'size' => 12, - 'angle' => 0, + 'text_source' => 'text', + 'text' => t('Hello World!'), + 'php' => 'if (!$image_context[\'entity\']) { + return \'' . t('No referring entity') . '\'; +} +$entity_type = \'node\'; +$field_name = \'my_field\'; +$entity = $image_context[\'entity\']; +$field = field_get_items($entity_type, $entity, $field_name); +if ($field) { + return isset($field[0][\'value\']) ? $field[0][\'value\'] : \'' . t('No field value') . '\'; +} +', + 'text_case' => 'none', + 'fontfile' => drupal_get_path('module', 'image_effects_text') . '/Komika_display.ttf', + 'size' => 50, + 'RGB' => array('HEX' => '#000000'), + 'alpha' => 100, 'xpos' => '0', 'ypos' => '0', 'halign' => 'left', - 'valign' => 'bottom', - 'RGB' => array('HEX' => '#000000'), - 'alpha' => 100, - 'fontfile' => 'lhandw.ttf', - 'text_source' => 'text', - 'text' => 'Hello World!', - 'php' => 'return \'Hello World!\'', + 'valign' => 'top', + 'angle' => 0, ); $data += $defaults; + $tokens = token_info(); + $tokens = array_keys($tokens['types']); $form = array( + 'text_help' => array( + '#type' => 'item', + '#title' => t('Text'), + '#description' => t('
Select the source of the text:
+See the help in README.txt for an extensive explanation of the possibilities.
', + array('%use_php' => t('Use PHP for settings'))), + ), + 'text_source' => array( + '#type' => 'select', + '#title' => t('Text source'), + '#default_value' => $data['text_source'], + '#options' => array( + 'alt' => t('Image alt'), + 'title' => t('Image title'), + 'text' => t('Text (with token replacement)'), + 'php' => t('PHP code'), + ), + ), + 'text' => array( + '#type' => 'textfield', + '#title' => t('Text'), + '#default_value' => $data['text'], + '#states' => array( + 'visible' => array(':input[name="data[text_source]"]' => array('value' => 'text')), + ), + ), + 'token_help' => array( + '#type' => 'fieldset', + '#title' => t('Token replacement patterns'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + 'tree' => array( + '#theme' => 'token_tree', + '#token_types' => $tokens, + ), + 'token_module_notice' => array( + '#markup' => !module_exists('token') ? t('You might want to enable the token module to view token replacement patterns.') : '', + ), + 'entity_token_module_notice' => array( + '#markup' => !module_exists('entity_token') ? t('You might want to enable the entity_token module to get token options for all entities and field properties.') : '', + ), + '#states' => array( + 'visible' => array(':input[name="data[text_source]"]' => array('value' => 'text')), + ), + ), + 'php' => array( + '#type' => 'textarea', + '#rows' => 12, + '#title' => t('PHP code'), + '#default_value' => $data['php'], + '#disabled' => !$allow_php, + '#states' => array( + 'visible' => array(':input[name="data[text_source]"]' => array('value' => 'php')), + ), + '#wysiwyg' => FALSE, + ), + 'text_case' => array( + '#title' => t('Capitalization'), + '#type' => 'select', + '#options' => array( + 'none' => t('No transform'), + 'upper' => t('Upper case'), + 'lower' => t('Lower case'), + 'ucfirst' => t('Capitalize first letter'), + 'ucwords' => t('Capitalize each word'), + ), + '#default_value' => $data['text_case'], + '#description' => t('Defines if and how to transform the case of the text to render.'), + ), + 'fontfile' => array( + '#type' => 'textfield', + '#title' => t('Font file name'), + '#default_value' => $data['fontfile'], + '#description' => imagecache_actions_file_field_description(), + '#element_validate' => array('image_effects_text_validate_font'), + '#size' => 80, + ), 'size' => array( '#type' => 'textfield', '#title' => t('Font size'), @@ -88,6 +184,14 @@ function image_effects_text_form_inc($data) { '#description' => t('The font size in points. Only in GD1 this is in pixels.'), '#size' => 3, ), + 'RGB' => imagecache_rgb_form($data['RGB']), + 'alpha' => array( + '#type' => 'textfield', + '#title' => t('Opacity'), + '#default_value' => $data['alpha'], + '#size' => 3, + '#description' => t('Opacity: 1-100.'), + ), 'xpos' => array( '#type' => 'textfield', '#title' => t('X offset'), @@ -107,22 +211,22 @@ function image_effects_text_form_inc($data) { '#title' => t('Horizontal alignment'), '#default_value' => $data['halign'], '#description' => t('The horizontal alignment of the text around the given %xpos.', array('%xpos' => t('X offset'))), - '#options' => array('left' => t('Left'), 'center' => t('Center'), 'right' => t('Right')), + '#options' => array( + 'left' => t('Left'), + 'center' => t('Center'), + 'right' => t('Right') + ), ), 'valign' => array( '#type' => 'select', '#title' => t('Vertical alignment'), '#default_value' => $data['valign'], '#description' => t('The vertical alignment of the text around the given %ypos.', array('%ypos' => t('Y offset'))), - '#options' => array('top' => t('Top'), 'center' => t('Center'), 'bottom' => t('Bottom')), - ), - 'RGB' => imagecache_rgb_form($data['RGB']), - 'alpha' => array( - '#type' => 'textfield', - '#title' => t('Opacity'), - '#default_value' => $data['alpha'] ? $data['alpha'] : 100, - '#size' => 3, - '#description' => t('Opacity: 1-100.'), + '#options' => array( + 'top' => t('Top'), + 'center' => t('Center'), + 'bottom' => t('Bottom') + ), ), 'angle' => array( '#type' => 'textfield', @@ -131,52 +235,8 @@ function image_effects_text_form_inc($data) { '#description' => t('Angle: The angle in degrees, with 0 degrees being left-to-right reading text. Higher values represent a counter-clockwise rotation. For example, a value of 90 would result in bottom-to-top reading text.'), '#size' => 3, ), - 'fontfile' => array( - '#type' => 'textfield', - '#title' => t('Font file name'), - '#default_value' => $data['fontfile'], - '#description' => imagecache_actions_file_field_description(), - '#element_validate' => array('imagecache_actions_validate_file'), - ), - 'text_help' => array( - '#type' => 'item', - '#title' => t('Text'), - '#description' => t('Select the source of the text:
-See the help for an extensive explanation of the possibilities.
', - array('%use_php' => t('Use PHP for settings'))), - ), - 'text_source' => array( - '#type' => 'select', - '#title' => t('Text source'), - '#default_value' => $data['text_source'], - '#options' => array('alt' => t('Image alt'), 'title' => t('Image title'), 'text' => t('Static text'), 'php' => t('PHP code')), - ), - 'text' => array( - '#type' => 'textfield', - '#title' => t('Text'), - '#default_value' => $data['text'], - '#states' => array( - 'visible' => array(':input[name="data[text_source]"]' => array('value' => 'text')), - ), - ), - 'php' => array( - '#type' => 'textarea', - '#rows' => 5, - '#title' => t('PHP code'), - '#default_value' => $data['php'], - '#disabled' => !$allow_dynamic, - '#states' => array( - 'visible' => array(':input[name="data[text_source]"]' => array('value' => 'php')), - ), - ), ); - if (!$allow_dynamic && $data['text_source'] !== 'php') { + if (!$allow_php && $data['text_source'] !== 'php') { unset($form['text_fieldset']['text_source']['#options']['php']); } @@ -188,7 +248,7 @@ function image_effects_text_form_inc($data) { * Element validation callback for the effect form. * @see http://api.drupal.org/api/drupal/developer--topics--forms_api_reference.html/7#element_validate */ -function image_effects_text_form_validate($element, &$form_state, $form) { +function image_effects_text_form_validate(array $element/*, &$form_state, $form*/) { if (!is_numeric($element['size']['#value']) || $element['size']['#value'] <= 0) { form_error($element['size'], t('%field must be a positive number.', array('%field' => t('Font size')))); } @@ -201,17 +261,30 @@ function image_effects_text_form_validate($element, &$form_state, $form) { } /** - * Implementation of theme_hook() for text image effect. + * Validates that the file as specified in the element exists and is readable. + * + * This is a Form API #element_validate callback. + * + * @param array $element + */ +function image_effects_text_validate_font(array &$element/*, &$form_status*/) { + if (!imagecache_actions_find_file($element['#value'])) { + drupal_set_message(t("Unable to find the font file '%file'. Please check the path. You can ignore this waning, if the font refers to a system or toolkit font, and the text shows correctly.", array('%file' => $element['#value'])), 'warning'); + } +} + +/** + * Implements theme_hook() for the text effect summary. * * @param array $variables * An associative array containing: - * - data: The current configuration for this resize effect. + * - data: The current configuration for this image effect. * * @return string - * The HTML for the summary of a text image effect. + * The HTML for the summary of this image effect. * @ingroup themeable */ -function theme_image_effects_text_summary($variables) { +function theme_image_effects_text_summary(array $variables) { $data = $variables['data']; switch ($data['text_source']) { case 'alt': @@ -226,27 +299,23 @@ function theme_image_effects_text_summary($variables) { case 'php': $text = 'PHP code'; break; + default: + $text = ''; + break; } return 'Text: ' . $text . '; Position: ' . $data['xpos'] . ',' . $data['ypos'] . '; Alignment: ' . $data['halign'] . ',' . $data['valign']; } /** - * (Real implementation of) Image effect callback; Overlay text on an image - * resource. - * - * @param object $image - * An image object returned by image_load(). + * Image effect callback for the text effect. * + * @param stdClass $image * @param array $data - * An array of attributes to use when performing the resize effect with the - * following items: - * - "width": An integer representing the desired width in pixels. - * - "height": An integer representing the desired height in pixels. * * @return boolean - * true on success, false on failure to apply the effect. + * true on success, false otherwise. */ -function image_effects_text_effect_inc($image, $data) { +function image_effects_text_effect_inc(stdClass $image, array $data) { // Use of imagecache_actions_hex2rgba() ,the imagecache_file_...() functions, // and imagecache_actions_get_image_context() create a dependency on // file utility.inc. @@ -256,79 +325,82 @@ function image_effects_text_effect_inc($image, $data) { // Start with a straight copy. $params = $data; + // Get the text to overlay. + $params['text'] = image_effects_text_get_text($image, $params); + if (empty($params['text'])) { + // No text to overlay. This is a situation that can be expected with token + // replacement, or custom PHP code. Return immediately with success. + return TRUE; + } + // Find out where the font file is located and if it is readable. $params['fontpath'] = imagecache_actions_find_file($data['fontfile']); if ($params['fontpath'] === FALSE) { - drupal_set_message(t("Failed to locate the requested font %fontfile. Cannot overlay text onto image.", array('%fontfile' => $data['fontfile'])), 'error'); - return FALSE; + // Pass the font on and let's hope that the toolkit knows where to find it. + // We did warn on the effect form. + $params['fontpath'] = $data['fontfile']; } - // Get the text to overlay. - $params['text'] = image_effects_text_get_text($image, $params); - if ($params['text'] === FALSE) { - drupal_set_message(t("Failed to evaluate text (is the 'PHP Filter' module enabled?). Not overlaying text."), 'error'); - return FALSE; - } - - // Parse offsets + // Parse offsets. $params['xpos'] = image_effects_text_get_offset($data['xpos'], $image->info['width'], $image->info['height'], $image->info['width']); $params['ypos'] = image_effects_text_get_offset($data['ypos'], $image->info['width'], $image->info['height'], $image->info['height']); // Convert color from hex (as it is stored in the UI). $params['RGB'] = $data['RGB']; - if($params['RGB']['HEX'] && $deduced = imagecache_actions_hex2rgba($params['RGB']['HEX'])) { + if ($params['RGB']['HEX'] && $deduced = imagecache_actions_hex2rgba($params['RGB']['HEX'])) { $params['RGB'] += $deduced; } - // Make int's of various parameters + // Make integers of various parameters. $params['size'] = (int) $params['size']; $params['xpos'] = (int) $params['xpos']; $params['ypos'] = (int) $params['ypos']; - // Hand over to toolkit + + // Hand over to toolkit. return image_toolkit_invoke('image_effects_text', $image, array($params)); } /** - * GD toolkit specific implementation of this image effect. + * GD toolkit specific implementation of the text effect. * - * @param object $image - * @param array $params - * An array containing the parameters for this effect. + * @param stdClass $image + * @param array $data + * The parameters for this effect. * * @return bool * true on success, false otherwise. */ -function image_gd_image_effects_text($image, $params) { +function image_gd_image_effects_text(stdClass $image, array $data) { // Convert color and alpha to GD alpha and color value. // GD alpha value: 0 = opaque, 127 = transparent. - $params['alpha'] = (int) ((1 - ($params['alpha'] / 100)) * 127); - $color = imagecolorallocatealpha($image->resource, $params['RGB']['red'], $params['RGB']['green'], $params['RGB']['blue'], $params['alpha']); + $data['alpha'] = (int) ((1 - ($data['alpha'] / 100)) * 127); + $color = imagecolorallocatealpha($image->resource, $data['RGB']['red'], $data['RGB']['green'], $data['RGB']['blue'], $data['alpha']); if ($color !== FALSE) { $bounds = NULL; // Adjust Y position for vertical alignment (if different from bottom). - if ($params['valign'] !== 'bottom') { + if ($data['valign'] !== 'bottom') { // Get bounding box. // PHP Manual: "This function requires both the GD library and the » FreeType library." // So it is not more demanding than imagettftext, which we need anyway. - $bounds = imagettfbbox($params['size'], 0, $params['fontpath'], $params['text']); - if ($bounds === FALSE) { + $bounds = imagettfbbox($data['size'], 0, $data['fontpath'], $data['text']); + if (!$bounds) { drupal_set_message(t('Failed to calculate text dimensions using GD toolkit. Ignoring the alignment settings.'), 'warning'); } else { // Get height of bounding box. $height = $bounds[1] - $bounds[7]; // Shift ypos down (full height on bottom, half the height on center). - $params['ypos'] += $params['valign'] === 'center' ? (int) ($height/2) : $height; + $data['ypos'] += $data['valign'] === 'center' ? (int) ($height / 2) : $height; } } // Adjust X position for horizontal alignment (if different from left). - if ($params['halign'] !== 'left') { - // Get bounding box. - // PHP Manual: "This function requires both the GD library and the » FreeType library." - // So it is not more demanding than imagettftext, which we need anyway. + if ($data['halign'] !== 'left') { + // Get bounding box. PHP Manual: "This function requires both the GD + // library and the » FreeType library.", so it is not more demanding than + // imagettftext(), which we need anyway. if ($bounds === NULL) { - $bounds = imagettfbbox($params['size'], 0, $params['fontpath'], $params['text']); - if ($bounds === FALSE) { + $bounds = imagettfbbox($data['size'], 0, $data['fontpath'], $data['text']); + if (!$bounds) { drupal_set_message(t('Failed to calculate text dimensions using GD toolkit. Ignoring the alignment.'), 'warning'); } } @@ -336,39 +408,40 @@ function image_gd_image_effects_text($image, $params) { // Get width of bounding box. $width = $bounds[2] - $bounds[0]; // Shift xpos to the left (full width on right, half the width on center). - $params['xpos'] -= $params['halign'] === 'center' ? (int) ($width/2) : $width; + $data['xpos'] -= $data['halign'] === 'center' ? (int) ($width / 2) : $width; } } // PHP Manual: "This function requires both the GD library and the » FreeType library." - $bounds = imagettftext($image->resource, $params['size'], $params['angle'], $params['xpos'], $params['ypos'], $color, $params['fontpath'], $params['text']); + $bounds = imagettftext($image->resource, $data['size'], $data['angle'], $data['xpos'], $data['ypos'], $color, $data['fontpath'], $data['text']); return $bounds !== FALSE; } return FALSE; } /** - * Imagemagick toolkit specific implementation of this image effect. + * Imagemagick toolkit specific implementation of the text effect. * * Text in Imagemagick: - * @link http://www.imagemagick.org/script/command-line-options.php?#draw - * @link http://www.imagemagick.org/script/command-line-options.php?#annotate + * - http://www.imagemagick.org/script/command-line-options.php?#draw + * - http://www.imagemagick.org/script/command-line-options.php?#annotate * - * UTF-8/non-ascii characters - * Though the online imagemagick manual mentions some problems with accented - * characters, it worked fine for me in a Windows Vista shell. TBC on other - * OS'es (including linux) - * (@see: http://www.imagemagick.org/Usage/windows/#character_encoding) + * UTF-8/non-ascii characters: + * To prevent problems with non-ASCII characters, the online manual suggests to + * put the text in a file and use the @{file} syntax. This does not work with + * the text primitive of the -draw command, so we use -annotate. + * We put the text in a temporary file which will be deleted by our hook_exit(). + * http://www.imagemagick.org/Usage/windows/#character_encoding * * Alignment in Imagemagick: - * This is not directly supported, though a justicifcation option has been - * proposed: @link http://www.imagemagick.org/Usage/bugs/future/#justification. + * This is not directly supported, though a justification option has been + * proposed: http://www.imagemagick.org/Usage/bugs/future/#justification. * * What we do have is the gravity option: - * @link http://www.imagemagick.org/Usage/annotating/#gravity * Gravity is used to position a text, but it also automatically applies a * justification based on that placement. So we use gravity here for alignment, * but will thus have to rebase our positioning. + * - http://www.imagemagick.org/Usage/annotating/#gravity * * Gravity |halign|valign |hpos change|vpos change * ------------------------------------------------ @@ -381,8 +454,16 @@ function image_gd_image_effects_text($image, $params) { * SouthWest left bottom 0 -height * South center bottom -width/2 -height * SouthEast right bottom -width -height + * + * @param stdClass $image + * @param array $data + * The parameters for this effect. + * + * @return bool + * true on success, false otherwise. + */ -function image_imagemagick_image_effects_text($image, $params) { +function image_imagemagick_image_effects_text(stdClass $image, array $data) { static $alignments2gravity = array( 'left' => array( 'top' => array( @@ -438,39 +519,65 @@ function image_imagemagick_image_effects_text($image, $params) { ); // Convert color and alpha to Imagemagick rgba color argument. - $alpha = $params['alpha'] / 100; - $color = 'rgba(' . $params['RGB']['red']. ',' . $params['RGB']['green'] . ',' . $params['RGB']['blue'] . ','. $alpha . ')'; + $alpha = $data['alpha'] / 100; + $color = 'rgba(' . $data['RGB']['red'] . ',' . $data['RGB']['green'] . ',' . $data['RGB']['blue'] . ',' . $alpha . ')'; - // Alignment - $alignment_corrections = $alignments2gravity[$params['halign']][$params['valign']]; + // Set gravity for the alignment and calculate the translation to the + // requested x and y offset as starting from the gravity point. + $alignment_corrections = $alignments2gravity[$data['halign']][$data['valign']]; $gravity = $alignment_corrections['gravity']; if ($alignment_corrections['tx'] > 0) { - $params['xpos'] = (int) ($alignment_corrections['tx'] * $image->info['width'] - $params['xpos']); + $data['xpos'] = (int) ($alignment_corrections['tx'] * $image->info['width'] - $data['xpos']); } else { - $params['xpos'] += (int) ($alignment_corrections['tx'] * $image->info['width']); + $data['xpos'] += (int) ($alignment_corrections['tx'] * $image->info['width']); } if ($alignment_corrections['ty'] > 0) { - $params['ypos'] = (int) ($alignment_corrections['ty'] * $image->info['height'] - $params['ypos']); + $data['ypos'] = (int) ($alignment_corrections['ty'] * $image->info['height'] - $data['ypos']); } else { - $params['ypos'] += (int) ($alignment_corrections['ty'] * $image->info['height']); + $data['ypos'] += (int) ($alignment_corrections['ty'] * $image->info['height']); } - // Define the quote to use around the text. This is part of the argument of - // the -draw command and thus should NOT be the shell argument enclosing - // character. - $quote = strstr($_SERVER['SERVER_SOFTWARE'], 'Win32') || strstr($_SERVER['SERVER_SOFTWARE'], 'IIS') ? "'" : '"'; - // and subsequently escape the use of that quote within the text. - $text = $params['text']; - $text = str_replace($quote, "\\$quote", $text); + // Add signs to translation, also when positive or 0. + if ($data['xpos'] >= 0) { + $data['xpos'] = '+' . $data['xpos']; + } + if ($data['ypos'] >= 0) { + $data['ypos'] = '+' . $data['ypos']; + } - $image->ops[] = '-font ' . escapeshellarg($params['fontpath']); - $image->ops[] = "-pointsize {$params['size']}"; + // Angle must be positive. + if ($data['angle'] < 0) { + $data['angle'] = $data['angle'] % 360 + 360; + } + + // Set font file, size and color. fontpath is the real path, not a wrapper. + $image->ops[] = '-font ' . escapeshellarg($data['fontpath']); + $image->ops[] = "-pointsize {$data['size']}"; $image->ops[] = '-fill ' . escapeshellarg($color); - // See issue http://drupal.org/node/1561214, Bootstrap should reset locale settings to UTF-8. - setlocale(LC_ALL, 'C.UTF-8'); - $image->ops[] = '-draw ' . escapeshellarg("gravity $gravity translate {$params['xpos']},{$params['ypos']} rotate {$params['angle']} text 0,0 $quote$text$quote"); + + // Add text to a temporary file, + $tmp_file_name = drupal_tempnam('temporary://', 'image_effects_text'); + $tmp_file = fopen($tmp_file_name, 'w'); + if ($tmp_file) { + fwrite($tmp_file, $data['text']); + fclose($tmp_file); + // and inform our hook_exit about it. + image_effects_text_exit($tmp_file_name); + $tmp_file_name = drupal_realpath($tmp_file_name); + $text = "@$tmp_file_name"; + } + else { + // Fallback to pass the text via the command line, let's hope there are no + // non-ASCII characters or that it works anyway (OS and locale dependent). + $text = $data['text']; + } + + // Add gravity. + $image->ops[] = "-gravity $gravity"; + // Add text angle, position and text (file) itself. + $image->ops[] = "-annotate {$data['angle']}x{$data['angle']}{$data['xpos']}{$data['ypos']} " . escapeshellarg($text); return TRUE; } @@ -499,14 +606,14 @@ function image_imagemagick_image_effects_text($image, $params) { * The algorithm will accept many more situations, though the result may be hard * to predict. * + * @param string $position + * The string defining the position. * @param int $width * The length of the horizontal dimension. * @param int $height * The length of the vertical dimension. * @param int $length * The length of the current dimension (should be either width or height). - * @param string $position - * The string defining the position. * * @return number * The computed offset in pixels. @@ -543,7 +650,7 @@ function image_effects_text_get_offset($position, $width, $height, $length) { break; case 'center': // half the current dimension as provided by $length. - $value += $sign * $length/2; + $value += $sign * $length / 2; $sign = 1; break; default: @@ -565,38 +672,123 @@ function image_effects_text_get_offset($position, $width, $height, $length) { /** * Get the text to use for this image. * - * @param object $image + * @param stdClass $image * The image the current effect is to be applied to. * @param array $data * An array containing the effect data. * * @return string - * Plain string to be placed on the image. + * Plain text to be placed on the image. */ -function image_effects_text_get_text($image, $data) { +function image_effects_text_get_text(stdClass $image, array $data) { + // Get context about the image. + $image_context = imagecache_actions_get_image_context($image, $data); + if ($data['text_source'] === 'text') { - $text = $data['text']; - } - else { - // Get context about the image. - $image_context = imagecache_actions_get_image_context($image, $data); + // Replace \n with a newline character, except when preceded by a \. + $text = preg_replace('/([^\\\\])\\\\n/', "$1\n", $data['text']); + // Replace \\n by \n. + $text = preg_replace('/\\\\\\\\n/', '\n', $text); - if ($data['text_source'] === 'alt' || $data['text_source'] === 'title') { - // Existence of an image field is not guaranteed, so check for that first. - $text = isset($image_context['image_field'][$data['text_source']]) ? $image_context['image_field'][$data['text_source']] : ''; + // Replace tokens. + $token_data = array(); + foreach ($image_context['referring_entities'] as /*$field_name =>*/ $field_referring_entities) { + foreach ($field_referring_entities as $entity_type => $entities) { + // We can pass only 1 entity per given type to token_replace(), we take + // the first. + $token_data[$entity_type] = reset($entities); + } } - else { // $data['text_source'] === 'php' - // Process the php using php_eval (rather than eval), but with GLOBAL - // variables, so they can be passed successfully. - $GLOBALS['image_context'] = $image_context; - $GLOBALS['image'] = $image; - // We don't need to check_plain() the resulting text, as the text is not - // rendered in a browser but processed on the server. - $text = module_exists('php') ? php_eval('<'.'?php global $image, $image_context; ' . $data['php'] . ' ?'.'>') : ''; - - unset($GLOBALS['image']); - unset($GLOBALS['image_context']); + if ($image_context['managed_file']) { + $token_data['file'] = $image_context['managed_file']; + } + // We should not sanitize the text as it will not be rendered in the browser + // but is rendered on the image canvas on the server. + $text = token_replace($text, $token_data, array('clear' => TRUE, 'sanitize' => FALSE)); + } + else if ($data['text_source'] === 'alt' || $data['text_source'] === 'title') { + $text = ''; + // We have 2 possible sources for the alt or title text: + // - Image field. + // - Media module (7.x-2.x) with file_entity: the alt and title come as + // fields of the file entity, stored in 'managed_file'. The names of the + // fields are field_file_image_alt_text resp. field_file_image_title_text. + // BTW: these fields are also available in the 'image_field' entry, but as + // a managed file may be existing without any image field referring to it, + // we do the lookup in the managed_file entry. + if (!empty($image_context['image_field'][$data['text_source']])) { + $text = $image_context['image_field'][$data['text_source']]; + } + else if (!empty($image_context['managed_file'])) { + $field = field_get_items('file', $image_context['managed_file'], "field_file_image_{$data['text_source']}_text"); + if ($field) { + $text = $field[0]['value']; + } } } + else { // $data['text_source'] === 'php' + // Process the php using php_eval (rather than eval), but with GLOBAL + // variables, so they can be passed successfully. + $GLOBALS['image_context'] = $image_context; + $GLOBALS['image'] = $image; + + // Get (non-alterable) context about the image style and image effect. + $execution_info = imagecache_actions_get_image_effect_context(); + $GLOBALS['image_style'] = $execution_info['image_style']; + $GLOBALS['image_effect_id'] = $execution_info['image_effect_id']; + + // We don't need to check_plain() the resulting text, as the text is not + // rendered in a browser but processed on the server. + $text = module_exists('php') ? php_eval('<' . '?php global $image, $image_context; ' . $data['php'] . ' ?' . '>') : ''; + + unset($GLOBALS['image_effect_id']); + unset($GLOBALS['image_style']); + unset($GLOBALS['image']); + unset($GLOBALS['image_context']); + } + + // Convert case. + $text = image_effect_text_case_transform($text, isset($data['text_case']) ? $data['text_case'] : 'none'); + return $text; } + +/** + * Transform a string by a certain method. + * + * Proudly copied from module://views/includes/handlers.inc. + * + * @param $string + * The input you want to transform. + * @param $option + * How do you want to transform it, possible values: + * - upper: Uppercase the string. + * - lower: lowercase the string. + * - ucfirst: Make the first char uppercase. + * - ucwords: Make each word in the string uppercase. + * + * @return string + * The transformed string. + */ +function image_effect_text_case_transform($string, $option) { + global $multibyte; + + switch ($option) { + default: + return $string; + case 'upper': + return drupal_strtoupper($string); + case 'lower': + return drupal_strtolower($string); + case 'ucfirst': + return drupal_strtoupper(drupal_substr($string, 0, 1)) . drupal_substr($string, 1); + case 'ucwords': + if ($multibyte == UNICODE_MULTIBYTE) { + return mb_convert_case($string, MB_CASE_TITLE); + } + else { + return ucwords($string); + } + } +} + diff --git a/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text.info b/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text.info index fb68962..58e3452 100644 --- a/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text.info +++ b/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text.info @@ -1,14 +1,14 @@ -name = Image Text Effects -description = Display simple or dynamic captions on images. +name = Image Effects Text +description = Provides an image effect to overlay text captions on images. package = Media core = 7.x dependencies[] = image dependencies[] = imagecache_actions -; Information added by drupal.org packaging script on 2012-12-04 -version = "7.x-1.1" +; Information added by Drupal.org packaging script on 2018-03-20 +version = "7.x-1.9" core = "7.x" project = "imagecache_actions" -datestamp = "1354653754" +datestamp = "1521550387" diff --git a/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text.install b/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text.install deleted file mode 100644 index 7505951..0000000 --- a/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text.install +++ /dev/null @@ -1,25 +0,0 @@ - l(t('settings'), 'admin/config/media/image-styles')))); -} - -/** - * Implements hook_uninstall(). - * - * This hook implementation clears the imagecache_actions cache. - */ -function image_effects_text_uninstall() { - cache_clear_all('imagecache_actions', 'cache'); -} diff --git a/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text.module b/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text.module index 0ae3226..24791b8 100644 --- a/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text.module +++ b/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text.module @@ -3,7 +3,7 @@ * @file Provide text manipulation effects for image styles. * * Ported by dman - * from http://drupal.org/node/264862#comment-865490 by patrickharris + * from https://drupal.org/node/264862#comment-865490 by patrickharris * * Ported to D7 by fietserwin * from imagecache_textactions 6.x-1.8. @@ -29,8 +29,8 @@ function image_effects_text_image_effect_info() { 'label' => t('Text'), 'help' => t('Add static or dynamic (coded) text to an image.'), 'dimensions passthrough' => TRUE, - 'form callback' => 'image_effects_text_form', 'effect callback' => 'image_effects_text_effect', + 'form callback' => 'image_effects_text_form', 'summary theme' => 'image_effects_text_summary', ); @@ -51,12 +51,13 @@ function image_effects_text_help($path, $arg) { // link to it. return ' '; } + return ''; } /** * Implements hook_theme(). * - * We register theme functions for the effect summaries. + * Registers theme functions for the effect summaries. */ function image_effects_text_theme() { return array( @@ -67,6 +68,36 @@ function image_effects_text_theme() { ); } +/** + * Implements hook_exit(). + * + * For imagemagick we place the text in a temporary file as this prevents + * problems with non-ASCII characters. This hook deletes files created during + * execution of the text effect. As a style may contain multiple text effects, + * there may be multiple files to delete. + * + * To keep track of temporary files created by this effect, the image effect + * function itself also calls this hook, but passes the filename as a parameter. + * + * @param string|null $destination + */ +function image_effects_text_exit($destination = NULL) { + static $tmp_file_names = array(); + + if (isset($destination)) { + if (substr($destination, 0 , strlen('temporary://')) === 'temporary://') { + // Called by the effect function. Add to our static list of files ot delete. + $tmp_file_names[] = $destination; + } + } + else { + // Normal invocation as Drupal hook: delete any files we have collected. + foreach ($tmp_file_names as $tmp_file_name) { + unlink($tmp_file_name); + } + } +} + /** * Builds the form structure for the overlay text image effect. */ diff --git a/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text_test/README.txt b/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text_test/README.txt index c646cb6..4dc21df 100644 --- a/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text_test/README.txt +++ b/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text_test/README.txt @@ -2,17 +2,18 @@ README ------ README for the Image effect text test module. -This module contains 2 image styles to test text effects. It uses an image +This module contains several image styles to test text effects. It uses an image containing a grid and a font which are included in the install package as well. +The image styles defined by this module start with 'text-test-'. Hard Dependencies ----------------- Hard dependencies: -- Imagecache actions. +- Imagecache actions (canvas_actions and image_effects_text). - Image (Drupal core). -- Features -- System stream wrapper (http://drupal.org/project/system_stream_wrapper) +- System stream wrapper (https://drupal.org/project/system_stream_wrapper) Soft Dependencies ----------------- -- Imagemagick (preferred toolkit, http://drupal.org/project/imagemagick). +- Imagemagick (preferred toolkit, https://drupal.org/project/imagemagick). +- GD diff --git a/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text_test/image_effects_text_test.features.inc b/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text_test/image_effects_text_test.features.inc deleted file mode 100644 index c3452f2..0000000 --- a/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text_test/image_effects_text_test.features.inc +++ /dev/null @@ -1,483 +0,0 @@ - 'text-rotate-test', - 'effects' => array( - 32 => array( - 'label' => 'Echelle', - 'help' => 'La mise à l\'échelle maintiendra les proportions originales de l\'image. Si une seule dimension est précisée, l\'autre dimension sera calculée automatiquement.', - 'effect callback' => 'image_scale_effect', - 'dimensions callback' => 'image_scale_dimensions', - 'form callback' => 'image_scale_form', - 'summary theme' => 'image_scale_summary', - 'module' => 'image', - 'name' => 'image_scale', - 'data' => array( - 'width' => '800', - 'height' => '', - 'upscale' => 0, - ), - 'weight' => '1', - ), - 33 => array( - 'label' => 'Overlay (watermark)', - 'help' => 'Choose the file image you wish to use as an overlay, and position it in a layer on top of the canvas.', - 'effect callback' => 'canvasactions_file2canvas_image', - 'dimensions passthrough' => TRUE, - 'form callback' => 'canvasactions_file2canvas_form', - 'summary theme' => 'canvasactions_file2canvas_summary', - 'module' => 'imagecache_canvasactions', - 'name' => 'canvasactions_file2canvas', - 'data' => array( - 'xpos' => 0, - 'ypos' => 0, - 'alpha' => '100', - 'path' => 'module://image_effects_text_test/grid800x600.png', - ), - 'weight' => '2', - ), - 34 => array( - 'label' => 'Texte', - 'help' => 'Add static or dynamic (coded) text to an image.', - 'dimensions passthrough' => TRUE, - 'form callback' => 'image_effects_text_form', - 'effect callback' => 'image_effects_text_effect', - 'summary theme' => 'image_effects_text_summary', - 'module' => 'image_effects_text', - 'name' => 'image_effects_text', - 'data' => array( - 'size' => '18', - 'xpos' => '100', - 'ypos' => '200', - 'halign' => 'left', - 'valign' => 'bottom', - 'RGB' => array( - 'HEX' => '000000', - ), - 'alpha' => '100', - 'angle' => '45', - 'fontfile' => 'module://image_effects_text_test/lhandw.ttf', - 'text_source' => 'text', - 'text' => '100,200 45 left,bottom', - 'php' => 'return \'Hello World!\'', - ), - 'weight' => '3', - ), - 35 => array( - 'label' => 'Texte', - 'help' => 'Add static or dynamic (coded) text to an image.', - 'dimensions passthrough' => TRUE, - 'form callback' => 'image_effects_text_form', - 'effect callback' => 'image_effects_text_effect', - 'summary theme' => 'image_effects_text_summary', - 'module' => 'image_effects_text', - 'name' => 'image_effects_text', - 'data' => array( - 'size' => '18', - 'xpos' => '300', - 'ypos' => '200', - 'halign' => 'center', - 'valign' => 'center', - 'RGB' => array( - 'HEX' => '000000', - ), - 'alpha' => '100', - 'angle' => '45', - 'fontfile' => 'module://image_effects_text_test/lhandw.ttf', - 'text_source' => 'text', - 'text' => '300,200 45 center,center', - 'php' => 'return \'Hello World!\'', - ), - 'weight' => '4', - ), - 37 => array( - 'label' => 'Texte', - 'help' => 'Add static or dynamic (coded) text to an image.', - 'dimensions passthrough' => TRUE, - 'form callback' => 'image_effects_text_form', - 'effect callback' => 'image_effects_text_effect', - 'summary theme' => 'image_effects_text_summary', - 'module' => 'image_effects_text', - 'name' => 'image_effects_text', - 'data' => array( - 'size' => '18', - 'xpos' => '500', - 'ypos' => '200', - 'halign' => 'right', - 'valign' => 'top', - 'RGB' => array( - 'HEX' => '000000', - ), - 'alpha' => '100', - 'angle' => '45', - 'fontfile' => 'module://image_effects_text_test/lhandw.ttf', - 'text_source' => 'text', - 'text' => '500,200 45 right,top', - 'php' => 'return \'Hello World!\'', - ), - 'weight' => '5', - ), - 38 => array( - 'label' => 'Texte', - 'help' => 'Add static or dynamic (coded) text to an image.', - 'dimensions passthrough' => TRUE, - 'form callback' => 'image_effects_text_form', - 'effect callback' => 'image_effects_text_effect', - 'summary theme' => 'image_effects_text_summary', - 'module' => 'image_effects_text', - 'name' => 'image_effects_text', - 'data' => array( - 'size' => '18', - 'xpos' => '100', - 'ypos' => '500', - 'halign' => 'left', - 'valign' => 'bottom', - 'RGB' => array( - 'HEX' => '000000', - ), - 'alpha' => '100', - 'angle' => '-30', - 'fontfile' => 'module://image_effects_text_test/lhandw.ttf', - 'text_source' => 'text', - 'text' => '100,500 -30 left,bottom', - 'php' => 'return \'Hello World!\'', - ), - 'weight' => '7', - ), - 39 => array( - 'label' => 'Texte', - 'help' => 'Add static or dynamic (coded) text to an image.', - 'dimensions passthrough' => TRUE, - 'form callback' => 'image_effects_text_form', - 'effect callback' => 'image_effects_text_effect', - 'summary theme' => 'image_effects_text_summary', - 'module' => 'image_effects_text', - 'name' => 'image_effects_text', - 'data' => array( - 'size' => '18', - 'xpos' => '300', - 'ypos' => '500', - 'halign' => 'center', - 'valign' => 'center', - 'RGB' => array( - 'HEX' => '000000', - ), - 'alpha' => '100', - 'angle' => '-30', - 'fontfile' => 'module://image_effects_text_test/lhandw.ttf', - 'text_source' => 'text', - 'text' => '300,500 -30 center,center', - 'php' => 'return \'Hello World!\'', - ), - 'weight' => '8', - ), - 40 => array( - 'label' => 'Texte', - 'help' => 'Add static or dynamic (coded) text to an image.', - 'dimensions passthrough' => TRUE, - 'form callback' => 'image_effects_text_form', - 'effect callback' => 'image_effects_text_effect', - 'summary theme' => 'image_effects_text_summary', - 'module' => 'image_effects_text', - 'name' => 'image_effects_text', - 'data' => array( - 'size' => '18', - 'xpos' => '600', - 'ypos' => '400', - 'halign' => 'right', - 'valign' => 'top', - 'RGB' => array( - 'HEX' => '000000', - ), - 'alpha' => '100', - 'angle' => '-30', - 'fontfile' => 'module://image_effects_text_test/lhandw.ttf', - 'text_source' => 'text', - 'text' => '600,400 -30 right,top', - 'php' => 'return \'Hello World!\'', - ), - 'weight' => '9', - ), - ), - ); - - // Exported image style: text-test. - $styles['text-test'] = array( - 'name' => 'text-test', - 'effects' => array( - 30 => array( - 'label' => 'Overlay (watermark)', - 'help' => 'Choose the file image you wish to use as an overlay, and position it in a layer on top of the canvas.', - 'effect callback' => 'canvasactions_file2canvas_image', - 'dimensions passthrough' => TRUE, - 'form callback' => 'canvasactions_file2canvas_form', - 'summary theme' => 'canvasactions_file2canvas_summary', - 'module' => 'imagecache_canvasactions', - 'name' => 'canvasactions_file2canvas', - 'data' => array( - 'xpos' => 'left', - 'ypos' => 'top', - 'alpha' => '100', - 'path' => 'module://image_effects_text_test/grid800x600.png', - ), - 'weight' => '-10', - ), - 31 => array( - 'label' => 'Tekst', - 'help' => 'Add static or dynamic (coded) text to an image.', - 'dimensions passthrough' => TRUE, - 'form callback' => 'image_effects_text_form', - 'effect callback' => 'image_effects_text_effect', - 'summary theme' => 'image_effects_text_summary', - 'module' => 'image_effects_text', - 'name' => 'image_effects_text', - 'data' => array( - 'size' => '18', - 'xpos' => '100', - 'ypos' => '200', - 'halign' => 'left', - 'valign' => 'top', - 'RGB' => array( - 'HEX' => '000000', - ), - 'alpha' => '100', - 'angle' => '0', - 'fontfile' => 'module://image_effects_text_test/lhandw.ttf', - 'text_source' => 'text', - 'text' => '100,200 left,top', - 'php' => 'return \'Hello World!\'', - ), - 'weight' => '-8', - ), - 32 => array( - 'label' => 'Tekst', - 'help' => 'Add static or dynamic (coded) text to an image.', - 'dimensions passthrough' => TRUE, - 'form callback' => 'image_effects_text_form', - 'effect callback' => 'image_effects_text_effect', - 'summary theme' => 'image_effects_text_summary', - 'module' => 'image_effects_text', - 'name' => 'image_effects_text', - 'data' => array( - 'size' => '18', - 'xpos' => '300', - 'ypos' => '200', - 'halign' => 'left', - 'valign' => 'center', - 'RGB' => array( - 'HEX' => '000000', - ), - 'alpha' => '100', - 'angle' => '0', - 'fontfile' => 'module://image_effects_text_test/lhandw.ttf', - 'text_source' => 'text', - 'text' => '300,200 left,center', - 'php' => 'return \'Hello World!\'', - ), - 'weight' => '3', - ), - 33 => array( - 'label' => 'Tekst', - 'help' => 'Add static or dynamic (coded) text to an image.', - 'dimensions passthrough' => TRUE, - 'form callback' => 'image_effects_text_form', - 'effect callback' => 'image_effects_text_effect', - 'summary theme' => 'image_effects_text_summary', - 'module' => 'image_effects_text', - 'name' => 'image_effects_text', - 'data' => array( - 'size' => '18', - 'xpos' => '500', - 'ypos' => '200', - 'halign' => 'left', - 'valign' => 'bottom', - 'RGB' => array( - 'HEX' => '000000', - ), - 'alpha' => '100', - 'angle' => '0', - 'fontfile' => 'module://image_effects_text_test/lhandw.ttf', - 'text_source' => 'text', - 'text' => '500,200 left,bottom', - 'php' => 'return \'Hello World!\'', - ), - 'weight' => '4', - ), - 34 => array( - 'label' => 'Tekst', - 'help' => 'Add static or dynamic (coded) text to an image.', - 'dimensions passthrough' => TRUE, - 'form callback' => 'image_effects_text_form', - 'effect callback' => 'image_effects_text_effect', - 'summary theme' => 'image_effects_text_summary', - 'module' => 'image_effects_text', - 'name' => 'image_effects_text', - 'data' => array( - 'size' => '18', - 'xpos' => '100', - 'ypos' => '400', - 'halign' => 'center', - 'valign' => 'top', - 'RGB' => array( - 'HEX' => '000000', - ), - 'alpha' => '100', - 'angle' => '0', - 'fontfile' => 'module://image_effects_text_test/lhandw.ttf', - 'text_source' => 'text', - 'text' => '100,400 center,top', - 'php' => 'return \'Hello World!\'', - ), - 'weight' => '5', - ), - 35 => array( - 'label' => 'Tekst', - 'help' => 'Add static or dynamic (coded) text to an image.', - 'dimensions passthrough' => TRUE, - 'form callback' => 'image_effects_text_form', - 'effect callback' => 'image_effects_text_effect', - 'summary theme' => 'image_effects_text_summary', - 'module' => 'image_effects_text', - 'name' => 'image_effects_text', - 'data' => array( - 'size' => '18', - 'xpos' => '300', - 'ypos' => '400', - 'halign' => 'center', - 'valign' => 'center', - 'RGB' => array( - 'HEX' => '000000', - ), - 'alpha' => '100', - 'angle' => '0', - 'fontfile' => 'module://image_effects_text_test/lhandw.ttf', - 'text_source' => 'text', - 'text' => '300,400 center,center', - 'php' => 'return \'Hello World!\'', - ), - 'weight' => '6', - ), - 36 => array( - 'label' => 'Tekst', - 'help' => 'Add static or dynamic (coded) text to an image.', - 'dimensions passthrough' => TRUE, - 'form callback' => 'image_effects_text_form', - 'effect callback' => 'image_effects_text_effect', - 'summary theme' => 'image_effects_text_summary', - 'module' => 'image_effects_text', - 'name' => 'image_effects_text', - 'data' => array( - 'size' => '18', - 'xpos' => '500', - 'ypos' => '400', - 'halign' => 'center', - 'valign' => 'bottom', - 'RGB' => array( - 'HEX' => '000000', - ), - 'alpha' => '100', - 'angle' => '0', - 'fontfile' => 'module://image_effects_text_test/lhandw.ttf', - 'text_source' => 'text', - 'text' => '500,400 center, bottom', - 'php' => 'return \'Hello World!\'', - ), - 'weight' => '7', - ), - 37 => array( - 'label' => 'Tekst', - 'help' => 'Add static or dynamic (coded) text to an image.', - 'dimensions passthrough' => TRUE, - 'form callback' => 'image_effects_text_form', - 'effect callback' => 'image_effects_text_effect', - 'summary theme' => 'image_effects_text_summary', - 'module' => 'image_effects_text', - 'name' => 'image_effects_text', - 'data' => array( - 'size' => '18', - 'xpos' => '100', - 'ypos' => '500', - 'halign' => 'right', - 'valign' => 'top', - 'RGB' => array( - 'HEX' => '000000', - ), - 'alpha' => '100', - 'angle' => '0', - 'fontfile' => 'module://image_effects_text_test/lhandw.ttf', - 'text_source' => 'text', - 'text' => '100,500 right,top', - 'php' => 'return \'Hello World!\'', - ), - 'weight' => '8', - ), - 38 => array( - 'label' => 'Tekst', - 'help' => 'Add static or dynamic (coded) text to an image.', - 'dimensions passthrough' => TRUE, - 'form callback' => 'image_effects_text_form', - 'effect callback' => 'image_effects_text_effect', - 'summary theme' => 'image_effects_text_summary', - 'module' => 'image_effects_text', - 'name' => 'image_effects_text', - 'data' => array( - 'size' => '18', - 'xpos' => '300', - 'ypos' => '500', - 'halign' => 'right', - 'valign' => 'center', - 'RGB' => array( - 'HEX' => '000000', - ), - 'alpha' => '100', - 'angle' => '0', - 'fontfile' => 'module://image_effects_text_test/lhandw.ttf', - 'text_source' => 'text', - 'text' => '300,500 right,center', - 'php' => 'return \'Hello World!\'', - ), - 'weight' => '9', - ), - 39 => array( - 'label' => 'Tekst', - 'help' => 'Add static or dynamic (coded) text to an image.', - 'dimensions passthrough' => TRUE, - 'form callback' => 'image_effects_text_form', - 'effect callback' => 'image_effects_text_effect', - 'summary theme' => 'image_effects_text_summary', - 'module' => 'image_effects_text', - 'name' => 'image_effects_text', - 'data' => array( - 'size' => '18', - 'xpos' => '500', - 'ypos' => '500', - 'halign' => 'right', - 'valign' => 'bottom', - 'RGB' => array( - 'HEX' => '000000', - ), - 'alpha' => '100', - 'angle' => '0', - 'fontfile' => 'module://image_effects_text_test/lhandw.ttf', - 'text_source' => 'text', - 'text' => '500,500 right,bottom', - 'php' => 'return \'Hello World!\'', - ), - 'weight' => '10', - ), - ), - ); - - return $styles; -} diff --git a/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text_test/image_effects_text_test.info b/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text_test/image_effects_text_test.info index 32092f1..5b01a01 100644 --- a/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text_test/image_effects_text_test.info +++ b/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text_test/image_effects_text_test.info @@ -1,19 +1,16 @@ name = Image Effects Text test -description = Image effects that test the text effect +description = Defines image styles that test the text effect. core = 7.x -package = Features -php = 5.2.4 -project = image_effects_text_test +package = Media + dependencies[] = image dependencies[] = image_effects_text +dependencies[] = imagecache_canvasactions dependencies[] = system_stream_wrapper -features[features_api][] = api:1 -features[image][] = text-rotate-test -features[image][] = text-test -; Information added by drupal.org packaging script on 2012-12-04 -version = "7.x-1.1" +; Information added by Drupal.org packaging script on 2018-03-20 +version = "7.x-1.9" core = "7.x" project = "imagecache_actions" -datestamp = "1354653754" +datestamp = "1521550387" diff --git a/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text_test/image_effects_text_test.module b/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text_test/image_effects_text_test.module index fff7b83..149d4c3 100644 --- a/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text_test/image_effects_text_test.module +++ b/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text_test/image_effects_text_test.module @@ -4,4 +4,606 @@ * Code for the Image Effects Text test feature. */ -include_once('image_effects_text_test.features.inc'); +/** + * Implements hook_image_default_styles(). + */ +function image_effects_text_test_image_default_styles() { + $styles = array(); + + // Exported image style: text-test-position-orientation. + $styles['text-test-position-orientation'] = array( + 'name' => 'text-test-position-orientation', + 'effects' => array( + 115 => array( + 'label' => 'Overlay (watermark)', + 'help' => 'Choose the file image you wish to use as an overlay, and position it in a layer on top of the canvas.', + 'effect callback' => 'canvasactions_file2canvas_image', + 'dimensions passthrough' => TRUE, + 'form callback' => 'canvasactions_file2canvas_form', + 'summary theme' => 'canvasactions_file2canvas_summary', + 'module' => 'imagecache_canvasactions', + 'name' => 'canvasactions_file2canvas', + 'data' => array( + 'xpos' => 'left', + 'ypos' => 'top', + 'alpha' => '100', + 'path' => 'module://image_effects_text_test/grid800x600.png', + ), + 'weight' => '-10', + ), + 116 => array( + 'label' => 'Tekst', + 'help' => 'Add static or dynamic (coded) text to an image.', + 'dimensions passthrough' => TRUE, + 'form callback' => 'image_effects_text_form', + 'effect callback' => 'image_effects_text_effect', + 'summary theme' => 'image_effects_text_summary', + 'module' => 'image_effects_text', + 'name' => 'image_effects_text', + 'data' => array( + 'size' => '18', + 'xpos' => '100', + 'ypos' => '200', + 'halign' => 'left', + 'valign' => 'top', + 'RGB' => array( + 'HEX' => '000000', + ), + 'alpha' => '100', + 'angle' => '0', + 'fontfile' => 'module://image_effects_text/Komika_display.ttf', + 'text_source' => 'text', + 'text' => '100,200 left,top', + 'php' => 'return \'Hello World!\'', + ), + 'weight' => '-8', + ), + 117 => array( + 'label' => 'Tekst', + 'help' => 'Add static or dynamic (coded) text to an image.', + 'dimensions passthrough' => TRUE, + 'form callback' => 'image_effects_text_form', + 'effect callback' => 'image_effects_text_effect', + 'summary theme' => 'image_effects_text_summary', + 'module' => 'image_effects_text', + 'name' => 'image_effects_text', + 'data' => array( + 'size' => '18', + 'xpos' => 'left+300', + 'ypos' => 'top+200', + 'halign' => 'left', + 'valign' => 'center', + 'RGB' => array( + 'HEX' => '000000', + ), + 'alpha' => '100', + 'angle' => '0', + 'fontfile' => 'module://image_effects_text/Komika_display.ttf', + 'text_source' => 'text', + 'text' => 'left+300,top+200 left,center', + 'php' => 'return \'Hello World!\'', + ), + 'weight' => '3', + ), + 118 => array( + 'label' => 'Tekst', + 'help' => 'Add static or dynamic (coded) text to an image.', + 'dimensions passthrough' => TRUE, + 'form callback' => 'image_effects_text_form', + 'effect callback' => 'image_effects_text_effect', + 'summary theme' => 'image_effects_text_summary', + 'module' => 'image_effects_text', + 'name' => 'image_effects_text', + 'data' => array( + 'size' => '18', + 'xpos' => 'right-300', + 'ypos' => '200+top', + 'halign' => 'left', + 'valign' => 'bottom', + 'RGB' => array( + 'HEX' => '000000', + ), + 'alpha' => '100', + 'angle' => '0', + 'fontfile' => 'module://image_effects_text/Komika_display.ttf', + 'text_source' => 'text', + 'text' => 'right-300,200+top left,bottom', + 'php' => 'return \'Hello World!\'', + ), + 'weight' => '4', + ), + 119 => array( + 'label' => 'Tekst', + 'help' => 'Add static or dynamic (coded) text to an image.', + 'dimensions passthrough' => TRUE, + 'form callback' => 'image_effects_text_form', + 'effect callback' => 'image_effects_text_effect', + 'summary theme' => 'image_effects_text_summary', + 'module' => 'image_effects_text', + 'name' => 'image_effects_text', + 'data' => array( + 'size' => '18', + 'xpos' => 'center-300', + 'ypos' => '100+center', + 'halign' => 'center', + 'valign' => 'top', + 'RGB' => array( + 'HEX' => '000000', + ), + 'alpha' => '100', + 'angle' => '0', + 'fontfile' => 'module://image_effects_text/Komika_display.ttf', + 'text_source' => 'text', + 'text' => 'center-300,100+center center,top', + 'php' => 'return \'Hello World!\'', + ), + 'weight' => '5', + ), + 120 => array( + 'label' => 'Tekst', + 'help' => 'Add static or dynamic (coded) text to an image.', + 'dimensions passthrough' => TRUE, + 'form callback' => 'image_effects_text_form', + 'effect callback' => 'image_effects_text_effect', + 'summary theme' => 'image_effects_text_summary', + 'module' => 'image_effects_text', + 'name' => 'image_effects_text', + 'data' => array( + 'size' => '18', + 'xpos' => '-100+center', + 'ypos' => 'center+100', + 'halign' => 'center', + 'valign' => 'center', + 'RGB' => array( + 'HEX' => '000000', + ), + 'alpha' => '100', + 'angle' => '0', + 'fontfile' => 'module://image_effects_text/Komika_display.ttf', + 'text_source' => 'text', + 'text' => '-100+center,center+100 center,center', + 'php' => 'return \'Hello World!\'', + ), + 'weight' => '6', + ), + 121 => array( + 'label' => 'Tekst', + 'help' => 'Add static or dynamic (coded) text to an image.', + 'dimensions passthrough' => TRUE, + 'form callback' => 'image_effects_text_form', + 'effect callback' => 'image_effects_text_effect', + 'summary theme' => 'image_effects_text_summary', + 'module' => 'image_effects_text', + 'name' => 'image_effects_text', + 'data' => array( + 'size' => '18', + 'xpos' => '-200+right', + 'ypos' => 'bottom-200', + 'halign' => 'center', + 'valign' => 'bottom', + 'RGB' => array( + 'HEX' => '000000', + ), + 'alpha' => '100', + 'angle' => '0', + 'fontfile' => 'module://image_effects_text/Komika_display.ttf', + 'text_source' => 'text', + 'text' => '-200+right,bottom-200 center, bottom', + 'php' => 'return \'Hello World!\'', + ), + 'weight' => '7', + ), + 122 => array( + 'label' => 'Tekst', + 'help' => 'Add static or dynamic (coded) text to an image.', + 'dimensions passthrough' => TRUE, + 'form callback' => 'image_effects_text_form', + 'effect callback' => 'image_effects_text_effect', + 'summary theme' => 'image_effects_text_summary', + 'module' => 'image_effects_text', + 'name' => 'image_effects_text', + 'data' => array( + 'size' => '18', + 'xpos' => '300', + 'ypos' => '500', + 'halign' => 'right', + 'valign' => 'top', + 'RGB' => array( + 'HEX' => '000000', + ), + 'alpha' => '100', + 'angle' => '0', + 'fontfile' => 'module://image_effects_text/Komika_display.ttf', + 'text_source' => 'text', + 'text' => '300,500 right,top', + 'php' => 'return \'Hello World!\'', + ), + 'weight' => '8', + ), + 123 => array( + 'label' => 'Tekst', + 'help' => 'Add static or dynamic (coded) text to an image.', + 'dimensions passthrough' => TRUE, + 'form callback' => 'image_effects_text_form', + 'effect callback' => 'image_effects_text_effect', + 'summary theme' => 'image_effects_text_summary', + 'module' => 'image_effects_text', + 'name' => 'image_effects_text', + 'data' => array( + 'size' => '18', + 'xpos' => 'right-center+100', + 'ypos' => 'bottom-100', + 'halign' => 'right', + 'valign' => 'center', + 'RGB' => array( + 'HEX' => '000000', + ), + 'alpha' => '100', + 'angle' => '0', + 'fontfile' => 'module://image_effects_text/Komika_display.ttf', + 'text_source' => 'text', + 'text' => 'right-center+100,bottom-100 right,center', + 'php' => 'return \'Hello World!\'', + ), + 'weight' => '9', + ), + 124 => array( + 'label' => 'Tekst', + 'help' => 'Add static or dynamic (coded) text to an image.', + 'dimensions passthrough' => TRUE, + 'form callback' => 'image_effects_text_form', + 'effect callback' => 'image_effects_text_effect', + 'summary theme' => 'image_effects_text_summary', + 'module' => 'image_effects_text', + 'name' => 'image_effects_text', + 'data' => array( + 'size' => '18', + 'xpos' => 'right-100', + 'ypos' => 'bottom-100', + 'halign' => 'right', + 'valign' => 'bottom', + 'RGB' => array( + 'HEX' => '000000', + ), + 'alpha' => '100', + 'angle' => '0', + 'fontfile' => 'module://image_effects_text/Komika_display.ttf', + 'text_source' => 'text', + 'text' => 'right-100,bottom-100 right,bottom', + 'php' => 'return \'Hello World!\'', + ), + 'weight' => '10', + ), + ), + ); + + // Exported image style: text-test-rotate-wrt-alignment. + $styles['text-test-rotate-wrt-alignment'] = array( + 'name' => 'text-test-rotate-wrt-alignment', + 'effects' => array( + 125 => array( + 'label' => 'Schalen', + 'help' => 'Door te schalen worden de originele verhoudingen behouden. Als één van de dimensies wordt ingevuld zal de andere worden berekend.', + 'effect callback' => 'image_scale_effect', + 'dimensions callback' => 'image_scale_dimensions', + 'form callback' => 'image_scale_form', + 'summary theme' => 'image_scale_summary', + 'module' => 'image', + 'name' => 'image_scale', + 'data' => array( + 'width' => '800', + 'height' => '', + 'upscale' => 0, + ), + 'weight' => '1', + ), + 126 => array( + 'label' => 'Overlay (watermark)', + 'help' => 'Choose the file image you wish to use as an overlay, and position it in a layer on top of the canvas.', + 'effect callback' => 'canvasactions_file2canvas_image', + 'dimensions passthrough' => TRUE, + 'form callback' => 'canvasactions_file2canvas_form', + 'summary theme' => 'canvasactions_file2canvas_summary', + 'module' => 'imagecache_canvasactions', + 'name' => 'canvasactions_file2canvas', + 'data' => array( + 'xpos' => 0, + 'ypos' => 0, + 'alpha' => '100', + 'path' => 'module://image_effects_text_test/grid800x600.png', + ), + 'weight' => '2', + ), + 127 => array( + 'label' => 'Tekst', + 'help' => 'Add static or dynamic (coded) text to an image.', + 'dimensions passthrough' => TRUE, + 'form callback' => 'image_effects_text_form', + 'effect callback' => 'image_effects_text_effect', + 'summary theme' => 'image_effects_text_summary', + 'module' => 'image_effects_text', + 'name' => 'image_effects_text', + 'data' => array( + 'size' => '18', + 'xpos' => '100', + 'ypos' => '200', + 'halign' => 'left', + 'valign' => 'bottom', + 'RGB' => array( + 'HEX' => '000000', + ), + 'alpha' => '100', + 'angle' => '45', + 'fontfile' => 'module://image_effects_text/Komika_display.ttf', + 'text_source' => 'text', + 'text' => '100,200 45 left,bottom', + 'php' => 'return \'Hello World!\'', + ), + 'weight' => '3', + ), + 128 => array( + 'label' => 'Tekst', + 'help' => 'Add static or dynamic (coded) text to an image.', + 'dimensions passthrough' => TRUE, + 'form callback' => 'image_effects_text_form', + 'effect callback' => 'image_effects_text_effect', + 'summary theme' => 'image_effects_text_summary', + 'module' => 'image_effects_text', + 'name' => 'image_effects_text', + 'data' => array( + 'size' => '18', + 'xpos' => '300', + 'ypos' => '200', + 'halign' => 'center', + 'valign' => 'center', + 'RGB' => array( + 'HEX' => '000000', + ), + 'alpha' => '100', + 'angle' => '66.67', + 'fontfile' => 'module://image_effects_text/Komika_display.ttf', + 'text_source' => 'text', + 'text' => '300,200 66.67 center,center', + 'php' => 'return \'Hello World!\'', + ), + 'weight' => '4', + ), + 129 => array( + 'label' => 'Tekst', + 'help' => 'Add static or dynamic (coded) text to an image.', + 'dimensions passthrough' => TRUE, + 'form callback' => 'image_effects_text_form', + 'effect callback' => 'image_effects_text_effect', + 'summary theme' => 'image_effects_text_summary', + 'module' => 'image_effects_text', + 'name' => 'image_effects_text', + 'data' => array( + 'size' => '18', + 'xpos' => '500', + 'ypos' => '200', + 'halign' => 'right', + 'valign' => 'top', + 'RGB' => array( + 'HEX' => '000000', + ), + 'alpha' => '100', + 'angle' => '90', + 'fontfile' => 'module://image_effects_text/Komika_display.ttf', + 'text_source' => 'text', + 'text' => '500,200 45 right,top', + 'php' => 'return \'Hello World!\'', + ), + 'weight' => '5', + ), + 130 => array( + 'label' => 'Tekst', + 'help' => 'Add static or dynamic (coded) text to an image.', + 'dimensions passthrough' => TRUE, + 'form callback' => 'image_effects_text_form', + 'effect callback' => 'image_effects_text_effect', + 'summary theme' => 'image_effects_text_summary', + 'module' => 'image_effects_text', + 'name' => 'image_effects_text', + 'data' => array( + 'size' => '18', + 'xpos' => '100', + 'ypos' => '500', + 'halign' => 'left', + 'valign' => 'bottom', + 'RGB' => array( + 'HEX' => '000000', + ), + 'alpha' => '100', + 'angle' => '-30', + 'fontfile' => 'module://image_effects_text/Komika_display.ttf', + 'text_source' => 'text', + 'text' => '100,500 -30 left,bottom', + 'php' => 'return \'Hello World!\'', + ), + 'weight' => '7', + ), + 131 => array( + 'label' => 'Tekst', + 'help' => 'Add static or dynamic (coded) text to an image.', + 'dimensions passthrough' => TRUE, + 'form callback' => 'image_effects_text_form', + 'effect callback' => 'image_effects_text_effect', + 'summary theme' => 'image_effects_text_summary', + 'module' => 'image_effects_text', + 'name' => 'image_effects_text', + 'data' => array( + 'size' => '18', + 'xpos' => '300', + 'ypos' => '500', + 'halign' => 'center', + 'valign' => 'center', + 'RGB' => array( + 'HEX' => '000000', + ), + 'alpha' => '100', + 'angle' => '-45', + 'fontfile' => 'module://image_effects_text/Komika_display.ttf', + 'text_source' => 'text', + 'text' => '300,500 -45 center,center', + 'php' => 'return \'Hello World!\'', + ), + 'weight' => '8', + ), + 132 => array( + 'label' => 'Tekst', + 'help' => 'Add static or dynamic (coded) text to an image.', + 'dimensions passthrough' => TRUE, + 'form callback' => 'image_effects_text_form', + 'effect callback' => 'image_effects_text_effect', + 'summary theme' => 'image_effects_text_summary', + 'module' => 'image_effects_text', + 'name' => 'image_effects_text', + 'data' => array( + 'size' => '18', + 'xpos' => '600', + 'ypos' => '400', + 'halign' => 'right', + 'valign' => 'top', + 'RGB' => array( + 'HEX' => '000000', + ), + 'alpha' => '100', + 'angle' => '-90', + 'fontfile' => 'module://image_effects_text/Komika_display.ttf', + 'text_source' => 'text', + 'text' => '600,400 -90 right,top', + 'php' => 'return \'Hello World!\'', + ), + 'weight' => '9', + ), + ), + ); + + // Exported image style: text-test-utf8-multi-line-color-transparency. + $styles['text-test-utf8-multi-line-color-transparency'] = array( + 'name' => 'text-test-utf8-multi-line-color-transparency', + 'effects' => array( + 133 => array( + 'label' => 'Overlay (watermark)', + 'help' => 'Choose the file image you wish to use as an overlay, and position it in a layer on top of the canvas.', + 'effect callback' => 'canvasactions_file2canvas_image', + 'dimensions passthrough' => TRUE, + 'form callback' => 'canvasactions_file2canvas_form', + 'summary theme' => 'canvasactions_file2canvas_summary', + 'module' => 'imagecache_canvasactions', + 'name' => 'canvasactions_file2canvas', + 'data' => array( + 'xpos' => 'left', + 'ypos' => 'top', + 'alpha' => '100', + 'path' => 'module://image_effects_text_test/grid800x600.png', + ), + 'weight' => '-10', + ), + 114 => array( + 'label' => 'Tekst', + 'help' => 'Add static or dynamic (coded) text to an image.', + 'dimensions passthrough' => TRUE, + 'form callback' => 'image_effects_text_form', + 'effect callback' => 'image_effects_text_effect', + 'summary theme' => 'image_effects_text_summary', + 'module' => 'image_effects_text', + 'name' => 'image_effects_text', + 'data' => array( + 'size' => '20', + 'xpos' => '100', + 'ypos' => '100', + 'halign' => 'left', + 'valign' => 'top', + 'RGB' => array( + 'HEX' => 'FF0000', + ), + 'alpha' => '100', + 'angle' => '0', + 'fontfile' => 'sites/all/modules/imagecache_actions/image_effects_text/Komika_display.ttf', + 'text_source' => 'text', + 'text' => 'top+100,left+100: é è à ù ç Ö ÿ ‘ ’ “ ” ° ² ³ € x ÷', + 'php' => 'if (!$image_context[\'entity\']) { + return \'No referring entity\'; +} +$entity_type = \'node\'; +$field_name = \'my_field\'; +$entity = $image_context[\'entity\']; +$field = field_get_items($entity_type, $entity, $field_name); +if ($field) { + return isset($field[0][\'value\']) ? $field[0][\'value\'] : \'No field value\'; +} +', + ), + 'weight' => '-9', + ), + 135 => array( + 'label' => 'Tekst', + 'help' => 'Add static or dynamic (coded) text to an image.', + 'dimensions passthrough' => TRUE, + 'form callback' => 'image_effects_text_form', + 'effect callback' => 'image_effects_text_effect', + 'summary theme' => 'image_effects_text_summary', + 'module' => 'image_effects_text', + 'name' => 'image_effects_text', + 'data' => array( + 'size' => '50', + 'xpos' => '100', + 'ypos' => '200', + 'halign' => 'left', + 'valign' => 'top', + 'RGB' => array( + 'HEX' => '0000FF', + ), + 'alpha' => '90', + 'angle' => '0', + 'fontfile' => 'sites/all/modules/imagecache_actions/image_effects_text/Komika_display.ttf', + 'text_source' => 'text', + 'text' => 'Hello World!\\nOlá, mundo\\\\nHej världen', + 'php' => 'if (!$image_context[\'entity\']) { + return \'No referring entity\'; +} +$entity_type = \'node\'; +$field_name = \'my_field\'; +$entity = $image_context[\'entity\']; +$field = field_get_items($entity_type, $entity, $field_name); +if ($field) { + return isset($field[0][\'value\']) ? $field[0][\'value\'] : \'No field value\'; +} +', + ), + 'weight' => '-8', + ), + 134 => array( + 'label' => 'Tekst', + 'help' => 'Add static or dynamic (coded) text to an image.', + 'dimensions passthrough' => TRUE, + 'form callback' => 'image_effects_text_form', + 'effect callback' => 'image_effects_text_effect', + 'summary theme' => 'image_effects_text_summary', + 'module' => 'image_effects_text', + 'name' => 'image_effects_text', + 'data' => array( + 'size' => '50', + 'xpos' => '400', + 'ypos' => 'bottom-100', + 'halign' => 'center', + 'valign' => 'bottom', + 'RGB' => array( + 'HEX' => 'FFFFFF', + ), + 'alpha' => '50', + 'angle' => '0', + 'fontfile' => 'arial.ttf', + 'text_source' => 'php', + 'text' => 'Hello World!', + 'php' => 'return "Hello World!\\nOlá, mundo\\\\nHej världen"; +', + ), + 'weight' => '-7', + ), + ), + ); + + return $styles; +} diff --git a/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text_test/lhandw.ttf b/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text_test/lhandw.ttf deleted file mode 100644 index 0b68973..0000000 Binary files a/sites/all/modules/imagecache_actions/image_effects_text/image_effects_text_test/lhandw.ttf and /dev/null differ diff --git a/sites/all/modules/imagecache_actions/image_overlay.inc b/sites/all/modules/imagecache_actions/image_overlay.inc index 34ef278..b68bc81 100644 --- a/sites/all/modules/imagecache_actions/image_overlay.inc +++ b/sites/all/modules/imagecache_actions/image_overlay.inc @@ -5,178 +5,201 @@ * layers, preserving transparency. */ -// Not sure where this library will live between upgrade versions. -// Be careful to not conflict with another copy of myself. -if (! function_exists('image_overlay')) { - - /** - * Place one image over another - * - * @param object $image - * An image object. - * @param object $overlay - * An image object. - * @param $x - * Position of the overlay - * @param $y - * Position of the overlay - * @param $alpha - * Transparency of the overlay from 0-100. 0 is totally transparent. 100 - * (default) is totally opaque. - * @param $reverse - * BOOL flag to indicate the 'overlay' actually goes under the image. As - * the imageapi callbacks modify the $image object by reference, this is needed - * to replace the old image resource with the new one. - * @return bool success - * - * @ingroup imageapi - */ - function image_overlay($image, &$layer, $x, $y, $alpha = 100, $reverse = FALSE) { - if ($reverse) { - $x = imagecache_actions_keyword_filter($x, $layer->info['width'], $image->info['width']); - $y = imagecache_actions_keyword_filter($y, $layer->info['height'], $image->info['height']); - } - else { - $x = imagecache_actions_keyword_filter($x, $image->info['width'], $layer->info['width']); - $y = imagecache_actions_keyword_filter($y, $image->info['height'], $layer->info['height']); - } - return image_toolkit_invoke('overlay', $image, array($layer, $x, $y, $alpha, $reverse)); +/** + * Places one image over another. + * + * @param stdClass $image + * A valid image object. + * @param stdClass $layer + * A valid image object to be placed over or under the $image image. + * @param int $x + * Position of the overlay. + * @param int $y + * Position of the overlay. + * @param int $alpha + * Transparency of the overlay from 0-100. 0 is totally transparent. 100 + * (default) is totally opaque. + * @param boolean $reverse + * Flag to indicate the 'overlay' actually goes under the image. + * + * @return boolean + * true on success, false otherwise. + */ +function image_overlay(stdClass $image, stdClass $layer, $x, $y, $alpha = 100, $reverse = FALSE) { + if ($reverse) { + $x = imagecache_actions_keyword_filter($x, $layer->info['width'], $image->info['width']); + $y = imagecache_actions_keyword_filter($y, $layer->info['height'], $image->info['height']); } - - /** - * Place one image over another - * This modifies the passed image by reference - * - * This func is nominated for inclusion in imageapi package. Until then, we do - * it ourselves. - * - * NOTE that the PHP libraries are not great at merging images SO we include a - * library that does it pixel-by-pixel which is INCREDIBLY inefficient. If this - * can be improved, in a way that supports all transparency, please let us know! - * - * A watermark is layer onto image, return the image. An underlay is image onto - * layer, return the layer. Almost identical, but seeing as we work with - * resource handles, the handle needs to be swapped before returning. - * - * @ingroup imageapi - * @param $image - * Base imageapi object. - * @param $overlay - * May be a filename or an imageAPI object - * @param $x - * Position of the overlay - * @param $y - * Position of the overlay - * @param $alpha - * Transparency of the overlay from 0-100. 0 is totally transparent. 100 - * (default) is totally opaque. - * @param $reverse - * BOOL flag to indicate the 'overlay' actually goes under the image. As - * the imageapi callbacks modify the $image object by reference, this is needed - * to replace the old image resource with the new one. - * @return bool success - */ - function image_gd_overlay($image, $layer, $x, $y, $alpha = 100, $reverse = FALSE) { - if (empty($layer->resource)) { - trigger_error("Invalid input to " . __FUNCTION__ . " 'layer' is not a valid resource"); - #dpm($layer); - return FALSE; - } - // If the given alpha is 100%, we can use imagecopy - which actually works, - // Is more efficient, and seems to retain the overlays partial transparancy - // Still does not work great for indexed gifs though? - if ($reverse) { - $upper = &$image; - $lower = &$layer; - } - else { - $upper = &$layer; - $lower = &$image; - } - if ($alpha == 100 && ($upper->info['mime_type'] != 'image/gif')) { - imagealphablending($lower->resource, TRUE); - imagesavealpha($lower->resource, TRUE); - imagealphablending($upper->resource, TRUE); - imagesavealpha($upper->resource, TRUE); - imagecopy($lower->resource, $upper->resource, $x, $y, 0, 0, $upper->info['width'], $upper->info['height']); - imagedestroy($upper->resource); - $image->resource = $lower->resource; - $image->info = $lower->info; - } - else { - // Else imagecopymerge fails and we have to use the slow library. - module_load_include('inc', 'imagecache_actions', 'watermark'); - $watermark = new watermark(); - $result_img = $watermark->create_watermark($lower->resource, $upper->resource, $x, $y, $alpha); - // Watermark creates a new image resource, so clean up both old images. - imagedestroy($lower->resource); - imagedestroy($upper->resource); - $image->resource = $result_img; - $image->info = $lower->info; - } - - return TRUE; + else { + $x = imagecache_actions_keyword_filter($x, $image->info['width'], $layer->info['width']); + $y = imagecache_actions_keyword_filter($y, $image->info['height'], $layer->info['height']); } - - /** - * Improvements on this are welcomed! - * - * Please be aware of the limitations of imagemagick libraries out there - the - * versions distributed on hosted servers (if any) are often several years - * behind. Using the latest imagemagick release features will make this function - * unusable in real deployments. - * - * @param $image - * Base imageapi object. - * @param $layer - * May be a filename or an imageAPI object, gets placed on top - * If using reverse, this is going to be the result we carry on working with. - */ - function image_imagemagick_overlay($image, $layer, $x = 0, $y = 0, $alpha = 100, $reverse = FALSE) { - // In imagemagick terms: - // - $image is the destination (the image being constructed) - // - $layer is the source (the source of the current operation) - // Add the layer image to the imagemagick command line. - $image->ops[] = escapeshellarg($layer->source) . ' '; - - // Set its offset. Offset arguments require a sign in front. - if ($x >= 0) { - $x = "+$x"; - } - if ($y >= 0) { - $y = "+$y"; - } - $image->ops[] = " -geometry $x$y"; - - // And compose it with the destination. - if ($alpha == 100) { - // Lay one image over the other. The transparency channel of the upper - // image and/or its dimensions (being smaller than the lower image) will - // determine what remains visible of the lower image). - // - // Note: In explicitly setting a -compose operator we reset/overwrite any - // previously set one (former versions could produce erroneous results - // in combination with other effects before this one). - if ($reverse) { - $compose_operator = 'dst-over'; - } - else { - $compose_operator = 'src-over'; - } - $image->ops[] = "-compose $compose_operator -composite "; - } - else { - // Alpha is not 100, so this image effect turns into a blend operation. - // The alpha determines what percentage of the upper image pixel will be - // taken. From the lower image pixel, 100 - alpha percent will be taken. - // - // Note 1: I'm not sure if and how transparency of one or both images is - // used in or after the blend operation. - // Note 2: As of IM v6.5.3-4 (around june 2009) we can use: - // -compose blend -define compose:args=30[,70] - $image->ops[] = "-compose blend -define compose:args=$alpha -composite "; - } - return TRUE; - } - + return image_toolkit_invoke('overlay', $image, array($layer, $x, $y, $alpha, $reverse)); +} + +/** + * GD toolkit specific implementation of the image overlay effect. + * + * NOTE that the PHP libraries are not great at merging images SO we include a + * library that does it pixel-by-pixel which is INCREDIBLY inefficient. If this + * can be improved, in a way that supports all transparency, please let us know! + * + * A watermark is layer onto image, return the image. An underlay is image onto + * layer, return the layer. Almost identical, but seeing as we work with + * resource handles, the handle needs to be swapped before returning. + * + * @param stdClass $image + * An image object. + * @param stdClass $layer + * Image object to be placed over or under the $image image. + * @param int $x + * Position of the overlay. + * @param int $y + * Position of the overlay. + * @param int $alpha + * Transparency of the overlay from 0-100. 0 is totally transparent. 100 + * (default) is totally opaque. + * @param boolean $reverse + * Flag to indicate that the 'overlay' actually goes under the image. + * + * @return boolean + * true on success, false otherwise. + */ +function image_gd_overlay(stdClass $image, stdClass $layer, $x, $y, $alpha = 100, $reverse = FALSE) { + // If the given alpha is 100%, we can use imagecopy - which actually works, + // is more efficient, and seems to retain the overlays partial transparency. + // Still does not work great for indexed gifs though? + if ($reverse) { + $upper = &$image; + $lower = &$layer; + } + else { + $upper = &$layer; + $lower = &$image; + } + if ($alpha == 100 && ($upper->info['mime_type'] != 'image/gif')) { + imagealphablending($lower->resource, TRUE); + imagesavealpha($lower->resource, TRUE); + imagealphablending($upper->resource, TRUE); + imagesavealpha($upper->resource, TRUE); + imagecopy($lower->resource, $upper->resource, $x, $y, 0, 0, $upper->info['width'], $upper->info['height']); + imagedestroy($upper->resource); + $image->resource = $lower->resource; + $image->info = $lower->info; + } + else { + // imagecopy() cannot be used and we have to use the slow library. + module_load_include('inc', 'imagecache_actions', 'watermark'); + $watermark = new watermark(); + $result_img = $watermark->create_watermark($lower->resource, $upper->resource, $x, $y, $alpha); + // Watermark creates a new image resource, so clean up both old images. + imagedestroy($lower->resource); + imagedestroy($upper->resource); + $image->resource = $result_img; + $image->info = $lower->info; + } + + return TRUE; +} + +/** + * Imagemagick toolkit specific implementation of the image overlay effect. + * + * An underlay should can be created like: + * -background None -extent 300x250-50-50 under.jpg -compose dst-over -composite + * + * Explanation: + * - first enlarge the canvas, making any new part fully transparent. + * - placing the "original" image on its requested position + * - define the "source" image to IM + * - compose the images placing the original over the source + * + * An overlay can be created like: + * overlay.png -geometry 50x50+100+75 -compose src-over -composite + * + * Explanation: + * - define the overlay image to IM + * - and define its size and position on the current image + * - compose the images placing the original over the source + * + * Please be aware of the limitations of imagemagick libraries out there - the + * versions distributed on hosted servers (if any) are often several years + * behind. Using the latest imagemagick release features will make this function + * unusable in real deployments. + * + * @param stdClass $image + * An image object. + * @param stdClass $layer + * Image object to be placed over or under the $image image. + * @param int $x + * Position of the overlay. + * @param int $y + * Position of the overlay. + * @param int $alpha + * Transparency of the overlay from 0-100. 0 is totally transparent. 100 + * (default) is totally opaque. + * @param boolean $reverse + * Flag to indicate that the 'overlay' actually goes under the image. + * + * @return boolean + * true on success, false otherwise. + */ +function image_imagemagick_overlay(stdClass $image, stdClass $layer, $x = 0, $y = 0, $alpha = 100, $reverse = FALSE) { + $realPath = imagecache_actions_find_file($layer->source); + if (!$realPath) { + return FALSE; + } + + // Reset any gravity settings from earlier effects. + $image->ops[] = '-gravity None'; + + // In imagemagick terms: + // - $image is the destination (the image being constructed) + // - $layer is the source (the source of the current operation) + // Add the layer image to the imagemagick command line. + + // Set the dimensions of the overlay. Use of the scale option means that we + // need to change the dimensions: always set them, they don't harm when the + // scale option is not used. + $sign = $reverse ? -1 : 1; + $geometry = sprintf('%ux%u%+d%+d', $layer->info['width'], $layer->info['height'], $sign * $x, $sign * $y); + + $compose_operator = $reverse ? 'dst-over' : 'src-over'; + + // And compose it with the destination. + if ($alpha == 100) { + // Lay one image over the other. The transparency channel of the upper + // image and/or its dimensions (being smaller than the lower image) will + // determine what remains visible of the lower image). + // + // Note: In explicitly setting a -compose operator we reset/overwrite any + // previously set one (former versions could produce erroneous results + // in combination with other effects before this one). + if ($reverse) { + // Underlay. + $image->ops[] = '-background'; + $image->ops[] = escapeshellarg('rgba(0,0,0,0)'); + $image->ops[] = "-extent $geometry"; + $image->ops[] = escapeshellarg($realPath); + } + else { + $image->ops[] = escapeshellarg($realPath); + $image->ops[] = "-geometry $geometry"; + } + $image->ops[] = "-compose $compose_operator -composite"; + } + else { + // Alpha is not 100, so this image effect turns into a blend operation. + // The alpha determines what percentage of the upper image pixel will be + // taken. From the lower image pixel, 100 - alpha percent will be taken. + // + // Note 1: I'm not sure if and how transparency of one or both images is + // used in or after the blend operation. + // Note 2: As of IM v6.5.3-4 (around june 2009) we can use: + // -compose blend -define compose:args=30[,70] + $image->ops[] = escapeshellarg($realPath); + $image->ops[] = "-geometry $geometry"; + $image->ops[] = "-compose blend -define compose:args=$alpha -composite"; + } + return TRUE; } diff --git a/sites/all/modules/imagecache_actions/image_styles_admin/README.txt b/sites/all/modules/imagecache_actions/image_styles_admin/README.txt index ff7d4e7..358c3a8 100644 --- a/sites/all/modules/imagecache_actions/image_styles_admin/README.txt +++ b/sites/all/modules/imagecache_actions/image_styles_admin/README.txt @@ -1,10 +1,10 @@ README for the Image styleds admin Drupal module ------------------------------------------------ -Project page: http://drupal.org/project/imagecache_actions +Project page: https://drupal.org/project/imagecache_actions Current and past maintainers for Image styles admin: -- fietserwin (http://drupal.org/user/750928) +- fietserwin (https://drupal.org/user/750928) Release notes for 7.x-1.x-dev @@ -24,12 +24,12 @@ a test/showcase sute of styles. Finally, it allows everybody to test D8 image module features in real life. This module is not a replacement for the features module -(http://drupal.org/project/features). If you are serious about configuration +(https://drupal.org/project/features). If you are serious about configuration management and want to distribute styles to other systems, use features. Use this module for 1 time export/imports between different sites, "copy & paste" reuse within a site, and when reporting issues to the imagecache_actions -issue queue. +issue queue. TODO diff --git a/sites/all/modules/imagecache_actions/image_styles_admin/image_styles_admin.css b/sites/all/modules/imagecache_actions/image_styles_admin/image_styles_admin.css new file mode 100644 index 0000000..9dfd40b --- /dev/null +++ b/sites/all/modules/imagecache_actions/image_styles_admin/image_styles_admin.css @@ -0,0 +1,52 @@ +table#image-styles { + table-layout: fixed; +} + +#image-styles th.style-name { + width: 20%; +} + +#image-styles th.effects { + width: 30%; +} + +#image-styles th.settings { + width: 15%; +} + +#image-styles td { + vertical-align: top; +} + +#image-styles .expand.inner { + background: transparent url(/misc/menu-collapsed.png) left 0.6em no-repeat; + margin-left: -12px; + padding-left: 12px; +} + +#image-styles .expanded.expand.inner { + background: transparent url(/misc/menu-expanded.png) left 0.6em no-repeat; +} + +#image-styles .description { + cursor: pointer; +} + +#image-styles .description .inner { + overflow: hidden; /* truncates descriptions if too long */ + text-overflow: ellipsis; + white-space: nowrap; +} + +#image-styles .description .expanded.inner { + overflow: visible; + white-space: normal; +} + +#image-styles .description .expanded .details { + display: block; +} + +#image-styles .description .expanded .separator { + display: none; +} diff --git a/sites/all/modules/imagecache_actions/image_styles_admin/image_styles_admin.inc b/sites/all/modules/imagecache_actions/image_styles_admin/image_styles_admin.inc index 767402d..85188d8 100644 --- a/sites/all/modules/imagecache_actions/image_styles_admin/image_styles_admin.inc +++ b/sites/all/modules/imagecache_actions/image_styles_admin/image_styles_admin.inc @@ -16,7 +16,9 @@ */ function image_styles_admin_duplicate_page_callback($style) { $duplicate_style = image_styles_admin_duplicate($style); - drupal_set_message(t('Style %name has been duplicated to %new_name.', array('%name' => $style['name'], '%new_name' => $duplicate_style['name']))); + drupal_set_message(t('Style %name has been duplicated to %new_name.', array( + '%name' => isset($style['label']) ? $style['label'] : $style['name'], + '%new_name' => isset($duplicate_style['label']) ? $duplicate_style['label'] : $duplicate_style['name']))); drupal_goto('admin/config/media/image-styles'); } @@ -29,14 +31,19 @@ function image_styles_admin_duplicate_page_callback($style) { * The preferred name for the new style. If left empty, the new name will be * based on the name of the style to duplicate. In both cases and when * necessary, the new name will be made unique by adding some suffix to it. + * @param string|null $new_style_label + * The preferred label for the new style. If left empty, the new label will be + * based on the label of the style to duplicate. If that one is also empty, + * no label will be defined for the new style, so Drupal (>=7.23) will create + * one. * * @return array * An image style array with the newly created copy of the given style. * * @see image_style_name_validate() */ -function image_styles_admin_duplicate($style, $new_style_name = NULL) { - // Find a unique name for copy. +function image_styles_admin_duplicate($style, $new_style_name = NULL, $new_style_label = NULL) { + // Find a unique name for the copy. // Step 1: Find the base: name without things like '-copy' or '-copy-1' $style_name_base = empty($new_style_name) ? $style['name'] : $new_style_name; if (preg_match('/-copy(-\d+)?$/', $style_name_base)) { @@ -58,6 +65,25 @@ function image_styles_admin_duplicate($style, $new_style_name = NULL) { } $style['name'] = $style_name; + // Step 4: Find a new label for the copy. + if (isset($new_style_label) || isset($style['label'])) { + $style_label = empty($new_style_label) ? $style['label'] : $new_style_label; + $copy = t('copy'); + if (preg_match("/ $copy( \d+)?$/", $style_label)) { + $style_label = substr($style_label, 0, strpos($style_label, " $copy")); + } + + // Step 4a: Add " copy" to it (if the name comes from the current style). + if (empty($new_style_label)) { + $style_label .= " $copy"; + } + + // Step 4b: Make "unique" (based on the number added to the name) + if ($i > 0) { + $style['label'] .= " $i"; + } + } + // Unset isid to save it as a new style. unset($style['isid']); $style = image_style_save($style); @@ -74,31 +100,35 @@ function image_styles_admin_duplicate($style, $new_style_name = NULL) { } /** -* drupal_get_form callback: form to export an image style. -* -* @param array $style -* An image style array. -*/ + * drupal_get_form callback: form to export an image style. + * + * @param array $form + * @param array $form_state + * @param array $style + * An image style array. + * + * @return array + */ function image_styles_admin_export_form($form, $form_state, $style) { - drupal_set_title(format_string('%page_name @style_name', array('%page_name' => t('Export image style'), '@style_name' => $style['name'])), PASS_THROUGH); + drupal_set_title(format_string('%page_name @style_name', + array('%page_name' => t('Export image style'), '@style_name' => isset($style['label']) ? $style['label'] : $style['name'])), + PASS_THROUGH); $form['serialized_style'] = array( '#type' => 'textarea', '#rows' => 5, '#title' => t('Image style export data'), - '#default_value' => serialize($style), + '#default_value' => image_styles_admin_export_serialize($style), '#attributes' => array('readonly' =>'readonly'), - '#description' => t('Copy the contents of this field to the clipboard and, on another site, paste it in the textarea of an %page_title page.', array('%page_title' => t('Import image style'))), + '#description' => t('Copy the contents of this field to the clipboard and, on another site, paste it in the textarea of an %page_title page.', + array('%page_title' => t('Import image style'))), ); return $form; } /** * drupal_get_form callback: form to import an image style. -* -* @param array $style -* An image style array. */ -function image_styles_admin_import_form($form, $form_state) { +function image_styles_admin_import_form($form/*, $form_state*/) { $form['serialized_style'] = array( '#type' => 'textarea', '#rows' => 5, @@ -121,7 +151,8 @@ function image_styles_admin_import_form($form, $form_state) { * Callback to validate the import style form. */ function image_styles_admin_import_form_validate($form, &$form_state) { - if (image_styles_admin_import_extract_style($form_state['values']['serialized_style']) === FALSE) { + $import = image_styles_admin_unify_newlines($form_state['values']['serialized_style']); + if (image_styles_admin_import_extract_style($import) === FALSE) { form_set_error('serialized_style', t('The %field cannot be imported as an image style.', array('%field' => t('Image style import data')))); } } @@ -130,19 +161,64 @@ function image_styles_admin_import_form_validate($form, &$form_state) { * Callback to process form submission of the import style form. */ function image_styles_admin_import_form_submit($form, &$form_state) { - $style = image_styles_admin_import_extract_style($form_state['values']['serialized_style']); + $import = image_styles_admin_unify_newlines($form_state['values']['serialized_style']); + $style = image_styles_admin_import_extract_style($import); // Import the style by "duplicating" it, but prevent adding the -copy suffix - // by passing the requested name as 2nd parameter. - $new_style = image_styles_admin_duplicate($style, $style['name']); + // by passing the requested name and label as 2nd and 3rd parameter. + $new_style = image_styles_admin_duplicate($style, $style['name'], isset($style['label']) ? $style['label'] : NULL); if ($new_style['name'] === $style['name']) { drupal_set_message(t('Style %name has been imported.', array('%name' => $style['name']))); } else { - drupal_set_message(t('Style %name has been imported as %new_name.', array('%name' => $style['name'], '%new_name' => $new_style['name']))); + drupal_set_message(t('Style %name has been imported as %new_name.', array( + '%name' => isset($style['label']) ? $style['label'] : $style['name'], + '%new_name' => isset($new_style['label']) ? $new_style['label'] : $new_style['name']))); } drupal_goto('admin/config/media/image-styles'); } +/** + * Serializes image style data so it can be exported. + * + * @param array $style + * An image style array. + * + * @return string + * The serialized image style. Keys that are not needed for import are not + * serialized. + */ +function image_styles_admin_export_serialize($style) { + $style = array_intersect_key($style, array('name' => 0, 'label' => 0, 'effects' => 0)); + foreach ($style['effects'] as &$effect) { + $effect = array_intersect_key($effect, array('weight' => 0, 'name' => 0, 'data' => 0)); + } + + array_walk_recursive($style, function(&$value) { + if (is_string($value)) { + $value = image_styles_admin_unify_newlines($value); + } + }); + return serialize($style); +} + +/** + * Unifies newlines in the string to the Unix newline standard. + * + * #2636314: textareas may convert newlines to the underlying OS style: convert + * all new lines to Unix style before unserializing. As string length is in the + * serialized data, we must ensure that we also do this on each array value + * before serializing. + * + * @param string $str + * + * @return string + */ +function image_styles_admin_unify_newlines($str) { + $str = str_replace("\r\n", "\n", $str); + $str = str_replace("\r", "\n", $str); + return $str; +} + /** * Unserializes and validates a string into image style data. * @@ -154,25 +230,31 @@ function image_styles_admin_import_form_submit($form, &$form_state) { * image style data. */ function image_styles_admin_import_extract_style($import) { - $style = @unserialize($import); + $style = unserialize($import); // Check if the contents of the textarea could be unserialized into an array. if (!is_array($style)) { return FALSE; } - // Check if the required keys are available, we will ignore the other. - $style = array_intersect_key($style, array('name' => 0, 'effects' => 0)); - if (count($style) !== 2) { + + // Filter out keys that we do not process. + $style = array_intersect_key($style, array('name' => 0, 'label' => 0, 'effects' => 0)); + + // 'name' is required and must be "machine name" string. + if (!isset($style['name']) || !is_string($style['name']) || preg_match('/[0-9a-z_\-]+/', $style['name']) !== 1) { return FALSE; } - // 'name' must be "machine name" string - if (!is_string($style['name']) || preg_match('/[0-9a-z_\-]+/', $style['name']) !== 1) { + + // Optional 'label' must be a string. + if (isset($style['label']) && !is_string($style['label'])) { return FALSE; } - // 'effects' must be an array - if (!is_array($style['effects'])) { + + // 'effects' is required and must be an array. + if (!isset($style['effects']) || !is_array($style['effects'])) { return FALSE; } + // Check effects elements foreach ($style['effects'] as &$effect) { // an effect must be an array. @@ -197,6 +279,7 @@ function image_styles_admin_import_extract_style($import) { return FALSE; } } + // @todo: are there any security implications for creating styles like this? // - Unserialize() is save in itself: it only creates data (except possibly // for__wakeup(), but that can only be in already existing code: safe @@ -209,14 +292,14 @@ function image_styles_admin_import_extract_style($import) { // contain invalid values. This is acceptable as it can also be done by // operating directly on the database. In Drupal this is not normally // checked for during processing: error messages will make clear that the - // data has been played with. Can incorrect dat be abused? It may contain: + // data has been played with. Can incorrect data be abused? It may contain: // - incorrect types: we do not know the data structure of the various // effects, so we cannot check that and have to accept it as it comes. // Effects should check_plain in summary theme and convert to int/float - // whenever possible befoire using it in commands. + // whenever possible before using it in commands. // - PHP code, but that may be valid content for the text or custom effects: // Effects should check_plain in summary theme and convert to int/float - // whenever possible befoire using it in commands. + // whenever possible before using it in commands. // @todo: if the style contains an effect that contains PHP code, the user // should need the 'use PHP for settings' permission. // - HTML and or JS code: when used as parameter, this normally won't hurt. diff --git a/sites/all/modules/imagecache_actions/image_styles_admin/image_styles_admin.info b/sites/all/modules/imagecache_actions/image_styles_admin/image_styles_admin.info index 9ed4993..83ad8d9 100644 --- a/sites/all/modules/imagecache_actions/image_styles_admin/image_styles_admin.info +++ b/sites/all/modules/imagecache_actions/image_styles_admin/image_styles_admin.info @@ -1,13 +1,13 @@ name = Image styles admin -description = Provides additional administrative image style functionality. +description = Provides additional administrative functionality to duplicate, export or import image styles. package = Media core = 7.x dependencies[] = image -; Information added by drupal.org packaging script on 2012-12-04 -version = "7.x-1.1" +; Information added by Drupal.org packaging script on 2018-03-20 +version = "7.x-1.9" core = "7.x" project = "imagecache_actions" -datestamp = "1354653754" +datestamp = "1521550387" diff --git a/sites/all/modules/imagecache_actions/image_styles_admin/image_styles_admin.js b/sites/all/modules/imagecache_actions/image_styles_admin/image_styles_admin.js new file mode 100644 index 0000000..4af7674 --- /dev/null +++ b/sites/all/modules/imagecache_actions/image_styles_admin/image_styles_admin.js @@ -0,0 +1,19 @@ +(function($) { +"use strict"; + +Drupal.behaviors.imageStylesAdmin = { + attach: function(context) { + $('#image-styles').find('th.expand-all').once('expand-all', function() { + $(this).click(function() { + $('#image-styles').find('td.description .inner.expand').addClass('expanded'); + }); + }); + $('#image-styles').find('td.description').once('description', function() { + $('.inner.expand', $(this)).click(function() { + $(this).toggleClass('expanded'); + }); + }); + } +}; + +})(jQuery); diff --git a/sites/all/modules/imagecache_actions/image_styles_admin/image_styles_admin.module b/sites/all/modules/imagecache_actions/image_styles_admin/image_styles_admin.module index 21983ca..8ffe56c 100644 --- a/sites/all/modules/imagecache_actions/image_styles_admin/image_styles_admin.module +++ b/sites/all/modules/imagecache_actions/image_styles_admin/image_styles_admin.module @@ -41,36 +41,122 @@ function image_styles_admin_menu() { * Implements hook_preprocess_HOOK for theme image_style_list. */ function image_styles_admin_preprocess_image_style_list(&$variables) { - // Tell imagecache_actions_preprocess_image_style_list to preprocess the next - // call to theme_table() - $flag = TRUE; - image_styles_admin_preprocess_table($flag); + // Sort the image styles by name. + uasort($variables['styles'], function ($a, $b) { + return strcasecmp($a['label'], $b['label']); + }); + + // Tell imagecache_actions_preprocess_table to preprocess the next call to + // theme_table(). + $image_styles = array_values($variables['styles']); + + image_styles_admin_preprocess_table($image_styles); + + // Add CSS and JS files. + drupal_add_css(drupal_get_path('module', 'image_styles_admin') . '/image_styles_admin.css'); + if (base_path() !== '/') { + $base_path = base_path(); + drupal_add_css(" + #image-styles .expand.inner { background-image: url($base_path/misc/menu-collapsed.png) } + #image-styles .expanded.expand.inner { background-image: url($base_path/misc/menu-expanded.png) }", + array('type' => 'inline')); + } + drupal_add_js(drupal_get_path('module', 'image_styles_admin') . '/image_styles_admin.js'); + } /** * Implements hook_preprocess_HOOK for theme table. */ function image_styles_admin_preprocess_table(&$variables) { - static $is_in_image_style_list = FALSE; + static $image_styles = NULL; - if (is_bool($variables)) { - // Called from imagecache_actions_style_duplicate(): set flag - $is_in_image_style_list = $variables; + // If called from image_styles_admin_preprocess_image_style_list(), the + // parameter will be a sequential array. + if (key($variables) === 0) { + $image_styles = $variables; } - else if ($is_in_image_style_list) { - // Normal preprocess hook call: only process if theme('table', ...) has been - // called by theme_image_style_list() - $variables['header'][2]['colspan'] = 4; - foreach ($variables['rows'] as &$row) { - array_splice($row, 2, 0, array($row[2], $row[2])); - // Replace edit with duplicate in text and href - $row[3] = str_replace('>' . t('edit') . '<', '>' . t('duplicate') . '<', $row[3]); - $row[3] = preg_replace('/\/edit\/([-a-z0-9_]+)"/', '/duplicate/\1"', $row[3]); - // Replace edit with export in text and href - $row[4] = str_replace('>' . t('edit') . '<', '>' . t('export') . '<', $row[4]); - $row[4] = preg_replace('/\/edit\/([-a-z0-9_]+)"/', '/export/\1"', $row[4]); + else if (!empty($image_styles)) { + // Normal preprocess hook call: we only process if theme('table', ...) has + // been called via theme_image_style_list() and we have a non empty list of + // styles; + + // Set an ID on the table so it can be targeted by our CSS. + $variables['attributes']['id'] = 'image-styles'; + + // Add a class to the Style name and Settings columns for styling. + foreach ($variables['header'] as &$cell) { + $temp_cell = is_string($cell) ? array('data' => $cell) : $cell; + $class_names = array( + 'style-name' => t('Style name'), + 'settings' => t('Settings'), + ); + foreach ($class_names as $class => $name) { + if ($temp_cell['data'] == $name) { + $temp_cell['class'][] = $class; + $cell = $temp_cell; + } + } } + + // Add the effects column header. + array_splice($variables['header'], 1, 0, array(array( + 'data' => t('Effects') . ' ', + 'class' => array('effects expand-all') + ))); + + // Add a column with a summary of all effects to each row. + foreach ($variables['rows'] as $i => &$row) { + $style = $image_styles[$i]; + $effects_list = array(); + foreach ($style['effects'] as $key => $effect) { + $definition = image_effect_definition_load($effect['name']); + $effect = array_merge($definition, $effect); + $style['effects'][$key] = $effect; + $effect_details = isset($effect['summary theme']) ? theme($effect['summary theme'], array('data' => $effect['data'])) : ''; + $effects_list[] = '' . $effect['label'] . ' ' . $effect_details . ''; + } + // Add the effects summary column to the row. + $effects_summary = array( + 'data' => ' ', + 'class' => 'description' + ); + array_splice($row, 1, 0, array($effects_summary)); + } + + // Find the column with the edit link in it. + $i = 0; + $first_row = reset($variables['rows']); + foreach ($first_row as $i => &$cell) { + $cell_data = is_array($cell) ? $cell['data'] : $cell; + if (strpos($cell_data, '>' . t('edit') . '<') !== FALSE) { + break; + } + } + + // Increase the colspan for the column with the edit link to include the + // duplicate and export links as well. This *should* be 2, but Drupal core + // specifies 1 more than should be there. + $variables['header'][$i]['colspan'] += 1; + + // Add the 2 links to each row by duplicating the edit link and then + // changing the text and the link. + $edit_column = $i; + foreach ($variables['rows'] as &$row) { + $i = $edit_column; + // Duplicate the edit link twice. + array_splice($row, $i + 1, 0, array($row[$i], $row[$i])); + // Replace edit with duplicate in text and href + $i++; + $row[$i] = str_replace('>' . t('edit') . '<', '>' . t('duplicate') . '<', $row[$i]); + $row[$i] = preg_replace('#/admin/config/media/image-styles/edit/#', '/admin/config/media/image-styles/duplicate/', $row[$i]); + // Replace edit with export in text and href + $i++; + $row[$i] = str_replace('>' . t('edit') . '<', '>' . t('export') . '<', $row[$i]); + $row[$i] = preg_replace('#/admin/config/media/image-styles/edit/#', '/admin/config/media/image-styles/export/', $row[$i]); + } + // Don't preprocess subsequent calls to theme_table(). - $is_in_image_style_list = FALSE; + $image_styles = NULL; } } diff --git a/sites/all/modules/imagecache_actions/imagecache_Actions.install b/sites/all/modules/imagecache_actions/imagecache_Actions.install deleted file mode 100644 index 208354d..0000000 --- a/sites/all/modules/imagecache_actions/imagecache_Actions.install +++ /dev/null @@ -1,9 +0,0 @@ - l('System Stream Wrapper', 'http://drupal.org/project/system_stream_wrapper', array('external' => TRUE)))), 'warning'); -} diff --git a/sites/all/modules/imagecache_actions/imagecache_actions.info b/sites/all/modules/imagecache_actions/imagecache_actions.info index 231abd8..b2accfb 100644 --- a/sites/all/modules/imagecache_actions/imagecache_actions.info +++ b/sites/all/modules/imagecache_actions/imagecache_actions.info @@ -1,16 +1,13 @@ name = Imagecache Actions -description = Provides a number of additional image effects. +description = Provides utility code for a number of additional image effects that can be found in the sub modules. package = Media core = 7.x dependencies[] = image -; files with classes -files[] = ImageCacheActionsModuleStreamWrapper.php - -; Information added by drupal.org packaging script on 2012-12-04 -version = "7.x-1.1" +; Information added by Drupal.org packaging script on 2018-03-20 +version = "7.x-1.9" core = "7.x" project = "imagecache_actions" -datestamp = "1354653754" +datestamp = "1521550387" diff --git a/sites/all/modules/imagecache_actions/imagecache_actions.install b/sites/all/modules/imagecache_actions/imagecache_actions.install new file mode 100644 index 0000000..bad1be7 --- /dev/null +++ b/sites/all/modules/imagecache_actions/imagecache_actions.install @@ -0,0 +1,16 @@ + l('System Stream Wrapper', 'https://drupal.org/project/system_stream_wrapper', array('external' => TRUE)))), 'warning'); +} + +/** + * Clear image styles cache. + */ +function imagecache_actions_update_7002(&$sandbox) { + // We need to clear this cache as many effect callbacks have been renamed. + cache_clear_all('image_styles', 'cache'); +} diff --git a/sites/all/modules/imagecache_actions/imagecache_actions.jquery.js b/sites/all/modules/imagecache_actions/imagecache_actions.jquery.js index 0fde1fe..265fb72 100644 --- a/sites/all/modules/imagecache_actions/imagecache_actions.jquery.js +++ b/sites/all/modules/imagecache_actions/imagecache_actions.jquery.js @@ -4,37 +4,9 @@ (function($){ /** - * Check if independent corners are enabled and disable other fields in the UI + * None here at the moment :-) + * Conditional form logic was moved to drupal FAPI #states instead. */ - Drupal.canvasactions_roundedcorners_form_disable_fields = function () { - // To get the right effect, we have to set the 'disabled' attribute on the - // field, but set the class on the container item. Tedious. - if (!$(":checkbox#edit-data-independent-corners-set-independent-corners").attr("checked")){ - $(".form-item-data-radius").removeClass("form-disabled"); - $(".form-item-data-radius input").attr("disabled", false); - $("#independent-corners-set .form-item").addClass("form-disabled"); - $("#independent-corners-set input").attr("disabled", true); - } - else { - $(".form-item-data-radius").addClass("form-disabled"); - $(".form-item-data-radius input").attr("disabled", true); - $("#independent-corners-set .form-item").removeClass("form-disabled"); - $("#independent-corners-set input").attr("disabled", false); - } - } - /** - * Trigger the update when the form is ready, and add listener to the checkbox - */ - Drupal.behaviors.canvasactions_roundedcorners = { - attach: function (context, settings) { - Drupal.canvasactions_roundedcorners_form_disable_fields(); - $(":checkbox#edit-data-independent-corners-set-independent-corners").change( - function() { - Drupal.canvasactions_roundedcorners_form_disable_fields(); - } - ); - } - } })(jQuery); diff --git a/sites/all/modules/imagecache_actions/imagecache_actions.module b/sites/all/modules/imagecache_actions/imagecache_actions.module index 48ec582..093ae3b 100644 --- a/sites/all/modules/imagecache_actions/imagecache_actions.module +++ b/sites/all/modules/imagecache_actions/imagecache_actions.module @@ -9,10 +9,6 @@ */ function imagecache_actions_theme() { return array( - 'imagecacheactions_rgb_form' => array( - 'file' => 'utility-color.inc', - 'render element' => 'form', - ), 'imagecacheactions_rgb' => array( 'file' => 'utility-color.inc', 'variables' => array('rgb' => NULL), diff --git a/sites/all/modules/imagecache_actions/imagefilter.inc b/sites/all/modules/imagecache_actions/imagefilter.inc index d2cf5be..f883c5d 100644 --- a/sites/all/modules/imagecache_actions/imagefilter.inc +++ b/sites/all/modules/imagecache_actions/imagefilter.inc @@ -1,73 +1,99 @@ > 16) & 0xFF); $g = 255 - (($rgb >> 8) & 0xFF); $b = 255 - ($rgb & 0xFF); $a = $rgb >> 24; - $new_pxl = imagecolorallocatealpha($source, $r, $g, $b, $a); + $new_pxl = imagecolorallocatealpha($image, $r, $g, $b, $a); if ($new_pxl == FALSE) { - $new_pxl = imagecolorclosestalpha($source, $r, $g, $b, $a); + $new_pxl = imagecolorclosestalpha($image, $r, $g, $b, $a); } - imagesetpixel($source, $x, $y, $new_pxl); + imagesetpixel($image, $x, $y, $new_pxl); ++$x; } ++$y; } return TRUE; - break; + case 1: $y = 0; while ($y < $max_y) { $x = 0; while ($x < $max_x) { - $rgb = imagecolorat($source, $x, $y); + $rgb = imagecolorat($image, $x, $y); $a = $rgb >> 24; $r = ((($rgb >> 16) & 0xFF) * 0.299) + ((($rgb >> 8) & 0xFF) * 0.587) + (($rgb & 0xFF) * 0.114); - $new_pxl = imagecolorallocatealpha($source, $r, $r, $r, $a); + $new_pxl = imagecolorallocatealpha($image, $r, $r, $r, $a); if ($new_pxl == FALSE) { - $new_pxl = imagecolorclosestalpha($source, $r, $r, $r, $a); + $new_pxl = imagecolorclosestalpha($image, $r, $r, $r, $a); } - imagesetpixel($source, $x, $y, $new_pxl); + imagesetpixel($image, $x, $y, $new_pxl); ++$x; } ++$y; } return TRUE; - break; + case 2: $y = 0; while ($y < $max_y) { $x = 0; while ($x < $max_x) { - $rgb = imagecolorat($source, $x, $y); + $rgb = imagecolorat($image, $x, $y); $r = (($rgb >> 16) & 0xFF) + $arg1; $g = (($rgb >> 8) & 0xFF) + $arg1; $b = ($rgb & 0xFF) + $arg1; @@ -75,24 +101,24 @@ if (!function_exists('imagefilter')) { $r = ($r > 255) ? 255 : (($r < 0) ? 0 : $r); $g = ($g > 255) ? 255 : (($g < 0) ? 0 : $g); $b = ($b > 255) ? 255 : (($b < 0) ? 0 : $b); - $new_pxl = imagecolorallocatealpha($source, $r, $g, $b, $a); + $new_pxl = imagecolorallocatealpha($image, $r, $g, $b, $a); if ($new_pxl == FALSE) { - $new_pxl = imagecolorclosestalpha($source, $r, $g, $b, $a); + $new_pxl = imagecolorclosestalpha($image, $r, $g, $b, $a); } - imagesetpixel($source, $x, $y, $new_pxl); + imagesetpixel($image, $x, $y, $new_pxl); ++$x; } ++$y; } return TRUE; - break; + case 3: $contrast = pow((100 - $arg1) / 100, 2); $y = 0; while ($y < $max_y) { $x = 0; while ($x < $max_x) { - $rgb = imagecolorat($source, $x, $y); + $rgb = imagecolorat($image, $x, $y); $a = $rgb >> 24; $r = (((((($rgb >> 16) & 0xFF) / 255) - 0.5) * $contrast) + 0.5) * 255; $g = (((((($rgb >> 8) & 0xFF) / 255) - 0.5) * $contrast) + 0.5) * 255; @@ -100,23 +126,23 @@ if (!function_exists('imagefilter')) { $r = ($r > 255) ? 255 : (($r < 0) ? 0 : $r); $g = ($g > 255) ? 255 : (($g < 0) ? 0 : $g); $b = ($b > 255) ? 255 : (($b < 0) ? 0 : $b); - $new_pxl = imagecolorallocatealpha($source, $r, $g, $b, $a); + $new_pxl = imagecolorallocatealpha($image, $r, $g, $b, $a); if ($new_pxl == FALSE) { - $new_pxl = imagecolorclosestalpha($source, $r, $g, $b, $a); + $new_pxl = imagecolorclosestalpha($image, $r, $g, $b, $a); } - imagesetpixel($source, $x, $y, $new_pxl); + imagesetpixel($image, $x, $y, $new_pxl); ++$x; } ++$y; } return TRUE; - break; + case 4: $x = 0; while ($x < $max_x) { $y = 0; while ($y < $max_y) { - $rgb = imagecolorat($source, $x, $y); + $rgb = imagecolorat($image, $x, $y); $r = (($rgb >> 16) & 0xFF) + $arg1; $g = (($rgb >> 8) & 0xFF) + $arg2; $b = ($rgb & 0xFF) + $arg3; @@ -124,55 +150,55 @@ if (!function_exists('imagefilter')) { $r = ($r > 255) ? 255 : (($r < 0) ? 0 : $r); $g = ($g > 255) ? 255 : (($g < 0) ? 0 : $g); $b = ($b > 255) ? 255 : (($b < 0) ? 0 : $b); - $new_pxl = imagecolorallocatealpha($source, $r, $g, $b, $a); + $new_pxl = imagecolorallocatealpha($image, $r, $g, $b, $a); if ($new_pxl == FALSE) { - $new_pxl = imagecolorclosestalpha($source, $r, $g, $b, $a); + $new_pxl = imagecolorclosestalpha($image, $r, $g, $b, $a); } - imagesetpixel($source, $x, $y, $new_pxl); + imagesetpixel($image, $x, $y, $new_pxl); ++$y; } ++$x; } return TRUE; - break; + case 5: - return imageconvolution($source, array( + return imageconvolution($image, array( array(-1, 0, -1), array(0, 4, 0), - array(-1, 0, -1) + array(-1, 0, -1), ), 1, 127); - break; + case 6: - return imageconvolution($source, array( + return imageconvolution($image, array( array(1.5, 0, 0), array(0, 0, 0), - array(0, 0, -1.5) + array(0, 0, -1.5), ), 1, 127); - break; + case 7: - return imageconvolution($source, array( + return imageconvolution($image, array( array(1, 2, 1), array(2, 4, 2), - array(1, 2, 1) + array(1, 2, 1), ), 16, 0); - break; + case 8: for ($y = 0; $y < $max_y; $y++) { for ($x = 0; $x < $max_x; $x++) { $flt_r_sum = $flt_g_sum = $flt_b_sum = 0; - $cpxl = imagecolorat($source, $x, $y); + $cpxl = imagecolorat($image, $x, $y); for ($j = 0; $j < 3; $j++) { for ($i = 0; $i < 3; $i++) { if (($j == 1) && ($i == 1)) { $flt_r[1][1] = $flt_g[1][1] = $flt_b[1][1] = 0.5; } else { - $pxl = imagecolorat($source, $x - (3 >> 1) + $i, $y - (3 >> 1) + $j); + $pxl = imagecolorat($image, $x - (3 >> 1) + $i, $y - (3 >> 1) + $j); $new_a = $pxl >> 24; - //$r = (($pxl >> 16) & 0xFF); - //$g = (($pxl >> 8) & 0xFF); - //$b = ($pxl & 0xFF); + // $r = (($pxl >> 16) & 0xFF); + // $g = (($pxl >> 8) & 0xFF); + // $b = ($pxl & 0xFF); $new_r = abs((($cpxl >> 16) & 0xFF) - (($pxl >> 16) & 0xFF)); if ($new_r != 0) { $flt_r[$j][$i] = 1 / $new_r; @@ -222,7 +248,7 @@ if (!function_exists('imagefilter')) { for ($j = 0; $j < 3; $j++) { for ($i = 0; $i < 3; $i++) { - $pxl = imagecolorat($source, $x - (3 >> 1) + $i, $y - (3 >> 1) + $j); + $pxl = imagecolorat($image, $x - (3 >> 1) + $i, $y - (3 >> 1) + $j); $new_r += (($pxl >> 16) & 0xFF) * $flt_r[$j][$i]; $new_g += (($pxl >> 8) & 0xFF) * $flt_g[$j][$i]; $new_b += ($pxl & 0xFF) * $flt_b[$j][$i]; @@ -232,29 +258,35 @@ if (!function_exists('imagefilter')) { $new_r = ($new_r > 255) ? 255 : (($new_r < 0) ? 0 : $new_r); $new_g = ($new_g > 255) ? 255 : (($new_g < 0) ? 0 : $new_g); $new_b = ($new_b > 255) ? 255 : (($new_b < 0) ? 0 : $new_b); - $new_pxl = imagecolorallocatealpha($source, (int) $new_r, (int) $new_g, (int) $new_b, $new_a); + $new_pxl = imagecolorallocatealpha($image, (int) $new_r, (int) $new_g, (int) $new_b, $new_a); if ($new_pxl == FALSE) { - $new_pxl = imagecolorclosestalpha($source, (int) $new_r, (int) $new_g, (int) $new_b, $new_a); + $new_pxl = imagecolorclosestalpha($image, (int) $new_r, (int) $new_g, (int) $new_b, $new_a); } - imagesetpixel($source, $x, $y, $new_pxl); + imagesetpixel($image, $x, $y, $new_pxl); } } return TRUE; - break; + case 9: - return imageconvolution($source, array( + return imageconvolution($image, array( array(-1, -1, -1), array(-1, 9, -1), - array(-1, -1, -1) + array(-1, -1, -1), ), 1, 0); - break; + case 10: - return imageconvolution($source, array( + return imageconvolution($image, array( array(1, 1, 1), array(1, $arg1, 1), - array(1, 1, 1) + array(1, 1, 1), ), $arg1 + 8, 0); - break; + + case 11: + default: + watchdog('imagecache_actions', 'Image filter failed: unknown filter type: %filtertype', + array('%filtertype' => $filtertype), + WATCHDOG_ERROR); + return FALSE; } } } diff --git a/sites/all/modules/imagecache_actions/tests/corners_combo.imagecache_preset.inc b/sites/all/modules/imagecache_actions/tests/corners_combo.imagecache_preset.inc index d236918..e0018b9 100644 --- a/sites/all/modules/imagecache_actions/tests/corners_combo.imagecache_preset.inc +++ b/sites/all/modules/imagecache_actions/tests/corners_combo.imagecache_preset.inc @@ -1,5 +1,4 @@ Administer > Site Building > Imagecache - * - * that lists a whole bunch of sample presets. - * - * @author dman http://coders.co.nz/ - * + * @file An admin-only utility to test image styles and effects. * + * It provides a page Test Suite in Administration > Configuration > Media > + * Image Styles (admin/config/media/image-styles/testsuite) that displays + * results for all existing image styles for all toolkits as well as for a set + * of test image styles defined in the various modules. */ include_once('imagecache_testsuite.features.inc'); @@ -22,8 +18,8 @@ function imagecache_testsuite_menu() { $items = array(); $items['admin/config/media/image-styles/testsuite'] = array( 'title' => 'Test Suite', - 'page callback' => 'imagecache_testsuite_generate', - 'access arguments' => array('administer imagecache'), + 'page callback' => 'imagecache_testsuite_page', + 'access arguments' => array('administer image styles'), 'type' => MENU_LOCAL_TASK, 'weight' => 10, ); @@ -31,13 +27,13 @@ function imagecache_testsuite_menu() { 'title' => 'Test Suite Image', 'page callback' => 'imagecache_testsuite_generate', 'page arguments' => array(5, 6), - 'access arguments' => array('administer imagecache'), + 'access arguments' => array('administer image styles'), 'type' => MENU_CALLBACK, ); $items['admin/config/media/image-styles/testsuite/positioning_test'] = array( 'title' => 'Positioning Test', 'page callback' => 'imagecache_testsuite_positioning', - 'access arguments' => array('administer imagecache'), + 'access arguments' => array('administer image styles'), 'type' => MENU_LOCAL_TASK, ); return $items; @@ -46,187 +42,278 @@ function imagecache_testsuite_menu() { /** * Implementation of hook_help() */ -function imagecache_testsuite_help($path, $arg) { +function imagecache_testsuite_help($path /*, $arg*/) { switch ($path) { + // @todo: this path does not exist anymore. case 'admin/build/imagecache/test' : - $output = file_get_contents(drupal_get_path('module', 'imagecache_testsuite') ."/README.txt"); + $output = file_get_contents(drupal_get_path('module', 'imagecache_testsuite') . "/README.txt"); return _filter_autop($output); - break; - case 'admin/build/imagecache/test' : - return t('This displays a number of examples of keyword positioning. This positioning algorithm is used when placing image overlays, such as watermarks or text on a base image canvas. Illustrated are both the expected result and the actual result. This page is just for debugging to confirm that this behavior doesnt change as the code gets updated. If the two illustrations do not match, there is probably something wrong with the calculation logic.'); - break; + break; + case 'admin/config/media/image-styles/testsuite' : + return t("+ This page displays a number of examples of image effects. + Illustrated are both the expected result and the actual result. +
+ This page is just for debugging to confirm that this behavior doesn't + change as the code gets updated. + If the two illustrations do not match, there is probably something + that needs fixing. +
+ More actions are provided by each of the imagecache actions submodules + and will be shown as you enable them. +
"); + break; + case 'admin/config/media/image-styles' : + return t(' + A number of styles here are provided by the Imagecache + Testsuite module as examples. + Disable this module to make them go away. + '); + break; } + return ''; } - /** - * Either returns the whole testsuite page or generates the requested - * image+preset + * Returns the test suite page. + * + * The test suite page contians img links to all image derivatives to create as + * part of the test suite. + * + * Samples to test are scanned from: + * - The existing image styles. + * - The file features.inc attached to this module. (@todo: no longer eisting?) + * - Individual *.imagecache_preset.inc files found near any known modules. + * Images illustrating the named preset are looked for also. * * Flushes the entire test cache every time anything is done. + * + * @return string + * The html for the page. */ -function imagecache_testsuite_generate($test_id = '', $toolkit = 'gd') { - // Samples to test are scanned from - // - the existing installed presets - // - features inc attached to this module - // - individual *.imagecache_preset.inc files found near any known modules - // Images illustrating the named preset are looked for also. +function imagecache_testsuite_page() { module_load_include('inc', 'image', 'image.admin'); module_load_include('inc', 'image', 'image.effects'); - $sample_path = drupal_get_path('module' , 'imagecache_testsuite') ; - $target = $sample_path .'/sample.jpg'; - $tests = image_styles() + imagecache_testsuite_get_tests(); - $toolkits = module_invoke_all('image_toolkits'); + $tests = array_merge(image_styles(), imagecache_testsuite_get_tests()); + $toolkits = image_get_available_toolkits(); - if (empty($test_id)) { - // Present the all-in-one overview page - $sample_folders = imagecache_testsuite_get_folders(); + // Present the all-in-one overview page. + $sample_folders = imagecache_testsuite_get_folders(); - // Firstly, remove any previous images - image_style_flush('testsuite'); + // Draw the admin table. + $test_table = array(); + foreach ($tests as $style_name => $style) { + // Firstly, remove any previous images for the current style + image_style_flush($style); - // Draw the admin table - $test_table = array(); - foreach ($tests as $style_name => $style) { - $row = array(); - $row_class = 'test'; - $details = ''; - // Render the details - foreach ($style['effects'] as $i => $effect) { - if (! isset($effect['name'])) { - // badness - watchdog('imagecache_testsuite', 'invalid testcase within %style_name. No effect name', array('%style_name' => $style_name), WATCHDOG_ERROR); - $details .= '$details
"; - - // Look for a sample image. May also be defined by the definition itself, - // but normally assume a file named after the presetname, in the preset file path. - foreach ($sample_folders as $sample_folder) { - if (file_exists("{$sample_folder}/{$style_name}.png")) { - $style['sample'] = "{$sample_folder}/{$style_name}.png"; - } - elseif (file_exists("{$sample_folder}/{$style_name}.jpg")) { - $style['sample'] = "{$sample_folder}/{$style_name}.jpg"; - } - } - - if (isset($style['sample']) && file_exists($style['sample']) ) { - $sample_img = theme('image', array('path' => $style['sample'])); - // I was having trouble with permissions on an OSX dev machine - if (! is_readable($style['sample'])) { - $sample_img = "FILE UNREADABLE: {$style['sample']}"; - } - } - else { - $sample_img = "[no sample]"; - } - $row['sample'] = $sample_img; - - // Generate a result for each available toolkit - foreach ($toolkits as $toolkit => $toolkit_info) { - $test_url = "admin/config/media/image-styles/testsuite/$style_name/$toolkit"; - $test_img = theme('image', array( - 'path' => $test_url, - 'alt' => "$style_name/$toolkit" - )); - $row[$toolkit] = l($test_img, $test_url, array('html' => TRUE)); - } - $test_table[$style_name] = array('data' => $row, 'class' => array($row_class)); - } - $header = array_merge(array('test', 'sample'), array_keys($toolkits)); - - $output = theme('table', array('header' => $header, 'rows' => $test_table, 'id' => 'imagecache-testsuite')); - - // Default system zebra-striping fails to show my transparency on white - drupal_add_html_head(''); - return $output; - } - else { - // Run the process and return the image. - // @see image_style_create_derivative() - - $style = $tests[$test_id]; - if (! $style) { - trigger_error("Unknown test style preset '$test_id' ", E_USER_ERROR); - return FALSE; - } - - // Start emulating image_style_create_derivative() - // The main difference being I determine the toolkit I want to use. - // SOME of this code is probably redundant, was a lot of copy&paste without true understanding of the new image.module - $image_uri = $target; - - if (!$image = image_load($target, $toolkit)) { - trigger_error("Failed to open original image $target with toolkit $toolkit", E_USER_ERROR); - return FALSE; - } - - // Need to save the result before returning it - to stay compatible with imagemagick - $filename = "$test_id-$toolkit.{$image->info['extension']}"; - $derivative_uri = image_style_path($style['name'], $filename); - file_prepare_directory(dirname($derivative_uri), FILE_CREATE_DIRECTORY); - watchdog('imagecache_testsuite', 'Checking a save dir %dir', array('%dir' => dirname($derivative_uri)), WATCHDOG_DEBUG); - - // Imagemagick is not quite right? place a file where the file is supposed to go - // before I put the real path there? else drupal_realpath() says nuh. - #file_save_data('touch this for imagemagick', $derivative_uri, FILE_EXISTS_REPLACE); + $row = array(); + $row_class = 'test'; + $details_list = array(); + // Render the details. foreach ($style['effects'] as $effect) { - // Need to load the full effect definitions, our test ones don't know all the callback info - $effect_definition = image_effect_definition_load($effect['name']); - if (empty($effect_definition)) { - watchdog('imagecache_testsuite', 'I have no idea what %name is', array('%name' => $full_effect['name']), WATCHDOG_ERROR); + if (!isset($effect['name'])) { + // badness + watchdog('imagecache_testsuite', 'invalid testcase within %style_name. No effect name', array('%style_name' => $style_name), WATCHDOG_ERROR); + $details_list[] = '" . implode($details_list) . "
"; - if (! image_effect_apply($image, $full_effect)) { - watchdog('imagecache_testsuite', 'action: %action (%callback) failed for %src', array('%action' => $full_effect['label'], '%src' => $target, '%callback' => $full_effect['effect callback']), WATCHDOG_ERROR); - #return FALSE; + // Look for a sample image. May also be defined by the definition itself, + // but normally assume a file named after the image style, in (one of the) + // directories with test styles. + foreach ($sample_folders as $sample_folder) { + if (file_exists("{$sample_folder}/{$style_name}.png")) { + $style['sample'] = "{$sample_folder}/{$style_name}.png"; + } + elseif (file_exists("{$sample_folder}/{$style_name}.jpg")) { + $style['sample'] = "{$sample_folder}/{$style_name}.jpg"; } } - #watchdog('imagecache_testsuite', "processed $test_id-$toolkit, ready to save", array(), WATCHDOG_DEBUG); - #watchdog('imagecache_testsuite', print_r($image, 1)); - #dpm(get_defined_vars()); - if (!image_save($image, $derivative_uri)) { - watchdog('imagecache_testsuite', 'saving image %label failed for %derivative_uri', array('%derivative_uri' => $derivative_uri, '%label' => $effect['label']), WATCHDOG_ERROR); - return FALSE; + if (isset($style['sample']) && file_exists($style['sample'])) { + $sample_img = theme('image', array('path' => $style['sample'])); + // I was having trouble with permissions on an OSX dev machine. + if (!is_readable($style['sample'])) { + $sample_img = "FILE UNREADABLE: {$style['sample']}"; + } } + else { + $sample_img = "[no sample]"; + } + $row['sample'] = $sample_img; - if ($result_image = image_load($derivative_uri)) { - #watchdog('imagecache_testsuite', 'transferring result', array(), WATCHDOG_DEBUG); - file_transfer($result_image->source, array('Content-Type' => $result_image->info['mime_type'], 'Content-Length' => $result_image->info['file_size'])); - drupal_exit(); + // Generate a result for each available toolkit. + foreach ($toolkits as $toolkit => $toolkit_info) { + $test_url = "admin/config/media/image-styles/testsuite/$style_name/$toolkit"; + $test_img = theme('image', array( + 'path' => $test_url, + 'alt' => "$style_name/$toolkit" + )); + $row[$toolkit] = l($test_img, $test_url, array('html' => TRUE)); } - return "Failed to load the expected result from $derivative_uri"; + $test_table[$style_name] = array( + 'data' => $row, + 'class' => array($row_class) + ); } + + $header = array_merge(array('test', 'sample'), array_keys($toolkits)); + $output = theme('table', array( + 'header' => $header, + 'rows' => $test_table, + 'id' => 'imagecache-testsuite' + )); + + // @todo: zebra striping can be disabled in D7. + // Default system zebra-striping fails to show my transparency on white. + drupal_add_html_head(''); + + return $output; } +/** + * Returns the requested image derivative. + * + * If the image derivative generation is successful, the function does not + * return but exits processing using drupal_exit(). + * + * Flushes the entire test cache every time anything is done. + * + * @param string $test_id + * The id of the test to generate the derivative for. + * @param string $toolkit + * The toolkit to use, or empty for the default toolkit + * + * @return string|bool + * - The html for the page ($test_id is empty) + * - False when the image derivative could not be created. + */ +function imagecache_testsuite_generate($test_id = '', $toolkit = '') { + module_load_include('inc', 'image', 'image.admin'); + module_load_include('inc', 'image', 'image.effects'); + + if (empty($toolkit)) { + $toolkit = image_get_toolkit(); + } + else { + // Set the toolkit for this invocation only, so do not use variable_set. + global $conf; + $conf['image_toolkit'] = $toolkit; + if ($toolkit === 'gd') { + // This seems not to be done automatically elsewhere. + include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'system') . '/' . 'image.gd.inc'; + } + } + + $target = 'module://imagecache_testsuite/sample.jpg'; + $tests = array_merge(image_styles(), imagecache_testsuite_get_tests()); + + // Run the process and return the image, @see image_style_create_derivative(). + $style = $tests[$test_id]; + if (!$style) { + trigger_error("Unknown test style preset '$test_id' ", E_USER_ERROR); + return FALSE; + } + + // @todo: should we let the image style system do its work and just interfere on hook_init with setting the toolkit? + // @todo: this would make the page generator easier as well and keep it working with secure image derivatives. + // Start emulating image_style_create_derivative() + // The main difference being I determine the toolkit I want to use. + // SOME of this code is probably redundant, was a lot of copy&paste without true understanding of the new image.module + if (!$image = image_load($target, $toolkit)) { + trigger_error("Failed to open original image $target with toolkit $toolkit", E_USER_ERROR); + return FALSE; + } + + // Need to save the result before returning it - to stay compatible with imagemagick + $filename = "$test_id-$toolkit.{$image->info['extension']}"; + $derivative_uri = image_style_path($style['name'], $filename); + $directory = dirname($derivative_uri); + file_prepare_directory($directory, FILE_CREATE_DIRECTORY); + watchdog('imagecache_testsuite', 'Checking a save dir %dir', array('%dir' => dirname($derivative_uri)), WATCHDOG_DEBUG); + + // Imagemagick is not quite right? place a file where the file is supposed to go + // before I put the real path there? else drupal_realpath() says nuh. + #file_save_data('touch this for imagemagick', $derivative_uri, FILE_EXISTS_REPLACE); + + foreach ($style['effects'] as $effect) { + // Need to load the full effect definitions, our test ones don't know all the callback info + $effect_definition = image_effect_definition_load($effect['name']); + if (empty($effect_definition)) { + watchdog('imagecache_testsuite', 'I have no idea what %name is', array('%name' => $effect['name']), WATCHDOG_ERROR); + continue; + } + $full_effect = array_merge($effect_definition, array('data' => $effect['data'])); + + // @todo: effects that involve other images (overlay, underlay) will load that image with the default toolkit which may differ from the toolkit tested here. + if (!image_effect_apply($image, $full_effect)) { + watchdog('imagecache_testsuite', 'action: %action (%callback) failed for %src', array( + '%action' => $full_effect['label'], + '%src' => $target, + '%callback' => $full_effect['effect callback'] + ), WATCHDOG_ERROR); + } + } + + if (!image_save($image, $derivative_uri)) { + watchdog('imagecache_testsuite', 'saving image %label failed for %derivative_uri', array( + '%derivative_uri' => $derivative_uri, + '%label' => isset($style['label']) ? $style['label'] : $style['name'] + ), WATCHDOG_ERROR); + return FALSE; + } + + if ($result_image = image_load($derivative_uri)) { + file_transfer($result_image->source, array( + 'Content-Type' => $result_image->info['mime_type'], + 'Content-Length' => $result_image->info['file_size'] + )); + drupal_exit(); + } + return "Failed to load the expected result from $derivative_uri"; +} /** * Implements hook_image_default_styles(). * - * Loads the individual test cases and makes them available as enabled styles + * Lists all our individual test cases and makes them available + * as enabled styles */ function imagecache_testsuite_image_default_styles() { - return imagecache_testsuite_get_tests(); + $styles = imagecache_testsuite_get_tests(); + + // Need to filter out the invalid test cases + // (ones that use unavailable actions) + // or the core complains with notices. +// foreach ($styles as $id => $style) { +// foreach ($style['effects'] as $delta => $action) { +// if (!empty($action['module']) && ($action['module'] != 'imagecache') && !module_exists($action['module'])) { +// unset($styles[$id]); +// break; +// } +// } +// } + return $styles; } /** @@ -250,7 +337,7 @@ function imagecache_testsuite_get_tests() { // Setting filepath in this scope allows the tests to know where they are. // The inc files may use it to create their rules. $filepath = $folder; - foreach($preset_files as $preset_file) { + foreach ($preset_files as $preset_file) { include_once($preset_file->uri); } } @@ -261,8 +348,8 @@ function imagecache_testsuite_get_tests() { /** * Places to scan for test presets and sample images. * - * @return an array of foldernames of everything that implements - * imagecache_actions. + * @return array + * an array of folder names of everything that implements imagecache_actions. */ function imagecache_testsuite_get_folders() { $folders = array(drupal_get_path('module', 'imagecache_testsuite')); @@ -272,7 +359,6 @@ function imagecache_testsuite_get_folders() { return $folders; } - /** * Display a page demonstrating a number of positioning tests * @@ -280,13 +366,16 @@ function imagecache_testsuite_get_folders() { * pls the css-like left=, top= version also. */ function imagecache_testsuite_positioning() { + module_load_include('inc', 'imagecache_actions', 'utility'); drupal_set_title("Testing the positioning algorithm"); $tests = imagecache_testsuite_positioning_get_tests(); $table = array(); // $dst_image represents tha field or canvas. // $src_image is the item being placed on it. // Both these represent an imageapi-type image resource handle, but contain just dimensions + $src_image = new stdClass(); $src_image->info = array('width' => 75, 'height' => 100); + $dst_image = new stdClass(); $dst_image->info = array('width' => 200, 'height' => 150); foreach ($tests as $testname => $test) { @@ -310,18 +399,27 @@ function imagecache_testsuite_positioning() { $row['result_image'] = $result_illustration; $table[] = $row; } - return 'Result of test:'. theme('table', array('test', 'parameters', 'expected', 'image', 'result', 'actual image', 'status'), $table); + return 'Result of test:' . theme('table', array( + 'test', + 'parameters', + 'expected', + 'image', + 'result', + 'actual image', + 'status' + ), $table); } - function theme_positioning_test($x, $y) { $inner = "