Delphi Programming Forum
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
델파이 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
컴포넌트/라이브러리
FreePascal/Lazarus
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
델마당
볼랜드포럼 광고 모집

델파이 팁&트릭
Delphi Programming Tip&Tricks
[293] 프로그램에서 AI 파일을 보여주기: Ghostscript
박지훈.임프 [cbuilder] 49232 읽음    2013-01-06 12:31
델파이와 C++빌더의 최근 버전에서는, bmp, wmf/emf, ico, jpeg 등 전통적으로 지원해왔던 이미지 포맷들 외에도 gif와 png 까지 지원하게 되었습니다. 또한 WIC를 통해 tiff와 raw 포맷까지 지원합니다. 이만하면 라스터 그래픽쪽으로는 막강한 파일 포맷 지원을 하고 있다고 할 수 있죠.

게다가, 무료 및 상용의 다양한 이미지 포맷 지원 라이브러리들이 있습니다. 완전한 무료이면서도 막강한 이미지 포맷들을 지원하는 GraphicEx가 대표적이죠. GraphicEx에선 위의 포맷들 외에 psd, pcx, tga, sgi, pcd, psp 등등의 여러 다른 포맷들까지 추가로 지원합니다.

그런데, 개발중인 애플리케이션에서 디자인에서 많이 사용되는 AI 포맷을 읽어들이는 기능이 필요한 경우라면 어떨까요? AI 포맷은 Adobe의 Illustrator에서 사용하는 포맷으로서(AI 자체가 Adobe Illustrator의 이니셜), 이름 그대로 일러스트 디자이너들이 많이 사용하는 포맷입니다. 어떤 제품의 기본 디자인을 스케치하는 경우 주로 이 AI 포맷을 사용하죠. 따라서 일러스트 디자인을 많이 사용하는 회사의 업무 개발을 한다면 이 AI 포맷을 읽어들일 필요가 생길 수 있겠습니다.

그런데 델파이와 C++빌더에서는 기본적으로도 AI 포맷을 지원하지 않을 뿐만 아니라, 서드파티 라이브러리에서도 지원되는 것이 (제가 알기로는) 전혀 없습니다. 델파이/C++빌더 외에 다른 프로그래밍 언어라도 마찬가지입니다. (이 블로그 포스트에서 소개하는 방법도, 완전하다거나 아주 깔끔한 방법이 아닌 일종의 편법입니다)

델파이/C++빌더를 비롯한 개발툴들, 그리고 서드파티 라이브러리까지 포함해서도 ai 파일의 뷰를 지원하는 기능이 거의 없는 것은, AI 파일의 포맷이 jpg나 png등과 같이 단순히 픽셀 단위로 압축된 라스터 그래픽 파일이 아니라 벡터 그래픽으로서 포스트스크립트 기반이기 때문입니다.

ai 파일의 데이터는 기본적으로 포스트스크립트를 기반으로 되어 있습니다. 포스트스크립트는 페이지 인쇄를 기술하기 위한 스크립트로, 어도비에서 개발한 것입니다. 기본적으로 대부분의 프린터에 하드웨어적으로 포스트스크립트 엔진이 내장되어 있어 프린터로 포스트스크립트 데이터를 보내면 포스트스크립트 엔진에서 데이터를 해석해서 출력해줍니다.

AI 파일의 경우, 정확하게는 포스트스크립트 자체가 아닌 pdf 포맷으로 되어 있는데요. 이 pdf 포맷도 사실 포스트스크립트의 변형이라서 여전히 포스트스크립트 엔진이 필요합니다. 즉, 프로그램에서 AI 파일을 보여주려면 소프트웨어적인 포스트스크립트 엔진이 있어야 합니다.

이에 대한 대안이 바로 고스트스크립트, Ghostscript 입니다. 고스트스크립트는 기본적으로는 포스트스크립트 인터프리터 엔진이며 여기에 포스트스크립트의 변형인 pdf 엔진과 부가적인 툴들이 포함되어 있습니다. 또한 이 고스트스크립트에서 ps/pdf로부터 일반적인 라스터 그래픽 파일로의 변환도 지원합니다.

고스트스크립트의 메인 홈페이지는 아래와 같습니다.
http://www.ghostscript.com/
http://pages.cs.wisc.edu/~ghost/

참고로, 고스트스크립트는 일정 경우에 무료로 사용할 수 있지만 무조건의 무료 라이선스는 아니며, 조건에 따라 상용 라이선스를 구입해야 하는 경우가 있습니다. 특히 상용 프로그램에는 꽤 까다롭습니다.
고스트스크립트의 라이선스는 AFPL이라는 자체 라이선스와 GPL 두 가지를 제시하고 있습니다. 요약하자면 'GPL에 라이선스를 받은 경우가 아니면 AFPL의 제한을 받는다' 입니다.

