Как изменить размер изображения на С# до определенного размера жесткого диска?

StackOverflow https://stackoverflow.com/questions/1258634

Вопрос

Как изменить размер изображения на С# до определенного размера жесткого диска, например 2 МБ?Есть ли лучший способ, чем метод проб и ошибок (даже если он приблизительный, конечно).

Какие-то конкретные ключевые слова, по которым нужно искать решение в Интернете?

Это было полезно?

Решение

Это зависит от того, что вы готовы изменить

  1. Уменьшите размер изображения
  2. Изменить формат изображения
  3. Если формат поддерживает сжатие с потерями, уменьшите качество.
  4. Если вы храните метаданные, которые вам не нужны, удалите их.
  5. Уменьшите количество цветов (и бит на пиксель)
  6. Перейти к формату палитры
  7. Перейти к формату палитры и уменьшить количество цветов.

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

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

Другие советы

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

info = fileSize / (width * height);

У меня есть изображение размером 369636 байт и 1200x800 пикселей, поэтому оно использует ~0,385 байт на пиксель.

У меня есть меньшая версия размером 101111 байт и 600x400 пикселей, поэтому она использует ~0,4213 байт на пиксель.

Когда вы уменьшите изображение, вы увидите, что оно обычно содержит немного больше информации на пиксель, в данном случае примерно на 9 % больше.В зависимости от вашего типа изображений и степени их сжатия вы сможете рассчитать среднее значение увеличения соотношения информация/пиксель (c), чтобы вы могли рассчитать приблизительный размер файла:

newFileSize = (fileSize / (width * height)) * (newWidth * newHeight) * c

Из этого вы можете извлечь формулу того, насколько большим вам нужно сделать изображение, чтобы достичь определенного размера файла:

newWidth * newHeight = (newFileSize / fileSize) * (width * height) / c

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

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

Примечание:Требуется добавить ссылку на System.Drawing.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;

namespace PhotoShrinker
{
class Program
{
/// <summary>
/// Max photo size in bytes
/// </summary>
const long MAX_PHOTO_SIZE = 409600;

static void Main(string[] args)
{
    var photos = Directory.EnumerateFiles(Directory.GetCurrentDirectory(), "*.jpg");

    foreach (var photo in photos)
    {
        var photoName = Path.GetFileNameWithoutExtension(photo);

        var fi = new FileInfo(photo);
        Console.WriteLine("Photo: " + photo);
        Console.WriteLine(fi.Length);

        if (fi.Length > MAX_PHOTO_SIZE)
        {
            using (var image = Image.FromFile(photo)) 
            {
                  using (var stream = DownscaleImage(image))
                  {
                        using (var file = File.Create(photoName + "-smaller.jpg"))
                        {
                            stream.CopyTo(file);
                        }
                  }
            }
            Console.WriteLine("File resized.");
        }
        Console.WriteLine("Done.")
        Console.ReadLine();
    }

}

private static MemoryStream DownscaleImage(Image photo)
{
    MemoryStream resizedPhotoStream = new MemoryStream();

    long resizedSize = 0;
    var quality = 93;
    //long lastSizeDifference = 0;
    do
    {
        resizedPhotoStream.SetLength(0);

        EncoderParameters eps = new EncoderParameters(1);
        eps.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, (long)quality);
        ImageCodecInfo ici = GetEncoderInfo("image/jpeg");

        photo.Save(resizedPhotoStream, ici, eps);
        resizedSize = resizedPhotoStream.Length;

        //long sizeDifference = resizedSize - MAX_PHOTO_SIZE;
        //Console.WriteLine(resizedSize + "(" + sizeDifference + " " + (lastSizeDifference - sizeDifference) + ")");
        //lastSizeDifference = sizeDifference;
        quality--;

    } while (resizedSize > MAX_PHOTO_SIZE);

    resizedPhotoStream.Seek(0, SeekOrigin.Begin);

    return resizedPhotoStream;
}

private static ImageCodecInfo GetEncoderInfo(String mimeType)
{
    int j;
    ImageCodecInfo[] encoders;
    encoders = ImageCodecInfo.GetImageEncoders();
    for (j = 0; j < encoders.Length; ++j)
    {
        if (encoders[j].MimeType == mimeType)
            return encoders[j];
    }
    return null;
}
}
}

Если это 24-битный BMP, я думаю, вам нужно будет сделать что-то вроде этого:

