Обеспечение экспорта JPEG меньше максимального размера файла

В настоящее время у меня есть приложение, которое снимает скриншот рабочего стола презентатора, а затем транслирует его через специальный протокол для зрителей. Для того, чтобы изображения были перенесены достаточно быстро, чтобы получить частоту кадров 2 - 3 изображения в секунду, мне нужно обеспечить, чтобы размер изображения был всегда меньше ~ 300 КБ.

Я использую С# для приложения-презентатора, который кодирует скриншот в JPEG через процесс ниже. Меня беспокоит то, что качество изображения может сильно различаться при использовании статической настройки сжатия. Если у меня будет приложение, захватывающее мой экран, выход изображений будет ~ 200 КБ, когда у меня будет полноэкранный режим Visual Studio, но если я скрою свой экран и появлюсь на рабочем столе, это будет ~ 400 КБ.

Я мог бы перевести процесс кодирования в цикл и постоянно уменьшать размер изображения до тех пор, пока размер байтового массива будет меньше 300 КБ, но это похоже на утомительную операцию. Есть ли другой способ, который я мог бы использовать?

Спасибо заранее.

// get the screenshot
System.Drawing.Rectangle totalSize = System.Drawing.Rectangle.Empty;
//foreach (Screen s in Screen.AllScreens)
totalSize = System.Drawing.Rectangle.Union(totalSize, Screen.PrimaryScreen.Bounds);
Bitmap screenShotBitmap = new Bitmap(totalSize.Width, totalSize.Height, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
screenShotBitmap.SetResolution(96, 96);
Graphics screenShotGraphics = Graphics.FromImage(screenShotBitmap);
screenShotGraphics.CopyFromScreen(totalSize.X, totalSize.Y,
                    0, 0, totalSize.Size, CopyPixelOperation.SourceCopy);
screenShotGraphics.Dispose();

// image codec information
ImageCodecInfo imageCodecInfo = GetEncoderInfo("image/jpeg");

// encoder settings
System.Drawing.Imaging.Encoder encoderQuality;
System.Drawing.Imaging.Encoder encoderColor;
encoderQuality = System.Drawing.Imaging.Encoder.Quality;
encoderColor = System.Drawing.Imaging.Encoder.ColorDepth;

// compression & quality for JPEG output
Int64 quality = 40L;

// storage for exported JPEG
byte[] screenShotByteArray;

// encoder parameters
EncoderParameter encoderQualityParameter = new EncoderParameter(encoderQuality, quality);
//EncoderParameter encoderColorParameter = new EncoderParameter(encoderColor, 8L);

// encoder parameters table
EncoderParameters encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = encoderQualityParameter;
//encoderParameters.Param[1] = encoderColorParameter;

// get the code into a memory stream
MemoryStream screenShotMemoryStream = new MemoryStream();
screenShotBitmap.Save(screenShotMemoryStream, imageCodecInfo, encoderParameters);

// convert to a byte array
screenShotByteArray = screenShotMemoryStream.GetBuffer();

// close the memory stream
screenShotMemoryStream.Close();

Ответ 1

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

РЕДАКТИРОВАТЬ: немного поясните бинарный поиск. Возьмите гипотетический пример изображения, которое сжимается до quality*10000 байтов, поэтому оптимальным параметром качества будет 30. Теперь наивный подход состоял бы в попытке установить фиксированную настройку качества (fe 80, которая дала бы 800 000 байт), а затем уменьшилась на определенное количество до 300000 байт. Если вы f.e. уменьшите качество изображения на 5 в каждом шаге, вы попробуете 12 параметров качества с помощью этого метода, пока не найдете нужную настройку. A двоичный поиск дал бы результат быстрее, например:

Quality    Size    Next step
80         800000  Too big, so quality := quality/2
40         400000  Too big, so quality := quality/2
20         200000  Too small, so quality := (40+20)/2
30         300000  Reached desired size

Это дает результат после того, как только 4 попытки (или 3 в зависимости от 200 000 байтов, которые слишком малы или просто прекрасны для вас). Поскольку размер не имеет линейного отношения к качеству, этот пример немного нереалистичен, но бинарный поиск все равно дает лучшие результаты, чем наивный подход.

Вы также можете использовать некоторые типичные изображения для "обучения". Кодируйте их, используя разные настройки качества (например, 100,90,..., 20,10) и посмотрите, насколько они велики по сравнению с их первоначальным размером. Это может дать хорошую первую оценку в большинстве случаев, хотя вам все равно придется корректировать, когда сталкиваются с изображениями с гораздо более или менее деталями в них.

В качестве альтернативы, взгляните на кодировщики JPEG2000, у них есть возможность установить размер файла вместо качества.

EDIT: я не знаю, какие библиотеки кодирования JPEG2000 для С#, там, похоже, только плавающие декодеры, поэтому это может усложниться, чем я думал вначале. Вы можете попробовать CSJ2K попробовать, но описание не похоже на его готовность к использованию.