
무료 포토샵 개발기
효과 세 개를 한 번 더
효과 이름이 많다는 것만으로 편집이 완성되지는 않습니다. 이번에는 새 사진에 Cutout·Texturizer·Colored Pencil을 쌓고 순서를 바꿨습니다. 적용 결과를 실행 취소한 뒤 녹화한 액션으로 다시 만들어 같은 결과인지 확인했습니다.
AI 생성 시연 이미지 · 실제 인물 촬영 및 공식 협업 이미지가 아닙니다. 편집 화면과 결과는 실제 포토샷에서 만들었습니다.
출처에서 시작하다.
메뉴의 이름을 실제 처리 코드에 연결한다
이전 편은 원본을 남기는 스마트 필터였습니다. 일반 필터는 선택한 레이어의 픽셀을 처리하는 경로로 들어갑니다. general-filters는 메뉴에서 고른 종류와 매개변수를 받아 렌더 함수로 보내고 선택 영역과 적용 결과를 연결합니다.
이번에 다시 실행한 일반 필터 렌더 검사는 106개 항목을 다룹니다. 이 숫자를 Photoshop의 알고리즘과 완전히 같다는 의미로 쓰지 않습니다. 이름별 처리 경로와 결과가 있는지, 매개변수에 따라 결과가 달라지는지를 확인하는 포토샷 검사입니다.
갤러리는 48개 효과와 여섯 묶음으로 구성했다
gallery-effects의 여섯 그룹은 Artistic, Brush Strokes, Gallery Distort, Sketch, Gallery Stylize, Texture입니다. 총 48개 효과가 있고 종류에 따라 Size·Strength·Tone·Angle·Seed·Ink·Paper 중 필요한 조절만 보여 줍니다. 모든 슬라이더가 모든 효과에 똑같이 적용되는 구조는 아닙니다.
새로 실행한 갤러리 UI 검사는 여섯 메뉴 그룹, 미리보기 취소, 스택 순서와 제어, 원본 보기, 적용 후 실행 취소·복원, 액션 재생을 확인했습니다. 효과 구현은 독립적인 회화·인쇄·종이 질감 처리이며 Adobe 커널을 그대로 가져온 것이 아닙니다.
export const generalFilterNames={...galleryEffectNames,lighting:'Lighting Effects',extrude:'Extrude',lensProfile:'Lens Profile Correction',...legacyFilterNames,...filterNames,...advancedFilterNames,displace:'Displace',smartSharpen:'Smart Sharpen',shakeReduction:'Shake Reduction',shear:'Shear',deinterlace:'De-Interlace',sharpen:'Sharpen',sharpenMore:'Sharpen More',edges:'Find Edges',emboss:'Emboss',solarize:'Solarize',posterize:'Posterize',offset:'Offset',twirl:'Twirl',ripple:'Ripple',lens:'Lens Correction'};
export type GeneralFilterType=keyof typeof generalFilterNames;
export type GeneralFilter=Omit<SmartFilter,'type'> & {type:GeneralFilterType;halftoneShape?:'dot'|'line'|'circle';ditherMode?:'ordered'|'diffusion';ditherColor?:boolean;galleryInk?:string;galleryPaper?:string;lighting?:LightingSettings;extrude?:ExtrudeSettings;lensProfile?:LensProfileSettings;offsetX:number;offsetY:number;edge:'wrap'|'clamp'|'transparent';scale:number;vertical:number;horizontal:number;vignette:number;displacement?:{width:number;height:number;data:string};mapMode?:'stretch'|'tile';redFringe?:number;blueFringe?:number;shear?:number[];removeBlur?:'gaussian'|'motion';iterations?:number;kernel?:number[];divisor?:number;bias?:number};
export const defaultGeneralFilter=(type:GeneralFilterType):GeneralFilter=>({id:crypto.randomUUID(),type,radius:type==='posterize'?6:3,amount:type==='lens'?0:type==='noise'?10:100,threshold:type==='traceContour'||Object.hasOwn(galleryEffectNames,type)?128:0,opacity:1,blend:'normal',enabled:true,angle:0,seed:1,distribution:'uniform',monochromatic:true,offsetX:0,offsetY:0,edge:type==='offset'?'wrap':(type==='lens'||type==='lensProfile')?'transparent':'clamp',scale:100,vertical:0,horizontal:0,vignette:0,...(Object.hasOwn(galleryEffectNames,type)?{galleryInk:'#24202c',galleryPaper:'#f7eedb'}:{}),...(type==='lighting'?{lighting:defaultLighting()}:{}),...(type==='extrude'?{extrude:defaultExtrude(),radius:16,amount:30,angle:-45}:{}),...(type==='displace'?{offsetX:10,offsetY:10,mapMode:'stretch' as const}:{}),...(type==='lens'?{redFringe:0,blueFringe:0}:{}),...((type==='smartSharpen'||type==='shakeReduction')?{removeBlur:(type==='shakeReduction'?'motion':'gaussian') as 'motion'|'gaussian',iterations:5,radius:type==='shakeReduction'?2:1}:{}),...(type==='shear'?{shear:[0,20,-20,20,0]}:{}),...(type==='custom'?{kernel:[0,-1,0,-1,5,-1,0,-1,0],divisor:1,bias:0}:{})});
export function validateGeneralFilters(value:unknown):asserts value is GeneralFilter[]{
if(!Array.isArray(value)||!value.length||value.length>16)throw Error('Choose between 1 and 16 filters.');계산의
안쪽으로.
이번 사진에서 수치와 순서를 바꿨다
공개 포토샷에서 새 AI 시연 PNG를 열고 액션 녹화를 시작했습니다. Cutout의 Tone Detail을 90으로 지정하고 Texturizer와 Colored Pencil을 더했습니다. 선택 효과의 Strength는 65, Opacity는 0.45로 바꾸고 Move Up을 실행했습니다.
화면에 보이는 효과의 조합은 이 과정에서 실제로 만든 것입니다. 적용 후 저장한 결과가 원본과 달라졌는지 확인했습니다. 전후 사진은 별도의 생성 이미지 두 장이 아니라, 같은 생성 원본과 포토샷에서 처리한 출력입니다.
실행 취소 뒤 액션으로 같은 결과를 만들었다
Apply Filters로 적용을 마친 뒤 녹화를 멈췄습니다. Undo 후 레이어의 src가 적용 전과 정확히 같았고, 녹화한 액션을 재생한 뒤에는 첫 적용 결과와 정확히 같았습니다. 저장한 프로젝트를 다시 연 결과도 일치했습니다.
이 검사는 이번 사진과 기록한 세 효과를 대상으로 합니다. 사진이 달라져도 시각적으로 같은 인상을 보장한다는 뜻은 아닙니다. 액션에는 효과의 종류와 수치가 남아야 하고, 같은 입력과 설정에서 같은 결과를 만들 수 있어야 한다는 기준을 확인한 것입니다.
코드를 읽다.
export const supportsPrecisionGeneralFilter=(type:string)=>!!smartFilterType(type)||hasPrecisionGeneralKernel(type);
export function canGeneralFilter(doc:StudioDoc,l?:Layer,type?:string){return (!(doc.precision&&(doc.precision.depth!==8||doc.precision.space!=='RGB'))||!!type&&supportsPrecisionGeneralFilter(type))&&!!l&&!layerLocked(doc,l,'pixels')&&(l.kind==='image'||l.kind==='paint')&&!l.smartObject&&!l.psdData?.placedLayer&&!l.shape&&!l.gridWarp&&!l.puppet;}
const check=(cancel:()=>boolean)=>{if(cancel())throw Error('Filter cancelled. No changes applied.');};export async function applyGeneralFilters(doc:StudioDoc,layer:Layer,filters:GeneralFilter[],cancel=()=>false):Promise<StudioDoc>{
validateGeneralFilters(filters);if(doc.precision&&(doc.precision.depth!==8||doc.precision.space!=='RGB')&&filters.some(f=>f.enabled&&f.opacity&&!supportsPrecisionGeneralFilter(f.type)))throw Error('This stack contains a filter that requires RGB 8-bit. Remove it or convert explicitly in Image > Color Mode and Precision.');if(!canGeneralFilter(doc,layer,'gaussian')||!filters.every(f=>!f.enabled||!f.opacity||canGeneralFilter(doc,layer,f.type)))throw Error('Select an unlocked raster layer. Use Rasterize Layer for text, shapes or Smart Objects.');if(layer.width*layer.height>16_000_000)throw Error('General filters support up to 16 megapixels per layer.');
if(doc.precision&&(doc.precision.depth!==8||doc.precision.space!=='RGB')){
check(cancel);const before=await precisionLayer(layer,doc.precision,{masks:false});check(cancel);const after=await renderPrecisionGeneralFilters(before,filters,1,cancel),clip=doc.selection?selectionForLayer(doc.selection,layer):undefined,n=precisionChannels(before.space),stride=n+1;const clamp=(v:number,a=0,b=255)=>Math.max(a,Math.min(b,v));
/** Independent painting, printmaking and paper-texture effects; not Adobe kernels. */
export async function galleryEffectPixels(image:ImageData,f:GeneralFilter,ratio=1,cancel=()=>false){
const {width:w,height:h,data:src}=image,n=w*h;
if(n>4_000_000)throw Error('Gallery painting and texture effects support up to 4 MP.');
const check=async()=>{await new Promise<void>(r=>setTimeout(r,0));if(cancel())throw Error('Filter cancelled. No changes applied.');};편집 화면에서.

크기와 정밀도 제한도 현재 코드로 확인한다
일반 필터는 레이어당 16MP 제한을 확인하고, 회화·질감 갤러리 효과는 별도로 4MP를 제한합니다. 이번 1536×1024 시연 사진은 두 제한 안에 있습니다. 제한을 넘는 처리를 성공한 것처럼 기록하지 않았습니다.
일부 필터는 정밀도 전용 커널을 사용하므로 일반 필터 전체가 RGB8 전용이라고 단정하지 않았습니다. 필터마다 지원 경로가 다르다는 것이 현재 코드의 상태입니다. 다음 편에서는 효과 목록 대신 메시를 움직이는 Liquify와 전용 작업창으로 넘어갑니다.
원본과 편집 결과.


AI 생성 시연 이미지 · 실제 인물 촬영 및 공식 협업 이미지가 아닙니다. 편집 화면과 결과는 실제 포토샷에서 만들었습니다.