Node.jsで美しいSVGプレースホルダーを生成します







SVG画像をプレースホルダーとして使用することは、特に私たちの世界では、ほとんどすべてのサイトが非同期でロードしようとしている写真の束で構成されている場合、非常に良いアイデアです。 写真が多く、ボリュームが大きいほど、ユーザーがそこに何がロードされているのかよくわからないという事実から始まり、写真をロードした後のインターフェース全体の有名なジャンプで終わる、さまざまな問題が発生する可能性が高くなります。 特にお使いの携帯電話からのインターネットの質が悪い場合-複数の画面で飛び去ることがあります。 そのような瞬間に、スタブが助けになります。 それらを使用する別のオプションは検閲です。 ユーザーから画像を非表示にする必要がある場合がありますが、ページの全体的なスタイル、画像が占める色、場所を維持したいです。







しかし、ほとんどの記事で、誰もが理論について話しています。これらのスタブ画像をすべてインラインでページに挿入するといいと思います。今日は、Node.jsを使用して好みに合わせて色を生成する方法を実際に見ていきます。 SVG画像からハンドルバーテンプレートを作成し、色やグラデーションを使用した単純な塗りつぶしから三角測量、ボロノイモザイク、フィルターの使用など、さまざまな方法でそれらを塗りつぶします。 すべてのアクションは段階的に分類されます。 この記事は、これがどのように行われるかに興味があり、アクションの詳細な分析を必要とする初心者にとって興味深いと思いますが、経験豊富な開発者もいくつかのアイデアを好むかもしれません。







準備する



まず、NPMと呼ばれるあらゆる種類のボトムレスリポジトリに移動します。 スタブイメージを生成するタスクには、サーバー側(または静的サイトの場合は開発者のマシンでも)での1回限りの生成が含まれるため、時期尚早な最適化は処理しません。 好きなものをすべて接続します。 そのため、 npm init



スペルから開始し、依存関係の選択に進みます。







まず第一に、これはColorThiefです。 おそらく彼のことをすでに聞いたことがあるでしょう。 画像で最も使用されている色のカラーパレットを分離できる素晴らしいライブラリ。 最初はそのようなものが必要です。







 npm i --save color-thief
      
      





Linuxでこのパッケージをインストールする際に問題が発生しました-NPMディレクトリにないcairoパッケージが欠落しています。 この奇妙なエラーは、いくつかのライブラリの開発バージョンをインストールすることで解決されました。







 sudo apt install libcairo2-dev libjpeg-dev libgif-dev
      
      





このツールがどのように機能するかは、プロセスで確認されます。 ただし、RGBから16進数に色形式を変換するためにrgb-hexパッケージをすぐに接続することは不要です。これはその名前から明らかです。 このような単純な機能を備えたサイクリングは行いません。







 npm i --save rgb-hex
      
      





トレーニングの観点からは、このようなことを自分で書くことは有益ですが、タスクが最小限の機能のプロトタイプを迅速に組み立てることである場合、NPMカタログからのものをすべて接続することは良い考えです。 時間を大幅に節約できます。

プラグの最も重要なパラメーターの1つはプロポーションです。 元の画像の比率と一致する必要があります。 したがって、そのサイズを知る必要があります。 この問題を解決するには、image-sizeパッケージを使用します。







 npm i --save image-size
      
      





異なるバージョンの画像を作成しようとし、それらはすべてSVG形式になるため、何らかの方法でテンプレートの問題が発生します。 もちろん、JSでパターン文字列をかわすことはできますが、なぜこれがすべてなのでしょうか? 「通常の」テンプレートエンジンを使用することをお勧めします。 たとえば、 handlebars 。 シンプルで上品な、私たちの仕事はちょうどいいでしょう。







 npm i --save handlebars
      
      





この実験のために、ある種の複雑なアーキテクチャをすぐには配置しません。 main.jsファイルを作成し、すべての依存関係とファイルシステムを操作するためのモジュールをインポートします。







 const ColorThief = require('color-thief'); const Handlebars = require('handlebars'); const rgbHex = require('rgb-hex'); const sizeOf = require('image-size'); const fs = require('fs');
      
      





ColorThiefには追加の初期化が必要です







 const thief = new ColorThief();
      
      





接続した依存関係を使用して、「画像をスクリプトにアップロードする」と「サイズを取得する」という問題を解決することは難しくありません。 画像1.jpgがあるとします。







 const image = fs.readFileSync('1.jpg'); const size = sizeOf('1.jpg'); const height = size.height; const width = size.width;
      
      





Node.jsに不慣れな人にとっては、ファイルシステムに関連するほとんどすべてが同期的または非同期的に発生する可能性があることを言う価値があります。 同期メソッドの場合、名前の最後に「Sync」が追加されます。 不必要な合併症に遭遇しないように、また、突然頭を悩ませないように、それらを使用します。







