Closed (fixed)
Project:
Image Field Repair
Version:
8.x-1.x-dev
Component:
Code
Priority:
Normal
Category:
Feature request
Assigned:
Unassigned
Reporter:
Anonymous (not verified)
Created:
31 Mar 2018 at 21:22 UTC
Updated:
10 Nov 2018 at 20:00 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #1
Anonymous (not verified) commentedvaplas created an issue. See original summary.
Comment #2
Anonymous (not verified) commentedComment #3
fietserwinThanks for this contribution. I think it would be a great addition, but am having difficulties with adding a mit licensed file to this Drupal repository. What is currently the best way (in D8) to add an external php library? Add it to our composer.json as a requirement? Is that enough to have it installed when people are installing this module via the UI? Or would that need a dependency on e.g. the Composer Manager module or the Libraries API?
If we are going this way, could we make it a soft dependency, thus e.g. first checking if the class exists? This way we can maybe save a lot of hassle for maintainers of a small site where the performance gain of this run once tool is of less importance?
What do you think?
Note that the github project that is used in this patch has been registered on packagist under https://packagist.org/packages/fastimage/fastimage and thus is available via composer.
Comment #4
Anonymous (not verified) commentedAbsolutely agree, thank you! Done.
Comment #5
fietserwinThanks for updating the patch. I changed it a bit by making $is_fast_image a static variable within image_field_repair_get_image_size(), so that this optimization is not exposed. The attached patch also contains some documentation updates (typo,missing @param), that will go in at the same time.
One more thing before committing: how to document this speed optimization? I guess we want this explained on the project page and in the readme.md (both are the same for now) with something like this:
Installation
============
If besides installing this module, you also install the library [fastimage/fastimage](https://packagist.org/packages/fastimage/fastimage) you can considerably speed up the process as this library can get image dimensions much faster than the internal PHP function getimagesize().
- If you install this module using composer, composer will download the library [fastimage/fastimage](https://packagist.org/packages/fastimage/fastimage) which speeds up the process as it considerably faster gets the dimensions of an image as the internal PHP function getimagesize().
- If you install this module manually or via the Drupal UI the library will not automatically be installed as well. If you have lots of images to check, we suggest that you install this library yourself using the command "composer require fastimage/fastimage".
Can you have a look a t that text and suggest any improvements you may see?
Comment #6
fietserwinBTW: the syntax for suggests in composer.json is as follows:
Thus no version indication but a reason why you may want to install the suggested package. I'm also not sure if our suggested package gets installed automatically when installing/requiring our module.
Comment #7
Anonymous (not verified) commented#5, #6: Thanks for the review and wise comments, @fietserwin. Unfortunately, I'm sick now, so I can not participate in a couple of days :(
I chose "suggest" like max soft dependency, but "require" also works for me.
Perhaps some notice about FastImage is worth adding to the "/admin/config/media/image_file_repair/dimensions" page, if the class not found?
#5: it looks like the attached patch was lost when sent.
Comment #8
fietserwinI changed the help texts as suggest does not automatically install the suggested library and I added the text also to the form.
However upon testing I found a few errors:
- The class name is Fastimage not Fastimage and that fooled class_exists().
- Fastimage fails to recognise a jpeg file ?! The first 2 bytes are 255 and 216 but after the mb_convert_encoding($result, "8BIT"); at the end of the getChars() method, this changes into 255 and 24 and thus subsequently the getType() method fails to recognise jpeg.
The latter is an error in the library which I cannot correct, so I have to put this one on hold. We either have to use another library or get it fixed. I attached the current state of this patch that now also covers for a failing Fastimage::getSize().
Comment #9
Anonymous (not verified) commented#8: Wow, cool points!
Looks like fastimage/fastimage is one of not the best fork (https://github.com/gr0g/fastimage).
Because the original repo (https://github.com/tommoor/fastimage) uses
FastImageand not uses mb_convert_encoding. Unfortunately, this repository is no longer supported.Another fork https://github.com/sillydong/phpfastimage also contains commit ("fix encoding problem when parsing jpeg with php 5.6+") and few other impromevents, but not support composer too.
The most popular solution on packagist is the https://packagist.org/packages/marc1706/fast-image-size.
Unfortunately, this library does not know how to work with URI (only with real path), and return info about size in an associative array. Therefore, if we choose it, we will need to do something like:
Another interesting solution is the https://packagist.org/packages/fasterimage/fasterimage, but I do not know whether some benefit from it.
Thanks for the trick with static variable. Looks just nice!
I like it too, but this is not supported in the PHP 5 :( So we have to do it in two lines.
Comment #10
fietserwinOK, good research on alternatives.
I think that the best we can do now is sort of go back to your patch from #2: let's take the orignal code from Tom Moor, just the class, not the readme, examples or whatever and add it to our module. This solves our bug. However the gr0g code has 1 addition: adding http to uris starting with //. I am not sure we need that, that is: can managed files start with this? I guess yes, so better add http or https based on what we are currently running in.
Looking at the MIT license we can do so if we leave the copyright messages at the top of the file:
According to the Drupal license faq point 2, we may incorporate MIT licensed work and redistribute it under GPL.
Comment #11
Anonymous (not verified) commentedI'm sorry for the delay. Honestly, I had some fear of using FastImage, after it turned out that it was not ideal solution :)
So I added another test to make sure that it works no worse than
@getimagesize(). Include cases where the format is not supported, or the image does not exist by uri. And it did not pass them as is.Also, efficiency is reduced, because of the constant creation of FastImage instances on every image.
We will need to rewrite almost every line to raise the class to the Drupal standard. Maybe better just add
@codingStandardsIgnoreFile?It seems to me that Drupal from the core does not support
//path-to-image, so this case can only be on specially configured sites, but I'm not against adding support for this.As a result, there was an idea to add our own decorating class (
ImageSizer). NowFastImageis full copy from https://github.com/tommoor/fastimage with one change:Comment #13
Anonymous (not verified) commentedComment #14
Anonymous (not verified) commentedMy bad! Sorry, I confused this with another sugar like
Comment #16
fietserwinThanks for all the work. I committed the last patch with some changes: using PHPStorm it is easy to reformat a file to Drupal coding standards. Furthermore, I am not a big fan of static classes, but I left ImageSizer as is
Comment #17
Anonymous (not verified) commentedThanks for this! I would like to clarify the effect slightly.
For local images, the performance profit will only be if they are really large (5 MB). Otherwise, our wrapper loses in speed by almost 3 times :(
Apparently the speed of the internal
getimagesize()function + full file reading is much faster than the PHP-wrapper + only first bytes reading.But! 3 times the winning is a fraction of a second.
Example, for 2000 local middle images the difference will be:
ImageSizer: 0.48287558555603 seconds.
getimagesize: 0.17654910061646 seconds.
However, when it comes to remote images, there is a great chance just to get error 'time out' with
getimagesize().100 images (1400x1000, 800Kb jpg):
ImageSizer: 7.8927567005157
getimagesize: 52.849262475967 (on shared hostings default time limit 30 seconds usualy).
100 images (8000 x 6000, 5Mb png):
ImageSizer: same time (because it does not depend on the file size)
getimagesize: ¯ \ _ (ツ) _ / ¯ (I did not wait)
Thus, this gives an insignificant time lag on the local images, and significant acceleration (and stability) for the remote images.
Comment #18
Anonymous (not verified) commentedTo neutralize the time difference when working with local files, we can use the following addition.
Difference after it for 1000 images in local storage near 0.005 seconds :) (vs. 0.1 seconds before)
Comment #19
fietserwinNOTE: I wrote this while you were posting the patch:
Interesting read. But if we have a file name, plain or in wrapper syntax, can we check if it is local (plain file name or a stream wrapper deriving from Drupal\Core\StreamWrapper\LocalStream) and subsequently decide what to use?
Because for sites with (mostly/only) local storage but enormous amounts of images, this may be a dramatic performance fallback after all...
EDIT (added this after seeing your patch):
I think your patch ignores stream wrappers around e.g. cloud storage, not sure they already exist for D8, though https://www.drupal.org/project/remote_stream_wrapper seems to do so.
Comment #20
Anonymous (not verified) commentedstream_wrapper_managerworks bit slower than strpos, but also effective:1000 images:
ImageSizer: 0.36590379142761
getimagesize: 0.29444312095642
(Edit: I changed server and images, so different times)
Comment #21
Anonymous (not verified) commentedAlternatively, we can add the checkbox to the UI page:
☑️ Enable accelerated processing of remote images (may slightly reduce the processing of local images)
Comment #22
Anonymous (not verified) commentedCombine #20 + #21.
Comment #23
Anonymous (not verified) commentedI also thought of adding a dataProvider to
ImageRepairTestfor check both regim. But it seems there is no need. Because we already have a specialImageSizerTestfor testing ImageSizer class.Comment #24
fietserwinSee \Drupal\Core\StreamWrapper\StreamWrapperInterface::realpath and its real implementation on \Drupal\Core\StreamWrapper\LocalStream and its FALSE returning implementation on \Drupal\file_test\StreamWrapper\DummyRemoteStreamWrapper.
So I think that something like the provided interdiff would be better regarding handling wrappers. I don't know the performance cost of repeated
\Drupal::service('stream_wrapper_manager')calls but I placed that manager in a local (static) variable.Comment #25
Anonymous (not verified) commentedSounds soundly. I added UI setting to shift the blame to the user) However, I really like #24.
This gives a good balance between fast execution and correct work on a variety of sites with any user experience. Zero UI ++!
The difference in the total switching between ImageSizer and getimagesize() is so small that it will only appear when processing hundreds of thousands of images. But for resources with so many images, a dozen seconds difference should not be critical.
Especially in view of the fact that now it is enough to repair only once.
@fietserwin++
Comment #26
fietserwinOK, some further reading and testing about local and remote stream wrappers taught me:
So, I created an array that keeps track of schemes and its locality and initializes it with the most common schemes (abusing the last remark ...)
What do you think of this patch (no UI, thus possibly slower on sites with many local large files). If you think we still need a UI, let's then do so in a separate patch.
Comment #27
Anonymous (not verified) commentedLooks very solid. I'm glad that you are author of this module! Forget about my UI version, please :)
Not used anymore, right?
The idea is to cache last 100 processed uri? This should give a profit on sites with a large number of revisions (ie, a large number of records with the same uri)?
Or is meant something like:
This gives the best effect when the processed uri are different. Near 0.01s +/- 0.005s on 1000 images than internal function!
Comment #28
fietserwinOops, I was a bit in a hurry to finish it, and after using the Drupal stream wrapper related classes, I found out abut stream_is_local(), so quite some refactorings. So, yes should store the scheme not the uri:
- FileSystem::uriScheme() returns scheme without the :// (and checks for the ://, not only :) and FALSE if there's no scheme part.
- explode(':"' ...) would store the uri if there's no scheme
Comment #29
Anonymous (not verified) commentedCool! I substituted this version of
isUriLocal()in my build with speedometer (#20 + #2966488: Refactoring image_field_repair.inc), and all works perfect!Comment #31
fietserwinThanks for testing. committed
Comment #32
Anonymous (not verified) commentedThanks for great works! Hm.. looks like #15 was commited?
Comment #34
fietserwinThanks for warning me. Should be restored by a merge commit.