着せ替えアプリの試作(1)

イラストの服装を考える時に便利かもしれないな…という事で、Twitterに投稿した絵の派生でアプリを作ってみました。

元ネタ画像

着せ替え遊びを楽しむには、かなり寂しいボリュームですけど…

CreateJS(EaselJS + PreloadJS)で実装をしています。以下はTypeScriptから生成されたものになりますが、かなりの機能をCreateJS側で処理出来たため、かなり少ないコード量で実現できました。

これ以外の選択肢としてはenchant.jsD3.jsによる実装を考えていましたが、CreateJSは実現したい事と提供されている機能の相性が良かったため、正解だったかも。

// ===========================================================================
/*!
 * @brief Character Image Collage
 * @author @MizunagiKB
 */
// -------------------------------------------------------------- reference(s)
/// <reference path="easeljs/easeljs.d.ts" />
/// <reference path="preloadjs/preloadjs.d.ts" />

var E_LAYER; (function (E_LAYER) {
    E_LAYER[E_LAYER["BODY"] = 0] = "BODY";
    E_LAYER[E_LAYER["WEAR"] = 1] = "WEAR";
})(E_LAYER || (E_LAYER = {}));

var GLOBAL = {
    STAGE: null,
    STAGE_LAYER: {},
    STAGE_RESOURCE_IMG: {},
    PRELOADER_QUEUE: null,
    PRELOADER_ORDER: {}
};

/*! */
function evt_pressmove(oCEvt) {
    var oCItem = GLOBAL.STAGE_RESOURCE_IMG[oCEvt.target.id];
    var oCInstance = oCEvt.target;
    var oCPos = oCInstance.offset;
    var nX = oCEvt.stageX + oCPos.x;
    var nY = oCEvt.stageY + oCPos.y;
    var nAdjustX = nX - oCItem.fit[0];
    var nAdjustY = nY - oCItem.fit[1];
    if (Math.abs(nAdjustX) < 16) {
        if (Math.abs(nAdjustY) < 16) {
            nX = nX - nAdjustX;
            nY = nY - nAdjustY;
        }
    }
    oCInstance.x = nX;
    oCInstance.y = nY;
    GLOBAL.STAGE.update();
}

/*! */
function evt_pressup(oCEvt) { var oCInstance = oCEvt.target; }

/*! */
function evt_mousedown(oCEvt) {
    var oCInstance = oCEvt.target;
    var oCPos = oCInstance.offset;
    oCInstance.addEventListener("pressmove", evt_pressmove);
    oCInstance.addEventListener("pressup", evt_pressup);
    oCInstance.offset = new createjs.Point(oCInstance.x - oCEvt.stageX, oCInstance.y - oCEvt.stageY);
}

/*! */
function evt_preload_fileload(oCEvt) {
    var oCItem = GLOBAL.PRELOADER_ORDER[oCEvt.item.src];
    var oCBitmap = new createjs.Bitmap(oCEvt.result);
    oCBitmap.x = oCItem.pos[0];
    oCBitmap.y = oCItem.pos[1];
    GLOBAL.STAGE_LAYER[oCItem.layer].addChild(oCBitmap);
    GLOBAL.STAGE_RESOURCE_IMG[oCBitmap.id] = oCItem;
    switch (oCItem.layer) {
        case E_LAYER.BODY: { } break;
        case E_LAYER.WEAR: { oCBitmap.addEventListener("mousedown", evt_mousedown); } break;
    }
}

/*! */
function import_from_url(strUrl) {
    $.getJSON(strUrl, function (oCJson) {
        var loadItem = [];

        for (var _i = 0, _a = oCJson.body.items; _i < _a.length; _i++) {
            var o = _a[_i];
            o.layer = E_LAYER.BODY;
            GLOBAL.PRELOADER_ORDER[o.src] = o;
            loadItem.push({ "src": o.src });
        }

        for (var _b = 0, _c = oCJson.wear.items; _b < _c.length; _b++) {
            var o = _c[_b]; o.layer = E_LAYER.WEAR;
            GLOBAL.PRELOADER_ORDER[o.src] = o; loadItem.push({ "src": o.src });
        }
        GLOBAL.PRELOADER_QUEUE.addEventListener("fileload", evt_preload_fileload);
        GLOBAL.PRELOADER_QUEUE.addEventListener("complete", function (oCEvt) {
            GLOBAL.PRELOADER_ORDER = null;
            GLOBAL.STAGE.update();
            console.log("Ready.");
        });
        GLOBAL.PRELOADER_QUEUE.loadManifest(loadItem, false);
        GLOBAL.PRELOADER_QUEUE.load();
    });
}

/*! */
function main() {
    GLOBAL.STAGE = new createjs.Stage("main_surface");
    GLOBAL.STAGE_LAYER[E_LAYER.BODY] = new createjs.Container();
    GLOBAL.STAGE_LAYER[E_LAYER.WEAR] = new createjs.Container();
    GLOBAL.STAGE.addChild(GLOBAL.STAGE_LAYER[E_LAYER.BODY]);
    GLOBAL.STAGE.addChild(GLOBAL.STAGE_LAYER[E_LAYER.WEAR]);
    GLOBAL.PRELOADER_QUEUE = new createjs.LoadQueue(false);
    import_from_url("ein.json");
}

やっている事はシンプルにCreateJSのマウスドラッグイベントで処理しているだけなのですが、CreateJSでは画像を対象とした際に、アルファ値を考慮した動作をしてくれます。

というわけで、自分よりも後ろの画像を消すのを兼ねてクリック可能領域を不透明に塗るだけです。

 

分かりやすく赤色にしてあります。

とまぁ、思わず作成してしまったのですが、着せ替え用のキャラクター画像って服を重ねる関係上、すこし細めに描いておいたほうが良さそうだって事がわかりました。