box-shadowを用いたCSSを作るcliツールを作りました

昨年のことと記憶していますが、box-shadow を用いた CSS でピクセルアートをするという話を LT で聞きました。
面白いとは思ったものの、私自身やるかという点では疑問符がつきました。

時を経て、Node.js を用いた cli ツールについて学ぶ中で、画像を CSS に変換できればよさそうだと考えました。
今回は、画像を box-shadow を用いた CSS に変換するツールを作ったので、そちらの紹介です。


目次

動作環境

以下の環境で動作確認済みです。

  • Windows10 1909
  • Node.js v12.12.0

参考

利用ライブラリ

今回利用したライブラリについて紹介します。

commander

npm - commander
コマンドライン引数を取り扱いやすくし、-h で表示されるヘルプを生成急いてくれるパッケージです。

jimp

npm - jimp
Javascript で」記述された画像処理ライブラリです。
ネイティブに異存がないので、導入がとても気軽にできます。

実装

convert_image_to_box_shadow.js を、以下の通り実装しました。

ソースコード

convert_image_to_box_shadow.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
const program = require("commander");
var Jimp = require("jimp");
const outputFile = require("output-file");

program
.option(
"-o, --output <type>",
'out file name(default param is "public/dst/output.css")'
)
.option(
"-i, --input <type>",
'input file name(default param is "src/input.png")'
)
.option("-e, --enlargement <type>", "enlargement(default param is 1)")
.option("-s, --split <type>", "pixel split size(default param is 1)")
.option(
"-t, --target <type>",
'output target is id(default param is ".mosaic").'
);

program.parse(process.argv);

const outputFileName =
program.output === undefined ? "public/dst/output.css" : program.output;
const inputFileName =
program.input === undefined ? "src/input.png" : program.input;
const split = program.split === undefined ? 1 : Number(program.split);
const enlargement =
program.enlargement === undefined ? 1 : Number(program.enlargement);
const target = program.target === undefined ? ".mosaic" : program.target;

Jimp.read(inputFileName, (err, image) => {
if (err) throw err;
image = image
.resize(
Math.round(image.bitmap.width * enlargement),
Math.round(image.bitmap.height * enlargement)
)
.pixelate(split);
let styletext = "";
styletext += `${target} {position: relative;}`;
styletext += `${target}::before {width:${split}px;height:${split}px;content: "";position: absolute;top: -${split}px;left: -${split}px;box-shadow:`;
for (let y = 0; y < image.bitmap.height; y += split) {
for (let x = 0; x < image.bitmap.width; x += split) {
const c = Jimp.intToRGBA(image.getPixelColor(x, y));
styletext += `${Number(split) + x}px ${y}px rgba(${c.r},${c.g},${c.b},${
c.a
}),`;
}
}
styletext = styletext.slice(0, styletext.length - 1);
styletext += "}";

(async () => {
await outputFile(outputFileName, styletext);
})();
});

package.json

package.json を以下の通り用意しました。

package.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"name": "convert_image_to_box_shadow",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"server": "serve public",
"convert": "node convert_image_to_box_shadow.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"commander": "^4.1.0",
"jimp": "^0.9.3",
"output-file": "^2.0.2"
},
"devDependencies": {
"serve": "^11.3.0"
}
}

###

1
2
3
4
5
6
7
8
9
10
11
<html>
<head>
<link rel="stylesheet" type="text/css" href="dst/output.css" />
<title>sample[convert_image_to_box_shadow]</title>
</head>
<body>
<div class="container">
<div class="mosaic"></div>
</div>
</body>
</html>

ディレクトリ構成

最終的に以下のディレクトリ構成になっています。

1
2
3
4
5
6
7
8
9
10
11
12
13
.
| node_modules
| .gitignore
| convert_image_to_box_shadow.js
| package-lock.json
| package.json
| README.md
+---public
| +---index.html
| \---dst
| +---output.css
\---src
+---input.png

確認

変換

以下コマンドで、画像から css への変換を実行できます。

1
2
3
node convert_image_to_box_shadow.js
# もしくは
npm run convert

オプションを設定しない場合、src/input.pngを読み込みpublic/dst/output.cssを出力します。
出力された output.css は以下のようになっています。

1
2
3
4
5
6
7
8
9
10
11
12
13
.mosaic {
position: relative;
}
.mosaic::before {
width: 16px;
height: 16px;
content: "";
position: absolute;
top: -16px;
left: -16px;
box-shadow: 16px 0px rgba(222, 133, 121, 255), 32px 0px rgba(218, 125, 109, 255),
48px 0px rgba(226, 127, 103, 255), ... ... ... 512px 496px rgba(117, 38, 66, 255);
}

オプション確認

実行オプションは以下を実行することで、表示できます。

1
2
3
node convert_image_to_box_shadow.js -h
# もしくは
npm run convert -- -h

実行すると、こちらが表示されます。
commander素晴らしいです。

1
2
3
4
5
6
7
8
9
Usage: convert_image_to_box_shadow [options]

Options:
-o, --output <type> out file name(default param is "public/dst/output.css")
-i, --input <type> input file name(default param is "src/input.png")
-e, --enlargement <type> enlargement(default param is 1)
-s, --split <type> pixel split size(default param is 1)
-t, --target <type> output target is id(default param is ".mosaic").
-h, --help output usage information

表示

以下のコマンドで、ローカルサーバーを起動して作成された css を確認できます。

1
npm run server

http://localhost:5000 にアクセスしてみると以下の画面が表示されるはずです。

css を読み込むことで画像を表示できました。

オプションの画像

ここまでは、box-shadow の大きさを 1×1 ピクセルとする標準で動かしてきました。
今回は、以下を実行して box-shadow の大きさを 16×16 ピクセルに変更してみます。

1
2
3
node convert_image_to_box_shadow.js -s 16
# または
npm run convert -- -s 16

再度以下のコマンドでローカルサーバーを起動し、確認します。

1
npm run server

http://localhost:5000 にアクセスしてみるとモザイク化した以下の画面が表示されるはずです。
変換には、jimpを使用しています。

比較

今回 box-shadow を用いた CSS で画像を表示してみましたが、気になったのは容量でした。
今回の元データのサイズが 512×512 で 301KB。
変換後の CSS は、8286KB でした。
重くなっちゃったじゃん

残念なお知らせでした。

試しに、前述の box-shadow の大きさを 16×16 ピクセルにした場合は、33KB になりました。
同じ処理をした場合の.png ファイルは 7KB でした。
やっぱり重くなっちゃったじゃん

文字列ってバイナリに比べて容量を喰うんだなと再認識した次第でした。


今回は、画像から box-shadow を用いた CSS を作る cli ツールを作りました。
結果からすると、容量の観点から有利なことがないので、実運用はしないというのが感想です。
ただし、commanderが有用だったので、次に cli ツールを作成するときにも使っていきたいと思いました。

ではでは。