photoshopでモザイク作ってみた。

ここって自前のサーバで細々と運営しているのはご覧の通りです。しかしながらそんな事するとOSやツールの脆弱性が発覚した時には素早い対応が必要な訳でございます。それもディストリビューションの絡みがあって、kernelに手を加えたりパッケージ管理システムから逸脱した保守をやっていたりと結構綱渡りなのです。
ほとんどはバーチャルマシン上で動いていますので管理はそう難しい事ではありません。しかし、一部でMySQLつかってなかったりとスタンダードな構成にはほど遠い状態の運用です。利用しているパッケージがあれが無いこれが無いって散々ブーたれる訳ですがパッケージの管理自体を人力に頼っている状態に近いので一つひとつソースを入手してmakeしていたりします。MTOSに関しても状況は同じで、先の脆弱性に対する対応に関してパッケージのアップグレードではなく、ソースに手を入れると言う愚挙に出たりもしたりします。そんなこんなで、ajabonさんごめんね!でもね、直ってないんだ。MTOSが6に変わる頃には何とかなると思います。
さて、本題です。Photoshopのスクリプトって、興味が無い訳ではございません。いまいち使いどころが判らなかったりする為手を出そうにも出せないでいると言うのが実情です。だって、アクション+バッチで充分間に合ってたりしますから、ね?
そんなこんなで手をこまねいている状態なのですが、思い立ってひとつ作ってみる事にしました。

よく見るのですが、小さな画像が並んでいるんだけど、引いて見るとひとつの画像になるようなやつです。ごちゃごちゃ言うよりはbeforeーafterをご覧頂いた方が早いですね。

mozaicTgt.jpgmozaicRslt.jpg

はい、こんな感じです。こちらのかわいらしいお嬢さんはstock.xchngから頂いてきました。

app.preferences.rulerUnits = Units.PIXELS; //change applications ruler units.
var dc = app.activeDocument; //target doc
var wh,ht; //number of split
var px = new Array(); //pixel color collection
var fcc = new Array();  //files color collection
var fcm = new Array();  //Str Channel collection
var fn = new Array();  //source files name list
var fu = new Array();  //use flag array
var bnds=new Array();  //points of selection bounds

//Script UI (get wh and ht)
var dlg = new Window (‘dialog’, ”, [100,100,360,280]);
    msgPnl = dlg.add(‘panel’, [25,10,240,100], ‘split numbers’);
    msgPnl.add(‘statictext’, [15,20,50,40],’width’);
    msgPnl.str1 = msgPnl.add(
        ‘edittext’, [60,20,200,40],”, {multiline:false});
    msgPnl.add(‘statictext’, [15,50,50,70],’height’);
    msgPnl.str2 = msgPnl.add(
        ‘edittext’, [60,50,200,70],”, {multiline:false});

    dlg.btnPnl = dlg.add(‘panel’, [25,110,240,160], ”);
    dlg.btnPnl.submitBtn = dlg.btnPnl.add(
        ‘button’, [10,10,90,35], ‘OK’, {name:’ok’});
    dlg.btnPnl.cancelBtn = dlg.btnPnl.add(
        ‘button’, [100,10,190,35], ‘cancel’, {name:’cancel’});

    dlg.btnPnl.submitBtn.onClick = function (){main()};
    dlg.show();

function main(){ //main
    wh = Number (msgPnl.str1.text);
    ht = Number (msgPnl.str2.text);
    dlg.close();
    getFlCC(); //get source files color list
    var a,df_;
    var df = 600; //reference numbers of diference.
    var dcHeight = dc.height;
    var dcWidth = dc.width;
    var h = Math.floor (dcHeight / ht);  //target unit size(height)
    var w = Math.floor (dcWidth / wh);  //target unit size(width)
    dc.resizeCanvas (w*wh, h*ht, AnchorPosition.TOPLEFT);
    getPxColor(); //get target documents color collection
    dc.resizeImage(w*wh, h*ht, 144);
    for (var i=0;i<ht;i++){
        for (var j=0;j<wh;j++){
            for (var k=0;k<fcc.length;k++){
                df_ = getDiff (px[i][j], k);
                if (df_<df) {
                    df = df_;
                    a = k;
                    }
                }
            imgAdjust (w, h, a);
            bnds = [
                [w*j,h*i],
                [w*j+w,h*i],
                [w*j+w,h*i+h],
                [w*j,h*i+h],
                [w*j,h*i]
                ];
            dc.selection.select (bnds);
            dc.paste(true);
            dc.mergeVisibleLayers();
            fu[a]++;
            df = 600;
            }
        }
    }

