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

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

любые конкретные ключевые слова для поиска при попытке найти решение в интернете?

4 ответов


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

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

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

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


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

info = fileSize / (width * height);

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

у меня есть уменьшенный вариант, который 101111 байт и 600х400 пикселей, поэтому он использует ~0.4213 байт на пиксель.

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

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

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

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

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


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

NB: требуется добавить систему.Ссылка на чертеж.

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