SNS에 글을 공유할 때 예쁜 썸네일이 나오는 경우와 나오지 않는 경우를 본 적 있을까. 대부분의 개발자는 Open Graph 메타태그만 넣고, 이미지를 하드코딩된 파일 경로로 고정해둔다. 하지만 게시글마다 다른 제목, 날짜, 카테고리 정보를 담은 썸네일을 동적으로 생성할 수 있다면 훨씬 더 프로페셔널한 SNS 공유 경험을 만들 수 있다. 이번에는 PHP의 imagecreatetruecolor 함수로 실시간 OG 이미지를 만드는 방법, 왜 필요한지, 어떻게 구현하는지 완벽하게 정리해서 소개하겠다.

 

1단계. 동적 OG 이미지 생성이 필요한 이유

정적 이미지를 사용하면 모든 게시글이 같은 썸네일을 표시한다. 검색 결과나 SNS 타임라인에서 사용자 입장에서는 무엇을 공유하는지 한눈에 알 수 없다. 동적 OG 이미지는 게시글 제목, 작성자, 날짜, 카테고리를 실시간으로 이미지에 합성해서 생성한다. 따라서 각 글마다 다른 썸네일이 표시되고, SNS 클릭률이 올라간다.
또 다른 장점은 이미지 저장소 용량을 절약한다는 것이다. 수천 개의 게시글마다 미리 이미지를 만들어 저장할 필요가 없다. 요청이 들어오는 순간 메모리에서 동적으로 생성하고 바로 응답하면 된다.

 

2단계. imagecreatetruecolor와 기본 함수들 이해하기

PHP의 GD 라이브러리는 이미지 생성과 조작을 위한 함수들을 제공한다. 가장 핵심은 imagecreatetruecolor 함수다. 이 함수는 지정한 가로/세로 크기의 빈 RGB 이미지를 메모리에 생성한다.

imagecreatetruecolor의 기본 문법

$image = imagecreatetruecolor(width: 1200, height: 630);

1200x630은 페이스북, 트위터 등 SNS의 표준 OG 이미지 사이즈다. 생성된 이미지는 검은색으로 채워진다. 여기에 배경색, 텍스트, 도형을 그려 넣는다.
다음은 자주 쓰는 함수들이다.

함수 역할 예시
imagefilledrectangle() 배경색 채우기 imagefilledrectangle($img, 0, 0, 1200, 630, $bgColor)
imagecolorallocate() 색상 RGB 값 정의 $white = imagecolorallocate($img, 255, 255, 255)
imagettftext() 폰트로 텍스트 그리기 imagettftext($img, 48, 0, 50, 100, $white, 'font.ttf', 'Hello')
imageline() 직선 그리기 imageline($img, 0, 200, 1200, 200, $gray)
imagepng() 이미지를 PNG로 출력 imagepng($img, 'path/to/file.png')

 

3단계. 실전 예제: 게시글 제목이 들어간 OG 이미지 생성

이제 실제 코드를 작성해보자. generate-og-image.php라는 파일을 만들어서, URL 파라미터로 받은 제목/카테고리/날짜를 이미지에 그려 넣는다.

✓ 올바른 코드: 동적 OG 이미지 생성

<?php
header('Content-Type: image/png');
header('Cache-Control: public, max-age=86400');

// URL 파라미터 받기
$title = isset($_GET['title']) ? htmlspecialchars($_GET['title']) : 'No Title';
$category = isset($_GET['category']) ? htmlspecialchars($_GET['category']) : 'Article';
$date = isset($_GET['date']) ? htmlspecialchars($_GET['date']) : date('Y-m-d');

// 이미지 생성 (1200x630, SNS 표준 사이즈)
$image = imagecreatetruecolor(1200, 630);

// 색상 정의
$bgColor = imagecolorallocate($image, 20, 120, 200);
$accentColor = imagecolorallocate($image, 255, 150, 0);
$textWhite = imagecolorallocate($image, 255, 255, 255);
$textGray = imagecolorallocate($image, 200, 200, 200);

// 배경색 채우기
imagefilledrectangle($image, 0, 0, 1200, 630, $bgColor);

// 상단 액센트 바 (카테고리 강조)
imagefilledrectangle($image, 0, 0, 1200, 80, $accentColor);

