見出し画像

クリエイターの作品をまもるプラットフォームを作るためにまず自分で画像を保護するサイトを作ってみる#2

はい。タイトルのテコ入れです。(カバー画像のテコ入れまでは間に合わなんだ)
でもタイトル正直盛ってます。
相変わらずキャプチャや写真についてはどうしようもないし、最終的な手段も結局抜け道自体は存在します。

最初方法を思いついた時は「これだ!」と脳内麻薬どばばでしたが古の言葉「素人が考え付くアイディアは先人がすでに試して破棄した案」を忘れておりました。(つまり私の思いついた案は没になったということ。でも誰かの思考の助けになるかもしれないから書く。)
しかし本当になんで簡単にDLできる仕様にしたんだ偉い人。(調べても出てこんかった(うまいワードを見つけられんかっただけな気もするが。)(もしかすると、論文を参照するにあたり(htmlはもともと論文共有のための言語)グラフや図などの画像も参照してくる必要があったためなのかもしれない))

と、いう感じで長い前置きを書いたところで、前回のあらすじ。
vercel公式のnextjs+cloudinaryのギャラリーテンプレートを動かしてみるもののうまく狙った画像を表示させることができず、また画像の加工がcloudinaryを利用した状態でできるのか不明だったため一旦画像はローカルに置いて制作することに決めたのだった。べべん。

つことで今回の四苦八苦

画像ファイルどこ置くねん

つっても多分ほとんどのNextjsユーザーさんわかってると思います。はい。
publicの中ですね。
publicの中にディレクトリ作って管理するだけ。
簡単ですね。
でも実際触らないと案外腑に落ちないもんなので、触ってみるのは無駄じゃない。経験値経験値。

Imageコンポーネントやobject要素で表示

まぁ、jsで右クリック禁止しても普通に保存する手段はあるよね。知ってる。

没った本命(pythonで画像の画素情報を配列としてjson出力。そしてdivで画素を表現)

最初に没った理由を。
単純にくそ重い。表示が。
結構単純な500*300くらいのほとんど単色の画像でも表示されるまで数秒かかる。論外。
でもやり方は語ってく。

ちなみに今回時間かかったポイントは

  • pythonの仮想環境を作る(なんでや)

  • pythonで画素情報のリストを作ったはいいけどjsonに保存できない(そのままだとエラーが出る)

  • 初めて触ったー!appルーティングー!

  • そして伝説(没)へ……!

pythonの仮想環境を作る

実は今までもpython自体は触ったことあってprogateとかpc上で触るとかも一応していたんだけども「仮想環境isなに?」(仮想環境という言葉も意味も知ってるけどpythonでそれがいるとか知らんかったぞの意)
google先生のお力を借りて何種類かある中のシンプルというかわかりやすいvenvを使用。
実は起動コマンドのほうはディレクトリ開いたら一応わかるけど終了コマンドのほうは毎度ググってる。
覚えるまで時間かかりそう。
一遍作ったらこっちのもんなので(やり方がなんとなくわかるから)いよいよ画像から画素の色情報の配列を作りますよっと。

pythonで画素情報のリストを作ったはいいけどjsonに保存できない(そのままだとエラーが出る)

まず「python 画像 ピクセル 色 取得」でググり
主に

このページを参考にして画素の配列を作成。
参照元のページでは画像はグレースケールなのでrgbの値は全て同じということで、画素用の配列は数値配列になっていたのを、div要素の背景色として表示させるために

r, g, b, a = img.getpixel((x, y))
image_array[y][x] = str(r) + ' ' + str(g) + ' ' + str(b)

こんな感じでcssでのbackground指定のrgbの中身のテキスト状態になるようにして格納。
コマンドで、保存された配列があっていそうなことを確認。
さあ、jsonに書き込むぞ!となったところでエラー。

どうやらimage_arrayの初期化で文字列の配列にしたいがためにdtype=objectにしたがためにlistが「np.ndarray」として定義され、そのままではlistとして認識できずjsonに書き出すこともできないということらしい。
で、どうにかjsonに書き出す方法がないかと調べてみた結果

このページを発見。
どうやらjson dumpに追加のエンコーダーを仕込むことで、jsonへの書き出しが可能になるらしい。
ということでさっそくコードをコピペ。
したのはいいものの、どうやってjson.dumpに組み込むんだ???
と、しばし右往左往。

こんな時は公式サイトに頼るに限る。
ちょうど「JSONEncoderの拡張」という項目があり、作成されているClass名は違えど、同じようなことをしているのでこちらを参考にコードを修正。
動かしてみる。
jsonが保存されている。やったー!
実際作ったpythonのコードがこちら

from PIL import Image
import numpy as np
import json

class NumpyEncoder(json.JSONEncoder):
	def default(self, obj):
		if isinstance(obj, np.integer):
			return int(obj)
		elif isinstance(obj, np.floating):
			return float(obj)
		elif isinstance(obj, np.ndarray):
			return obj.tolist()

		return super(NumpyEncoder, self).encode(obj)

# 画像データの読み込み
img = Image.open('dummy1.png')
# 画像のサイズ(幅[px] x 高さ[px])を取得し、それぞれを変数に代入
width, height = img.size

