특수한 Photoshop 플러그인이 필요해서, 플러그인을 만들려 문서를 봐봤더니 도통 잘 정리된 것이 없어서, 직접 한번 써봤습니다.
2021-7-17 이제 macOS 빌드 지원이 추가되었습니다! 자세한 건 github repository에 주석으로 설명되어 있으니 참고해주세요. 빌드가 크게 차이 나는 게 없어서 블로그 글은 따로 업데이트 하진 않았습니다.

시작

이 파트에서 기본적으로 필요한 환경과 준비물을 설치하는 과정을 포함합니다.

환경

  • Windows 10
  • Photoshop CC 2019 버전 이상
  • Visual Studio 2017 (Windows SDK가 설치되지 않았다면 설치해야 합니다.)
  • CMake (Visual Studio CMake 확장)
  • MSVC v141 (꼭 이 버전을 설치해야 합니다. Photoshop과 같은 컴파일러로 빌드하는 것을 권장합니다.)

준비물

준비물을 옮겨 놓을 폴더를 하나 만들어줍니다. 저는 “test” 폴더를 만들었습니다. 폴더 이름을 같게 만들면 앞으로 따라 하기가 좀 더 수월해집니다.

Photoshop C++ SDK

https://console.adobe.io/downloads/ps

포토샵 SDK 다운로드

다운로드하고 “test” 폴더에 압축을 풀어두면 됩니다.

Adobe CEP

https://github.com/Adobe-CEP/CEP-Resources

포토샵 CEP 다운로드

다운로드하여 마찬가지로 “test” 폴더에 압축을 푼 다음, “CEP_9.x” 폴더의 “CSInterface.js”를 “test/cep” 폴더에 옮겨 놓습니다.

이것으로 기본적으로 필요한 것들은 모두 준비가 끝났습니다.

빌드 프로세스

이 파트에서는 빌드 프로세스를 알아보고, CMake로 빌드 프로세스를 자동화합니다. 프로세스가 별로 궁금하지 않으시거나 시간이 없으신 분은 바로 맨 아래쪽을 확인하시길 바랍니다.

프로세스

본격적으로 시작하기 전, 다운받은 준비물들이 생각나시나요? 아래를 참고하여 다음 파일과 폴더들을 해당 위치에 옮겨줍니다. (test폴더 내에 sdk와 tool 폴더를 만들어줍니다.)

  • {SDK 압축 해제 폴더}/pluginsdk/samplecode/common -> test/sdk/common
  • {SDK 압축 해제 폴더}/pluginsdk/photoshopapi/photoshop -> test/sdk/photoshop
  • {SDK 압축 해제 폴더}/pluginsdk/photoshopapi/pica_sp -> test/sdk/pica_sp
  • {SDK 압축 해제 폴더}/pluginsdk/samplecode/resources/Cnvtpipl.exe -> test/tool/Cnvtpipl.exe
  • {CEP 압축 해제 폴더}/ZXPSignCMD/4.1.1/win64/ZXPSignCmd.exe -> test/tool/ZXPSignCmd.exe

PiPL (Plug In Property List) 빌드

PiPL 빌드 플로우

PiPL은 이름처럼 플러그인의 정보를 가지고 있는 파일입니다. 빌드 과정 중 제일 까다롭고 복잡한 부분이기도 합니다.

CMAKE_SHARED_LINKER_FLAGS에서 .res를 포함해주는 것을 확인할 수 있습니다. 또 “string”으로 ‘/’을 ‘\\’로 바꿔주는 것을 볼 수 있는데, 이는 Windows가 CMake가 사용하는 Unix 주소 규칙을 잘 인식하지 못하기 때문입니다.

