본문 바로가기

Web/PHP

text2png: 순수 php만으로 구현한 png 생성 소스

<?php
# TokigunStudio Text2PNG by Kang Seonghoon <tokigun@gmail.com>

error_reporting(0);
extract($_GET);

$DATA = array(
    '',
    '',
    'pJhD50QC4Qio',
    'VFEYhhSqXEc4Xzfc7j4Z4/z/z4e8Yx4nPHPH/hjEYx/IRBIBkLDCSA',
    'VFPkiRScIMWRiaCAxiBQCgjIoRQghChlGPFFFFFAkMYjGIYiUgGQEMJI',
    'T+koSIaiFEgQqhAUYpBjAzlKMEMIQQoaRVpgxgxQJDGIqUKIoned7u/3l7ed59fxjJjHySA',
    'RSjIMoSCFEgjS94TopGP4YqSl6CH34P0OIq0wYwYnEhUqiKJEYIYxGMjG0yYxjHRIxkqiJJGIA',
    'RRwTSof/USED8MSReAgAAUpKUYIYQnhQ4iTLB9B8CSFSqIQkRfGI/kY3GTGMYzJFVUiixHJA',
    'X4oUxIRCiRARBiSIQDH8MlJ/jBDCEMKGkSZYMIMgSQqVSiIiMYxGEjG0yYxjGHIqqkUhJIw',
    'CgokxoRCSiSERRiiIkgxgJ9DFCKEEYUMogxooIogkhIRIkhCYxiMZGNlkxjGMGRIiUSCSA',
    'SjwjOUhCUc77hOchzEgKBEBD4/z8D4f0fgxnIHIfEeIRIk/CX+d7o+Njkxd585vIiiTySA',
    'IkojwEhIWAUEkA',
    'IZJAf/7qAUCo-',
    ''
);
$INDEX =
    'kMMOucMW?GM]MWu_?C?CgoMkCOcsQG?CMSMSMSMSMSMSMSMSMSMSMwA?Mk]K'.
    'MkMQG?MUMSMUMUMSMSMUMUMOMQMSMQMWMUMWMS?_MUMSMSMUMSM[MSMSMS?C'.
    'E_?CiGEQ}CM{M?MyM?M{K{ACM?MK?UM?KwO?M{M{ACACMwMyMOM{M{O?M{AC'.
    'My?E?C?E]K';

function pngchunk($type, $data) {
    return
        pack('N', strlen($data)).
        $type.$data.
        pack('N', crc32($type.$data));
}

$data = array();
for($i=0; $i<14; $i++) {
    $str = base64_decode($DATA[$i]);
    for($j=0; $j<strlen($str); $j++)
        $data[$i] .= sprintf('%08b', ord($str{$j}));
}

$fontarr = array();
$dataptr = array();
for($i=0; $i<190; $i+=2) {
    $value = (((ord($INDEX{$i})-63) >> 1) << 5) +
             ((ord($INDEX{$i+1})-63) >> 1);
    $uomit = ($value / 10) % 11 + 2;
    $lomit = ($value / 110) % 10 + 1;
    $width = $value % 10 + 1;
    $omitstr = str_repeat('0', $width);
    $font = array();
    for($j=0; $j<14; $j++)
        if($j<$uomit || 13-$lomit<$j) {
            $font[$j] = $omitstr;
        } else {
            $font[$j] = substr($data[$j], $dataptr[$j], $width);
            $dataptr[$j] += $width;
        }
    $fontarr[] = $font;
}

if(get_magic_quotes_gpc())
    $t = stripslashes($t);
switch($e) {
    case 'base64': $t = base64_decode($t); break;
    case 'hex': $t = pack('H*', $t); break;
    case 'rot13': $t = str_rot13($t); break;
    case 'reverse': $t = strrev($t); break;
    default: break;
}
$t = strtr($t, array("\r\n" => "\n", "\r" => "\n"));

$height = $width = 0;
$bitmap = array();
for($i=0; $i<strlen($t); $i++) {
    $char = ord($t{$i});
    if($char == 10) {
        $height += 14;
    } else {
        for($j=0; $j<14; $j++) {
            if(isset($fontarr[$char - 32])) {
                $line = $fontarr[$char - 32][$j] . '0';
            } elseif($j == 0 || $j == 13) {
                $line = '00000000';
            } elseif($j == 1 || $j == 12) {
                $line = '11111110';
            } else {
                $line = '10000010';
            }
            $bitmap[$height + $j] .= $line;
        }
    }
}

$height += 14;
for($i=0; $i<$height; $i++) {
    $rowwidth = strlen($bitmap[$i]);
    if($width < $rowwidth) $width = $rowwidth;
}

