From c96f3c17a1232cd1f9b6f208471ad96299f0238b Mon Sep 17 00:00:00 2001 From: Bartosz Firyn Date: Wed, 17 Jan 2018 18:56:36 +0100 Subject: [PATCH] Add adaptive size writer with example, refs #551 --- README.md | 1 + .../java/AdaptiveSizeWriterExample.java | 138 ++++++++++++++++++ .../webcam/util/AdaptiveSizeWriter.java | 88 +++++++++++ 3 files changed, 227 insertions(+) create mode 100644 webcam-capture/src/example/java/AdaptiveSizeWriterExample.java create mode 100644 webcam-capture/src/main/java/com/github/sarxos/webcam/util/AdaptiveSizeWriter.java diff --git a/README.md b/README.md index 7a5b9a81..9b00a1c8 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,7 @@ Below are the very pretty basic examples demonstrating of how Webcam Capture API * [How to flip (mirror) image displayed in ```WebcamPanel```](https://github.com/sarxos/webcam-capture/blob/master/webcam-capture/src/example/java/WebcamPanelFlippingExample.java) * [How to rotate image displayed in ```WebcamPanel```](https://github.com/sarxos/webcam-capture/blob/master/webcam-capture/src/example/java/WebcamPanelRotationExample.java) * [How to rotate image from camera with ```WebcamImageTransformer```](https://github.com/sarxos/webcam-capture/blob/master/webcam-capture/src/example/java/ImageTransformerRotationExample.java) +* [How to use AdaptiveSizeWriter to compress images](https://github.com/sarxos/webcam-capture/blob/master/webcam-capture/src/example/java/AdaptiveSizeWriterExample.java) And here are some more advanced examples, few with quite fancy GUI. diff --git a/webcam-capture/src/example/java/AdaptiveSizeWriterExample.java b/webcam-capture/src/example/java/AdaptiveSizeWriterExample.java new file mode 100644 index 00000000..f06c7ab7 --- /dev/null +++ b/webcam-capture/src/example/java/AdaptiveSizeWriterExample.java @@ -0,0 +1,138 @@ +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import javax.imageio.ImageIO; +import javax.swing.BorderFactory; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JSlider; +import javax.swing.SwingUtilities; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import com.github.sarxos.webcam.Webcam; +import com.github.sarxos.webcam.WebcamEvent; +import com.github.sarxos.webcam.WebcamListener; +import com.github.sarxos.webcam.WebcamResolution; +import com.github.sarxos.webcam.util.AdaptiveSizeWriter; + + +/** + * This class demonstrate how you can use {@link AdaptiveSizeWriter} to compress video frame to JPEG + * with a given max number of bytes. + */ +public class AdaptiveSizeWriterExample extends JFrame implements ChangeListener, WebcamListener { + + /** + * Serial. + */ + private static final long serialVersionUID = 1L; + + /** + * Lets assume we want to have our JPEG frames to have max size of 40 KiB. + */ + private static final int MAX_BYTES = 20 * 1024; + + /** + * Lets assume we want to have our JPEG frames to have min size of 5 KiB. + */ + private static final int MIN_BYTES = 6 * 1024; + + /** + * Webcam resolkution to use. + */ + private static final Dimension RESOLUTION = WebcamResolution.VGA.getSize(); + + final JSlider slider = new JSlider(JSlider.VERTICAL, MIN_BYTES, MAX_BYTES, MIN_BYTES + (MAX_BYTES - MIN_BYTES) / 2); + final Webcam webcam = Webcam.getDefault(); + final ImagePanel panel = new ImagePanel(); + final AdaptiveSizeWriter writer = new AdaptiveSizeWriter(slider.getValue()); + + public AdaptiveSizeWriterExample() { + + slider.addChangeListener(this); + slider.setMajorTickSpacing(2 * 1024); + slider.setPaintTicks(true); + slider.setPaintLabels(true); + slider.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 10)); + + panel.setPreferredSize(RESOLUTION); + panel.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createLoweredBevelBorder(), + BorderFactory.createEmptyBorder(10, 10, 10, 10))); + + webcam.setViewSize(RESOLUTION); + webcam.addWebcamListener(this); + webcam.open(true); + + final JPanel root = new JPanel(); + root.setLayout(new BorderLayout()); + root.add(slider, BorderLayout.WEST); + root.add(panel, BorderLayout.CENTER); + root.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + setContentPane(root); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + pack(); + setVisible(true); + } + + @Override + public void stateChanged(ChangeEvent e) { + writer.setSize(slider.getValue()); + } + + @Override + public void webcamOpen(WebcamEvent we) { + } + + @Override + public void webcamClosed(WebcamEvent we) { + } + + @Override + public void webcamDisposed(WebcamEvent we) { + } + + @Override + public void webcamImageObtained(WebcamEvent we) { + panel.setImage(writer.write(we.getImage())); + } + + public static void main(String[] args) throws IOException { + new AdaptiveSizeWriterExample(); + } + + private class ImagePanel extends JPanel { + + private static final long serialVersionUID = 1L; + private BufferedImage image; + + @Override + protected void paintComponent(Graphics g) { + g.drawImage(image, 0, 0, this); + } + + public void setImage(byte[] bytes) { + + try (InputStream is = new ByteArrayInputStream(bytes)) { + this.image = ImageIO.read(is); + } catch (IOException e) { + throw new IllegalStateException(e); + } + + SwingUtilities.invokeLater(new Runnable() { + + @Override + public void run() { + repaint(); + } + }); + } + } +} diff --git a/webcam-capture/src/main/java/com/github/sarxos/webcam/util/AdaptiveSizeWriter.java b/webcam-capture/src/main/java/com/github/sarxos/webcam/util/AdaptiveSizeWriter.java new file mode 100644 index 00000000..794713a5 --- /dev/null +++ b/webcam-capture/src/main/java/com/github/sarxos/webcam/util/AdaptiveSizeWriter.java @@ -0,0 +1,88 @@ +package com.github.sarxos.webcam.util; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.plugins.jpeg.JPEGImageWriteParam; +import javax.imageio.stream.MemoryCacheImageOutputStream; + + +/** + * This class will save {@link BufferedImage} into a byte array and try to compress it a given size. + * + * @author Bartosz Firyn (sarxos) + */ +public class AdaptiveSizeWriter { + + private static final float INITIAL_QUALITY = 1f; + + private volatile int size; + private final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + private float quality = 1f; // 1f = 100% quality, at the beginning + + public AdaptiveSizeWriter(int size) { + this.size = size; + } + + public byte[] write(final BufferedImage bi) { + + // loop and try to compress until compressed image bytes array is not longer than a given + // maximum value, reduce quality by 25% in every step + + int m = size; + int s = 0; + int i = 0; + do { + if ((s = compress(bi, quality)) > m) { + quality *= 0.75; + if (i++ >= 20) { + break; + } + } + } while (s > m); + + return baos.toByteArray(); + } + + /** + * Compress {@link BufferedImage} with a given quality into byte array. + * + * @param bi the {@link BufferedImage} to compres into byte array + * @param quality the compressed image quality (1 = 100%, 0.5 = 50%, 0.1 = 10%, etc) + * @return The size of compressed data (number of bytes) + */ + private int compress(BufferedImage bi, float quality) { + + baos.reset(); + + final JPEGImageWriteParam params = new JPEGImageWriteParam(null); + params.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + params.setCompressionQuality(quality); + + try (MemoryCacheImageOutputStream mcios = new MemoryCacheImageOutputStream(baos)) { + final ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next(); + writer.setOutput(mcios); + writer.write(null, new IIOImage(bi, null, null), params); + } catch (IOException e) { + throw new IllegalStateException(e); + } + + return baos.size(); + } + + public int getSize() { + return size; + } + + public void setSize(int size) { + if (this.size != size) { + this.size = size; + this.quality = INITIAL_QUALITY; + } + } +} \ No newline at end of file