大家好,今天我们来玩一下图像原始字节。我要做的是,操纵图像原始像素字节,并做一些愚蠢的事情,这是有趣的事情。本文包含一些理论和实际实现,所以让我们开始......
众所周知,图像是由一堆像素组合而成,像素只不过是RGB(红、绿、蓝)或RGBA(红、绿、蓝、 Alpha) 每个占用 1 个字节。
我们使用 PNG 或 JPG 等扩展名查看的图像是图像的压缩格式,PNG 是无损压缩,PNG 使用 DEFLATE 等算法进行压缩而不丢失像素,而 JPG 是有损压缩,它将丢失一些像素,因此图像质量会有所损失,如果我们想在不压缩的情况下查看图像,我们需要将图像转换为BMP(位图图像文件)或者还有其他一些格式,如果我们转换为这种格式,我们会得到未压缩的图像。但我们不需要这个,我们将提取这些原始字节并使用它们,我们将再次将它们转换回 PNG 或 JPG。
首先,让我们设置客户端上传图片,我将为此设置一个简单的React应用程序
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,或者如果图像具有 Alpha 通道,则总字节数将为 722 x 407 x 4 = 1175416。
让我们一些服务器端,我使用的是node 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 install Sharp
安装 Sharp,现在我将创建一个单独的文件来处理这些逻辑,
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 所以它有 Alpha 通道,所以每个像素是 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 倍,因为我们在每次迭代时都会更改一个像素。现在输出将是这样的。
我们可以看到该图像的透明度,因为 Alpha 通道设置为 0.8
我忘了告诉你写入图像,我们不需要fs来写入新图像,我们可以使用sharp本身。
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