はじめに

[Node.js] 無料で簡単にウェブアプリを公開できるサービス「Glitch」を使ってみた!
上記の記事読んで、思いついたからやりました。
Node.jsを触ったことがありませんでしたが、簡単に作れました。

作ったの

Glitch_Image.png
https://glitch-img.glitch.me

使い方

  1. 画像を選択してください。
  2. アップロードボタンを押します。
  3. 素敵に最適化されたGlitch画像がダウンロードされます。

IMG_20161126_125121.jpg
ただの風景写真が

glitch_IMG_20161126_125121.jpg
欠けた器に美しさを感じるような、侘び寂び感ある写真に変わりますね。

処理の流れ

  1. アップロードされたファイルをtmpに保存します。
  2. imagemagickを通してクリーニングを行います。
  3. glitch-canvas通してフィルターをかけます。
  4. imagemagickを通して壊れた表示の壊れてないファイルに変換を行います。
  5. jpegoptimかoptipngで画像の最適化を行います
  6. ユーザーにダウンロードさせます。

なんで作ったの?

タイトルを思いついてしまったので。
node.jsで遊んでみたかったのです。
レンタルサーバーで動いているシステムの画像を最適化を行いたいのですが、
最適化は該当サーバではできそうにもないので、
外部で最適化を行い上書きするテスト。

はまったところというか作成時の覚書

間違ったnpmをinstallしてしまった。

package.jsonを直接書き換えれば自動でuninstallを依存関係含めて行なってくれます。

Glitchからディスク容量オーバーエラーが出てくる。

Glitchではディスク容量が125MBまででした。
tmpフォルダや使用しない画像は処理後に削除する処理を加えました。

アップロード失敗したり、通信切られたらファイル残るぞ

本来であればcronでガベージコレクション回したいのですが、Glitchでは難しそうです。
ダウンロード完了フラグで削除処理を別口かアクセスがあるたびに加えても良いかもしれません。

ディスク容量オーバーエラーどうやって解決するんだよ。

Advanced Optionsにconsoleがあったのでrm -rf

jsなのにPromise動かないのかよ

requireで呼び出さないと動きません。

jpegoptimがファイルが壊れているとか言ってるけど

画像ファイルを自ら壊しているのがglitch処理なので、imagemagick通して壊れた表示の壊れてないファイルにする必要がありました。
それでもエラー出ることありますが、握りつぶしました。

jpgアップロードしてもpngになるんだけど

glitch-canvasのデフォルトというか、
Canvasがpng返すようなので、これもimagemagick通して元の拡張子に戻しました。
引数やプロパティでjpg返すこともできそうですが、手が止まりそうだったのでimagemagickで行なっています。

mineってなんだよ

mimeのタイポです。

余談

どのくらい時間かかった?

nodeの本を読みながら3時間くらいです。

console動くならPHPとかも動くの?

/usr/bin/php -r "echo time();"
とかできたので色々できそうではあります。

このデザインで使っているのは?

CSS フレームワークのBulmaです。
https://bulma.io

グリッチ処理ってなんなの?

意図的に画像ファイルを壊れた表示にする処理です。
下記サイトが参考になるかもしれません。
http://www.osadagenki.com/gvgas/ja/about-glitch-art/

ソースコードみたい

下記URLからどうぞ
こうやって簡単に公開できるのがglitch強みですね。
https://glitch.com/edit/#!/glitch-img
追記Qiitaにもコード貼っておく

server.js
// server.js
// where your node app starts

/**
 * init project
 */
var express = require('express');
var app = express();
var fs = require("fs");
var fileType = require('file-type');
var Promise = require('promise');
var glitch = require('glitch-canvas');

var bodyParser = require('body-parser');
var multer = require("multer");

var im = require('imagemagick');
var execFile = require('child_process').execFile;
var jpegoptim = require('jpegoptim-bin');
var optipng = require('optipng-bin');

/**
 * 《JavaScript》範囲指定したランダムな値を得るスクリプト。
 * https://qiita.com/uto-usui/items/7193db237175ba15aaa3
 * @param {number} min
 * @param {number} max
 * @return {number}
 */
var randRange = function (min, max) {
  return Math.floor(Math.random() * (max - min + 1) + min);
};
/**
 * body encode
 */
app.use(bodyParser.urlencoded({extended: false}));

/**
 * upload
 */
app.use(multer({dest: './tmp/'}).single('file'));


// we've started you off with Express,
// but feel free to use whatever libs or frameworks you'd like through `package.json`.
// http://expressjs.com/en/starter/static-files.html
app.use(express.static('public'));

/**
 * router
 */
/**
 * view index.html
 */
app.get("/", function (in_request, in_response) {
  in_response.sendFile(__dirname + '/views/index.html');
});