function getFlCC(){ //get source files colors
    var srcDir = new Folder (
        Folder.selectDialog (“select a source folder…”));
    var flist = srcDir.getFiles (‘*.jpg’);
    var fobj,tmp,pcr,rd,gr,bl;
    for (var i=0;i<flist.length;i++){
        fu[i] = 0;
        fobj = new File(flist[i].fsName);
        fn[i] = flist[i].fsName;
        tmp = app.open (fobj);
        tmp.resizeImage (1,1);
        pcr = tmp.colorSamplers.add([0,0]);
        rd = pcr.color.rgb.red;
        gr = pcr.color.rgb.green;
        bl = pcr.color.rgb.blue;
        if (rd>gr&&rd>bl){
            fcm[i] = “r”;
            } else if (gr>rd&&gr>bl) {
                fcm[i] = “g”;
                } else if (bl>gr&&rd<bl) {
                    fcm[i] = “b”;
                    } else {
                        fcm[i] = “n”
                        }
        fcc.push([
            Math.floor(rd),
            Math.floor(gr),
            Math.floor(bl)
            ]);
        tmp.close (SaveOptions.DONOTSAVECHANGES);
        }
    }

function imgAdjust(w, h, num){
    var tgFile = new File(fn[num]);
    var tmp = app.open(tgFile);
    var r1 = w / h;
    var r2 = tmp.width / tmp.height;
    var r
    if (r1>r2){
        tmp.resizeImage(w);
        tmp.resizeCanvas (w, h,
            AnchorPosition.MIDDLECENTER);
        }else{
            r = h/tmp.height;
            tmp.resizeImage(tmp.width*r, h);
            tmp.resizeCanvas (w, h,
                AnchorPosition.MIDDLECENTER);
            }
    tmp.selection.selectAll();
    tmp.selection.copy ();
    tmp.c
lose (SaveOptions.DONOTSAVECHANGES);
    }

function getDiff(tg, n){
    var cm_;
    if (tg[0]>tg[1]&&tg[0]>tg[2]){
        cm_ = “r”;
        } else if (tg[1]>tg[0]&&tg[1]>tg[2]) {
            cm_ = “g”;
            } else if (tg[2]>tg[1]&&tg[0]<tg[2]) {
                cm_ = “b”;
                } else {
                    cm_ = “n”
                    }
    var df = Math.abs(tg[0] – fcc[n][0])
            + Math.abs(tg[1] – fcc[n][1])
            + Math.abs(tg[2] – fcc[n][2]);
    if (cm_!=fcm[n]) df += 100;
    if (Math.abs(tg[0]-fcc[n][0])>30) df += 30;
    if (Math.abs(tg[1]-fcc[n][1])>30) df += 30;
    if (Math.abs(tg[2]-fcc[n][2])>30) df += 30;
    df += fu[n]*40;
    return Math.floor(df);
    }

