Сжатие изображения не работает

У меня есть операция на сайте, которая обрезает изображение, однако результирующее обрезанное изображение получается значительно больше с точки зрения размера файла (исходный размер 24 КБ, а обрезанное изображение - около 650 КБ). Итак, я обнаружил, что мне нужно применить некоторое сжатие к изображению перед его сохранением. Я придумал следующее:

public static System.Drawing.Image CropImage(System.Drawing.Image image, Rectangle cropRectangle, ImageFormat format)
{
    var croppedImage = new Bitmap(cropRectangle.Width, cropRectangle.Height);
    using (var g = Graphics.FromImage(croppedImage))
    {
        g.InterpolationMode = InterpolationMode.HighQualityBicubic;
        g.DrawImage(
            image, 
            new Rectangle(new Point(0,0), new Size(cropRectangle.Width, cropRectangle.Height)), 
            cropRectangle, 
            GraphicsUnit.Pixel); 
        return CompressImage(croppedImage, format);
    }
}

public static System.Drawing.Image CompressImage(System.Drawing.Image image, ImageFormat imageFormat)
{
    var bmp = new Bitmap(image);
    var codecInfo = EncoderFactory.GetEncoderInfo(imageFormat);
    var encoder = System.Drawing.Imaging.Encoder.Quality;
    var parameters = new EncoderParameters(1);
    var parameter = new EncoderParameter(encoder, 10L);
    parameters.Param[0] = parameter;

    using (var ms = new MemoryStream())
    {
        bmp.Save(ms, codecInfo, parameters);
        var resultImage = System.Drawing.Image.FromStream(ms);
        return resultImage;
    }
}

Я установил низкое качество, чтобы увидеть, есть ли вообще какие-либо изменения. Нет. Кроп сохраняется корректно внешне, но сжатие не радует. Если я полностью обойду CompressImage(), ни размер файла, ни качество изображения не будут отличаться.

Итак, 2 вопроса. Почему ничего не происходит? Есть ли более простой способ сжать результирующее изображение для «оптимизации в Интернете», аналогично тому, как фотошоп сохраняет веб-изображения (я думал, что он просто удалил из него много информации, чтобы уменьшить размер).


person Sinaesthetic    schedule 06.02.2014    source источник
comment
В каком формате изображения вы читаете и сохраняете? BMP сжимает с трудом. PNG или JPG сжимаются намного лучше. Я могу легко преобразовать изображение из 24k PNG в 650k BMP.   -  person DasKrümelmonster    schedule 06.02.2014
comment
Я читаю в png/jpg. Bitmap - это просто промежуточный объект   -  person Sinaesthetic    schedule 06.02.2014
comment
Проблема не в том, что вы читаете, а в том, что вы сохраняете. Пожалуйста, проверьте правильность настроек condecInfo и объекта paramets... Возможно, вы сохраняете изображение с более высоким качеством, чем оригинал...   -  person HiperiX    schedule 06.02.2014


Ответы (2)


Ваша проблема в том, что вы должны «сжимать» (действительно кодировать) изображение при его сохранении, а не до его сохранения. Объект Image в вашей программе всегда несжатый.

При сохранении в MemoryStream и обратном считывании из потока изображение будет кодироваться, а затем снова декодироваться до того же размера (с некоторой потерей качества в процессе, если вы используете JPEG). Однако, если вы сохраните его в файл с параметрами сжатия, вы получите сжатый файл изображения.

Использование этой процедуры с уровнем качества JPEG 90 на исходном изображении размером 153 КБ дает выходное изображение размером 102 КБ. Если вам нужен меньший размер файла (с большим количеством артефактов кодирования), измените параметр кодировщика на значение меньше 90.