/**
 * Optimize and return the uploaded image
 */
app.post('/upload', function (in_request, in_response) {

  var tmpFilePath = in_request.file.path;
  var filePath = __dirname + "/public/images_" + in_request.file.originalname;
  var fileData = {mime: "", ext: ""};
  new Promise(function (resolve, reject) {
    /**
     * read tmp image
     */
    fs.readFile(tmpFilePath, function (error, data) {
      if (error) reject(error);
      fileData = fileType(data);
      resolve(data);
    });
  })
    .then(function (data) {
      /**
       * write image
       */
      return new Promise(function (resolve, reject) {
        fs.writeFile(filePath, data, function (error) {
          if (error) reject(error);
          resolve();
        });
      });
    })
    .then(function () {
      /**
       * convert image
       */
      return new Promise(function (resolve, reject) {
        var options = [];
        if (fileData.mime === 'image/jpeg') {
          options = [
            filePath,
            '-quality',
            '100',
            '-auto-orient',
            '-format',
            'jpg',
            filePath
          ];
        } else if (fileData.mime === 'image/png') {
          options = [
            filePath,
            '-format',
            'png',
            filePath
          ];
        } else {
          resolve();
        }
        im.convert(options, function (error, stdout) {
          if (error) reject(error);
          console.log(stdout);
          resolve();
        });
      });
    })
    .then(function () {
      /**
       * read image
       */
      return new Promise(function (resolve, reject) {
        fs.readFile(filePath, function (err, buffer) {
          if (err) reject(err);
          resolve(buffer);
        })
      })
    })
    .then(function (buffer) {
      /**
       * generate glitch image
       */
      // random
      /*
      var glitchParams = {
        seed:       randRange(0,99), // integer between 0 and 99
        quality:    randRange(0,99), // integer between 0 and 99
        amount:     randRange(0,99), // integer between 0 and 99
        iterations: randRange(0,99)  // integer
      };
      */
      return glitch()
        .fromBuffer(buffer)
        .toBuffer();
    })
    .then(function (glitchImageBuffer) {
      /**
       * write glitch image
       */
      return new Promise(function (resolve, reject) {
        fs.writeFile(filePath, glitchImageBuffer, function (err) {
          if (err) reject(err);
          resolve();
        });
      });
    })
    .then(function () {
      /**
       * convert image
       */
      return new Promise(function (resolve, reject) {
        var options = [];
        if (fileData.mime === 'image/jpeg') {
          options = [
            filePath,
            '-quality',
            '100',
            '-auto-orient',
            '-format',
            'jpg',
            filePath
          ];
        } else if (fileData.mime === 'image/png') {
          options = [
            filePath,
            '-format',
            'png',
            filePath
          ];
        } else {
          resolve();
        }
        im.convert(options, function (error, stdout) {
          if (error) reject(error);
          console.log(stdout);
          resolve();
        });
      });
    })
    .then(function () {
      /**
       * optimize glitch image
       */
      return new Promise(function (resolve, reject) {
        var options = [];
        if (fileData.mime === 'image/jpeg') {
          options = [
            '-o',
            '--strip-all',
            '-m80',
            filePath
          ];

          execFile(jpegoptim, options, function (error) {
            if (error) reject(error);
            resolve();
          });

        } else if (fileData.mime === 'image/png') {
          options = [
            '-strip',
            'all',
            '-o2',
            filePath
          ];

          execFile(optipng, options, function (error) {
            if (error) reject(error);
            resolve();
          });

        } else {
          resolve();
        }

      });

    })
    .then(function () {
      /**
       * download glitch image
       */
      return new Promise(function (resolve, reject) {
        in_response.download(filePath, "glitch_" + in_request.file.originalname, function (error) {
          if (error) reject(error);
          resolve()
        });
      });
    })
    .then(function () {
      /**
       * image delete
       */
      return new Promise(function (resolve, reject) {
        fs.unlink(filePath, function (error) {
          if (error) reject(error);
          resolve();
        });
      });
    })
    .then(function () {
      /**
       * tmp file delete
       */
      return new Promise(function (resolve, reject) {
        fs.unlink(tmpFilePath, function (error) {
          if (error) reject(error);
          resolve();
        });
      });
    })
    .then(function () {
      /**
       * response end
       */
      in_response.end();
    })
    .catch(function (error) {
      console.log({error: error});
    });

});

// listen for in_requests :)
var listener = app.listen(process.env.PORT, function () {
  console.log('Your app is listening on port ' + listener.address().port);
});



まとめ

  • Glitchは簡単にサクッと作りたいものに集中できて良かった。
  • npmすごい、他のプログラムにも欲しい。
  • 基本的な処理が非同期で行われることの素晴らしみがある。
  • 思いつきでも手を動かすと楽しい。
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.