>웹 프론트엔드 >PS 튜토리얼 >Photoshop용 타사 필터 플러그인 개발 소개

Photoshop용 타사 필터 플러그인 개발 소개

高洛峰
高洛峰원래의
2017-02-20 09:02:073292검색

Photoshop은 디지털 이미지 처리 분야에서 뛰어난 소프트웨어입니다. 동시에 제3자가 플러그인 형태로 기능을 확장할 수도 있습니다. Photoshop 플러그인은 현재 자동화(일괄 처리)('자동' 하위 메뉴에 표시됨), 색상 픽업, 가져오기, 내보내기('가져오기' '내보내기' 하위 메뉴에 표시됨), 확장, 확장 등 9가지 유형으로 나눌 수 있습니다. 필터, 파일 형식(열기, 다른 이름으로 저장 아래에 표시됨), 구문 분석(내보내기 기능 포함), 선택('선택' 메뉴 아래에 표시됨). 여기서는 사용자에게 가장 친숙한 필터를 예로 들어보겠습니다.
(1) 플러그인 일반 부분 소개:
플러그인의 메인 프로그램을 호출하여 호스트가 되는데, 대부분의 경우 Photoshop(이하 PS)입니다. 플러그인은 실제로 Windows 시스템 아래의 동적 링크 라이브러리입니다(확장자가 다를 뿐입니다). PS는 LoadLibray를 사용하여 플러그인 모듈을 로드합니다. 사용자가 해당 작업을 수행하면 플러그인 모듈에 대한 일련의 PS 호출이 발생합니다. 이러한 모든 호출은 동일한 진입점을 호출합니다. 진입점은 다음과 같이 정의된 함수입니다. (PS는 Windows 및 Mac과 호환되므로 여기서는 Windows 시스템에 대한 정의만 제공합니다.)

void ENTRYPOINT(
짧은 선택기) ,
void* PluginParamBlock,
long* PluginData,
short* result);

selector:
작업 유형 표시기. selector=0인 경우 모든 유형의 플러그인에 대해 동일한 의미를 갖습니다. 즉, 정보 대화 상자를 표시할지 묻는 것입니다. 다른 값은 플러그인 유형에 따라 다른 의미를 갖습니다.

pluginParamBlock:
이는 호스트와 플러그인 간에 정보와 데이터를 전송하는 데 사용되는 대규모 구조에 대한 포인터입니다. 플러그인 유형에 따라 구조가 다릅니다.

pluginData:
int32 유형에 대한 포인터입니다. 이는 PS가 플러그인에 대한 여러 호출을 통해 저장하는 값입니다. 표준 용도 중 하나는 플러그인이 저장을 위해 이 매개변수에 일부 전역 데이터 포인터를 전달할 수 있다는 것입니다.

result:
int16에 대한 포인터 플러그인이 실행될 때마다 결과를 설정해야 합니다. 호출됩니다. 0을 반환하면 플러그인 코드에 오류가 발생하지 않았음을 나타냅니다. 오류가 발생하면 이 값은 오류 코드를 반환합니다. 오류 코드와 관련하여 ps는 다양한 유형의 플러그인에 대해 오류 코드 범위를 나누고 SDK에서 일부 값을 미리 정의합니다.

대화 상자 정보:
모든 플러그인은 호출에 대해 응답해야 합니다. 플러그인은 사용자 정의 대화 상자를 표시할 수 있습니다. 단, 일관성을 유지하기 위해 다음과 같은 규칙을 준수해야 합니다.
(1) 메인 화면의 가로 중앙, 세로 높이의 1/3에 표시됩니다.
(2) 확인 버튼을 포함할 필요는 없지만, 어느 위치에서든 클릭하고 Enter 키를 누르면 응답합니다.

(2) 필터 플러그인 소개
필터 플러그인의 기능은 이미지의 선택된 영역을 수정하는 것입니다. 필터 동작은 채도 및 밝기 조정부터 이미지 필터링까지 다양합니다. Windows에서 필터의 확장자는 ".8BF"입니다.
다음 그림은 PS와 필터 플러그인 간의 호출 순서를 보여줍니다. 이것은 SDK 문서에 있는 그림입니다. 여기에 표시된 내용은 다음과 같습니다. 필터. 플러그인의 호출 순서입니다.
      Photoshop용 타사 필터 플러그인 개발 소개