$stream = '';
for($i=0; $i<$height; $i++) {
    $char = 0;
    $stream .= "\0";
    for($j=0; $j<$width; $j++) {
        $char = $char * 2 + $bitmap[$i]{$j};
        if($j%8 == 7) {
            $stream .= chr($char);
            $char = 0;
        }
    }
    if($width%8 > 0) {
        $char <<= 8 - $width%8;
        $stream .= chr($char);
    }
}

$adler1 = 1; $adler2 = 0;
$result = '';
$length = strlen($stream);
for($i=0; $i<$length; $i+=4096) {
    $block = substr($stream, $i, 4096);
    if($i+4096 < $length) {
        $result .= '010';
    } else {
        $result .= '110';
    }
    
    for($j=0; $j<strlen($block); $j++) {
        $char = ord($block{$j});
        $adler1 += $char;
        $adler2 += $adler1;
        if($char < 144) {
            $code = 0x30 + $char;
            $codelen = 8;
        } else {
            $code = 0x100 + $char;
            $codelen = 9;
        }
        for($k=$codelen-1; $k>=0; $k--) {
            $result .= ($code >> $k) & 1 ? '1' : '0';
        }
    }
    
    $adler1 %= 65521;
    $adler2 %= 65521;
    $result .= '0000000';
}

$length = strlen($result);
$chunk = '';
for($i=0; $i<$length; ) {
    $char = 0;
    for($j=0; $i<$length && $j<8; $i++, $j++) {
        $char |= $result{$i} << $j;
    }
    $chunk .= chr($char);
}
$chunk = "\x78\x01" . $chunk . pack('nn', $adler2, $adler1);

header("Content-Type: image/png");
if(!$b) $b = '255,255,255';
$b = split(',', "$b,0");
if(!$f) $f = '0,0,0';
$f = split(',', "$f,255");

echo "\x89PNG\r\n\x1a\n";
echo pngchunk('IHDR', pack('NNCCCCC', $width, $height, 1, 3, 0, 0, 0));
echo pngchunk('PLTE', pack('CCCCCC', $b[0], $b[1], $b[2], $f[0], $f[1], $f[2]));
echo pngchunk('tRNS', pack('CC', $b[3], $f[3]));
echo pngchunk('IDAT', $chunk);
echo pngchunk('IEND', '');
?>


안녕하세요, 토끼군입니다.

다음 소스(text2png.php)는 입력받은 텍스트를 png로 변환해서 출력해 주는 프로그램입니다. 예를 들어서 text2png.php?t=dG9raWd1bkBnbWFpbC5jb20&e=base64&f=60,44,96라고 하면 제 메일 주소가 나옵니다. 자세한 설명은 링크#1에 올렸으니 거기서 확인해 주세요. :)




....코드를 올리려 했으나 칸이 안 맞아서 결국 포기....
링크#1에서 소스를 확인해 주시길 바랍니다.
(혹은 http://pandora.sapzil.info/dev/obfuscation/file/text2png.php.txt)





음... 가로 80칸, 세로 31줄로 정리(....)했습니다. -_-;;;; 도대체 이게 뭔 코드냐 하실 분들께서는 링크#2에 올라 와 있는 원본 소스를 참고하세요. (사실은 다 만들고 나서 다시 풀어 헤친 겁니다만... ;;;)

- 토끼군

예상되는 질문들이 있어서 미리 FAQ 올려 놓겠습니다.

Q: 왜 저런 모양으로 만드셨나요?
A: 재미로요 :) 모양 맞춰서 만드는 취미도 있고 해서... 링크 된 곳 둘러 보시면 재밌는 소스 좀 많이 나올 겁니다. (일명 obfuscation collection이라 부릅니다)

Q: gd library 쓰면 되잖아요.
A: 없는 곳도 있잖습니까 -_- 외부 라이브러리의 힘을 빌지 않고 png를 생성해 내는 게 나쁘지는 않다고 생각합니다. (그리고 결정적으로, gd library를 썼다면 코드가 재미 없었겠죠. :)

Q: 한글 좀 지원해 주세요.
A: 소스가 너무 커져서 때려 치웠습니다. 할 수는 있지만... -_- 그리고 결정적으로 문제가 되는 것이 cp949(euc-kr) 코드에서 자소를 추출하는 건 사실상 불가능하다는 겁니다. :( 이런 저런 이유로 일단은 보류된 상태입니다.

Q: 설명이 필요해요.
A: 누가 요청해서 만들어 둔 게 있습니다. http://pandora.sapzil.info/dev/obfuscation/file/text2png-explain.php.txt 를 참고하세요.

Q: 이런 거 더 만드실 거에요?
A: php라면 뭔가 괜찮은 아이디어가 떠 오를 때까지 보류입니다. -_-