こんにちは、今日は画像生バイトで遊んでみましょう。私がやろうとしているのは、画像の生のピクセル バイトを操作して、何かおかしなものを作ることです。これは楽しいことです。この記事には、いくつかの理論と実践的な実装が含まれています。それでは、行きましょう…
画像は多数のピクセルの集まりによって形成されることがわかっているため、ピクセルは RGB (赤、緑、青) または RGBA (赤、緑、青、 Alpha) それぞれ 1 バイトを使用します。
PNG や JPG などの拡張子で表示される画像は画像の圧縮形式です。PNG は DEFLATE などのアルゴリズムを使用してピクセルを失わずに圧縮する可逆圧縮で、JPG は非可逆圧縮です。一部のピクセルが失われるため、画質が多少低下します。圧縮せずに画像を表示したい場合は、画像を BMP (ビットマップ画像ファイル) に変換するか、その他のファイルに変換する必要があります。形式に変換すると、非圧縮画像が得られます。しかし、これは必要ありません。生のバイトを抽出して処理し、再度 PNG または JPG に変換します。
まず、画像をアップロードするようにクライアントを設定しましょう。これに簡単な反応アプリケーションを設定します
import axios from "axios"; import { useState } from "react"; import "./App.css"; function App() { const [img, setImg] = useState(null); const [pending, setPending] = useState(false); const handleImg = (e) => { setImg(e.target.files[0]); }; const handleSubmit = async (e) => { e.preventDefault(); if (!img) return; const formData = new FormData(); formData.append("img", img); try { setPending(true); const response = await axios.post("http://localhost:4000/img", formData); console.log(response.data); } catch (error) { console.log("Error uploading image:", error); } finally { setPending(false); } }; return (); } export default App;
これは画像をアップロードするための単純なコードであり、主要部分はサーバー側にあります。
次に、画像のバイトを手動で計算し、サーバー側のコードで確認してみましょう。
下の画像を選択しました。
画像は前回の記事のサムネイルです。これは PNG ファイルです。プロパティ セクションに移動すると、画像の幅と高さを確認できます。この場合、幅と高さは 722 x 407 で、293854 ピクセルに相当します。また、これは合計バイト数ではなく、単なる合計ピクセル数です。ご存知のとおり、各ピクセルは 3 バイトまたは 4 バイト、RGB または RGBA です。したがって、上の画像が RGB の場合、合計バイト数は 722 x 407 x 3 = 881562 になります。または、画像にアルファ チャネルがある場合、合計バイト数は 722 x 407 x 4 = 1175416 になります。
サーバー側について少し説明しましょう。私はノード js を使用しています。
複数形式のデータを解析するための multer というライブラリがあります。
app.post("/img", upload.single("img"), async (req, res) => { const arr = req.file.buffer console.log(arr.length) //output: 30929 res.send("success") });
画像のバイトをバッファ配列に保存します。バッファ配列の長さをとれば、答えは 30929 です。配列にはこれだけのバイトがありますが、合計バイト数は 1175416 になるはずですよね?ここで何が起こるかというと、multer は圧縮などは行わず、ユーザーから画像を取得してそのままバッファに保存するだけです。そのため、PNG ファイルをアップロードしました。表示されているバッファは、 PNG 画像サイズ。
次に、圧縮画像バイトのバイトを変更しましょう。
app.post("/img", upload.single("img"), async (req, res) => { const arr = req.file.buffer; console.log("multer " arr.length); fs.writeFile("output.png", arr, (err) => { console.log(err); }); res.send("successfull"); });
fs を使用して、既存のイメージを使用して新しいイメージを作成しました。したがって、最初のバイトの arr[0] = 231 を変更すると、画像は開きません。
最初の特定のバイトはメタデータ用に予約されているため、それらのメタデータを変更すると画像が破損する可能性があります。
それでは、500 バイト目にジャンプしましょう。 arr[500] = 123、イメージを書き込みます。しかし、現在、画像は壊れています。圧縮アルゴリズムでエンコードされたデータが変更される可能性があるため、圧縮された画像のバイトを直接操作するべきではありません。
画像から生のバイトが必要です。その後、バイトを個別に操作できます。そのために、sharp ライブラリを使用できます。
npm シャープのインストール
シャープをインストールします。次に、これらのロジックを処理するための別のファイルを作成します。
シャープ.js
export async function convert(buffer) { try { const data = await sharp(buffer).metadata(); console.log(data) }catch(err){ console.log(err) } }
これは非同期関数です。アップロードした png からメタデータを取得しましょう。
{ format: 'png', size: 30929, width: 722, height: 407, space: 'srgb', channels: 4, depth: 'uchar', density: 72, isProgressive: false, hasProfile: false, hasAlpha: true }
これは画像のメタデータです。最後のデータは hasAlpha: true であるため、アルファ チャネルがあり、各ピクセルは 4 バイトであることがわかります。
次に、画像から生のバイトを取得しましょう。
const rawBytes = await sharp(buffer) .raw() .toBuffer({ resolveWithObject: true }); console.log(rawBytes.data.length) //1175416
これで、配列の長さが計算結果と同じであることがわかります。したがって、この画像には 1175416 バイトが含まれます。 これで、任意のバイトを自由に変更できます。 これで、メタデータはバッファーに保存されなくなり、バッファーには画像の生のバイトのみが含まれます。
1 ピクセルだけを赤に変更しましょう。
rawBytes.data[0] = 225; //red rawBytes.data[1] = 10; //green rawBytes.data[2] = 10; //blue rawBytes.data[3] = Math.floor(0.8 * 255); //alpha
1 つのピクセルが赤に変更されるため、ピクセルの変化を確認するには画像を拡大する必要があります。
次に、画像を分割して色を変更してみましょう。上半分が黄色、下半分が緑色です
const div = rawBytes.data.length / 2; for (let i = 0; i反復ごとに 1 ピクセルを変更するため、ループを 4 回インクリメントします。出力は次のようになります。
アルファ チャネルが 0.8 に設定されているため、この画像では透明度が確認できます
画像の書き込みについて言うのを忘れていました。新しい画像を書き込むのに fs は必要ありません。シャープ自体を使用できます。
await sharp(rawBytes.data, { raw: { width: data.width, height: data.height, channels: data.channels, }, }) .png() .toFile("demo.png");同じメタデータを使用して新しい画像を生成しています。
サーバー側の完全なコードは次のとおりです。
//index.js import express from "express"; import dotenv from "dotenv"; import multer from "multer"; import cors from "cors"; import { convert } from "./sharp.js"; const app = express(); dotenv.config(); app.use(cors({ origin: "http://localhost:5173" })); const storage = multer.memoryStorage(); const upload = multer(); app.post("/img", upload.single("img"), async (req, res) => { const arr = req.file.buffer; await convert(arr); res.send("successful"); }); app.listen(process.env.PORT, () => { console.log("server started"); });//sharp.js import sharp from "sharp"; export async function convert(buffer) { try { const data = await sharp(buffer).metadata(); console.log(data); //raw data const rawBytes = await sharp(buffer) .raw() .toBuffer({ resolveWithObject: true }); console.log(rawBytes.data.length); const div = rawBytes.data.length / 2; for (let i = 0; iこれで終わりです。これらのピクセルで遊んだだけです。そして最後に、この記事のサムネイルはループ内のこの 1 行で作成されます。
rawBytes.data[i] = Math.floor(Math.random()*256)各バイトをランダムに変更しただけです ?
完全なコードについては、私のリポジトリをチェックしてください:pixel-byte-manipulation
間違いがあればコメントしてください
ありがとう!!!
免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。
Copyright© 2022 湘ICP备2022001581号-3