AFPL의 내용을 잘 분석해보니, 다음과 같이 요약할 수 있습니다. 첫번째, "상용 애플리케이션이 아닌 무료 소프트웨어의 경우 고스트스크립트를 무료로 사용할 수 있다"는 것입니다. 무료 소프트웨어인 경우 배포에도 아무런 제약이 없습니다. 두번째, "상용 애플리케이션인 경우, 고스트스크립트의 파일들과 함께 배포해서는 안된다" 입니다. 예를 들면 사용자에게 링크를 제공하여 직접 다운로드해서 설치하도록 유도하면 AFPL의 제한을 피할 수 있을 것으로 보입니다. (저작권사인 Artifex에서는 프로그램에 포함시키거나, 설치 CD에 함께 넣어 배포하거나 등을 예로 들고 있습니다)

고스트스크립트의 라이선스에 대한 더 자세한 설명은 Conditions on distributing Ghostscript in a commercial context 을 보시면 됩니다. 자유롭게 배포하면서 라이선스 문제로부터 자유로우려면 저작권사 Artifex사에서 라이선스를 구입하면 됩니다. (가격 정보는 공개되어 있지 않네요)

그럼 이제 이 고스트스크립트를 이용해서 델파이/C++빌더에서 AI 파일을 보여주는 방법으로 들어가볼까요. 고스트스크립트는 PS, PDF와 관련한 많은 기능들을 지원하는데, 우리가 필요한 것은 AI 파일로부터 png나 jpg 같은 일반적인 라스터 그래픽 파일로 변환하는 기능입니다. 일반적으로 일러스트레이터로 디자인한 파일은 jpg보다는 png가 더 적합하므로, png로 가보지요.

고스트스크립트를 이용하여 AI 파일을 PNG로 변환하려면, 커맨드라인 툴인 gswin32c.exe를 이용하여 다음과 같은 커맨드라인 명령을 내리면 됩니다.

gswin32c.exe -sDEVICE=png16m -sOutputFile=(png파일이름) -dBATCH -dNOPAUSE -dNOPROMPT -dQUIET
  -r(DPI) -dEPSCrop -dPDFCrop (AI파일이름)


위 파라미터 옵션들에 대해서는, 커맨드라인에서 gs -h 또는 gs -?를 쳐보시면 자세히 나옵니다. 주요 옵션만 설명하자면, -sDEVICE=png16m은 출력 디바이스를 png 파일 16만컬러로 지정한 것이며, -r옵션은 출력될 파일의 DPI, 즉 인치당 픽셀수를 지정합니다.

아래는 이 gswin32c.exe 커맨드라인 유틸리티를 이용하여 AI 파일을 PNG 파일로 변환하는 함수 ConvertPsToPng()의 전체 코드입니다. 델파이 버전과 C++빌더 버전이 따로 있습니다.

델파이 코드
function ConvertPsToPng(InFile: string; iDpi: integer=150): string;

  function GetGsPath: string;
  const
    SGSKey = '\SOFTWARE\GPL Ghostscript\9.06';
  var
    Reg: TRegistry;
  begin
    Reg := TRegistry.Create(KEY_READ);
    try
      Reg.RootKey := HKEY_LOCAL_MACHINE;
      if Reg.OpenKey(SGSKey, false) then
        result := ExtractFilePath(Reg.ReadString('GS_DLL'));
    finally
      Reg.Free;
    end;
  end;
var
  CmdLine: string;
  start: TStartupInfo;
  sec: TSecurityAttributes;
  hwrite, hread: THandle;
  pinfo: TProcessInformation;
  Buffer: array[0..512] of char;
  BytesRead: dword;
  ResultString: string;
begin
  Application.ProcessMessages;

  result := ExtractFilePath(ParamStr(0)) + ChangeFileExt(ExtractFileName(InFile), '.png');
  CmdLine := '"' + GetGsPath + 'gswin32c.exe" '
           + '-sDEVICE=png16m '                     // pnggray png256 png16 pngmono pngmonod
           + '-sOutputFile="' + result + '" '
           + '-dBATCH -dNOPAUSE -dNOPROMPT -dQUIET -r' + IntToStr(iDpi) + ' '
           + '-dEPSCrop -dPDFCrop '
           + '"' + InFile + '"';

  sec.nLength := sizeof(sec);
  sec.lpSecurityDescriptor := nil;
  sec.bInheritHandle := true;

