-
Posts
4,088 -
Joined
-
Last visited
-
Days Won
88
Everything posted by horst
-
Oh, sorry there was a bug in the function. I have corrected it above. But maybe we should use a more thouroughful checking function: function checkOrientation($filename) { $out = array('file_exists' => file_exists($filename)); $out['exif_read_data'] = function_exists('exif_read_data') ? 'TRUE' : 'FALSE'; $exif = function_exists('exif_read_data') ? @exif_read_data($filename, 'IFD0') : null; $out['exif'] = (is_array($exif) ? 'TRUE' : 'FALSE'); $out['exif["Orientation"]'] = (isset($exif['Orientation']) ? 'TRUE' : 'FALSE'); $out['Orientation'] = htmlentities(strval($exif['Orientation'])); $ret = ''; foreach($out as $k => $v) $ret .= "<li>$k = $v</li>"; return "<ul>$ret</ul>"; } $image = $page->images->get('name=problematic.jpg'); $result = checkOrientation($image->filename); // flush the $result to the browser, e.g. with echo, // or add it to your delayed output buffer, if you uses one
-
Indeed! What you describe here is, that in the upload process the settings seems to be used toggled to its opposite. I will check this by myself in the next days and report back here. Thank you for your help.
-
@HannaP: Hi, this is not logical to me. I need to clarify what you mean with "They are ok in large version": Do you mean the original image or a resized variation? --- Regarding exif-tag values: the PHP function exif_read_data() can extract the IFD0 marker from Exif where it looks for the value in key 'Orientation'. Orientation only holds an integer in the range of 1 - 8, what not only represents the orientation but also if it is flipped vertical or horizontal. But neither 'bottom-left' nor 'Rotate 270 CW' will be stored there I assume. This sounds like the interpreted integer by two different softwares. Can you confirm this? If you want to check the direct return from the function, use this to test, and if it is not an integer in the range 1-8, please report back and sent me such an image. function checkOrientation($filename) { if(!function_exists('exif_read_data')) return false; $exif = @exif_read_data($filename, 'IFD0'); if(!is_array($exif) || !isset($exif['Orientation'])) return false; return $exif['Orientation']; } var_dump(checkOrientation($page->images->get('name=problematic.jpg')->filename));
-
I fully agree. From a logical point of view, it is simply not possible, that the imagesizer does handle variation creations different. The imagesizer doesn't know about the disered target. It must have to do with something other. As I have posted above, the ONLY difference between admin-thumbnails and other rezised variations is the default configuration. Therefore it is important that you doublecheck that all settings mentioned above ($config->imageSizerOptions => 'autoRotation' AND $config->adminThumbOptions=> 'autoRotation') are set identical. (and without typos!) It would be really helpful if you can do a test as follows Copy both of these config settings into the site/config.php, if not already there. Set both autoRotation settings to false Somewhere in a template file get you one of the problematic images, $page->images->get("name=problematic.jpg") and run an image call like this: $image = $page->images->get("name=problematic.jpg"); if($image) { $image->removeVariations(); // remove all variations, including AdminThumb $image = $image->width(intval($image->width / 2)); // create a new, halfsized variation echo "<img src='{$image->url}' />"; } You now should see a halfsized image, not corrected in rotation. Now go into the backend edit page, where PW will create a new adminthumb. The adminthumb also should be displayed without autocorrection. If you click onto it to view the original image, this also isn't autocorrected. All three images should have the identical orientation. Ok, step 2: set autoRotation for both settings to true call the page with the code from above with the same problematic image and report back which image variation has what orientation:frontend, halfsized is in right orientation: yes [ ] - no [ ] backend Original is in right orientation: yes [ ] - no [ ] backend Thumbnal is in right orientation: yes [ ] - no [ ] And please doublecheck the config settings before testing. If this will not show matching results, we have to look into Pageimage, what is the instance just before imagesizer. But before that, I want to be sure that there is no misconfiguration.
-
As sharpening is the last manipulation step, we can try to optimize the imagesizer in that regard that we try to free memory for one image instance before we pass the $thumb-instance to the sharpening method. If you like, you can first backup the imagesizer.php and then rearrange this part // optionally apply sharpening to the final thumb if($this->sharpening && $this->sharpening != 'none') { // @horst if(IMAGETYPE_PNG != $this->imageType || ! $this->hasAlphaChannel()) { // is needed for the USM sharpening function to calculate the best sharpening params $this->usmValue = $this->calculateUSMfactor($targetWidth, $targetHeight); $thumb = $this->imSharpen($thumb, $this->sharpening); } } // write to file $result = false; switch($this->imageType) { case IMAGETYPE_GIF: // correct gamma from linearized 1.0 back to 2.0 $this->gammaCorrection($thumb, false); $result = imagegif($thumb, $dest); break; case IMAGETYPE_PNG: if(!$this->hasAlphaChannel()) $this->gammaCorrection($thumb, false); // always use highest compression level for PNG (9) per @horst $result = imagepng($thumb, $dest, 9); break; case IMAGETYPE_JPEG: // correct gamma from linearized 1.0 back to 2.0 $this->gammaCorrection($thumb, false); $result = imagejpeg($thumb, $dest, $this->quality); break; } if(isset($image) && is_resource($image)) @imagedestroy($image); // @horst if(isset($thumb) && is_resource($thumb)) @imagedestroy($thumb); if(isset($thumb2) && is_resource($thumb2)) @imagedestroy($thumb2); if(isset($image)) $image = null; if(isset($thumb)) $thumb = null; if(isset($thumb2)) $thumb2 = null; to become this one: if(isset($image) && is_resource($image)) @imagedestroy($image); if(isset($thumb2) && is_resource($thumb2)) @imagedestroy($thumb2); if(isset($image)) $image = null; if(isset($thumb2)) $thumb2 = null; // optionally apply sharpening to the final thumb if($this->sharpening && $this->sharpening != 'none') { // @horst if(IMAGETYPE_PNG != $this->imageType || ! $this->hasAlphaChannel()) { // is needed for the USM sharpening function to calculate the best sharpening params $this->usmValue = $this->calculateUSMfactor($targetWidth, $targetHeight); $thumb = $this->imSharpen($thumb, $this->sharpening); } } // write to file $result = false; switch($this->imageType) { case IMAGETYPE_GIF: // correct gamma from linearized 1.0 back to 2.0 $this->gammaCorrection($thumb, false); $result = imagegif($thumb, $dest); break; case IMAGETYPE_PNG: if(!$this->hasAlphaChannel()) $this->gammaCorrection($thumb, false); // always use highest compression level for PNG (9) per @horst $result = imagepng($thumb, $dest, 9); break; case IMAGETYPE_JPEG: // correct gamma from linearized 1.0 back to 2.0 $this->gammaCorrection($thumb, false); $result = imagejpeg($thumb, $dest, $this->quality); break; } if(isset($thumb) && is_resource($thumb)) @imagedestroy($thumb); if(isset($thumb)) $thumb = null; We only have to move 4 lines that try to release gd image handles above the call to the sharpening method.
-
Hi Spica, thanks for the report. With todays large images from smartphones, 128MB is to small for them to be processed in a good manner. The line 1655 is image sharpening! There is no extra checking for memorylimit in this function. Please can you test the same page / image with the error and than add this additional option to the options array of the api call: $image->size($width, $height, array('sharpening' => 'none')); or alternatively, set the sharpening to 'none' in the site/config.php $config->imageSizerOptions = array( 'upscaling' => true, // upscale if necessary to reach target size? 'cropping' => true, // crop if necessary to reach target size? 'autoRotation' => true, // automatically correct orientation? 'sharpening' => 'none', // sharpening: none | soft | medium | strong 'quality' => 90, // quality: 1-100 where higher is better but bigger 'hidpiQuality' => 60, // Same as above quality setting, but specific to hidpi images 'defaultGamma' => 2.0, // defaultGamma: 0.5 to 4.0 or -1 to disable gamma correction (default=2.0) );
-
HermodBB - Basic BB/Forum/Comments module
horst replied to GuruMeditation's topic in Module/Plugin Development
For me, this looks good. If this is a module and it creates all the required fields and templates and handles data / pages, what is missing? This is a lot! If you have the possibility to upload this into a GitHub repo, this would be a good start to test, if someone will laugh, I don't think so. But maybe others start to use it and contribute a bit here and a piece there. Who knows? -
Hi @Spica, please have a look into ImageSizer. PW already calculates dimensions and compare the needed amount of ram with available ram _before_ opening an image or _before_ creating another instance of an already loaded image: https://github.com/ryancramerdesign/ProcessWire/blob/master/wire/core/ImageSizer.php#L412 https://github.com/ryancramerdesign/ProcessWire/blob/master/wire/core/ImageSizer.php#L1913 Only thing what comes to my mind is to increase memory, use smaller images or upload only one after the other and not a lot in parallel. Have you tried to upload multiple images as zip-archive? ------------------------------------------------------------------------------------- You can test and call the method from the outsite of imagesizer too, if you like: $info = getimagesize($image->filename); $sourceDimensions = array($info[0], $info[1], $info['channels']); $result = ImageSizer::checkMemoryForImage($sourceDimensions); var_dump($result); EDIT: the only thing what maybe happen in your system is that PHP isn't able to read a setting for max_mem, please have a look here to this line: https://github.com/ryancramerdesign/ProcessWire/blob/master/wire/core/ImageSizer.php#L1934 If you use the above code for a test, and var_dump($result); will show null, PHP isn't able to read a seting for max_mem or there isn't one set. In this case, please also refer to output from phpinfo().
-
The Imagesizer detects the settings of the Exif orientation tag and autocorrects this where needed, if it is enabled. The setting to enable / disable this for all variations can be set in $config->imageSizerOptions "autoRotation". The default is true: $config->imageSizerOptions = array( 'upscaling' => true, // upscale if necessary to reach target size? 'cropping' => true, // crop if necessary to reach target size? 'autoRotation' => true, // automatically correct orientation? 'sharpening' => 'soft', // sharpening: none | soft | medium | strong 'quality' => 90, // quality: 1-100 where higher is better but bigger 'hidpiQuality' => 60, // Same as above quality setting, but specific to hidpi images 'defaultGamma' => 2.0, // defaultGamma: 0.5 to 4.0 or -1 to disable gamma correction (default=2.0) ); There is an extra setting for this for AdminThumbs, default is true, too: $config->adminThumbOptions = array( 'width' => 0, // max width of admin thumbnail or 0 for proportional to height 'height' => 100, // max height of admin thumbnail or 0 for proportional to width 'scale' => 1, // admin thumb scale (1=auto detect, 0.5=always hidpi, 1.0=force non-hidpi) 'upscaling' => false, 'cropping' => true, 'autoRotation' => true, // automatically correct orientation? 'sharpening' => 'soft', // sharpening: none | soft | medium | strong 'quality' => 90, 'suffix' => '', ); Please ensure that you have autoRotation set to true for both use cases.
-
Example for a use case with importing things. This is a raw draft with a bit pseudo code: // you have created a queue in PW, the ID of the page is 2000 for example // here is some skeleton code for an importer that uses this queue // get and validate the queue handle if(! ($queue = $pages->get('id=2000')->wireQueue())) exit(); // we could not get the Queue page // now start to scan / read data for your imports, wrap each record into an array and put it into the queue foreach($pages->find(YOURSELECTOR) as $p) { $data = array($p->field1, $p->field2); $queue->addItem($data); } // now start to process your items by pulling one after the other from the Queue while($timer < $maxtime) { $data = $queue->getItem(); // process the $data ... } $session->redirect('./');
-
A task is something that needs to be done, e.g. sending an email at the end of someones buying in a shop. Sending the email is the "task". You can use this module for what ever you like. It is really very basic. If you has setup one Queue, you can access it (like in the code snippet in the first post) from a template file or from within a bootstrapscript. You need to know the name or id of the page that holds the Queue, thats all you need. Than you can pass data records to it. With the textfile storage every data record should be an array. I don't know if this can be useful for your import script. Definitely a Pro would be if you are limited with script execution time by the server. Then you can scan / read in all needed pages / images and put them into the queue without starting to process them. After you have finished this read in, you start processing them by pulling out items from the queue as long as you are within the time limit. If you reach critical time, you stop processing and end the script. It needs to be reloaded somehow, e.g. could be done without any cron but via session->redirect() as last command that ends the script. This way you strat with a fresh amount of execution time. No record gets lost as it could be if you use only session for storage. Maybe, you know the name Stack? It something similar as a Queue. Difference is, in a Queue is something like a pipe with both sides open. What is put in first, gets pulled out first at the other end: FiFo (First in First out). A stack is something where you put one item upon the other, maybe also a pipe, but with one end closed. Therefore you can only pull the last item from it, not the first: FiLo (First in Last out) WireQueue module is nothing more than the basic implementation of it into PW. ?? Ok ?? If you are unsure if this can be useful for your >cron task<, maybe you can explain a bit how this is be done. From where and when do you get the items that needs to be imported and how do you process the images and what is the next step, etc.
-
Hey mr. Ivan! Nice to hear from you. There can be some different usecases. Normally you want to use a Queue for decoupling time intensive tasks from the websites responses so that the users doesn't have to wait long times. For example, if someone booked / collected something on a site, there will finally an email sent to him / her. If the script would wait until it could connect to the smtp server and has sent the email, it could be that the smtp server is busy and delays the sending. With a Queue, you simply drop in this job and give fast response to the users screen: "Thanks for your booking! We will response by email soon.". Another script will pickup the job and proceed it. So, decoupling time intensive tasks and optionally split up the work to multiple workers if usefull / necessary. Another example would be sending a newsletter to 5k recipients. It seems better to me to have those temporary data seperated from the main Database in regard of Backups / Restores. With WireQueue you can create and controll Queues visually in the backend. This wouldn't be necessary but I found it usefull and comfortable. Currently there is only a very simple StorageModule available with plaintext files. The next one uses SQlite Databasefiles what is much more comfortable as it opens the WireQueue to not only put and get jobs into it but also can be used to store the current state of each job. Or if the collected data should be exported to other systems for further processing, etc. You can use whatever storage type you like. You only needs to create your own storage module upon the abstract class that comes with the module. Currently there are only comments in the code itself and the WireQueueTextfile module for detecting how it should be done. But it isn't complicated. You can setup one that uses a MYSQL table in another DB than that from your PW installation. etc. etc. More and better information will come later in next step. Does that help?
-
@ukyo: this looks very well. I have bookmarked this post because I allready use lazysizes and respimg in two projects, but I think I haven't it that well organized as you have. Next possibilty I have time for it, I will compare mine with yours and update / reorganize mine where needed. Many thanks for sharing!
-
I'm really not sure if this works every time. Lately I have tricked my self with using dashes - - - in fieldnames and not underscores _ _ _ . Fieldcreation was via API, where the fieldname was stored in a class constant: const MYFIELDNAME = 'my-new-fieldname'. The new fieldname of the created field worked out as: 'my_new_fieldname'. Later in the code, where I want access one field with a selector like $page->get("name=" . CLASS::MYFIELDNAME), it doesn't work. I'm not sure if this was with fields of type text / integer or only Page, but I perfectly remember that it hasn't worked. ---- There was also this: https://processwire.com/talk/topic/8709-croppableimage/page-2#entry86866 (read this and the two following posts)
-
Hm, is 'Slider1' a valid fieldname? I thought fieldnames are only lowercase?
-
Not with Likes but with Follows you can use the IPboard filter: ------------------ Another approach: Something like Kongondo uses, (Browserbookmarks with SearchCapabilities), but browser independent, could be: to use the bowsers functionality to send pagelinks by email. (It opens your default email program with a new email where the subject and body part is populated with the page title and page url). Before sending, you can add as many notices, text / tags / keywords / categories, or some copy / pasted segments from the webpage, etc. When sending this to a dedicated emailaddress that gets parsed by a module within PW, you have a browser independent storage with fulltext search.
-
I forgott to mention that it shows usefull information directly in the pagetree: total number of pending items and current states of the queues via icons
-
------------------------------------------------------------------------------ This is officially released now with some fixes and a second storage handler based on SQLite3. ------------------------------------------------------------------------------ WireQueue WireQueue is a module that allows easy creation and usage of Queues in ProcessWire. It is based upon a basic (parent) module that can or should have one or multiple StorageHandler modules. This alpha pre release contains a basic plain text storage module, WireQueueTextfile. The base module creates the needed: FIELDS (wire_queue_type, wire_queue_state, wire_queue_storage_options) TEMPLATES (wire-queue-container, wire-queue-storage, wire-queue-tools) PAGES (wire-queues = container for all Queuepages, wire-queue-storages = container for StoragetypePages) ROLE (wire-queue-admin) Each storage module creates one page under wire-queue-storages. New Queues can be created in the backend by adding a new page under "Wire Queues". After creation one have to select a Storage type from availables list and publish the page. After that, there are some buttons available to start / pause / and close the queue. Putting and getting data to and from the queue is done via API calls. First you have to get the page that holds the queue object. // get and validate the queue handle if($queue = $pages->get(SELECTOR_TO_DESIRED_PAGE)->WireQueue()) { // wrap your data into an array and pass it into the queue $success = $queue->addItem($data); ... } // get and validate the queue handle if($queue = $pages->get(SELECTOR_TO_DESIRED_PAGE)->WireQueue()) { $data = $queue->getItem(); ... } Thats basically all what you want from a queue. Following are a few conveniences: $queue->getPage()->title gives you the title of the queue, ($queue->getPage() returns the page object) $queue->className() shows the StorageType of the queue $queue->getState() and $queue->getStateStr() returns the current state of a queue: 1 = new / empty 2 = enabled / running 3 = paused 4 = closed / archived $queue->itemCount() gives the total number of all pending items in the queue Here is code that gives an overview of all Queues in the system: $container = $pages->get('template=wire-queue-container'); $bodyContent = "<h1>$container->title</h1>"; $bodyContent .= "<p>There are currently {$container->numChildren} Queues defined:</p>"; $bodyContent .= "<ul>"; foreach($container->children() as $p) { if(! ($queue = $p->wireQueue())) continue; $bodyContent .= "<li>{$queue->getPage()->title}<ul>"; if(!$queue->ready2use()) { $bodyContent .= "<li>{$queue->className}</li>"; $bodyContent .= "<li>This Storagetype is not ready to use! The System seems not to provide all requirements.</li>"; $bodyContent .= "</ul></li>"; continue; } $bodyContent .= "<li>{$queue->className}</li>"; $bodyContent .= "<li>{$queue->getStateStr()} ({$queue->getState()})</li>"; $bodyContent .= "<li>Currently are {$queue->itemCount()} items pending!</li>"; $bodyContent .= "</ul></li>"; } $bodyContent .= "</ul>"; Following are a screenshots of the backend The module is on Github: https://github.com/horst-n/WireQueue Would be nice if someone has time and can test it a bit and report back.
- 13 replies
-
- 14
-
-
No, its not a bug. CroppableImages's UI was made before the crop functionality was introduced. CroppableImages was planned to add more functionality and better space usage to the UI, therefore it was not build on top of the core images UI. Thus truncate the path to all further updates / changes to the core image. It would be really cool if someone of the guys who are building the new images UI also could (re)build the croppableimages UI back to use the default one only with the additional buttons for the crop, after the core UI image is finished.
-
Good find! ..., and welcome to the forums. PS: do you know PIA? She can assist in images API coding. $url2 = $image->size(100, 100, array('upscale' => false, 'cropping' => false, 'suffix' => array('noupscsale', 'nocrop')))->url; can be written like $url2 = $image->contain("size=100, suffix=noupscsale-nocrop")->url;
-
Looks good, only thing I would check is: You probaply will give a name and / or title to your new page. I would suggest to create it first and check if a page with that given name or title already exists: $parent = wire('pages')->get('/site-manager/bookings/'); $genericTitle = $somehowCreatedFromYourDataOrTimestampOrWhatever; $p = $pages->get("title={$genericTitle}, template=booking, parent.id=" . $parent->id); if(0 == $p->id) { // page does not exist, we can create a new one $p = new Page(); // create new page object $p->template = 'booking'; // set template $p->parent = $parent; $p->title = $genericTitle; ...... } else { // UUUPs! A Page with that title exists. // This case must be handled different! ...... }
-
look here: https://processwire.com/talk/topic/11842-change-timezone-per-user-in-admin/#entry110099
-
I have had this too and it is also reported on Github. The error comes from other third party admin modules that try to fetch the id of the currently edited page in admin. The error is raised because those third party modules blindly assume the id is an integer value and passes it unsanitized, for example, via $pages->get($input->get->id). That will raise those errors. Very hard to debug and detect is such a unsanitized selector request when it is called in a autoloaded modules ready event! I have submitted a patch / pull request for that issue on github. PS: Devs of third party modules that need to fetch the currently edited page can do it at least like: $p = $pages->get('id=' . $input->get->id); // but better validate the id before using: if(!is_int($input->get->id) && !is_numeric($input->get->id)) return false; // or only using is_int() ?? if(is_int($input->get->id)) { $p = $pages->get('id=' . $input->get->id); . PPS: I have to admit that I had done it wrong too in some of my custom modules.
-
You can use if / elseif / else, but you also can use switch(). foreach($pageArray as $child) { switch($child->template) { case: 'home': // your code here break; case: 'anothername': // your code here break; default: // your code here if there is a default for this, otherwise yiou can ommit it } // ... }
- 13 replies
-
- navigation
- child
-
(and 1 more)
Tagged with:
-
I have some more different in my collection