// 카테고리 텍스트 (상단)
imagettftext(
    $image,
    32,
    0,
    50,
    55,
    $textWhite,
    __DIR__ . '/fonts/bold.ttf',
    strtoupper($category)
);

// 제목 텍스트 (중앙, 여러 줄)
$title = wordwrap($title, 30, "\n", true); // 30글자 이상이면 줄바꿈
imagettftext(
    $image,
    48,
    0,
    50,
    200,
    $textWhite,
    __DIR__ . '/fonts/bold.ttf',
    $title
);

// 날짜 텍스트 (하단)
imagettftext(
    $image,
    24,
    0,
    50,
    580,
    $textGray,
    __DIR__ . '/fonts/regular.ttf',
    'Published on ' . $date
);

// 로고/브랜드 텍스트 (우측 하단)
imagettftext(
    $image,
    28,
    0,
    900,
    580,
    $textWhite,
    __DIR__ . '/fonts/bold.ttf',
    'REDINFO'
);

// PNG로 출력 (캐시 헤더 설정했으므로 브라우저가 24시간 캐시)
imagepng($image);

// 메모리 해제
imagedestroy($image);
?>

HTML에서 이미지 사용하기

<!-- 게시글 페이지 HEAD 영역 -->
<meta property="og:title" content="PHP imagecreatetruecolor 완벽 가이드" />
<meta property="og:description" content="동적 OG 이미지를 생성해서 SNS 공유를 최적화하자" />
<meta property="og:image" content="https://example.com/generate-og-image.php?title=PHP%20imagecreatetruecolor%20완벽%20가이드&category=개발&date=2024-01-15" />
<meta property="og:type" content="article" />

결과

위 코드를 실행하면 파란 배경 위에 주황색 카테고리 바, 중앙의 흰색 제목, 하단의 날짜와 로고가 들어간 깔끔한 이미지가 생성된다. URL 파라미터를 바꾸면 매번 다른 이미지가 만들어진다.

 

4단계. 주의사항과 흔한 실수

✗ 잘못된 코드: 폰트 파일 경로 오류

imagettftext($image, 48, 0, 50, 200, $textWhite, 'fonts/bold.ttf', $title);
// 상대경로로 폰트를 찾으면 스크립트 위치에 따라 다르게 동작한다

✓ 올바른 코드: 절대경로 사용

imagettftext(
    $image,
    48,
    0,
    50,
    200,
    $textWhite,
    __DIR__ . '/fonts/bold.ttf',  // 절대경로로 안정성 확보
    $title
);

폰트 파일은 반드시 서버에 업로드되어 있어야 한다. 일반 웹폰트(구글 폰트 등)는 imagettftext에서 직접 사용할 수 없다. 미리 .ttf 또는 .otf 파일을 다운로드해서 서버에 저장해야 한다.
또 다른 주의점은 텍스트 길이다. 제목이 너무 길면 이미지를 벗어난다. wordwrap() 함수로 자동으로 줄바꿈하거나, 글자 수를 제한해야 한다.

✗ 메모리 누수: imagedestroy() 빠뜨리기

imagepng($image);
// imagedestroy($image); // 이 줄을 빼먹으면 메모리 누수 발생

✓ 올바른 방법: 이미지 생성 후 항상 해제

imagepng($image);
imagedestroy($image); // 필수. 이미지 리소스 메모리 해제

요청이 들어올 때마다 imagecreatetruecolor로 메모리에 이미지를 생성한다. 사용 후 imagedestroy()로 메모리를 해제하지 않으면, PHP-FPM 프로세스 메모리가 계속 증가해서 결국 서버가 다운될 수 있다.

 

5단계. 캐싱으로 성능 최적화하기

매 요청마다 이미지를 생성하면 CPU 사용률이 올라간다. 같은 이미지가 여러 번 요청되는 경우, 미리 생성해서 파일로 저장해두는 것이 효율적이다.

✓ 파일 캐싱으로 최적화

<?php
header('Content-Type: image/png');

$title = isset($_GET['title']) ? htmlspecialchars($_GET['title']) : 'No Title';
$category = isset($_GET['category']) ? htmlspecialchars($_GET['category']) : 'Article';
$date = isset($_GET['date']) ? htmlspecialchars($_GET['date']) : date('Y-m-d');