필터는 상단 호출 시작점인 필터 메뉴를 이용하여 호출할 수 있습니다. 한 번 호출하면 Photoshop에서는 필터 메뉴의 "마지막 필터" 하위 메뉴에 최신 필터 작업을 추가합니다. 앞으로 이 메뉴를 클릭하면 위 그림의 "마지막 필터 명령"에 해당됩니다. 아래에서는 위에서 보여드린 과정을 간략하게 소개하겠습니다. 먼저 필터 진입점 함수의 "템플릿"을 살펴보겠습니다.

플러그인 진입점 :PlugInMain
// 내보낸 함수에 대한 정의 만들기
#define DLLExport extern" C" __declspec(dllexport)
#define SPAPI

DLLExport SPAPI 
void PluginMain(const int16 선택기,
        
void * filterRecord,
        int32 
* data,
        int16 
* 결과)

 
스위치 (선택기)
 {
  
케이스 filterSelectorAbout:
   
// DoAbout();
   break;
  
케이스 filterSelectorParameters:
   DoParameters();
   
break;
  
case filterSelectorPrepare:
   DoPrepare();
   
break;
  
case filterSelectorStart:
   DoStart();
   
break;
  
case filterSelectorContinue:
   DoContinue();
   
break ;
  
케이스 filterSelectorFinish:
   DoFinish();
   
break;
  
기본값:
   
*gResult = filterBadParameters;
   
휴식;
 }
}

위 함수는 우리 필터의 가장 중요한 함수인데, 이 함수는 PS 호출을 위해 제공되기 때문에 이 함수가 Dll 내보내기 함수로 선언되어 있음을 알 수 있습니다. 호출 순서에서 볼 수 있듯이 이 메커니즘은 이 함수의 기능을 창의 창 프로시저와 매우 유사하게 만듭니다. 윈도우 프로시저는 MSG ID를 기준으로 메시지를 처리하는 데 사용되며, 이 기능은 주로 선택자를 기준으로 해당 작업을 수행하는 데 사용됩니다. 따라서 모두 스위치 케이스 분기 처리 구조를 포함합니다.

filterRecord
위 함수에 사용된 두 번째 매개변수는 about이 호출될 때 AboutRecord 구조에 대한 포인터입니다(예: selector=0). FilterRecord 구조는 매우 크고 복잡한 구조로, ps와 필터 간의 통신 및 데이터 전송을 위한 핵심 캐리어입니다. 크기는 452바이트이며, 총 7페이지로 구성됩니다. 이 구조의 구성원의 의미를 소개하는 데 사용되는 문서입니다. FilterRecord의 전체 정의는 SDK의 헤더 파일:pifilter.h에 있습니다. 아래에서는 언급된 가장 기본적이고 중요한 구성원 중 일부에 대해 설명하겠습니다.

(3) 호출 프로세스를 소개합니다.

(3.1) filterSelectorParameters 호출:
필터에 사용자가 설정해야 하는 매개변수가 있는 경우 해당 매개변수를 특정 위치에 저장해야 합니다. 그런 다음 주소를 세 번째 매개변수 데이터로 설정합니다. PS는 이 매개변수를 NULL로 초기화합니다. 이 호출이 발생하는지 여부는 사용자의 호출 방법에 따라 다릅니다. 필터가 방금 호출되면 필터는 필터의 가장 최근 명령 메뉴에 나타납니다. 사용자는 동일한 매개변수로 이 메뉴를 사용할 수 있습니다. 여기에는 대화 상자가 표시되지 않습니다. 사용자가 새 매개변수를 설정하도록 요청한 후 다시 호출합니다. 이 호출은 사용자가 마지막 필터 명령으로 호출할 때 발생하지 않습니다. (위 사진에 참석하세요). 따라서 잘못된 매개변수로 인해 프로그램이 중단될 위험이 있는 경우 매개변수를 매번 확인하고 검증하고 초기화해야 합니다.
주목! : 크기가 다른 이미지에도 동일한 매개변수를 사용할 수 있으므로 매개변수는 이미지 크기에 따라 달라져서는 안 됩니다. 예를 들어, 매개변수는 이미지 너비나 높이에 의존해서는 안 됩니다. 일반적으로 매개변수로 백분율이나 축척 비율을 사용하는 것이 더 적합합니다.
따라서 매개변수 데이터 블록에는 다음 정보가 포함되어야 합니다.
1. 필터가 해당 매개변수 데이터임을 신속하게 확인할 수 있도록 하는 서명입니다.
2. 서명을 변경하지 않고도 플러그인을 자유롭게 업그레이드할 수 있도록 하는 버전 번호입니다.
3. 바이트 순서 식별. (크로스 플랫폼 목적의 경우) 현재 사용 중인 엔디안을 나타냅니다.