set( PiPL_File_Path "pipl.r" )
set( Build_Tmp_Path "${CMAKE_CURRENT_SOURCE_DIR}/tmp" )
set( Sdk_Path "${CMAKE_CURRENT_SOURCE_DIR}/sdk")
set( PiPL_File_Path "pipl.r" )
set( Tool_Path "${CMAKE_CURRENT_SOURCE_DIR}/tool" )
string(REPLACE "/" "\\" MS_Build_Tmp_Path "${Build_Tmp_Path}")
string(REPLACE "/" "\\" MS_PiPL_File_Path "${CMAKE_CURRENT_SOURCE_DIR}/${PiPL_File_Path}")
set(EX_LIB_Linker_Flags "/defaultlib:User32.lib /defaultlib:Kernel32.lib /defaultlib:Shell32.lib /nodefaultlib:LIBCMTD.lib /nodefaultlib:LIBCMT.lib")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /incremental:no ${EX_LIB_Linker_Flags} \"${Build_Tmp_Path}/pipl.res\" /nologo")
set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} /opt:noref")
set(PH_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_DEBUG} ")
set( MS_Sdk_Paths "/I \"${Sdk_Path}/common/resources\" \
        /I \"${Sdk_Path}/common/includes\" \
        /I \"${Sdk_Path}/common/sources\" \
        /I \"${Sdk_Path}/photoshop\" \
        /I \"${Sdk_Path}/pica_sp\"")
add_custom_target(PiPL ALL
            COMMENT  "Build PiPL..."
            SOURCES ${PiPL_File_Path}
            COMMAND if not exist "${MS_Build_Tmp_Path}" mkdir "${MS_Build_Tmp_Path}" #tmp폴더 생성
            COMMAND ${CMAKE_CXX_COMPILER} ${PH_FLAGS} ${MS_Sdk_Paths} /EP /DMSWindows=1 /Tc ${MS_PiPL_File_Path} > "${Build_Tmp_Path}/pipl.rr" #전처리기 .rr
            COMMAND echo .rr done (%errorlevel%)
            COMMAND ${Tool_Path}/Cnvtpipl.exe "${Build_Tmp_Path}/pipl.rr" "${Build_Tmp_Path}/pipl.rc" #Cnvtpipl.exe .rc
            COMMAND echo .rc done (%errorlevel%)
            COMMAND rc /v /fo "${Build_Tmp_Path}/pipl.res" "${Build_Tmp_Path}/pipl.rc" #rc.exe .res
            COMMAND echo .res done (%errorlevel%)
            )

소스 빌드

소스 빌드 플로우

크게 자세히 살펴볼 것은 없으나 Plugin_Output_Suffix로 확장자를 .8bp로 지정해주는 것을 확인할 수 있는데, 이는 이 플러그인이 일반 플러그인이라는 것을 의미합니다. (필수는 아닙니다) 자세한 내용은 pluginsdk/documentation/html/pgplugintypes.html을 참고해주세요.

set( Plugin_Name "test")
set( Plugin_Output_Suffix ".8bp")
add_library(${Plugin_Name} SHARED ${Src_List})
    add_dependencies(${Plugin_Name} PiPL)
    set_target_properties(${Plugin_Name} PROPERTIES PREFIX "")
    set_target_properties(${Plugin_Name} PROPERTIES OUTPUT_NAME ${Plugin_Name})
    set_target_properties(${Plugin_Name} PROPERTIES SUFFIX "${Plugin_Output_Suffix}")
    set_target_properties( ${Plugin_Name}
            PROPERTIES
            ARCHIVE_OUTPUT_DIRECTORY "${Build_Tmp_Path}"
            LIBRARY_OUTPUT_DIRECTORY "${Build_Tmp_Path}"
            RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin"
            )

참고로 pluginsdk/documentation/html/faqs.html에 나와있듯이, Photoshop API는 thread safe하지 않습니다. 빌드에 사용한 많은 옵션이 이해가 잘 되지 않으신다면 이 페이지를 참고하시면 됩니다.

서명

서명은 필수가 아니라 선택사항입니다. (다만 Adobe Exchange로 배포하려면 필수입니다) 원래라면 인증기관(CA)에서 인증서를 발급받아 적용해야 하지만 지금은 그냥 위에서 옮겨놓은 test/tool/ZXPSignCmd.exe을 이용해 자체 서명 인증서를 발급받아 적용해 보겠습니다.

  1. 파일 탐색기에서 test/tool로 이동한 다음 Shift키를 누른 상태에서 빈 공간을 우클릭하여 “여기에 Power Shell 창 열기”를 누릅니다.
  2. .\ZXPSignCmd -selfSignedCert를 이용해 test/cert 폴더에 cert.p12를 만들어줍니다.
서명 플로우

${Plugin_Name} 빌드가 끝난 후 서명합니다.

