Поговорим о бинарном интерфейсе из кадрирования изображения

JavaScript Canvas
Поговорим о бинарном интерфейсе из кадрирования изображения

Напишите это вверху

Небольшой первый запрос

Два дня назад в проекте было небольшое требование: фронтенд загружает файл бинарного потока, возвращаемый младшим братом в фоновом режиме.

Когда я впервые получил это требование, я почувствовал, что это очень просто (хотя я не могу, но я могу Baidu,)

Затем напишите следующий код:

let blob = new Blob([res.data]);
let fileName = `Cosen.csv`;
if (window.navigator.msSaveOrOpenBlob) {
  navigator.msSaveBlob(blob, fileName);
} else {
  let link = document.createElement("a");
  let evt = document.createEvent("HTMLEvents");
  evt.initEvent("click", false, false);
  link.href = URL.createObjectURL(blob);
  link.download = fileName;
  link.style.display = "none";
  document.body.appendChild(link);
  link.click();
  window.URL.revokeObjectURL(link.href);
}

Этот кусок кода, я, вероятно,силаобъяснять:

Судите первымwindow.navigator.msSaveOrOpenBlobдля совместимостиIE(кто хочет быть совместимым с этим ххIE! ! )

тогда неIEпрохождениеURL.createObjectURL()будетBlob(BlobЧто это? понятия не имею? Неважно, я буду конкретен нижеделать видобъясняется) строится какobject URLобъект, указать имя файла и тип файла, создатьaСсылка имитирует щелчок для осуществления загрузки и, наконец, проходитURL.revokeObjectURLОсвободите созданный объект.

Хоть функция и реализована, но я в ней не разбираюсь~

затем следует не очень простое требование

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

Хотя подобные потребности и слышал, но от руки толком не писал, а потом начал лазить по интернету (поиски всякие,). Но на этот раз все не так просто, как я думал.

В Интернете вы видите такие вещи, какFileReader,canvas,ArrayBuffer,FormData,Blobэти существительные. Я совершенно ошеломлен, я только слышал о них обычно, и я не использую их много. После некоторого изучения я обнаружил, что все они относятся к категории знаний внешнего двоичного кода, поэтому, прежде чем начать бизнес, я собираюсь разобраться с задействованным внешним двоичным файлом.Как говорится: базовая основа определяет надстройку. 🙈

FileReader

HTML5ОпределенныйFileReaderкак файлAPIВажные члены используются для чтения файлов, согласноW3CОпределение,FileReaderИнтерфейс предоставляет методы для чтения файлов и модель событий, содержащую результаты чтения.

Создать экземпляр

var reader = new FileReader();

метод

имя метода описывать
abort Прервать операцию чтения
readAsArrayBuffer Асинхронно считывает содержимое файла побайтно, и результатArrayBufferпредставление объекта
readAsBinaryString Асинхронно считывает содержимое файла побайтно, результатом является двоичная строка файла
readAsDataURL Прочитайте содержимое файла асинхронно и используйте результатdata:urlстроковое представление
readAsText Асинхронно считывает содержимое файла по символам, а результат представляется в виде строки

событие

название события описывать
onabort Триггер по прерыванию
onerror Срабатывает при ошибке
onload Запускается после успешного завершения чтения файла
onloadend Запускается при завершении чтения (независимо от успеха или неудачи)
onloadstart Запускается, когда начинается чтение
onprogress Загрузка

Пример

Попробуем прочитать содержимое файла в виде строки:

<input type="file" id='upload' />


document.getElementById('upload').addEventListener('change', function (e) {
    var file = this.files[0];
    const reader = new FileReader();
    reader.onload = function () {
        const result = reader.result;
        console.log(result);
    }
    reader.readAsText(file);
}, false);

ArrayBuffer/TypedArray/DataView 对象

ArrayBuffer

Давайте взглянемArrayBufferфункция:

Давайте сначала представимArrayBuffer,Потому чтоFileReaderимеетreadAsArrayBuffer()метод, если файл для чтения представляет собой двоичные данные, наиболее целесообразно использовать этот метод для чтения, считанные данные представляют собойArraybufferобъекта, посмотрите на определение:

ArrayBufferОбъект, используемый для представления универсального буфера фиксированной длины необработанных двоичных данных.ArrayBufferнельзя манипулировать напрямую, но передать объект массива типа илиDataViewОбъекты для работы, они будут представлять данные в буфере в определенном формате и через эти форматы читать и записывать содержимое буфера.

ArrayBufferЭто также конструктор, который выделяет непрерывную область памяти, в которой могут храниться данные.

const buffer = new ArrayBuffer(8);
// ArrayBuffer 对象有实例属性 byteLength ,表示当前实例占用的内存字节长度(单位字节)
console.log(buffer.byteLength);

из-за невозможностиArraybufferПрямая работа, поэтому для работы нам нужно использовать другие объекты.TypedArray(тип объект массива) иDataViewобъект.

DataView 对象

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

Чтобы читать и записывать этот контент, вам нужно указать для него представление.DataViewПросмотреть создание, необходимо предоставитьArrayBufferЭкземпляр объекта в качестве параметра.

DataViewПредставление — этоArrayBufferНизкоуровневый интерфейс для чтения и записи нескольких числовых типов в объекте.

  • setint8()отDataViewисходное положение сbyteуказанное смещение для счетчика (byteOffset) хранить8-bitчисло (один байт)
  • getint8()отDataViewисходное положение сbyteуказанное смещение для счетчика (byteOffset) чтобы получить8-bitчисло (один байт)

перечислить

new DataView(buffer, [, byteOffset [, byteLength]])

Пример

let buffer = new ArrayBuffer(2);
console.log(buffer.byteLength); // 2
let dataView = new DataView(buffer);
dataView.setInt(0, 1);
dataView.setInt(1, 2);
console.log(dataView.getInt8(0)); // 1
console.log(dataView.getInt8(1)); // 2
console.log(dataView.getInt16(0)); // 258

TypedArray

ДругаяTypedArrayвид, сDataViewОдно отличие представления состоит в том, что это не конструктор, а набор конструкторов, представляющих разные форматы данных.

TypedArrayобъект описывает базовый буфер двоичных данных (binary data buffer) массивообразного представления (view).

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

Выполнение

тип диапазон значений отдельных элементов размер (байты) описывать
Int8Array -128 to 127 1 8-битное двоичное целое число со знаком
Uint8Array 0 to 255 1 8-битное целое число без знака
Int16Array -32768 to 32767 2 16-битное двоичное целое со знаком
Uint16Array 0 to 65535 2 16-битное целое число без знака

Пример

const buffer = new ArrayBuffer(8);
console.log(buffer.byteLength); // 8
const int8Array = new Int8Array(buffer);
console.log(int8Array.length); // 8
const int16Array = new Int16Array(buffer);
console.log(int16Array.length); // 4

Blob

Blobиспользуется для поддержки файловых операций. Проще говоря: вJS, есть два конструктораFileиBlob, иFileунаследовал всеBlobхарактеристики.

Итак, на наш взгляд,FileОбъект можно рассматривать как особыйBlobобъект.

сказал выше,Fileобъект – это спец.Blobобъект, то он может, естественно, вызывать напрямуюBlobметоды объекта. Давайте взглянемBlobКакие методы существуют, и какие функции могут быть достигнуты с их помощью:Да, мы больше склоняемся к применению в реальных боях~

оBlobДля более подробного ознакомления см.Blob

atobиbtoa

base64Я полагаю, что все будут знакомы с ним (если вы не знаете, см.здесь), наиболее часто используемой операцией может быть преобразование изображенияbase64правильно?

перед строкой, за которой следуетbase64Нам может понадобиться скопировать чей-то метод онлайн, и в большинстве случаев у вас нет времени, чтобы проверить, действительно ли этот метод надежен или нет.bug.

отIE10+Браузеры запускаются, все браузеры предоставляют его изначальноBase64Метод кодирования и декодирования.

Декодирование Base64

var decodedData = window.atob(encodedData);

Кодировка Base64

var encodedData = window.btoa(stringToEncode);

CanvasсерединаImageDataобъект

оCanvas, я не буду вводить здесь слишком много, вы можете обратиться к конкретнымхолст документация

Сегодня я в основном буду говорить оCanvasсерединаImageDataПредметы (также чтобы проложить путь к некоторым базовым знаниям об элементе, обрезанном на картинке ниже~)

