Skip to content

Commit

Permalink
Add adaptive size writer with example, refs #551
Browse files Browse the repository at this point in the history
  • Loading branch information
sarxos committed Jan 17, 2018
1 parent 012fa10 commit c96f3c1
Show file tree
Hide file tree
Showing 3 changed files with 227 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
138 changes: 138 additions & 0 deletions webcam-capture/src/example/java/AdaptiveSizeWriterExample.java
Original file line number Diff line number Diff line change
@@ -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();
}
});
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
}

0 comments on commit c96f3c1

Please sign in to comment.