add_custom_command(TARGET ${Plugin_Name}
            POST_BUILD
            COMMAND if not exist "${MS_Build_Bin_Path}" mkdir "${MS_Build_Bin_Path}"
            COMMAND ${Tool_Path}/ZXPSignCmd.exe -sign "${MS_Build_Bin_Path}" "${MS_Build_Out_Path}" "${Cert_Path}/cert.p12" "${Cert_Password}"
            COMMENT "Set cert.cert..."
            VERBATIM
            )

전체 소스

cmake_minimum_required(VERSION 3.16)
project(test)
set( Plugin_Name "test")
set( Plugin_Output_Suffix ".8bp")
set( PiPL_File_Path "pipl.r" )
set( Tool_Path "${CMAKE_CURRENT_SOURCE_DIR}/tool" )
set( Cert_Path "${CMAKE_CURRENT_SOURCE_DIR}/cert" )
set( Sdk_Path "${CMAKE_CURRENT_SOURCE_DIR}/sdk")
set( Build_Tmp_Path "${CMAKE_CURRENT_SOURCE_DIR}/tmp" )
set( Cert_Password "1234" )
set( Src_List "globals.h"
        "main.cpp")
# required file
# in tool folder (Cnvtpipl.exe, ZXPSignCmd.exe - for release)
# in cert folder (cert.p12) - for release
# in sdk folder
# pluginsdk/samplecode/common -> sdk/common
# pluginsdk/photoshopapi/photoshop -> sdk/photoshop
# pluginsdk/photoshopapi/pica_sp -> sdk/pica_sp
if(!MSVC OR MSVC_VERSION LESS 1910 OR MSVC_VERSION GREATER 1919)
    message( FATAL_ERROR "Use MSVC v141" )
endif()
if( NOT EXISTS "${Tool_Path}/Cnvtpipl.exe" OR
        NOT EXISTS "${Tool_Path}/ZXPSignCmd.exe" OR
        NOT EXISTS "${Sdk_Path}/common/includes" OR
        NOT EXISTS "${Sdk_Path}/common/resources" OR
        NOT EXISTS "${Sdk_Path}/common/sources" OR
        NOT EXISTS "${Sdk_Path}/photoshop" OR
        NOT EXISTS "${Sdk_Path}/pica_sp" OR
        NOT EXISTS "${Cert_Path}/cert.p12"
        )
    message( FATAL_ERROR "The required file was not found. Correct it by referring to the comments." )
endif()
set(CXX_FLAGS_BACKUP "${CMAKE_CXX_FLAGS}")
set(CXX_FLAGS_DEBUG_BACKUP "${CMAKE_CXX_FLAGS_DEBUG}")
set(CXX_FLAGS_RELEASE_BACKUP "${CMAKE_CXX_FLAGS_RELEASE}")
set(INCLUDE_DIRECTORIES_BACKUP ${INCLUDE_DIRECTORIES})
set(SHARED_LINKER_FLAGS_DEBUG_BACKUP "${CMAKE_SHARED_LINKER_FLAGS_DEBUG}")
set(SHARED_LINKER_FLAGS_RELEASE_BACKUP "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /nologo /WX /EHsc /Zc:__cplusplus /arch:AVX2")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /D_DEBUG")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /DNDEBUG")
set(EX_LIB_Linker_Flags "/defaultlib:User32.lib /defaultlib:Kernel32.lib /defaultlib:Shell32.lib /nodefaultlib:LIBCMTD.lib /nodefaultlib:LIBCMT.lib")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /incremental:no ${EX_LIB_Linker_Flags} \"${Build_Tmp_Path}/pipl.res\" /nologo")
set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} /opt:noref")
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /opt:ref")
set( PS_Sdk_Paths "${CMAKE_CURRENT_SOURCE_DIR}"
        "${Sdk_Path}/common/includes"
        "${Sdk_Path}/common/sources"
        "${Sdk_Path}/common/resources"
        "${Sdk_Path}/photoshop"
        "${Sdk_Path}/pica_sp"
        )
string(REPLACE "/" "\\" MS_Build_Tmp_Path "${Build_Tmp_Path}")
string(REPLACE "/" "\\" MS_Build_Bin_Path "${CMAKE_CURRENT_SOURCE_DIR}/bin")
string(REPLACE "/" "\\" MS_Build_Out_Path "${CMAKE_CURRENT_SOURCE_DIR}/bin/${Plugin_Name}.zxp")
string(REPLACE "/" "\\" MS_PiPL_File_Path "${CMAKE_CURRENT_SOURCE_DIR}/${PiPL_File_Path}")
set( MS_Sdk_Paths "/I \"${Sdk_Path}/common/resources\" \
        /I \"${Sdk_Path}/common/includes\" \
        /I \"${Sdk_Path}/common/sources\" \
        /I \"${Sdk_Path}/photoshop\" \
        /I \"${Sdk_Path}/pica_sp\"")
