개발
php gd 라이브러리를 이용한 이미지 리사이징 (webp)
50 views as of .
거의 5년전에 php 이미지 용량 리사이징이라는 개발 글을 쓴적이 있다.
해당 글에서는 순수 php 코드를 이용해서 간단히 용량 리사이징을 하는 법을 알아보았다.
5년전과 지금의 이미지 서비스 환경
그 이후로 여러 웹 관련 작업을 진행하면서 경험을 얻기도했고, 웹 환경자체도 약간 변했다.
예전엔 단순히 이미지 품질과 다운스케일링을 통해 이미지 최적화 작업을 진행했지만 요즘들어서는 이미지 확장자 자체를 압축률이 좋은것으로 변환해서 사용하기도 한다.
특히 이 분야에선 webp 파일이 특출나다.
webp는 기존 jpg, png에 비해서 30퍼이상 용량을 줄일 수 있고 더불어 png 처럼 알파 채널을 지원해 투명한 배경을 사용할 수 도 있다.
이 webp가 5년전에도 없던거는 아니었지만 대부분의 웹사이트가 지원하지 않았고 브라우저나 디바이스도 일부 지원느낌으로 미적지근한 서비스를 보였고, 사용자또한 고전적인 jpg나 png, gif 가 익숙했던 시기여서 해당 기술을 이용한 이미지 리사이징은 너무 빠른 기술이었다.
하지만 지금와서는 webp는 많이 친숙한 이미지 확장자이고 커뮤니티나 위키사이트들도 webp를 이용한 이미지 서비스를 지원하며 웹 트래픽을 제어하는 상황이니까 개발자 입장에서도 이런 webp를 다루는 법도 필요하다고 생각이 들어서 이렇게 이미지 리사이징에 대해 다시금 정보를 정리해보자 한다.
php gd 라이브러리 설치
php에서 이미지를 제어하기 위해서는 php-gd 라이브러리가 필수로 설치되어있어야하며 이는
phpinfo()
등의 방법으로 쉽게 확인할 수 있다.설치가 안되어있다면 터미널에서 아래 명령어로 설치하면 될것이다.
Linux
sudo apt update
sudo apt install php-gd
Copy
Unix
; <- 주석 해제
extension=gd
Copy
MacOS
brew install gd
brew services restart php
Copy
최신 php는 거의 gd 라이브러리가 활성화 되어있을것이다.
가끔 구버전 php-gd는 webp가 기본적으로 지원이 안되는 경우가 있는데 그럴경우에는 livwebp-dev 라이브러리를 추가 설치해주면 된다.
# Ubuntu/Debian
sudo apt update
sudo apt install libwebp-dev
sudo apt install php-gd
#CentOS/RHEL
sudo yum install libwebp-devel
sudo yum install php-gd
sudo systemctl restart php-fpm
sudo systemctl restart httpd
Copy
이후 php 컴파일 시 GD webp 옵션을 아래와 같이 활성화 시켜주면된다.
./configure --with-gd --with-webp --with-jpeg --with-png --with-zlib
make
sudo make install
Copy
만약에 도커를 이용해 php를 컴파일해 사용한다면 아래와 같이 도커 옵션에 추가해주면된다.
# php7.3 까지
docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ --with-webp-dir=/usr/include/
# php7.4 부터
docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp
Copy
환경에따라 적용하는 방법이 다르니 이 부분은 추가적인 검색을 통해 본인에게 맞는 방식으로 적용하면 좋을것 같다.
업로드시 webp 형식으로 이미지 다운 스케일링
일반적으로 사용자가 폼을 통해서 업로드한 이미지를 다운스케일링해 병행으로 저장해 원본이미지와 섬네일 두가지를 관리하는 기법이 일반적이다.
그래야 무손실 원본이미지도 경우에 따라 서비스 할 수 있고, 웹 서비스 최적화를 위한 이미지 리사이징도 할 수 있기 때문이다.
public function uploadFile(Request $request)
{
$file = $request->file('file');
$uploadPath = 'editor';
$originFilePath = $file->store($uploadPath, 's3'); // S3에 파일 업로드
if ($originFilePath) {
$fileSize = $file->getSize(); // 파일 크기 (바이트 단위)
$imagePath = $file->getPathname();
$imageInfo = getimagesize($imagePath);
$mime = $imageInfo['mime'];
$width = $imageInfo[0]; // 이미지의 가로 길이 (픽셀 단위)
// 원본 파일명을 기반으로 최적화된 파일명 생성
$originFileName = pathinfo($originFilePath, PATHINFO_FILENAME);
$originFileExtension = pathinfo($originFilePath, PATHINFO_EXTENSION); // 원파일 확장자는 url 뒤에 붙여 반환
// 파일 크기가 0.05MB 이상일 경우 최적화 수행
if ($fileSize > 0.05 * 1024 * 1024) {
$height = $imageInfo[1];
$image = imagecreatefromstring(file_get_contents($imagePath));
if ($width > 600) {
// 가로가 600px 초과인 경우, 900px 변환작업 (1.5 배수)
$newWidth = 900;
$newHeight = floor($height * ($newWidth / $width));
} else {
$newWidth = $width;
$newHeight = $height;
}
// 최적화된 이미지 생성
$tmpImage = imagecreatetruecolor($newWidth, $newHeight); // 임시 이미지 생성
imagecopyresampled($tmpImage, $image, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height); // 이미지 리사이즈
ob_start();
/*
if ($mime == 'image/webp') {
// WebP 형식으로 저장
imagewebp($tmpImage, null, 80); // 품질 80
} else {
// JPEG 형식으로 저장
imagejpeg($tmpImage, null, 80); // 품질 80
}
*/
// 무조건 이미지를 webp로 저장
imagewebp($tmpImage, null, 50);
$optimizedImage = ob_get_clean(); // 출력된 이미지를 변수에 저장
imagedestroy($image); // 원본 이미지 메모리 해제
imagedestroy($tmpImage); // 임시 이미지 메모리 해제
// 최적화된 파일을 임시 경로에 저장
$tmpFilePath = tempnam(sys_get_temp_dir(), 'optimized_image');
file_put_contents($tmpFilePath, $optimizedImage);
// 확장자 변경 처리
$optimizedFileExtension = 'webp'; // webp로 확장자 변경
$optimizedFileName = $originFileName . '_optimized.' . $optimizedFileExtension;
$optimizedFilePath = $uploadPath . '/' . $optimizedFileName;
// 최적화된 파일을 S3에 업로드
Storage::disk('s3')->put($optimizedFilePath, file_get_contents($tmpFilePath));
// 임시 파일 삭제
unlink($tmpFilePath);
}
$result = array('path' => $originFilePath);
}
}
return json_encode($result);
Copy
나같은 경우는 라라벨 프레임워크의
Storage
클래스를 이용해 S3에 파일 업로드를 하는데, 이부분은 각자의 웹 애플리케이션 방식에 맞는 저장방법으로 변경해주면 될것 같다.위 코드는 원본파일을 먼저 서버에 저장해두고, 그 원본파일에서 파일 사이즈, 이미지 크기등의 정보를 불러온 후 상황에 맞는 작업을 하는 코드이다.
너무 작은 파일의 경우에는 리사이징을 할 필요가없고 나는 이 기준을 50KB로 잡았다.
위 코드는 파일크기가 50KB 이상일때 이미지 변환 작업을 실시하며 이미지 가로 크기가 600보다 크면 600에 맞춰서 이미지를 다운 스케일링한다.
그러나 다운스케일링크기를 1.5배수로 지정해 실제 랜더링될때 이미지가 더 선명하게 보이는 작은 트릭을 사용했다.
해당 기능이 필요없다면 똑같이 600으로 설정해도좋고, 섬네일 크기가 더 작아도된다면 기준점을 더 작게 300으로 설정해도 된다.
요는 이미지 크기가 작고 품질이 낮을수록 리사이징된 이미지의 크기가 비약적으로 감소한다는 것이다.
imagecreatetruecolor
함수와 imagecopyresampled
함수를 이용해서 이미지의 색상과 배지 크기를 설정하고 imagewebp 함수를 통해 webp 파일을 생성한다.그리고 이 파일을 적당히 임시 이름을 붙여
file_put_contents
함수를 이용해 로컬위치에 파일을 임시로 저장한다.그리고 필요에 따라 저장할 위치나 웹서버(S3)등으로 파일을 업로드하고 임시파일을 삭제처리하면 끝난다.
사실상 핵심코드는
imagecreatetruecolor
, imagecopyresampled
, imagewebp
이 3개에 불과하고 나머지는 파일을 다루는 추가적인 로직이라고 보면 될것같다.파일 리사이징 결과
실제로 webp 방식으로 품질 50으로 리사이징하면 아래와 같은 결과가 나오게 된다.
파일명 | 파일 크기 | 이미지 해상도 | |
---|---|---|---|
원본 파일 | 8AbppsPBYW...0m6RyS0wI.jpg | 362,425 Byte | 1920x1080 |
리사이징 파일 | 8AbppsPBYW...0m6RyS0wI_optimized.webp | 31,116 Byte | 900x506 |
정말 어마어마한 수치로 이미지의 용량이 줄어듦을 확인할 수 있다.
그에비해서 실제 이미지는 거의 비슷한 수준으로 보이는것임을 확인할 수 있다.
위 이미지는 지금 가로 600픽셀 기준으로 랜더링 되지만 실제 스케일링된 이미지는 1.5배수로 리사이징한 900픽셀짜리 이미지이다.
브라우저는 이렇게 1.5배수 2배수처럼 0.5배수에 해당하는 이미지가 주어질때 이를 자글거림없이 줄여서 보여주는데, 이를 이용해 이미지 변환품질을 극단적으로 낮춰도 어느정도 사람눈에는 그럴듯한 이미지를 보여줄 수있다.
결론
실제로 구글 페이지스피드 인사이트에서 페이지를 로딩할때 속도와 UX를 위해 권장하는 리소스의 총합의 크기는 200~500KB이다.
위와같은 이미지를 그대로 쓰면 300KB로 이미지만하더라도 로드 트래픽이 넘쳐나게되니까 이런 수치가 얼마나 위협적인지는 쉽게 파악할 수 있다.
점차 인터넷 속도가 빨라지고 웹사이트가 뜨는데 답답합을 느끼는 지금 시기에서는 최대한 리소스 이미지를 줄이기위한 우리들의 노력이 필요한 시기라고 생각한다.
끝!
#php
0
개의 댓글
개발 카테고리의 다른 글
01/23
Nginx gzip 적용하는 법 (웹 리소스 압축)
웹페이지를 최적화 하는방법은 당장 웹사이트에 걸리는 여러 리소스를 압축하거나 제거, 필요할때 로딩하는 방식으로 줄일 수 있...
01/14
페이지네이션과 로드 모어(Load More)를 동시 구현해보자
우리가 일반적으로 여러 데이터 레코드를 보기좋게 구현하기 위해서는 테이블 구조를 이용하곤한다. 테이블 구조는 행과 열로 구...
01/10
S3 데이터 가져와서 테이블로 관리하기
S3 서버는 아마존 웹 서비스에서 이용할 수 있는 스토리지 저장 서비스이다.실제로 대용량의 파일을 저장해도 과금이 별로 안되...