Схема сжатия изображения с оптимизацией внешнего интерфейса

JavaScript
Схема сжатия изображения с оптимизацией внешнего интерфейса

предисловие

Загрузка изображений/видео/файлов — это проблема, с которой мы часто сталкиваемся, но как только изображение станет слишком большим, это приведет к ухудшению работы. Загрузка изображений — распространенный бизнес-сценарий во внешнем интерфейсе. Будь то передний план или фон, изображение правильно сжато, Это может значительно улучшить пользовательский опыт. В системе управления фоном сжатие изображения может не только улучшить работу администратора фонового изображения, но и предотвратить установку слишком большого фонового изображения, что приведет к слишком долгой загрузке основного изображения, что повлияет на работу пользователя.

О сжатии изображений

думать

Подумайте об основном процессе сжатия изображений.

  • ввод для чтения файла, используйте FileReader для преобразования его в кодировку base64
  • Создайте новый img, чтобы его src прямо сейчас указывал на base64
  • Создайте новый холст и нарисуйте img на холсте.
  • Экспортировать холст как base64 или Blob с помощью canvas.toDataURL/toBlob
  • Преобразование base64 или BLOB-объекта в файл

Разбирая эти шаги один за другим, мы обнаружим, что качество изображения, похоже, связано с canvas.toDataURL, так что давайте начнем отсюда.

Подготовить

HTMLCanvasElement.toDataURL()

Метод HTMLCanvasElement.toDataURL() возвращает URI данных, содержащий отображаемое изображение. Тип можно использовать с параметром type, который по умолчанию имеет формат PNG. Разрешение картинки 96dpi.

  • Если высота или ширина холста равна 0, возвращается строка «данные:».
  • Если входящий тип не "image/png", но возвращаемое значение начинается с "data:image/png", то входящий тип не поддерживается.

грамматика

canvas.toDataURL(type, encoderOptions);

параметр

  • тип необязательный

Формат изображения, по умолчанию image/png

  • encoderOptions необязательный

Если задан формат изображения image/jpeg или image/webp, качество изображения можно выбрать от 0 до 1. Если оно превышает диапазон значений, будет использоваться значение по умолчанию 0,92. Другие параметры игнорируются. Chrome поддерживает тип «image/webp».

догадка

возможноtoDataURL(type,quality)Чем меньше второй параметр (качество), тем меньше размер файла

упражняться

Первый взгляд на исходный размер информации об изображении7.31kb

看下原图.png

toDataURL(type,quality)качество по умолчанию 0,92, чтобы увидеть, как результаты сжатия

<input id="fileInput" type="file" />
<img id="img" src="" alt="">
let fileId = document.getElementById('fileInput')
let img = document.getElementById('img')
fileId.onchange = function (e) {
  let file = e.target.files[0]
  compressImg(file, 0.92).then(res => {//compressImg方法见附录
    console.log(res)
    img.src = window.URL.createObjectURL(res.file);
  })
}

compressImg方法见附录

0.92.pngВы можете видеть, что размер изображения8.83kbПосле сжатия картинка становится крупнее, в чем дело?

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

fileId.onchange = function (e) {
  let file = e.target.files[0]
  compressImg(file, 0.1).then(res => {//compressImg方法见附录
    console.log(res)
    img.src = window.URL.createObjectURL(res.file);
  })
}

0.1.pngкогдаqualityКогда он равен 0,1, изображение имеет только1.63kb, то же качество падает

Продолжить... Много времени спустя

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

折线图.pngЯ сделал еще несколько снимков и позволил им использовать значение по умолчанию 0,92, и все они оказались больше оригинала.

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

Посмотрим, когдаqualityНасколько можно увеличить эффективность сжатия изображения

Максимальная эффективность сжатия, то есть максимальное сжатие без ущерба для качества изображения.

Попробовав серию картинок, я нашелкогдаqualityМежду 0,2 и 0,5 качество картинки сильно не меняется.qualityЧем меньше значение, тем более впечатляющей является эффективность сжатия (то есть, когда она составляет около 0,2, сжатое изображение может быть максимально увеличено, не оказывая слишком большого влияния на качество изображения).

В заключение

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

когдаqualityМежду 0,2 и 0,5 качество картинки сильно не меняется.qualityЧем меньше значение, тем более впечатляющей является эффективность сжатия (то есть, когда она составляет около 0,2, сжатое изображение может быть максимально увеличено, не оказывая слишком большого влияния на качество изображения).

приложение

/**
 * 压缩方法 
 * @param {string} file 文件
 * @param {Number} quality  0~1之间
*/
function compressImg(file, quality) {
  if (file[0]) {
    return Promise.all(Array.from(file).map(e => compressImg(e,
      quality))) // 如果是 file 数组返回 Promise 数组
  } else {
    return new Promise((resolve) => {
      const reader = new FileReader() // 创建 FileReader
      reader.onload = ({
        target: {
          result: src
        }
      }) => {
        const image = new Image() // 创建 img 元素
        image.onload = async () => {
          const canvas = document.createElement('canvas') // 创建 canvas 元素
          canvas.width = image.width
          canvas.height = image.height
          canvas.getContext('2d').drawImage(image, 0, 0, image.width, image.height) // 绘制 canvas
          const canvasURL = canvas.toDataURL('image/jpeg', quality)
          const buffer = atob(canvasURL.split(',')[1])
          let length = buffer.length
          const bufferArray = new Uint8Array(new ArrayBuffer(length))
          while (length--) {
            bufferArray[length] = buffer.charCodeAt(length)
          }
          const miniFile = new File([bufferArray], file.name, {
            type: 'image/jpeg'
          })
          resolve({
            file: miniFile,
            origin: file,
            beforeSrc: src,
            afterSrc: canvasURL,
            beforeKB: Number((file.size / 1024).toFixed(2)),
            afterKB: Number((miniFile.size / 1024).toFixed(2))
          })
        }
        image.src = src
      }
      reader.readAsDataURL(file)
    })
  }
}