//initial size =  WxH
long bitsperpixel = 24; //for 24 bit BMP
double ratio;
long size = 2 * 1 << 20;//2MB = 2 * 2^20
size -= 0x35;//subtract the BMP header size from it
long newH, newW, left, right, middle,BMProwsize;
left = 1;
right = size;//binary search for new width and height
while (left < right)
{
    middle = (left + right + 1) / 2;
    newW = middle;
    ratio = Convert.ToDouble(newW) / Convert.ToDouble(W);
    newH = Convert.ToInt64(ratio * Convert.ToDouble(H));
    BMProwsize = 4 * ((newW * bitsperpixel + 31) / 32);
    //row size must be multiple of 4
    if (BMProwsize * newH <= size)
        left = middle;
    else
        right = middle-1;                
}

newW = left;
ratio = Convert.ToDouble(newW) / Convert.ToDouble(W);
newH = Convert.ToInt64(ratio * Convert.ToDouble(H));
//resize image to newW x newH and it should fit in <= 2 MB

Если это другой тип BMP, например 8-битный BMP, также в разделе заголовка будет больше данных, определяющих фактический цвет каждого значения от 0 до 255, поэтому вам нужно будет вычесть больше из общего размера файла перед двоичным поиском.

Преобразование, сокращение (итерационное, в памяти) и загрузка (MVC)

public ActionResult ReduceFileSize(string ImageURL, long MAX_PHOTO_SIZE) //KB
{
    var photo = Server.MapPath("~/" + ImageURL); //Files/somefiles/2018/DOC_82401583cb534b95a10252d29a1eb4ee_1.jpg

    var photoName = Path.GetFileNameWithoutExtension(photo);

    var fi = new FileInfo(photo);

    //const long MAX_PHOTO_SIZE = 100; //KB //109600;

    var MAX_PHOTO_SIZE_BYTES = (MAX_PHOTO_SIZE * 1000);

    if (fi.Length > MAX_PHOTO_SIZE_BYTES)
    {
        using (var image = Image.FromFile(photo))
        {
            using (var mstream = DownscaleImage(image, MAX_PHOTO_SIZE_BYTES))
            {

                //Convert the memorystream to an array of bytes.
                byte[] byteArray = mstream.ToArray();
                //Clean up the memory stream
                mstream.Flush();
                mstream.Close();
                // Clear all content output from the buffer stream
                Response.Clear();
                // Add a HTTP header to the output stream that specifies the default filename
                // for the browser's download dialog
                Response.AddHeader("Content-Disposition", "attachment; filename=" + fi.Name);
                // Add a HTTP header to the output stream that contains the 
                // content length(File Size). This lets the browser know how much data is being transfered
                Response.AddHeader("Content-Length", byteArray.Length.ToString());
                // Set the HTTP MIME type of the output stream
                Response.ContentType = "application/octet-stream";
                // Write the data out to the client.
                Response.BinaryWrite(byteArray);

            }
        }
    }
    else
    {
        return null;
    }

    return null;
}



private static MemoryStream DownscaleImage(Image photo, long MAX_PHOTO_SIZE_BYTES)
{
    MemoryStream resizedPhotoStream = new MemoryStream();

    long resizedSize = 0;
    var quality = 93;
    //long lastSizeDifference = 0;
    do
    {
        resizedPhotoStream.SetLength(0);

        EncoderParameters eps = new EncoderParameters(1);
        eps.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, (long)quality);
        ImageCodecInfo ici = GetEncoderInfo("image/jpeg");

        photo.Save(resizedPhotoStream, ici, eps);
        resizedSize = resizedPhotoStream.Length;

        //long sizeDifference = resizedSize - MAX_PHOTO_SIZE;
        //Console.WriteLine(resizedSize + "(" + sizeDifference + " " + (lastSizeDifference - sizeDifference) + ")");
        //lastSizeDifference = sizeDifference;
        quality--;

    } while (resizedSize > MAX_PHOTO_SIZE_BYTES);

    resizedPhotoStream.Seek(0, SeekOrigin.Begin);

    return resizedPhotoStream;
}

private static ImageCodecInfo GetEncoderInfo(String mimeType)
{
    int j;
    ImageCodecInfo[] encoders;
    encoders = ImageCodecInfo.GetImageEncoders();
    for (j = 0; j < encoders.Length; ++j)
    {
        if (encoders[j].MimeType == mimeType)
            return encoders[j];
    }
    return null;
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top