最初の例に移りましょう。







色塗り









まず、長方形を塗りつぶすという問題を解決します。 写真には、幅、高さ、塗りつぶし色の3つのパラメーターがあります。 長方形でSVG画像を作成しますが、これらの値の代わりに、角括弧のペアと、スクリプトから送信されたデータを含むフィールドの名前を置き換えます。 おそらく、従来のHTMLでこの構文を既に見たことがあるでしょう(たとえば、Vueは似たようなものを使用しています)が、SVGイメージで使用することを気にする人はいません-テンプレートエンジンは、長期的にはどうなるかを気にしません。 テキストは彼とアフリカのテキストです。







 <svg version='1.1' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' preserveAspectRatio='none' height='{{ height }}' width='{{ width }}'> <rect x='0' y='0' height='100' width='100' fill='{{ color }}' /> </svg>
      
      





さらにColorThiefを使用すると、最も一般的な色の1つが得られます。この例では灰色です。 テンプレートを使用するために、このライブラリをコンパイルするようにハンドルバーなどのファイルを読み取り、完成したSVGスタブを含む行を生成します。 テンプレートエンジン自体は、適切な場所でデータ(色とサイズ)を置き換えます。







 function generateOneColor() { const rgb = thief.getColor(image); const color = '#' + rgbHex(...rgb); const template = Handlebars.compile(fs.readFileSync('template-one-color.svg', 'utf-8')); const svg = template({ height, width, color }); fs.writeFileSync('1-one-color.svg', svg, 'utf-8'); }
      
      





結果をファイルに書き込むためだけに残ります。 ご覧のとおり、SVGでの作業は非常に便利です。すべてのファイルはテキストなので、簡単に読み書きできます。 結果は長方形の画像です。 興味深いことは何もありませんが、少なくともアプローチが機能していることを確認しました(完全なソースへのリンクは記事の最後にあります)。







グラデーション塗りつぶし



グラデーションの使用は、より興味深いアプローチです。 ここでは、画像からいくつかの一般的な色を使用して、ある色から別の色にスムーズに移行できます。 これは、長いイメージテープがアップロードされているサイトで時々見られます。













SVGテンプレートは、この非常に勾配で拡張されています。 例として、通常の線形勾配を使用します。 2つのパラメーターだけに興味があります-最初の色と最後の色:







 <defs> <linearGradient id='my-gradient' x1='0%' y1='0%' x2='100%' y2='0%' gradientTransform='rotate(45)'> <stop offset='0%' style='stop-color:{{ startColor }};stop-opacity:1' /> <stop offset='100%' style='stop-color:{{ endColor }};stop-opacity:1' /> </linearGradient> </defs> <rect x='0' y='0' height='100' width='100' fill='url(#my-gradient)' />
      
      





色自体は、同じColorThiefを使用して取得されます。 2つの操作モードがあります。1つの原色を指定するか、指定した色数のパレットを使用します。 十分に快適。 グラデーションには、2色が必要です。







