웹 서비스 운영 중 상품 정보나 이벤트 배너를 매번 디자이너에게 요청해서 받는 게 비효율적이라고 생각해봤을까? 또는 사용자마다 다른 텍스트가 들어가야 하는 동적 이미지가 필요한데 서버에서 직접 생성할 수 있는 방법을 모르는 경우가 많다. 이번에는 PHP의 GD 라이브러리를 활용해 프로그래밍으로 이미지를 동적 생성하는 방법을 정확하게 정리해서 소개하겠다.
PHP에서 이미지를 동적으로 생성하려면 GD(Graphics Draw) 라이브러리가 필요하다. 대부분의 웹 호스팅 환경에는 이미 설치되어 있지만, 혹시 모르니 먼저 확인하자.
<?php
phpinfo();
?>
위 코드를 실행했을 때 GD section이 나타나면 준비가 된 것이다. imagecreatetruecolor()는 지정한 크기의 투명한 캔버스(이미지 리소스)를 생성하는 함수다. 인자는 가로, 세로 픽셀값이다.
// ✓ 올바른 방법: 가로 800px, 세로 400px 캔버스 생성
$image = imagecreatetruecolor(800, 400);
// ✓ 올바른 방법: 캔버스가 제대로 생성되었는지 체크
if ($image === false) {
die('이미지 생성 실패');
}
echo '캔버스 생성 완료';
?>
imagecreatetruecolor()는 리소스(resource) 타입을 반환한다. 이 리소스는 이후 모든 그림 그리기 함수의 첫 번째 인자로 전달된다.
캔버스를 만들었으면 색상을 정의하고 배경을 채워야 한다. imagecolorallocate()로 RGB 색상을 할당하고, imagefilledrectangle()로 사각형 영역을 채운다.
<?php
$image = imagecreatetruecolor(800, 400);
// ✓ 올바른 방법: RGB 색상 할당 (R, G, B는 각각 0~255)
$white = imagecolorallocate($image, 255, 255, 255);
$blue = imagecolorallocate($image, 52, 152, 219);
$black = imagecolorallocate($image, 0, 0, 0);
// ✓ 올바른 방법: 흰색 배경으로 전체 채우기
// imagefilledrectangle(이미지, x1, y1, x2, y2, 색상)
imagefilledrectangle($image, 0, 0, 800, 400, $white);
// ✓ 올바른 방법: 파란색 헤더 영역 추가 (상단 100px)
imagefilledrectangle($image, 0, 0, 800, 100, $blue);
echo '배경 설정 완료';
?>
imagefilledrectangle()의 인자 순서를 정확히 알아야 한다. (이미지, 좌상단x, 좌상단y, 우하단x, 우하단y, 색상) 인자 순서를 헷갈리면 원하지 않는 영역이 채워진다.
이제 텍스트를 추가해보자. imagettftext()를 사용하면 트루타입 폰트로 텍스트를 그릴 수 있다.
<?php
header('Content-Type: image/png');
$image = imagecreatetruecolor(800, 400);
$white = imagecolorallocate($image, 255, 255, 255);
$blue = imagecolorallocate($image, 52, 152, 219);
$black = imagecolorallocate($image, 0, 0, 0);
imagefilledrectangle($image, 0, 0, 800, 400, $white);
imagefilledrectangle($image, 0, 0, 800, 100, $blue);
// ✓ 올바른 방법: TTF 폰트를 사용해 텍스트 추가
// 서버의 폰트 파일 경로가 정확해야 함
$fontFile = '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf';
$fontSize = 48;
$x = 50;
$y = 70;
$text = '동적 배너 생성';
imagettftext($image, $fontSize, 0, $x, $y, $black, $fontFile, $text);
// ✓ 올바른 방법: 이미지를 PNG 형식으로 브라우저에 전송
imagepng($image);
// ✓ 올바른 방법: 메모리 해제
imagedestroy($image);
?>
imagettftext()에서 주의할 점은 폰트 파일의 경로가 정확해야 한다는 것이다. 서버에 존재하지 않는 폰트 경로를 사용하면 에러가 발생한다. 리눅스 서버의 경우 일반적으로 /usr/share/fonts/ 디렉토리에 시스템 폰트가 저장되어 있다.
이제 쿼리 파라미터로 텍스트를 받아 동적으로 배너를 생성해보자.
<?php
header('Content-Type: image/png');
// ✓ 올바른 방법: 사용자 입력값 받기 (XSS 방지)
$title = isset($_GET['title']) ? htmlspecialchars($_GET['title']) : '기본 제목';
$subtitle = isset($_GET['subtitle']) ? htmlspecialchars($_GET['subtitle']) : '';
// ✓ 올바른 방법: 텍스트 길이 제한 (무한 입력 방지)
if (strlen($title) > 50) {
$title = substr($title, 0, 50);
}
$image = imagecreatetruecolor(800, 400);
// 색상 정의
$white = imagecolorallocate($image, 255, 255, 255);
$darkBlue = imagecolorallocate($image, 25, 25, 112);
$lightGray = imagecolorallocate($image, 240, 240, 240);
$white_text = imagecolorallocate($image, 255, 255, 255);
// 배경 그리기
imagefilledrectangle($image, 0, 0, 800, 400, $lightGray);
imagefilledrectangle($image, 0, 0, 800, 150, $darkBlue);
// 폰트 경로
$boldFont = '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf';
$normalFont = '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf';
// 제목 추가
if (file_exists($boldFont)) {
imagettftext($image, 48, 0, 50, 100, $white_text, $boldFont, $title);
}
// 부제목 추가
if (!empty($subtitle) && file_exists($normalFont)) {
imagettftext($image, 28, 0, 50, 200, $darkBlue, $normalFont, $subtitle);
}
// ✓ 올바른 방법: 이미지 출력 및 메모리 해제
imagepng($image);
imagedestroy($image);
?>
위 파일을 banner.php로 저장하고 다음과 같이 호출하면 된다:
<img src="banner.php?title=2024년%20신제품&subtitle=지금%20예약하세요" alt="배너">
✗ 잘못된 코드: 폰트 파일 경로를 하드코딩하고 존재 확인 없이 사용
<?php
header('Content-Type: image/png');
$image = imagecreatetruecolor(800, 400);
$black = imagecolorallocate($image, 0, 0, 0);
imagefilledrectangle($image, 0, 0, 800, 400, $black);
// ✗ 위험: 서버에 없는 폰트 경로를 사용하면 경고 에러 발생
imagettftext($image, 40, 0, 50, 100, $black, '/fonts/arial.ttf', 'Test');
imagepng($image);
imagedestroy($image);
?>
✓ 올바른 코드: 폰트 파일 존재 확인 후 사용
<?php
header('Content-Type: image/png');
$image = imagecreatetruecolor(800, 400);
$black = imagecolorallocate($image, 0, 0, 0);
$white = imagecolorallocate($image, 255, 255, 255);
imagefilledrectangle($image, 0, 0, 800, 400, $white);
// ✓ 올바른: 먼저 폰트 파일이 있는지 확인
$fontFile = '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf';
if (file_exists($fontFile)) {
imagettftext($image, 40, 0, 50, 100, $black, $fontFile, 'Test');
} else {
// 폰트가 없으면 기본 내장 폰트 사용 (더 제한적이지만 항상 작동)
imagestring($image, 5, 50, 100, 'Test', $black);
}
imagepng($image);
imagedestroy($image);
?>
✗ 잘못된 코드: 메모리 해제 없이 여러 번 호출
<?php
// ✗ 위험: 이미지를 여러 번 생성할 때 destroy()를 하지 않으면 메모리 누수
for ($i = 0; $i < 100; $i++) {
$image = imagecreatetruecolor(800, 400);
$color = imagecolorallocate($image, 255, 0, 0);
imagefilledrectangle($image, 0, 0, 800, 400, $color);
// imagedestroy($image); 가 없음!
}
?>
✓ 올바른 코드: 항상 destroy()로 메모리 해제
<?php
// ✓ 올바른: 항상 destroy로 메모리 정리
for ($i = 0; $i < 100; $i++) {
$image = imagecreatetruecolor(800, 400);
$color = imagecolorallocate($image, 255, 0, 0);
imagefilledrectangle($image, 0, 0, 800, 400, $color);
imagedestroy($image); // 필수!
}
?>
✗ 잘못된 코드: 사용자 입력값을 검증 없이 사용
<?php
header('Content-Type: image/png');
$title = $_GET['title']; // ✗ 위험: 길이 확인 없음
$image = imagecreatetruecolor(800, 400);
$black = imagecolorallocate($image, 0, 0, 0);
$white = imagecolorallocate($image, 255, 255, 255);
imagefilledrectangle($image, 0, 0, 800, 400, $white);
// 너무 긴 텍스트면 이미지 영역을 벗어남
imagettftext($image, 40, 0, 50, 100, $black, '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', $title);
imagepng($image);
imagedestroy($image);
?>
✓ 올바른 코드: 입력값 검증과 길이 제한
<?php
header('Content-Type: image/png');
// ✓ 올바른: 기본값 설정 및 길이 제한
$title = isset($_GET['title']) ? htmlspecialchars($_GET['title']) : 'Default';
if (strlen($title) > 40) {
$title = substr($title, 0, 40);
}
$image = imagecreatetruecolor(800, 400);
$black = imagecolorallocate($image, 0, 0, 0);
$white = imagecolorallocate($image, 255, 255, 255);
imagefilledrectangle($image, 0, 0, 800, 400, $white);
imagettftext($image, 40, 0, 50, 100, $black, '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', $title);
imagepng($image);
imagedestroy($image);
?>
PHP의 GD 라이브러리로 동적 이미지 생성은 생각보다 간단하지만 메모리 관리와 입력값 검증이 매우 중요하다. 작은 배너 하나도 여러 번 생성되면 서버 리소스를 빠르게 소진할 수 있기 때문이다. 이 글의 "5단계 주의사항" 부분을 참고해 사용자 입력값을 반드시 검증하고, 반복 생성 시 destroy()를 빼먹지 않으면, 안정적인 동적 이미지 생성 시스템을 만들 수 있을 것이다. 실제로 상품 썸네일, 이벤트 배너, SNS 공유 이미지 같은 곳에 바로 적용해보자.