// anonymous 파이프 생성
  if CreatePipe(hread, hwrite, @sec, 0)<>true then
  begin
    ShowMessage('Fail to open pipe: ' + #13#10 + SysErrorMessage(GetLastError));
    exit;
  end;

  // 콘솔어플리케이션 프로세스 실행을 위한 준비
  FillChar(start, sizeof(TStartupInfo), 0);
  start.cb := sizeof(TStartupInfo);
  start.dwFlags := STARTF_USESTDHANDLES;
  start.hStdOutput := hwrite;  // 표준출력(stdout) 리다이렉션
  start.hStdError := hwrite;   // 표준에러(stderr) 리다이렉션

  if CreateProcess(nil, PChar(CmdLine), @sec, @sec, true, DETACHED_PROCESS, nil, nil,  start, pinfo) <> true then
  begin
    ShowMessage('CreateProcess() failed: ' + #13#10 + SysErrorMessage(GetLastError));
    exit;
  end;
  CloseHandle(hwrite);//이것을 하지 않으면 프로세스가 block된다

  while ReadFile(hread, Buffer, Length(buffer)-1, BytesRead, nil) and (BytesRead>0) do
  begin
    Buffer[BytesRead] := #0;
    ResultString := ResultString + Buffer;
  end;

  CloseHandle(hread);
  if ResultString<>'' then
    result := '';
end;


C++빌더 코드
String GetGsPath(void)
{
    TRegistry *Reg = new TRegistry(KEY_READ);
    try
    {
        Reg->RootKey = HKEY_LOCAL_MACHINE;
        if(Reg->OpenKey("\\SOFTWARE\\GPL Ghostscript\\9.06", false))
            return ExtractFilePath(Reg->ReadString("GS_DLL"));
        return "";
    }
    __finally
    {
        delete Reg;
    }
}

String ConvertPsToPng(String InFile, int iDpi)
{
    String sOutputFile = ExtractFilePath(ParamStr(0)) + ChangeFileExt(ExtractFileName(InFile), ".png");
    String CmdLine = String("\"") + GetGsPath() + "gswin32c.exe\" "
                   + "-sDEVICE=png16m "                     // pnggray png256 png16 pngmono pngmonod
                   + "-sOutputFile=\"" + sOutputFile + "\" "
                   + "-dBATCH -dNOPAUSE -dNOPROMPT -dQUIET -r" + IntToStr(iDpi) + " "
                   + "-dEPSCrop -dPDFCrop " + "\"" + InFile + "\"";

    SECURITY_ATTRIBUTES sec;
    sec.nLength = sizeof(sec);
    sec.lpSecurityDescriptor = NULL;
    sec.bInheritHandle = true;

    // anonymous 파이프 생성
    HANDLE hwrite, hread;
    if (CreatePipe(&hread, &hwrite, &sec, 0) == 0)
    {
        ShowMessage(String("Fail to open pipe: \r\n") + SysErrorMessage(GetLastError()));
        return "";
    }

    // 콘솔어플리케이션 프로세스 실행을 위한 준비
    STARTUPINFO start;
    memset(&start, 0, sizeof(STARTUPINFO));
    start.cb = sizeof(TStartupInfo);
    start.dwFlags = STARTF_USESTDHANDLES;
    start.hStdOutput = hwrite; // 표준출력(stdout) 리다이렉션
    start.hStdError = hwrite;    // 표준에러(stderr) 리다이렉션
    PROCESS_INFORMATION pinfo;

    if (CreateProcess(NULL, CmdLine.c_str(), &sec, &sec, TRUE, DETACHED_PROCESS, NULL, NULL, &start, &pinfo) == 0)
    {
        ShowMessage(String("CreateProcess() failed: \r\n") + SysErrorMessage(GetLastError()));
        return "";
    }
    CloseHandle(hwrite); // 이것을 하지 않으면 프로세스가 block된다

    char buffer[512];
    DWORD BytesRead;
    String ResultString;
    while (ReadFile(hread, buffer, sizeof(buffer) - 1, &BytesRead, NULL) && BytesRead)
    {
        buffer[BytesRead] = '\0';
        ResultString = ResultString + buffer;
    }
    CloseHandle(hread);

    if (ResultString != "")
    {
        ShowMessage("파일 변환 중 에러가 발생했습니다.\r\n" + ResultString);
        return "";
    }

    return sOutputFile;
}


위의 두 코드가 좀 길어진 이유는, CreateProcess()로 gswin32c.exe를 실행한 후 실행이 완료될 때까지 대기하고, 또 그 결과 문자열을 받아오기 위한 코딩 때문입니다. 실행 종료를 기다리지 않는다면 png 파일이 다 만들어지는 시점을 알 수가 없으니 불러올 수가 없겠지요.

콘솔 프로그램의 종료까지 대기하는 방법과 표준출력을 통해 결과를 받아오는 방법에 대해서는 볼랜드포럼에 아주 오래 전에 올렸던 내용이니 궁금하신 분들은 참고하시면 되겠습니다.

[팁] 실행시킨 프로그램의 종료 알아내기
http://delphi.borlandforum.com/impboard/impboard.dll?action=read&db=del_tip&no=147
[팁] 콘솔 어플리케이션의 표준출력/표준에러 받아오기
http://delphi.borlandforum.com/impboard/impboard.dll?action=read&db=del_tip&no=148

사실 고스트스크립트의 기능들은 모두 gsdll32.dll에 있고 gswin32c.exe은 커맨드라인 래퍼 실행파일일 뿐이기 때문에 gsdll32.dll을 직접 사용해도 됩니다. 하지만 gsdll32.dll을 사용하는 과정도 좀 복잡하고, gswin32c.exe의 크기도 100kb 남짓에 불과하기 때문에 굳이 dll을 써서 복잡한 방식으로 갈 필요가 별로 없을 것 같습니다.

참고로, 이 코드로 모든 AI 파일을 불러올 수 있는 것은 아닙니다. 아주 오래된 AI 파일들 일부는 읽기에 실패할 수도 있습니다. 제 기억으로는 일러스트레이터 7 버전 이하에서 만들어진 파일은 불러올 수 없었던 걸로 기억합니다. 그리고 또한 일러스트레이터가 아닌 코렐드로우 등으로 만들어진 파일도 일러스트레이터의 옛날 파일 포맷으로 되어 있어 읽기가 안됩니다. 하지만 수천개 이상의 파일들로 테스트해본 결과, 이런 경우는 아주 극소수이고 AI 파일들 중 99% 정도는 읽어올 수 있습니다.

또한, AI 파일 외에 ps, eps, pdf 파일들도 동일한 코딩으로 읽어올 수 있습니다. ps와 eps 파일은 포스트스크립트 파일이어서 동일하게 읽을 수 있고, pdf도 비슷합니다.

배포에 관련하여, 만약 라이선스에도 불구하고 프로그램과 함께 고스트스크립트를 배포하려고 한다면, 고스트스크립트의 bin 디렉토리 외에 lib 디렉토리도 배포해야 합니다. 그리고 이런 경우 고스트스크립트가 자체 설치 파일에 의해 설치된 것이 아니므로 고스트스크립트 레지스트리가 설정되지 않게 되는데, 레지스트리가 반드시 설정되어야 고스트스크립트가 동작합니다.

고스트스크립트에서 읽어들이는 레지스트리 키는 HKEY_LOCAL_MACHINE\SOFTWARE\GPL Ghostscript\9.06 이며(물론 마지막의 9.06은 버전에 따라 달라집니다) 여기서 GS_DLL과 GS_LIB 두 문자열 값이 필요합니다. 여기서 GS_LIB은 기본적으로 필요한 데이터와 폰트 데이터를 읽어들이기 위한 패스입니다.

AI 파일과 고스트스크립트에 대한 정보는 몇년 전에 관련 기능을 요청받아서 작업했던 것이고, 위의 간단한 AI 뷰어 프로그램도 그때 작업했던 소스를 약간만 다듬은 것입니다. 꽤 많은 테스트와 연구를 했었는데 이 글 하나에 다 담기는 어렵네요. 추가 정보가 궁금하신 분들은 질문 주시면 가능한 대로 답변을 드리도록 하겠습니다.

이 팁을 활용한 간단한 AI 파일 뷰어 프로그램을 델파이 자료실에 올려두었으니 참고하세요.
http://delphi.borlandforum.com/impboard/impboard.dll?action=read&db=del_res&no=329
박지훈.임프 [cbuilder]   2013-01-06 14:18 X
참고로, 한컴 뷰어에서도 이 고스트스크립트를 사용하는가보더군요.
한컴뷰어를 설치하면 설치된 디렉토리 아래에 고스트스크립트 8.71 버전 파일들이 깔려 있더군요.

그리고 AI 파일까지 보여주는 그래픽뷰어로 알려져 있는 GSView, XnView도 역시 고스트스크립트를 이용하고 있습니다.
Nibble [gameover]   2013-01-16 14:14 X
주옥같은 글 감사합니다 : )

+ -

관련 글 리스트
293 프로그램에서 AI 파일을 보여주기: Ghostscript 박지훈.임프 49232 2013/01/06
(링크)     C++Builder Tip'N Tricks > 프로그램에서 AI 파일을 보여주기: Ghostscript
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.