«Если рабочий хочет хорошо выполнять свою работу, он должен сначала заточить свои инструменты» — Конфуций, «Аналитики Конфуция. Лу Лингун»
титульная страница > программирование > Игра с необработанными байтами изображения

Игра с необработанными байтами изображения

Опубликовано 1 ноября 2024 г.
Просматривать:121

Привет, ребята, сегодня давайте поиграем с необработанными байтами изображения. Что я собираюсь сделать, так это манипулировать необработанными пиксельными байтами изображения и сделать что-нибудь глупое, это забавное занятие. В этой статье содержится не только теория, но и практическая реализация. Итак, поехали…

Поскольку мы знаем, что изображение формируется совокупностью пикселей, пиксель представляет собой не что иное, как комбинацию RGB (красный, зеленый, синий) или RGBA (красный, зеленый, синий, Alpha) каждый занимает 1 байт.

Изображения, которые мы просматриваем с такими расширениями, как PNG или JPG, представляют собой сжатые форматы изображения, PNG — это сжатие без потерь, поскольку 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;

Итак, это простой код для загрузки изображений, основная часть которого находится на стороне сервера.

Теперь давайте вручную посчитаем байты изображения и сверимся с серверным кодом.

Я выбрал изображение ниже.

Playing with raw image bytes

Изображение взято из миниатюры моей предыдущей статьи. Итак, это файл 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, изображение не откроется.

Playing with raw image bytes

Потому что первые определенные байты зарезервированы для метаданных, поэтому, если мы изменим эти метаданные, изображение может испортиться.

Итак, давайте перейдем к 500-му байту. arr[500] = 123, затем запишите изображение. Но теперь, когда изображение повреждено, нам не следует напрямую манипулировать байтами сжатого изображения, поскольку это может изменить закодированные данные алгоритма сжатия.

Нам нужны необработанные байты изображения, а затем мы можем независимо манипулировать ими, и для этого мы можем использовать библиотеку sharp.

npm установить 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, то есть у них есть альфа-канал, поэтому каждый пиксель имеет размер 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

Playing with raw image bytes

Поскольку мы можем изменить цвет одного пикселя на красный, нам нужно увеличить изображение, чтобы увидеть изменение пикселя.

Теперь давайте разделим изображение и изменим цвет: верхняя половина будет желтой, а нижняя — зеленой

const div = rawBytes.data.length / 2;
    for (let i = 0; i 



Мы увеличиваем цикл в 4 раза, потому что на каждой итерации мы меняем один пиксель. Теперь результат будет таким.

Playing with raw image bytes

На этом изображении мы можем видеть прозрачность, поскольку для альфа-канала установлено значение 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)

Я просто случайным образом изменил каждый байт?

полный код можно найти в моем репозитории: пиксельно-байтовая манипуляция

если есть ошибки, напишите в комментариях

Спасибо!!!

Заявление о выпуске Эта статья воспроизводится по адресу: https://dev.to/sanx/playing-with-raw-image-bytes-2k8a?1. Если есть какие-либо нарушения, пожалуйста, свяжитесь с [email protected], чтобы удалить его.
Последний учебник Более>

Изучайте китайский

Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.

Copyright© 2022 湘ICP备2022001581号-3