웹에서 서명 패드나 그림판, 화면 캡처 같은 기능을 만들면 결과물이 보통 canvas.toDataURL() 로 나옵니다. 이 값은 파일이 아니라 data:image/png;base64,iVBORw0KG... 처럼 생긴 긴 문자열이라, 일반 파일 업로드($_FILES)로는 받을 수 없습니다. 문자열 그대로 POST로 보낸 다음 서버에서 base64를 풀어 파일로 써 주면 됩니다.
서버 쪽 코드와 테스트 폼
<?
if(isset($_POST['submit'])){
define('UPLOAD_DIR', 'img/');
$img = $_POST['image'];
$img = str_replace('data:image/png;base64,', '', $img);
$img = str_replace(' ', '+', $img);
$data = base64_decode($img);
$file = UPLOAD_DIR . uniqid() . '.png';
$success = file_put_contents($file, $data);
print $success ? $file : 'Unable to save the file.';
}
?>
<form method="post" name="change" enctype="multipart-form/data">
<!-- <img src="" id="img" name="image" > -->
<input type="text" name="image" id="img" hidden="hidden" value="base64Code">
<input type="submit" name="submit">
</form>
폼의 숨겨진 image 입력란 value 에 실제 base64 문자열을 넣어서 제출하는 구조입니다. 테스트할 때는 base64Code 자리에 데이터 URL을 붙여 넣으면 되고, 실제로는 자바스크립트에서 document.getElementById('img').value = canvas.toDataURL('image/png'); 처럼 값을 채운 뒤 제출하면 됩니다.
한 줄씩 하는 일
먼저 data:image/png;base64, 접두어를 떼어 냅니다. 이게 남아 있으면 base64_decode 결과가 깨진 데이터가 됩니다. 다음 줄의 공백을 + 로 바꾸는 처리가 은근히 중요한데, base64 문자열에는 + 가 자주 들어가고, 이 값을 인코딩 없이 URL 쿼리나 application/x-www-form-urlencoded 본문에 넣어 보내면 서버에서 + 가 공백으로 해석됩니다. 그 상태로 디코딩하면 이미지가 깨지거나 일부만 보이니, 공백을 다시 + 로 돌려놓는 겁니다.
파일 이름은 uniqid() 로 만들어서 겹치지 않게 하고, file_put_contents 가 성공하면 저장된 경로를, 실패하면 오류 문구를 출력합니다. img/ 폴더는 미리 만들어 두고 웹 서버 계정이 쓸 수 있는 권한을 줘야 합니다. 폴더가 없으면 여기서 실패합니다.
실제로 쓸 때 손볼 부분
폼의 enctype 이 multipart-form/data 로 되어 있는데, 올바른 값은 multipart/form-data 입니다. 브라우저가 모르는 값이라 무시하고 기본 방식으로 보내기 때문에 이 예제는 그냥 동작하지만, 나중에 파일 입력란을 같이 넣을 생각이라면 고쳐 두세요.
검증이 전혀 없다는 점도 기억해야 합니다. 받은 문자열이 정말 PNG인지 확인하지 않고 그대로 .png 로 저장하니, 공개된 페이지에 올린다면 base64_decode($img, true) 로 엄격하게 디코딩해서 실패 여부를 보고, getimagesizefromstring($data) 로 이미지가 맞는지 확인한 다음 저장하는 게 안전합니다. 크기 제한도 필요합니다. base64는 원본보다 3분의 1 정도 커지기 때문에 큰 캔버스는 post_max_size 에 걸릴 수 있습니다.
JPEG로 받는다면 접두어가 data:image/jpeg;base64, 로 바뀌니, 특정 형식 문자열을 하드코딩하기보다 쉼표 위치를 찾아 그 뒤만 잘라 내는 쪽이 여러 형식에 두루 쓸 수 있습니다. 그리고 <? 짧은 여는 태그는 short_open_tag 가 꺼진 서버에서는 PHP 코드로 인식되지 않으니 <?php 로 쓰는 게 안전합니다.