Initial Commit
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/processing-app"/>
|
||||
<classpathentry kind="lib" path="tool/jai_imageio.jar"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
||||
17
Java/Processing/processing-3.3.6/tools/MovieMaker/.project
Normal file
17
Java/Processing/processing-3.3.6/tools/MovieMaker/.project
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>processing-tools-moviemaker</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
29
Java/Processing/processing-3.3.6/tools/MovieMaker/build.xml
Normal file
29
Java/Processing/processing-3.3.6/tools/MovieMaker/build.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0"?>
|
||||
<project name="Movie Maker Tool" default="build">
|
||||
|
||||
<target name="clean" description="Clean the build directories">
|
||||
<delete dir="bin" />
|
||||
<delete file="tool/MovieMaker.jar" />
|
||||
</target>
|
||||
|
||||
<target name="compile" description="Compile sources">
|
||||
<mkdir dir="bin" />
|
||||
<javac target="1.8"
|
||||
source="1.8"
|
||||
srcdir="src"
|
||||
destdir="bin"
|
||||
encoding="UTF-8"
|
||||
includeAntRuntime="false"
|
||||
classpath="../../../../app/bin"
|
||||
nowarn="true"
|
||||
compiler="org.eclipse.jdt.core.JDTCompilerAdapter">
|
||||
<compilerclasspath path="../../../../java/mode/org.eclipse.jdt.core.jar;
|
||||
../../../../java/mode/jdtCompilerAdapter.jar" />
|
||||
</javac>
|
||||
</target>
|
||||
|
||||
<target name="build" depends="compile" description="Build Movie Maker Tool">
|
||||
<mkdir dir="tool" />
|
||||
<jar basedir="bin" destfile="tool/MovieMaker.jar" />
|
||||
</target>
|
||||
</project>
|
||||
108
Java/Processing/processing-3.3.6/tools/MovieMaker/license.html
Normal file
108
Java/Processing/processing-3.3.6/tools/MovieMaker/license.html
Normal file
@@ -0,0 +1,108 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>QuickTimeDemo License</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<style type="text/css">
|
||||
<!--
|
||||
* {
|
||||
font-family: Verdana, Arial, Helvetica, sans-serif;
|
||||
line-height: 140%;
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
background-color: #FFFFFF;
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.header {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.header h1 {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.header .byline {
|
||||
font-weight: bold;
|
||||
}
|
||||
.article {
|
||||
}
|
||||
.article ul {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.toc ul {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.article p {
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
body {
|
||||
width: 512px;
|
||||
padding-top: 10px;
|
||||
padding-right: 10px;
|
||||
padding-bottom: 20px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
.article h1 {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
padding-top: 10px;
|
||||
}
|
||||
.article h2 {
|
||||
font-weight: bold;
|
||||
padding-top: 10px;
|
||||
}
|
||||
-->
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="header" >
|
||||
<h1>QuickTimeDemo</h1>
|
||||
<p class="byline">License</p>
|
||||
</div>
|
||||
<div class="toc" >
|
||||
<h1>Table of Contents</h1>
|
||||
<ul>
|
||||
<li><a href="#disclaimer">Disclaimer</a></li>
|
||||
<li><a href="#Fee2">License Fee</a></li>
|
||||
<li><a href="#Fee">License Terms</a></li>
|
||||
<li><a href="#Copyright">Copyright</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="article" >
|
||||
<h1><a name="disclaimer" id="disclaimer"></a>Disclaimer</h1>
|
||||
<p>Use of the QuickTimeDemo is entirely at your own risk. I will not be
|
||||
liable for any data
|
||||
loss, hardware damage or whatever this program might cause. </p>
|
||||
<p class="semiparagraph">Permission to use this release of QuickTimeDemo is granted provided
|
||||
you agree with its license terms, and the copyright
|
||||
notice and this license notice appear in all copies and in supporting documentation.</p>
|
||||
<h1 class="semiparagraph"><a name="Fee" id="Fee2"></a>License Fee</h1>
|
||||
<p>Use of QuickTimeDemo is free for all uses, including personal use, educational use, and commercial use.</p>
|
||||
<h1></h1>
|
||||
<h1><a name="Terms" id="Terms"></a>License Terms</h1>
|
||||
<p>You can license QuickTimeDemo under the terms of the <a href="http://creativecommons.org/licenses/by/3.0/">Creative Commons Attribution 3.0</a> license.</p>
|
||||
<h2> You are free:</h2>
|
||||
<ul>
|
||||
<li>to Share — to copy, distribute and transmit the work</li>
|
||||
<li> to Remix — to adapt the work</li>
|
||||
</ul>
|
||||
<h2> Under the following conditions:</h2>
|
||||
<ul>
|
||||
<li> Attribution. You must attribute the work in the manner specified by the author or licensor (but not in any way that suggests that they endorse you or your use of the work).</li>
|
||||
</ul>
|
||||
<p> For any reuse or distribution, you must make clear to others the license terms of this work. The best way to do this is with a link to <a href="http://creativecommons.org/licenses/by/3.0/">this web page</a>.</p>
|
||||
<p>Any of the above conditions can be waived if you get permission from the copyright holder.</p>
|
||||
<p>Nothing in this license impairs or restricts the author's moral rights.</p>
|
||||
<h1><a name="Copyright" id="Copyright"></a>Copyright</h1>
|
||||
<h5 class="semiparagraph">QuickTimeDemo © 2008-2010</h5>
|
||||
<p> Werner Randelshofer, Hausmatt 10, Immensee,
|
||||
CH-6405, Switzerland<br />
|
||||
<a href="http://www.randelshofer.ch/">http://www.randelshofer.ch/</a><br />
|
||||
<a href="mailto:werner.randelshofer@bluewin.ch">werner.randelshofer@bluewin.ch<br />
|
||||
</a>All Rights Reserved.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* @(#)AbstractTransferable.java 1.0 22. August 2007
|
||||
*
|
||||
* Copyright (c) 2007 Werner Randelshofer, Immensee, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
|
||||
package ch.randelshofer.gui.datatransfer;
|
||||
|
||||
import java.awt.datatransfer.*;
|
||||
|
||||
/**
|
||||
* Base class for transferable objects.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version 1.0 22. August 2007 Created.
|
||||
*/
|
||||
public abstract class AbstractTransferable implements Transferable {
|
||||
private DataFlavor[] flavors;
|
||||
|
||||
/** Creates a new instance. */
|
||||
public AbstractTransferable(DataFlavor[] flavors) {
|
||||
this.flavors = flavors;
|
||||
}
|
||||
|
||||
public DataFlavor[] getTransferDataFlavors() {
|
||||
return flavors.clone();
|
||||
}
|
||||
|
||||
public boolean isDataFlavorSupported(DataFlavor flavor) {
|
||||
for (DataFlavor f : flavors) {
|
||||
if (f.equals(flavor)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* @(#)CompositeTransferable.java 1.0 2002-04-07
|
||||
*
|
||||
* Copyright (c) 2001 Werner Randelshofer, Immensee, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
|
||||
package ch.randelshofer.gui.datatransfer;
|
||||
|
||||
import java.awt.datatransfer.*;
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
*/
|
||||
public class CompositeTransferable implements java.awt.datatransfer.Transferable {
|
||||
private HashMap<DataFlavor, Transferable> transferables = new HashMap<DataFlavor, Transferable>();
|
||||
private LinkedList<DataFlavor> flavors = new LinkedList<DataFlavor>();
|
||||
|
||||
/** Creates a new instance of CompositeTransferable */
|
||||
public CompositeTransferable() {
|
||||
}
|
||||
|
||||
public void add(Transferable t) {
|
||||
DataFlavor[] f = t.getTransferDataFlavors();
|
||||
for (int i=0; i < f.length; i++) {
|
||||
if (! transferables.containsKey(f[i])) flavors.add(f[i]);
|
||||
transferables.put(f[i], t);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object which represents the data to be transferred. The class
|
||||
* of the object returned is defined by the representation class of the flavor.
|
||||
*
|
||||
* @param flavor the requested flavor for the data
|
||||
* @see DataFlavor#getRepresentationClass
|
||||
* @exception IOException if the data is no longer available
|
||||
* in the requested flavor.
|
||||
* @exception UnsupportedFlavorException if the requested data flavor is
|
||||
* not supported.
|
||||
*/
|
||||
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
|
||||
Transferable t = transferables.get(flavor);
|
||||
if (t == null) throw new UnsupportedFlavorException(flavor);
|
||||
return t.getTransferData(flavor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of DataFlavor objects indicating the flavors the data
|
||||
* can be provided in. The array should be ordered according to preference
|
||||
* for providing the data (from most richly descriptive to least descriptive).
|
||||
* @return an array of data flavors in which this data can be transferred
|
||||
*/
|
||||
public DataFlavor[] getTransferDataFlavors() {
|
||||
return flavors.toArray(new DataFlavor[transferables.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the specified data flavor is supported for
|
||||
* this object.
|
||||
* @param flavor the requested flavor for the data
|
||||
* @return boolean indicating wjether or not the data flavor is supported
|
||||
*/
|
||||
public boolean isDataFlavorSupported(DataFlavor flavor) {
|
||||
return transferables.containsKey(flavor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
/*
|
||||
* @(#)FileTextFieldTransferHandler.java 1.2 2010-10-02
|
||||
*
|
||||
* Copyright (c) 2007-2010 Werner Randelshofer
|
||||
* Staldenmattweg 2, CH-6405 Immensee, Switzerland
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is the confidential and proprietary information of
|
||||
* Werner Randelshofer. ("Confidential Information"). You shall not
|
||||
* disclose such Confidential Information and shall use it only in
|
||||
* accordance with the terms of the license agreement you entered into
|
||||
* with Werner Randelshofer.
|
||||
*/
|
||||
package ch.randelshofer.gui.datatransfer;
|
||||
|
||||
import java.awt.datatransfer.*;
|
||||
import java.awt.im.InputContext;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import javax.swing.*;
|
||||
import javax.swing.text.*;
|
||||
|
||||
/**
|
||||
* The FileTextFieldTransferHandler can be used to add drag and drop
|
||||
* support for JTextFields, which contain the path to a file.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version 1.2 2010-10-03 Adds support for file filter.
|
||||
* <br>1.1 2008-12-03 Added file selection mode.
|
||||
* <br>1.0 September 8, 2007 Created.
|
||||
*/
|
||||
public class FileTextFieldTransferHandler extends TransferHandler {
|
||||
|
||||
private boolean shouldRemove;
|
||||
private JTextComponent exportComp;
|
||||
private int p0;
|
||||
private int p1;
|
||||
private int fileSelectionMode;
|
||||
private FileFilter fileFilter;
|
||||
|
||||
/** Creates a new instance. */
|
||||
public FileTextFieldTransferHandler() {
|
||||
this(JFileChooser.FILES_ONLY);
|
||||
}
|
||||
|
||||
/** Creates a new instance.
|
||||
* @param fileSelectionMode JFileChooser file selection mode.
|
||||
*/
|
||||
public FileTextFieldTransferHandler(int fileSelectionMode) {
|
||||
this(fileSelectionMode, null);
|
||||
}
|
||||
|
||||
/** Creates a new instance.
|
||||
* @param fileSelectionMode JFileChooser file selection mode.
|
||||
*/
|
||||
public FileTextFieldTransferHandler(int fileSelectionMode, FileFilter filter) {
|
||||
this.fileFilter = filter;
|
||||
if (fileSelectionMode != JFileChooser.FILES_AND_DIRECTORIES
|
||||
&& fileSelectionMode != JFileChooser.FILES_ONLY
|
||||
&& fileSelectionMode != JFileChooser.DIRECTORIES_ONLY) {
|
||||
throw new IllegalArgumentException("illegal file selection mode:" + fileSelectionMode);
|
||||
}
|
||||
this.fileSelectionMode = fileSelectionMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean importData(JComponent comp, Transferable t) {
|
||||
JTextComponent c = (JTextComponent) comp;
|
||||
|
||||
// if we are importing to the same component that we exported from
|
||||
// then don't actually do anything if the drop location is inside
|
||||
// the drag location and set shouldRemove to false so that exportDone
|
||||
// knows not to remove any data
|
||||
if (c == exportComp && c.getCaretPosition() >= p0 && c.getCaretPosition() <= p1) {
|
||||
shouldRemove = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean imported = false;
|
||||
if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
|
||||
InputContext ic = c.getInputContext();
|
||||
if (ic != null) {
|
||||
ic.endComposition();
|
||||
}
|
||||
|
||||
try {
|
||||
List<?> list = (List<?>)
|
||||
t.getTransferData(DataFlavor.javaFileListFlavor);
|
||||
if (list.size() > 0) {
|
||||
File file = (File) list.get(0);
|
||||
|
||||
switch (fileSelectionMode) {
|
||||
case JFileChooser.FILES_AND_DIRECTORIES:
|
||||
break;
|
||||
case JFileChooser.FILES_ONLY:
|
||||
if (file.isDirectory()) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case JFileChooser.DIRECTORIES_ONLY:
|
||||
if (!file.isDirectory()) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (fileFilter != null && !fileFilter.accept(file)) {
|
||||
return false;
|
||||
}
|
||||
c.setText(file.getPath());
|
||||
}
|
||||
imported = true;
|
||||
} catch (UnsupportedFlavorException ex) {
|
||||
// ex.printStackTrace();
|
||||
} catch (IOException ex) {
|
||||
// ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (!imported) {
|
||||
DataFlavor importFlavor = getImportFlavor(t.getTransferDataFlavors(), c);
|
||||
if (importFlavor != null) {
|
||||
InputContext ic = c.getInputContext();
|
||||
if (ic != null) {
|
||||
ic.endComposition();
|
||||
}
|
||||
try {
|
||||
// String text = (String) t.getTransferData(DataFlavor.stringFlavor);
|
||||
Reader r = importFlavor.getReaderForText(t);
|
||||
boolean useRead = false;
|
||||
handleReaderImport(r, c, useRead);
|
||||
imported = true;
|
||||
} catch (UnsupportedFlavorException ex) {
|
||||
// ex.printStackTrace();
|
||||
} catch (BadLocationException ex) {
|
||||
// ex.printStackTrace();
|
||||
} catch (IOException ex) {
|
||||
// ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
return imported;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Transferable createTransferable(JComponent comp) {
|
||||
|
||||
CompositeTransferable t;
|
||||
|
||||
JTextComponent c = (JTextComponent) comp;
|
||||
shouldRemove = true;
|
||||
p0 = c.getSelectionStart();
|
||||
p1 = c.getSelectionEnd();
|
||||
if (p0 != p1) {
|
||||
t = new CompositeTransferable();
|
||||
|
||||
String text = c.getSelectedText();
|
||||
|
||||
//LinkedList fileList = new LinkedList();
|
||||
//fileList.add(new File(text));
|
||||
//t.add(new JVMLocalObjectTransferable(DataFlavor.javaFileListFlavor, fileList));
|
||||
t.add(new StringTransferable(text));
|
||||
t.add(new PlainTextTransferable(text));
|
||||
} else {
|
||||
t = null;
|
||||
}
|
||||
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
|
||||
JTextComponent c = (JTextComponent) comp;
|
||||
if (!(c.isEditable() && c.isEnabled())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (DataFlavor flavor : transferFlavors) {
|
||||
if (flavor.isFlavorJavaFileListType()
|
||||
|| flavor.isFlavorTextType()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to find a flavor that can be used to import a Transferable.
|
||||
* The set of usable flavors are tried in the following order:
|
||||
* <ol>
|
||||
* <li>First, an attempt to find a text/plain flavor is made.
|
||||
* <li>Second, an attempt to find a flavor representing a String reference
|
||||
* in the same VM is made.
|
||||
* <li>Lastly, DataFlavor.stringFlavor is searched for.
|
||||
* </ol>
|
||||
*/
|
||||
protected DataFlavor getImportFlavor(DataFlavor[] flavors, JTextComponent c) {
|
||||
// DataFlavor plainFlavor = null;
|
||||
DataFlavor refFlavor = null;
|
||||
DataFlavor stringFlavor = null;
|
||||
|
||||
for (int i = 0; i < flavors.length; i++) {
|
||||
String mime = flavors[i].getMimeType();
|
||||
if (mime.startsWith("text/plain")) {
|
||||
return flavors[i];
|
||||
} else if (refFlavor == null && mime.startsWith("application/x-java-jvm-local-objectref") && flavors[i].getRepresentationClass() == java.lang.String.class) {
|
||||
refFlavor = flavors[i];
|
||||
} else if (stringFlavor == null && flavors[i].equals(DataFlavor.stringFlavor)) {
|
||||
stringFlavor = flavors[i];
|
||||
}
|
||||
}
|
||||
if (refFlavor != null) {
|
||||
return refFlavor;
|
||||
} else if (stringFlavor != null) {
|
||||
return stringFlavor;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import the given stream data into the text component.
|
||||
*/
|
||||
protected void handleReaderImport(Reader in, JTextComponent c, boolean useRead)
|
||||
throws BadLocationException, IOException {
|
||||
if (useRead) {
|
||||
int startPosition = c.getSelectionStart();
|
||||
int endPosition = c.getSelectionEnd();
|
||||
int length = endPosition - startPosition;
|
||||
EditorKit kit = c.getUI().getEditorKit(c);
|
||||
Document doc = c.getDocument();
|
||||
if (length > 0) {
|
||||
doc.remove(startPosition, length);
|
||||
}
|
||||
kit.read(in, doc, startPosition);
|
||||
} else {
|
||||
char[] buff = new char[1024];
|
||||
int nch;
|
||||
boolean lastWasCR = false;
|
||||
int last;
|
||||
StringBuilder sb = null;
|
||||
|
||||
// Read in a block at a time, mapping \r\n to \n, as well as single
|
||||
// \r to \n.
|
||||
while ((nch = in.read(buff, 0, buff.length)) != -1) {
|
||||
if (sb == null) {
|
||||
sb = new StringBuilder(nch);
|
||||
}
|
||||
last = 0;
|
||||
for (int counter = 0; counter < nch; counter++) {
|
||||
switch (buff[counter]) {
|
||||
case '\r':
|
||||
if (lastWasCR) {
|
||||
if (counter == 0) {
|
||||
sb.append('\n');
|
||||
} else {
|
||||
buff[counter - 1] = '\n';
|
||||
}
|
||||
} else {
|
||||
lastWasCR = true;
|
||||
}
|
||||
break;
|
||||
case '\n':
|
||||
if (lastWasCR) {
|
||||
if (counter > (last + 1)) {
|
||||
sb.append(buff, last, counter - last - 1);
|
||||
}
|
||||
// else nothing to do, can skip \r, next write will
|
||||
// write \n
|
||||
lastWasCR = false;
|
||||
last = counter;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (lastWasCR) {
|
||||
if (counter == 0) {
|
||||
sb.append('\n');
|
||||
} else {
|
||||
buff[counter - 1] = '\n';
|
||||
}
|
||||
lastWasCR = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (last < nch) {
|
||||
if (lastWasCR) {
|
||||
if (last < (nch - 1)) {
|
||||
sb.append(buff, last, nch - last - 1);
|
||||
}
|
||||
} else {
|
||||
sb.append(buff, last, nch - last);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastWasCR) {
|
||||
sb.append('\n');
|
||||
}
|
||||
System.out.println("FileTextTransferHandler " + c.getSelectionStart() + ".." + c.getSelectionEnd());
|
||||
c.replaceSelection(sb != null ? sb.toString() : "");
|
||||
}
|
||||
}
|
||||
|
||||
// --- TransferHandler methods ------------------------------------
|
||||
/**
|
||||
* This is the type of transfer actions supported by the source. Some models are
|
||||
* not mutable, so a transfer operation of COPY only should
|
||||
* be advertised in that case.
|
||||
*
|
||||
* @param comp The component holding the data to be transfered. This
|
||||
* argument is provided to enable sharing of TransferHandlers by
|
||||
* multiple components.
|
||||
* @return This is implemented to return NONE if the component is a JPasswordField
|
||||
* since exporting data via user gestures is not allowed. If the text component is
|
||||
* editable, COPY_OR_MOVE is returned, otherwise just COPY is allowed.
|
||||
*/
|
||||
@Override
|
||||
public int getSourceActions(JComponent comp) {
|
||||
JTextComponent c = (JTextComponent) comp;
|
||||
|
||||
if (c instanceof JPasswordField
|
||||
&& c.getClientProperty("JPasswordField.cutCopyAllowed")
|
||||
!= Boolean.TRUE) {
|
||||
return NONE;
|
||||
}
|
||||
|
||||
return c.isEditable() ? COPY_OR_MOVE : COPY;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called after data has been exported. This method should remove
|
||||
* the data that was transfered if the action was MOVE.
|
||||
*
|
||||
* @param comp The component that was the source of the data.
|
||||
* @param data The data that was transferred or possibly null
|
||||
* if the action is <code>NONE</code>.
|
||||
* @param action The actual action that was performed.
|
||||
*/
|
||||
@Override
|
||||
protected void exportDone(JComponent comp, Transferable data, int action) {
|
||||
JTextComponent c = (JTextComponent) comp;
|
||||
|
||||
// only remove the text if shouldRemove has not been set to
|
||||
// false by importData and only if the action is a move
|
||||
if (shouldRemove && action == MOVE) {
|
||||
try {
|
||||
Document doc = c.getDocument();
|
||||
doc.remove(p0, p1 - p0);
|
||||
} catch (BadLocationException e) {
|
||||
}
|
||||
}
|
||||
exportComp = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the fileFilter
|
||||
*/
|
||||
public FileFilter getFileFilter() {
|
||||
return fileFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fileFilter the fileFilter to set
|
||||
*/
|
||||
public void setFileFilter(FileFilter fileFilter) {
|
||||
this.fileFilter = fileFilter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* @(#)PlainTextTransferable.java 1.1 2009-09-01
|
||||
*
|
||||
* Copyright (c) 2007-2009 Werner Randelshofer, Immensee, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
|
||||
package ch.randelshofer.gui.datatransfer;
|
||||
|
||||
import java.awt.datatransfer.*;
|
||||
import java.io.*;
|
||||
/**
|
||||
* PlainTextTransferable.
|
||||
* <p>
|
||||
* Note: This transferable should (almost) always be used in conjunction with
|
||||
* PlainTextTransferable.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version 1.1 2009-09-01 Replaced use of deprecated class StringBufferInputStream.
|
||||
* <br>1.0 22. August 2007 Created.
|
||||
*/
|
||||
public class PlainTextTransferable extends AbstractTransferable {
|
||||
private String plainText;
|
||||
|
||||
public PlainTextTransferable(String plainText) {
|
||||
this(getDefaultFlavors(), plainText);
|
||||
}
|
||||
public PlainTextTransferable(DataFlavor flavor, String plainText) {
|
||||
this(new DataFlavor[] { flavor }, plainText);
|
||||
}
|
||||
public PlainTextTransferable(DataFlavor[] flavors, String plainText) {
|
||||
super(flavors);
|
||||
this.plainText = plainText;
|
||||
}
|
||||
|
||||
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
|
||||
if (! isDataFlavorSupported(flavor)) {
|
||||
throw new UnsupportedFlavorException(flavor);
|
||||
}
|
||||
plainText = (plainText == null) ? "" : plainText;
|
||||
if (String.class.equals(flavor.getRepresentationClass())) {
|
||||
return plainText;
|
||||
} else if (Reader.class.equals(flavor.getRepresentationClass())) {
|
||||
return new StringReader(plainText);
|
||||
} else if (InputStream.class.equals(flavor.getRepresentationClass())) {
|
||||
String charsetName = flavor.getParameter("charset");
|
||||
return new ByteArrayInputStream(plainText.getBytes(charsetName==null?"UTF-8":charsetName));
|
||||
//return new StringBufferInputStream(plainText);
|
||||
} // fall through to unsupported
|
||||
|
||||
throw new UnsupportedFlavorException(flavor);
|
||||
}
|
||||
|
||||
protected static DataFlavor[] getDefaultFlavors() {
|
||||
try {
|
||||
return new DataFlavor[] {
|
||||
new DataFlavor("text/plain;class=java.lang.String"),
|
||||
new DataFlavor("text/plain;class=java.io.Reader"),
|
||||
new DataFlavor("text/plain;charset=unicode;class=java.io.InputStream")
|
||||
};
|
||||
} catch (ClassNotFoundException cle) {
|
||||
InternalError ie = new InternalError(
|
||||
"error initializing PlainTextTransferable");
|
||||
ie.initCause(cle);
|
||||
throw ie;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* @(#)StringTransferable.java 1.0 22. August 2007
|
||||
*
|
||||
* Copyright (c) 2007 Werner Randelshofer, Immensee, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
|
||||
package ch.randelshofer.gui.datatransfer;
|
||||
|
||||
import java.awt.datatransfer.*;
|
||||
import java.io.IOException;
|
||||
/**
|
||||
* StringTransferable.
|
||||
* <p>
|
||||
* Note: This transferable should always be used in conjunction with
|
||||
* PlainTextTransferable.
|
||||
* <p>
|
||||
* Usage:
|
||||
* <pre>
|
||||
* String text = "bla";
|
||||
* CompositeTransfer t = new CompositeTransferable();
|
||||
* t.add(new StringTransferable(text));
|
||||
* t.add(new PlainTextTransferable(text));
|
||||
* </pre>
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version 1.0 22. August 2007 Created.
|
||||
*/
|
||||
public class StringTransferable extends AbstractTransferable {
|
||||
private String string;
|
||||
|
||||
public StringTransferable(String string) {
|
||||
this(getDefaultFlavors(), string);
|
||||
}
|
||||
public StringTransferable(DataFlavor flavor, String string) {
|
||||
this(new DataFlavor[] { flavor }, string);
|
||||
}
|
||||
public StringTransferable(DataFlavor[] flavors, String string) {
|
||||
super(flavors);
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
|
||||
if (! isDataFlavorSupported(flavor)) {
|
||||
throw new UnsupportedFlavorException(flavor);
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
protected static DataFlavor[] getDefaultFlavors() {
|
||||
try {
|
||||
return new DataFlavor[] {
|
||||
new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType+";class=java.lang.String"),
|
||||
DataFlavor.stringFlavor
|
||||
};
|
||||
} catch (ClassNotFoundException cle) {
|
||||
InternalError ie = new InternalError(
|
||||
"error initializing StringTransferable");
|
||||
ie.initCause(cle);
|
||||
throw ie;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* @(#)ImageOutputStreamAdapter.java 1.1 2011-01-07
|
||||
*
|
||||
* Copyright © 2010 Werner Randelshofer, Immensee, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package ch.randelshofer.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
|
||||
/**
|
||||
* Adapts an {@code ImageOutputStream} for classes requiring an
|
||||
* {@code OutputStream}.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version 1.1 2011-01-07 Fixes performance.
|
||||
* <br>1.0 2010-12-26 Created.
|
||||
*/
|
||||
public class ImageOutputStreamAdapter extends OutputStream {
|
||||
|
||||
/**
|
||||
* The underlying output stream to be filtered.
|
||||
*/
|
||||
protected ImageOutputStream out;
|
||||
|
||||
/**
|
||||
* Creates an output stream filter built on top of the specified
|
||||
* underlying output stream.
|
||||
*
|
||||
* @param out the underlying output stream to be assigned to
|
||||
* the field <tt>this.out</tt> for later use, or
|
||||
* <code>null</code> if this instance is to be
|
||||
* created without an underlying stream.
|
||||
*/
|
||||
public ImageOutputStreamAdapter(ImageOutputStream out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the specified <code>byte</code> to this output stream.
|
||||
* <p>
|
||||
* The <code>write</code> method of <code>FilterOutputStream</code>
|
||||
* calls the <code>write</code> method of its underlying output stream,
|
||||
* that is, it performs <tt>out.write(b)</tt>.
|
||||
* <p>
|
||||
* Implements the abstract <tt>write</tt> method of <tt>OutputStream</tt>.
|
||||
*
|
||||
* @param b the <code>byte</code>.
|
||||
* @exception IOException if an I/O error occurs.
|
||||
*/
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
out.write(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes <code>b.length</code> bytes to this output stream.
|
||||
* <p>
|
||||
* The <code>write</code> method of <code>FilterOutputStream</code>
|
||||
* calls its <code>write</code> method of three arguments with the
|
||||
* arguments <code>b</code>, <code>0</code>, and
|
||||
* <code>b.length</code>.
|
||||
* <p>
|
||||
* Note that this method does not call the one-argument
|
||||
* <code>write</code> method of its underlying stream with the single
|
||||
* argument <code>b</code>.
|
||||
*
|
||||
* @param b the data to be written.
|
||||
* @exception IOException if an I/O error occurs.
|
||||
* @see java.io.FilterOutputStream#write(byte[], int, int)
|
||||
*/
|
||||
@Override
|
||||
public void write(byte b[]) throws IOException {
|
||||
write(b, 0, b.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes <code>len</code> bytes from the specified
|
||||
* <code>byte</code> array starting at offset <code>off</code> to
|
||||
* this output stream.
|
||||
* <p>
|
||||
* The <code>write</code> method of <code>FilterOutputStream</code>
|
||||
* calls the <code>write</code> method of one argument on each
|
||||
* <code>byte</code> to output.
|
||||
* <p>
|
||||
* Note that this method does not call the <code>write</code> method
|
||||
* of its underlying input stream with the same arguments. Subclasses
|
||||
* of <code>FilterOutputStream</code> should provide a more efficient
|
||||
* implementation of this method.
|
||||
*
|
||||
* @param b the data.
|
||||
* @param off the start offset in the data.
|
||||
* @param len the number of bytes to write.
|
||||
* @exception IOException if an I/O error occurs.
|
||||
* @see java.io.FilterOutputStream#write(int)
|
||||
*/
|
||||
@Override
|
||||
public void write(byte b[], int off, int len) throws IOException {
|
||||
out.write(b,off,len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes this output stream and forces any buffered output bytes
|
||||
* to be written out to the stream.
|
||||
* <p>
|
||||
* The <code>flush</code> method of <code>FilterOutputStream</code>
|
||||
* calls the <code>flush</code> method of its underlying output stream.
|
||||
*
|
||||
* @exception IOException if an I/O error occurs.
|
||||
* @see java.io.FilterOutputStream#out
|
||||
*/
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
out.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes this output stream and releases any system resources
|
||||
* associated with the stream.
|
||||
* <p>
|
||||
* The <code>close</code> method of <code>FilterOutputStream</code>
|
||||
* calls its <code>flush</code> method, and then calls the
|
||||
* <code>close</code> method of its underlying output stream.
|
||||
*
|
||||
* @exception IOException if an I/O error occurs.
|
||||
* @see java.io.FilterOutputStream#flush()
|
||||
* @see java.io.FilterOutputStream#out
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
flush();
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* @(#)SeekableByteArrayOutputStream.java 1.0 2010-12-27
|
||||
*
|
||||
* Copyright © 2010 Werner Randelshofer, Immensee, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
|
||||
package ch.randelshofer.io;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import static java.lang.Math.*;
|
||||
/**
|
||||
* {@code SeekableByteArrayOutputStream}.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version 1.0 2010-12-27 Created.
|
||||
*/
|
||||
public class SeekableByteArrayOutputStream extends ByteArrayOutputStream {
|
||||
|
||||
/**
|
||||
* The current stream position.
|
||||
*/
|
||||
private int pos;
|
||||
|
||||
/**
|
||||
* Creates a new byte array output stream. The buffer capacity is
|
||||
* initially 32 bytes, though its size increases if necessary.
|
||||
*/
|
||||
public SeekableByteArrayOutputStream() {
|
||||
this(32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new byte array output stream, with a buffer capacity of
|
||||
* the specified size, in bytes.
|
||||
*
|
||||
* @param size the initial size.
|
||||
* @exception IllegalArgumentException if size is negative.
|
||||
*/
|
||||
public SeekableByteArrayOutputStream(int size) {
|
||||
if (size < 0) {
|
||||
throw new IllegalArgumentException("Negative initial size: "
|
||||
+ size);
|
||||
}
|
||||
buf = new byte[size];
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the specified byte to this byte array output stream.
|
||||
*
|
||||
* @param b the byte to be written.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void write(int b) {
|
||||
int newcount = max(pos + 1, count);
|
||||
if (newcount > buf.length) {
|
||||
buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount));
|
||||
}
|
||||
buf[pos++] = (byte)b;
|
||||
count = newcount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes <code>len</code> bytes from the specified byte array
|
||||
* starting at offset <code>off</code> to this byte array output stream.
|
||||
*
|
||||
* @param b the data.
|
||||
* @param off the start offset in the data.
|
||||
* @param len the number of bytes to write.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void write(byte b[], int off, int len) {
|
||||
if ((off < 0) || (off > b.length) || (len < 0) ||
|
||||
((off + len) > b.length) || ((off + len) < 0)) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
} else if (len == 0) {
|
||||
return;
|
||||
}
|
||||
int newcount = max(pos+len,count);
|
||||
if (newcount > buf.length) {
|
||||
buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount));
|
||||
}
|
||||
System.arraycopy(b, off, buf, pos, len);
|
||||
pos+=len;
|
||||
count = newcount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the <code>count</code> field of this byte array output
|
||||
* stream to zero, so that all currently accumulated output in the
|
||||
* output stream is discarded. The output stream can be used again,
|
||||
* reusing the already allocated buffer space.
|
||||
*
|
||||
* @see java.io.ByteArrayInputStream#count
|
||||
*/
|
||||
@Override
|
||||
public synchronized void reset() {
|
||||
count = 0;
|
||||
pos=0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current stream position to the desired location. The
|
||||
* next read will occur at this location. The bit offset is set
|
||||
* to 0.
|
||||
*
|
||||
* <p> An <code>IndexOutOfBoundsException</code> will be thrown if
|
||||
* <code>pos</code> is smaller than the flushed position (as
|
||||
* returned by <code>getflushedPosition</code>).
|
||||
*
|
||||
* <p> It is legal to seek past the end of the file; an
|
||||
* <code>EOFException</code> will be thrown only if a read is
|
||||
* performed.
|
||||
*
|
||||
* @param pos a <code>long</code> containing the desired file
|
||||
* pointer position.
|
||||
*
|
||||
* @exception IndexOutOfBoundsException if <code>pos</code> is smaller
|
||||
* than the flushed position.
|
||||
* @exception IOException if any other I/O error occurs.
|
||||
*/
|
||||
public void seek(long pos) throws IOException {
|
||||
this.pos = (int)pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current byte position of the stream. The next write
|
||||
* will take place starting at this offset.
|
||||
*
|
||||
* @return a long containing the position of the stream.
|
||||
*
|
||||
* @exception IOException if an I/O error occurs.
|
||||
*/
|
||||
public long getStreamPosition() throws IOException {
|
||||
return pos;
|
||||
}
|
||||
|
||||
/** Writes the contents of the byte array into the specified output
|
||||
* stream.
|
||||
* @param out
|
||||
*/
|
||||
public void toOutputStream(OutputStream out) throws IOException {
|
||||
out.write(buf, 0, count);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* @(#)MP3AudioInputStream.java 1.0 2011-01-01
|
||||
*
|
||||
* Copyright © 2010 Werner Randelshofer, Immensee, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package ch.randelshofer.media.mp3;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.AudioInputStream;
|
||||
|
||||
/**
|
||||
* {@code AudioInputStream} adapter for {@link MP3ElementaryInputStream}.
|
||||
* <p>
|
||||
* Unlike a regular audio input stream, an MP3 audio input stream can have a
|
||||
* variable frame size and can change its encoding method in mid-stream.
|
||||
* Therefore method getFormat can return different values for each frame,
|
||||
* and mark/reset is not supported, and method getFrameLength can not return
|
||||
* the total number of frames in the stream.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version 1.0 2011-01-01 Created.
|
||||
*/
|
||||
public class MP3AudioInputStream extends AudioInputStream {
|
||||
|
||||
private MP3ElementaryInputStream in;
|
||||
|
||||
/** Creates an MP3AudioInputStream and reads the stream until the first
|
||||
* frame is reached.
|
||||
*
|
||||
* @param file A File.
|
||||
* @throws IOException if the file does not contain an MP3 elementary stream.
|
||||
*/
|
||||
public MP3AudioInputStream(File file) throws IOException {
|
||||
this(new BufferedInputStream(new FileInputStream(file)));
|
||||
}
|
||||
|
||||
/** Creates an MP3AudioInputStream and reads the stream until the first
|
||||
* frame is reached.
|
||||
*
|
||||
* @param in An InputStream.
|
||||
* @throws IOException if the stream does not contain an MP3 elementary stream.
|
||||
*/
|
||||
public MP3AudioInputStream(InputStream in) throws IOException {
|
||||
// Feed superclass with nonsense - we override all methods anyway.
|
||||
super(null, new AudioFormat(MP3ElementaryInputStream.MP3, 44100, 16, 2, 626, 44100f / 1152f, true), -1);
|
||||
this.in = new MP3ElementaryInputStream(in);
|
||||
if (this.in.getNextFrame() == null) {
|
||||
throw new IOException("Stream is not an MP3 elementary stream");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return in.available();
|
||||
}
|
||||
|
||||
/** Returns the format of the <i>next</i> frame. Returns null if the stream
|
||||
* is not positioned inside a frame.
|
||||
*/
|
||||
@Override
|
||||
public AudioFormat getFormat() {
|
||||
return in.getFormat();
|
||||
}
|
||||
|
||||
/** Returns -1 because we don't know how many frames the stream has. */
|
||||
@Override
|
||||
public long getFrameLength() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
in.close();
|
||||
}
|
||||
|
||||
/** Throws an IOException, because the frame size is greater than 1. */
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
throw new IOException("cannot read a single byte if frame size > 1");
|
||||
}
|
||||
|
||||
/** Reads some number of bytes from the audio input stream and stores them
|
||||
* into the buffer array b. The number of bytes actually read is returned as
|
||||
* an integer. This method blocks until input data is available, the end of
|
||||
* the stream is detected, or an exception is thrown.
|
||||
* This method will always read an integral number of frames. If the length
|
||||
* of the array is not an integral number of frames, a maximum of
|
||||
* {@code b.length - (b.length % frameSize)} bytes will be read.
|
||||
*
|
||||
* @return Returns the total number of bytes read into the buffer, or -1 if there is
|
||||
* no more data because the end of the stream has been reached.
|
||||
*/
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
if (in.getFrame() == null && in.getNextFrame() == null) {
|
||||
return -1;
|
||||
}
|
||||
if (in.getStreamPosition() != in.getFrame().getFrameOffset()) {
|
||||
if (in.getNextFrame() == null) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int bytesRead = 0;
|
||||
int frameSize = in.getFrame().getFrameSize();
|
||||
while (len >= frameSize) {
|
||||
in.readFully(b, off, frameSize);
|
||||
len -= frameSize;
|
||||
bytesRead += frameSize;
|
||||
off += frameSize;
|
||||
if (in.getNextFrame() == null) {
|
||||
break;
|
||||
}
|
||||
frameSize = in.getFrame().getFrameSize();
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long n) throws IOException {
|
||||
return in.skip(n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mark(int readlimit) {
|
||||
// can't do anything
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() throws IOException {
|
||||
throw new IOException("mark/reset not supported");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,546 @@
|
||||
/*
|
||||
* @(#)MP3ElementaryInputStream.java 1.0 2011-01-03
|
||||
*
|
||||
* Copyright © 2010 Werner Randelshofer, Immensee, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package ch.randelshofer.media.mp3;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PushbackInputStream;
|
||||
import java.util.HashMap;
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
|
||||
/**
|
||||
* Facilitates reading of an MP3 elementary stream frame by frame.
|
||||
* <p>
|
||||
* An MP3 frame has a 32-bit header with the following contents in big endian
|
||||
* order:
|
||||
* <ul>
|
||||
* <li>bit 31-21, MP3 Sync Word, all bits must be set</li>
|
||||
* <li>bit 20-19, Version, 00=MPEG 2.5,01=reserved,10=MPEG 2, 11=MPEG 1</li>
|
||||
* <li>bit 18-17, Layer, 00=reserved, 01=layer 3, 10=layer 2, 11=layer 1</li>
|
||||
* <li>bit 16, Error protection, 0=16 bit CRC follows header, 1=Not protected</li>
|
||||
* <li>bit 15-12, Bit Rate in kbps, interpretation depends on version and layer</li>
|
||||
* <li>bit 11-10, Frequency, interpretation depends on version</li>
|
||||
* <li>bit 9, Pad Bit, 0=frame is not padded, 1=frame is padded to exactly fit the bit rate</li>
|
||||
* <li>bit 8, Private bit, only informative</li>
|
||||
* <li>bit 7-6, Channel Mode, 00=stereo, 01=joint stereo, 10=dual channel (2 mono channels), 11=single channel (mono)</li>
|
||||
* <li>bit 5-4, Mode Extension (only used with Joint Stereo), interpretation depends on version and layer</li>
|
||||
* <li>bit 3, Copyright, 0=not copyrighted, 1=copyrighted</li>
|
||||
* <li>bit 2, Original, 0=Copy of original media,1=original media</li>
|
||||
* <li>bit 1-0, Emphasis, 00=none,01=50/15ms,10=reserved,11=CCIT J.17</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Reference:<br>
|
||||
* <a href="http://en.wikipedia.org/wiki/MP3">http://en.wikipedia.org/wiki/MP3</a><br>
|
||||
* <a href="http://www.datavoyage.com/mpgscript/mpeghdr.htm">http://www.datavoyage.com/mpgscript/mpeghdr.htm</a><br>
|
||||
* <a href="http://www.mp3-tech.org/programmer/frame_header.html">http://www.mp3-tech.org/programmer/frame_header.html</a><br>
|
||||
* <a href="http://lame.sourceforge.net/tech-FAQ.txt">http://lame.sourceforge.net/tech-FAQ.txt</a><br>
|
||||
* <a href0"http://www.altera.com/literature/dc/1.4-2005_Taiwan_2nd_SouthernTaiwanU-web.pdf">http://www.altera.com/literature/dc/1.4-2005_Taiwan_2nd_SouthernTaiwanU-web.pdf</a><br>
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version 1.0 2011-01-03 Created.
|
||||
*/
|
||||
public class MP3ElementaryInputStream extends FilterInputStream {
|
||||
|
||||
/** Defines the "MP3" encoding. */
|
||||
public final static AudioFormat.Encoding MP3 = new AudioFormat.Encoding("MP3");
|
||||
private Frame frame;
|
||||
private long pos;
|
||||
private final static int[][] BIT_RATES = { // All values are in kbps
|
||||
// V1 - MPEG Version 1
|
||||
// V2 - MPEG Version 2 and Version 2.5
|
||||
// L1 - Layer I
|
||||
// L2 - Layer II
|
||||
// L3 - Layer III
|
||||
//
|
||||
// V1L1, V1L2, V1L3, V2L1, V2L2&L3
|
||||
{-1, -1, -1, -1, -1}, // free
|
||||
{32, 32, 32, 32, 8},
|
||||
{64, 48, 40, 48, 16},
|
||||
{96, 56, 48, 56, 24},
|
||||
{128, 64, 56, 64, 32},
|
||||
{160, 80, 64, 80, 40},
|
||||
{192, 96, 80, 96, 48},
|
||||
{224, 112, 96, 112, 56},
|
||||
{256, 128, 112, 128, 64},
|
||||
{288, 160, 128, 144, 80},
|
||||
{320, 192, 160, 160, 96},
|
||||
{352, 224, 192, 176, 112},
|
||||
{384, 256, 224, 192, 128},
|
||||
{416, 320, 256, 224, 144},
|
||||
{448, 384, 320, 256, 160},
|
||||
{-2, -2, -2, -2, -2}, // bad
|
||||
};
|
||||
private final static int[][] SAMPLE_RATES = { // All values are in Hz
|
||||
// V1 - MPEG Version 1
|
||||
// V2 - MPEG Version 2
|
||||
// V25 - MPEG Version 2.5
|
||||
//
|
||||
// V1, V2, V25
|
||||
{44100, 22050, 11025},
|
||||
{48000, 24000, 12000},
|
||||
{32000, 16000, 8000},
|
||||
{-1, -1, -1}, // reserved
|
||||
};
|
||||
|
||||
/** An elementary frame. */
|
||||
public static class Frame {
|
||||
|
||||
/** 32-bit header. */
|
||||
private int header;
|
||||
/** 16-bit CRC. */
|
||||
private int crc;
|
||||
/** The size of the data in the frame. */
|
||||
private int bodySize;
|
||||
/** The offset of the data in the frame. */
|
||||
private long bodyOffset;
|
||||
|
||||
/** Creates a new frame.
|
||||
*
|
||||
* @param header The 32-bit Frame header
|
||||
*/
|
||||
public Frame(int header) {
|
||||
this.header = header;
|
||||
}
|
||||
|
||||
public int getHeader() {
|
||||
return header;
|
||||
|
||||
}
|
||||
|
||||
/** Returns the version number: 1=MPEG 1, 2=MPEG 2, 25=MPEG 2.5; -1=unknown. */
|
||||
public int getVersion() {
|
||||
switch (getVersionCode()) {
|
||||
case 0:
|
||||
return 25;
|
||||
case 2:
|
||||
return 2;
|
||||
case 3:
|
||||
return 1;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the raw version code as it is stored in the
|
||||
* header. 3=MPEG 1, 2=MPEG 2, 1=reserved, 0=MPEG 2.5. */
|
||||
public int getVersionCode() {
|
||||
return (header >>> 19) & 3;
|
||||
}
|
||||
|
||||
/** Returns the layer number.
|
||||
* 1=Layer I, 2=Layer II, 3=Layer III, -1=unknown. */
|
||||
public int getLayer() {
|
||||
switch (getLayerCode()) {
|
||||
case 1:
|
||||
return 3;
|
||||
case 2:
|
||||
return 2;
|
||||
case 3:
|
||||
return 1;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the raw layer code as it is stored in the header.
|
||||
* 3=Layer I, 2=Layer II, 1=Layer III, 0=reserved. */
|
||||
public int getLayerCode() {
|
||||
return (header >>> 17) & 3;
|
||||
}
|
||||
|
||||
/** Returns the bitrate of the frame. Returns -1 if unknown.
|
||||
*/
|
||||
public int getBitRate() {
|
||||
if (getVersion() < 0 || getLayer() < 0) {
|
||||
return -1;
|
||||
}
|
||||
int v = getVersion() == 1 ? 0 : 3;
|
||||
int l = getVersion() == 1 ? getLayer() - 1 : (getLayer() == 1 ? 0 : 1);
|
||||
return BIT_RATES[getBitRateCode()][v + l];
|
||||
}
|
||||
|
||||
/** Returns the raw bitrate code as it is stored in the header.
|
||||
*/
|
||||
public int getBitRateCode() {
|
||||
return (header >>> 12) & 15;
|
||||
}
|
||||
|
||||
/** Returns true if this frame has a CRC. */
|
||||
public boolean hasCRC() {
|
||||
return ((header >>> 16) & 1) == 0;
|
||||
}
|
||||
|
||||
/** Returns the CRC of this frame. The value is only valid if hasCRC() returns true. */
|
||||
public int getCRC() {
|
||||
return crc;
|
||||
}
|
||||
|
||||
public boolean hasPadding() {
|
||||
return ((header >>> 9) & 1) == 1;
|
||||
}
|
||||
|
||||
/** Returns the sample rate in Hz.
|
||||
* Returns -1 if unknown.
|
||||
*/
|
||||
public int getSampleRate() {
|
||||
if (getVersion() < 0 || getLayer() < 0) {
|
||||
return -1;
|
||||
}
|
||||
int v = getVersion() == 25 ? 2 : getVersion() - 1;
|
||||
return SAMPLE_RATES[getSampleRateCode()][v];
|
||||
}
|
||||
|
||||
/** Returns the raw sample rate code as it is stored in the header.
|
||||
*/
|
||||
public int getSampleRateCode() {
|
||||
return (header >>> 10) & 3;
|
||||
}
|
||||
|
||||
/** Returns the number of samples in the frame.
|
||||
* It is constant and always 384 samples for Layer I and 1152 samples
|
||||
* for Layer II and Layer III.
|
||||
* Returns -1 if unknown.
|
||||
*/
|
||||
public int getSampleCount() {
|
||||
if (getLayer() < 0) {
|
||||
return -1;
|
||||
}
|
||||
return (getLayer() == 1 ? 192 : 576) * getChannelCount();
|
||||
}
|
||||
|
||||
/** Returns the number of channels.
|
||||
* @return 1=mono, 2=stereo, joint stereo or dual channel.
|
||||
*/
|
||||
public int getChannelCount() {
|
||||
return getChannelModeCode() == 3 ? 1 : 2;
|
||||
}
|
||||
|
||||
/** Returns the sample size in bits. Always 16 bit per sample. */
|
||||
public int getSampleSize() {
|
||||
return 16;
|
||||
}
|
||||
|
||||
/** Returns the raw channel mode as stored in the header.
|
||||
*
|
||||
* @return 0=stereo, 1=joint stereo, 2=dual channel, 3=single channel (mono).
|
||||
*/
|
||||
public int getChannelModeCode() {
|
||||
return (header >>> 6) & 3;
|
||||
}
|
||||
|
||||
/** Returns the frame header as a byte array. */
|
||||
public byte[] headerToByteArray() {
|
||||
byte[] data = new byte[hasCRC() ? 6 : 4];
|
||||
headerToByteArray(data, 0);
|
||||
return data;
|
||||
}
|
||||
|
||||
/** Writes the frame header into the specified byte array.
|
||||
* Returns the number of bytes written.
|
||||
*/
|
||||
public int headerToByteArray(byte[] data, int offset) {
|
||||
if (data.length - offset < getHeaderSize()) {
|
||||
throw new IllegalArgumentException("data array is too small");
|
||||
}
|
||||
data[offset + 0] = (byte) (header >>> 24);
|
||||
data[offset + 1] = (byte) (header >>> 16);
|
||||
data[offset + 2] = (byte) (header >>> 8);
|
||||
data[offset + 3] = (byte) (header >>> 0);
|
||||
if (hasCRC()) {
|
||||
data[offset + 4] = (byte) (crc >>> 8);
|
||||
data[offset + 5] = (byte) (crc >>> 0);
|
||||
}
|
||||
return getHeaderSize();
|
||||
}
|
||||
|
||||
/** Writes the frame header into the specified output stream. */
|
||||
public void writeHeader(OutputStream out) throws IOException {
|
||||
out.write((header >>> 24));
|
||||
out.write((header >>> 16));
|
||||
out.write((header >>> 8));
|
||||
out.write((header >>> 0));
|
||||
if (hasCRC()) {
|
||||
out.write((crc >>> 8));
|
||||
out.write((crc >>> 0));
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the offset of the frame in the input stream. */
|
||||
public long getFrameOffset() {
|
||||
return getBodyOffset() - getHeaderSize();
|
||||
}
|
||||
|
||||
/** Returns the size of the frame in bytes.
|
||||
* This size includes the header, the data and the padding.
|
||||
*/
|
||||
public int getFrameSize() {
|
||||
return getHeaderSize() + getBodySize();
|
||||
}
|
||||
|
||||
/** Returns the offset of the header in the input stream. */
|
||||
public long getHeaderOffset() {
|
||||
return getFrameOffset();
|
||||
}
|
||||
|
||||
/** Returns the size of the header in bytes. */
|
||||
public int getHeaderSize() {
|
||||
return hasCRC() ? 6 : 4;
|
||||
}
|
||||
|
||||
/** Returns the offset of the side info in the input stream. */
|
||||
public long getSideInfoOffset() {
|
||||
return bodyOffset;
|
||||
}
|
||||
|
||||
/** Returns the size of the side info in bytes.
|
||||
* It is 17 bytes long in a single channel frame and 32 bytes in dual
|
||||
* channel or stereo channel.
|
||||
*/
|
||||
public int getSideInfoSize() {
|
||||
return getChannelCount() == 1 ? 17 : 32;
|
||||
}
|
||||
|
||||
/** Returns the offset of the frame body in the input stream. */
|
||||
public long getBodyOffset() {
|
||||
return bodyOffset;
|
||||
}
|
||||
|
||||
/** Returns the size of the frame body in bytes.
|
||||
* The body includes the side info, the audio data, and the padding.
|
||||
*/
|
||||
public int getBodySize() {
|
||||
return bodySize;
|
||||
}
|
||||
|
||||
/** Padding is used to fit the bit rates exactly.
|
||||
* For an example: 128k 44.1kHz layer II uses a lot of 418 bytes and
|
||||
* some of 417 bytes long frames to get the exact 128k bitrate. For
|
||||
* Layer I slot is 32 bits long, for Layer II and Layer III slot is 8
|
||||
* bits long. */
|
||||
public int getPaddingSize() {
|
||||
if (hasPadding()) {
|
||||
return getLayer() == 1 ? 4 : 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private float getFrameRate() {
|
||||
return (float) getSampleRate() / getSampleCount();
|
||||
}
|
||||
}
|
||||
|
||||
public MP3ElementaryInputStream(File file) throws IOException {
|
||||
super(new PushbackInputStream(new BufferedInputStream(new FileInputStream(file)), 6));
|
||||
}
|
||||
|
||||
public MP3ElementaryInputStream(InputStream in) {
|
||||
super(new PushbackInputStream(in, 6));
|
||||
}
|
||||
|
||||
/** Gets the next frame from the input stream.
|
||||
* Positions the stream in front of the frame header.
|
||||
*/
|
||||
public Frame getNextFrame() throws IOException {
|
||||
while (frame != null && pos < frame.getBodyOffset() + frame.getBodySize()) {
|
||||
long skipped = skip(frame.getBodyOffset() + frame.getBodySize() - pos);
|
||||
if (skipped < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
int b = read0();
|
||||
if (b == -1) {
|
||||
frame = null;
|
||||
break;
|
||||
} else if (b == 255) {
|
||||
int h0 = b;
|
||||
int h1 = read0();
|
||||
if (h1 != -1 && (h1 & 0xe0) == 0xe0) {
|
||||
int h2 = read0();
|
||||
int h3 = read0();
|
||||
if (h3 != -1) {
|
||||
frame = new Frame((h0 << 24) | (h1 << 16) | (h2 << 8) | h3);
|
||||
if (frame.getBitRate() == -1 || frame.getLayer() == -1 || frame.getSampleRate() == -1) {
|
||||
// => the header is corrupt: push back 3 bytes
|
||||
PushbackInputStream pin = (PushbackInputStream) in;
|
||||
pin.unread(h3);
|
||||
pin.unread(h2);
|
||||
pin.unread(h1);
|
||||
pos -= 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
int crc0 = -1, crc1 = -1;
|
||||
if (frame.hasCRC()) {
|
||||
crc0 = read0();
|
||||
crc1 = read0();
|
||||
if (crc1 == -1) {
|
||||
throw new EOFException();
|
||||
}
|
||||
frame.crc = (crc0 << 8) | crc1;
|
||||
}
|
||||
frame.bodyOffset = pos;
|
||||
if (frame.getBitRate() <= 0 || frame.getSampleRate() <= 0) {
|
||||
frame.bodySize = 0;
|
||||
} else if (frame.getLayer() == 1) {
|
||||
frame.bodySize = (int) ((12000L * frame.getBitRate() / frame.getSampleRate()) * 4) - frame.getHeaderSize() + frame.getPaddingSize();
|
||||
} else if (frame.getLayer() == 2 || frame.getLayer() == 3) {
|
||||
if (frame.getChannelCount() == 1) {
|
||||
frame.bodySize = (int) (72000L * frame.getBitRate() / (frame.getSampleRate() + frame.getPaddingSize())) - frame.getHeaderSize() + frame.getPaddingSize();
|
||||
} else {
|
||||
frame.bodySize = (int) (144000L * frame.getBitRate() / (frame.getSampleRate() + frame.getPaddingSize())) - frame.getHeaderSize() + frame.getPaddingSize();
|
||||
}
|
||||
}
|
||||
PushbackInputStream pin = (PushbackInputStream) in;
|
||||
if (frame.hasCRC()) {
|
||||
pin.unread(crc1);
|
||||
pin.unread(crc0);
|
||||
pos -= 2;
|
||||
}
|
||||
pin.unread(h3);
|
||||
pin.unread(h2);
|
||||
pin.unread(h1);
|
||||
pin.unread(h0);
|
||||
pos -= 4;
|
||||
assert pos == frame.getFrameOffset() : pos + "!=" + frame.getFrameOffset();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
/** Returns the current frame. */
|
||||
public Frame getFrame() {
|
||||
return frame;
|
||||
}
|
||||
|
||||
/** Gets the format of the current frame.
|
||||
* Returns null if the input stream is not positioned on a frame, or the frame
|
||||
* is not valid.
|
||||
* @return AudioFormat of current frame or null.
|
||||
*/
|
||||
public AudioFormat getFormat() {
|
||||
if (frame == null) {
|
||||
return null;
|
||||
} else {
|
||||
HashMap<String, Object> properties = new HashMap<String, Object>();
|
||||
properties.put("vbr", true);
|
||||
return new AudioFormat(MP3, //
|
||||
frame.getSampleRate(), frame.getSampleSize(), frame.getChannelCount(),//
|
||||
frame.getFrameSize(), frame.getFrameRate(), true, properties);
|
||||
}
|
||||
}
|
||||
|
||||
private int read0() throws IOException {
|
||||
int b = super.read();
|
||||
if (b != -1) {
|
||||
pos++;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
/** Reads a byte from the current frame (its header and its data).
|
||||
* Returns -1 on an attempt to read past the end of the frame.
|
||||
*/
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (frame == null || pos >= frame.getBodyOffset() + frame.getBodySize()) {
|
||||
return -1;
|
||||
}
|
||||
return read0();
|
||||
}
|
||||
|
||||
/** Reads up to {@code len} bytes from the current frame (its header and its data).
|
||||
* May read less then {@code len} bytes. Returns the actual number of bytes read.
|
||||
* Returns -1 on an attempt to read past the end of the frame.
|
||||
*/
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
if (frame == null) {
|
||||
return -1;
|
||||
}
|
||||
int maxlen = (int) (frame.getBodyOffset() + frame.getBodySize() - pos);
|
||||
if (maxlen < 1) {
|
||||
return -1;
|
||||
}
|
||||
len = Math.min(maxlen, len);
|
||||
int count = super.read(b, off, len);
|
||||
if (count != -1) {
|
||||
pos += count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads {@code b.length} bytes from the current frame (its header and its data).
|
||||
* @throws {@code IOException} on an attempt to read past the end of the frame.
|
||||
*/
|
||||
public final void readFully(byte b[]) throws IOException {
|
||||
readFully(b, 0, b.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads {@code len} bytes from the current frame (its header and its data).
|
||||
* @throws {@code IOException} on an attempt to read past the end of the frame.
|
||||
*/
|
||||
public final void readFully(byte b[], int off, int len) throws IOException {
|
||||
if (len < 0) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
int n = 0;
|
||||
while (n < len) {
|
||||
int count = in.read(b, off + n, len - n);
|
||||
if (count < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
n += count;
|
||||
pos += count;
|
||||
}
|
||||
}
|
||||
|
||||
/** Skips up to {@code n} bytes from the current frame (its header and its data).
|
||||
* Returns the actual number of bytes that have been skipped.
|
||||
* Returns -1 on an attempt to skip past the end of the frame.
|
||||
*/
|
||||
@Override
|
||||
public long skip(long n) throws IOException {
|
||||
if (frame == null) {
|
||||
return -1;
|
||||
}
|
||||
int maxlen = (int) (frame.getBodyOffset() + frame.getBodySize() - pos);
|
||||
if (maxlen < 1) {
|
||||
return -1;
|
||||
}
|
||||
n = Math.min(maxlen, n);
|
||||
long skipped = in.skip(n);
|
||||
if (skipped > 0) {
|
||||
pos += skipped;
|
||||
}
|
||||
return skipped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current position in the stream.
|
||||
* @return The stream position.
|
||||
*/
|
||||
public long getStreamPosition() {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,997 @@
|
||||
/*
|
||||
* @(#)AppleRLEEncoder.java 1.3 2011-01-16
|
||||
*
|
||||
* Copyright © 2011 Werner Randelshofer, Immensee, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package ch.randelshofer.media.quicktime;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import ch.randelshofer.io.SeekableByteArrayOutputStream;
|
||||
|
||||
/**
|
||||
* Implements the run length encoding of the Apple QuickTime Animation (RLE)
|
||||
* format.
|
||||
* <p>
|
||||
* An RLE-encoded frame has the following format:
|
||||
* <p>
|
||||
* <pre>
|
||||
* Header:
|
||||
* uint32 chunkSize
|
||||
*
|
||||
* uint16 header 0x0000 => decode entire image
|
||||
* 0x0008 => starting line and number of lines follows
|
||||
* if header==0x0008 {
|
||||
* uint16 startingLine at which to begin updating frame
|
||||
* uint16 reserved 0x0000
|
||||
* uint16 numberOfLines to update
|
||||
* uint16 reserved 0x0000
|
||||
* }
|
||||
* n-bytes compressed lines
|
||||
* </pre>
|
||||
*
|
||||
* The first 4 bytes defines the chunk length. This field also carries some
|
||||
* other unknown flags, since at least one of the high bits is sometimes set.<br>
|
||||
*
|
||||
* If the overall length of the chunk is less than 8, treat the frame as a
|
||||
* NOP, which means that the frame is the same as the one before it.<br>
|
||||
*
|
||||
* Next, there is a header of either 0x0000 or 0x0008. A header value with
|
||||
* bit 3 set (header & 0x0008) indicates that information follows revealing
|
||||
* at which line the decode process is to begin:<br>
|
||||
*
|
||||
* <pre>
|
||||
* 2 bytes starting line at which to begin updating frame
|
||||
* 2 bytes unknown
|
||||
* 2 bytes the number of lines to update
|
||||
* 2 bytes unknown
|
||||
* </pre>
|
||||
*
|
||||
* If the header is 0x0000, then the decode begins from the first line and
|
||||
* continues through the entire height of the image.<br>
|
||||
*
|
||||
* After the header comes the individual RLE-compressed lines. An individual
|
||||
* compressed line is comprised of a skip code, followed by a series of RLE
|
||||
* codes and pixel data:<br>
|
||||
* <pre>
|
||||
* 1 byte skip code
|
||||
* 1 byte RLE code
|
||||
* n bytes pixel data
|
||||
* 1 byte RLE code
|
||||
* n bytes pixel data
|
||||
* </pre>
|
||||
* Each line begins with a byte that defines the number of pixels to skip in
|
||||
* a particular line in the output line before outputting new pixel
|
||||
* data. Actually, the skip count is set to one more than the number of
|
||||
* pixels to skip. For example, a skip byte of 15 means "skip 14 pixels",
|
||||
* while a skip byte of 1 means "don't skip any pixels". If the skip byte is
|
||||
* 0, then the frame decode is finished. Therefore, the maximum skip byte
|
||||
* value of 255 allows for a maximum of 254 pixels to be skipped.
|
||||
* <p>
|
||||
* After the skip byte is the first RLE code, which is a single signed
|
||||
* byte. The RLE code can have the following meanings:<br>
|
||||
* <ul>
|
||||
* <li>equal to 0: There is another single-byte skip code in the stream.
|
||||
* Again, the actual number of pixels to skip is 1 less
|
||||
* than the skip code. Therefore, the maximum skip byte
|
||||
* value of 255 allows for a maximum of 254 pixels to be
|
||||
* skipped.</li>
|
||||
*
|
||||
* <li>equal to -1: End of the RLE-compressed line</li>
|
||||
*
|
||||
* <li>greater than 0: Run of pixel data is copied directly from the
|
||||
* encoded stream to the output frame.</li>
|
||||
*
|
||||
* <li>less than -1: Repeat pixel data -(RLE code) times.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* The pixel data has the following format:
|
||||
* <ul>
|
||||
* <li>8-bit data: Pixels are handled in groups of four. Each pixel is a palette
|
||||
* index (the palette is determined by the Quicktime file transporting the
|
||||
* data).<br>
|
||||
* If (code > 0), copy (4 * code) pixels from the encoded stream to the
|
||||
* output.<br>
|
||||
* If (code < -1), extract the next 4 pixels from the encoded stream
|
||||
* and render the entire group -(code) times to the output frame. </li>
|
||||
*
|
||||
* <li>16-bit data: Each pixel is represented by a 16-bit RGB value with 5 bits
|
||||
* used for each of the red, green, and blue color components and 1 unused bit
|
||||
* to round the value tmp to 16 bits: {@code xrrrrrgg gggbbbbb}. Pixel data is
|
||||
* rendered to the output frame one pixel at a time.<br>
|
||||
* If (code > 0), copy the run of (code) pixels from the encoded stream to
|
||||
* the output.<br>
|
||||
* If (code < -1), unpack the next 16-bit RGB value from the encoded stream
|
||||
* and render it to the output frame -(code) times.</li>
|
||||
*
|
||||
* <li>24-bit data: Each pixel is represented by a 24-bit RGB value with 8 bits
|
||||
* (1 byte) used for each of the red, green, and blue color components:
|
||||
* {@code rrrrrrrr gggggggg bbbbbbbb}. Pixel data is rendered to the output
|
||||
* frame one pixel at a time.<br>
|
||||
* If (code > 0), copy the run of (code) pixels from the encoded stream to
|
||||
* the output.<br>
|
||||
* If (code < -1), unpack the next 24-bit RGB value from the encoded stream
|
||||
* and render it to the output frame -(code) times.</li>
|
||||
*
|
||||
* <li>32-bit data: Each pixel is represented by a 32-bit ARGB value with 8 bits
|
||||
* (1 byte) used for each of the alpha, red, green, and blue color components:
|
||||
* {@code aaaaaaaa rrrrrrrr gggggggg bbbbbbbb}. Pixel data is rendered to the
|
||||
* output frame one pixel at a time.<br>
|
||||
* If (code > 0), copy the run of (code) pixels from the encoded stream to
|
||||
* the output.<br>
|
||||
* If (code < -1), unpack the next 32-bit ARGB value from the encoded stream
|
||||
* and render it to the output frame -(code) times.</li>
|
||||
* </ul>
|
||||
*
|
||||
* References:<br/>
|
||||
* <a href="http://multimedia.cx/qtrle.txt">http://multimedia.cx/qtrle.txt</a><br>
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version 1.3 2011-01-17 Fixes an index out of bounds exception when a
|
||||
* sub-image is compressed.
|
||||
* <br>1.2 2011-01-07 Improves compression rate.
|
||||
* <br>1.1 2011-01-07 Reduces seeking operations on output stream by using
|
||||
* a seekable output stream internally.
|
||||
* <br>1.0 2011-01-05 Created.
|
||||
*/
|
||||
public class AppleRLEEncoder {
|
||||
|
||||
private SeekableByteArrayOutputStream tmpSeek = new SeekableByteArrayOutputStream();
|
||||
private DataAtomOutputStream tmp = new DataAtomOutputStream(tmpSeek);
|
||||
|
||||
/** Encodes a 16-bit key frame.
|
||||
*
|
||||
* @param tmp The output stream. Must be set to Big-Endian.
|
||||
* @param data The image data.
|
||||
* @param offset The offset to the first pixel in the data array.
|
||||
* @param width The width of the image in data elements.
|
||||
* @param scanlineStride The number to add to offset to get to the next scanline.
|
||||
*/
|
||||
public void writeKey16(OutputStream out, short[] data, int width, int height, int offset, int scanlineStride)
|
||||
throws IOException {
|
||||
tmpSeek.reset();
|
||||
long headerPos = tmpSeek.getStreamPosition();
|
||||
|
||||
// Reserve space for the header:
|
||||
tmp.writeInt(0);
|
||||
tmp.writeShort(0x0000);
|
||||
|
||||
// Encode each scanline
|
||||
int ymax = offset + height * scanlineStride;
|
||||
for (int y = offset; y < ymax; y += scanlineStride) {
|
||||
int xy = y;
|
||||
int xymax = y + width;
|
||||
|
||||
tmp.write(1); // this is a key-frame, there is nothing to skip at the start of line
|
||||
|
||||
int literalCount = 0;
|
||||
int repeatCount = 0;
|
||||
for (; xy < xymax; ++xy) {
|
||||
// determine repeat count
|
||||
short v = data[xy];
|
||||
for (repeatCount = 0; xy < xymax && repeatCount < 127; ++xy, ++repeatCount) {
|
||||
if (data[xy] != v) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
xy -= repeatCount;
|
||||
|
||||
if (repeatCount < 2) {
|
||||
literalCount++;
|
||||
if (literalCount == 127) {
|
||||
tmp.write(literalCount); // Literal OP-code
|
||||
tmp.writeShorts(data, xy - literalCount + 1, literalCount);
|
||||
literalCount = 0;
|
||||
}
|
||||
} else {
|
||||
if (literalCount > 0) {
|
||||
tmp.write(literalCount); // Literal OP-code
|
||||
tmp.writeShorts(data, xy - literalCount, literalCount);
|
||||
literalCount = 0;
|
||||
}
|
||||
tmp.write(-repeatCount); // Repeat OP-code
|
||||
tmp.writeShort(v);
|
||||
xy += repeatCount - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// flush literal run
|
||||
if (literalCount > 0) {
|
||||
tmp.write(literalCount);
|
||||
tmp.writeShorts(data, xy - literalCount, literalCount);
|
||||
literalCount = 0;
|
||||
}
|
||||
|
||||
tmp.write(-1);// End of line OP-code
|
||||
}
|
||||
|
||||
|
||||
// Complete the header
|
||||
long pos = tmpSeek.getStreamPosition();
|
||||
tmpSeek.seek(headerPos);
|
||||
tmp.writeInt((int) (pos - headerPos));
|
||||
tmpSeek.seek(pos);
|
||||
tmpSeek.toOutputStream(out);
|
||||
}
|
||||
|
||||
/** Encodes a 16-bit delta frame.
|
||||
*
|
||||
* @param tmp The output stream. Must be set to Big-Endian.
|
||||
* @param data The image data.
|
||||
* @param prev The image data of the previous frame.
|
||||
* @param offset The offset to the first pixel in the data array.
|
||||
* @param width The width of the image in data elements.
|
||||
* @param scanlineStride The number to add to offset to get to the next scanline.
|
||||
*/
|
||||
public void writeDelta16(OutputStream out, short[] data, short[] prev, int width, int height, int offset, int scanlineStride)
|
||||
throws IOException {
|
||||
tmpSeek.reset();
|
||||
|
||||
// Determine whether we can skip lines at the beginning
|
||||
int ymin;
|
||||
int ymax = offset + height * scanlineStride;
|
||||
scanline:
|
||||
for (ymin = offset; ymin < ymax; ymin += scanlineStride) {
|
||||
int xy = ymin;
|
||||
int xymax = ymin + width;
|
||||
for (; xy < xymax; ++xy) {
|
||||
if (data[xy] != prev[xy]) {
|
||||
break scanline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (ymin == ymax) {
|
||||
// => Frame is identical to previous one
|
||||
tmp.writeInt(4);
|
||||
tmpSeek.toOutputStream(out);
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine whether we can skip lines at the end
|
||||
scanline:
|
||||
for (; ymax > ymin; ymax -= scanlineStride) {
|
||||
int xy = ymax - scanlineStride;
|
||||
int xymax = ymax - scanlineStride + width;
|
||||
for (; xy < xymax; ++xy) {
|
||||
if (data[xy] != prev[xy]) {
|
||||
break scanline;
|
||||
}
|
||||
}
|
||||
}
|
||||
//System.out.println("AppleRLEEncoder ymin:" + ymin / step + " ymax" + ymax / step);
|
||||
|
||||
// Reserve space for the header
|
||||
long headerPos = tmpSeek.getStreamPosition();
|
||||
tmp.writeInt(0);
|
||||
|
||||
if (ymin == offset && ymax == offset + height * scanlineStride) {
|
||||
// => we can't skip any lines:
|
||||
tmp.writeShort(0x0000);
|
||||
} else {
|
||||
// => we can skip lines:
|
||||
tmp.writeShort(0x0008);
|
||||
tmp.writeShort(ymin / scanlineStride);
|
||||
tmp.writeShort(0);
|
||||
tmp.writeShort((ymax - ymin + 1) / scanlineStride);
|
||||
tmp.writeShort(0);
|
||||
}
|
||||
|
||||
|
||||
// Encode each scanline
|
||||
for (int y = ymin; y < ymax; y += scanlineStride) {
|
||||
int xy = y;
|
||||
int xymax = y + width;
|
||||
|
||||
// determine skip count
|
||||
int skipCount = 0;
|
||||
for (; xy < xymax; ++xy, ++skipCount) {
|
||||
if (data[xy] != prev[xy]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (skipCount == width) {
|
||||
// => the entire line can be skipped
|
||||
tmp.write(1); // don't skip any pixels
|
||||
tmp.write(-1); // end of line
|
||||
continue;
|
||||
}
|
||||
tmp.write(Math.min(255, skipCount + 1));
|
||||
if (skipCount > 254) {
|
||||
skipCount -= 254;
|
||||
while (skipCount > 254) {
|
||||
tmp.write(0); // Skip OP-code
|
||||
tmp.write(255);
|
||||
skipCount -= 254;
|
||||
}
|
||||
tmp.write(0); // Skip OP-code
|
||||
tmp.write(skipCount + 1);
|
||||
}
|
||||
|
||||
int literalCount = 0;
|
||||
int repeatCount = 0;
|
||||
for (; xy < xymax; ++xy) {
|
||||
// determine skip count
|
||||
for (skipCount = 0; xy < xymax; ++xy, ++skipCount) {
|
||||
if (data[xy] != prev[xy]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
xy -= skipCount;
|
||||
|
||||
// determine repeat count
|
||||
short v = data[xy];
|
||||
for (repeatCount = 0; xy < xymax && repeatCount < 127; ++xy, ++repeatCount) {
|
||||
if (data[xy] != v) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
xy -= repeatCount;
|
||||
|
||||
if (skipCount < 2 && xy + skipCount < xymax && repeatCount < 2) {
|
||||
literalCount++;
|
||||
if (literalCount == 127) {
|
||||
tmp.write(literalCount); // Literal OP-code
|
||||
tmp.writeShorts(data, xy - literalCount + 1, literalCount);
|
||||
literalCount = 0;
|
||||
}
|
||||
} else {
|
||||
if (literalCount > 0) {
|
||||
tmp.write(literalCount); // Literal OP-code
|
||||
tmp.writeShorts(data, xy - literalCount, literalCount);
|
||||
literalCount = 0;
|
||||
}
|
||||
if (xy + skipCount == xymax) {
|
||||
// => we can skip until the end of the line without
|
||||
// having to write an op-code
|
||||
xy += skipCount - 1;
|
||||
} else if (skipCount >= repeatCount) {
|
||||
while (skipCount > 254) {
|
||||
tmp.write(0); // Skip OP-code
|
||||
tmp.write(255);
|
||||
xy += 254;
|
||||
skipCount -= 254;
|
||||
}
|
||||
tmp.write(0); // Skip OP-code
|
||||
tmp.write(skipCount + 1);
|
||||
xy += skipCount - 1;
|
||||
} else {
|
||||
tmp.write(-repeatCount); // Repeat OP-code
|
||||
tmp.writeShort(v);
|
||||
xy += repeatCount - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// flush literal run
|
||||
if (literalCount > 0) {
|
||||
tmp.write(literalCount);
|
||||
tmp.writeShorts(data, xy - literalCount, literalCount);
|
||||
literalCount = 0;
|
||||
}
|
||||
|
||||
tmp.write(-1);// End of line OP-code
|
||||
}
|
||||
|
||||
|
||||
// Complete the header
|
||||
long pos = tmpSeek.getStreamPosition();
|
||||
tmpSeek.seek(headerPos);
|
||||
tmp.writeInt((int) (pos - headerPos));
|
||||
tmpSeek.seek(pos);
|
||||
tmpSeek.toOutputStream(out);
|
||||
}
|
||||
|
||||
/** Encodes a 24-bit key frame.
|
||||
*
|
||||
* @param tmp The output stream. Must be set to Big-Endian.
|
||||
* @param data The image data.
|
||||
* @param offset The offset to the first pixel in the data array.
|
||||
* @param width The width of the image in data elements.
|
||||
* @param scanlineStride The number to add to offset to get to the next scanline.
|
||||
*/
|
||||
public void writeKey24(OutputStream out, int[] data, int width, int height, int offset, int scanlineStride)
|
||||
throws IOException {
|
||||
tmpSeek.reset();
|
||||
long headerPos = tmpSeek.getStreamPosition();
|
||||
|
||||
// Reserve space for the header:
|
||||
tmp.writeInt(0);
|
||||
tmp.writeShort(0x0000);
|
||||
|
||||
// Encode each scanline
|
||||
int ymax = offset + height * scanlineStride;
|
||||
for (int y = offset; y < ymax; y += scanlineStride) {
|
||||
int xy = y;
|
||||
int xymax = y + width;
|
||||
|
||||
tmp.write(1); // this is a key-frame, there is nothing to skip at the start of line
|
||||
|
||||
int literalCount = 0;
|
||||
int repeatCount = 0;
|
||||
for (; xy < xymax; ++xy) {
|
||||
// determine repeat count
|
||||
int v = data[xy];
|
||||
for (repeatCount = 0; xy < xymax && repeatCount < 127; ++xy, ++repeatCount) {
|
||||
if (data[xy] != v) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
xy -= repeatCount;
|
||||
|
||||
if (repeatCount < 2) {
|
||||
literalCount++;
|
||||
if (literalCount > 126) {
|
||||
tmp.write(literalCount); // Literal OP-code
|
||||
tmp.writeInts24(data, xy - literalCount + 1, literalCount);
|
||||
literalCount = 0;
|
||||
}
|
||||
} else {
|
||||
if (literalCount > 0) {
|
||||
tmp.write(literalCount); // Literal OP-code
|
||||
tmp.writeInts24(data, xy - literalCount, literalCount);
|
||||
literalCount = 0;
|
||||
}
|
||||
tmp.write(-repeatCount); // Repeat OP-code
|
||||
tmp.writeInt24(v);
|
||||
xy += repeatCount - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// flush literal run
|
||||
if (literalCount > 0) {
|
||||
tmp.write(literalCount);
|
||||
tmp.writeInts24(data, xy - literalCount, literalCount);
|
||||
literalCount = 0;
|
||||
}
|
||||
|
||||
tmp.write(-1);// End of line OP-code
|
||||
}
|
||||
|
||||
|
||||
// Complete the header
|
||||
long pos = tmpSeek.getStreamPosition();
|
||||
tmpSeek.seek(headerPos);
|
||||
tmp.writeInt((int) (pos - headerPos));
|
||||
tmpSeek.seek(pos);
|
||||
tmpSeek.toOutputStream(out);
|
||||
}
|
||||
|
||||
/** Encodes a 24-bit delta frame.
|
||||
*
|
||||
* @param tmp The output stream. Must be set to Big-Endian.
|
||||
* @param data The image data.
|
||||
* @param prev The image data of the previous frame.
|
||||
* @param offset The offset to the first pixel in the data array.
|
||||
* @param width The width of the image in data elements.
|
||||
* @param scanlineStride The number to add to offset to get to the next scanline.
|
||||
*/
|
||||
public void writeDelta24(OutputStream out, int[] data, int[] prev, int width, int height, int offset, int scanlineStride)
|
||||
throws IOException {
|
||||
tmpSeek.reset();
|
||||
|
||||
// Determine whether we can skip lines at the beginning
|
||||
int ymin;
|
||||
int ymax = offset + height * scanlineStride;
|
||||
scanline:
|
||||
for (ymin = offset; ymin < ymax; ymin += scanlineStride) {
|
||||
int xy = ymin;
|
||||
int xymax = ymin + width;
|
||||
for (; xy < xymax; ++xy) {
|
||||
if (data[xy] != prev[xy]) {
|
||||
break scanline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (ymin == ymax) {
|
||||
// => Frame is identical to previous one
|
||||
tmp.writeInt(4);
|
||||
tmpSeek.toOutputStream(out);
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine whether we can skip lines at the end
|
||||
scanline:
|
||||
for (; ymax > ymin; ymax -= scanlineStride) {
|
||||
int xy = ymax - scanlineStride;
|
||||
int xymax = ymax - scanlineStride + width;
|
||||
for (; xy < xymax; ++xy) {
|
||||
if (data[xy] != prev[xy]) {
|
||||
break scanline;
|
||||
}
|
||||
}
|
||||
}
|
||||
//System.out.println("AppleRLEEncoder ymin:" + ymin / step + " ymax" + ymax / step);
|
||||
|
||||
// Reserve space for the header
|
||||
long headerPos = tmpSeek.getStreamPosition();
|
||||
tmp.writeInt(0);
|
||||
|
||||
if (ymin == offset && ymax == offset + height * scanlineStride) {
|
||||
// => we can't skip any lines:
|
||||
tmp.writeShort(0x0000);
|
||||
} else {
|
||||
// => we can skip lines:
|
||||
tmp.writeShort(0x0008);
|
||||
tmp.writeShort(ymin / scanlineStride);
|
||||
tmp.writeShort(0);
|
||||
tmp.writeShort((ymax - ymin + 1) / scanlineStride);
|
||||
tmp.writeShort(0);
|
||||
}
|
||||
|
||||
|
||||
// Encode each scanline
|
||||
for (int y = ymin; y < ymax; y += scanlineStride) {
|
||||
int xy = y;
|
||||
int xymax = y + width;
|
||||
|
||||
// determine skip count
|
||||
int skipCount = 0;
|
||||
for (; xy < xymax; ++xy, ++skipCount) {
|
||||
if (data[xy] != prev[xy]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (skipCount == width) {
|
||||
// => the entire line can be skipped
|
||||
tmp.write(1); // don't skip any pixels
|
||||
tmp.write(-1); // end of line
|
||||
continue;
|
||||
}
|
||||
tmp.write(Math.min(255, skipCount + 1));
|
||||
if (skipCount > 254) {
|
||||
skipCount -= 254;
|
||||
while (skipCount > 254) {
|
||||
tmp.write(0); // Skip OP-code
|
||||
tmp.write(255);
|
||||
skipCount -= 254;
|
||||
}
|
||||
tmp.write(0); // Skip OP-code
|
||||
tmp.write(skipCount + 1);
|
||||
}
|
||||
|
||||
int literalCount = 0;
|
||||
int repeatCount = 0;
|
||||
for (; xy < xymax; ++xy) {
|
||||
// determine skip count
|
||||
for (skipCount = 0; xy < xymax; ++xy, ++skipCount) {
|
||||
if (data[xy] != prev[xy]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
xy -= skipCount;
|
||||
|
||||
// determine repeat count
|
||||
int v = data[xy];
|
||||
for (repeatCount = 0; xy < xymax && repeatCount < 127; ++xy, ++repeatCount) {
|
||||
if (data[xy] != v) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
xy -= repeatCount;
|
||||
|
||||
if (skipCount < 1 && xy + skipCount < xymax && repeatCount < 2) {
|
||||
literalCount++;
|
||||
if (literalCount == 127) {
|
||||
tmp.write(literalCount); // Literal OP-code
|
||||
tmp.writeInts24(data, xy - literalCount + 1, literalCount);
|
||||
literalCount = 0;
|
||||
}
|
||||
} else {
|
||||
if (literalCount > 0) {
|
||||
tmp.write(literalCount); // Literal OP-code
|
||||
tmp.writeInts24(data, xy - literalCount, literalCount);
|
||||
literalCount = 0;
|
||||
}
|
||||
if (xy + skipCount == xymax) {
|
||||
// => we can skip until the end of the line without
|
||||
// having to write an op-code
|
||||
xy += skipCount - 1;
|
||||
} else if (skipCount >= repeatCount) {
|
||||
while (skipCount > 254) {
|
||||
tmp.write(0); // Skip OP-code
|
||||
tmp.write(255);
|
||||
xy += 254;
|
||||
skipCount -= 254;
|
||||
}
|
||||
tmp.write(0); // Skip OP-code
|
||||
tmp.write(skipCount + 1);
|
||||
xy += skipCount - 1;
|
||||
} else {
|
||||
tmp.write(-repeatCount); // Repeat OP-code
|
||||
tmp.writeInt24(v);
|
||||
xy += repeatCount - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// flush literal run
|
||||
if (literalCount > 0) {
|
||||
tmp.write(literalCount);
|
||||
tmp.writeInts24(data, xy - literalCount, literalCount);
|
||||
literalCount = 0;
|
||||
}
|
||||
|
||||
tmp.write(-1);// End of line OP-code
|
||||
}
|
||||
|
||||
|
||||
// Complete the header
|
||||
long pos = tmpSeek.getStreamPosition();
|
||||
tmpSeek.seek(headerPos);
|
||||
tmp.writeInt((int) (pos - headerPos));
|
||||
tmpSeek.seek(pos);
|
||||
tmpSeek.toOutputStream(out);
|
||||
}
|
||||
|
||||
/** Encodes a 32-bit key frame.
|
||||
*
|
||||
* @param tmp The output stream. Must be set to Big-Endian.
|
||||
* @param data The image data.
|
||||
* @param offset The offset to the first pixel in the data array.
|
||||
* @param width The width of the image in data elements.
|
||||
* @param scanlineStride The number to add to offset to get to the next scanline.
|
||||
*/
|
||||
public void writeKey32(OutputStream out, int[] data, int width, int height, int offset, int scanlineStride)
|
||||
throws IOException {
|
||||
tmpSeek.reset();
|
||||
long headerPos = tmpSeek.getStreamPosition();
|
||||
|
||||
// Reserve space for the header:
|
||||
tmp.writeInt(0);
|
||||
tmp.writeShort(0x0000);
|
||||
|
||||
// Encode each scanline
|
||||
int ymax = offset + height * scanlineStride;
|
||||
for (int y = offset; y < ymax; y += scanlineStride) {
|
||||
int xy = y;
|
||||
int xymax = y + width;
|
||||
|
||||
tmp.write(1); // this is a key-frame, there is nothing to skip at the start of line
|
||||
|
||||
int literalCount = 0;
|
||||
int repeatCount = 0;
|
||||
for (; xy < xymax; ++xy) {
|
||||
// determine repeat count
|
||||
int v = data[xy];
|
||||
for (repeatCount = 0; xy < xymax && repeatCount < 127; ++xy, ++repeatCount) {
|
||||
if (data[xy] != v) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
xy -= repeatCount;
|
||||
|
||||
if (repeatCount < 2) {
|
||||
literalCount++;
|
||||
if (literalCount > 126) {
|
||||
tmp.write(literalCount); // Literal OP-code
|
||||
tmp.writeInts(data, xy - literalCount + 1, literalCount);
|
||||
literalCount = 0;
|
||||
}
|
||||
} else {
|
||||
if (literalCount > 0) {
|
||||
tmp.write(literalCount); // Literal OP-code
|
||||
tmp.writeInts(data, xy - literalCount, literalCount);
|
||||
literalCount = 0;
|
||||
}
|
||||
tmp.write(-repeatCount); // Repeat OP-code
|
||||
tmp.writeInt(v);
|
||||
xy += repeatCount - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// flush literal run
|
||||
if (literalCount > 0) {
|
||||
tmp.write(literalCount);
|
||||
tmp.writeInts(data, xy - literalCount, literalCount);
|
||||
literalCount = 0;
|
||||
}
|
||||
|
||||
tmp.write(-1);// End of line OP-code
|
||||
}
|
||||
|
||||
|
||||
// Complete the header
|
||||
long pos = tmpSeek.getStreamPosition();
|
||||
tmpSeek.seek(headerPos);
|
||||
tmp.writeInt((int) (pos - headerPos));
|
||||
tmpSeek.seek(pos);
|
||||
tmpSeek.toOutputStream(out);
|
||||
}
|
||||
|
||||
/** Encodes a 32-bit delta frame.
|
||||
*
|
||||
* @param tmp The output stream. Must be set to Big-Endian.
|
||||
* @param data The image data.
|
||||
* @param prev The image data of the previous frame.
|
||||
* @param offset The offset to the first pixel in the data array.
|
||||
* @param width The width of the image in data elements.
|
||||
* @param scanlineStride The number to add to offset to get to the next scanline.
|
||||
*/
|
||||
public void writeDelta32(OutputStream out, int[] data, int[] prev, int width, int height, int offset, int scanlineStride)
|
||||
throws IOException {
|
||||
tmpSeek.reset();
|
||||
|
||||
// Determine whether we can skip lines at the beginning
|
||||
int ymin;
|
||||
int ymax = offset + height * scanlineStride;
|
||||
scanline:
|
||||
for (ymin = offset; ymin < ymax; ymin += scanlineStride) {
|
||||
int xy = ymin;
|
||||
int xymax = ymin + width;
|
||||
for (; xy < xymax; ++xy) {
|
||||
if (data[xy] != prev[xy]) {
|
||||
break scanline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (ymin == ymax) {
|
||||
// => Frame is identical to previous one
|
||||
tmp.writeInt(4);
|
||||
tmpSeek.toOutputStream(out);
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine whether we can skip lines at the end
|
||||
scanline:
|
||||
for (; ymax > ymin; ymax -= scanlineStride) {
|
||||
int xy = ymax - scanlineStride;
|
||||
int xymax = ymax - scanlineStride + width;
|
||||
for (; xy < xymax; ++xy) {
|
||||
if (data[xy] != prev[xy]) {
|
||||
break scanline;
|
||||
}
|
||||
}
|
||||
}
|
||||
//System.out.println("AppleRLEEncoder ymin:" + ymin / step + " ymax" + ymax / step);
|
||||
|
||||
// Reserve space for the header
|
||||
long headerPos = tmpSeek.getStreamPosition();
|
||||
tmp.writeInt(0);
|
||||
|
||||
if (ymin == offset && ymax == offset + height * scanlineStride) {
|
||||
// => we can't skip any lines:
|
||||
tmp.writeShort(0x0000);
|
||||
} else {
|
||||
// => we can skip lines:
|
||||
tmp.writeShort(0x0008);
|
||||
tmp.writeShort(ymin / scanlineStride);
|
||||
tmp.writeShort(0);
|
||||
tmp.writeShort((ymax - ymin + 1) / scanlineStride);
|
||||
tmp.writeShort(0);
|
||||
}
|
||||
|
||||
|
||||
// Encode each scanline
|
||||
for (int y = ymin; y < ymax; y += scanlineStride) {
|
||||
int xy = y;
|
||||
int xymax = y + width;
|
||||
|
||||
// determine skip count
|
||||
int skipCount = 0;
|
||||
for (; xy < xymax; ++xy, ++skipCount) {
|
||||
if (data[xy] != prev[xy]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (skipCount == width) {
|
||||
// => the entire line can be skipped
|
||||
tmp.write(1); // don't skip any pixels
|
||||
tmp.write(-1); // end of line
|
||||
continue;
|
||||
}
|
||||
tmp.write(Math.min(255, skipCount + 1));
|
||||
if (skipCount > 254) {
|
||||
skipCount -= 254;
|
||||
while (skipCount > 254) {
|
||||
tmp.write(0); // Skip OP-code
|
||||
tmp.write(255);
|
||||
skipCount -= 254;
|
||||
}
|
||||
tmp.write(0); // Skip OP-code
|
||||
tmp.write(skipCount + 1);
|
||||
}
|
||||
|
||||
int literalCount = 0;
|
||||
int repeatCount = 0;
|
||||
for (; xy < xymax; ++xy) {
|
||||
// determine skip count
|
||||
for (skipCount = 0; xy < xymax; ++xy, ++skipCount) {
|
||||
if (data[xy] != prev[xy]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
xy -= skipCount;
|
||||
|
||||
// determine repeat count
|
||||
int v = data[xy];
|
||||
for (repeatCount = 0; xy < xymax && repeatCount < 127; ++xy, ++repeatCount) {
|
||||
if (data[xy] != v) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
xy -= repeatCount;
|
||||
|
||||
if (skipCount < 1 && xy + skipCount < xymax && repeatCount < 2) {
|
||||
literalCount++;
|
||||
if (literalCount == 127) {
|
||||
tmp.write(literalCount); // Literal OP-code
|
||||
tmp.writeInts(data, xy - literalCount + 1, literalCount);
|
||||
literalCount = 0;
|
||||
}
|
||||
} else {
|
||||
if (literalCount > 0) {
|
||||
tmp.write(literalCount); // Literal OP-code
|
||||
tmp.writeInts(data, xy - literalCount, literalCount);
|
||||
literalCount = 0;
|
||||
}
|
||||
if (xy + skipCount == xymax) {
|
||||
// => we can skip until the end of the line without
|
||||
// having to write an op-code
|
||||
xy += skipCount - 1;
|
||||
} else if (skipCount >= repeatCount) {
|
||||
while (skipCount > 254) {
|
||||
tmp.write(0); // Skip OP-code
|
||||
tmp.write(255);
|
||||
xy += 254;
|
||||
skipCount -= 254;
|
||||
}
|
||||
tmp.write(0); // Skip OP-code
|
||||
tmp.write(skipCount + 1);
|
||||
xy += skipCount - 1;
|
||||
} else {
|
||||
tmp.write(-repeatCount); // Repeat OP-code
|
||||
tmp.writeInt(v);
|
||||
xy += repeatCount - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// flush literal run
|
||||
if (literalCount > 0) {
|
||||
tmp.write(literalCount);
|
||||
tmp.writeInts(data, xy - literalCount, literalCount);
|
||||
literalCount = 0;
|
||||
}
|
||||
|
||||
tmp.write(-1);// End of line OP-code
|
||||
}
|
||||
|
||||
|
||||
// Complete the header
|
||||
long pos = tmpSeek.getStreamPosition();
|
||||
tmpSeek.seek(headerPos);
|
||||
tmp.writeInt((int) (pos - headerPos));
|
||||
tmpSeek.seek(pos);
|
||||
tmpSeek.toOutputStream(out);
|
||||
}
|
||||
/*
|
||||
public static void main(String[] args) {
|
||||
BufferedImage img=new BufferedImage(640,400,BufferedImage.TYPE_INT_RGB);
|
||||
BufferedImage subImg = img.getSubimage(10, 20, 320, 200);
|
||||
BufferedImage subsubImg = subImg.getSubimage(10, 10, 160, 100);
|
||||
Graphics2D g = img.createGraphics();
|
||||
g.setBackground(Color.WHITE);
|
||||
g.clearRect(0, 0, 800, 600);
|
||||
g.setColor(Color.BLUE);
|
||||
g.drawRect(0, 0, 320-1, 400-1);
|
||||
g.setColor(Color.RED);
|
||||
g.drawRect(10, 20, 320-1, 200-1);
|
||||
QuickTimeWriter qtr=null;
|
||||
try {
|
||||
qtr = new QuickTimeWriter(new File("RLE SubImg.mov"));
|
||||
qtr.addVideoTrack("rle ","Animation", 600, 160, 100,24,30);
|
||||
qtr.writeFrame(0, subsubImg, 100);
|
||||
g.setColor(Color.GREEN);
|
||||
g.drawRect(20,30, 160-1, 100-1);
|
||||
qtr.writeFrame(0, subsubImg, 100);
|
||||
qtr.close();
|
||||
qtr=null;
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
} finally {
|
||||
if (qtr!=null) {
|
||||
try {
|
||||
qtr.close();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
g.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
public static void main1(String[] args) {
|
||||
int w = 800, h = 600;
|
||||
int[] data = new int[w * h];
|
||||
int[] prev = new int[w * h];
|
||||
|
||||
Random rnd = new Random();
|
||||
int repeat = 0;
|
||||
for (int i = 0; i < data.length; ++i) {
|
||||
if (--repeat > 0) {
|
||||
data[i] = data[i - 1];
|
||||
} else {
|
||||
repeat = rnd.nextInt(10);
|
||||
data[i] = rnd.nextInt(1 << 24);
|
||||
}
|
||||
}
|
||||
ArrayIndexOutOfBoundsException ai;
|
||||
|
||||
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
||||
AppleRLEEncoder enc = new AppleRLEEncoder();
|
||||
try {
|
||||
enc.writeKey24(buf, data, w-2, h, 1, w);
|
||||
buf.close();
|
||||
byte[] result = buf.toByteArray();
|
||||
int fullSize=(w-2)*h*3;
|
||||
System.out.println("full size:" + fullSize);
|
||||
System.out.println("compressed size:" + result.length);
|
||||
System.out.println("compression percentage:" + (100 * (result.length / (float) fullSize)));
|
||||
/* System.out.println(Arrays.toString(result));
|
||||
|
||||
System.out.print("0x [");
|
||||
for (int i = 0; i < result.length; ++i) {
|
||||
if (i != 0) {
|
||||
System.out.print(',');
|
||||
}
|
||||
String hex = "00" + Integer.toHexString(result[i]);
|
||||
System.out.print(hex.substring(hex.length() - 2));
|
||||
}
|
||||
System.out.println(']');
|
||||
* /
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void main2(String[] args) {
|
||||
short[] data = {//
|
||||
8, 1, 1, 1, 1, 2, 8,//
|
||||
8, 0, 2, 0, 0, 0, 8,//
|
||||
8, 2, 3, 4, 4, 3, 8,//
|
||||
8, 2, 2, 3, 4, 4, 8,//
|
||||
8, 1, 4, 4, 4, 5, 8};
|
||||
short[] prev = {//
|
||||
8, 1, 1, 1, 1, 1, 8, //
|
||||
8, 5, 5, 5, 5, 0, 8,//
|
||||
8, 3, 3, 3, 3, 3, 8,//
|
||||
8, 2, 2, 0, 0, 0, 8,//
|
||||
8, 2, 0, 0, 0, 5, 8};
|
||||
|
||||
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
||||
AppleRLEEncoder enc = new AppleRLEEncoder();
|
||||
try {
|
||||
// enc.writeDelta16(buf, data, prev, 1, 5, 7);
|
||||
enc.writeKey16(buf, data, 5, 5, 1, 7);
|
||||
buf.close();
|
||||
byte[] result = buf.toByteArray();
|
||||
System.out.println("size:" + result.length);
|
||||
System.out.println(Arrays.toString(result));
|
||||
|
||||
System.out.print("0x [");
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
if (i != 0) {
|
||||
System.out.print(',');
|
||||
}
|
||||
String hex = "00" + Integer.toHexString(result[i]);
|
||||
System.out.print(hex.substring(hex.length() - 2));
|
||||
}
|
||||
System.out.println(']');
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
/**
|
||||
* @(#)DataAtomOutputStream.java 1.3 2011-01-07
|
||||
*
|
||||
* Copyright (c) 2008-2011 Werner Randelshofer, Immensee, Switzerland.
|
||||
* All rights reserved.
|
||||
*
|
||||
* You may not use, copy or modify this file, except in compliance with the
|
||||
* license agreement you entered into with Werner Randelshofer.
|
||||
* For details see accompanying license terms.
|
||||
*/
|
||||
package ch.randelshofer.media.quicktime;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import javax.imageio.stream.ImageOutputStreamImpl;
|
||||
|
||||
/**
|
||||
* This output stream filter supports common data types used inside
|
||||
* of QuickTime Data Atoms.
|
||||
*
|
||||
* @author Werner Randelshofer
|
||||
* @version 1.3 2011-01-07 Adds writeShort/s and writeInt24/s methods.
|
||||
* <br>1.2.1 2010-10-03 Fixes writing of empty P-Strings.
|
||||
* <br>1.2 2009-08-29 Added writePString(String, int) method.
|
||||
* <br>1.1 2008-08-11 Streamlined API and source code with AVI DataChunkOutputStream.
|
||||
* <br>1.0.1 2008-06-22 Use ASCII instead of MacRoman for encoding
|
||||
* type strings.
|
||||
* <br>1.0 Jun 15, 2008 Created.
|
||||
*/
|
||||
public class DataAtomOutputStream extends FilterOutputStream {
|
||||
|
||||
ImageOutputStreamImpl impl;
|
||||
protected static final long MAC_TIMESTAMP_EPOCH = new GregorianCalendar(1904, GregorianCalendar.JANUARY, 1).getTimeInMillis();
|
||||
/**
|
||||
* The number of bytes written to the data output stream so far.
|
||||
* If this counter overflows, it will be wrapped to Integer.MAX_VALUE.
|
||||
*/
|
||||
protected long written;
|
||||
|
||||
public DataAtomOutputStream(OutputStream out) {
|
||||
super(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an Atom Type identifier (4 bytes).
|
||||
* @param s A string with a length of 4 characters.
|
||||
*/
|
||||
public void writeType(String s) throws IOException {
|
||||
if (s.length() != 4) {
|
||||
throw new IllegalArgumentException("type string must have 4 characters");
|
||||
}
|
||||
|
||||
try {
|
||||
out.write(s.getBytes("ASCII"), 0, 4);
|
||||
incCount(4);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new InternalError(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes out a <code>byte</code> to the underlying output stream as
|
||||
* a 1-byte value. If no exception is thrown, the counter
|
||||
* <code>written</code> is incremented by <code>1</code>.
|
||||
*
|
||||
* @param v a <code>byte</code> value to be written.
|
||||
* @exception IOException if an I/O error occurs.
|
||||
* @see java.io.FilterOutputStream#out
|
||||
*/
|
||||
public final void writeByte(int v) throws IOException {
|
||||
out.write(v);
|
||||
incCount(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes <code>len</code> bytes from the specified byte array
|
||||
* starting at offset <code>off</code> to the underlying output stream.
|
||||
* If no exception is thrown, the counter <code>written</code> is
|
||||
* incremented by <code>len</code>.
|
||||
*
|
||||
* @param b the data.
|
||||
* @param off the start offset in the data.
|
||||
* @param len the number of bytes to write.
|
||||
* @exception IOException if an I/O error occurs.
|
||||
* @see java.io.FilterOutputStream#out
|
||||
*/
|
||||
@Override
|
||||
public synchronized void write(byte b[], int off, int len)
|
||||
throws IOException {
|
||||
out.write(b, off, len);
|
||||
incCount(len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the specified byte (the low eight bits of the argument
|
||||
* <code>b</code>) to the underlying output stream. If no exception
|
||||
* is thrown, the counter <code>written</code> is incremented by
|
||||
* <code>1</code>.
|
||||
* <p>
|
||||
* Implements the <code>write</code> method of <code>OutputStream</code>.
|
||||
*
|
||||
* @param b the <code>byte</code> to be written.
|
||||
* @exception IOException if an I/O error occurs.
|
||||
* @see java.io.FilterOutputStream#out
|
||||
*/
|
||||
@Override
|
||||
public synchronized void write(int b) throws IOException {
|
||||
out.write(b);
|
||||
incCount(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an <code>int</code> to the underlying output stream as four
|
||||
* bytes, high byte first. If no exception is thrown, the counter
|
||||
* <code>written</code> is incremented by <code>4</code>.
|
||||
*
|
||||
* @param v an <code>int</code> to be written.
|
||||
* @exception IOException if an I/O error occurs.
|
||||
* @see java.io.FilterOutputStream#out
|
||||
*/
|
||||
public void writeInt(int v) throws IOException {
|
||||
out.write((v >>> 24) & 0xff);
|
||||
out.write((v >>> 16) & 0xff);
|
||||
out.write((v >>> 8) & 0xff);
|
||||
out.write((v >>> 0) & 0xff);
|
||||
incCount(4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an unsigned 32 bit integer value.
|
||||
*
|
||||
* @param v The value
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public void writeUInt(long v) throws IOException {
|
||||
out.write((int) ((v >>> 24) & 0xff));
|
||||
out.write((int) ((v >>> 16) & 0xff));
|
||||
out.write((int) ((v >>> 8) & 0xff));
|
||||
out.write((int) ((v >>> 0) & 0xff));
|
||||
incCount(4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a signed 16 bit integer value.
|
||||
*
|
||||
* @param v The value
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public void writeShort(int v) throws IOException {
|
||||
out.write((v >> 8) & 0xff);
|
||||
out.write((v >>> 0) & 0xff);
|
||||
incCount(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a <code>BCD2</code> to the underlying output stream.
|
||||
*
|
||||
* @param v an <code>int</code> to be written.
|
||||
* @exception IOException if an I/O error occurs.
|
||||
* @see java.io.FilterOutputStream#out
|
||||
*/
|
||||
public void writeBCD2(int v) throws IOException {
|
||||
out.write(((v % 100 / 10) << 4) | (v % 10));
|
||||
incCount(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a <code>BCD4</code> to the underlying output stream.
|
||||
*
|
||||
* @param v an <code>int</code> to be written.
|
||||
* @exception IOException if an I/O error occurs.
|
||||
* @see java.io.FilterOutputStream#out
|
||||
*/
|
||||
public void writeBCD4(int v) throws IOException {
|
||||
out.write(((v % 10000 / 1000) << 4) | (v % 1000 / 100));
|
||||
out.write(((v % 100 / 10) << 4) | (v % 10));
|
||||
incCount(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a 32-bit Mac timestamp (seconds since 1902).
|
||||
* @param date
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public void writeMacTimestamp(Date date) throws IOException {
|
||||
long millis = date.getTime();
|
||||
long qtMillis = millis - MAC_TIMESTAMP_EPOCH;
|
||||
long qtSeconds = qtMillis / 1000;
|
||||
writeUInt(qtSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes 32-bit fixed-point number divided as 16.16.
|
||||
*
|
||||
* @param f an <code>int</code> to be written.
|
||||
* @exception IOException if an I/O error occurs.
|
||||
* @see java.io.FilterOutputStream#out
|
||||
*/
|
||||
public void writeFixed16D16(double f) throws IOException {
|
||||
double v = (f >= 0) ? f : -f;
|
||||
|
||||
int wholePart = (int) Math.floor(v);
|
||||
int fractionPart = (int) ((v - wholePart) * 65536);
|
||||
int t = (wholePart << 16) + fractionPart;
|
||||
|
||||
if (f < 0) {
|
||||
t = t - 1;
|
||||
}
|
||||
writeInt(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes 32-bit fixed-point number divided as 2.30.
|
||||
*
|
||||
* @param f an <code>int</code> to be written.
|
||||
* @exception IOException if an I/O error occurs.
|
||||
* @see java.io.FilterOutputStream#out
|
||||
*/
|
||||
public void writeFixed2D30(double f) throws IOException {
|
||||
double v = (f >= 0) ? f : -f;
|
||||
|
||||
int wholePart = (int) v;
|
||||
int fractionPart = (int) ((v - wholePart) * 1073741824);
|
||||
int t = (wholePart << 30) + fractionPart;
|
||||
|
||||
if (f < 0) {
|
||||
t = t - 1;
|
||||
}
|
||||
writeInt(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes 16-bit fixed-point number divided as 8.8.
|
||||
*
|
||||
* @param f an <code>int</code> to be written.
|
||||
* @exception IOException if an I/O error occurs.
|
||||
* @see java.io.FilterOutputStream#out
|
||||
*/
|
||||
public void writeFixed8D8(float f) throws IOException {
|
||||
float v = (f >= 0) ? f : -f;
|
||||
|
||||
int wholePart = (int) v;
|
||||
int fractionPart = (int) ((v - wholePart) * 256);
|
||||
int t = (wholePart << 8) + fractionPart;
|
||||
|
||||
if (f < 0) {
|
||||
t = t - 1;
|
||||
}
|
||||
writeUShort(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a Pascal String.
|
||||
*
|
||||
* @param s
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public void writePString(String s) throws IOException {
|
||||
if (s.length() > 0xffff) {
|
||||
throw new IllegalArgumentException("String too long for PString");
|
||||
}
|
||||
if (s.length() != 0 && s.length() < 256) {
|
||||
out.write(s.length());
|
||||
} else {
|
||||
out.write(0);
|
||||
writeShort(s.length()); // increments +2
|
||||
}
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
out.write(s.charAt(i));
|
||||
}
|
||||
incCount(1 + s.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a Pascal String padded to the specified fixed size in bytes
|
||||
*
|
||||
* @param s
|
||||
* @param length the fixed size in bytes
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public void writePString(String s, int length) throws IOException {
|
||||
if (s.length() > length) {
|
||||
throw new IllegalArgumentException("String too long for PString of length " + length);
|
||||
}
|
||||
if (s.length() != 0 && s.length() < 256) {
|
||||
out.write(s.length());
|
||||
} else {
|
||||
out.write(0);
|
||||
writeShort(s.length()); // increments +2
|
||||
}
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
out.write(s.charAt(i));
|
||||
}
|
||||
|
||||
// write pad bytes
|
||||
for (int i = 1 + s.length(); i < length; i++) {
|
||||
out.write(0);
|
||||
}
|
||||
|
||||
incCount(length);
|
||||
}
|
||||
|
||||
public void writeLong(long v) throws IOException {
|
||||
out.write((int) (v >>> 56) & 0xff);
|
||||
out.write((int) (v >>> 48) & 0xff);
|
||||
out.write((int) (v >>> 40) & 0xff);
|
||||
out.write((int) (v >>> 32) & 0xff);
|
||||
out.write((int) (v >>> 24) & 0xff);
|
||||
out.write((int) (v >>> 16) & 0xff);
|
||||
out.write((int) (v >>> 8) & 0xff);
|
||||
out.write((int) (v >>> 0) & 0xff);
|
||||
incCount(8);
|
||||
}
|
||||
|
||||
public void writeUShort(int v) throws IOException {
|
||||
out.write((v >> 8) & 0xff);
|
||||
out.write((v >>> 0) & 0xff);
|
||||
incCount(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases the written counter by the specified value
|
||||
* until it reaches Long.MAX_VALUE.
|
||||
*/
|
||||
protected void incCount(int value) {
|
||||
long temp = written + value;
|
||||
if (temp < 0) {
|
||||
temp = Long.MAX_VALUE;
|
||||
}
|
||||
written = temp;
|
||||
}
|
||||
|
||||
public void writeShorts(short[] s, int off, int len) throws IOException {
|
||||
// Fix 4430357 - if off + len < 0, overflow occurred
|
||||
if (off < 0 || len < 0 || off + len > s.length || off + len < 0) {
|
||||
throw new IndexOutOfBoundsException("off < 0 || len < 0 || off + len > s.length!");
|
||||
}
|
||||
|
||||
byte[] b = new byte[len * 2];
|
||||
int boff = 0;
|
||||
for (int i = 0; i < len; i++) {
|
||||
short v = s[off + i];
|
||||
b[boff++] = (byte) (v >>> 8);
|
||||
b[boff++] = (byte) (v >>> 0);
|
||||
}
|
||||
|
||||
write(b, 0, len * 2);
|
||||
}
|
||||
|
||||
public void writeInts(int[] i, int off, int len) throws IOException {
|
||||
// Fix 4430357 - if off + len < 0, overflow occurred
|
||||
if (off < 0 || len < 0 || off + len > i.length || off + len < 0) {
|
||||
throw new IndexOutOfBoundsException("off < 0 || len < 0 || off + len > i.length!");
|
||||
}
|
||||
|
||||
byte[] b = new byte[len * 4];
|
||||
int boff = 0;
|
||||
for (int j = 0; j < len; j++) {
|
||||
int v = i[off + j];
|
||||
b[boff++] = (byte) (v >>> 24);
|
||||
b[boff++] = (byte) (v >>> 16);
|
||||
b[boff++] = (byte) (v >>> 8);
|
||||
b[boff++] = (byte) (v >>> 0);
|
||||
}
|
||||
|
||||
write(b, 0, len * 4);
|
||||
}
|
||||
private byte[] byteBuf = new byte[3];
|
||||
|
||||
public void writeInt24(int v) throws IOException {
|
||||
byteBuf[0] = (byte) (v >>> 16);
|
||||
byteBuf[1] = (byte) (v >>> 8);
|
||||
byteBuf[2] = (byte) (v >>> 0);
|
||||
write(byteBuf, 0, 3);
|
||||
}
|
||||
|
||||
public void writeInts24(int[] i, int off, int len) throws IOException {
|
||||
// Fix 4430357 - if off + len < 0, overflow occurred
|
||||
if (off < 0 || len < 0 || off + len > i.length || off + len < 0) {
|
||||
throw new IndexOutOfBoundsException("off < 0 || len < 0 || off + len > i.length!");
|
||||
}
|
||||
|
||||
byte[] b = new byte[len * 3];
|
||||
int boff = 0;
|
||||
for (int j = 0; j < len; j++) {
|
||||
int v = i[off + j];
|
||||
//b[boff++] = (byte)(v >>> 24);
|
||||
b[boff++] = (byte) (v >>> 16);
|
||||
b[boff++] = (byte) (v >>> 8);
|
||||
b[boff++] = (byte) (v >>> 0);
|
||||
}
|
||||
|
||||
write(b, 0, len * 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current value of the counter <code>written</code>,
|
||||
* the number of bytes written to this data output stream so far.
|
||||
* If the counter overflows, it will be wrapped to Integer.MAX_VALUE.
|
||||
*
|
||||
* @return the value of the <code>written</code> field.
|
||||
* @see java.io.DataOutputStream#written
|
||||
*/
|
||||
public final long size() {
|
||||
return written;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,243 @@
|
||||
package processing.app.tools;
|
||||
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.FileDialog;
|
||||
import java.awt.Frame;
|
||||
import java.io.File;
|
||||
|
||||
import javax.swing.JFileChooser;
|
||||
|
||||
|
||||
/** File chooser additions, cannibalized from PApplet. */
|
||||
class Chooser {
|
||||
static final boolean useNativeSelect = true;
|
||||
|
||||
|
||||
static abstract class Callback {
|
||||
//abstract void select(File file);
|
||||
void handle(final File file) {
|
||||
EventQueue.invokeLater(new Runnable() {
|
||||
// new Thread(new Runnable() {
|
||||
public void run() {
|
||||
select(file);
|
||||
}
|
||||
});
|
||||
// }).start();
|
||||
}
|
||||
|
||||
abstract void select(File file);
|
||||
}
|
||||
|
||||
|
||||
// Frame parent;
|
||||
//
|
||||
// public Chooser(Frame parent) {
|
||||
// this.parent = parent;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Open a platform-specific file chooser dialog to select a file for input.
|
||||
* After the selection is made, the selected File will be passed to the
|
||||
* 'callback' function. If the dialog is closed or canceled, null will be
|
||||
* sent to the function, so that the program is not waiting for additional
|
||||
* input. The callback is necessary because of how threading works.
|
||||
*
|
||||
* <pre>
|
||||
* void setup() {
|
||||
* selectInput("Select a file to process:", "fileSelected");
|
||||
* }
|
||||
*
|
||||
* void fileSelected(File selection) {
|
||||
* if (selection == null) {
|
||||
* println("Window was closed or the user hit cancel.");
|
||||
* } else {
|
||||
* println("User selected " + fileSeleted.getAbsolutePath());
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* For advanced users, the method must be 'public', which is true for all
|
||||
* methods inside a sketch when run from the PDE, but must explicitly be
|
||||
* set when using Eclipse or other development environments.
|
||||
*
|
||||
* @webref input:files
|
||||
* @param prompt message to the user
|
||||
* @param callback name of the method to be called when the selection is made
|
||||
*/
|
||||
// public void selectInput(String prompt, String callback) {
|
||||
// selectInput(prompt, callback, null);
|
||||
// }
|
||||
|
||||
|
||||
// public void selectInput(String prompt, String callback, File file) {
|
||||
// selectInput(prompt, callback, file, this);
|
||||
// }
|
||||
|
||||
|
||||
// public void selectInput(String prompt, String callback,
|
||||
// File file, Object callbackObject) {
|
||||
// selectInput(prompt, callback, file, callbackObject, selectFrame());
|
||||
// }
|
||||
|
||||
|
||||
static public void selectInput(Frame parent, String prompt, File file,
|
||||
Callback callback) {
|
||||
selectImpl(parent, prompt, file, callback, FileDialog.LOAD);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* See selectInput() for details.
|
||||
*
|
||||
* @webref output:files
|
||||
* @param prompt message to the user
|
||||
* @param callback name of the method to be called when the selection is made
|
||||
*/
|
||||
// public void selectOutput(String prompt, String callback) {
|
||||
// selectOutput(prompt, callback, null);
|
||||
// }
|
||||
//
|
||||
// public void selectOutput(String prompt, String callback, File file) {
|
||||
// selectOutput(prompt, callback, file, this);
|
||||
// }
|
||||
//
|
||||
//
|
||||
// public void selectOutput(String prompt, String callback,
|
||||
// File file, Object callbackObject) {
|
||||
// selectOutput(prompt, callback, file, callbackObject, selectFrame());
|
||||
// }
|
||||
|
||||
|
||||
static public void selectOutput(Frame parent, String prompt, File file,
|
||||
Callback callback) {
|
||||
selectImpl(parent, prompt, file, callback, FileDialog.SAVE);
|
||||
}
|
||||
|
||||
|
||||
static protected void selectImpl(final Frame parentFrame,
|
||||
final String prompt,
|
||||
final File defaultSelection,
|
||||
final Callback callback,
|
||||
final int mode) {
|
||||
// EventQueue.invokeLater(new Runnable() {
|
||||
// public void run() {
|
||||
File selectedFile = null;
|
||||
|
||||
if (useNativeSelect) {
|
||||
FileDialog dialog = new FileDialog(parentFrame, prompt, mode);
|
||||
if (defaultSelection != null) {
|
||||
dialog.setDirectory(defaultSelection.getParent());
|
||||
dialog.setFile(defaultSelection.getName());
|
||||
}
|
||||
dialog.setVisible(true);
|
||||
String directory = dialog.getDirectory();
|
||||
String filename = dialog.getFile();
|
||||
if (filename != null) {
|
||||
selectedFile = new File(directory, filename);
|
||||
}
|
||||
|
||||
} else {
|
||||
JFileChooser chooser = new JFileChooser();
|
||||
chooser.setDialogTitle(prompt);
|
||||
if (defaultSelection != null) {
|
||||
chooser.setSelectedFile(defaultSelection);
|
||||
}
|
||||
|
||||
int result = -1;
|
||||
if (mode == FileDialog.SAVE) {
|
||||
result = chooser.showSaveDialog(parentFrame);
|
||||
} else if (mode == FileDialog.LOAD) {
|
||||
result = chooser.showOpenDialog(parentFrame);
|
||||
}
|
||||
if (result == JFileChooser.APPROVE_OPTION) {
|
||||
selectedFile = chooser.getSelectedFile();
|
||||
}
|
||||
}
|
||||
//selectCallback(selectedFile, callbackMethod, callbackObject);
|
||||
callback.handle(selectedFile);
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* See selectInput() for details.
|
||||
*
|
||||
* @webref input:files
|
||||
* @param prompt message to the user
|
||||
* @param callback name of the method to be called when the selection is made
|
||||
*/
|
||||
// public void selectFolder(String prompt, String callback) {
|
||||
// selectFolder(prompt, callback, null);
|
||||
// }
|
||||
//
|
||||
//
|
||||
// public void selectFolder(String prompt, String callback, File file) {
|
||||
// selectFolder(prompt, callback, file, this);
|
||||
// }
|
||||
//
|
||||
//
|
||||
// public void selectFolder(String prompt, String callback,
|
||||
// File file, Object callbackObject) {
|
||||
// selectFolder(prompt, callback, file, callbackObject, selectFrame());
|
||||
// }
|
||||
|
||||
|
||||
static public void selectFolder(final Frame parentFrame,
|
||||
final String prompt,
|
||||
final File defaultSelection,
|
||||
final Callback callback) {
|
||||
// EventQueue.invokeLater(new Runnable() {
|
||||
// public void run() {
|
||||
File selectedFile = null;
|
||||
|
||||
if (System.getProperty("os.name").contains("Mac") && useNativeSelect) {
|
||||
FileDialog fileDialog =
|
||||
new FileDialog(parentFrame, prompt, FileDialog.LOAD);
|
||||
System.setProperty("apple.awt.fileDialogForDirectories", "true");
|
||||
fileDialog.setVisible(true);
|
||||
System.setProperty("apple.awt.fileDialogForDirectories", "false");
|
||||
String filename = fileDialog.getFile();
|
||||
if (filename != null) {
|
||||
selectedFile = new File(fileDialog.getDirectory(), fileDialog.getFile());
|
||||
}
|
||||
} else {
|
||||
JFileChooser fileChooser = new JFileChooser();
|
||||
fileChooser.setDialogTitle(prompt);
|
||||
fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
|
||||
if (defaultSelection != null) {
|
||||
fileChooser.setSelectedFile(defaultSelection);
|
||||
}
|
||||
|
||||
int result = fileChooser.showOpenDialog(parentFrame);
|
||||
if (result == JFileChooser.APPROVE_OPTION) {
|
||||
selectedFile = fileChooser.getSelectedFile();
|
||||
}
|
||||
}
|
||||
//selectCallback(selectedFile, callbackMethod, callbackObject);
|
||||
callback.handle(selectedFile);
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
|
||||
// static private void selectCallback(File selectedFile,
|
||||
// String callbackMethod,
|
||||
// Object callbackObject) {
|
||||
// try {
|
||||
// Class<?> callbackClass = callbackObject.getClass();
|
||||
// Method selectMethod =
|
||||
// callbackClass.getMethod(callbackMethod, new Class[] { File.class });
|
||||
// selectMethod.invoke(callbackObject, new Object[] { selectedFile });
|
||||
//
|
||||
// } catch (IllegalAccessException iae) {
|
||||
// System.err.println(callbackMethod + "() must be public");
|
||||
//
|
||||
// } catch (InvocationTargetException ite) {
|
||||
// ite.printStackTrace();
|
||||
//
|
||||
// } catch (NoSuchMethodException nsme) {
|
||||
// System.err.println(callbackMethod + "() could not be found");
|
||||
// }
|
||||
// }
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user