mirror of
https://github.com/Tooloop/Tooloop-Packages.git
synced 2026-04-28 04:51:38 +02:00
change folder structure and control files to newest concept
This commit is contained in:
+42
@@ -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;
|
||||
}
|
||||
}
|
||||
+75
@@ -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);
|
||||
}
|
||||
}
|
||||
+363
@@ -0,0 +1,363 @@
|
||||
/*
|
||||
* @(#)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 | 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 | BadLocationException | 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;
|
||||
}
|
||||
}
|
||||
|
||||
+72
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+67
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+143
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+153
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+149
@@ -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");
|
||||
}
|
||||
}
|
||||
+546
@@ -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;
|
||||
}
|
||||
}
|
||||
+997
@@ -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();
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
+408
@@ -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;
|
||||
}
|
||||
}
|
||||
+3387
File diff suppressed because it is too large
Load Diff
+243
@@ -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");
|
||||
// }
|
||||
// }
|
||||
}
|
||||
+1234
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user