Problem/Motivation
As of hook_image_effect_info() now has optional dimensions keys for hook_image_effect_info()
the new callback "dimensions callback" was introduced.
Idea is to provide the ability to get the image dimensions, to set the <img> width / height attributes, without additional I/O (#1129642: Populate HTML image tags with dimension attributes (like D6 imagefield) without re-introducing I/O). Unfortunately this doesn't work for all kinds of effects since some of them need to know what the original image was.
Examples:
- A custom/manual crop effect as implemented by several contrib modules (as stated in #3 and #16) needs to know what image is being handled to be able to retrieve the custom crop meta data and apply the defined crop.
- An autorotate effect (rotate based on exif data) (though that effect will always need IO).
Proposed resolution
In a sense, if we want effects to be able to determine resulting dimensions, the transformDimensions()
should have access to the same info as the apply()
method. However, passing in an image object is a no go as that would need to be created, thereby forcibly leading to IO.
So we changed this to adding an additional $uri parameter to transformDimensions()
both at image style and image effect level. This new $uri parameter contains the path to the original image. This gives a higher chance of getting the derivative's dimensions without the need to create the derivative first or do some IO on the derivative to get the dimensions.
Furthermore, the originally proposed solution indicated that template_preprocess_image_style()
should try to get the dimensions by calling the "dimensions callback" first but if that would fail, it would fall-back to getimagesize()
. However, as the derivative might not yet have been created, this fallback is not guaranteed to work in all cases either. So we dropped this part of the solution.
Another proposed point was that, for reasons of consistency, a similar method getDerivativeExtension() - similar in the sense that it gets called when the img tag gets generated by a theme function and that it should provide information about the resulting derivative without actually creating it - should get the same parameters. This was postponed to when it becomes actually needed and can be done in a separate issue.
Remaining tasks
- Core committer: Accept solution.
- Core committer: Accept inconsistency in API.
User interface changes
none.
API changes
A new parameter for ImageStyleInterface::transformDimensions()
and ImageEffectInterface::transformDimensions()
.
Beta phase evaluation
Issue category | Bug, because this is a Contributed project blocker and is tagged for backport to D7. |
---|---|
Issue priority | Major because this is a Contributed project blocker. |
Comment | File | Size | Author |
---|---|---|---|
#77 | 1364670-77.patch | 15.28 KB | mondrake |
#77 | interdiff_63-77.txt | 4.11 KB | mondrake |
#63 | 1364670-63.patch | 13.88 KB | mondrake |
Comments
Comment #1
RobLoachInstead of a third parameter, maybe switch it to an $options array? It's nice to have a clean set of parameters rather than a ridiculous amount of arguments.
Comment #2
RobLoachWould also have to be fixed in Drupal 8 first ;-) .
Comment #3
mrfelton CreditAttribution: mrfelton commentedJust hit upon this from #1556244: Using 'manual crop' style, and leaving the minimum height and/or width fields blank results in missing image attributes. The Manual Crop module needs to know the source image in order to be able to look up the applied crop. Without knowing the source image in the dimensions callback, it's not posible to calculate the updated dimensions. Other cropping modules have hit upon the same limitation such as the ImageCrop module in #1392568: Add dimensions callback.
Comment #4
mrfelton CreditAttribution: mrfelton commentedAttached patch adds the additional optional third paramater to image_style_transform_dimensions(), and ensures that it is used in theme_image_style().
However, I do see a potential problem with this because in order to get that new 'source' data into the the dimensions callback, I have to add it to $effect['data']['source'], but $effect['data'] is the data as returned from the effect's config form, which means that this could potentially cause a conflict with a module that implements an effect that defines a 'source' property on their effect config. Unlikely, but possible, and not sure how we could protect against that other than document the fact that the 'source' keyword is reserved.
Alternatively, we would have to a new third paramater to the dimensions callback itself, or change that callback so that the second paramater was a keyd array with 'data' holding the config data, and 'source' holding the new image source value. That would be the most flexible approach, but would require all dimension callbacks from core and contrib to be updated to accommodate it.
Comment #5
mrfelton CreditAttribution: mrfelton commentedHere is the same patch for D7.
Comment #7
mrfelton CreditAttribution: mrfelton commentedMaybe this on'll pass the test.
Comment #8
mrfelton CreditAttribution: mrfelton commentedoops, typo.
Comment #9
mrfelton CreditAttribution: mrfelton commentedAlso updated D7 version to use 'path' rather than 'source' for consistency.
Comment #10
Wolfgang Reszel CreditAttribution: Wolfgang Reszel commentedIs there a reason, why it is not in 7.15?
Comment #11
nils.destoop CreditAttribution: nils.destoop commentedUpdate for this patch. Both style and file uri should be added as argument.
Comment #12
Wolfgang Reszel CreditAttribution: Wolfgang Reszel commented#11: 1364670-11-info-for-dimensions.patch queued for re-testing.
Comment #14
OnkelTem CreditAttribution: OnkelTem commentedThe patch from #9 applies smoothly on D7.x-19
2 #10
Similar thoughts...
Comment #15
das-peter CreditAttribution: das-peter commentedRe-rolled for the latest D8 code.
@zuuperman What would be the benefit of having the style too? Will that help to figure out the dimensions?
And #9 still applies to the latest 7.x code and looks pretty good to go to me.
Comment #16
Fabianx CreditAttribution: Fabianx commentedRTBC - This is very important to get in D8 - especially now that we are close to RC.
There are contrib modules that cannot find dimensions unless they know the URI (e.g. manualcrop).
Comment #18
attiks CreditAttribution: attiks commentedpicture has been renamed to responsive_image, so patch needs a reroll
Comment #19
Fabianx CreditAttribution: Fabianx commentedComment #20
er.pushpinderrana CreditAttribution: er.pushpinderrana commentedAs per https://www.drupal.org/node/2120875 picture module removed from core.
Comment #21
dinarcon CreditAttribution: dinarcon commentedAdding to @er.pushpinderrana's patch, responsive_image module updates.
Note: Interdiff is against #15.
Comment #24
Fabianx CreditAttribution: Fabianx commentedHm, the non standard base:// that is not a stream wrapper seems to bite us now ... :-/
Comment #33
fietserwinA few notes:
Can someone reroll and take care of these points (at least the first 2), I will review.
Comment #34
Wim Leers@FabianX: unpublished those duplicate comments due to that d.o flakiness.
Comment #35
Wim Leers@mondrake pointed me to this issue. I'm coming from #2420789: Make RotateImageEffect's random rotation deterministic, so ::transformDimensions() can return the actual dimensions. That issue describes a very precise, well-defined use case for needing extra information to be passed to
transformDimensions()
. We're debating there whether$uri
is acceptable, since it'd allowtransformDimensions()
to perform I/O, which AFAICT this interface was designed to specifically prevent.However, given this issue, it'd seem it'd be better to pass either
$file_uri
or even justImageInterface $image
(consistent withpublic function applyEffect(ImageInterface $image)
on the same interface).Please read that other issue, perhaps we can merge it into this one.
Comment #44
heddnComment #45
drupaldrop CreditAttribution: drupaldrop commentedPatch is rerolled - got a conflicts and reuploaded the patch after fixing them . Find details below
First, rewinding head to replay your work on top of it...
Applying: Applying patch from issue 1364670 comment 9245255
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
Auto-merging core/modules/responsive_image/responsive_image.module
CONFLICT (content): Merge conflict in core/modules/responsive_image/responsive_image.module
Auto-merging core/modules/image/src/Plugin/ImageEffect/DesaturateImageEffect.php
CONFLICT (content): Merge conflict in core/modules/image/src/Plugin/ImageEffect/DesaturateImageEffect.php
Auto-merging core/modules/image/src/ImageStyleInterface.php
Auto-merging core/modules/image/src/ImageEffectInterface.php
CONFLICT (content): Merge conflict in core/modules/image/src/ImageEffectInterface.php
Auto-merging core/modules/image/src/ImageEffectBase.php
CONFLICT (content): Merge conflict in core/modules/image/src/ImageEffectBase.php
Auto-merging core/modules/image/src/Entity/ImageStyle.php
Failed to merge in the changes.
Patch failed at 0001 Applying patch from issue 1364670 comment 9245255
Patch is clean now and reuploaded..
Comment #47
Fabianx CreditAttribution: Fabianx as a volunteer commentedComment #48
cchanana CreditAttribution: cchanana commentedRe-roll the patch #45
Comment #49
fietserwinLet's see what the testbot thinks of #48 before reviewing it.
Comment #51
Manjit.Singh@fietserwin Any idea why test has been failed ?
Comment #52
mondrake#51: it seems the reroll in #48 removed some code currently in HEAD. Here's a new patch based on #21 and current head - some responsive_image module code changed quite a bit since then.
This would be an API change, so not sure about chances for 8.0.x; also would need tests for newly added $uri parameter.
Comment #54
mondrakeFixed NullTestImageEffect.
Comment #55
mondrakeSorry, I mistyped the interdiff extension in #54 (.patch instead of .txt). The patch to review is 1364670-54.patch.
Comment #56
mondrake.
Comment #57
fietserwin@mondrake thanks for rerolling. I had a quick look at the current patch and my points from #33 still stand.
- It does not make sense to make $uri optional:
* All usages pass it, so no need t make it optional for the calling sides. If new usages would not want to pass it, they could always pass the empty string or null themselves.
* The ImageEffectInterface is changed, so all effects need to be changed anyway (they cannot not declare the optional parameter, even if they don't use it).
Is that so? I guess that $uri either contains the stream wrapper name (public://...) or else it will be an absolute or relative (to the Drupal root) file path.
Comment #58
mondrake@fietserwin
re #57:
Agreed.
Changed docs to be consistent with the doc of
ImageStyleInterface::createDerivative
re #33:
Not sure why you believe so, but in any case not in scope of this issue.
Absolutely right... but it seems to me that check is redundant, the line above is calling
ImageStyle::load
, what could be the class of $entity at that point if not an instance of ImageStyleInterface?? I'd leave that to a follow-up cleanup.Also - the IS needs update as the proposed solution here is different, we are passing the URI of the original image, not the one of the derivative.
Comment #60
mondrakeActually, removing the default NULL to $uri helped to spot that we were missing to add the parameter in the most obvious place... :)
Also, changed my mind on #58
and made the fix here.
Comment #61
mondrakeAdded a test effect in image_module_test. This effect resizes the image based on the original image file extension - both
::transformDimensions
and::applyEffect
. UpdatedImageDimensionsTest
to test for that.Comment #62
fietserwinThanks for writing the patch and adding tests. On reviewing I found a few minor points:
return $image->resize($dimensions['width'], $dimensions['height']);
Make this switch more robust, even if it is only for testing:
- use strtolower()
- also check for 'jpg' (and/or use default): you are only testing the gif and png. If you would have tested the jpg test file as well, it would have failed.
ImageStyle is not an injected service, so checking for ImageStyleInterface instead of ImageStyle - as I originally suggested - is not needed. And as Entity::load() returns static or NULL, indeed no check is needed at all. So this is good: keep this change.
I also think we should still update the other method (responsive_image_get_image_dimensions()) as well, even if there is no direct current contrib effect blocked (it is a new method after all). Reasons to do it now and here:
- Keep the API consistent.
- Introduce these kind of changes all at once, minimizing the number of times for contrib to make similar changes to their effects.
(- There still are none or very few D8 contrib effects developed.)
I updated the issue summary to state the current situation of the patch.
However, in doing so, I found that this issue also proposes to fall back to getimagesize() if either of width or height is not returned. This would result in only 1 palce where I/O is done so that image effects should never have to do I/O: they can set width and/or height to NULL if they cannot set it to correct values. Note that some D7 contrib effects currently do so, but that no transformDimensions() method does check for this... Anyway, we still should decide on what to do with this part of the suggested solution. I think it should be part of this patch but won't block RTBC'íng it, if others think differently.
Comment #63
mondrake#62:
1, 2 - done
3 - ok
what needs to be done?
That's the problem - before the edit of the IS, the proposal was to fallback to retrieving dimensions via
getimagesize()
on the transformed image. ButImageStyle::transformDimension
is run at a point when the derived image has not (necessarily) been created yet - see the flow intemplate_preprocess_image_style
:ImageStyle::transformDimension
is called first, thenImageStyle::buildUrl
;ImageStyle::createDerivative
is only invoked in a separate request by the download controller.So if we were to access the derivative image, we would necessarily have to run
ImageStyle::createDerivative
withinImageStyle::transformDimension
, if the file of derived image is not existing at destination URI. But this would break the entire concept of splitting the generation of the derivative URI from its actual creation.My proposal would be not to do anything like that, and just stick to what we have in the current patch i.e. only use the URI of the original image file. Thoughts?
Comment #64
fietserwinRE#63:
- My fault: should be ImageStyle::getDerivativeExtension() and its call chain. This method is like transformDimensions() about finding out what an image style will deliver without actually applying that style. If we acknowledge that transformDimensions() may need to know the URI to give a correct answer, we should also acknowledge that getDerivativeExtension() may need to know the URI as well.
- You are right about using getimagesize(), but this will only be once (per derivative). Subsequent executions of template_preprocess_image_style() for the same derivative could have access to the derivative file. But if the result of this first execution gets cached we are in trouble anyway, so we should prevent caching of this if transformDimensions() does not result a correct result. This is indeed difficult stuff that makes this complex, so perhaps better to say that with the URI passed each effect should be able to return correct values with transformDimensions() and thus there will be no need any more for this fallback.
Comment #65
mondrake@fietserwin re #64:
1. ImageStyle::getDerivativeExtension() - I am still of my opinion in #58, if we want that I would suggest to file a separate issue, and discuss the use cases there. Honestly I do not see how we could make an extension dependent on the original file URI, given also that we have a ConvertImageEffect that can control that on a style level. But of course I can be mistaken :)
2.
I agree. BTW another use case is Imagemagick autorotate effect - ::transformDimension will only be able to return expected derivative dimensions if it has access to EXIF data of the original file. Currently it is voiding height/width and this issue would be able to solve that.
Comment #66
fietserwinUse cases would be to change the format only if the image does not contain transparency or only if the format is not well optimized, but that can be handled by looking only at the extension as already gets passed in. But having said that, I am sure that eventually someone will come up with a use case for his web site (showing private pictures in a non lossy full quality way, showing public pictures in a lossy reduced quality way?)
But OK, let's postpone that if it would be for the sake of the feature in itself only. If we say we want to keep the API consistent then it should be done here. But I am deferring that decision to a core committer.
So
- code reviewed and improved upon review
- tests added
RTBC
Comment #67
fietserwinand the status change itself...
Comment #68
fietserwinComment #69
Fabianx CreditAttribution: Fabianx as a volunteer commentedAt this point in the process, this will need a beta evaluation.
Comment #70
alexpottYep #69, we do need a beta evaluation. We also need a change record. Since this is an API breaking change - code that used to work with not. We need to make the case that the disruption is worth the cost - since this type of change can only be made under committer discretion part...
Comment #72
mondrakeDraft change record: https://www.drupal.org/node/2534082
Tagging for D7 backport given the OP and initial comments.
Anybody up for beta evaluation?
Comment #73
fietserwinAdded beta evaluation and rephrased the change record.
Comment #74
fietserwinAnd back to RTBC.
Comment #75
alexpottWhat is the inconsistency we're accepting?
drupal_basename()
is deprecated - use\Drupal::service('file_system')->basename()
instead.I think this needs more docs as to why it's being passed and what it can be used for - and the dangers of additional IO.
Comment #76
mondrake#75:
2.
It's about the fact that there is a proposal in #33 and later to also include a $uri argument to ::getDerivativeExtension, which is not done here.
Working on fixing 3. and 4.
Comment #77
mondrakeFixed #75.3 and #75.4
Comment #78
mondrakeComment #79
fietserwinThe changes correctly address the issues as posted in #75.
Comment #80
alexpottI don't know how this could be back-ported to D7 seems tricky without breaking APIs. Removing tag.
Thank you for addressing my feedback. I don't think this patch makes the API inconsistent and it fixes the bug. so +1 for the approach. Committed 19f7369 and pushed to 8.0.x. Thanks!
Thanks you for adding the beta evaluation to the issue summary.
Comment #83
Fabianx CreditAttribution: Fabianx as a volunteer commentedD7 issue added, thanks to a data array it's possible to fix this in a BC-compatible way for D7:
#3035571: [D7] ImageStyle::transformDimensions unable to deal with all effects.