include_directories(${PS_Sdk_Paths})
if(WIN32)
    message( STATUS "OS is Windows")
    add_definitions(/D_CRT_SECURE_NO_DEPRECATE /D_SCL_SECURE_NO_DEPRECATE /DISOLATION_AWARE_ENABLED=1 /DWIN32=1 /D_WINDOWS /D_USRDLL /D_WINDLL /D_MBCS)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
    message( STATUS "Debug build")
    set(PH_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_DEBUG} ")
else()
    message( STATUS "Release build")
    set(PH_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_RELEASE} ")
endif()
    add_custom_target(PiPL ALL
            COMMENT  "Build PiPL..."
            SOURCES ${PiPL_File_Path}
            COMMAND if not exist "${MS_Build_Tmp_Path}" mkdir "${MS_Build_Tmp_Path}"
            COMMAND ${CMAKE_CXX_COMPILER} ${PH_FLAGS} ${MS_Sdk_Paths} /EP /DMSWindows=1 /Tc ${MS_PiPL_File_Path} > "${Build_Tmp_Path}/pipl.rr"
            COMMAND echo .rr done (%errorlevel%)
            COMMAND ${Tool_Path}/Cnvtpipl.exe "${Build_Tmp_Path}/pipl.rr" "${Build_Tmp_Path}/pipl.rc"
            COMMAND echo .rc done (%errorlevel%)
            COMMAND rc /v /fo "${Build_Tmp_Path}/pipl.res" "${Build_Tmp_Path}/pipl.rc"
            COMMAND echo .res done (%errorlevel%)
            )
    add_library(${Plugin_Name} SHARED ${Src_List})
    add_dependencies(${Plugin_Name} PiPL)
    set_target_properties(${Plugin_Name} PROPERTIES PREFIX "")
    set_target_properties(${Plugin_Name} PROPERTIES OUTPUT_NAME ${Plugin_Name})
    set_target_properties(${Plugin_Name} PROPERTIES SUFFIX "${Plugin_Output_Suffix}")
    set_target_properties( ${Plugin_Name}
            PROPERTIES
            ARCHIVE_OUTPUT_DIRECTORY "${Build_Tmp_Path}"
            LIBRARY_OUTPUT_DIRECTORY "${Build_Tmp_Path}"
            RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin"
            )
    add_custom_command(TARGET ${Plugin_Name}
            POST_BUILD
            COMMAND if not exist "${MS_Build_Bin_Path}" mkdir "${MS_Build_Bin_Path}"
            COMMAND ${Tool_Path}/ZXPSignCmd.exe -sign "${MS_Build_Bin_Path}" "${MS_Build_Out_Path}" "${Cert_Path}/cert.p12" "${Cert_Password}"
            COMMENT "Set cert.cert..."
            VERBATIM
            )
elseif(APPLE)
    message( FATAL_ERROR "OS X not support now" )
endif()
set(CMAKE_CXX_FLAGS "${CXX_FLAGS_BACKUP}")
set(CMAKE_CXX_FLAGS_DEBUG "${CXX_FLAGS_BACKUP}")
set(CMAKE_CXX_FLAGS_RELEASE "${CXX_FLAGS_BACKUP}")
set(INCLUDE_DIRECTORIES ${INCLUDE_DIRECTORIES_BACKUP})
set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${SHARED_LINKER_FLAGS_DEBUG_BACKUP}")
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${SHARED_LINKER_FLAGS_RELEASE_BACKUP}")

이것으로 빌드 프로세스 자동화는 끝났습니다. 간단한 예제와 함께 실제 빌드되는 플러그인은 제 github repository에 있으니 참고해주세요. 말 그대로 Photoshop Plugin Starter라서 플러그인 개발 시작하실 때 쉽게 사용하실 수 있을 것 같습니다.

글쓴이

phruse

쉬운 길보다는 어려운 길을 즐깁니다. 다양한 분야에 관심이 많으며 언젠가 많은 사람이 사용하는 기반 기술을 개발하는 것이 목표입니다.