-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add adaptive size writer with example, refs #551
- Loading branch information
Showing
3 changed files
with
227 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
138 changes: 138 additions & 0 deletions
138
webcam-capture/src/example/java/AdaptiveSizeWriterExample.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
}); | ||
} | ||
} | ||
} |
88 changes: 88 additions & 0 deletions
88
webcam-capture/src/main/java/com/github/sarxos/webcam/util/AdaptiveSizeWriter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |