Вопрос-ответ

Setting jpg compression level with ImageIO in Java

Настройка уровня сжатия jpg с помощью ImageIO в Java

Я использую javax.imageio.ImageIO для сохранения BufferedImage в формате jpeg. В частности, я создал следующую функцию Java:

public static void getScreenShot(BufferedImage capture, Path folder, String filename) {
try {
ImageIO.write(capture, "jpeg", new File(folder.toString()+"/"+filename+".jpg"));
} catch (AWTException | IOException ex) {
Logger.getLogger(ScreenShotMaker.class.getName()).log(Level.SEVERE, null, ex);
}
}

Как и в любом программном обеспечении для обработки изображений, я хочу изменить уровень сжатия файла jpeg. Однако я ищу эту опцию, которая, похоже, отсутствует в ImageIO.

Могу ли я установить уровень сжатия и как?

Переведено автоматически
Ответ 1

Более лаконичный способ - получить ImageWriter непосредственно из ImageIO:

ImageWriter jpgWriter = ImageIO.getImageWritersByFormatName("jpg").next();
ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam();
jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
jpgWriteParam.setCompressionQuality(0.7f);

ImageOutputStream outputStream = createOutputStream(); // For example implementations see below
jpgWriter.setOutput(outputStream);
IIOImage outputImage = new IIOImage(image, null, null);
jpgWriter.write(null, outputImage, jpgWriteParam);
jpgWriter.dispose();

Вызов ImageWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT) необходим для того, чтобы явно задать уровень сжатия (качество).

В ImageWriteParam.setCompressionQuality() 1.0f - максимальное качество, минимальное сжатие, в то время как 0.0f - минимальное качество, максимальное сжатие.

ImageWriter.setOutput должен быть передан ImageOutputStream. Хотя метод принимает Object, согласно документации, он обычно не поддерживается:


Использование общего, Object отличного от ImageOutputStream, предназначено для авторов, которые напрямую взаимодействуют с устройством вывода или протоколом обработки изображений. Набор разрешенных классов объявляется getOutputTypes методом поставщика услуг writer; большинство writer возвращают одноэлементный массив, содержащий только ImageOutputStream.class, чтобы указать, что они принимают только an ImageOutputStream.


В большинстве случаев эти два класса должны обрабатываться:


  • FileImageOutputStream - реализация ImageOutputStream, которая записывает свои выходные данные непосредственно в File или RandomAccessFile.

  • MemoryCacheImageOutputStream - реализация ImageOutputStream, которая записывает свои выходные данные в обычный OutputStream формат. Обычно используется с ByteArrayOutputStream (спасибо за совет, @lmiguelmh!).

Ответ 2

Вы должны использовать JPEGImageWriteParam, а затем сохранить изображение с помощью ImageWriter.write(). Перед записью установите вывод с помощью ImageWriter.setOutput.

Установите уровень сжатия следующим образом:

JPEGImageWriteParam jpegParams = new JPEGImageWriteParam(null);
jpegParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
jpegParams.setCompressionQuality(1f);

Где 1f - число с плавающей точкой, обозначающее 100% качество. Значение по умолчанию - около 70%, если я не ошибаюсь.

Редактировать

Затем вам нужно сделать следующее, чтобы получить экземпляр an ImageWriter. Есть два способа, короткий и длинный (я сохраняю оба, на всякий случай).

Короткий способ (предложенный лапо в одном комментарии) заключается в следующем:

final ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();
// specifies where the jpg image has to be written
writer.setOutput(new FileImageOutputStream(
new File(folder.toString() + "/" + filename + ".jpg")));

// writes the file with given compression level
// from your JPEGImageWriteParam instance
writer.write(null, new IIOImage(capture, null, null), jpegParams);

или более длинный способ

// use IIORegistry to get the available services
IIORegistry registry = IIORegistry.getDefaultInstance();
// return an iterator for the available ImageWriterSpi for jpeg images
Iterator<ImageWriterSpi> services = registry.getServiceProviders(ImageWriterSpi.class,
new ServiceRegistry.Filter() {
@Override
public boolean filter(Object provider) {
if (!(provider instanceof ImageWriterSpi)) return false;

ImageWriterSpi writerSPI = (ImageWriterSpi) provider;
String[] formatNames = writerSPI.getFormatNames();
for (int i = 0; i < formatNames.length; i++) {
if (formatNames[i].equalsIgnoreCase("JPEG")) {
return true;
}
}

return false;
}
},
true);
//...assuming that servies.hasNext() == true, I get the first available service.
ImageWriterSpi writerSpi = services.next();
ImageWriter writer = writerSpi.createWriterInstance();

// specifies where the jpg image has to be written
writer.setOutput(new FileImageOutputStream(
new File(folder.toString() + "/" + filename + ".jpg")));

// writes the file with given compression level
// from your JPEGImageWriteParam instance
writer.write(null, new IIOImage(capture, null, null), jpegParams);
Ответ 3

Более общим методом будет (из ответа Игоря) :

static void saveImage(BufferedImage image,File jpegFiletoSave,float quality) throws IOException{
// save jpeg image with specific quality. "1f" corresponds to 100% , "0.7f" corresponds to 70%

ImageWriter jpgWriter = ImageIO.getImageWritersByFormatName("jpg").next();
ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam();
jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
jpgWriteParam.setCompressionQuality(quality);

jpgWriter.setOutput(ImageIO.createImageOutputStream(jpegFiletoSave));
IIOImage outputImage = new IIOImage(image, null, null);
jpgWriter.write(null, outputImage, jpgWriteParam);
jpgWriter.dispose();

}
Ответ 4

Нашел такой же метод в моей древней библиотеке:

/**
* Work method.
* Reads the jpeg image in rendImage, compresses the image, and writes it back out to outfile.
* JPEGQuality ranges between 0.0F and 1.0F, 0-lowest, 1-highest. ios is closed internally
*
* @param rendImage [@link RenderedImage} instance with an Rendered Image
* @param ios {@link ImageOutputStream} instance,
* note that it is disposed in this method
* @param JPEGQuality float value for the JPEG compression quality (0..1(max))
* @return {@code true} if image was successfully compressed
* else {@code false} on any error, e.g. bad (null) parameters
*/

public static boolean compressJpegFile( RenderedImage rendImage, ImageOutputStream ios, float JPEGQuality )
{
if ( rendImage == null )
return false;
if ( ios == null )
return false;
if ( ( JPEGQuality <= 0.0F ) || ( JPEGQuality > 1.0F ) )
return false;
ImageWriter writer = null;
try
{

// Find a jpeg writer
Iterator iter = ImageIO.getImageWritersByFormatName( "jpg" );
if ( iter.hasNext() )
writer = (ImageWriter) iter.next();

if ( writer == null )
throw new IllegalArgumentException( "jpg writer not found by call to ImageIO.getImageWritersByFormatName( \"jpg\" )" );
writer.setOutput( ios );

// Set the compression quality
ImageWriteParam iwparam = new MyImageWriteParam();
iwparam.setCompressionMode( ImageWriteParam.MODE_EXPLICIT );
iwparam.setCompressionQuality( JPEGQuality );
// float res = iwparam.getCompressionQuality();

// Write the image
writer.write( null, new IIOImage( rendImage, null, null ), iwparam );

return true;
}
catch ( Exception e )
{
return false;
}
finally
{
if ( writer != null )
writer.dispose();
// Cleanup
try
{
ios.flush();
ios.close();
}
catch ( IOException e )
{
}
}
}
java