Оптимизация и обновление

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

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

Для примера возьмем картинку размером 14M и размером 6016X4016.

image.png

Один14MИсходное изображение (6016X4016) не меняет качество, а только меняет размер после сжатия (1400X935)139.62KB

Вполне возможно, что ограничение размера может максимизировать эффективность сжатия.

image.png


  /**
   * 压缩图片方法
   * @param {file} file 文件
   * @param {Number} quality 图片质量(取值0-1之间默认0.92)
   */
  compressImg(file, quality) {
    var qualitys = 0.52
    console.log(parseInt((file.size / 1024).toFixed(2)))
    if (parseInt((file.size / 1024).toFixed(2)) < 1024) {
      qualitys = 0.85
    }
    if (5 * 1024 < parseInt((file.size / 1024).toFixed(2))) {
      qualitys = 0.92
    }
    if (quality) {
      qualitys = quality
    }
    if (file[0]) {
      return Promise.all(Array.from(file).map(e => this.compressImg(e,
        qualitys))) // 如果是 file 数组返回 Promise 数组
    } else {
      return new Promise((resolve) => {
        console.log(file)
        if ((file.size / 1024).toFixed(2) < 300) {
          resolve({
            file: file
          })
        } else {
          const reader = new FileReader() // 创建 FileReader
          reader.onload = ({
            target: {
              result: src
            }
          }) => {
            const image = new Image() // 创建 img 元素
            image.onload = async() => {
              const canvas = document.createElement('canvas') // 创建 canvas 元素
              const context = canvas.getContext('2d')
              var targetWidth = image.width
              var targetHeight = image.height
              var originWidth = image.width
              var originHeight = image.height
              if (1 * 1024 <= parseInt((file.size / 1024).toFixed(2)) && parseInt((file.size / 1024).toFixed(2)) <= 10 * 1024) {
                var maxWidth = 1600
                var maxHeight = 1600
                targetWidth = originWidth
                targetHeight = originHeight
                // 图片尺寸超过的限制
                if (originWidth > maxWidth || originHeight > maxHeight) {
                  if (originWidth / originHeight > maxWidth / maxHeight) {
                    // 更宽,按照宽度限定尺寸
                    targetWidth = maxWidth
                    targetHeight = Math.round(maxWidth * (originHeight / originWidth))
                  } else {
                    targetHeight = maxHeight
                    targetWidth = Math.round(maxHeight * (originWidth / originHeight))
                  }
                }
              }
              if (10 * 1024 <= parseInt((file.size / 1024).toFixed(2)) && parseInt((file.size / 1024).toFixed(2)) <= 20 * 1024) {
                maxWidth = 1400
                maxHeight = 1400
                targetWidth = originWidth
                targetHeight = originHeight
                // 图片尺寸超过的限制
                if (originWidth > maxWidth || originHeight > maxHeight) {
                  if (originWidth / originHeight > maxWidth / maxHeight) {
                    // 更宽,按照宽度限定尺寸
                    targetWidth = maxWidth
                    targetHeight = Math.round(maxWidth * (originHeight / originWidth))
                  } else {
                    targetHeight = maxHeight
                    targetWidth = Math.round(maxHeight * (originWidth / originHeight))
                  }
                }
              }
              canvas.width = targetWidth
              canvas.height = targetHeight
              context.clearRect(0, 0, targetWidth, targetHeight)
              context.drawImage(image, 0, 0, targetWidth, targetHeight) // 绘制 canvas
              const canvasURL = canvas.toDataURL('image/jpeg', qualitys)
              const buffer = atob(canvasURL.split(',')[1])
              let length = buffer.length
              const bufferArray = new Uint8Array(new ArrayBuffer(length))
              while (length--) {
                bufferArray[length] = buffer.charCodeAt(length)
              }
              const miniFile = new File([bufferArray], file.name, {
                type: 'image/jpeg'
              })
              console.log({
                file: miniFile,
                origin: file,
                beforeSrc: src,
                afterSrc: canvasURL,
                beforeKB: Number((file.size / 1024).toFixed(2)),
                afterKB: Number((miniFile.size / 1024).toFixed(2)),
                qualitys: qualitys
              })
              resolve({
                file: miniFile,
                origin: file,
                beforeSrc: src,
                afterSrc: canvasURL,
                beforeKB: Number((file.size / 1024).toFixed(2)),
                afterKB: Number((miniFile.size / 1024).toFixed(2))
              })
            }
            image.src = src
          }
          reader.readAsDataURL(file)
        }
      })
    }
  },

напиши в конце

якрутой город а, фронтенд, любит технологии и любит жизнь.

Я очень счастлив встретить тебя.

Если вы хотите узнать больше, пожалуйста, нажмите здесь, с нетерпением жду вашего маленького ⭐⭐

  • Если в статье есть ошибки, исправьте их в комментариях, если статья вам поможет, ставьте лайк и подписывайтесь 😊

  • Эта статья была впервые опубликована на Наггетс, перепечатка без разрешения запрещена 💌