Skip to content

Commit c96f3c1

Browse files
committed
Add adaptive size writer with example, refs #551
1 parent 012fa10 commit c96f3c1

File tree

3 files changed

+227
-0
lines changed

3 files changed

+227
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ Below are the very pretty basic examples demonstrating of how Webcam Capture API
138138
* [How to flip (mirror) image displayed in ```WebcamPanel```](https://github.com/sarxos/webcam-capture/blob/master/webcam-capture/src/example/java/WebcamPanelFlippingExample.java)
139139
* [How to rotate image displayed in ```WebcamPanel```](https://github.com/sarxos/webcam-capture/blob/master/webcam-capture/src/example/java/WebcamPanelRotationExample.java)
140140
* [How to rotate image from camera with ```WebcamImageTransformer```](https://github.com/sarxos/webcam-capture/blob/master/webcam-capture/src/example/java/ImageTransformerRotationExample.java)
141+
* [How to use AdaptiveSizeWriter to compress images](https://github.com/sarxos/webcam-capture/blob/master/webcam-capture/src/example/java/AdaptiveSizeWriterExample.java)
141142

142143
And here are some more advanced examples, few with quite fancy GUI.
143144

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import java.awt.BorderLayout;
2+
import java.awt.Dimension;
3+
import java.awt.Graphics;
4+
import java.awt.image.BufferedImage;
5+
import java.io.ByteArrayInputStream;
6+
import java.io.IOException;
7+
import java.io.InputStream;
8+
9+
import javax.imageio.ImageIO;
10+
import javax.swing.BorderFactory;
11+
import javax.swing.JFrame;
12+
import javax.swing.JPanel;
13+
import javax.swing.JSlider;
14+
import javax.swing.SwingUtilities;
15+
import javax.swing.event.ChangeEvent;
16+
import javax.swing.event.ChangeListener;
17+
18+
import com.github.sarxos.webcam.Webcam;
19+
import com.github.sarxos.webcam.WebcamEvent;
20+
import com.github.sarxos.webcam.WebcamListener;
21+
import com.github.sarxos.webcam.WebcamResolution;
22+
import com.github.sarxos.webcam.util.AdaptiveSizeWriter;
23+
24+
25+
/**
26+
* This class demonstrate how you can use {@link AdaptiveSizeWriter} to compress video frame to JPEG
27+
* with a given max number of bytes.
28+
*/
29+
public class AdaptiveSizeWriterExample extends JFrame implements ChangeListener, WebcamListener {
30+
31+
/**
32+
* Serial.
33+
*/
34+
private static final long serialVersionUID = 1L;
35+
36+
/**
37+
* Lets assume we want to have our JPEG frames to have max size of 40 KiB.
38+
*/
39+
private static final int MAX_BYTES = 20 * 1024;
40+
41+
/**
42+
* Lets assume we want to have our JPEG frames to have min size of 5 KiB.
43+
*/
44+
private static final int MIN_BYTES = 6 * 1024;
45+
46+
/**
47+
* Webcam resolkution to use.
48+
*/
49+
private static final Dimension RESOLUTION = WebcamResolution.VGA.getSize();
50+
51+
final JSlider slider = new JSlider(JSlider.VERTICAL, MIN_BYTES, MAX_BYTES, MIN_BYTES + (MAX_BYTES - MIN_BYTES) / 2);
52+
final Webcam webcam = Webcam.getDefault();
53+
final ImagePanel panel = new ImagePanel();
54+
final AdaptiveSizeWriter writer = new AdaptiveSizeWriter(slider.getValue());
55+
56+
public AdaptiveSizeWriterExample() {
57+
58+
slider.addChangeListener(this);
59+
slider.setMajorTickSpacing(2 * 1024);
60+
slider.setPaintTicks(true);
61+
slider.setPaintLabels(true);
62+
slider.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 10));
63+
64+
panel.setPreferredSize(RESOLUTION);
65+
panel.setBorder(BorderFactory.createCompoundBorder(
66+
BorderFactory.createLoweredBevelBorder(),
67+
BorderFactory.createEmptyBorder(10, 10, 10, 10)));
68+
69+
webcam.setViewSize(RESOLUTION);
70+
webcam.addWebcamListener(this);
71+
webcam.open(true);
72+
73+
final JPanel root = new JPanel();
74+
root.setLayout(new BorderLayout());
75+
root.add(slider, BorderLayout.WEST);
76+
root.add(panel, BorderLayout.CENTER);
77+
root.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
78+
79+
setContentPane(root);
80+
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
81+
pack();
82+
setVisible(true);
83+
}
84+
85+
@Override
86+
public void stateChanged(ChangeEvent e) {
87+
writer.setSize(slider.getValue());
88+
}
89+
90+
@Override
91+
public void webcamOpen(WebcamEvent we) {
92+
}
93+
94+
@Override
95+
public void webcamClosed(WebcamEvent we) {
96+
}
97+
98+
@Override
99+
public void webcamDisposed(WebcamEvent we) {
100+
}
101+
102+
@Override
103+
public void webcamImageObtained(WebcamEvent we) {
104+
panel.setImage(writer.write(we.getImage()));
105+
}
106+
107+
public static void main(String[] args) throws IOException {
108+
new AdaptiveSizeWriterExample();
109+
}
110+
111+
private class ImagePanel extends JPanel {
112+
113+
private static final long serialVersionUID = 1L;
114+
private BufferedImage image;
115+
116+
@Override
117+
protected void paintComponent(Graphics g) {
118+
g.drawImage(image, 0, 0, this);
119+
}
120+
121+
public void setImage(byte[] bytes) {
122+
123+
try (InputStream is = new ByteArrayInputStream(bytes)) {
124+
this.image = ImageIO.read(is);
125+
} catch (IOException e) {
126+
throw new IllegalStateException(e);
127+
}
128+
129+
SwingUtilities.invokeLater(new Runnable() {
130+
131+
@Override
132+
public void run() {
133+
repaint();
134+
}
135+
});
136+
}
137+
}
138+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.github.sarxos.webcam.util;
2+
3+
import java.awt.image.BufferedImage;
4+
import java.io.ByteArrayOutputStream;
5+
import java.io.IOException;
6+
7+
import javax.imageio.IIOImage;
8+
import javax.imageio.ImageIO;
9+
import javax.imageio.ImageWriteParam;
10+
import javax.imageio.ImageWriter;
11+
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
12+
import javax.imageio.stream.MemoryCacheImageOutputStream;
13+
14+
15+
/**
16+
* This class will save {@link BufferedImage} into a byte array and try to compress it a given size.
17+
*
18+
* @author Bartosz Firyn (sarxos)
19+
*/
20+
public class AdaptiveSizeWriter {
21+
22+
private static final float INITIAL_QUALITY = 1f;
23+
24+
private volatile int size;
25+
private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
26+
private float quality = 1f; // 1f = 100% quality, at the beginning
27+
28+
public AdaptiveSizeWriter(int size) {
29+
this.size = size;
30+
}
31+
32+
public byte[] write(final BufferedImage bi) {
33+
34+
// loop and try to compress until compressed image bytes array is not longer than a given
35+
// maximum value, reduce quality by 25% in every step
36+
37+
int m = size;
38+
int s = 0;
39+
int i = 0;
40+
do {
41+
if ((s = compress(bi, quality)) > m) {
42+
quality *= 0.75;
43+
if (i++ >= 20) {
44+
break;
45+
}
46+
}
47+
} while (s > m);
48+
49+
return baos.toByteArray();
50+
}
51+
52+
/**
53+
* Compress {@link BufferedImage} with a given quality into byte array.
54+
*
55+
* @param bi the {@link BufferedImage} to compres into byte array
56+
* @param quality the compressed image quality (1 = 100%, 0.5 = 50%, 0.1 = 10%, etc)
57+
* @return The size of compressed data (number of bytes)
58+
*/
59+
private int compress(BufferedImage bi, float quality) {
60+
61+
baos.reset();
62+
63+
final JPEGImageWriteParam params = new JPEGImageWriteParam(null);
64+
params.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
65+
params.setCompressionQuality(quality);
66+
67+
try (MemoryCacheImageOutputStream mcios = new MemoryCacheImageOutputStream(baos)) {
68+
final ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();
69+
writer.setOutput(mcios);
70+
writer.write(null, new IIOImage(bi, null, null), params);
71+
} catch (IOException e) {
72+
throw new IllegalStateException(e);
73+
}
74+
75+
return baos.size();
76+
}
77+
78+
public int getSize() {
79+
return size;
80+
}
81+
82+
public void setSize(int size) {
83+
if (this.size != size) {
84+
this.size = size;
85+
this.quality = INITIAL_QUALITY;
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)