I'm trying to resize an image using the method fit, and although I get the correct proportions, the result has an awful pixelated effect. How I can prevent this?
if ($imgHeight >= $imgWidth) {
$img->fit((int) ($value[0] * $ratio), $value[0]);
} else {
$img->fit($value[0], (int) ($value[0] / $ratio));
}
The original image is:
https://www.dropbox.com/s/gs7z4s4jg1x6k6o/gallery-portraits-of-strangers-18.jpg?dl=0
And I take this:
https://www.dropbox.com/s/q9l17sf3r5x02fn/63_big.jpg?dl=0
Sorry, I don't have enough reputation to post pics.
Related
I'm saving an image to uploads folder, but I'm using file_put_contents instead of wp_handle_upload - because I get an image in base64 and not as a file in $_FILES.
Image and certain post data are saved/updated as they should be using this functions:
wp_insert_attachment
wp_update_attachment_metadata
The problem is when I want to remove an old image (when saving a new one).
wp_delete_attachment does not remove the image (it does seem to remove stuff in db though..). I am thinking the problem lies in not using wp_handle_upload. (when I upload an image via upload btn and receive it using $_FILES and then upload it with wp_handle_upload - removing works)
Does anyone have an idea of what might be the right way to remove an image in my case?
Perhaps I can save it properly using wp_handle_upload even if I have an image in base64?
Thanks for any info.
EDIT: I also tried saving image with wp_upload_bits and wp_delete_attachment still did not work.
Another thing I checked: the code of wp_handle_upload function located in wp-admin/includes/file.php: I don't see an easy way to modify or copy the existing function and add a custom one which would accept base64 image instead of a file as in $_FILES. Someone has a "base64 to $_FILES" workaround perhaps?
as #KamilP said, WP inserts records in wp_posts and wp_postmeta tables.
So first you have to save your base64 images in temporary directory, then by using it's relative path and other data you can insert record in database by using wp_insert_attachment, link contains appropriate example.
This function will add image to media library.
To generate more thumbnails you can use wp_generate_attachment_metadata function. This function will update wp_postmeta table also with all details of image and thumbnails.
After this you can use wp_delete_attachment function to delete image from directory and database as well.
Another solution:
Use function from this link to generate image from base64 string.
then get mime type, size and path of image
create an array similar to $_FILES's.
then pass it to wp_handle_upload
UPDATE :
$_FILES array's structure is like this
array(5) {
'name' => string(8) "file name.extension" // file name with extension
'type' => string(0) "" // mime type of file, i.e. image/png
'tmp_name' => string(0) "" // absolute path of file on disk.
'error' => int(2) // 0 for no error
'size' => int(0) // size in bytes
}
You can create array like above with all details, use various file handling PHP functions to get size and mime type. Name is whatever you want to put, and tmp_name is a path of file on server where file is exists, in your case location of folder where you save your file from base64 string.
See updated link above for function which will give you image from base64 string.
I actually accomplished this before I saw an answer here, so I'm providing a solution here if someone runs into the same problem. I used the function below (first one) and also using wp_delete_attachment after just to be sure all stuff got removed.
/**
* Attempt at removing all images in uploads folder by providing an image url
* Example:
* - Provide http://mypage.com/wp-content/themes/mytheme/uploads/2015/12/testImage.jpg
* - this should remove all its images created when uploading:
* - testImage-150x150.jpg, testImage-300x300.jpg etc and also the provided original image
*
* We'r doing this because wp_delete_attachment() will not remove an image that was uploaded via a
* custom mytheme_upload_image() function (which is providing base64 image instead of file in $_FILES)
*
* TODO TODO mytheme_get_image_sizes() does not return ALL IMAGES THAT WERE CREATED (all sizes)
*/
function mytheme_remove_all_image_sizes_from_uploads($primary_image_url) {
$pi = pathinfo($primary_image_url);
$img_dirname = $pi['dirname'];
$img_dirname_exploded = explode('/',$img_dirname);
$last_dir = array_pop($img_dirname_exploded); // month usually (two digits)
$second_last_dir = array_pop($img_dirname_exploded); // year usually (4 digits)
$basename = $pi['basename']; // without trailing /
$img_name = $pi['filename'];
$img_extension = $pi['extension'];
$uploads = wp_upload_dir();
$base_uploads_dir = $uploads['basedir']; // without trailing /
$path_to_appropriate_uploads_dir = $base_uploads_dir.'/'.$second_last_dir.'/'.$last_dir.'/';
$img_name_to_remove = $img_name.'.'.$img_extension; // UNLINK
if(!#unlink($path_to_appropriate_uploads_dir.$img_name_to_remove)) {
// this image was not removed
}
$image_sizes = mytheme_get_image_sizes();
foreach($image_sizes as $size) {
$img_name_to_remove = $img_name.'-'.$size.'.'.$img_extension; // UNLINK
$img_path = $path_to_appropriate_uploads_dir.$img_name_to_remove;
if(mytheme_image_on_url_exists($img_path) && !#unlink($img_path)) {
// this image was not removed
}
}
}
/**
* Get size information for all currently-registered image sizes.
* Found an example of this on one of the wordpress' example pages ..
*
* #global $_wp_additional_image_sizes
* #uses get_intermediate_image_sizes()
* #return array $sizes Data for all currently-registered image sizes.
* TODO TODO mytheme_get_image_sizes() does not return ALL IMAGES THAT WERE CREATED (all sizes)
*/
function mytheme_get_image_sizes() {
global $_wp_additional_image_sizes;
$sizes = array();
foreach ( get_intermediate_image_sizes() as $_size ) {
if ( in_array( $_size, array( 'thumbnail', 'medium', 'large' ) ) ) {
$width = get_option( "{$_size}_size_w" );
$height = get_option( "{$_size}_size_h" );
} elseif ( isset( $_wp_additional_image_sizes[ $_size ] ) ) {
$width = $_wp_additional_image_sizes[ $_size ]['width'];
$height = $_wp_additional_image_sizes[ $_size ]['height'];
}
$img_name_end = $width."x".$height;
if(!in_array($img_name_end,$sizes)) {
$sizes[] = $img_name_end;
}
}
// ADD CUSTOM SIZES (this one is not returned otherwise?!)
$sizes[] = '300x200';
return $sizes;
}
The simple solution is probably to just manually add _wp_attached_file meta value to your attachment.
Set it to the file you want deleted when the attachment is deleted.
The logic behind this is according to https://core.trac.wordpress.org/browser/tags/4.4/src/wp-includes/post.php#L0:
get_attached_file returns that value to wp_delete_attachment which sets it as $file
And at the end of wp_delete_attachment it calls wp_delete_file($file)
So I'm guessing the underlying issue is that, you are never setting a value for the meta key _wp_attached_file.
The only downside is that I'm not sure what other processes in wp might be using that value. But you might get lucky, perhaps none, and/or it doesn't mess anything up by setting it manually.
I would like to know with certainty if a YouTube video is widescreen or not using the v3 API. There are many old videos that have a 4:3 ratio, so I need to detect this.
This was possible with API v2, but it is officially retired now. Here are the API v3 docs.
An API call looks something like this:
https://www.googleapis.com/youtube/v3/videos?id=[VIDEOID]&part=snippet&key=[DEVELOPERKEY]
Also, the thumbnail data always returns dimensions of 4:3, so that doesn't help. Here is an example:
[thumbnails] => Array
(
[default] => Array
(
[url] => https://i.ytimg.com/vi/nnnnnnnnn/default.jpg
[width] => 120
[height] => 90
)
...
)
Any ideas?
(I'm currently hacking this by analyzing pixels in the thumbnails where tell-tale black bars on 4:3 videos will be.)
Here is a sample video in 4:3 ratio:
https://www.youtube.com/watch?v=zMJ-Dl4eJu8 (old martial arts video)
and one in 16:9:
https://www.youtube.com/watch?v=7O2Jqi-LhEI (a new workout video)
Update: One promising suggestion was to explore fileDetails.videoStreams[].aspectRatio but it seems that this is only available to the video owner. Otherwise requesting fileDetails results in
The request cannot access user rating information. This error may occur because the request is not properly authorized
If you're open to using a different method other than V3 of the API, then I believe it is possible via the oEmbed API.
http://www.youtube.com/oembed?url={VIDEO_URL}&format=json
Like so:
http://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=zMJ-Dl4eJu8&format=json
Would produce:
{
"provider_url":"https:\/\/www.youtube.com\/",
"thumbnail_url":"https:\/\/i.ytimg.com\/vi\/zMJ-Dl4eJu8\/hqdefault.jpg",
"thumbnail_height":360,
"height":344,
"type":"video",
"version":"1.0",
"html":"\u003ciframe width=\"459\" height=\"344\" src=\"https:\/\/www.youtube.com\/embed\/zMJ-Dl4eJu8?feature=oembed\" frameborder=\"0\" allowfullscreen\u003e\u003c\/iframe\u003e",
"author_name":"hadronica2",
"width":459,
"provider_name":"YouTube",
"author_url":"https:\/\/www.youtube.com\/user\/hadronica2",
"title":"Aikido - Kazuo Chiba sensei - 1\u00ba part",
"thumbnail_width":480
}
In the examples you've given, the output was as follows:
http://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=zMJ-Dl4eJu8&format=json
Width: 459
Height: 344
Ratio: w/h = 1.3343 = 4:3 (ish)
http://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=zMJ-Dl4eJu8&format=json
Width: 480
Height: 270
Ratio: w/h = 1.7777 = 16/9
This appears to work in the examples you've provided.
Here is the abridged version that I have been using since v2 of the API retired.
It tests a few points on the top and bottom of the default.jpg thumbnail image of a given video where black bars might be. A vertically opposite point from a top point is tested to see if those pixels are similar to each other to within some delta. This is repeated for a few more points.
function isWidescreen($video = null) {
// LOGIC:
// 4:3 videos will have default.jpg with no top black bars
// 16:9 videos will have black top and bottom borders on default.jpg
// Get the default thumbnail (may have black bars on top and bottom)
$response = self::accessCurlObj()->get("https://i.ytimg.com/vi/{$video}/default.jpg");
$defaultImgRes = imagecreatefromstring($response);
$samplePoints = array(array(20,2), array(40,4), array(60,6), array(80,8));
// Scan a few points for equality between top and bottom
$height = imagesy($defaultImgRes);
foreach($samplePoints as $point) {
// Top
$rgbTop = imagecolorat($defaultImgRes, $point[0], $point[1]);
$colorsTop = imagecolorsforindex($defaultImgRes, $rgbTop);
// Bottom
$rgbBottom = imagecolorat($defaultImgRes, $point[0], $height - $point[1]);
$colorsBottom = imagecolorsforindex($defaultImgRes, $rgbBottom);
// If these arrays are not close, then let's call this 4:3 aspect
if(!$this->areArraysClose($colorsTop, $colorsBottom, 20)) {
return false;
}
}
// Default to widescreen
return true;
}
// Determine if the numeric values in the RGBA array are within some delta from each other
function areArraysClose(&$a, &$b, $delta = 10) {
foreach($a as $key => $val) {
if(abs($val - $b[$key]) > $delta) {
return false;
}
}
return true;
}
This seems to be working sufficiently enough. An obvious improvement is to check if the pixels are close to black, or apply some image processing to remove black bars automatically then check the dimensions of the remaining image.
However, my hope was that a domain-knowledgeable SO member would have a better solution before going deeper down this rabbit hole... and someone came through.
How to change Vimeo/Youtube embed sizes?
Currently the default embed is very small 300x150, i need 540px minimum :(
Using snippet to filter in functions.php, the code no works.
function rezzz_embed_defaults($defaults) {
$defaults['width'] = 350;
$defaults['height'] = 200;
return $defaults;
}
add_filter('embed_defaults','rezzz_embed_defaults');
Please see screenshot -> http://i.imgur.com/kv8SPEA.png
Please see your code carefully. I have used your code but changed width and height.
function rezzz_embed_defaults($defaults) {
$defaults['width'] = 540;
$defaults['height'] = 304;
return $defaults;
}
add_filter('embed_defaults','rezzz_embed_defaults');
You need to provide width and height in proper ratio to use video from youtube, vimeo and other source.
You can see working demo of above code here
I'd like to be able to return an image in Black&white in a controller, so I can use it in a template. On this page I found that the GD class has a greyscale method. Unfortunately I don't understand the GD class and how I can use it. I tried doing
$final = $image->getFormattedImage('greyscale',36,36,36);
But that didn't work. It does return an image object with a new URL but the image does not exist.
Can anyone explain to me how to make an imageobject into a greyscale image in a Silverstripe page Controller?
Well I had a go myself and this is what I came up with:
_config.php
Object::add_extension('Image', 'Greyscaled');
UPDATE: as of SilverStripe 3.1, you should use the config system instead of _config.php. Put the following in your mysite/_config/config.yml (Don't forget to ?flush=1 to reload the config cache after adding it):
Image:
extensions:
- 'Greyscaled'
Greyscaled.php
<?php
class Greyscaled extends DataExtension {
//This allows the template to pick up "GreyscaleImage" property, it requests a copy of the image from the cache or if it doesn't exist, generates a new one
public function GreyscaleImage($RGB = '76 147 29') {
return $this->owner->getFormattedImage('GreyscaleImage', $RGB);
}
//This is called internally by "generateFormattedImage" when the item is not already cached
public function generateGreyscaleImage(GD $gd, $RGB) {
$Vars = explode(' ', $RGB);
return $gd->greyscale($Vars[0], $Vars[1], $Vars[2]);
}
}
UPDATE2: With newer Versions of 3.1 ?? you can pass in more than 2 parameters and GD has been renamed to Image_Backend. This way you do not have spaces between the RGB-values in the image-name. Be aware $gd->greyscale needs a lot of juice - so you probable better downsize first and GreyscaleImage afterwards.
UPDATE3: Since this answer got some votes recently I assume people still using it, but I think in 2017 CSS filters are in many cases a better choice. Prefixed you'll have close to 90% coverage.
css-filters on caniuse.com
<?php
class Greyscaled extends DataExtension {
public function GreyscaleImage($R = '76', $G = '147', $B = '29') {
return $this->owner->getFormattedImage('GreyscaleImage', $R, $G, $B);
}
public function generateGreyscaleImage(Image_Backend $gd, $R, $G, $B) {
return $gd->greyscale($R, $G, $B);
}
}
and in the template:
<img src="$Images.GreyscaleImage.CroppedImage(1000,400).URL" alt="$Images.Title" />
Silverstripe 3.1 Image API
There is a module for this. Sorry but it's not on packagist just yet.
https://github.com/NightJar/ssrigging-greyscaleimages
Im using REST API to get products from store, there is around ~3k records. Unfortunately I need create thumbnails of products myself. To do this I use Imagine library for PHP.
After I insert/update products to database in next task I select from database all records and trying to create thumbnail for each of product.
It works... but I'm able to create 12 thumbnails in 120s(thats my execution time script). 12 thumbnails it's far too low for me, I would like to speed up this process, but how can I do this?
All thumbnails are 240x360px, size of each thumbnail is around 12KB.
Here is code which I use for generating thumbnails:
public function generateThumbnails($products)
{
$imagine = new Imagine();
$resize = 240/360;
foreach($products as $product)
{
if(!file_exists('data/thumbs/'.$product['productId'].'.jpg')){
$img = $imagine->open($product['productImage']);
$size = $img->getSize();
$width = $size->getWidth();
$height = $size->getHeight();
$newWidth = floor($height*$resize);
$cordX = $width/2-$newWidth/2;
if($cordX < 0)
$cordX = 0;
$img->crop(new Point($cordX, 0), new Box($newWidth, $height))->resize(new Box(240, 360))->save('data/thumbs/'.$product['productId'].'.jpg');
}
}
}
Im running this script on localhost (Windows 7), it's part of application based on Zend Framework 2 + Doctrine 2
You can use a cache or a file cache when the thumbnail is needed again.