안녕하세요 여러분, 오늘은 이미지 원시 바이트를 가지고 놀아보겠습니다. 내가 할 일은 이미지의 원시 픽셀 바이트를 조작하고 뭔가 어리석은 일을 만드는 것입니다. 이것은 재미있는 일입니다. 이 기사에는 몇 가지 이론과 실제 구현이 포함되어 있습니다. 이제 살펴보겠습니다…
이미지는 여러 개의 픽셀로 구성되어 있으므로 픽셀은 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이면 이미지를 씁니다. 하지만 이제 이미지가 손상되었습니다. 압축 알고리즘으로 인코딩된 데이터를 변경할 수 있으므로 압축된 이미지 바이트를 직접 조작해서는 안 됩니다.
이미지의 원시 바이트가 필요하며, 그런 다음 바이트를 독립적으로 조작할 수 있으며 이를 위해 샤프 라이브러리를 사용할 수 있습니다.
npm 설치 샤프
샤프를 설치합니다. 이제 해당 로직을 처리하기 위한 별도의 파일을 생성하겠습니다.
sharp.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바이트가 포함되어 있습니다. 이제 자유입니다.. 바이트를 변경할 수 있습니다. 이제 메타데이터는 버퍼에 저장되지 않으며 버퍼에는 이미지의 원시 바이트만 포함됩니다.
한 픽셀만 빨간색으로 변경해 보겠습니다.
rawBytes.data[0] = 225; //red rawBytes.data[1] = 10; //green rawBytes.data[2] = 10; //blue rawBytes.data[3] = Math.floor(0.8 * 255); //alpha
한 픽셀이 빨간색으로 변경될 수 있으므로 픽셀 변경을 보려면 이미지를 확대해야 합니다.
이제 이미지를 분할하여 색상을 변경해 보겠습니다. 위쪽은 노란색, 아래쪽은 녹색입니다
const div = rawBytes.data.length / 2; for (let i = 0; i매 반복마다 한 픽셀을 변경하므로 루프를 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그래서 우리는 그 픽셀을 가지고 놀았습니다. 그리고 마지막으로 이 기사 썸네일이 루프의 한 줄로 만들어졌습니다.
rawBytes.data[i] = Math.floor(Math.random()*256)각 바이트를 무작위로 변경했을 뿐입니다. ?
전체 코드를 보려면 내 저장소를 확인하세요: pixel-byte-manipulation
실수한 부분이 있으면 댓글을 남겨주세요
감사합니다!!!
부인 성명: 제공된 모든 리소스는 부분적으로 인터넷에서 가져온 것입니다. 귀하의 저작권이나 기타 권리 및 이익이 침해된 경우 자세한 이유를 설명하고 저작권 또는 권리 및 이익에 대한 증거를 제공한 후 이메일([email protected])로 보내주십시오. 최대한 빨리 처리해 드리겠습니다.
Copyright© 2022 湘ICP备2022001581号-3