I would like to do some custom processing on a certain thumbnail size in Wordpress with ImageMagick beyond the normal Wordpress functionality and am not quite sure how to go about doing this.
So I add my new thumbnail size:
add_image_size( 'new-thumb', 100, 100 );
And then this is where I'm not sure where I should hook into. Before the final copy of the thumbnail is saved within Wordpress I want to do some custom processing on it. Basic psuedo code for what I want is:
The_hook_or_action_that_fires_when_a_thumbnail_is_saved() {
if (<Thumbnail Being Generated> == 'new-thumb') {
$thumb_file = 'the thumbnail image we are about to save';
$thumbfile = 'do some imagemagic stuff here';
}
save_thumbnail;
}
I can handle the imagemagick stuff, but I'm not sure how / where to hook this custom thumbnail processing into.
Any advise would be greatly appreciated!
Thanks #brasofilo for pointing me in the right direction...
I poked around a bit and figured this one out. You can hook into wp_generate_attachment_metadata and do some image manipulation.
The basics of what I was trying to do is resize an image to a specific size ("brands" thumbnail), then expand the canvas for that thumbnail to a static height and width with a white background.
In case anyone has a similar situation I thought I would paste some code. It could be cleaned up to remove the repletion for each image type. The one issue with this code is that if the original image size is less than the desired thumbnail size it will not be generated (which is WordPress functionality).
add_image_size( 'brands', 200, 168 );
add_filter('wp_generate_attachment_metadata','replace_uploaded_image');
function replace_uploaded_image($image_data) {
// if there is no brands image : return
if ( !isset($image_data['sizes']['brands']) )
return $image_data;
//Set our desired static height / width (200px * 168px)
$staticWidth = 200;
$staticHeight = 168;
// paths to the uploaded image and the large image
$upload_dir = wp_upload_dir();
$brands_image_location = $upload_dir['path'] . '/' . $image_data['sizes']['brands']['file'];
// set our temp image file
$brands_image_location_tmp = "$brands_image_location.tmp";
// get the attributes of the source image
list($imageWidth, $imageHeight, $imageType, $imageAttr) = getimagesize($brands_image_location);
// there are different php functions depending on what type of image it is, so check the type
switch($imageType) {
//GIF
case 1:
//Create a 200x168 white canvas
$newimage=imagecreatetruecolor($staticWidth,$staticHeight);
$white=imagecolorallocate($newimage, 255, 255, 255);
imagefill($newimage,0,0,$white);
//Calculate where the image should start so its centered
if($imageWidth == $staticWidth) { $x_pos = 0; } else { $x_pos = round( ($staticWidth - $imageWidth) / 2 ); }
if($imageHeight == $staticHeight) { $y_pos = 0; } else { $y_pos = round( ($staticHeight - $imageHeight) / 2 ); }
//Copy the source image to the new canvas
$src = imagecreatefromgif($brands_image_location);
imagecopy($newimage, $src, $x_pos, $y_pos, 0, 0, $imageWidth, $imageHeight);
imagegif($newimage,$brands_image_location_tmp);
// delete the uploaded image
unlink($brands_image_location);
// rename the temporary brands image
rename($brands_image_location_tmp, $brands_image_location);
// update image metadata and return them
$image_data['sizes']['brands']['width'] = $staticWidth;
$image_data['sizes']['brands']['height'] = $staticHeight;
break;
//JPG
case 2:
//Create a 200x168 white canvas
$newimage=imagecreatetruecolor($staticWidth,$staticHeight);
$white=imagecolorallocate($newimage, 255, 255, 255);
imagefill($newimage,0,0,$white);
//Calculate where the image should start so its centered
if($imageWidth == $staticWidth) { $x_pos = 0; } else { $x_pos = round( ($staticWidth - $imageWidth) / 2 ); }
if($imageHeight == $staticHeight) { $y_pos = 0; } else { $y_pos = round( ($staticHeight - $imageHeight) / 2 ); }
//Copy the source image to the new canvas
$src = imagecreatefromjpeg($brands_image_location);
imagecopy($newimage, $src, $x_pos, $y_pos, 0, 0, $imageWidth, $imageHeight);
imagejpeg($newimage,$brands_image_location_tmp);
// delete the uploaded image
unlink($brands_image_location);
// rename the temporary brands image
rename($brands_image_location_tmp, $brands_image_location);
// update image metadata and return them
$image_data['sizes']['brands']['width'] = $staticWidth;
$image_data['sizes']['brands']['height'] = $staticHeight;
break;
//PNG
case 3:
//Create a 200x168 white canvas
$newimage=imagecreatetruecolor($staticWidth,$staticHeight);
$white=imagecolorallocate($newimage, 255, 255, 255);
imagefill($newimage,0,0,$white);
//Calculate where the image should start so its centered
if($imageWidth == $staticWidth) { $x_pos = 0; } else { $x_pos = round( ($staticWidth - $imageWidth) / 2 ); }
if($imageHeight == $staticHeight) { $y_pos = 0; } else { $y_pos = round( ($staticHeight - $imageHeight) / 2 ); }
//Copy the source image to the new canvas
$src = imagecreatefrompng($brands_image_location);
imagecopy($newimage, $src, $x_pos, $y_pos, 0, 0, $imageWidth, $imageHeight);
imagepng($newimage,$brands_image_location_tmp);
// delete the uploaded image
unlink($brands_image_location);
// rename the temporary brands image
rename($brands_image_location_tmp, $brands_image_location);
// update image metadata and return them
$image_data['sizes']['brands']['width'] = $staticWidth;
$image_data['sizes']['brands']['height'] = $staticHeight;
break;
}
return $image_data;
}
In my library I have the following:
Set custom name for generated thumbnails
Very interesting manipulation of thumb names and crop. Check the original Q&A linked in this one.
Uses:
intermediate_image_sizes_advanced
wp_generate_attachment_metadata
Automatically Use Resized Images Instead Of Originals
Uses:
wp_generate_attachment_metadata
How to automatically add rounded corners to thumbnails?
Uses:
image_make_intermediate_size and L#432
How to Require a Minimum Image Dimension for Uploading?
Uses:
wp_handle_upload_prefilter
Organize uploads by year, month and day
Uses:
wp_handle_upload_prefilter
wp_handle_upload and L#466
upload_dir
I would like to suggesta change, in order to make it work with Thumbnails Regenerate Plugin
$upload_dir = wp_upload_dir();
$brands_image_location = $upload_dir['basedir'].'/'.dirname($image_data['file']) . '/' . $image_data['sizes']['brands']['file'];
$brands_image_location = $brands_image_location.'.tmp';
That's so because wp_upload_dir returns the path for the current year-month but some previously uploaded images could have different path.
Related
This function can resize image when we upload at add new post and publish. When I try to use any import CSV plugin (Ultimate CSV Importer) for create multiple post and put my image url from other website, this function will not resize the image.
Can someone help to modified this function so can resize image during import?
// Hook the function to the upload handler
// https://developer.wordpress.org/reference/hooks/wp_handle_upload/
add_filter('wp_handle_upload', 'resize_image_after_upload');
function resize_image_after_upload($image_data){
// Set to null to disable that width/height resizing
$max_width = 800;
$max_height = 800;
// Check if there is a valid file
if(empty($image_data['file']) || empty($image_data['type'])) {
return $image_data;
}
// NOTE: We are not resizing any gifs, to avoid resizing animated gifs
// (which I think is the most common gif nowadays)
$valid_types = array('image/png','image/jpeg','image/jpg', 'image/webp');
if(!in_array($image_data['type'], $valid_types)) {
return $image_data;
}
// Get image image_editor
// https://developer.wordpress.org/reference/classes/wp_image_editor/
$image_editor = wp_get_image_editor($image_data['file']);
if(is_wp_error($image_editor)) {
return $image_data;
}
// Check if the image editor supports the image type
if(!$image_editor->supports_mime_type($image_data['type'])) {
return $image_data;
}
// Perform resizing
$sizes = $image_editor->get_size();
if((isset($sizes['width']) && $sizes['width'] > $max_width)
|| (isset($sizes['height']) && $sizes['height'] > $max_height)) {
// Resize, but do not crop
$image_editor->resize($max_width, $max_height, false);
// We will use the default recommended image quality
// Change, if you want to set a custom quality
//$image_editor->set_quality(90);
$image_editor->save($image_data['file']);
}
return $image_data;
}
In my application I utilize TCPDF to create a PDF that pastes an SVG onto it with the $link being a url that is being provided by the user so the SVG on the PDF is clickable like this
// create new PDF document
$pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, $pageLayout, true, 'UTF-8', false);
// remove default header/footer
$pdf->setPrintHeader(false);
$pdf->setPrintFooter(false);
// set margins
$pdf->SetMargins(0, 0, 0);
$pdf->SetXY(0, 0);
// set auto page breaks
$pdf->SetAutoPageBreak(TRUE, 0);
// set image scale factor
$pdf->setImageScale(1);
// add a page
$pdf->AddPage();
$fullPath = "$path".'qr-code/'."$uniqname".'.svg';
// Paste SVG onto page
$pdf->ImageSVG($file=$fullPath, $x=0, $y=0, $w='', $h='', $link=$query, $align='', $palign='', $border=0, $fitonpage=true);
/* Save as File */
$pdf->Output($path.'temp/'.$uniqname.'.pdf', 'F');
I then turn this PDF into a fully black CMYK PDF using Ghostscript
$targetFile = $path.'temp/'.$uniqname.'.pdf';
$iccFile = $this->params->get('ICC_FILE');
$process = new Process(['gs', '-o', $path.'pdf/'.$uniqname.'.pdf', '-sDEVICE=pdfwrite', '-sColorConversionStrategy=Gray', '-dProcessColorModel=/DeviceGray', '-dCompatibilityLevel=1.4', '-dOverrideICC=true', "-sDefaultCMYKProfile=$iccFile", $targetFile]);
$process->run();
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
And at the end I merge this PDF with another containing a Logo using FDPI like so
// create second PDF document for pasting logo
$pdf = new \setasign\Fpdi\Tcpdf\Fpdi(PDF_PAGE_ORIENTATION, PDF_UNIT, $pageLayout, true, 'UTF-8', false);
// remove default header/footer
$pdf->setPrintHeader(false);
$pdf->setPrintFooter(false);
// set margins
$pdf->SetMargins(0, 0, 0);
// set auto page breaks
$pdf->SetAutoPageBreak(TRUE, 0);
// set image scale factor
$pdf->setImageScale(1);
// add a page
$pdf->AddPage();
/* Import fullHeight page */
$pages = $pdf->setSourceFile( $path.'pdf/'.$uniqname.'.pdf', 'F' );
$page = $pdf->ImportPage( 1 );
$pdf->useTemplate( $page, 0, 0 );
if($logoselect == 'eng')
{
$logos = $pdf->setSourceFile( $path.'img/engadin_online_CMYK.pdf', 'F' );
$logo = $pdf->ImportPage( 1 );
$pdf->useTemplate( $logo, 34.25, 33.75, null, 32.5);
}
if($logoselect == 'gam')
{
$logos = $pdf->setSourceFile( $path.'img/gammetermedia_CMYK.pdf', 'F' );
$logo = $pdf->ImportPage( 1 );
$pdf->useTemplate( $logo, 34.25, 33.75, null, 32.5);
}
// Close and output PDF document
$base = $pdf->Output('qrcode.pdf', 'E');
My problem now is that the link I created on the SVG of the first PDF is not in the last PDF anymore. Is there any way I can preserve this link so that it is still clickable in the last PDF or any other way I can achieve the same result?
As requested heres the initial pdf, the ghostscript output, and the final pdf
With the extension "Mask" I've added some fields in Page Templates to assign Icons to pages. Also you can use a checkbox to activate the selected icon in menus. Menu meaning this case: I've also made a Content Element with Mask where you only enter a UID and all subpages of that UID will be created as <li> list.
I've set up the following fields in Page Templates:
page_01_icon (String): will contain something like "icon-home"
page_02_color (String): this will be the color of the icon or the background-color for the list element; depends on `page_color_background`, valid values are any css hex colors like "#a5011c"
page_02_color_text (String): if set, the text color will be changed to this
page_03_menu (Checkbox): checked == `page_icon` will be shown; else only the title of the page
page_04_icon_only_menu (Checkbox): checked == only icon will be shown, title not (not needed for my question, I use this only in the main menu)
page_05_color_background (Checkbox): if checked `page_color` is used as background color for this list element, if not the icon will get the colored with `page_color`; if `page_color` is not set at all neither icon or the background will get an color
I am very new to Typo3, but I actually get the above code work with the normal header menu on my page. It was a hard way there but that part works. For the header menu I could do it in FLUID, but this is now a lib and I need to do the same here but with TypoScript - no matter what I try I coudn't get it right. Most examples and how-tos rely on "if uid = 1..25" do something else. But I need multiple if elses for this. I think how I've done in FLUID is the right way, now trying to get the same logic to TypoScript... and ended up here.
Page Template Fields in Mask
This is how I added the fields in Mask
Content-Fields
Pages -> Edit -> Content-Fields (of Mask-Fields)
Main Menu Bar
This is what I've done with FLUID (the main menu bar). Icon-only + Icon and Title + Title only.
Spoken in simple PHP logic I would do the following (just to explain the main thinking / way I try in TypoScript):
// dont need to check if page_01_icon is set: field is required
// dont need to check if page_03_menu is checked: icons in this typo3 lib will always be displayed
// just assume that $title holds the title and $url the url to that page
foreach(....) {
// save html/css code in var for text color if a color is set
if(isset($page_02_color_text)) {
$textColorHTML = ' style="color' . $page_02_color_text . '"';
} else {
$textColorHTML = '';
}
// save html/css code in background of <li> if background-checkbox is checked and a color is set
if ($page_05_color_background == 1 && isset(page_02_color)) {
$liBackgroundHTML = ' style="background-color:' . $page_05_color_background . '"';
} else {
$liBackgroundHTML = '';
}
echo '<li' . $liBackgroundHTML . '></i> ' . $title . </li>';
}
Basicly thats what I tryied to try in TypoScript :)
What I "have":
A Mask template-file with the following content:
<f:cObject typoscriptObjectPath="lib.linkListAuto" data="{data}" />
The lib.linkListAuto-file:
This is the default version without the php logic from above. here I need to "translate" the php logic into.. I couldn't even post a half way woring script cuz everything I#ve tried ended as the wrong way :/
lib.linkListAuto = COA
lib.linkListAuto {
1 = HMENU
1 {
special = directory
special.value.field = tx_mask_link_root_id
wrap = <ul class="list-group">|</ul>
stdWrap.if.isTrue.data = register:count_menuItems
1 = TMENU
1 {
NO = 1
NO {
allWrap = <li class="list-group-item">|</li>
stdWrap.htmlSpecialChars = 1
ATagTitle.field = description // subtitle // title
}
}
}
}
How can I say in TypoScript "render this way every link (sub-page) that comes",
but if a color ist set and the checkbox for use_color_as_background ist checked: change the rendering for this entry and give the <li> a background-color
also change the text color if it is set
and finaly add the icon in front of the page-title
?
Please help with ideas that dont come up with "if uid equals X"-examples. Had enough of them, they work, yes, but I cant change them to what I am trying to do.
You need to understand the concept of TypoScript.
So the name may be misleading. TypoScript is not a scripting language but a configuration language. So you can't use the concept of a sequential computation.
In this way you configure your rendering.
Anyway you can add some logic to the rendering in the form of the .stdWrap.if function which can control whether a value is printed in the output.
The other important functionality is string concatenation: in TypoScript you can use COAs (Content Object Array) to build up a string where you decide for each array element how it is displayed.
For building up the string <li' . $liBackgroundHTML . '></i> ' . $title . </li> you might use something like:
temp.li_tag = COA
temp.li_tag {
10 = TEXT
10.value = <li
// if ($page_05_color_background == 1 && isset(page_02_color)) {
// $liBackgroundHTML = ' style="background-color:' . $page_05_color_background . '"';
// } else {
// $liBackgroundHTML = '';
// }
20 = TEXT
20.field = page_05_color_background
20.noTrimWrap = | style="background-color:|"|
20.if.isTrue.field = page_02_color
30 = TEXT
30.value = ><a href="
40 = TEXT
40.field = title
40.dataWrap = <i class="{field.page_01_icon}"></i> |
40.stdWrap.typolink {
parameter.field = uid
// if(isset($page_02_color_text)) {
// $textColorHTML = ' style="color' . $page_02_color_text . '"';
// } else {
// $textColorHTML = '';
// }
ATagParams.cObject = TEXT
ATagParams.cObject {
field = page_02_color_text
wrap = style="color|"
if.isTrue.field = page_02_color_text
}
}
50 = TEXT
50.value = </li>
}
I have a Canvas with multiple raster images.
I use onMouseDown on Tool to find select the item which was clicked.
I have a new requirement.
Suppose, two images overlap each other, and the upper image is partially transparent. That makes the lower image visible. But when I try to click on the lower image, obviously I end up choosing the upper image.
Failed Attempt
I tried to use the getPixel(point) function on Raster. I thought if I can figure that the selected pixel is transparent, I can ignore that raster and look for other items. But I am not getting the color value that I am expecting (transparent or not) using this function.
So, my second thought was that I need to change the mousedown event point from the global co-ordinate space to local raster co-ordinate space. It still did not work.
Is there a way to achieve what I want?
Code
tool.onMouseDown = (event) => {
project.activeLayer.children.forEach((item) => {
if (item.contains(event.point)) {
// check if hit was on a transparent raster pixel
const pixel = item.getPixel(event.point)
console.error(pixel.toCSS(true))
// 2nd attempt
const pixel = item.getPixel(item.globalToLocal(event.point))
console.error(pixel.toCSS(true))
}
}
}
There is a simpler way to do what you want to achieve.
You can rely on project.hitTestAll() method to do a hit test on all items.
Then, if the hit item is a raster, hit pixel color information will be contained in hitResult.color. hitResult.color.alpha is all you need to check if a raster was hit on a non-transparent pixel.
Here is a sketch demonstration of the solution.
const dataUrl = 'data:image/png;base64,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';
const lowOpacity = 0.3;
// create 2 rasters
new Raster({
source: dataUrl,
opacity: lowOpacity,
onLoad: function() {
this.position = view.center - 100;
}
});
new Raster({
source: dataUrl,
opacity: lowOpacity,
onLoad: function() {
this.position = view.center + 100;
}
});
// on mouse down
function onMouseDown(event) {
// unselect previously selected items
paper.project.selectedItems.forEach(item => {
item.selected = false;
item.opacity = lowOpacity;
});
// do a hit test on all project items
const hitResults = project.hitTestAll(event.point);
// for each hit result
for (let i = 0; i < hitResults.length; i++) {
const hitResult = hitResults[i];
// if item was hit on a non transparent pixel
if (hitResult && hitResult.color && hitResult.color.alpha > 0) {
// select item
hitResult.item.selected = true;
hitResult.item.opacity = 1;
// break loop
break;
}
}
}
I'm working on my WordPress website with Visual Composer.
I need to include a pageable container but it would be great if it can be like a slideshow.
This is my pageable container
Thanks in advance,
Regards :)
Based upon the current version of WP Bakery Page Builder the below works for me:
To build it I created a row with 3 columns, with the pageable container in the middle column and the left and right arrow images in the columns on either side.
Both arrow images and the pageable container were given IDs. In my example the IDs of the arrows were #arrow_prev and #arrow_next respectively. You can give your pageable container any unique ID.
(function ($) {
$(document).ready(function(){
$( '#arrow_prev' ).click( function( e ) {
var pageable_container = $(this).closest(".vc_row").find(".vc_tta-panels-container");
move_pageable_container(pageable_container,'prev');
});
$( '#arrow_next' ).click( function( e ) {
var pageable_container = $(this).closest(".vc_row").find(".vc_tta-panels-container");
move_pageable_container(pageable_container,'next');
});
function move_pageable_container(pageable_container,direction){
// Make a list of the panel IDs
var panel_ids = $(pageable_container.find(".vc_tta-panel"))
.map(function() { return this.id; }) // convert to set of IDs
.get();
// Find position of the active panel in list
var current_active_pos = panel_ids.indexOf($(pageable_container).find(".vc_tta-panel.vc_active").attr('id'));
var new_pos = 0;
switch(direction) {
case 'prev':
if (current_active_pos > 0){
new_pos = current_active_pos-1;
}else{
new_pos = panel_ids.length-1;
}
break;
case 'next':
if (current_active_pos < panel_ids.length-1){
new_pos = current_active_pos+1;
}else{
new_pos = 0;
}
break;
}
// Clear active panels
$(pageable_container.find(".vc_tta-panel")).each(function(i,a) {
$(this).removeClass("vc_active");
});
var new_active_panel = $(pageable_container).find('#'+ panel_ids[new_pos]);
$(new_active_panel).addClass("vc_animating");
$(new_active_panel).addClass("vc_active");
setTimeout(
function(){
$(new_active_panel).removeClass("vc_animating");
}, 350);
}
}
);
})(jQuery);
If you want a pseudo fading-in effect then you can use this additional CSS in your style sheet:
#id_of_pageable_container .vc_tta-panel.vc_animating {
opacity: 0!important;
}
Where #id_of_pageable_container is the ID that you gave your pageable container
A simpler solution with vanilla js only:
The idea is to find the target page button and press it programmatically, so that there is no need to mimic the plugin's animations as in Chaz's solution.
Add js (via Raw JS widget / other means):
function prevSlide () {
const slides = document.getElementsByClassName('vc_pagination-item');
for (let i = 0; i < slides.length; i++) {
if (slides[i].className.includes('vc_active')) {
if (i - 1 < 0) return;
slides[i - 1].firstChild.click();
return;
}
}
}
function nextSlide () {
const slides = document.getElementsByClassName('vc_pagination-item');
for (let i = 0; i < slides.length; i++) {
if (slides[i].className.includes('vc_active')) {
if (i + 1 >= slides.length) return;
slides[i + 1].firstChild.click();
return;
}
}
}
Add button widgets and set href to call js:
For left arrow button,
javascript:prevSlide();
For right arrow button,
javascript:nextSlide();
Hope this helps.
I prefer to use the Post Grid widget for that. Keep in mind that the pageable container is not totally responsive, it doesn't react to swipe touching, but the Post Grid does.
Post Grid is really powerful, although it also has its caveouts. You can create your content with posts and pages, or a custom post type and then filter what you want to show in your slider from the widget options.
In "advanced mode" you can use the Grid Builder to create your own template and control the output.
The only problems that I've found with this method is to set a variable height in sliders and that sometimes it is slow loading content and is not possible to do a lazyload.