UXP for Photoshop:psjsでノイズフィルター作るサンプルの確認

UXP for Photoshop:psjsでノイズフィルター作るサンプルの確認

シリーズ投稿。Photoshopフィルタとしてsbsarを使う

psjsだけでノイズフィルター作ってる例があったので実装を見てみる。
psjsは単独ファイルで動くプラグイン。jsxぽく先祖返りした感じがする。
https://developer.adobe.com/photoshop/uxp/2022/assets/874d23d0b54117a6e72ee98bbe79c41b/imaging.psjs (ファイル直リンク注意)

※ 記事執筆時、最新のフォトショップにおいて、このコードそのままではgetPixelsがモーダルモードの制限に引っかかり動かなかったが、そこは気にせずにコードの確認を行う。

ソースコードの確認


async function jsFilter(augmentMode, width, height) {
    try {
        const imaging = require('photoshop').imaging_beta;
        const app = require('photoshop').app;

        if(app.activeDocument === null) throw new Error("You must have a document open for this script.");
        if(app.activeDocument.activeLayers.length === 0) throw new Error("You must have a layer selected for this script.");

        await require("photoshop").core.executeAsModal(
            async () => {
                function noise() {return Math.floor(Math.random()*256)}
                function invert(val) {return 255 - val}

                let channels = 3; // RGB
                const doc = app.activeDocument;
                const docWidth = doc.width;
                const docHeight = doc.height;
                const center = {x: Math.round(docWidth/2), y: Math.round(docHeight/2)}

                let arr;
                let hasAlpha = false;
                if (augmentMode === 'invert') {
                    // get 
                    const imgData = await imaging.getPixels({
                        sourceBounds: {
                            left: center.x - (width/2),
                            top: center.y - (height/2),
                            right: center.x + (width/2),
                            bottom: center.y + (height/2)
                        },
                        targetSize: {height: height, width: width}
                    });
                    arr = await imgData.imageData.getData(); 
                    channels = imgData.imageData.components;
                    hasAlpha = imgData.imageData.hasAlpha;
                } else {
                    arr = new Uint8Array(channels * width * height);
                }

                // Process by component chunks
                for (let i=0; i<channels * width * height; i = i + channels) {
                    switch (augmentMode) {
                        case 'red':
                            // full on the red channel
                            arr[i] = 255; 
                            // leave G & B channels at 0
                        break;
                        
                        case 'invert':
                            const affectChannels = hasAlpha ? channels - 1 : channels
                            for (let k=0; k<affectChannels; k++) { //RGBA, skip A
                                arr[i+k] = invert(arr[i+k]);
                            }
                        break;
                            
                        case 'noise':
                            for (let k=0; k<channels; k++) {
                                arr[i+k] = noise();
                            }
                        break;
                    }
                }
                const imageData = await imaging.createImageDataFromBuffer(
                    arr,
                    {
                        width: width,
                        height: height,
                        components: channels,
                        colorProfile: "sRGB IEC61966-2.1",
                        colorSpace: "RGB"
                    }
                );
                await imaging.putPixels({
                    layerID: doc.activeLayers[0].id,
                    imageData: imageData,
                    targetBounds: {top: center.x - (height/2), left: center.x - (width/2)},
                    replace: false
                });

                // Release image data immediately
                imageData.dispose();
            }
        )
    }catch (e) {
        require('photoshop').core.showAlert(e.message);
    }
}

await jsFilter('noise', 400, 300);
// await jsFilter('invert', 250, 150);
// await jsFilter('red', 100, 100);
  • imaging_betaという開発中のモジュールを使う。つまり今後動かなくなるコード。
  • noise、invert、redの3つのフィルタ処理が実装されている。
  • invertではimaging.getPixelsで選択中のレイヤ?のピクセル情報を取得しているが、矩形の指定が複雑なのはドキュメントの真ん中から指定サイズ分を編集するから。
  • それ以外のモードでは新規でピクセルを作る模様。
  • ピクセルデータはUint8Arrayの配列にごそっと展開される。配列にはチャンネル情報や縦幅横幅の情報は含まれていない。
  • データはRGB(A)RGB(A)の順に並んでいる模様。
  • createImageDataFromBufferで配列を画像データに戻す。
  • putPixelsでレイヤーに戻す。
  • 作成したimageDataは明示的に破棄する必要がありそう。
  • わかりづらいが、imgDataはオブジェクト型で内部にimageData型のメンバを持つ。imageDataはimageData型そのもの。

考察

速度的にどうなのかは怪しいが、ピクセルを処理するのは意外と簡単。配列をそのままファイルに書き出せば外部での編集もできるかも。素直にレイヤーのエクスポート命令があるならそれを使いたいが。

権限周りどうなってるのかわからないが、単独ファイルで処理できることならそのほうがお手軽。自分が作りたい処理もこれでいいのかも。

NEXT TODO

ついに手を動かすとき!?

  • メニューからコマンド実行させる 。
  • レイヤー名で画像を書き出す。
  • python編へ

前の投稿

次の投稿

N/A

Previous post UXP for Photoshop:fsモジュールとentrypoint.setupのドキュメント確認
Next post UXP for Photoshop:Photoshopで動かすサブスタンスフィルター できた報告 報告のみ