매개변수 블록(매개변수 데이터 블록) 및 스크립팅 시스템(스크립트 설명 시스템)
스크립팅 시스템은 매개변수를 캐시하는 데 사용되며 각 호출 유형에 사용됩니다. . 모든 매개변수를 저장하는 데 사용할 수 있도록 플러그인에 전달됩니다. 매개변수 블록이 검증되면 전달된 매개변수에서 데이터를 읽은 다음 매개변수를 업데이트해야 합니다. 예:
1. 먼저 ValidateMyParameters를 호출하여 전역 매개변수를 확인하거나 초기화합니다.
2. 그런 다음 ReadScriptingParameters 메서드를 호출하여 매개변수를 읽고 전역 매개변수 데이터 구조에 씁니다.

(3.2) filterSelectorPrepare 호출:
이 호출을 사용하면 플러그인 모듈이 ps의 메모리 할당 알고리즘을 조정할 수 있습니다. "마지막 필터" 명령은 이 호출에서 시작됩니다. PS는 maxSpace(FilterRecord 구조(두 번째 매개변수)의 멤버이며 이후에 나타나는 새 멤버는 특별히 설명되지 않음)를 플러그인에 할당할 수 있는 최대 바이트 수로 설정합니다.
imageSize, planes 및 filterRect 멤버:
이러한 멤버는 이제 정의되었으며(SDK 6.0 참조) 메모리 요구 사항을 계산하는 데 사용할 수 있습니다. 이미지 크기, 이미지 크기. 비행기, 채널 수.
filterRect: 필터 직사각형.
여기서는 PS에서 정의한 Rect 유형(Windows API의 RECT 구조와 유사)인 filterRect를 다시 강조하고 싶습니다. 이 개념은 "변위 필터 원리"에 대한 내 연구 게시물에서 반복적으로 언급하고 강조한 "선택 포함 사각형"의 개념이기도 합니다. 당시 저는 아직 PS SDK를 접하지 못했습니다. 여기서 우리는 Photoshop의 코드에서 filterRect라고 불리는 것을 볼 수 있습니다.
버퍼스페이스:
필터가 32K 이상의 공간을 할당하려는 경우 이 멤버는 적용하려는 바이트 수로 설정되어야 합니다. ps는 다음 호출(호출 시작) 전에 이 크기의 공간을 해제하여 다음 호출이 성공하도록 시도합니다.

(3.3) filterSelectorStart 호출:
이 호출에서는 매개변수 데이터 블록을 확인하고 ps에서 전달된 매개변수를 기반으로 자체 매개변수를 업데이트하고 필요한 경우 UI를 표시해야 합니다. 그런 다음 데이터 처리 프로세스로 이동합니다.
advanceState 콜백: (PS에 해당 데이터 업데이트를 요청하는 데 사용)
PS가 필터에 제공하는 매우 중요한 콜백 함수로 다음과 같이 정의됩니다.
#define MACPASCAL
typedef short OSErr;
typedef MACPASCAL OSErr (*AdvanceStateProc) (void);
그의 기능은 PS가 FilterRecord의 오래된 데이터를 즉시 업데이트하도록 요구하는 것입니다. 예를 들어, 새로운 처리 사각형을 설정한 다음 이 함수를 호출하면 이 호출 후에 필요한 새 데이터를 얻을 수 있습니다. 이 콜백을 사용하면 계속 호출을 사용하지 않고도 시작 호출에서 핵심 처리를 모두 수행할 수 있습니다. 처리가 완료되면 inRect=outRect=maskRect=NULL로 설정할 수 있습니다.
이 콜백을 사용하지 않는 경우 첫 번째 직사각형 영역을 설정한 다음 계속을 사용하여 루프 처리를 호출해야 합니다.
예를 들어 시작 호출에서 다음 루프를 사용하여 전체 이미지 처리가 완료될 때까지 이미지를 처리할 수 있습니다.