function getPxColor(){
    dc.resizeImage (wh, ht, 1,
        ResampleMethod.BICUBIC);
    for (var i=0;i<ht;i++){
        px[i] = new Array();
        for(var j=0;j<wh;j++){
            pcr = dc.colorSamplers.add([j,i]);
            px[i][j] = [
                Math.floor (pcr.color.rgb.red),
                Math.floor (pcr.color.rgb.green),
                Math.floor (pcr.color.rgb.blue)
                ];
            pcr.remove();
            }
        }
    }<ht;i++){ for="" (var="" j="0;j<wh;j++){" k="0;k<fcc.length;k++){" df_="getDiff" (px[i][j],="" k);="" if="" (df_<ht;i++){ px[i]="new" array();="" for(var="" j="0;j<wh;j++){" pcr="dc.colorSamplers.add([j,i]);" px[i][j]="[" math.floor="" (pcr.color.rgb.red),="" (pcr.color.rgb.green),="" (pcr.color.rgb.blue)="" ];="" pcr.remove();="" }="" }

まずは全貌をご覧いただきました。そう長いコードでもないですね。それでは詳細を見ていきましょう。

app.preferences.rulerUnits = Units.PIXELS; //change applications ruler units.
var dc = app.activeDocument; //target doc
var wh,ht; //number of split
var px = new Array(); //pixel color collection
var fcc = new Array();  //files color collection
var fcm = new Array();  //Str Channel collection
var fn = new Array();  //source files name list
var fu = new Array();  //use flag array
var bnds=new Array();  //points of selection bounds

パラメータを見てみるとわかると思いますがメモリ食います。実行時間はわたしが書いたコードの中ではダントツです。大型の画像では半日パタパタやっているって事なんてざらだったりするから困ったものです。

//Script UI (get wh and ht)
var dlg = new Window (‘dialog’, ”, [100,100,360,280]);
    msgPnl = dlg.add(‘panel’, [25,10,240,100], ‘split numbers’);
    msgPnl.add(‘statictext’, [15,20,50,40],’width’);
    msgPnl.str1 = msgPnl.add(
        ‘edittext’, [60,20,200,40],”, {multiline:false});
    msgPnl.add(‘statictext’, [15,50,50,70],’height’);
    msgPnl.str2 = msgPnl.add(
        ‘edittext’, [60,50,200,70],”, {multiline:false});

    dlg.btnPnl = dlg.add(‘panel’, [25,110,240,160], ”);
    dlg.btnPnl.submitBtn = dlg.btnPnl.add(
        ‘button’, [10,10,90,35], ‘OK’, {name:’ok’});
    dlg.btnPnl.cancelBtn = dlg.btnPnl.add(
        ‘button’, [100,10,190,35], ‘cancel’, {name:’cancel’});

    dlg.btnPnl.submitBtn.onClick = function (){main()};
    dlg.show();

UIは非常に単純です。縦横の分割数を入力します。
メインを追いかける前にファンクション類を解説しましょう。

function getPxColor(){
    dc.resizeImage (wh, ht, 1,
        ResampleMethod.BICUBIC);
    for (var i=0;i<ht;i++){
        px[i] = new Array();
        for(var j=0;j<wh;j++){
            pcr = dc.colorSamplers.add([j,i]);
            px[i][j] = [
                Math.floor (pcr.color.rgb.red),
                Math.floor (pcr.color.rgb.green),
                Math.floor (pcr.color.rgb.blue)
                ];
            pcr.remove();
            }
        }
    }<ht;i++){ px[i]="new" array();="" for(var="" j="0;j<wh;j++){" pcr="dc.colorSamplers.add([j,i]);" px[i][j]="[" math.floor="" (pcr.color.rgb.red),="" (pcr.color.rgb.green),="" (pcr.color.rgb.blue)="" ];="" pcr.remove();="" }="" }

こちらは各単位セルのキーカラーを集める為のルーチン。選択範囲のヒストグラムをどうのこうの…と考えましたが、実際はもっと単純です。
縦横分割数をそのままピクセル数に設定してPhotoshopにバイキュービックにて再サンプルさせます。出来あがったイメージのピクセル毎のカラーを二次元配列に収容します。ちなみにリサイズされたサイズを再度元に戻すのはメーンルーチンだったりします。いいかげんwww

function getFlCC(){ //get source files colors
    var srcDir = new Folder (
        Folder.selectDialog (“select a source folder…”));
    var flist = srcDir.getFiles (‘*.jpg’);
    var fobj,tmp,pcr,rd,gr,bl;
    for (var i=0;i<flist.length;i++){
        fu[i] = 0;
        fobj = new File(flist[i].fsName);
        fn[i] = flist[i].fsName;
        tmp = app.open (fobj);
        tmp.resizeImage (1,1);
        pcr = tmp.colorSamplers.add([0,0]);
        rd = pcr.color.rgb.red;
        gr = pcr.color.rgb.green;
        bl = pcr.color.rgb.blue;
        if (rd>gr&&rd>bl){
            fcm[i] = “r”;
            } else if (gr>rd&&gr>bl) {
                fcm[i] = “g”;
                } else if (bl>gr&&rd<bl) {
                    fcm[i] = “b”;
                    } else {
                        fcm[i] = “n”
                        }
        fcc.push([
            Math.floor(rd),
            Math.floor(gr),
            Math.floor(bl)
            ]);
        tmp.close (SaveOptions.DONOTSAVECHANGES);
        }
    }<bl) {="" fcm[i]="b" ;="" }="" else="" fcc.push([="" math.floor(rd),="" math.floor(gr),="" math.floor(bl)="" ]);="" tmp.close="" (saveoptions.donotsavechanges);="" }

こちらはソースファイルを処理するルーチンです。ちなみに今回用意したファイルは以下の様に700点以上となっています。

mozaicSrc.png

