center crop with gravity using Imagick and PHP - php

I'm looking to center crop and image using Imagick PHP apis (not command line version of Imagick).
Essentially I want to do what is possible via command line, using API. Here is an example via command line:
http://www.imagemagick.org/Usage/crop/#crop_gravity
Here is what I'm doing (not working). It always crops the upper left corner of the source:
$this->imagickObj->setGravity(\Imagick::GRAVITY_CENTER);
$this->imagickObj->cropImage(300,250,0,0);
$this->imagickObj->setImagePage(0, 0, 0, 0);
Why is the setGravity not applying to the image before the crop? http://www.php.net/manual/en/function.imagick-setgravity.php says it should apply to the object (in this case the single image)...

Its too late for the original person who asked the question but for future visitors, correct solution is
bool Imagick::cropThumbnailImage ( int $width , int $height )
Sorry for late reply but I too stuck here just 30 mins ago and first google result redirected me here. Hope same will not happen with others.

Looks like there is not support, here is how I ended up doing it:
https://gist.github.com/1364489

The Imagemagick object's cropImage() method's 3rd and 4th argument are defining the upper-left corner of the crop. Either try passing those as null (and use the setGravity() method), or you may actually have to calculate where the crop is supposed to take place and pop those numbers into the cropImage() method (and don't bother with setGravity()).
For what it's worth, I have done a lot of coding around Imagemagick using PHP, and due to the horrible documentation of the Imagemagick extension, I resorted to making lots of nice'd command line calls.

I have created component to crop and resize images
here is the code (yii2)
Component uses imagine/imagine extension, install it before
<?php
namespace common\components;
use Imagine\Gd\Imagine;
use Imagine\Image\Box;
use Imagine\Image\ImageInterface;
use Imagine\Image\Point;
use Imagine\Imagick\Image;
class ResizeComponent
{
/**
* Resize image
* #param string $source source image path
* #param string $destination destination image path
* #param int $width
* #param int $height
* #param int $quality Jpeg sampling quality (0-100, 80 is best for seo)
* #return boolean is picture cropped
*/
public static function resizeImage($source, $destination, $width, $height, $quality = 80)
{
if (file_exists($source) && is_file($source)) {
$imagine = new Imagine();
$size = new Box($width, $height);
$mode = ImageInterface::THUMBNAIL_INSET;
$resizeimg = $imagine->open($source)->thumbnail($size, $mode);
$sizeR = $resizeimg->getSize();
$widthR = $sizeR->getWidth();
$heightR = $sizeR->getHeight();
$preserve = $imagine->create($size);
$startX = $startY = 0;
if ($widthR < $width) {
$startX = ($width - $widthR) / 2;
}
if ($heightR < $height) {
$startY = ($height - $heightR) / 2;
}
$preserve->paste($resizeimg, new Point($startX, $startY))
->save($destination, array('jpeg_quality' => $quality));
return true;
} else {
return false;
}
}
/**
* Crop image
* #param string $source source image path
* #param string $destination destination image path
* #param int $width
* #param int $height
* #param int $quality Jpeg sampling quality (0-100, 80 is best for seo)
* #return boolean is picture cropped
*/
public static function cropImage($source, $destination, $width, $height, $quality = 80)
{
if (file_exists($source) && is_file($source)) {
$imagine = new Imagine();
$size = new Box($width, $height);
$mode = ImageInterface::THUMBNAIL_OUTBOUND;
$image = $imagine->open($source)->thumbnail($size, $mode);
$image->thumbnail($size, $mode)->save($destination, array('jpeg_quality' => $quality));
return true;
} else {
return false;
}
}
}
The difference between crop and resize is :
crop cant display all image, so borders will be cropped (best for not informative thumbnails)
resize displays full image, but borders will be filled with static color (or transperency if needed) (best if all image needed to be shown, as in shop catalog)
Use this component statically, best practice as ServiceLocator

Related

remove Exiff from UploadedFile Symfony

I have form that use to upload picture.
I can't save this picture on my server, But I send it to another service ( external)
I have to encode base 64
my code is:
$base_img = base64_encode(file_get_contents($data["image"]));
where $data['image'] is UploadedFile
How can Remove all Exiff from $data['image'] before encode?
Recently I needed exactly that and I achieved it with passing $uploadedFile->getRealPath() to the Imagick. Complete function:
/**
* #param UploadedFile $uploadedFile
* #throws \ImagickException
*/
public function stripMeta(UploadedFile $uploadedFile): void
{
$img = new Imagick($uploadedFile->getRealPath());
$profiles = $img->getImageProfiles("icc", true);
$img->stripImage();
if(!empty($profiles)) {
$img->profileImage("icc", $profiles['icc']);
}
$img->writeImage($uploadedFile->getRealPath());
}
I took saving icc profile idea from comments here: https://www.php.net/manual/en/imagick.stripimage.php

XenForo Avatar upload fails, $image == false - PHP7

I'm stumped on a XenForo 1.5.7 / php7 issue. I've read that tempnam() was changed as of php7 (based on temp dir permissions), but I've chmod'd the directory as that link states, still to no avail.
I printed out $newTempFile which returns /var/www/forum/internal_data/temp/xfJ9FLyG (looks correct). It's the next line, the $image variable, that does not get set, and then throws the error in the if() below.
$newTempFile = tempnam(XenForo_Helper_File::getTempDir(), 'xf');
$image = XenForo_Image_Abstract::createFromFile($fileName, $imageType);
if (!$image)
{
throw new XenForo_Exception(new XenForo_Phrase('image_could_be_processed_try_another_contact_owner'), true);
}
Here is the code for createFromFile() in Image\Abstract.php:
/**
* Creates an image from an existing file.
*
* #param string $fileName
* #param integer $inputType IMAGETYPE_XYZ constant representing image type
*
* #return XenForo_Image_Abstract|false
*/
public static function createFromFileDirect($fileName, $inputType)
{
throw new XenForo_Exception('Must be overridden');
}
...
public static function createFromFile($fileName, $inputType)
{
$class = self::_getDefaultClassName();
return call_user_func(array($class, 'createFromFileDirect'), $fileName, $inputType);
}
Because it looks like createFromFileDirect() is called from createFromFile(), my thought was that a "Must be overriden" error would be thrown, but this doesn't appear to be the case.
Any ideas?

Imagick loads images on local server, but not godaddy server. PHP

I have an image that is uploaded to the server, I then get the image information and resize using imagick, this works great on my local server (xammp) but when I upload to site to godaddy, only certain images load. Here is a snippet of code;
$image = frameEngine($_FILES['myFile']['tmp_name'];
$tempHeight = $image->getHeight();
$tempWidth = $image->getWidth();
$inches = $image->getInches();
In the class frameEngine it reads;
Class frameEngine {
private $image;
private $height;
private $width;
private $dpi;
function __construct($file) {
$this->image = new Imagick(realpath($file));
$this->width = $this->image->getImageWidth();
$this->height = $this->image->getImageHeight();
$this->dpi = $this->image->getImageResolution();
}
public function getHeight() {
return $this->height;
}
public function getWidth() {
return $this->width;
}
public function getInches() {
$dpi = $this->dpi;
$iw = round(($this->width / $dpi['x']*100)/100,2);
$ih = round(($this->height / $dpi['y']*100)/100,2);
return array('w' => $iw, 'h' => $ih);
}
}
I get a division by zero error in getInches() obviously because Imagick is returning 0 as image height and width, therefore I believe imagick is failing on the image. The image is obviously fine as it works on my local server.
Two things before you come to the conclusion that Imagick is not working
Make a test script and use phpinfo() . Hit the page from your browser and look for imagick mentioned in that page. If this is not mentioned then boom! you need to contact your hosting provider for this
Secondly, make sure that file upload is enabled and file is uploaded properly by using old-school var_dump
Hope this helps fella!

Create a QR-Code Image with the php imagick library

Hi,
We are using imagick for different image manipulations and have a request to add
QR watermarks in the end.
Right now I could only find PHP QR Code library which uses the GD2 library:
Implemented purely in PHP, no external dependencies except GD2
Is there any php snippet or library which uses imagick to create QR codes?
Looking at the PHP QR Code library, there is only one file (I think) that accesses the GD library: qrimage.php. So change that file to output via imagick and use the rest of PHP QR Code.
Below is a possible imagick output file I wrote to replace qrimage.php. However, I am unable to test this code, since I am on Windows, and cannot install imagick.
Can someone please debug it, and edit this post with any corrections?
<?php
/*
* PHP QR Code encoder
*
* Image output of code using GD2
*
* PHP QR Code is distributed under LGPL 3
* Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
define('QR_IMAGE', true);
class QRimage {
//----------------------------------------------------------------------
public static function png($frame, $filename = false, $pixelPerPoint = 4, $outerFrame = 4,$saveandprint=FALSE)
{
$image = self::image($frame, $pixelPerPoint, $outerFrame, "png", 85, $filename, $saveandprint);
}
//----------------------------------------------------------------------
public static function jpg($frame, $filename = false, $pixelPerPoint = 8, $outerFrame = 4, $q = 85)
{
$image = self::image($frame, $pixelPerPoint, $outerFrame, "jpeg", $q, $filename, $saveandprint);
}
//----------------------------------------------------------------------
private static function image($frame, $pixelPerPoint = 4, $outerFrame = 4,
$format = "png", $quality = 85, $filename = FALSE, $saveandprint = FALSE)
{
$imgH = count($frame);
$imgW = strlen($frame[0]);
$col[0] = new ImagickPixel("white");
$col[1] = new ImagickPixel("black");
$image = new Imagick();
$image->newImage($imgW, $imgH, $col[0]);
$image->setCompressionQuality($quality);
$image->setImageFormat($format);
$draw = new ImagickDraw();
$draw->setFillColor($col[1]);
for($y=0; $y<$imgH; $y++) {
for($x=0; $x<$imgW; $x++) {
if ($frame[$y][$x] == '1') {
$draw->point($x,$y);
}
}
}
$image->drawImage($draw);
$image->borderImage($col[0],$outerFrame,$outerFrame);
$image->scaleImage( $imgW * $pixelPerPoint, 0 );
if ($filename === FALSE) {
Header("Content-type: image/jpeg");
echo $image;
} else {
if($saveandprint===TRUE){
$image->writeImages($filename, true);
Header("Content-type: image/" . $format);
echo $image;
} else {
echo $image;
}
}
}
}
There is a merged file called phpqrcode.php that contains the entire qrimage.php, so you will either have to remerge that file, or else replace the relevant section.
If you use a different filename for the above code, you will have to change the reference in the file qrlib.php and merge.php.
I tested the above implementation and it worked.
There is one mistake : you missed to add the outerframe in the final image size.
$image->scaleImage( ($imgW + 2*$outerFrame) * $pixelPerPoint, 0 );
Also, it seems that GD library is a lot faster than ImageMagick in this case.
I benched the use of GD and imagick on the creation of the same 50 random qr codes.
I isolated the part were QR code are generated so in fact the use of QRimage::png.
I only teste png generation.
These are my results :
GD :
min time : 0,0148401260 s
max time : 0,0211210251 s
average : 0,0167747593 s
ImageMagick :
min time : 0,0799968243 s
max time : 0,1147611141 s
average : 0,0918840790 s
In the final code, this makes a little difference. The other part of the code takes something like 0.15s to run and on a large amount of codes it makes a difference (I benched QRcode::png with a result like : 0.17s per qrcode ith GD and 0.24s per code with imagemagick).

PHP Thumbnail Image Generator Caching: How to set If-Last-Modified/Max-Age/Last-Modified HEADERS correctly in PHP?

Even after a very high score of Google PageSpeed(97) & Yahoo! YSlow(92) the PHP generated thumbnails don't seem to be coming passively from an old cache: they seem to be generated every time again...and again... freshly baked consuming lots of waisted time.
This question will focus only & specifically on how to solve the CACHE problem of the PHP Code that generates the thumbs:
Just have a look at these tiny puny little thumbnails measuring only 3 ~ 5 kb each!
Waterfall in detail: http://www.webpagetest.org/result/110328_AM_8T00/1/details/
Any & all suggestons are +1 help to me and warmly welcome, for I have grown quite desperate on this issue for the last months. Thanx a Thousand!
Using or not Modrewrite does not influence speed: both are same. I use these rewrite conditions: RewriteCond %{REQUEST_URI} ^/IMG-.*$ & RewriteCond %{REQUEST_FILENAME} !-f
Both the original default URL as well as the beautified rewritten URL produce the same delays!! So let us not point the fault to the lightning fast Apache: its the PHP Cache / headers that are somehow wrongly coded...
Warning by webpagetest.org: Leverage browser caching of static assets: 69/100
FAILED - (No max-age or expires): http://aster.nu/imgcpu?src=aster_bg/124.jpg&w=1400&h=100&c=p
After each refresh, you will see either of these two warnings appear on random at REDbot.org
Relevant Portions of The Code:
// Script is directly called
if(isset($_GET['src']) && (isset($_GET['w']) || isset($_GET['h']) || isset($_GET['m']) || isset($_GET['f']) || isset($_GET['q']))){
$ImageProcessor = new ImageProcessor(true);
$ImageProcessor->Load($_GET['src'], true);
$ImageProcessor->EnableCache("/var/www/vhosts/blabla.org/httpdocs/tmp/", 345600);
$ImageProcessor->Parse($quality);
}
/* Images processing class
* - create image thumbnails on the fly
* - Can be used with direct url imgcpu.php?src=
* - Cache images for efficiency
*/
class ImageProcessor
{
private $_image_path; # Origninal image path
protected $_image_name; # Image name string
private $_image_type; # Image type int
protected $_mime; # Image mime type string
private $_direct_call = false; # Is it a direct url call? boolean
private $_image_resource; # Image resource var Resource
private $_cache_folder; # Cache folder strig
private $_cache_ttl; # Cache time to live int
private $_cache = false; # Cache on boolean
private $_cache_skip = false; # Cache skip var boolean
private function cleanUrl($image){ # Cleanup url
$cimage = str_replace("\\", "/", $image);
return $cimage;
}
/** Get image resource
* #access private, #param string $image, #param string $extension, #return resource */
private function GetImageResource($image, $extension){
switch($extension){
case "jpg":
#ini_set('gd.jpeg_ignore_warning', 1);
$resource = imagecreatefromjpeg($image);
break;
}
return $resource;
}
/* Save image to cache folder
* #access private, #return void */
private function cacheImage($name, $content){
# Write content file
$path = $this->_cache_folder . $name;
$fh = fopen($path, 'w') or die("can't open file");
fwrite($fh, $content);
fclose($fh);
# Delete expired images
foreach (glob($this->_cache_folder . "*") as $filename) {
if(filemtime($filename) < (time() - $this->_cache_ttl)){
unlink( $filename );
}
}
}
/* Get an image from cache
* #access public, #param string $name, #return void */
private function cachedImage($name){
$file = $this->_cache_folder . $name;
$fh = fopen($file, 'r');
$content = fread($fh, filesize($file));
fclose($fh);
return $content;
}
/* Get name of the cache file
* #access private, #return string */
private function generateCacheName(){
$get = implode("-", $_GET);
return md5($this->_resize_mode . $this->_image_path . $this->_old_width . $this->_old_height . $this->_new_width . $this->_new_height . $get) . "." . $this->_extension;
}
/* Check if a cache file is expired
* #access private, #return bool */
private function cacheExpired(){
$path = $this->_cache_folder . $this->generateCacheName();
if(file_exists($path)){
$filetime = filemtime($path);
return $filetime < (time() - $this->_cache_ttl);
}else{
return true;
}
}
/* Lazy load the image resource needed for the caching to work
* #return void */
private function lazyLoad(){
if(empty($this->_image_resource)){
if($this->_cache && !$this->cacheExpired()){
$this->_cache_skip = true;
return;
}
$resource = $this->GetImageResource($this->_image_path, $this->_extension);
$this->_image_resource = $resource;
}
}
/* Constructor
* #access public, #param bool $direct_call, #return void */
public function __construct($direct_call=false){
# Check if GD extension is loaded
if (!extension_loaded('gd') && !extension_loaded('gd2')) {
$this->showError("GD is not loaded");
}
$this->_direct_call = $direct_call;
}
/* Resize
* #param int $width, #param int $height, #param define $mode
* #param bool $auto_orientation houd rekening met orientatie wanneer er een resize gebeurt */
public function Resize($width=100, $height=100, $mode=RESIZE_STRETCH, $auto_orientation=false){
// Validate resize mode
$valid_modes = array("f", "p");
}
// .... omitted .....
// Set news size vars because these are used for the
// cache name generation
// .... omitted .....
$this->_old_width = $width;
$this->_old_height = $height;
// Lazy load for the directurl cache to work
$this->lazyLoad();
if($this->_cache_skip) return true;
// Create canvas for the new image
$new_image = imagecreatetruecolor($width, $height);
imagecopyresampled($new_image, $this->_image_resource, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h);
// .... omitted .....
$this->_image_resource = $new_image;
}
/* Create image resource from path or url
* #access public, #param string $location, #param bool $lazy_load, #return */
public function Load($image,$lazy_load=false){
// Cleanup image url
$image = $this->cleanUrl($image);
// Check if it is a valid image
if(isset($mimes[$extension]) && ((!strstr($image, "http://") && file_exists($image)) || strstr($image, "http://")) ){
// Urlencode if http
if(strstr($image, "http://")){
$image = str_replace(array('http%3A%2F%2F', '%2F'), array('http://', '/'), urlencode($image));
}
$image = str_replace("+", "%20", $image);
$this->_extension = $extension;
$this->_mime = $mimes[$extension];
$this->_image_path = $image;
$parts = explode("/", $image);
$this->_image_name = str_replace("." . $this->_extension, "", end($parts));
// Get image size
list($width, $height, $type) = getimagesize($image);
$this->_old_width = $width;
$this->_old_height = $height;
$this->_image_type = $type;
}else{
$this->showError("Wrong image type or file does not exists.");
}
if(!$lazy_load){
$resource = $this->GetImageResource($image, $extension);
$this->_image_resource = $resource;
}
}
/* Save image to computer
* #access public, #param string $destination, #return void */
public function Save($destination, $quality=60){
if($this->_extension == "png" || $this->_extension == "gif"){
imagesavealpha($this->_image_resource, true);
}
switch ($this->_extension) {
case "jpg": imagejpeg($this->_image_resource,$destination, $quality); break;
case "gif": imagegif($this->_image_resource,$destination); break;
default: $this->showError('Failed to save image!'); break;
}
}
/* Print image to screen
* #access public, #return void */
public function Parse($quality=60){
$name = $this->generateCacheName();
$content = "";
if(!$this->_cache || ($this->_cache && $this->cacheExpired())){
ob_start();
header ("Content-type: " . $this->_mime);
if($this->_extension == "png" || $this->_extension == "gif"){
imagesavealpha($this->_image_resource, true);
}
switch ($this->_extension) {
case "jpg": imagejpeg($this->_image_resource, "", $quality); break;
case "gif": imagegif($this->_image_resource); break;
default: $this->showError('Failed to save image!'); break;
}
$content = ob_get_contents();
ob_end_clean();
}else{
if (isset ($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
if (strtotime ($_SERVER['HTTP_IF_MODIFIED_SINCE']) < strtotime('now')) {
header ('HTTP/1.1 304 Not Modified');
die ();
}
}
// change the modified headers
$gmdate_expires = gmdate ('D, d M Y H:i:s', strtotime ('now +10 days')) . ' GMT';
$gmdate_modified = gmdate ('D, d M Y H:i:s') . ' GMT';
header ("Content-type: " . $this->_mime);
header ('Accept-Ranges: bytes');
header ('Last-Modified: ' . $gmdate_modified);
header ('Cache-Control: max-age=864000, must-revalidate');
header ('Expires: ' . $gmdate_expires);
echo $this->cachedImage($name);
exit();
}
// Save image content
if(!empty($content) && $this->_cache){
$this->cacheImage($name, $content);
}
// Destroy image
$this->Destroy();
echo $content;
exit();
}
/* Destroy resources
* #access public, #return void */
public function Destroy(){
imagedestroy($this->_image_resource);
}
/* Get image resources
* #access public, #return resource */
public function GetResource(){
return $this->_image_resource;
}
/* Set image resources
* #access public, #param resource $image, #return resource */
public function SetResource($image){
$this->_image_resource = $image;
}
/* Enable caching
* #access public, #param string $folder, #param int $ttl, * #return void */
public function EnableCache($folder="/var/www/vhosts/blabla.org/httpdocs/tmp/", $ttl=345600){
if(!is_dir($folder)){
$this->showError("Directory '" . $folder . "' does'nt exist");
}else{
$this->_cache = true;
$this->_cache_folder = $folder;
$this->_cache_ttl = $ttl;
}
return false;
}
}
The original author granted me permission for placing parts of code in here for solving this issue.
If I'm understanding the question correctly, this is entirely to be expected. Image manipulation is slow.
The yellow is your browser sending the request. The green is your browser waiting on the server to actually create the thumbnail, which takes a very significant amount of time, no matter what library the server is using. The blue is the server sending the response, which, unlike the previous steps, is affected by filesize.
There's not much to be done about the inherent slowness of image manipulation. It would be wise to cache these thumbnails so that they are only generated once and are then served statically. That way, very few of your users will ever have to sit through that green delay, and your server will be happy, too.
EDIT: If the issue is that the files exist at those URLs, but your RewriteRule is kicking in anyway, bear in mind that, by default, rules run without checking if the file exists.
Use the following condition above your RewriteRule to make sure the file exists.
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule # ...etc...
imgcpu.php?src=foo/foo.jpg&w=100&h=100
so imgcpu.php is running for every image request?
In that case, if you are worried about performance,:
the script needs to do some caching of the thumbnails it creates. If it resizes stuff on every request, that's your problem right there.
the script needs to send some caching headers to the browser - a pure PHP script won't do that, and will be refreshed on every page load
a session_start() call inside the PHP script could lead to concurrency issues because of session locking.
you will need to show some PHP code. Maybe in a separate question, though.
Apache can serve up files from your hard disk a lot faster than PHP can, and it appears that you're doing the latter to handle caching:
/**
* Get an image from cache
*
* #access public
* #param string $name
* #return void
*/
private function cachedImage($name){
$file = $this->_cache_folder . $name;
$fh = fopen($file, 'r');
$content = fread($fh, filesize($file));
fclose($fh);
return $content;
}
There's a better way of doing what that function is doing (passthru), but the best option is to setup a regex that'll only rewrite a request to your thumbnailing script if the file doesn't already exist:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^images/.*$ - [NC,L]
RewriteRule ^images/(.*)$ /imgcpu.php/$1 [NC,L]
And then introduce logic to parse the request to an image and format it accordingly. For example, you could say thumbs should be named after the original file and have the W x H dimensions appended like "stackoverflow_logo_100x100.jpg".
Make sense?
Per request (in the comment), the description of the "s", "l", and "d" flags is as follows (quoting the docs):
'-d' (is directory) Treats the
TestString as a pathname and tests
whether or not it exists, and is a
directory.
'-s' (is
regular file, with size) Treats the
TestString as a pathname and tests
whether or not it exists, and is a
regular file with size greater than
zero.
'-l' (is symbolic link) Treats
the TestString as a pathname and tests
whether or not it exists, and is a
symbolic link.
Your checking your HTTP_IF_MODIFIED_SINCE header and cache AFTER you generate the image so the image is getting generated and cached every time you load the page. You would get a considerable decrease in time if you move these checks closer to the start of execution, before you start processing the image.
Matchu gave you answer why. If you want to fix it, save the created thumbnails, so they are not recreated on each request. I use simple 404 page that catches request to thumbnails that haven't been created, that script figures out the required dimensions and file from url - eg /thumbs/100x100/cat.png means create 100x100 thumbnail from /images/cat.png.

Categories