Bonjour les gars, aujourd'hui, jouons avec les octets bruts des images. Ce que je vais faire, c'est manipuler les octets de pixels bruts de l'image et faire quelque chose de stupide, c'est quelque chose d'amusant à faire. Cet article contient de la théorie ainsi que de la mise en œuvre pratique, alors c'est parti…
Comme nous le savons, une image est formée d'un groupe de pixels ensemble, le pixel n'est rien d'autre qu'une combinaison de RVB (rouge, vert, bleu) ou RGBA (rouge, vert, bleu, Alpha) chacun avec 1 octet.
Les images que nous visualisons avec des extensions comme PNG ou JPG sont les formats compressés de l'image, PNG est sans perte et PNG utilise des algorithmes comme DEFLATE pour compresser sans perdre le pixel et JPG est une compression avec perte. perdre quelques pixels, il y aura donc une certaine perte de qualité de l'image. Si nous voulons visualiser l'image sans la compression, nous devons convertir l'image au format BMP (fichier image bitmap) ou il existe également d'autres formats, si nous convertissons dans ce format, nous obtenons l'image non compressée. Mais nous n'en avons pas besoin, nous extrairons ces octets bruts et jouerons avec eux et nous les reconvertirons à nouveau en PNG ou JPG.
Tout d'abord, configurons le client pour qu'il télécharge des images, je vais définir une application de réaction simple pour cela
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;
C'est donc un code simple, pour télécharger les images, la partie principale est côté serveur.
Calculons maintenant manuellement les octets de l'image et vérifions avec le code côté serveur.
J'ai choisi l'image ci-dessous.
L'image provient de la vignette de mon article précédent. Il s'agit donc d'un fichier PNG, si on va dans la section propriétés, on peut voir la largeur et la hauteur de l'image. Pour cela, la largeur et la hauteur sont de 722 x 407, ce qui équivaut à 293 854 pixels. Ce n'est pas non plus un nombre total d'octets, c'est juste un nombre total de pixels. Comme nous le savons, chaque pixel fait 3 ou 4 octets, RVB ou RGBA. Ainsi, si l'image ci-dessus est RVB, le total d'octets serait de 722 x 407 x 3 = 881562 ou si l'image a le canal alpha, alors le total d'octets serait de 722 x 407 x 4 = 1175416.
Parlons du côté serveur, j'utilise le nœud js.
Il existe une bibliothèque appelée multer pour analyser les données multiformes.
app.post("/img", upload.single("img"), async (req, res) => { const arr = req.file.buffer console.log(arr.length) //output: 30929 res.send("success") });
Nous stockons les octets de l'image dans le tableau tampon, si nous prenons la longueur du tableau tampon, la réponse est 30929, il y a ces nombreux octets dans le tableau, mais attendez, le nombre total d'octets devrait être de 1175416, n'est-ce pas ? Ce qui se passe ici, c'est que Multer ne fait pas de compression ou quoi que ce soit, il récupère simplement l'image de l'utilisateur et la stocke dans le tampon tel quel, nous avons donc téléchargé le fichier PNG, le tampon que vous voyez a la même taille que le Taille de l'image PNG.
Modifions maintenant les octets dans l'octet de l'image compressée.
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"); });
J'ai utilisé le fs pour créer une nouvelle image avec celle existante. Alors maintenant, si nous modifions le premier octet arr[0] = 231, l'image ne s'ouvrira pas.
Parce que certains premiers octets sont réservés aux métadonnées, donc si nous modifions ces métadonnées, l'image peut alors être corrompue.
Passons donc au 500ème octet. arr[500] = 123, puis écrivez l'image. Mais maintenant, l'image est cassée, nous ne devons pas manipuler directement les octets de l'image compressée car cela peut modifier l'algorithme de compression des données codées.
Nous avons besoin des octets bruts de l'image, puis nous pouvons manipuler les octets indépendamment, et pour cela, nous pouvons utiliser une bibliothèque sharp.
npm installer Sharp
installez le Sharp, je vais maintenant créer un fichier séparé pour gérer ces logiques,
sharp.js
export async function convert(buffer) { try { const data = await sharp(buffer).metadata(); console.log(data) }catch(err){ console.log(err) } }
Il s'agit d'une fonction asynchrone. Récupérons maintenant les métadonnées du png que nous avons téléchargé.
{ format: 'png', size: 30929, width: 722, height: 407, space: 'srgb', channels: 4, depth: 'uchar', density: 72, isProgressive: false, hasProfile: false, hasAlpha: true }
Ce sont les métadonnées de l'image, comme nous pouvons le voir les dernières données hasAlpha: true donc elles ont le canal alpha, donc chaque pixel fait 4 octets.
Récupérons maintenant les octets bruts de l'image.
const rawBytes = await sharp(buffer) .raw() .toBuffer({ resolveWithObject: true }); console.log(rawBytes.data.length) //1175416
Nous pouvons maintenant voir que la longueur du tableau est égale à notre calcul. Cette image contient donc 1175416 octets. Maintenant, nous sommes libres.. de modifier n'importe quel octet. Désormais, les métadonnées ne sont pas stockées dans le tampon, le tampon ne contient que les octets bruts de l'image.
Changeons un seul pixel en rouge.
rawBytes.data[0] = 225; //red rawBytes.data[1] = 10; //green rawBytes.data[2] = 10; //blue rawBytes.data[3] = Math.floor(0.8 * 255); //alpha
Comme nous pouvons changer un pixel en rouge, nous devons zoomer sur l'image pour voir le changement de pixel.
Divisons maintenant l'image et changeons la couleur, la moitié supérieure est jaune et la moitié inférieure est verte
const div = rawBytes.data.length / 2; for (let i = 0; iNous incrémentons la boucle de 4 fois car nous changeons un pixel à chaque itération. Maintenant, le résultat sera comme ceci.
Nous pouvons voir la transparence sur cette image car le canal Alpha est réglé sur 0,8
J'ai oublié de dire pour écrire l'image, nous n'avons pas besoin de fs pour écrire une nouvelle image, nous pouvons utiliser le Sharp lui-même.
await sharp(rawBytes.data, { raw: { width: data.width, height: data.height, channels: data.channels, }, }) .png() .toFile("demo.png");nous générons la nouvelle image avec les mêmes métadonnées.
Voici le code complet côté serveur,
//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; iAlors ça y est, nous venons de jouer avec ces pixels. et enfin la vignette de cet article est réalisée avec cette seule ligne dans la boucle.
rawBytes.data[i] = Math.floor(Math.random()*256)J'ai juste changé chaque octet au hasard ?
pour le code complet, consultez mon dépôt : pixel-byte-manipulation
s'il y a des erreurs, veuillez commenter
Merci!!!
Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.
Copyright© 2022 湘ICP备2022001581号-3