ImageDataобъект, хранящийся вcanvasФактические пиксельные данные объекта, которые содержат следующие свойства только для чтения:

  • width: Ширина изображения в пикселях.
  • height: высота изображения в пикселях.
  • data:Uint8ClampedArrayодномерный массив типа, содержащийRGBAФорматировать целочисленные данные в диапазоне от 0 до 255 включительно.

СоздаватьImageDataобъект

использоватьcreateImageData()метод создания нового, пустогоImageDataобъект.

var myImageData = ctx.createImageData(width, height);

Приведенный выше код создает новый определенный размерImageDataобъект. Все пиксели настроены на прозрачный черный цвет.

получить данные о пикселях сцены

Чтобы получитьImageDataобъект, вы можете использоватьgetImageData()метод:

var myImageData = ctx.getImageData(left, top, width, height);

Запись пиксельных данных в сцену

ты можешь использовать этоputImageData()метод для записи пиксельных данных в сцену.

ctx.putImageData(myImageData, dx, dy);

toDataURLбудетcanvasПреобразовать вdata URIФормат

Есть следующие<canvas>элемент:

<canvas id="canvas" width="5" height="5"></canvas>

Можно получить следующим образомdata-URL

var canvas = document.getElementById("canvas");
var dataURL = canvas.toDataURL();
console.log(dataURL);
// "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNby
// blAAAADElEQVQImWNgoBMAAABpAAFEI8ARAAAAAElFTkSuQmCC"

К этому моменту я уже заложил основу для базовых знаний о двоичном коде. Вернемся к «не столь простому» новому требованию продукта, упомянутому в начале статьи: обрезка изображения, загрузка и предварительный просмотр.

На самом деле, как图片裁剪上传У такого сообщества уже есть очень зрелые решения, такие какvue-cropper. Здесь я решил написать простую обрезку изображения от руки, потому что она использует много бинарных знаний, упомянутых выше, которые могут хорошо сочетать теорию с практикой.

Без лишних слов откройте Giao! !

Развитие спроса Giao Giao!

Давайте посмотрим на окончательный эффект:

Опубликовать здеськодовый адрес

Кроме того, я использовал картинку, чтобы разобраться во взаимосвязи между упомянутыми выше интерфейсными бинарными модулями, что очень поможет при разработке следующих требований:

Все требование разделено на следующие четыре шага:

1. Получите файл и прочитайте файл.

2. Получите координаты отсечения.

3. Обрежьте изображение.

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

получить файл и прочитать файл

Во-первых, давайте взглянем на файл сбора данных, упомянутый в первом шаге выше. соответствующийinputграницаhandleChangeсобытие:

handleChange = (event) => {
  let file = event.target.files[0];
  let fileReader = new FileReader();
  fileReader.onload = (event) => {
    this.setState({
      file,
      dataURL: event.target.result,
    });
    this.imageRef.current.onload = () => this.drawImage();
  };
  fileReader.readAsDataURL(file);
};

HTML5Поддержка отinput[type=file]Информация о файле может быть получена непосредственно из элемента, а также может быть прочитано содержимое файла.

Здесь нужно использоватьFileReader, этот класс специально используется для чтения локальных файлов. Обычный текст или двоичный файл можно читать, но локальные файлы должны быть прочитаны с разрешения пользователя, то есть пользователь долженinput[type=file]Вы можете прочитать этот файл, только если выберете его в .

пройти черезFileReaderМы можем конвертировать файлы изображений вDataURL, то есть сdata:image/png;base64вид началаURL, который затем может быть помещен непосредственно вimage.src, чтобы отображалось локальное изображение.

Получить координаты обрезки

В основном здесьmousedown,mousemove,mouseupИспользуйте в сочетании с событиями.

mousedown

событие нажатия мыши. Здесь нам нужно записать координаты начала при нажатии мыши, т.е.startXиstartY, при установке бита флагаstartDragустановить какtrue, отмечая начало движения мыши.

handleMouseDown = (event) => {
  this.setState({
    startX: event.clientX,
    startY: event.clientY,
    startDrag: true,
  });
};

mousemove

Событие перемещения мыши. судитьstartDragзаtrue(то есть мышь начинает двигаться), а затем записывают расстояние, соответствующее движению.