advanceState 콜백 예시
for(..)
{
SetOutRect(out_recc);
gFilterRecord
->outLoPlane=0;
gFilterRecord
->outHiPlane=(g_Planes-1);

 
//PS에 데이터 업데이트를 요청하세요!
 *gResult = gFilterRecord- >advanceState();
 
if (*gResult ! =kNoErr)
 
goto done;

 
//데이터 처리 중
. . . . .
}


Indirect, Outress & Maskect
Set inRect 및 Outress(Masklect)는 첫 번째 처리 영역을 요청합니다. 가능하다면 이미지를 더 작은 조각으로 잘라서 데이터를 전달할 때 필요한 메모리 양을 줄여야 합니다. 64x64 또는 128x128 패치를 사용하는 것이 일반적입니다.

(3.4) filterSelectorContinue 호출:
inRect, outRect, MaskRect 중 하나라도 빈 사각형이 아닌 경우 이 호출이 계속 발생합니다.
inData, outData & MaskData
이 세 멤버는 요청한 이미지 데이터의 시작점을 가리키는 void * 포인터입니다. 우리의 주요 임무는 inData 및 outData를 호출할 때 여기에서 데이터를 계산하고 설정하는 것입니다. 요청한 직사각형 영역에 따라 해당 위치에 이미지 데이터가 채워집니다. 그런 다음 처리 작업은 주로 outData 데이터 영역을 수정하고 PS에 처리 결과를 알려주는 것이며 PS는 결과를 이미지로 업데이트합니다. 전달된 선택 및 선택 모양을 고려할 필요가 없습니다. 이 작동 PS는 선택 외부의 데이터를 자동으로 보호하므로 자체 알고리즘에만 주의하면 됩니다. 처리가 완료된 후 다음 처리를 위해 inRect 및 outRect를 설정합니다. 끝났다면 그냥 비워두세요. inRect가 반드시 outRect와 동일할 필요는 없습니다.
ProgressProc 콜백: (PS 진행률 표시줄을 설정하는 데 사용됨)
해당 유형은 다음과 같이 정의됩니다:
typedef MACPASCAL void (*ProgressProc) (int32 done, int32 total);

이는 진행률 표시줄 설정을 위해 PS에서 제공하는 콜백 함수입니다. 플러그인은 이 콜백을 사용하여 PS가 아래의 진행률 표시줄을 업데이트하도록 할 수 있습니다. 하나는 완료된 작업 수이고 다른 하나는 총계입니다. 작업 수. 사용자에게 처리 진행 상황에 대한 피드백을 제공해야 하는 경우 다음과 같이 진행 상황을 설정할 수 있습니다.
gFilterRecord->progressProc ( Progress_complete, Progress_total )
abortProc 콜백: (보고하는 데 사용됨) 호스트는 사용자가 취소 작업을 수행했는지 쿼리합니다)
유형은 다음과 같이 정의됩니다.
typedef Boolean (*TestAbortProc) (void);
이 콜백은 사용자가 취소 작업을 수행했는지 쿼리하는 데 사용됩니다. 처리하는 동안 플러그인은 이 함수를 초당 여러 번 호출하여 사용자가 취소했는지 확인해야 합니다(예: Esc 키 누르기 등). TRUE가 반환되면 현재 작업이 취소되고 긍정적인 오류 코드가 반환되어야 합니다.


(3.5) filterSelectorFinish 호출:

이 호출을 사용하면 플러그인이 처리 후 정리할 수 있습니다. start 호출이 성공한 경우에만 start가 호출됩니다(오류가 반환되지 않음). 계속에서 오류가 발생하더라도 종료 호출은 계속 발생합니다. 주목! : 계속 통화 중, 일반적으로 다음 계속 통화를 예상할 때 사용자 취소에 주의하세요. 사용자가 취소하면 다음 통화는 계속이 아닌 종료 통화가 됩니다. ! ! .
규칙: 시작 호출이 성공하면 Photoshop은 종료 호출이 시작되도록 보장합니다.

(3.6) 오류 코드
플러그인은 표준 운영 체제 오류 코드를 반환하거나 자체 오류(양의 정수)를 보고할 수 있습니다. SDK에 정의되어 있습니다:

#define filterBadParameters –30100 // #define filterBadMode –30101 // 이 모드 이미지는 지원되지 않습니다.

(4) Raindrop 필터 DEMO
앞서 우리는 주로 필터의 기본 지식에 대해 이야기했습니다. 그러면 설명할 가장 중요한 부분만 추출하며, 더 많은 기술적 세부 사항은 지면의 제약으로 인해 추정할 수 없습니다. 위의 주요 근거는 PS SDK6.0의 문서입니다. 주요 규칙은 원문의 번역에 속하며, 일부 내용은 개인적인 실천과 이해에 속합니다(개인의 이해를 구분하기 위해 색상을 사용하겠습니다). 시간이 있을 때).
이제 데모 설명을 시작하겠습니다! ! ! 정말 피곤해요. . . .
빗방울 필터의 알고리즘은 주로 외국 사이트를 참고하고 있습니다. 한 네티즌이 다른 글에 답글로 제보해 주신 주소입니다. 이 필터의 기원은 구형화 알고리즘입니다(PS에는 이 필터가 내장되어 있습니다). 알고리즘은 필터의 핵심이지만 이 글의 초점은 아니기 때문에 소개하지 않겠습니다. 이를 바탕으로 물방울 효과의 의사 코드를 제공합니다.

---물방울 효과------
무작위로 cx,cy 위치에서 반경 R이 무작위로 생성됩니다.
                                                                                                                                                                                           위치 cx, cy에서 무작위로 생성되는 반경 R을 가집니다.
이 위치에 물방울의 하이라이트와 그림자를 추가하세요.
물방울 내부에 3*3 템플릿 가우시안 블러를 만듭니다.
           ---------------------

(4.1) 픽셀 위치 지정
위의 과정을 반복하여 여러 개의 물방울을 생성합니다. 이것이 이 필터의 핵심 알고리즘입니다. 구체적인 수식은 제공되지 않습니다. 하지만 데이터를 처리하려면 PS가 전달한 데이터에서 픽셀 데이터를 찾는 방법을 이해해야 합니다. 예를 들어 원본 이미지의 (x, y) 위치에 있는 R 채널의 픽셀 데이터를 얻으려고 합니다. .어떻게 얻나요? 여기에서는 FilterRecord의 픽셀 위치 지정과 관련된 중요한 데이터 멤버도 소개합니다. 이는 int32 유형이며 PS에서 플러그인에 제공하는 데이터에 속합니다. C#의 BitmapData.Stride와 동일합니다. 하지만 inData, outData에서는 데이터가 4바이트에 맞춰 정렬되지 않을 수 있으니 주의하세요! 그러나 ps는 줄 끝에 중복 바이트가 없다고 말하지 않습니다. 간단히 말해서, 메모리에서 이미지 데이터 행이 차지하는 바이트 수(범위)입니다.
int16 inLoPlane, inHiPlane, outLoPlane, outHiPlane,
플러그인이 PS를 요청할 때 PS에 통보되는 데이터에 속합니다. 값은 에서 요청한 첫 번째 채널과 마지막 채널 값입니다. 다음 프로세스에서 이 값은 0을 기준으로 하는 인덱스 값입니다. 예를 들어, RGB 이미지의 경우 각각 B, G, R에 해당하는 0~2의 3개 채널이 있습니다(파일의 순서는 PS의 관례적인 RGB 순서가 아니라 여기에서 유지된다는 점에 유의하세요!!!). 한 번에 하나의 채널을 요청하거나 한 번에 여러 채널을 요청할 수 있으며 여러 채널의 데이터가 순서대로 인터리브됩니다. 예를 들어 inLoPlane=0, inHiPlane=2로 설정하면 PS에서 제공하는 inData 데이터 배열은 다음과 같습니다.
                                                                                    . 1. PS에서 제공하는 inData는 [G] [G] 입니다. ..
                                                    다음과 같이 픽셀을 찾습니다.
먼저 요청하는 채널 수는 평면으로 설정됩니다.
planes=inHiPlane-inLoPlane+1; //채널 수
uint8 * pixel=(uint8*)inData ;
다음과 같이 (x, y) 위치에 인덱스 k가 있는 채널 데이터 표현식을 얻습니다.
픽셀 [ y * inRowBytes + x * planes + k ]
또는
*(픽셀 + y * inRowBytes + x * 평면 + k);
                                                                             > , y) 위치는 다음과 같습니다: x * 3 + 1 ] // p( x,y).G