// 캐시 파일명 생성 (파라미터 조합으로 해시)
$cacheKey = md5($title . $category . $date);
$cachePath = __DIR__ . '/cache/og-' . $cacheKey . '.png';
$cacheDir = __DIR__ . '/cache';

// 캐시 디렉토리 없으면 생성
if (!is_dir($cacheDir)) {
    mkdir($cacheDir, 0755, true);
}

// 캐시 파일이 있고 24시간 이내면 재사용
if (file_exists($cachePath) && (time() - filemtime($cachePath) < 86400)) {
    header('Content-Length: ' . filesize($cachePath));
    readfile($cachePath);
    exit;
}

// 캐시가 없으면 새로 생성
$image = imagecreatetruecolor(1200, 630);

// ... (위의 색상 정의, 텍스트 그리기 코드 동일) ...

$bgColor = imagecolorallocate($image, 20, 120, 200);
$accentColor = imagecolorallocate($image, 255, 150, 0);
$textWhite = imagecolorallocate($image, 255, 255, 255);
$textGray = imagecolorallocate($image, 200, 200, 200);

imagefilledrectangle($image, 0, 0, 1200, 630, $bgColor);
imagefilledrectangle($image, 0, 0, 1200, 80, $accentColor);

imagettftext($image, 32, 0, 50, 55, $textWhite, __DIR__ . '/fonts/bold.ttf', strtoupper($category));
imagettftext($image, 48, 0, 50, 200, $textWhite, __DIR__ . '/fonts/bold.ttf', wordwrap($title, 30, "\n", true));
imagettftext($image, 24, 0, 50, 580, $textGray, __DIR__ . '/fonts/regular.ttf', 'Published on ' . $date);
imagettftext($image, 28, 0, 900, 580, $textWhite, __DIR__ . '/fonts/bold.ttf', 'REDINFO');

// 캐시 파일로 저장
imagepng($image, $cachePath);

// 헤더 설정 후 파일 출력
header('Content-Length: ' . filesize($cachePath));
header('Cache-Control: public, max-age=86400');
readfile($cachePath);

imagedestroy($image);
?>

이제 첫 요청 시에만 이미지를 생성하고, 이후 24시간 동안은 디스크에서 읽어서 응답한다. CPU 부하가 대폭 줄어든다.

 

6단계. URL 파라미터 보안: 악의적 입력 방지

URL 파라미터로 받은 텍스트를 그대로 이미지에 그려 넣으면, 공격자가 매우 긴 텍스트를 보내서 이미지를 깨트릴 수 있다. 또한 글자 수가 많으면 imagettftext의 $y 좌표가 이미지 영역을 벗어날 수 있다.

✓ 텍스트 길이 제한 및 검증

<?php
function sanitizeText($text, $maxLength = 100) {
    // HTML 엔티티로 변환
    $text = htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    
    // 길이 초과 시 자르기
    if (mb_strlen($text) > $maxLength) {
        $text = mb_substr($text, 0, $maxLength - 3) . '...';
    }
    
    return $text;
}

$title = sanitizeText($_GET['title'] ?? 'No Title', 60);
$category = sanitizeText($_GET['category'] ?? 'Article', 30);
?>

이제 제목은 최대 60글자, 카테고리는 최대 30글자로 제한된다. 그보다 긴 텍스트를 보내면 자동으로 잘린다.

 

마무리: 동적 OG 이미지로 SNS 공유 극대화하기

동적 OG 이미지 생성은 단순한 기능이 아니라 마케팅 전략이다. 각 게시글마다 고유한 썸네일을 표시하면, 검색 결과와 SNS 타임라인에서 사용자의 클릭률이 올라간다. 이 글에서 배운 imagecreatetruecolor 함수, wordwrap으로 텍스트 줄바꿈하기, 파일 캐싱으로 성능 최적화하기는 모두 실무에서 바로 쓸 수 있는 기법이다. 특히 캐싱을 설정하면 서버 부하를 획기적으로 줄일 수 있으므로 필수다. 지금 당신의 블로그나 뉴스 사이트에 이 기능을 적용하면, SNS 공유 수가 눈에 띄게 증가할 것이다.