handleMouseMove = (event) => {
  if (this.state.startDrag) {
    this.drawImage(
      event.clientX - this.state.startX + this.state.lastX,
      event.clientY - this.state.startY + this.state.lastY
    );
  }
};

mouseup

Событие «Мышь вверх». Здесь, чтобы записать координаты конечной точки падения мыши,lastXиlastY.

handleMouseUp = (event) => {
  this.setState({
    lastX: event.clientX - this.state.startX + this.state.lastX,
    lastY: event.clientY - this.state.startY + this.state.lastY,
    startDrag: false,
  });
};

Обрезать изображение

В это время нам нужно использоватьcanvasв настоящее время,canvasТо же, что и на картинке, поэтому создайте новуюcanvasопределить его высоту и ширину.

вставь картинку вcanvasнужно позвонитьdrawImage:

drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)

конкретныйAPIиспользовать ссылкуMDNВверхdrawImage

drawImage = (left = this.state.lastX, top = this.state.lastY) => {
  let image = this.imageRef.current;
  let canvas = this.canvasRef.current;
  let ctx = canvas.getContext("2d");
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  let imageWidth = image.width;
  let imageHeight = image.height;
  if (imageWidth > imageHeight) {
    let scale = canvas.width / canvas.height;
    imageWidth = canvas.width * this.state.times;
    imageHeight = imageHeight * scale * this.state.times;
  } else {
    let scale = canvas.height / canvas.width;
    imageHeight = canvas.height * this.state.times;
    imageWidth = imageWidth * scale * this.state.times;
  }
  ctx.drawImage(
    image,
    (canvas.width - imageWidth) / 2 + left,
    (canvas.height - imageHeight) / 2 + top,
    imageWidth,
    imageHeight
  );
};

Среди них мы также добавилиscale, эта переменная используется для реализации изображения放大,缩小эффект.

И он будет судить о соотношении размеров между шириной и высотой изображения, чтобы реализовать изображение вcanvasсоответствующую адаптацию.

Прочтите обрезанное изображение и загрузите его

Тогда мы получимcanvasинформация на картинке, используйтеtoDataURLможет быть преобразован в вышеуказанный используемыйDataURL.

confirm = () => {
  let canvas = this.canvasRef.current;
  let ctx = canvas.getContext("2d");
  const imageData = ctx.getImageData(100, 100, 100, 100);
  let avatarCanvas = document.createElement("canvas");
  avatarCanvas.width = 100;
  avatarCanvas.height = 100;
  let avatarCtx = avatarCanvas.getContext("2d");
  avatarCtx.putImageData(imageData, 0, 0);
  let avatarDataUrl = avatarCanvas.toDataURL();
  this.setState({ avatarDataUrl });
  this.avatarRef.current.src = avatarDataUrl;
};

затем выньте этоbase64информация, повторное использованиеwindow.atobПреобразование в двоичную строку. ноwindow.atobПреобразованный результат по-прежнему является строкой, прямо дающейBlobвсе равно пойдет не так. Так что используйте сноваUint8ArrayПреобразуйте это.

Затем обрезанный файл сохраняется вblob, мы можем рассматривать его как обычный файл и добавить вFormDataи загрузить его на сервер.

upload = (event) => {
  // console.log("文件url", this.state.avatarDataUrl);
  let bytes = atob(this.state.avatarDataUrl.split(",")[1]);
  console.log("bytes", bytes);
  let arrayBuffer = new ArrayBuffer(bytes.length);
  let uInt8Array = new Uint8Array();
  for (let i = 0; i < bytes.length; i++) {
    uInt8Array[i] = bytes.charCodeAt[i];
  }
  let blob = new Blob([arrayBuffer], { type: "image/png" });
  let xhr = new XMLHttpRequest();
  let formData = new FormData();
  formData.append("avatar", blob);
  xhr.open("POST", "/upload", true);
  xhr.send(formData);
};

Ссылаться на

  • https://es6.ruanyifeng.com/#docs/arraybuffer
  • https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Pixel_manipulation_with_canvas

❤️ Люблю тройное комбо

1. Если вы считаете, что эта статья неплохая, заходите сюдаПоделиться, Нравится, ИзбранноеСанлиан, пусть больше людей увидят это~

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

3. На спецучастках носите маску и пользуйтесь средствами индивидуальной защиты.