10

如何将 C# 中的图像大小调整为特定的硬盘大小,例如 2MiB?有没有比反复试验更好的方法(当然,即使它是近似的)。

尝试在网络上找到解决方案时要搜索的任何特定关键字?

4

5 回答 5

10

您可以通过将原始图像大小除以像素数来计算图像的近似信息级别:

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

这将使您非常接近所需的文件大小。如果您想更接近,您可以将图像大小调整为计算出的大小,对其进行压缩并根据您获得的文件大小计算每像素值的新字节数。

于 2009-08-11T06:56:54.680 回答
6

我通过降低质量直到达到我想要的尺寸来实现这一点。

注意:需要您添加 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;
}
}
}
于 2014-07-21T14:36:07.400 回答
1

如果它是 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,因此您需要在二进制搜索之前从总文件大小中减去更多。

于 2009-08-11T07:01:59.613 回答
1

转换、缩减(迭代、在内存中)和下载 (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;
}
于 2019-01-09T10:06:06.557 回答
0

这取决于你愿意改变什么

  1. 缩小图片尺寸
  2. 更改图像的格式
  3. 如果格式支持有损压缩,请降低质量
  4. 如果您要存储不需要的元数据,请将其删除
  5. 减少颜色数量(和每像素位数)
  6. 更改为调色板格式
  7. 更改为调色板格式并减少颜色

很难猜测最终的磁盘大小是多少,但是如果您知道起点,则可以得到一个很好的估计。减小尺寸可能是成比例的,减少每像素的位数也可能是成比例的。

如果您更改格式、压缩或质量,这实际上只是一种猜测——很大程度上取决于图像内容。通过在与您认为您将看到的内容相匹配的图像语料库上进行尝试,您可能会获得一个很好的范围。

于 2009-08-18T20:34:36.437 回答