Hola chicos, hoy juguemos con bytes sin formato de imágenes. Lo que voy a hacer es manipular los bytes de píxeles sin procesar de la imagen y hacer algo tonto, es algo divertido de hacer. Este artículo contiene algo de teoría y una implementación práctica. Así que vamos…
Como sabemos una imagen está formada por un montón de píxeles juntos, el píxel no es más que una combinación de RGB (Rojo, Verde, Azul) o RGBA (Rojo, Verde, Azul, Alfa) cada uno con 1 byte.
Las imágenes que vemos con extensiones como PNG o JPG son los formatos comprimidos de la imagen, PNG no tiene pérdidas y PNG usa algoritmos como DEFLATE para comprimir sin perder el píxel y JPG es una compresión con pérdida que lo hará. perderá algunos píxeles, por lo que habrá cierta pérdida en la calidad de la imagen. Si queremos ver la imagen sin la compresión, necesitamos convertir la imagen a BMP (archivo de imagen de mapa de bits) o también hay algunos otros. formatos, si convertimos a este obtenemos la imagen sin comprimir. Pero no necesitamos esto, extraeremos esos bytes sin procesar, jugaremos con ellos y los convertiremos nuevamente a PNG o JPG.
Primero, configuremos el cliente para cargar imágenes, configuraré una aplicación de reacción simple para esto
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;
Así que este es un código simple, para cargar las imágenes, la parte principal está en el lado del servidor.
Ahora calculemos manualmente los bytes de la imagen y verifiquemos con el código del lado del servidor.
He elegido la imagen de abajo.
La imagen es de la miniatura de mi artículo anterior. Entonces este es un archivo PNG, si vamos a la sección de propiedades, podemos ver el ancho y el alto de la imagen. Para esto, el ancho y el alto son 722 x 407, lo que equivale a 293854 píxeles. Además, este no es un número total de bytes, es solo un número total de píxeles. Como sabemos, cada píxel tiene 3 o 4 bytes, RGB o RGBA. Entonces, si la imagen de arriba es RGB, el total de bytes sería 722 x 407 x 3 = 881562 o si la imagen tiene el canal alfa, entonces el total de bytes sería 722 x 407 x 4 = 1175416.
Vayamos al lado del servidor, estoy usando el nodo js.
Existe una biblioteca llamada multer para analizar datos multiformes.
app.post("/img", upload.single("img"), async (req, res) => { const arr = req.file.buffer console.log(arr.length) //output: 30929 res.send("success") });
Almacenamos los bytes de la imagen en la matriz de búfer, si tomamos la longitud de la matriz de búfer la respuesta es 30929, hay tantos bytes en la matriz, pero espera, el número total de bytes debería ser 1175416, ¿verdad? Lo que sucede aquí es que Multer no realiza compresión ni nada por el estilo, simplemente obtiene la imagen del usuario y la almacena en el búfer tal como está, así que cargamos el archivo PNG, el búfer que estás viendo tiene el mismo tamaño que el Tamaño de imagen PNG.
Ahora cambiemos los bytes en el byte de la imagen comprimida.
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"); });
Utilicé fs para crear una nueva imagen con la existente. Entonces, si cambiamos el primer byte arr[0] = 231, la imagen no se abrirá.
Debido a que los primeros bytes están reservados para los metadatos, si cambiamos esos metadatos, la imagen puede corromperse.
Así que saltemos al byte 500. arr[500] = 123, luego escribe la imagen. Pero ahora que la imagen está rota, no debemos manipular directamente los bytes de la imagen comprimida porque puede cambiar el algoritmo de compresión de los datos codificados.
Necesitamos los bytes sin procesar de la imagen, y luego podemos manipular los bytes de forma independiente, y para eso, podemos usar una biblioteca sharp.
npm instala Sharp
instale Sharp. Ahora crearé un archivo separado para manejar esas lógicas,
sharp.js
export async function convert(buffer) { try { const data = await sharp(buffer).metadata(); console.log(data) }catch(err){ console.log(err) } }
Esta es una función asíncrona. Ahora obtengamos los metadatos del png que hemos subido.
{ format: 'png', size: 30929, width: 722, height: 407, space: 'srgb', channels: 4, depth: 'uchar', density: 72, isProgressive: false, hasProfile: false, hasAlpha: true }
Estos son los metadatos de la imagen, como podemos ver, los últimos datos tienenAlpha: true, por lo que tiene el canal alfa, por lo que cada píxel tiene 4 bytes.
Ahora obtengamos los bytes sin procesar de la imagen.
const rawBytes = await sharp(buffer) .raw() .toBuffer({ resolveWithObject: true }); console.log(rawBytes.data.length) //1175416
Ahora podemos ver que la longitud de la matriz es igual a nuestro cálculo. Entonces esta imagen contiene 1175416 bytes. Ahora somos libres... de cambiar cualquier byte. Ahora los metadatos no se almacenan en el búfer, el búfer solo contiene los bytes sin procesar de la imagen.
Cambiemos solo un píxel a rojo.
rawBytes.data[0] = 225; //red rawBytes.data[1] = 10; //green rawBytes.data[2] = 10; //blue rawBytes.data[3] = Math.floor(0.8 * 255); //alpha
Como podemos, un píxel cambia a rojo, necesitamos acercar la imagen para ver el cambio de píxel.
Ahora dividamos la imagen y cambiemos el color, la mitad superior es amarilla y la mitad inferior es verde
const div = rawBytes.data.length / 2; for (let i = 0; iEstamos incrementando el bucle 4 veces porque cambiamos un píxel en cada iteración. Ahora el resultado será así.
Podemos ver la transparencia en esta imagen porque el canal Alfa está configurado en 0,8
Olvidé decirte que para escribir la imagen no necesitamos fs para escribir una nueva imagen, podemos usar el Sharp mismo.
await sharp(rawBytes.data, { raw: { width: data.width, height: data.height, channels: data.channels, }, }) .png() .toFile("demo.png");estamos generando la nueva imagen con los mismos metadatos.
Aquí está el código completo del lado del servidor,
//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; iAsí que esto es todo. Simplemente jugamos con esos píxeles. y finalmente la miniatura de este artículo se crea con esta línea en el bucle.
rawBytes.data[i] = Math.floor(Math.random()*256)¿Acabo de cambiar cada byte al azar?
para ver el código completo, consulte mi repositorio: manipulación de bytes de píxeles
si hay algún error por favor comenten
¡¡¡Gracias!!!
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3