pixel [ y * inRowBytes + x * 3 + 2 ]; // p(x,y).R

좋아요, 위의 기초를 바탕으로 다음과 같은 Gaussian 3*3 템플릿 처리를 볼 수 있습니다. Gaussian 3*3 템플릿은 다음과 같습니다:
1 2 1
2 4 2 /16
1 2 1
위의 픽셀 위치 지정 방법을 사용하여 다음 루프 처리의 내용을 쉽게 작성합니다. :

Gaussian Blur(3*3 템플릿)
sum=0;
// 각 채널을 차례로 처리
(k=0;kg_Planes;k++)
{
 
//흐리게 하세요!
sum+=bufferPixels[(j -1)*rowBytes+(i -1)*g_Planes +k];
합계
+=bufferPixels[(j-1)*rowBytes+(i)*g_Planes +k] *2;
합계
+=bufferPixels[(j- 1)*rowBytes+(i+ 1)*g_Planes +k];
sum
+=bufferPixels[j*rowBytes+(i -1)*g_Planes +k] *2;
    sum
+=bufferPixels[j*rowBytes+i*g_Planes +k]*4;
    sum
+=bufferPixels[j*rowBytes+(i+1)*g_Planes +k]*2;
    합계
+=버퍼픽셀[(j+1)*rowBytes+(i-1)*g_Planes +k];
    sum
+=버퍼픽셀[(j+1)*rowBytes +(i)*g_Planes +k]*2;
    합계
+=버퍼픽셀[(j+1)*rowBytes+(i+1)*g_Planes +k];
    sum
= >>4;//即除以16
    픽셀[j*rowBytes+(i) *g_Planes +k]=sum;
}


(5) 결론:
마지막으로 필터 사용 효과의 스크린샷을 살펴보겠습니다. PS가 시작되면 각 플러그인 디렉토리의 플러그인을 검색하여 로드합니다. 해당 메뉴로 이동하세요.
Photoshop용 타사 필터 플러그인 개발 소개
처리 결과:
Photoshop용 타사 필터 플러그인 개발 소개
마지막으로 이 필터의 압축 패키지에 대한 다운로드 링크가 있습니다:
RainDropFilter.rar
설치 방법은 다음과 같습니다. 파일의 압축을 풀고 Photoshop의 필터 설치 디렉터리에 넣기만 하면 됩니다. 예를 들어 Photoshop CS의 경우 필터 설치 디렉터리는
C:Program FilesAdobePhotoshop CS 플러그인 필터” 형식일 수 있습니다.
PS에 대하여 SDK는 Adobe 공식 홈페이지에서 현재 무료인지는 모르겠습니다. . . . .


(6) 참고자료:
(1) Photoshop SDK 6.0.
(2)Photoshop SDK CS.
(3) (Raindrop 필터 알고리즘) 필터: Raindrops: http://www.php.cn/

--------- -- ------------------------------------------------ -- ---
부록: Adobe SDK의 설명!
---------------------------------- --- ----------------
// ADOBE SYSTEMS INCORPORATED
// 저작권 1993 - 2002 Adobe Systems Incorporated
// All Rights Reserved
//
// ADOBE Systems Incorporated
// Copyright 1993 - 2002 Adobe Incorporated
// All Rights Reserved.
//
// 주의 사항: Adobe는 동봉된 Adobe 라이센스 계약
//의 조건에 따라 이
// 파일을 사용, 수정 및 배포하는 것을 허용합니다. 이 파일을 Adobe가 아닌 소스
//에서 받은 경우 해당 파일을 사용, 수정 또는 배포
//하려면 Adobe의 사전 서면 승인이 필요합니다.
//
// 참고: Adobe는 해당 Adobe 라이센스 계약의 조건에 따라 이 파일을 사용, 수정 및 배포하는 것을 허용합니다.
// Adobe가 아닌 타사로부터 이 파일을 얻은 경우 사용, 수정 및 배포하려면 이전에 서명한 Adobe 라이센스 계약이 필요합니다.
//---------------------------------- ---------------------

Photoshop용 타사 필터 플러그인 개발에 대한 더 많은 소개 및 관련 기사를 보려면 PHP 중국어 웹사이트를 주목하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.