処理的にはソースフォルダのファイルを一つひとつ開いてカラー値を拾います。ヒストグラムがどうとか難しい事はしません。画像を1×1ピクセルに再サンプルします。もうひとつ、一番値の大きいチャンネルも配列に記録しておきましょう。これは後ほど、ペナルティを算出する際に利用する物です。

function getDiff(tg, n){
    var cm_;
    if (tg[0]>tg[1]&&tg[0]>tg[2]){
        cm_ = “r”;
        } else if (tg[1]>tg[0]&&tg[1]>tg[2]) {
            cm_ = “g”;
            } else if (tg[2]>tg[1]&&tg[0]<tg[2]) {
                cm_ = “b”;
                } else {
                    cm_ = “n”
                    }
    var df = Math.abs(tg[0] – fcc[n][0])
            + Math.abs(tg[1] – fcc[n][1])
            + Math.abs(tg[2] – fcc[n][2]);
    if (cm_!=fcm[n]) df += 100;
    if (Math.abs(tg[0]-fcc[n][0])>30) df += 30;
    if (Math.abs(tg[1]-fcc[n][1])>30) df += 30;
    if (Math.abs(tg[2]-fcc[n][2])>30) df += 30;
    df += fu[n]*40;
    return Math.floor(df);
    }

こちらはファンクション名そのまま、引数で渡されたカラー値とn番目のファイルのカラーを比較評価して結果を返すルーチンです。
このあたりのパラメータと言うのは結構いい加減なのですが、割と納得してなかったりするのです。もうちょっとペナルティの値を煮詰めると良くなる様なならない様な…

とりあえずメインを見ましょう。

function main(){ //main
    wh = Number (msgPnl.str1.text);
    ht = Number (msgPnl.str2.text);
    dlg.close();
    getFlCC(); //get source files color list
    var a,df_;
    var df = 600; //reference numbers of difference.
    var dcHeight = dc.height;
    var dcWidth = dc.width;
    var h = Math.floor (dcHeight / ht);  //target unit size(height)
    var w = Math.floor (dcWidth / wh);  //target unit size(width)
    dc.resizeCanvas (w*wh, h*ht, AnchorPosition.TOPLEFT);
    getPxColor(); //get target documents color collection
    dc.resizeImage(w*wh, h*ht, 144);
    for (var i=0;i<ht;i++){
        for (var j=0;j<wh;j++){
            for (var k=0;k<fcc.length;k++){
                df_ = getDiff (px[i][j], k);
                if (df_<df) {
                    df = df_;
                    a = k;
                    }
                }
            imgAdjust (w, h, a);
            bnds = [
                [w*j,h*i],
                [w*j+w,h*i],
                [w*j+w,h*i+h],
                [w*j,h*i+h],
                [w*j,h*i]
                ];
            dc.selection.select (bnds);
            dc.paste(true);
            dc.mergeVisibleLayers();
            fu[a]++;
            df = 600;
            }
        }
    }<ht;i++){ for="" (var="" j="0;j<wh;j++){" k="0;k<fcc.length;k++){" df_="getDiff" (px[i][j],="" k);="" if="" (df_<df)="" {="" df="df_;" a="k;" }="" imgadjust="" (w,="" h,="" a);="" bnds="[" [w*j,h*i],="" [w*j+w,h*i],="" [w*j+w,h*i+h],="" [w*j,h*i+h],="" [w*j,h*i]="" ];="" dc.selection.select="" (bnds);="" dc.paste(true);="" dc.mergevisiblelayers();="" fu[a]++;="" }

まあ、色々とあるのですが、基本的に各種ルーチンを順番に呼び出すのが仕事です。イメージは分割数に応じて割り切れるピクセル数にリサイズされます。
あとはターゲットを移しながら配列の数値を総当たりで比較し最適な物を貼り込んでいきます。配置処理は選択範囲を用意しておき、選択範囲へペーストを行います。
実際に大型の物を作る際は多少の覚悟が必要です。最後に少し大きめの結果をのっけておきます。

mozaicResultOrg.jpg

こちらは目の部分の拡大です。

mozaicCloseup.png

ten_a

Graphic Designer, Scripter and Coder. Adobe Community Professional.

シェアする

1件のコメント

  1. 今ごろ名指しいただいてるのに気づきましたすいませんorz
     ぜんぜん違う目的の作業しててヒントでも転がってればと思って改めて見にきた次第で。
    合理的でロジカルな処理方法、たいへん参考になりました(ばくぜん”^)

コメントを残す