public static void SaveJpegImage(System.Drawing.Image image, string fileName)
{
    ImageCodecInfo codecInfo = ImageCodecInfo.GetImageEncoders()
        .Where(r => r.CodecName.ToUpperInvariant().Contains("JPEG"))
        .Select(r => r).FirstOrDefault();

    var encoder = System.Drawing.Imaging.Encoder.Quality;
    var parameters = new EncoderParameters(1);
    var parameter = new EncoderParameter(encoder, 90L);
    parameters.Param[0] = parameter;

    using (FileStream fs = new FileStream(fileName, FileMode.Create))
    {
        image.Save(fs, codecInfo, parameters);
    }
}
person Doug    schedule 06.02.2014
comment
Итак, в моей конкретной реализации изображение никогда не сохраняется на диск. Изображение сохраняется как двоичная информация в базе данных, а затем считывается непосредственно на веб-страницу (MVC FileResult). Как я могу сжать изображение и сохранить байты? Если я прочитаю сжатые байты из базы данных в образ (промежуточный), основываясь на том, что вы говорите, размер снова увеличится. - person Sinaesthetic; 06.02.2014
comment
И это практически идентично тому, что у меня сейчас есть, с той лишь разницей, что я прочитал его обратно в Image, прежде чем вернуть. Я попытался опустить эту часть и вернуть массив байтов из MemoryStream, но тот же результат — размер файла 600 КБ, тогда как исходный размер был 24 КБ. - person Sinaesthetic; 06.02.2014
comment
Не уверен, насколько это оптимально, но у меня есть приложение, которое сохраняет изображения в Oracle Blob. В вашем примере сразу после сохранения растрового изображения в потоке памяти получите необработанные btyes из потока: 'byte[] imageBytes = ms.ToArray();` и сохраните массив байтов в базе данных. Вы можете использовать разные кодировки - когда вы читаете его обратно из базы данных, это точно так же, как если бы вы сделали File.Read для массива байтов - обратите внимание, что это jpg или png, а не объект изображения. Вам нужно будет создать поток из байта [] и загрузить его, чтобы получить изображение. - person Doug; 06.02.2014
comment
Просто повторюсь: чтение потока памяти обратно в изображение декодирует изображение из другого формата (jpg, png, tiff) обратно в растровое изображение - вы увеличиваете изображение до исходного размера. - person Doug; 06.02.2014
comment
Конечно, но я также сказал, что когда я опускаю этот шаг и просто читаю байты непосредственно из потока (без загрузки их в Image) и наблюдаю за ними, прежде чем делать с ними что-либо еще. Я вижу, что размер файла все еще слишком велик. - person Sinaesthetic; 06.02.2014
comment
Когда вы говорите, что оригинал 24k - это размер файла png или jpg, верно? Какой тип объекта или файла вы измеряете в 650 КБ? Я только что сделал небольшую тестовую программу и открыл файл JPG размером 54 КБ на диске. Если я пишу растровое изображение в поток памяти как растровое изображение, массив необработанных байтов составляет 920 КБ. Но если я запишу то же растровое изображение в поток памяти, что и jpg уровня 90, размер необработанного массива байтов составит 77 КБ, что очень похоже на исходный файл. - person Doug; 06.02.2014
comment
Да, исходный jpg/png 350x350 @ 24k. Затем я обрезаю изображение, сжимаю его, затем наблюдаю массив байтов с размером 330 КБ, затем масштабирую кадрирование обратно до 350x350 и сжимаю, а затем наблюдаю за файлом с разрешением около 620 КБ. - person Sinaesthetic; 06.02.2014
comment
Вот схема процесса, что происходит на самом деле, если это поможет bit.ly/1dtegED - person Sinaesthetic; 06.02.2014
comment
Хорошо, я заработал. Сначала казалось, что я правильно сохранял байты во всех нужных местах, но я попытался использовать Image в качестве промежуточного объекта, и я думаю, что было несколько мест, где я использовал зигзаг, а не зиг. Я исключил Image везде, где мог, и попытался просто использовать byte[] сам по себе, и, кажется, это прояснилось. Спасибо за терпеливость :) - person Sinaesthetic; 06.02.2014

Я считаю, что вам не следует избавляться от MemoryStream, пока вы используете изображение, созданное с помощью Image.FromStream, которое ссылается на поток. Создание растрового изображения непосредственно из потока также не работает.

Попробуй это:

private static Image CropAndCompressImage(Image image, Rectangle rectangle, ImageFormat imageFormat)
{
    using(Bitmap bitmap = new Bitmap(image))
    {
        using(Bitmap cropped = bitmap.Clone(rectangle, bitmap.PixelFormat))
        {
            using (MemoryStream memoryStream = new MemoryStream())
            {
                cropped.Save(memoryStream, imageFormat);
                return new Bitmap(Image.FromStream(memoryStream));
            }
        }
    }
}
person Thejaka Maldeniya    schedule 06.02.2014