それ以外の場合、この例は前の例と似ています。







 function generateGradient() { const palette = thief.getPalette(image, 2); const startColor = '#' + rgbHex(...palette[0]); const endColor = '#' + rgbHex(...palette[1]); const template = Handlebars.compile(fs.readFileSync('template-gradient.svg', 'utf-8')); const svg = template({ height, width, startColor, endColor }); // . . .
      
      





この方法で、すべての種類のグラデーションを作成できます-必ずしも線形ではありません。 しかし、これでもかなり退屈な結果です。 リモートで元の画像に似たようなモザイクを作成することは素晴らしいことです。







長方形のモザイク



始めに、多くの長方形を作成し、同じライブラリが提供するパレットの色で塗りつぶしましょう。













ハンドルバーはさまざまなことを実行できます。特に、サイクルがあります。 私たちは彼に座標と色の配列を渡し、それから彼はそれを理解します。 それぞれのテンプレートで長方形をラップするだけです:







 {{# each rects }} <rect x='{{ x }}' y='{{ y }}' height='11' width='11' fill='{{ color }}' /> {{/each }}
      
      





したがって、スクリプト自体には、本格的なカラーパレットがあり、X / Y座標をループして、パレットからランダムな色の四角形を作成します。 すべてが非常に簡単です:







 function generateMosaic() { const palette = thief.getPalette(image, 16); palette.forEach(function(color, index) { palette[index] = '#' + rgbHex(...color); }); const rects = []; for (let x = 0; x < 100; x += 10) { for (let y = 0; y < 100; y += 10) { const color = palette[Math.floor(Math.random() * 15)]; rects.push({ x, y, color }); } } const template = Handlebars.compile(fs.readFileSync('template-mosaic.svg', 'utf-8')); const svg = template({ height, width, rects }); // . . .
      
      





明らかに、モザイクは、写真と色は似ていますが、色の配列がありますが、すべてが望んでいるようなものではありません。 この分野でのColorThiefの機能は限られています。 多かれ少なかれ同じ色のレンガのセットだけでなく、元の画像が推測されるモザイクを取得したいと思います。







モザイクの改善



ここで、もう少し深くして、画像のピクセルから色を取得する必要があります...













通常、このデータを取得するキャンバスがコンソールにないため、get-pixelsパッケージの形式でヘルプを使用します。 彼は私たちがすでに持っている写真でバッファから必要な情報を引き出すことができます。







 npm i --save get-pixels
      
      





次のようになります。







 getPixels(image, 'image/jpg', (err, pixels) => { // . . . });
      
      





データフィールドを含むオブジェクトを取得します-キャンバスから取得するのと同じピクセルの配列。 座標(X、Y)でピクセルの色を取得するには、簡単な計算を行う必要があることを思い出させてください。







 const pixelPosition = 4 * (y * width + x); const rgb = [ pixels.data[pixelPosition], pixels.data[pixelPosition + 1], pixels.data[pixelPosition + 2] ];
      
      





したがって、各長方形について、パレットからではなく、画像から直接色を取得して使用できます。 このようなものが得られます(ここでの主なことは、画像の座標が0から100までの「正規化」と異なることを忘れないことです)。







 function generateImprovedMosaic() { getPixels(image, 'image/jpg', (err, pixels) => { if (err) { console.log(err); return; } const rects = []; for (let x = 0; x < 100; x += 5) { const realX = Math.floor(x * width / 100); for (let y = 0; y < 100; y += 5) { const realY = Math.floor(y * height / 100); const pixelPosition = 4 * (realY * width + realX); const rgb = [ pixels.data[pixelPosition], pixels.data[pixelPosition + 1], pixels.data[pixelPosition + 2] ]; const color = '#' + rgbHex(...rgb); rects.push({ x, y, color }); } } // . . .
      
      





より美しいものにするために、「ブリック」の数をわずかに増やして、サイズを小さくすることができます。 このサイズをテンプレートに渡さないので(もちろん、画像の幅または高さと同じパラメーターにする価値があります)、テンプレート自体のサイズの値を変更します。







 {{# each rects }} <rect x='{{ x }}' y='{{ y }}' height='6' width='6' fill='{{ color }}' /> {{/each }}
      
      





これで、元の画像のように見えるモザイクができましたが、同時に占有するスペースが1桁少なくなりました。







GZIPはテキストファイル内のこのような繰り返しシーケンスを適切に圧縮することを忘れないでください。そのため、ブラウザに転送すると、そのようなプレビューのサイズはさらに小さくなります。

しかし、さらに先に進みましょう。







三角測量









長方形は良いですが、三角形は通常、はるかに興味深い結果をもたらします。 それでは、三角形の山からモザイクを作成してみましょう。 この問題にはさまざまなアプローチがありますが、 ドロネー三角形分割を使用します。







 npm i --save delaunay-triangulate
      
      





使用するアルゴリズムの主な利点は、可能な限り非常に鋭角で鈍角の三角形を避けることです。 美しい画像の場合、細長い三角形は必要ありません。







これは、私たちの分野に存在する数学アルゴリズムとそれらの違いを知ることが有用な瞬間の1つです。 それらのすべての実装を覚えておく必要はありませんが、少なくともグーグルするものを知ることは有用です。

タスクを小さなものに分割します。 まず、三角形の頂点のポイントを生成する必要があります。 そして、それらの座標にいくつかのランダム性を追加すると良いでしょう:







 function generateTriangulation() { // . . . const basePoints = []; for (let x = 0; x <= 100; x += 5) { for (let y = 0; y <= 100; y += 5) { const point = [x, y]; if ((x >= 5) && (x <= 95)) { point[0] += Math.floor(10 * Math.random() - 5); } if ((y >= 5) && (y <= 95)) { point[1] += Math.floor(10 * Math.random() - 5); } basePoints.push(point); } } const triangles = triangulate(basePoints); // . . .
      
      





三角形の配列の構造(console.logを参考にしてください)を見て、ピクセルの色を取得するポイントを見つけます。 単純に三角形の頂点の座標の算術平均を計算できます。 次に、余分なポイントを極端な境界から移動して、どこにも出ないようにし、正規化されていない実際の座標を受け取って、ピクセルの色を取得します。これが三角形の色になります。







 const polygons = []; triangles.forEach((triangle) => { let x = Math.floor((basePoints[triangle[0]][0] + basePoints[triangle[1]][0] + basePoints[triangle[2]][0]) / 3); let y = Math.floor((basePoints[triangle[0]][1] + basePoints[triangle[1]][1] + basePoints[triangle[2]][1]) / 3); if (x === 100) { x = 99; } if (y === 100) { y = 99; } const realX = Math.floor(x * width / 100); const realY = Math.floor(y * height / 100); const pixelPosition = 4 * (realY * width + realX); const rgb = [ pixels.data[pixelPosition], pixels.data[pixelPosition + 1], pixels.data[pixelPosition + 2] ]; const color = '#' + rgbHex(...rgb); const points = ' ' + basePoints[triangle[0]][0] + ',' + basePoints[triangle[0]][1] + ' ' + basePoints[triangle[1]][0] + ',' + basePoints[triangle[1]][1] + ' ' + basePoints[triangle[2]][0] + ',' + basePoints[triangle[2]][1]; polygons.push({ points, color }); });
      
      





以前に行ったように、文字列内の目的のポイントの座標を収集し、処理のためにハンドルバーに色とともに送信するだけです。







テンプレート自体には、長方形ではなくポリゴンがあります。







 {{# each polygons }} <polygon points='{{ points }}' style='stroke-width:0.1;stroke:{{ color }};fill:{{ color }}' /> {{/each }}
      
      





三角測量は非常に興味深いものです。 三角形の数を増やすと、美しい写真を得ることができます。これは、三角形をスタブとしてのみ使用する必要があると言う人がいないためです。







モザイクボロノイ



問題があります、前のもののミラー- ボロノイのパーティションまたはモザイクシェーダーでの作業時にすでに使用しましが、ここでも便利です。













他の既知のアルゴリズムと同様に、既製の実装があります。







 npm i --save voronoi
      
      





それ以降のアクションは、前の例で行ったことと非常に似ています。 唯一の違いは、異なる構造になったことです。三角形の配列ではなく、複雑なオブジェクトがあります。 そして、オプションは少し異なります。 それ以外は、すべてがほぼ同じです。 基点の配列も同じ方法で生成されます。リストを長くしすぎないようにスキップします。







 function generateVoronoi() { // . . . const box = { xl: 0, xr: 100, yt: 0, yb: 100 }; const diagram = voronoi.compute(basePoints, box); const polygons = []; diagram.cells.forEach((cell) => { let x = cell.site.x; let y = cell.site.y; if (x === 100) { x = 99; } if (y === 100) { y = 99; } const realX = Math.floor(x * width / 100); const realY = Math.floor(y * height / 100); const pixelPosition = 4 * (realY * width + realX); const rgb = [ pixels.data[pixelPosition], pixels.data[pixelPosition + 1], pixels.data[pixelPosition + 2] ]; const color = '#' + rgbHex(...rgb); let points = ''; cell.halfedges.forEach((halfedge) => { const endPoint = halfedge.getEndpoint(); points += endPoint.x.toFixed(2) + ',' + endPoint.y.toFixed(2) + ' '; }); polygons.push({ points, color }); }); // . . .
      
      





その結果、凸多角形のモザイクが得られます。 また、非常に興味深い結果です。







すべての数値を整数または少なくとも小数点以下数桁に丸めると便利です。 SVGの過度の精度はここでは完全に不要であり、画像のサイズを大きくするだけです。


ぼやけたモザイク



最後の例は、ぼやけたモザイクです。 SVGのすべての力を手に入れているので、フィルターを使用してみませんか?













長方形の最初のモザイクを取り、それに標準の「ぼかし」フィルターを追加します。







 <defs> <filter id='my-filter' x='0' y='0'> <feGaussianBlur in='SourceGraphic' stdDeviation='2' /> </filter> </defs> <g filter='url(#my-filter)'> {{# each rects }} <rect x='{{ x }}' y='{{ y }}' height='6' width='6' fill='{{ color }}' /> {{/each }} </g>
      
      





その結果、画像のぼやけた「検閲済み」プレビューが表示され、占有するスペースが(圧縮なしで)ほぼ10分の1に減り、ベクトルになり、あらゆる画面サイズに拡大されます。 同様に、残りのモザイクをぼかすことができます。







このようなフィルターを長方形の通常のモザイクに適用すると、「ジープ効果」が判明する可能性があります。そのため、特に大きなサイズの写真に制作でこのようなものを使用する場合、ぼかしではなくボロノイの分割に適用するとより美しくなる場合があります。


結論の代わりに



この記事では、すべての種類のSVGスタブイメージをNode.jsで生成する方法を検討し、すべてを手作業で記述せず、できれば既製のモジュールを組み立てる場合、これがそれほど難しいタスクではないことを確認しました。 完全なソースはgithubで入手できます








All Articles