# 0が格納された配列を画像のサイズと同じサイズで用意
image_array = np.empty((height, width), dtype = object)

for y in range(height):
	for x in range(width):
		# 順次、ピクセルの色の数値を代入している
		r, g, b, a = img.getpixel((x, y))
		image_array[y][x] = str(r) + ' ' + str(g) + ' ' + str(b)

image_data = []
image_data.append(image_array)

new_json = open('pyimagecolors.json', 'w')
json.dump(image_data, new_json, cls=NumpyEncoder)

(いらんコードは消してます)

初めて触ったー!appルーティングー!

とかいうサブタイトルなのにappルーティングに行く前にコンポーネント作らにゃなりません。
イメージとしてはobject要素からdivにより画素が表現されたページを呼び出し表示するというものなので、先ほど作ったjsonを呼び出しそのがその分だけdivを作りcss変数をあて着色するという、そんなコンポーネントを作成する必要があります。
先ほど作ったjsonファイルをsrc配下にdataディレクトリを作成してそこに格納。ピクセル表示用コンポーネントでjsonファイルを読み込み使用します。

実を言うとこの時点で、最初「publicに設置してfetchで読み込んではならんのか?」と思って試してみたわけですが、エラー出ます。
エラーメッセージに沿ってuse client追加してみたりしましたが今度はuse clientの状態でfetch使うなよ言われます(再現しようと思ったら再現できんかった。どんなコード書いてたっけ(リアルタイム記録大事だわ……。))。
どうすればってことでuseEffect使うとエラーなくなりましたが、ここまでするなら普通にsrcにファイルを置いて読み込んだ方がスマートだよね。うん。

てことで先のjsonは縦×横の行列状の入れ子状態になってるので一直線にするために配列加工します(すごくニューラルネットワークの入力を思い出す加工だな今思うと。)
加工自体はこんなん

const imagePixel = pixeldata[0].reduce((accumulator, currentValue, currentIndex, array) => {
    return [...accumulator, ...currentValue]
})

ちなみにpixeldata[0]ていう風にしてるのは単純にこれうまくいったら複数画像分の情報をjsonに含めるつもりだったからです。没になったんで出番なしです。
こんな感じで配列上にしたら幅と高さを指定したdivの中に1*1ピクセルの背景色指定済みdivを配置するということをします。
cssとコンポーネントはこんなん

.pixelArea {
  --width: 0;
  --height: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  height: var(--height);
  width: var(--width);
}

.pixelArea__dot {
  --color: rgb(0 0 0);
  background: var(--color);
  height: 1px;
  width: 1px;
}
import styles from './pixelview.module.scss'
import pixeldata from '@/data/pyimagecolors.json'

const Pixelview = () => {
  const imageH = pixeldata[0].length
  const imageW = pixeldata[0][0].length
  const imagePixel = pixeldata[0].reduce((accumulator, currentValue, currentIndex, array) => {
    return [...accumulator, ...currentValue]
  })

  return (
    <>
      <div
        className={styles.pixelArea}
        style={{'--width': imageW + 'px', '--height': imageH + 'px'} as React.CSSProperties}
      >
        {imagePixel.map((item, index)=>{
          return(
            <div
              className={styles.pixelArea__dot}
              style={{'--color': 'rgb('+item+')'} as React.CSSProperties}
              key= {index}
            ></div>
          )
        })}
      </div>
    </>
  );
};

export default Pixelview;

ここでコンポーネントができたので表示ページの方準備します

フォルダ構造

ヘッダー要素など共通パーツはいらず、画像用ピクセルだけ表示したいのでそれ用にレイアウト分けます。
昔Nextjs勉強したときはpageルーティングだった気がするけども(気のせいかも)なんとなしこっちのほうがわかりやすいかもしれない。(それも気のせいかも)

そして表示サイズに合わせてスクロール出ないように表示サイズに合わせての拡大縮小処理を追加してこんな感じ

import './pixelpage.scss'
import Script from 'next/script'
import Pixelview from '@/components/pixelview'

export default function Number() {

  return (
    <>
      <Pixelview />
      <Script
        src="/js/windowfit.js"
        strategy="beforeInteractive"
      />
    </>
  )
}
console.log(document.body.clientWidth)
console.log(document.body.children[0].clientWidth)

const parentW = document.body.clientWidth
const contentsW = document.body.children[0].clientWidth

const change_scale = {
  "container": contentsW,
  "percent": 1,
  "function": function() {
      "use strict";
      if (change_scale.percent === window.devicePixelRatio) {
          let scale = parentW;
          scale = scale / change_scale.container;
          const scaleValue = "scale(" + scale + ")";
          document.body.style.transform = scaleValue;
      } else {
          change_scale.percent = window.devicePixelRatio;
      }
  }
}

change_scale.function()

そして伝説(没)へ……!

まあこんな感じでなんやかんややりましてできましたのが

これ(没置き場)の2番目。
なんで没にしたかっつったらこのサイズですでに表示に時間がかかるのと、拡大縮小で謎に薄くなるからです。はい。

つことで次回、一応採用した方法について解説していきます(て言っても先人いるんだけど。)

この記事が気に入ったらサポートをしてみませんか?